Permalink
Browse files

Adding support for ~/NuGet.exe route that returns the exe

corresponding to the latest NuGet.CommandLine package
  • Loading branch information...
1 parent 90a05bc commit f9e29c8768d62c2cc5d9cd2a447a9b820cd6ec1a @pranavkm pranavkm committed with pranavkm May 7, 2012
@@ -128,6 +128,51 @@ public void WillCreateAPackageWithTheUserMatchingTheApiKey()
packageSvc.Verify(x => x.CreatePackage(It.IsAny<IPackage>(), matchingUser));
}
+
+ [Fact]
+ public void CreatePackageRefreshesNuGetExeIfCommandLinePackageIsUploaded()
+ {
+ // Arrange
+ var nuGetPackage = new Mock<IPackage>();
+ nuGetPackage.Setup(x => x.Id).Returns("NuGet.CommandLine");
+ nuGetPackage.Setup(x => x.Version).Returns(new SemanticVersion("1.0.42"));
+ var packageSvc = new Mock<IPackageService>();
+ packageSvc.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny<User>())).Returns(new Package { IsLatestStable = true });
+ var userSvc = new Mock<IUserService>();
+ var nugetExeDownloader = new Mock<INuGetExeDownloaderService>(MockBehavior.Strict);
+ nugetExeDownloader.Setup(s => s.UpdateExecutable(nuGetPackage.Object)).Verifiable();
+ var matchingUser = new User();
+ userSvc.Setup(x => x.FindByApiKey(It.IsAny<Guid>())).Returns(matchingUser);
+ var controller = CreateController(userSvc: userSvc, packageSvc: packageSvc, nugetExeDownloader: nugetExeDownloader, packageFromInputStream: nuGetPackage.Object);
+
+ // Act
+ controller.CreatePackagePut(Guid.NewGuid().ToString());
+
+ // Assert
+ nugetExeDownloader.Verify();
+ }
+
+ [Fact]
+ public void CreatePackageDoesNotRefreshNuGetExeIfItIsNotLatestStable()
+ {
+ // Arrange
+ var nuGetPackage = new Mock<IPackage>();
+ nuGetPackage.Setup(x => x.Id).Returns("NuGet.CommandLine");
+ nuGetPackage.Setup(x => x.Version).Returns(new SemanticVersion("2.0.0-alpha"));
+ var packageSvc = new Mock<IPackageService>();
+ packageSvc.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny<User>())).Returns(new Package { IsLatest = true, IsLatestStable = false });
+ var userSvc = new Mock<IUserService>();
+ var nugetExeDownloader = new Mock<INuGetExeDownloaderService>(MockBehavior.Strict);
+ var matchingUser = new User();
+ userSvc.Setup(x => x.FindByApiKey(It.IsAny<Guid>())).Returns(matchingUser);
+ var controller = CreateController(userSvc: userSvc, packageSvc: packageSvc, nugetExeDownloader: nugetExeDownloader, packageFromInputStream: nuGetPackage.Object);
+
+ // Act
+ controller.CreatePackagePut(Guid.NewGuid().ToString());
+
+ // Assert
+ nugetExeDownloader.Verify(s => s.UpdateExecutable(It.IsAny<IPackage>()), Times.Never());
+ }
}
public class TheDeletePackageAction
@@ -492,12 +537,15 @@ private static void AssertStatusCodeResult(ActionResult result, int statusCode,
Mock<IPackageService> packageSvc = null,
Mock<IPackageFileService> fileService = null,
Mock<IUserService> userSvc = null,
+ Mock<INuGetExeDownloaderService> nugetExeDownloader = null,
IPackage packageFromInputStream = null)
{
packageSvc = packageSvc ?? new Mock<IPackageService>();
userSvc = userSvc ?? new Mock<IUserService>();
fileService = fileService ?? new Mock<IPackageFileService>(MockBehavior.Strict);
- var controller = new Mock<ApiController>(packageSvc.Object, fileService.Object, userSvc.Object);
+ nugetExeDownloader = nugetExeDownloader ?? new Mock<INuGetExeDownloaderService>(MockBehavior.Strict);
+
+ var controller = new Mock<ApiController>(packageSvc.Object, fileService.Object, userSvc.Object, nugetExeDownloader.Object);
controller.CallBase = true;
if (packageFromInputStream != null)
controller.Setup(x => x.ReadPackageFromRequest()).Returns(packageFromInputStream);
View
@@ -137,6 +137,7 @@
<Compile Include="Services\CloudBlobFileStorageServiceFacts.cs" />
<Compile Include="Services\FileSystemFileStorageServiceFacts.cs" />
<Compile Include="Services\MessageServiceFacts.cs" />
+ <Compile Include="Services\NuGetExeDownloaderServiceFacts.cs" />
<Compile Include="Services\PackageServiceFacts.cs" />
<Compile Include="Services\UploadFileServiceFacts.cs" />
<Compile Include="Services\UsersServiceFacts.cs" />
@@ -0,0 +1,161 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using NuGet;
+using Xunit;
+
+namespace NuGetGallery.Services
+{
+ public class NuGetExeDownloaderServiceFacts
+ {
+ private static readonly string _exePath = @"x:\NuGetGallery\nuget.exe";
+
+ [Fact]
+ public void CreateNuGetExeDownloadDoesNotExtractFileIfItAlreadyExistsAndIsRecent()
+ {
+ // Arrange
+ var fileSystem = new Mock<IFileSystemService>(MockBehavior.Strict);
+ fileSystem.Setup(s => s.FileExists(_exePath)).Returns(true).Verifiable();
+ fileSystem.Setup(s => s.GetCreationTimeUtc(_exePath))
+ .Returns(DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(60)))
+ .Verifiable();
+
+ // Act
+ var downloaderSvc = GetDownloaderService(fileSystemSvc: fileSystem);
+ var result = downloaderSvc.CreateNuGetExeDownloadActionnResult();
+
+ // Assert
+ fileSystem.Verify();
+ AssertActionResult(result);
+ }
+
+ [Fact]
+ public void CreateNuGetExeDownloadExtractsFileIfItDoesNotExist()
+ {
+ // Arrange
+ var fileSystem = new Mock<IFileSystemService>(MockBehavior.Strict);
+ fileSystem.Setup(s => s.FileExists(_exePath)).Returns(false);
+ fileSystem.Setup(s => s.OpenWrite(_exePath)).Returns(Stream.Null);
+
+ var package = new Package { Version = "2.0.0" };
+ var packageService = new Mock<IPackageService>(MockBehavior.Strict);
+ packageService.Setup(s => s.FindPackageByIdAndVersion("NuGet.CommandLine", null, false))
+ .Returns(package)
+ .Verifiable();
+ var packageFileSvc = new Mock<IPackageFileService>(MockBehavior.Strict);
+ packageFileSvc.Setup(s => s.DownloadPackageFile(package))
+ .Returns(CreateCommandLinePackage)
+ .Verifiable();
+
+ // Act
+ var downloaderSvc = GetDownloaderService(packageService, packageFileSvc, fileSystem);
+ var result = downloaderSvc.CreateNuGetExeDownloadActionnResult();
+
+ // Assert
+ packageFileSvc.Verify();
+ packageService.Verify();
+ AssertActionResult(result);
+ }
+
+ [Fact]
+ public void CreateNuGetExeDownloadExtractsFileIfItExistsButIsNotRecent()
+ {
+ // Arrange
+ var fileSystem = new Mock<IFileSystemService>(MockBehavior.Strict);
+ fileSystem.Setup(s => s.FileExists(_exePath)).Returns(true);
+ fileSystem.Setup(s => s.GetCreationTimeUtc(_exePath))
+ .Returns(DateTime.UtcNow.Subtract(TimeSpan.FromHours(32)));
@jeffhandley

jeffhandley May 29, 2012

Member

Why have a 32-hour staleness if we're proactively updating it whenever NuGet.CommandLine is uploaded?

+ fileSystem.Setup(s => s.OpenWrite(_exePath)).Returns(Stream.Null);
+
+ var package = new Package { Version = "2.0.0" };
+ var packageService = new Mock<IPackageService>(MockBehavior.Strict);
+ packageService.Setup(s => s.FindPackageByIdAndVersion("NuGet.CommandLine", null, false))
+ .Returns(package)
+ .Verifiable();
+ var packageFileSvc = new Mock<IPackageFileService>(MockBehavior.Strict);
+ packageFileSvc.Setup(s => s.DownloadPackageFile(package))
+ .Returns(CreateCommandLinePackage)
+ .Verifiable();
+
+ // Act
+ var downloaderSvc = GetDownloaderService(packageService, packageFileSvc, fileSystem);
+ var result = downloaderSvc.CreateNuGetExeDownloadActionnResult();
+
+ // Assert
+ packageFileSvc.Verify();
+ packageService.Verify();
+ AssertActionResult(result);
+ }
+
+ [Fact]
+ public void UpdateExecutableExtractsExeToDisk()
+ {
+ // Arrange
+ var fileSystem = new Mock<IFileSystemService>(MockBehavior.Strict);
+ fileSystem.Setup(s => s.OpenWrite(_exePath)).Returns(Stream.Null);
+
+ var nugetPackage = new Mock<IPackage>();
+ nugetPackage.Setup(s => s.GetFiles()).Returns(new[] { CreateExePackageFile() }.AsQueryable());
+
+ // Act
+ var downloaderSvc = GetDownloaderService(fileSystemSvc: fileSystem);
+ downloaderSvc.UpdateExecutable(nugetPackage.Object);
+
+ // Assert
+ fileSystem.Verify();
+ }
+
+ private static void AssertActionResult(ActionResult result)
+ {
+ Assert.IsType<FilePathResult>(result);
+ var filePathResult = (FilePathResult)result;
+ Assert.Equal(_exePath, filePathResult.FileName);
+ Assert.Equal(@"application/octet-stream", filePathResult.ContentType);
+ Assert.Equal(@"NuGet.exe", filePathResult.FileDownloadName);
@jeffhandley

jeffhandley May 29, 2012

Member

Let's normalize this to lower-case "nuget.exe" - we're seeing some Mono bugs come in about case-sensitivity.

+ }
+
+ private static Stream CreateCommandLinePackage()
+ {
+ var packageBuilder = new PackageBuilder
+ {
+ Id = "NuGet.CommandLine",
+ Version = new SemanticVersion("2.0.0"),
+ Description = "Some desc"
+ };
+ packageBuilder.Authors.Add("test");
+ var exeFile = CreateExePackageFile();
+ packageBuilder.Files.Add(exeFile);
+
+ var memoryStream = new MemoryStream();
+ packageBuilder.Save(memoryStream);
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ return memoryStream;
+ }
+
+ private static IPackageFile CreateExePackageFile()
+ {
+ var exeFile = new Mock<IPackageFile>();
+ exeFile.Setup(s => s.Path).Returns(@"tools\NuGet.exe");
+ exeFile.Setup(s => s.GetStream()).Returns(Stream.Null);
+ return exeFile.Object;
+ }
+
+ private static NuGetExeDownloaderService GetDownloaderService(
+ Mock<IPackageService> packageSvc = null,
+ Mock<IPackageFileService> packageFileSvc = null,
+ Mock<IFileSystemService> fileSystemSvc = null)
+ {
+ packageSvc = packageSvc ?? new Mock<IPackageService>(MockBehavior.Strict);
+ packageFileSvc = packageFileSvc ?? new Mock<IPackageFileService>(MockBehavior.Strict);
+ fileSystemSvc = fileSystemSvc ?? new Mock<IFileSystemService>(MockBehavior.Strict);
+
+ return new NuGetExeDownloaderService(packageSvc.Object, packageFileSvc.Object, fileSystemSvc.Object)
+ {
+ NuGetExePath = _exePath
+ };
+ }
+ }
+}
@@ -75,6 +75,7 @@ public partial class ApiController {
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionNamesClass {
public readonly string GetPackage = "GetPackageApi";
+ public readonly string GetNuGetExe = "GetNuGetExeApi";
public readonly string VerifyPackageKey = "VerifyPackageKeyApi";
public readonly string CreatePackagePut = "PushPackageApi";
public readonly string CreatePackagePost = "PushPackageApi";
@@ -102,6 +103,11 @@ public class T4MVC_ApiController: NuGetGallery.ApiController {
return callInfo;
}
+ public override System.Web.Mvc.ActionResult GetNuGetExe() {
+ var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.GetNuGetExe);
+ return callInfo;
+ }
+
public override System.Web.Mvc.ActionResult VerifyPackageKey(string apiKey, string id, string version) {
var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.VerifyPackageKey);
callInfo.RouteValueDictionary.Add("apiKey", apiKey);
@@ -89,6 +89,10 @@ public override void Load()
.To<LuceneIndexingService>()
.InRequestScope();
+ Bind<INuGetExeDownloaderService>()
+ .To<NuGetExeDownloaderService>()
+ .InRequestScope();
+
Lazy<IMailSender> mailSenderThunk = new Lazy<IMailSender>(() =>
{
var settings = Kernel.Get<GallerySetting>();
@@ -208,6 +208,11 @@ public static void RegisterRoutes(RouteCollection routes)
"api/v2/",
typeof(V2Feed));
+ routes.MapRoute(
+ RouteName.DownloadNuGetExe,
+ "nuget.exe",
+ new { controller = MVC.Api.Name, action = MVC.Api.ActionNames.GetNuGetExe });
+
// Redirected Legacy Routes
routes.Redirect(
View
@@ -10,6 +10,7 @@ public static class Constants
public const int DefaultPasswordResetTokenExpirationHours = 24;
public const int MaxEmailSubjectLength = 255;
public const string PackageContentType = "application/zip";
+ public const string OctetStreamContentType = "application/octet-stream";
public const string NuGetPackageFileExtension = ".nupkg";
public const string PackageFileDownloadUriTemplate = "packages/{0}/{1}/download";
public const string PackageFileSavePathTemplate = "{0}.{1}{2}";
@@ -4,24 +4,31 @@
using System.Linq;
using System.Net;
using System.Web.Mvc;
+using System.Web.UI;
using NuGet;
namespace NuGetGallery
{
public partial class ApiController : Controller
{
+ private const string NuGetCommandLinePackage = "NuGet.CommandLine";
private readonly IPackageService packageSvc;
private readonly IUserService userSvc;
private readonly IPackageFileService packageFileSvc;
+ private readonly INuGetExeDownloaderService nugetExeDownloaderSvc;
- public ApiController(IPackageService packageSvc, IPackageFileService packageFileSvc, IUserService userSvc)
+ public ApiController(IPackageService packageSvc,
+ IPackageFileService packageFileSvc,
+ IUserService userSvc,
+ INuGetExeDownloaderService nugetExeDownloaderSvc)
{
this.packageSvc = packageSvc;
this.packageFileSvc = packageFileSvc;
this.userSvc = userSvc;
+ this.nugetExeDownloaderSvc = nugetExeDownloaderSvc;
}
- [ActionName("GetPackageApi"), HttpGet]
+ [ActionName("GetPackageApi"), HttpGet]
public virtual ActionResult GetPackage(string id, string version)
{
// if the version is null, the user is asking for the latest version. Presumably they don't want pre release versions.
@@ -38,7 +45,17 @@ public virtual ActionResult GetPackage(string id, string version)
if (!string.IsNullOrWhiteSpace(package.ExternalPackageUrl))
return Redirect(package.ExternalPackageUrl);
else
+ {
return packageFileSvc.CreateDownloadPackageActionResult(package);
+ }
+ }
+
+ [ActionName("GetNuGetExeApi"),
+ HttpGet,
+ OutputCache(VaryByParam = "none", Location = OutputCacheLocation.ServerAndClient, Duration = 600)]
+ public virtual ActionResult GetNuGetExe()
+ {
+ return nugetExeDownloaderSvc.CreateNuGetExeDownloadActionnResult();
}
[ActionName("VerifyPackageKeyApi"), HttpGet]
@@ -112,7 +129,13 @@ private ActionResult CreatePackageInternal(string apiKey)
}
}
- packageSvc.CreatePackage(packageToPush, user);
+ var package = packageSvc.CreatePackage(packageToPush, user);
+ if (packageToPush.Id.Equals(NuGetCommandLinePackage, StringComparison.OrdinalIgnoreCase) && package.IsLatestStable)
+ {
+ // If we're pushing a new stable version of NuGet.CommandLine, update the extracted executable.
+ nugetExeDownloaderSvc.UpdateExecutable(packageToPush);
+ }
+
return new HttpStatusCodeResult(201);
}
View
@@ -10,6 +10,7 @@ public static class RouteName
public const string Profile = "Profile";
public const string DisplayPackage = "package-route";
public const string DownloadPackage = "DownloadPackage";
+ public const string DownloadNuGetExe = "DownloadNuGetExe";
public const string Home = "Home";
public const string Policies = "Policies";
public const string ListPackages = "ListPackages";
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
namespace NuGetGallery
{
@@ -33,5 +34,10 @@ public Stream OpenWrite(string path)
{
return File.OpenWrite(path);
}
+
+ public DateTimeOffset GetCreationTimeUtc(string path)
+ {
+ return File.GetCreationTimeUtc(path);
+ }
}
}
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
namespace NuGetGallery
{
@@ -10,5 +11,6 @@ public interface IFileSystemService
bool FileExists(string path);
Stream OpenRead(string path);
Stream OpenWrite(string path);
+ DateTimeOffset GetCreationTimeUtc(string path);
}
}
Oops, something went wrong.

0 comments on commit f9e29c8

Please sign in to comment.