Complete release compatibility and host inventory sprints
Signed-off-by: master <>
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Scanner.WebService.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Endpoints;
|
||||
|
||||
@@ -90,7 +92,7 @@ internal static class RegistryEndpoints
|
||||
new RegistryDigestEntry
|
||||
{
|
||||
Tag = "latest",
|
||||
Digest = $"sha256:{Guid.NewGuid():N}",
|
||||
Digest = CreateDeterministicDigest(repository),
|
||||
PushedAt = "2026-03-20T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -256,4 +258,10 @@ internal static class RegistryEndpoints
|
||||
LastPushed = "2026-03-20T15:00:00Z"
|
||||
},
|
||||
};
|
||||
|
||||
private static string CreateDeterministicDigest(string repository)
|
||||
{
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(repository.Trim().ToLowerInvariant()));
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using StellaOps.TestKit;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class RegistryEndpointsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RegistrySearchAndDigestEndpoints_ReturnExpectedResults()
|
||||
{
|
||||
await using var factory = ScannerApplicationFactory.CreateLightweight();
|
||||
await factory.InitializeAsync();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var searchResponse = await client.GetAsync(
|
||||
"/api/v1/registries/images/search?q=nginx",
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, searchResponse.StatusCode);
|
||||
|
||||
var searchPayload = await searchResponse.Content.ReadFromJsonAsync<RegistrySearchResponse>(
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(searchPayload);
|
||||
Assert.Contains(searchPayload!.Items, item => item.Repository == "library/nginx");
|
||||
|
||||
var digestResponse = await client.GetAsync(
|
||||
"/api/v1/registries/images/digests?repository=library/nginx",
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, digestResponse.StatusCode);
|
||||
|
||||
var digestPayload = await digestResponse.Content.ReadFromJsonAsync<RegistryDigestResponse>(
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(digestPayload);
|
||||
Assert.Equal("library/nginx", digestPayload!.Repository);
|
||||
Assert.NotEmpty(digestPayload.Digests);
|
||||
Assert.All(digestPayload.Digests, item => Assert.StartsWith("sha256:", item.Digest, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task UnknownRepositoryDigest_IsDeterministicAcrossRequests()
|
||||
{
|
||||
await using var factory = ScannerApplicationFactory.CreateLightweight();
|
||||
await factory.InitializeAsync();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var first = await client.GetFromJsonAsync<RegistryDigestResponse>(
|
||||
"/api/v1/registries/images/digests?repository=example/custom-service",
|
||||
TestContext.Current.CancellationToken);
|
||||
var second = await client.GetFromJsonAsync<RegistryDigestResponse>(
|
||||
"/api/v1/registries/images/digests?repository=example/custom-service",
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(first);
|
||||
Assert.NotNull(second);
|
||||
Assert.Equal(first!.Digests[0].Digest, second!.Digests[0].Digest);
|
||||
}
|
||||
|
||||
public sealed class RegistrySearchResponse
|
||||
{
|
||||
public RegistryImageDto[] Items { get; set; } = [];
|
||||
public int TotalCount { get; set; }
|
||||
public string? RegistryId { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RegistryDigestResponse
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Repository { get; set; } = string.Empty;
|
||||
public string[] Tags { get; set; } = [];
|
||||
public RegistryDigestEntry[] Digests { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class RegistryImageDto
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Repository { get; set; } = string.Empty;
|
||||
public string[] Tags { get; set; } = [];
|
||||
public RegistryDigestEntry[] Digests { get; set; } = [];
|
||||
public string LastPushed { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class RegistryDigestEntry
|
||||
{
|
||||
public string Tag { get; set; } = string.Empty;
|
||||
public string Digest { get; set; } = string.Empty;
|
||||
public string PushedAt { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user