Add unit test for issue 4050 (push to server does not follow redirection).

This commit is contained in:
feiling 2014-03-12 15:18:25 -07:00
Родитель 2cda345ffd
Коммит e3b351c4b1
3 изменённых файлов: 482 добавлений и 3 удалений

Просмотреть файл

@ -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&lt;HttpListenerResponse&gt;: 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" />