Gaps fill up, fixes, ui restructuring

This commit is contained in:
master
2026-02-19 22:10:54 +02:00
parent b5829dce5c
commit 04cacdca8a
331 changed files with 42859 additions and 2174 deletions

View File

@@ -0,0 +1,231 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.TestKit;
using System.Linq;
using System.Net;
using System.Net.Http.Json;
namespace StellaOps.Platform.WebService.Tests;
public sealed class AdministrationTrustSigningMutationEndpointsTests : IClassFixture<PlatformWebApplicationFactory>
{
private readonly PlatformWebApplicationFactory _factory;
public AdministrationTrustSigningMutationEndpointsTests(PlatformWebApplicationFactory factory)
{
_factory = factory;
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TrustSigningLifecycle_CreateRotateRevokeAndConfigure_Works()
{
var tenantId = Guid.NewGuid().ToString("D");
using var client = CreateTenantClient(tenantId);
var createKeyResponse = await client.PostAsJsonAsync(
"/api/v1/administration/trust-signing/keys",
new CreateAdministrationTrustKeyRequest(
Alias: "core-signing-k1",
Algorithm: "ed25519",
MetadataJson: "{\"owner\":\"secops\"}"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, createKeyResponse.StatusCode);
var key = await createKeyResponse.Content.ReadFromJsonAsync<AdministrationTrustKeySummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(key);
Assert.Equal("active", key!.Status);
Assert.Equal(1, key.CurrentVersion);
var rotateResponse = await client.PostAsJsonAsync(
$"/api/v1/administration/trust-signing/keys/{key.KeyId}/rotate",
new RotateAdministrationTrustKeyRequest("scheduled_rotation", "CHG-100"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, rotateResponse.StatusCode);
var rotatedKey = await rotateResponse.Content.ReadFromJsonAsync<AdministrationTrustKeySummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(rotatedKey);
Assert.Equal(2, rotatedKey!.CurrentVersion);
var issuerResponse = await client.PostAsJsonAsync(
"/api/v1/administration/trust-signing/issuers",
new RegisterAdministrationTrustIssuerRequest(
Name: "Core Root CA",
IssuerUri: "https://issuer.core.example/root",
TrustLevel: "high"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, issuerResponse.StatusCode);
var issuer = await issuerResponse.Content.ReadFromJsonAsync<AdministrationTrustIssuerSummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(issuer);
var certificateResponse = await client.PostAsJsonAsync(
"/api/v1/administration/trust-signing/certificates",
new RegisterAdministrationTrustCertificateRequest(
KeyId: key.KeyId,
IssuerId: issuer!.IssuerId,
SerialNumber: "SER-2026-0001",
NotBefore: DateTimeOffset.Parse("2026-02-01T00:00:00Z"),
NotAfter: DateTimeOffset.Parse("2027-02-01T00:00:00Z")),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, certificateResponse.StatusCode);
var certificate = await certificateResponse.Content.ReadFromJsonAsync<AdministrationTrustCertificateSummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(certificate);
Assert.Equal("active", certificate!.Status);
var revokeCertificateResponse = await client.PostAsJsonAsync(
$"/api/v1/administration/trust-signing/certificates/{certificate.CertificateId}/revoke",
new RevokeAdministrationTrustCertificateRequest("scheduled_retirement", "IR-77"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, revokeCertificateResponse.StatusCode);
var revokedCertificate = await revokeCertificateResponse.Content.ReadFromJsonAsync<AdministrationTrustCertificateSummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(revokedCertificate);
Assert.Equal("revoked", revokedCertificate!.Status);
var revokeKeyResponse = await client.PostAsJsonAsync(
$"/api/v1/administration/trust-signing/keys/{key.KeyId}/revoke",
new RevokeAdministrationTrustKeyRequest("post-rotation retirement", "CHG-101"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, revokeKeyResponse.StatusCode);
var revokedKey = await revokeKeyResponse.Content.ReadFromJsonAsync<AdministrationTrustKeySummary>(
TestContext.Current.CancellationToken);
Assert.NotNull(revokedKey);
Assert.Equal("revoked", revokedKey!.Status);
var configureResponse = await client.PutAsJsonAsync(
"/api/v1/administration/trust-signing/transparency-log",
new ConfigureAdministrationTransparencyLogRequest(
LogUrl: "https://rekor.core.example",
WitnessUrl: "https://rekor-witness.core.example",
EnforceInclusion: true),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, configureResponse.StatusCode);
var transparencyConfig = await configureResponse.Content.ReadFromJsonAsync<AdministrationTransparencyLogConfig>(
TestContext.Current.CancellationToken);
Assert.NotNull(transparencyConfig);
Assert.Equal("https://rekor.core.example", transparencyConfig!.LogUrl);
Assert.True(transparencyConfig.EnforceInclusion);
var keys = await client.GetFromJsonAsync<PlatformListResponse<AdministrationTrustKeySummary>>(
"/api/v1/administration/trust-signing/keys?limit=10&offset=0",
TestContext.Current.CancellationToken);
Assert.NotNull(keys);
Assert.Single(keys!.Items);
Assert.Equal("revoked", keys.Items[0].Status);
var issuers = await client.GetFromJsonAsync<PlatformListResponse<AdministrationTrustIssuerSummary>>(
"/api/v1/administration/trust-signing/issuers?limit=10&offset=0",
TestContext.Current.CancellationToken);
Assert.NotNull(issuers);
Assert.Single(issuers!.Items);
var certificates = await client.GetFromJsonAsync<PlatformListResponse<AdministrationTrustCertificateSummary>>(
"/api/v1/administration/trust-signing/certificates?limit=10&offset=0",
TestContext.Current.CancellationToken);
Assert.NotNull(certificates);
Assert.Single(certificates!.Items);
Assert.Equal("revoked", certificates.Items[0].Status);
var transparency = await client.GetFromJsonAsync<PlatformItemResponse<AdministrationTransparencyLogConfig>>(
"/api/v1/administration/trust-signing/transparency-log",
TestContext.Current.CancellationToken);
Assert.NotNull(transparency);
Assert.Equal("https://rekor.core.example", transparency!.Item.LogUrl);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateTrustKey_WithDuplicateAlias_ReturnsConflict()
{
var tenantId = Guid.NewGuid().ToString("D");
using var client = CreateTenantClient(tenantId);
var request = new CreateAdministrationTrustKeyRequest("duplicate-key", "ed25519", null);
var first = await client.PostAsJsonAsync(
"/api/v1/administration/trust-signing/keys",
request,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, first.StatusCode);
var second = await client.PostAsJsonAsync(
"/api/v1/administration/trust-signing/keys",
request,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Conflict, second.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TrustSigningMutations_WithoutTenantHeader_ReturnsBadRequest()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync(
"/api/v1/administration/trust-signing/keys",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TrustSigningMutationEndpoints_RequireExpectedPolicies()
{
var endpoints = _factory.Services
.GetRequiredService<EndpointDataSource>()
.Endpoints
.OfType<RouteEndpoint>()
.ToArray();
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/keys", "GET", PlatformPolicies.TrustRead);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/keys", "POST", PlatformPolicies.TrustWrite);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/keys/{keyId:guid}/rotate", "POST", PlatformPolicies.TrustWrite);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/keys/{keyId:guid}/revoke", "POST", PlatformPolicies.TrustAdmin);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/issuers", "POST", PlatformPolicies.TrustWrite);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/certificates/{certificateId:guid}/revoke", "POST", PlatformPolicies.TrustAdmin);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/transparency-log", "GET", PlatformPolicies.TrustRead);
AssertPolicy(endpoints, "/api/v1/administration/trust-signing/transparency-log", "PUT", PlatformPolicies.TrustAdmin);
}
private static void AssertPolicy(
IReadOnlyList<RouteEndpoint> endpoints,
string routePattern,
string method,
string expectedPolicy)
{
var endpoint = endpoints.Single(candidate =>
string.Equals(candidate.RoutePattern.RawText, routePattern, StringComparison.Ordinal)
&& candidate.Metadata
.GetMetadata<HttpMethodMetadata>()?
.HttpMethods
.Contains(method, StringComparer.OrdinalIgnoreCase) == true);
var policies = endpoint.Metadata
.GetOrderedMetadata<IAuthorizeData>()
.Select(metadata => metadata.Policy)
.Where(static policy => !string.IsNullOrWhiteSpace(policy))
.ToArray();
Assert.Contains(expectedPolicy, policies);
}
private HttpClient CreateTenantClient(string tenantId)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
client.DefaultRequestHeaders.Add("X-StellaOps-Actor", "trust-signing-tests");
return client;
}
}

View File

@@ -0,0 +1,156 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using System.Net;
using System.Text.Json;
using StellaOps.Platform.WebService.Constants;
using StellaOps.TestKit;
namespace StellaOps.Platform.WebService.Tests;
public sealed class PackAdapterEndpointsTests : IClassFixture<PlatformWebApplicationFactory>
{
private readonly PlatformWebApplicationFactory _factory;
public PackAdapterEndpointsTests(PlatformWebApplicationFactory factory)
{
_factory = factory;
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DashboardSummary_IsDeterministicAndContainsPackFields()
{
using var client = CreateTenantClient($"tenant-dashboard-{Guid.NewGuid():N}");
var firstResponse = await client.GetAsync("/api/v1/dashboard/summary", TestContext.Current.CancellationToken);
var secondResponse = await client.GetAsync("/api/v1/dashboard/summary", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
var first = await firstResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var second = await secondResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.Equal(first, second);
using var document = JsonDocument.Parse(first);
var item = document.RootElement.GetProperty("item");
Assert.Equal("warning", item.GetProperty("dataConfidence").GetProperty("status").GetString());
Assert.Equal(2, item.GetProperty("environmentsWithCriticalReachable").GetInt32());
Assert.True(item.GetProperty("topDrivers").GetArrayLength() >= 1);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PackAdapterRoutes_ReturnSuccessAndStableOrdering()
{
using var client = CreateTenantClient($"tenant-ops-{Guid.NewGuid():N}");
var endpoints = new[]
{
"/api/v1/platform/data-integrity/summary",
"/api/v1/platform/data-integrity/report",
"/api/v1/platform/feeds/freshness",
"/api/v1/platform/scan-pipeline/health",
"/api/v1/platform/reachability/ingest-health",
"/api/v1/administration/summary",
"/api/v1/administration/identity-access",
"/api/v1/administration/tenant-branding",
"/api/v1/administration/notifications",
"/api/v1/administration/usage-limits",
"/api/v1/administration/policy-governance",
"/api/v1/administration/trust-signing",
"/api/v1/administration/system",
};
foreach (var endpoint in endpoints)
{
var response = await client.GetAsync(endpoint, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
var feedsResponse = await client.GetStringAsync("/api/v1/platform/feeds/freshness", TestContext.Current.CancellationToken);
using var feedsDocument = JsonDocument.Parse(feedsResponse);
var sources = feedsDocument.RootElement
.GetProperty("items")
.EnumerateArray()
.Select(item => item.GetProperty("source").GetString()!)
.ToArray();
var ordered = sources.OrderBy(source => source, StringComparer.Ordinal).ToArray();
Assert.Equal(ordered, sources);
var administrationSummary = await client.GetStringAsync("/api/v1/administration/summary", TestContext.Current.CancellationToken);
using var administrationSummaryDocument = JsonDocument.Parse(administrationSummary);
var actionPaths = administrationSummaryDocument.RootElement
.GetProperty("item")
.GetProperty("domains")
.EnumerateArray()
.Select(domain => domain.GetProperty("actionPath").GetString()!)
.ToArray();
Assert.Contains("/administration/identity-access", actionPaths);
Assert.Contains("/administration/tenant-branding", actionPaths);
var identityAccess = await client.GetStringAsync("/api/v1/administration/identity-access", TestContext.Current.CancellationToken);
using var identityAccessDocument = JsonDocument.Parse(identityAccess);
var tabs = identityAccessDocument.RootElement
.GetProperty("item")
.GetProperty("tabs")
.EnumerateArray()
.Select(tab => tab.GetProperty("tabId").GetString()!)
.ToArray();
Assert.Contains("users", tabs);
Assert.Contains("api-tokens", tabs);
var policyGovernance = await client.GetStringAsync("/api/v1/administration/policy-governance", TestContext.Current.CancellationToken);
using var policyDocument = JsonDocument.Parse(policyGovernance);
var aliases = policyDocument.RootElement
.GetProperty("item")
.GetProperty("legacyAliases")
.EnumerateArray()
.Select(alias => alias.GetProperty("legacyPath").GetString()!)
.ToArray();
var orderedAliases = aliases.OrderBy(path => path, StringComparer.Ordinal).ToArray();
Assert.Equal(orderedAliases, aliases);
Assert.Contains("/policy/governance", aliases);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DashboardSummary_WithoutTenantHeader_ReturnsBadRequest()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v1/dashboard/summary", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TrustSigningEndpoint_RequiresTrustReadPolicy()
{
var dataSource = _factory.Services.GetRequiredService<EndpointDataSource>();
var trustEndpoint = dataSource.Endpoints
.OfType<RouteEndpoint>()
.Single(endpoint => string.Equals(endpoint.RoutePattern.RawText, "/api/v1/administration/trust-signing", StringComparison.Ordinal));
var policies = trustEndpoint.Metadata
.GetOrderedMetadata<IAuthorizeData>()
.Select(metadata => metadata.Policy)
.Where(static policy => !string.IsNullOrWhiteSpace(policy))
.ToArray();
Assert.Contains(PlatformPolicies.TrustRead, policies);
Assert.DoesNotContain(PlatformPolicies.SetupRead, policies);
}
private HttpClient CreateTenantClient(string tenantId)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
client.DefaultRequestHeaders.Add("X-StellaOps-Actor", "pack-adapter-tests");
return client;
}
}

View File

@@ -0,0 +1,131 @@
using System.Net;
using System.Net.Http.Json;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Platform.WebService.Tests;
public sealed class ReleaseControlEndpointsTests : IClassFixture<PlatformWebApplicationFactory>
{
private readonly PlatformWebApplicationFactory _factory;
public ReleaseControlEndpointsTests(PlatformWebApplicationFactory factory)
{
_factory = factory;
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BundleLifecycle_CreateListPublishAndMaterialize_Works()
{
var tenantId = Guid.NewGuid().ToString("D");
using var client = CreateTenantClient(tenantId);
var createResponse = await client.PostAsJsonAsync(
"/api/v1/release-control/bundles",
new CreateReleaseControlBundleRequest("checkout-service", "Checkout Service", "primary checkout flow"),
TestContext.Current.CancellationToken);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<ReleaseControlBundleDetail>(
TestContext.Current.CancellationToken);
Assert.NotNull(created);
var list = await client.GetFromJsonAsync<PlatformListResponse<ReleaseControlBundleSummary>>(
"/api/v1/release-control/bundles?limit=20&offset=0",
TestContext.Current.CancellationToken);
Assert.NotNull(list);
Assert.Single(list!.Items);
Assert.Equal(created!.Id, list.Items[0].Id);
var publishRequest = new PublishReleaseControlBundleVersionRequest(
Changelog: "initial release",
Components:
[
new ReleaseControlBundleComponentInput(
ComponentVersionId: "checkout@1.0.0",
ComponentName: "checkout",
ImageDigest: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
DeployOrder: 10,
MetadataJson: "{\"track\":\"stable\"}")
]);
var publishResponse = await client.PostAsJsonAsync(
$"/api/v1/release-control/bundles/{created.Id}/versions",
publishRequest,
TestContext.Current.CancellationToken);
publishResponse.EnsureSuccessStatusCode();
var version = await publishResponse.Content.ReadFromJsonAsync<ReleaseControlBundleVersionDetail>(
TestContext.Current.CancellationToken);
Assert.NotNull(version);
Assert.Equal(1, version!.VersionNumber);
Assert.StartsWith("sha256:", version.Digest, StringComparison.Ordinal);
Assert.Single(version.Components);
var materializeResponse = await client.PostAsJsonAsync(
$"/api/v1/release-control/bundles/{created.Id}/versions/{version.Id}/materialize",
new MaterializeReleaseControlBundleVersionRequest("prod-eu", "promotion", "idem-001"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Accepted, materializeResponse.StatusCode);
var materialization = await materializeResponse.Content.ReadFromJsonAsync<ReleaseControlBundleMaterializationRun>(
TestContext.Current.CancellationToken);
Assert.NotNull(materialization);
Assert.Equal("queued", materialization!.Status);
var secondMaterializeResponse = await client.PostAsJsonAsync(
$"/api/v1/release-control/bundles/{created.Id}/versions/{version.Id}/materialize",
new MaterializeReleaseControlBundleVersionRequest("prod-eu", "promotion", "idem-001"),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Accepted, secondMaterializeResponse.StatusCode);
var duplicateMaterialization = await secondMaterializeResponse.Content.ReadFromJsonAsync<ReleaseControlBundleMaterializationRun>(
TestContext.Current.CancellationToken);
Assert.NotNull(duplicateMaterialization);
Assert.Equal(materialization.RunId, duplicateMaterialization!.RunId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateBundle_WithDuplicateSlug_ReturnsConflict()
{
var tenantId = Guid.NewGuid().ToString("D");
using var client = CreateTenantClient(tenantId);
var request = new CreateReleaseControlBundleRequest("payments", "Payments", null);
var first = await client.PostAsJsonAsync(
"/api/v1/release-control/bundles",
request,
TestContext.Current.CancellationToken);
first.EnsureSuccessStatusCode();
var second = await client.PostAsJsonAsync(
"/api/v1/release-control/bundles",
request,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Conflict, second.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListBundles_WithoutTenantHeader_ReturnsBadRequest()
{
using var client = _factory.CreateClient();
var response = await client.GetAsync(
"/api/v1/release-control/bundles",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
private HttpClient CreateTenantClient(string tenantId)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
client.DefaultRequestHeaders.Add("X-StellaOps-Actor", "release-control-test");
return client;
}
}

View File

@@ -5,6 +5,8 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| Task ID | Status | Notes |
| --- | --- | --- |
| PACK-ADM-01-T | DONE | Added/verified `PackAdapterEndpointsTests` coverage for `/api/v1/administration/{summary,identity-access,tenant-branding,notifications,usage-limits,policy-governance,trust-signing,system}` and deterministic alias ordering assertions. |
| PACK-ADM-02-T | DONE | Added `AdministrationTrustSigningMutationEndpointsTests` covering trust-owner key/issuer/certificate/transparency lifecycle plus route metadata policy bindings for `platform.trust.read`, `platform.trust.write`, and `platform.trust.admin`. |
| AUDIT-0762-M | DONE | Revalidated 2026-01-07 (test project). |
| AUDIT-0762-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0762-A | DONE | Waived (test project; revalidated 2026-01-07). |