Gaps fill up, fixes, ui restructuring
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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). |
|
||||
|
||||
Reference in New Issue
Block a user