зеркало из 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;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Extensions;
|
|
||||||
|
|
||||||
namespace NuGet.Test.Integration.NuGetCommandLine
|
namespace NuGet.Test.Integration.NuGetCommandLine
|
||||||
{
|
{
|
||||||
|
@ -116,5 +114,217 @@ namespace NuGet.Test.Integration.NuGetCommandLine
|
||||||
Util.DeleteDirectory(source);
|
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>
|
||||||
<Compile Include="CommandRunner.cs" />
|
<Compile Include="CommandRunner.cs" />
|
||||||
<Compile Include="Core\LocalPackageRepositoryTest.cs" />
|
<Compile Include="Core\LocalPackageRepositoryTest.cs" />
|
||||||
|
<Compile Include="MockServer.cs" />
|
||||||
<Compile Include="NuGetCommandLine\DefaultConfigurationFilePreserver.cs" />
|
<Compile Include="NuGetCommandLine\DefaultConfigurationFilePreserver.cs" />
|
||||||
<Compile Include="NuGetCommandLine\NuGetConfigCommandTest.cs" />
|
<Compile Include="NuGetCommandLine\NuGetConfigCommandTest.cs" />
|
||||||
<Compile Include="NuGetCommandLine\NuGetDeleteCommandTest.cs" />
|
<Compile Include="NuGetCommandLine\NuGetDeleteCommandTest.cs" />
|
||||||
|
|
Загрузка…
Ссылка в новой задаче