diff --git a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs index 8e42b69..182184a 100644 --- a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs +++ b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; +using System.Linq; using Microsoft.AspNetCore.Mvc; using Isn.Abstract; using isnd.Entities; @@ -10,13 +11,28 @@ namespace isnd.Controllers public partial class PackagesController { - // Web get the paquet - [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetPackage + "/{id}/{lower}/{idf}-{lowerf}." + // NuGet v3 flat-container versions index. + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetPackage + "/{id}/" + ApiConfig.IndexDotJson)] + public IActionResult GetPackageVersionsById( + [FromRoute][SafeName][Required] string id) + { + var versions = dbContext.PackageVersions + .Where(v => v.PackageId.ToLower() == id.ToLower()) + .Select(v => v.FullString) + .Distinct() + .OrderBy(v => v) + .ToArray(); + + return Ok(new { versions }); + } + + // NuGet v3 flat-container package download by filename, e.g. id.version.nupkg. + [HttpGet("~/" + Constants.APIPrefix + ApiConfig.GetPackage + "/{id}/{lower}/{filename}." + Constants.PaquetFileEstension)] - public IActionResult GetPackage( - [FromRoute][SafeName][Required] string id, + public IActionResult GetPackageByFilename( + [FromRoute][SafeName][Required] string id, [FromRoute][SafeName][Required] string lower, - [FromRoute] string idf, [FromRoute] string lowerf) + [FromRoute][SafeName][Required] string filename) { var pkgpath = Path.Combine(isndSettings.PackagesRootDir, id, lower, $"{id}-{lower}." + Constants.PaquetFileEstension @@ -32,6 +48,8 @@ namespace isnd.Controllers } // Web get spec + [HttpGet("~/" + Constants.APIPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}.{lowerf}." + + Constants.SpecFileEstension)] [HttpGet("~/" + Constants.APIPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}." + Constants.SpecFileEstension)] public IActionResult GetNuspec( diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index 84ac2c0..0ba1c04 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -50,7 +50,8 @@ namespace isnd.Services }, // under dev, only leash in release mode - new Resource(extUrl + ApiConfig.GetPackage, "PackageBaseAddress/3.0.0") + // NuGet clients expect a trailing slash on PackageBaseAddress to resolve relative paths. + new Resource(extUrl + ApiConfig.GetPackage + "/", "PackageBaseAddress/3.0.0") { Comment = @"Package Base Address service - Base URL of where NuGet packages are stored, in the format https:///nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg" }, diff --git a/test/isnd.tests/UnitTestWebHost.cs b/test/isnd.tests/UnitTestWebHost.cs index e7386e8..126372a 100644 --- a/test/isnd.tests/UnitTestWebHost.cs +++ b/test/isnd.tests/UnitTestWebHost.cs @@ -17,9 +17,11 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Versioning; using Xunit; namespace isnd.host.tests @@ -168,6 +170,63 @@ namespace isnd.host.tests await AssertAutocompleteContainsPushedVersionAsync(DummyPackageId, DummyPackageVersion); } + [Fact] + public async Task NuGetV3ProtocolCanDownloadDummyPackage() + { + var apiKeyValue = $"dummy-key-{Guid.NewGuid():N}"; + var packagePath = GetDummyArtifactPath(); + Assert.True(File.Exists(packagePath), $"Missing dummy artifact at {packagePath}"); + + using (var serviceScope = server.Host.Services.CreateScope()) + { + var dbContext = serviceScope.ServiceProvider.GetRequiredService(); + var userId = $"dummy-user-{Guid.NewGuid():N}"; + + await ResetDummyPackageStateAsync(serviceScope.ServiceProvider, dbContext); + + dbContext.Users.Add(new ApplicationUser + { + Id = userId, + UserName = userId, + NormalizedUserName = userId.ToUpperInvariant(), + Email = $"{userId}@tests.local", + NormalizedEmail = $"{userId}@tests.local".ToUpperInvariant(), + EmailConfirmed = true + }); + + dbContext.ApiKeys.Add(new ApiKey + { + Id = apiKeyValue, + UserId = userId, + Name = "dummy-key", + CreationDate = DateTime.UtcNow, + ValidityPeriodInDays = 30 + }); + + await dbContext.SaveChangesAsync(); + } + + await PushPackageAsync(packagePath, apiKeyValue); + + var indexUrl = GetServerBaseUrl() + "/" + Constants.APIPrefix + "index.json"; + var repository = Repository.Factory.GetCoreV3(indexUrl); + var findPackageById = await repository.GetResourceAsync(); + Assert.NotNull(findPackageById); + + await using var nupkgStream = new MemoryStream(); + using var cache = new SourceCacheContext(); + var copied = await findPackageById.CopyNupkgToStreamAsync( + DummyPackageId, + NuGetVersion.Parse(DummyPackageVersion), + nupkgStream, + cache, + NullLogger.Instance, + CancellationToken.None); + + Assert.True(copied, "NuGet v3 client could not download the package from the feed."); + Assert.True(nupkgStream.Length > 0, "Downloaded package stream is empty."); + } + [Fact] public void TestRegistrationV3Resource() {