зеркало из https://github.com/mono/nuget.git
Add unit test for issue 4050 (push to server does not follow redirection).
This commit is contained in:
Родитель
2cda345ffd
Коммит
e3b351c4b1
|
@ -0,0 +1,268 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NuGet.Test.Integration
|
||||
{
|
||||
/// <summary>
|
||||
/// A Mock Server that is used to mimic a NuGet Server.
|
||||
/// </summary>
|
||||
class MockServer
|
||||
{
|
||||
HttpListener _listener;
|
||||
RouteTable _get, _put;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of MockServer.
|
||||
/// </summary>
|
||||
/// <param name="endPoint">The endpoint of the server.</param>
|
||||
public MockServer(string endPoint)
|
||||
{
|
||||
_listener = new HttpListener();
|
||||
_listener.Prefixes.Add(endPoint);
|
||||
|
||||
_get = new RouteTable();
|
||||
_put = new RouteTable();
|
||||
}
|
||||
|
||||
public RouteTable Get
|
||||
{
|
||||
get { return _get; }
|
||||
}
|
||||
|
||||
public RouteTable Put
|
||||
{
|
||||
get { return _put; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the mock server.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
_listener.Start();
|
||||
Task.Factory.StartNew(() => HandleRequest());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the mock server.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_listener.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pushed package from a nuget push request.
|
||||
/// </summary>
|
||||
/// <param name="r">The request generated by nuget push command.</param>
|
||||
/// <returns>The content of the package that is pushed.</returns>
|
||||
public static byte[] GetPushedPackage(HttpListenerRequest r)
|
||||
{
|
||||
byte[] buffer;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
r.InputStream.CopyTo(memoryStream);
|
||||
buffer = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
byte[] result = new byte[] { };
|
||||
var multipartContentType = "multipart/form-data; boundary=";
|
||||
if (!r.ContentType.StartsWith(multipartContentType, StringComparison.Ordinal))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var boundary = r.ContentType.Substring(multipartContentType.Length);
|
||||
byte[] delimiter = Encoding.UTF8.GetBytes("\r\n--" + boundary);
|
||||
int bodyStartIndex = Find(buffer, 0, new byte[] { 0x0d, 0x0a, 0x0d, 0x0a });
|
||||
if (bodyStartIndex == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyStartIndex += 4;
|
||||
}
|
||||
|
||||
int bodyEndIndex = Find(buffer, 0, delimiter);
|
||||
if (bodyEndIndex == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = buffer.Skip(bodyStartIndex).Take(bodyEndIndex - bodyStartIndex).ToArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the first occurrence of <paramref name="pattern"/> in
|
||||
/// <paramref name="buffer"/>. The search starts at a specified position.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to search.</param>
|
||||
/// <param name="startIndex">The search start position.</param>
|
||||
/// <param name="pattern">The pattern to search.</param>
|
||||
/// <returns>The index position of <paramref name="pattern"/> if it is found in buffer, or -1
|
||||
/// if not.</returns>
|
||||
private static int Find(byte[] buffer, int startIndex, byte[] pattern)
|
||||
{
|
||||
for (int s = startIndex; s + pattern.Length <= buffer.Length; ++s)
|
||||
{
|
||||
if (StartsWith(buffer, s, pattern))
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the subset of <paramref name="buffer"/> starting at
|
||||
/// <paramref name="startIndex"/> starts with <paramref name="pattern"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to check.</param>
|
||||
/// <param name="startIndex">The start index of the subset to check.</param>
|
||||
/// <param name="pattern">The pattern to search.</param>
|
||||
/// <returns>True if the subset starts with the pattern; otherwise, false.</returns>
|
||||
private static bool StartsWith(byte[] buffer, int startIndex, byte[] pattern)
|
||||
{
|
||||
if (startIndex + pattern.Length > buffer.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pattern.Length; ++i)
|
||||
{
|
||||
if (buffer[startIndex + i] != pattern[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SetResponseContent(HttpListenerResponse response, string text)
|
||||
{
|
||||
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(text);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
void SetResponseNotFound(HttpListenerResponse response)
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
SetResponseContent(response, "404 not found");
|
||||
}
|
||||
|
||||
void GenerateResponse(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
HttpListenerResponse response = context.Response;
|
||||
try
|
||||
{
|
||||
RouteTable m = null;
|
||||
if (request.HttpMethod == "GET")
|
||||
{
|
||||
m = _get;
|
||||
}
|
||||
else if (request.HttpMethod == "PUT")
|
||||
{
|
||||
m = _put;
|
||||
}
|
||||
|
||||
if (m == null)
|
||||
{
|
||||
SetResponseNotFound(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
var f = m.Match(request);
|
||||
if (f != null)
|
||||
{
|
||||
var r = f(request);
|
||||
if (r is string)
|
||||
{
|
||||
SetResponseContent(response, (string)r);
|
||||
}
|
||||
else if (r is Action<HttpListenerResponse>)
|
||||
{
|
||||
var action = (Action<HttpListenerResponse>)r;
|
||||
action(response);
|
||||
}
|
||||
else if (r is int || r is HttpStatusCode)
|
||||
{
|
||||
response.StatusCode = (int)r;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetResponseNotFound(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.OutputStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRequest()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = _listener.GetContext();
|
||||
GenerateResponse(context);
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the route table of the mock server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return type of a request handler could be:
|
||||
/// - string: the string will be sent back as the response content, and the response
|
||||
/// status code is OK.
|
||||
/// - HttpStatusCode: the value is returned as the response status code.
|
||||
/// - Action<HttpListenerResponse>: The action will be called to construct the response.
|
||||
/// </remarks>
|
||||
class RouteTable
|
||||
{
|
||||
List<Tuple<string, Func<HttpListenerRequest, object>>> _mappings;
|
||||
|
||||
public RouteTable()
|
||||
{
|
||||
_mappings = new List<Tuple<string, Func<HttpListenerRequest, object>>>();
|
||||
}
|
||||
|
||||
public void Add(string pattern, Func<HttpListenerRequest, object> f)
|
||||
{
|
||||
_mappings.Add(new Tuple<string, Func<HttpListenerRequest, object>>(pattern, f));
|
||||
}
|
||||
|
||||
public Func<HttpListenerRequest, object> Match(HttpListenerRequest r)
|
||||
{
|
||||
foreach (var m in _mappings)
|
||||
{
|
||||
if (r.Url.AbsolutePath.StartsWith(m.Item1, StringComparison.Ordinal))
|
||||
{
|
||||
return m.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace NuGet.Test.Integration.NuGetCommandLine
|
||||
{
|
||||
|
@ -116,5 +114,217 @@ namespace NuGet.Test.Integration.NuGetCommandLine
|
|||
Util.DeleteDirectory(source);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests pushing to an http source
|
||||
[Fact]
|
||||
public void PushCommand_PushToServer()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var packageDirectory = Path.Combine(tempPath, Guid.NewGuid().ToString());
|
||||
var mockServerEndPoint = "http://localhost:1234/";
|
||||
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
Util.CreateDirectory(packageDirectory);
|
||||
var packageFileName = Util.CreateTestPackage("testPackage1", "1.1.0", packageDirectory);
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
TextWriter writer = new StreamWriter(memoryStream);
|
||||
Console.SetOut(writer);
|
||||
string outputFileName = Path.Combine(packageDirectory, "t1.nupkg");
|
||||
|
||||
var server = new MockServer(mockServerEndPoint);
|
||||
server.Get.Add("/push", r => "OK");
|
||||
server.Put.Add("/push", r =>
|
||||
{
|
||||
byte[] buffer = MockServer.GetPushedPackage(r);
|
||||
using (var of = new FileStream(outputFileName, FileMode.Create))
|
||||
{
|
||||
of.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return HttpStatusCode.Created;
|
||||
});
|
||||
server.Start();
|
||||
|
||||
// Act
|
||||
string[] args = new string[] { "push", packageFileName, "-Source", mockServerEndPoint + "push" };
|
||||
int ret = Program.Main(args);
|
||||
writer.Close();
|
||||
server.Stop();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, ret);
|
||||
var output = Encoding.Default.GetString(memoryStream.ToArray());
|
||||
Assert.Contains("Your package was pushed.", output);
|
||||
AssertFileEqual(packageFileName, outputFileName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Util.DeleteDirectory(packageDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that push command can follow redirection correctly.
|
||||
[Fact]
|
||||
public void PushCommand_PushToServerFollowRedirection()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var packageDirectory = Path.Combine(tempPath, Guid.NewGuid().ToString());
|
||||
var mockServerEndPoint = "http://localhost:1234/";
|
||||
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
Util.CreateDirectory(packageDirectory);
|
||||
var packageFileName = Util.CreateTestPackage("testPackage1", "1.1.0", packageDirectory);
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
TextWriter writer = new StreamWriter(memoryStream);
|
||||
Console.SetOut(writer);
|
||||
string outputFileName = Path.Combine(packageDirectory, "t1.nupkg");
|
||||
|
||||
var server = new MockServer(mockServerEndPoint);
|
||||
server.Get.Add("/redirect", r => "OK");
|
||||
server.Put.Add("/redirect", r =>
|
||||
new Action<HttpListenerResponse>(
|
||||
res =>
|
||||
{
|
||||
res.Redirect(mockServerEndPoint + "nuget");
|
||||
}));
|
||||
server.Put.Add("/nuget", r =>
|
||||
{
|
||||
byte[] buffer = MockServer.GetPushedPackage(r);
|
||||
using (var of = new FileStream(outputFileName, FileMode.Create))
|
||||
{
|
||||
of.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return HttpStatusCode.Created;
|
||||
});
|
||||
server.Start();
|
||||
|
||||
// Act
|
||||
string[] args = new string[] { "push", packageFileName, "-Source", mockServerEndPoint + "redirect" };
|
||||
int ret = Program.Main(args);
|
||||
writer.Close();
|
||||
server.Stop();
|
||||
|
||||
// Assert
|
||||
var output = Encoding.Default.GetString(memoryStream.ToArray());
|
||||
Assert.Equal(0, ret);
|
||||
Assert.Contains("Your package was pushed.", output);
|
||||
AssertFileEqual(packageFileName, outputFileName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Util.DeleteDirectory(packageDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that push command will terminate even when there is an infinite
|
||||
// redirection loop.
|
||||
[Fact]
|
||||
public void PushCommand_PushToServerWithInfiniteRedirectionLoop()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var packageDirectory = Path.Combine(tempPath, Guid.NewGuid().ToString());
|
||||
var mockServerEndPoint = "http://localhost:1234/";
|
||||
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
Util.CreateDirectory(packageDirectory);
|
||||
var packageFileName = Util.CreateTestPackage("testPackage1", "1.1.0", packageDirectory);
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
TextWriter writer = new StreamWriter(memoryStream);
|
||||
Console.SetOut(writer);
|
||||
Console.SetError(writer);
|
||||
|
||||
var server = new MockServer(mockServerEndPoint);
|
||||
server.Get.Add("/redirect", r => "OK");
|
||||
server.Put.Add("/redirect", r =>
|
||||
new Action<HttpListenerResponse>(
|
||||
res =>
|
||||
{
|
||||
res.Redirect(mockServerEndPoint + "redirect");
|
||||
}));
|
||||
server.Start();
|
||||
|
||||
// Act
|
||||
string[] args = new string[] { "push", packageFileName, "-Source", mockServerEndPoint + "redirect" };
|
||||
int ret = Program.Main(args);
|
||||
writer.Close();
|
||||
server.Stop();
|
||||
|
||||
// Assert
|
||||
var output = Encoding.Default.GetString(memoryStream.ToArray());
|
||||
Assert.NotEqual(0, ret);
|
||||
Assert.Contains("Too many automatic redirections were attempted.", output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Util.DeleteDirectory(packageDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that push command generates error when it detects invalid redirection location.
|
||||
[Fact]
|
||||
public void PushCommand_PushToServerWithInvalidRedirectionLocation()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var packageDirectory = Path.Combine(tempPath, Guid.NewGuid().ToString());
|
||||
var mockServerEndPoint = "http://localhost:1234/";
|
||||
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
Util.CreateDirectory(packageDirectory);
|
||||
var packageFileName = Util.CreateTestPackage("testPackage1", "1.1.0", packageDirectory);
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
TextWriter writer = new StreamWriter(memoryStream);
|
||||
Console.SetOut(writer);
|
||||
Console.SetError(writer);
|
||||
|
||||
var server = new MockServer(mockServerEndPoint);
|
||||
server.Get.Add("/redirect", r => "OK");
|
||||
server.Put.Add("/redirect", r => HttpStatusCode.Redirect);
|
||||
server.Start();
|
||||
|
||||
// Act
|
||||
string[] args = new string[] { "push", packageFileName, "-Source", mockServerEndPoint + "redirect" };
|
||||
int ret = Program.Main(args);
|
||||
writer.Close();
|
||||
server.Stop();
|
||||
|
||||
// Assert
|
||||
var output = Encoding.Default.GetString(memoryStream.ToArray());
|
||||
Assert.NotEqual(0, ret);
|
||||
Assert.Contains("The remote server returned an error: (302) Redirect.", output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Util.DeleteDirectory(packageDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts that the contents of two files are equal.
|
||||
void AssertFileEqual(string fileName1, string fileName2)
|
||||
{
|
||||
byte[] content1, content2;
|
||||
using (var r1 = new FileStream(fileName1, FileMode.Open))
|
||||
{
|
||||
content1 = r1.ReadAllBytes();
|
||||
}
|
||||
using (var r1 = new FileStream(fileName2, FileMode.Open))
|
||||
{
|
||||
content2 = r1.ReadAllBytes();
|
||||
}
|
||||
|
||||
Assert.Equal(content1, content2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
</Compile>
|
||||
<Compile Include="CommandRunner.cs" />
|
||||
<Compile Include="Core\LocalPackageRepositoryTest.cs" />
|
||||
<Compile Include="MockServer.cs" />
|
||||
<Compile Include="NuGetCommandLine\DefaultConfigurationFilePreserver.cs" />
|
||||
<Compile Include="NuGetCommandLine\NuGetConfigCommandTest.cs" />
|
||||
<Compile Include="NuGetCommandLine\NuGetDeleteCommandTest.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче