Compare commits

..

2 commits

Author SHA1 Message Date
88de476b1b test a push dummy.nupkg 2026-07-05 23:20:25 +01:00
fb609a8b41 code cleanup 2026-07-05 23:12:04 +01:00
3 changed files with 227 additions and 64 deletions

Binary file not shown.

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>dummy.nuget</id>
<version>1.0.0</version>
<authors>isnd.tests</authors>
<description>Dummy test package used by integration tests.</description>
</metadata>
</package>

View file

@ -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>
{
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<ApplicationDbContext>();
if (myDependency.Database.ProviderName?.Contains("InMemory", StringComparison.OrdinalIgnoreCase) == true)
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<ApplicationDbContext>();
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<ApplicationDbContext>();
var paul = dbContext.Users.FirstOrDefaultAsync(u => u.Email == "paul@pschneider.fr").Result;
if (paul != null)
{
var services = serviceScope.ServiceProvider;
var dbContext = services.GetRequiredService<ApplicationDbContext>();
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<IOptions<isnd.Entities.IsndSettings>>().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<ApplicationDbContext>();
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<IOptions<isnd.Entities.IsndSettings>>().Value;
using var serviceScope = server.Host.Services.CreateScope();
var isnSettings = serviceScope.ServiceProvider.GetService<IOptions<isnd.Entities.IsndSettings>>().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<IOptions<isnd.Entities.IsndSettings>>().Value;
using var serviceScope = server.Host.Services.CreateScope();
var isnSettings = serviceScope.ServiceProvider.GetService<IOptions<isnd.Entities.IsndSettings>>().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<HttpHandlerResource> 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<IOptions<isnd.Entities.IsndSettings>>().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");