diff --git a/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg b/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg
new file mode 100644
index 0000000..070a94b
Binary files /dev/null and b/test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg differ
diff --git a/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec b/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec
new file mode 100644
index 0000000..feb3a4a
--- /dev/null
+++ b/test/data/nuget-artifacts/dummy.nuget.1.0.0/dummy.nuget.nuspec
@@ -0,0 +1,9 @@
+
+
+
+ dummy.nuget
+ 1.0.0
+ isnd.tests
+ Dummy test package used by integration tests.
+
+
diff --git a/test/isnd.tests/UnitTestWebHost.cs b/test/isnd.tests/UnitTestWebHost.cs
index b89caae..c71b5ea 100644
--- a/test/isnd.tests/UnitTestWebHost.cs
+++ b/test/isnd.tests/UnitTestWebHost.cs
@@ -1,34 +1,39 @@
-using System.Threading;
using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
using System.Net.Http;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore;
-using Xunit;
+using System.Net.Http.Headers;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Isn.Abstract;
using isnd.Data;
+using isnd.Data.ApiKeys;
+using isnd.Helpers;
+using isnd.tests;
+using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using System.Diagnostics;
-using Microsoft.AspNetCore.Hosting.Server;
-using Microsoft.AspNetCore.Hosting.Server.Features;
-using Microsoft.Extensions.Configuration;
-using isnd.tests;
-using NuGet.Protocol;
-using NuGet.Configuration;
-using System.Threading.Tasks;
-using NuGet.Protocol.Core.Types;
using NuGet.Common;
-using Isn.Abstract;
-using isnd.Helpers;
-using Microsoft.AspNetCore.DataProtection;
-using System.Security.Cryptography;
+using NuGet.Configuration;
+using NuGet.Protocol;
+using NuGet.Protocol.Core.Types;
+using Xunit;
namespace isnd.host.tests
-{
+{
[Collection("Web server collection")]
public class UnitTestWebHost : IClassFixture
{
- WebServerFixture server;
+ private const string DummyPackageId = "dummy.nuget";
+ private const string DummyPackageVersion = "1.0.0";
+ private const string DummyArtifactRelativePath = "test/data/nuget-artifacts/dummy.nuget.1.0.0.nupkg";
+
+ private readonly WebServerFixture server;
+
public UnitTestWebHost(WebServerFixture server)
{
this.server = server;
@@ -37,18 +42,16 @@ namespace isnd.host.tests
[Fact]
public void TestHaveTestDbContextAndMigrate()
{
- using (var serviceScope = server.Host.Services.CreateScope())
+ using var serviceScope = server.Host.Services.CreateScope();
+ var services = serviceScope.ServiceProvider;
+ var myDependency = services.GetRequiredService();
+ if (myDependency.Database.ProviderName?.Contains("InMemory", StringComparison.OrdinalIgnoreCase) == true)
{
- var services = serviceScope.ServiceProvider;
- var myDependency = services.GetRequiredService();
- if (myDependency.Database.ProviderName?.Contains("InMemory", StringComparison.OrdinalIgnoreCase) == true)
- {
- myDependency.Database.EnsureCreated();
- }
- else
- {
- myDependency.Database.Migrate();
- }
+ myDependency.Database.EnsureCreated();
+ }
+ else
+ {
+ myDependency.Database.Migrate();
}
}
@@ -74,67 +77,121 @@ namespace isnd.host.tests
}
[Fact]
- void TestDropUser()
+ public void TestDropUser()
{
-
- using (var serviceScope = server.Host.Services.CreateScope())
+ using var serviceScope = server.Host.Services.CreateScope();
+ var services = serviceScope.ServiceProvider;
+ var dbContext = services.GetRequiredService();
+ var paul = dbContext.Users.FirstOrDefaultAsync(u => u.Email == "paul@pschneider.fr").Result;
+ if (paul != null)
{
- var services = serviceScope.ServiceProvider;
- var dbContext = services.GetRequiredService();
- var paul = dbContext.Users.FirstOrDefaultAsync(u => u.Email == "paul@pschneider.fr").Result;
- if (paul!=null)
- {
- dbContext.Users.Remove(paul);
- dbContext.SaveChanges();
- }
+ dbContext.Users.Remove(paul);
+ dbContext.SaveChanges();
}
}
-
[Fact]
public void NugetInstallsTest()
{
+ var pkgSourceUrl = GetServerBaseUrl() + "/" + Constants.APIPrefix + "index.json";
+ using var client = new HttpClient();
+ var response = client.GetAsync(pkgSourceUrl).GetAwaiter().GetResult();
+ var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+
+ Assert.True(response.IsSuccessStatusCode, $"Expected {pkgSourceUrl} to be reachable but got {(int)response.StatusCode} {response.ReasonPhrase}");
+ Assert.False(string.IsNullOrWhiteSpace(body));
+ Assert.Contains("PackagePublish/2.0.0", body, StringComparison.OrdinalIgnoreCase);
+ Assert.Contains("RegistrationsBaseUrl/Versioned", body, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void DummyNugetArtifactShouldBeAReadablePackage()
+ {
+ var packagePath = GetDummyArtifactPath();
+ Assert.True(File.Exists(packagePath), $"Missing dummy artifact at {packagePath}");
+
+ var fileInfo = new FileInfo(packagePath);
+ Assert.True(fileInfo.Length > 0, "Dummy artifact is empty.");
+
+ using var stream = File.OpenRead(packagePath);
+ using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false);
+ var nuspecEntry = archive.GetEntry("dummy.nuget.nuspec");
+ Assert.NotNull(nuspecEntry);
+
+ using var nuspecStream = nuspecEntry.Open();
+ var nuspec = XDocument.Load(nuspecStream);
+ XNamespace ns = "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd";
+ var id = nuspec.Root?.Element(ns + "metadata")?.Element(ns + "id")?.Value;
+ var version = nuspec.Root?.Element(ns + "metadata")?.Element(ns + "version")?.Value;
+
+ Assert.Equal(DummyPackageId, id);
+ Assert.Equal(DummyPackageVersion, version);
+ }
+
+ [Fact]
+ public async Task PushDummyPackageAndDiscoverItThroughNuGetEndpoints()
+ {
+ 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 isnSettings = serviceScope.ServiceProvider.GetService>().Value;
- string pkgSourceUrl = isnSettings.ExternalUrl + "/" + Constants.APIPrefix + "index.json";
- using var client = new HttpClient();
- var response = client.GetAsync(pkgSourceUrl).GetAwaiter().GetResult();
- var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+ var dbContext = serviceScope.ServiceProvider.GetRequiredService();
+ var userId = $"dummy-user-{Guid.NewGuid():N}";
- Assert.True(response.IsSuccessStatusCode, $"Expected {pkgSourceUrl} to be reachable but got {(int)response.StatusCode} {response.ReasonPhrase}");
- Assert.False(string.IsNullOrWhiteSpace(body));
+ 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);
+ await AssertRegistrationContainsPushedVersionAsync(DummyPackageId, DummyPackageVersion);
+ await AssertAutocompleteContainsPushedVersionAsync(DummyPackageId, DummyPackageVersion);
}
[Fact]
public void TestRegistrationV3Resource()
{
- using (var serviceScope = server.Host.Services.CreateScope())
- { var isnSettings = serviceScope.ServiceProvider.GetService>().Value;
+ using var serviceScope = server.Host.Services.CreateScope();
+ var isnSettings = serviceScope.ServiceProvider.GetService>().Value;
string pkgSourceUrl = isnSettings.ExternalUrl + "/pkgs/index.json";
- NullThrottle throttle = new NullThrottle();
+ var throttle = new NullThrottle();
- PackageSource packageSource = new PackageSource(pkgSourceUrl);
- HttpSource client = new HttpSource(packageSource, PkgSourceMessageHandler, throttle);
- NuGet.Protocol.RegistrationResourceV3 res = new NuGet.Protocol.RegistrationResourceV3(client ,
- new Uri(isnSettings.ExternalUrl + "/v3.4.0//registration"));
- }
+ var packageSource = new PackageSource(pkgSourceUrl);
+ var client = new HttpSource(packageSource, PkgSourceMessageHandler, throttle);
+ _ = new RegistrationResourceV3(client, new Uri(isnSettings.ExternalUrl + "/v3.4.0//registration"));
}
-
+
[Fact]
public void TrueTestRegistrationV3Resource()
{
- using (var serviceScope = server.Host.Services.CreateScope())
- {
- var isnSettings = serviceScope.ServiceProvider.GetService>().Value;
+ using var serviceScope = server.Host.Services.CreateScope();
+ var isnSettings = serviceScope.ServiceProvider.GetService>().Value;
string pkgSourceUrl = isnSettings.ExternalUrl + "/pkgs/index.json";
var prov = new RegistrationResourceV3Provider();
var source = new PackageSource(pkgSourceUrl);
- var repo = new SourceRepository(source, new INuGetResourceProvider[]{ prov });
+ var repo = new SourceRepository(source, new INuGetResourceProvider[] { prov });
prov.TryCreate(repo, CancellationToken.None);
-
- }
}
private Task PkgSourceMessageHandler()
@@ -142,6 +199,103 @@ namespace isnd.host.tests
throw new NotImplementedException();
}
+ private string GetServerBaseUrl()
+ {
+ var address = server.Addresses.FirstOrDefault();
+ Assert.False(string.IsNullOrWhiteSpace(address), "No listening address was captured from WebServerFixture.");
+ return address.TrimEnd('/');
+ }
+
+ private string GetDummyArtifactPath()
+ {
+ var root = FindRepoRoot();
+ return Path.Combine(root, DummyArtifactRelativePath);
+ }
+
+ private static string FindRepoRoot()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir != null)
+ {
+ if (File.Exists(Path.Combine(dir.FullName, "isn.sln")))
+ {
+ return dir.FullName;
+ }
+
+ dir = dir.Parent;
+ }
+
+ throw new DirectoryNotFoundException("Could not locate repository root from AppContext.BaseDirectory.");
+ }
+
+ private static async Task ResetDummyPackageStateAsync(IServiceProvider serviceProvider, ApplicationDbContext dbContext)
+ {
+ var settings = serviceProvider.GetRequiredService>().Value;
+ var versions = dbContext.PackageVersions.Where(v => v.PackageId == DummyPackageId);
+ var packages = dbContext.Packages.Where(p => p.Id == DummyPackageId);
+
+ dbContext.PackageVersions.RemoveRange(versions);
+ dbContext.Packages.RemoveRange(packages);
+ await dbContext.SaveChangesAsync();
+
+ var packageRootDir = settings.PackagesRootDir;
+ if (!Path.IsPathRooted(packageRootDir))
+ {
+ packageRootDir = Path.Combine(FindRepoRoot(), packageRootDir);
+ }
+
+ var packageOnDiskPath = Path.Combine(packageRootDir, DummyPackageId);
+ if (Directory.Exists(packageOnDiskPath))
+ {
+ Directory.Delete(packageOnDiskPath, recursive: true);
+ }
+ }
+
+ private async Task PushPackageAsync(string packagePath, string apiKey)
+ {
+ using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) };
+ using var request = new HttpRequestMessage(HttpMethod.Put, $"/{Constants.APIPrefix}{isnd.Entities.ApiConfig.Publish}");
+ using var multipart = new MultipartFormDataContent();
+ await using var packageStream = File.OpenRead(packagePath);
+ using var packageContent = new StreamContent(packageStream);
+
+ packageContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+ multipart.Add(packageContent, "package", Path.GetFileName(packagePath));
+ request.Content = multipart;
+ request.Headers.Add("X-NuGet-ApiKey", apiKey);
+ request.Headers.Add("X-NuGet-Client-Version", "6.11.1");
+
+ var response = await client.SendAsync(request);
+ var responseBody = await response.Content.ReadAsStringAsync();
+ Assert.True(response.IsSuccessStatusCode,
+ $"Expected push to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {responseBody}");
+ }
+
+ private async Task AssertRegistrationContainsPushedVersionAsync(string packageId, string packageVersion)
+ {
+ using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) };
+ var lowerId = packageId.ToLowerInvariant();
+ var route = $"/{Constants.APIPrefix}v3.4.0/{isnd.Entities.ApiConfig.Registration}/{lowerId}/index.json";
+ var response = await client.GetAsync(route);
+ var body = await response.Content.ReadAsStringAsync();
+
+ Assert.True(response.IsSuccessStatusCode,
+ $"Expected registration endpoint to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {body}");
+ Assert.Contains(packageVersion, body, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private async Task AssertAutocompleteContainsPushedVersionAsync(string packageId, string packageVersion)
+ {
+ using var client = new HttpClient { BaseAddress = new Uri(GetServerBaseUrl()) };
+ var route = $"/{Constants.APIPrefix}{isnd.Entities.ApiConfig.AutoComplete}?id={packageId}&semVerLevel=3.0.0&prerelease=true";
+ var response = await client.GetAsync(route);
+ var body = await response.Content.ReadAsStringAsync();
+
+ Assert.True(response.IsSuccessStatusCode,
+ $"Expected autocomplete endpoint to succeed but got {(int)response.StatusCode} {response.ReasonPhrase}. Body: {body}");
+ Assert.Contains(packageVersion, body, StringComparison.OrdinalIgnoreCase);
+ }
+
private sealed class ThrowingProtector : IDataProtector
{
public byte[] Protect(byte[] plaintext) => throw new CryptographicException("unexpected protect call");