partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ExportSurfacingClientTests.cs
|
||||
// Sprint: SPRINT_20260208_036_ExportCenter_cli_ui_surfacing_of_hidden_backend_capabilities
|
||||
// Description: Unit tests for IExportSurfacingClient, ExportSurfacingClient, and models.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.Client.Models;
|
||||
|
||||
namespace StellaOps.ExportCenter.Client.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ExportSurfacingModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateExportProfileRequest_DefaultValues()
|
||||
{
|
||||
var req = new CreateExportProfileRequest("my-profile");
|
||||
|
||||
req.Name.Should().Be("my-profile");
|
||||
req.Adapter.Should().Be("default");
|
||||
req.OutputFormat.Should().Be("tar.gz");
|
||||
req.SigningEnabled.Should().BeFalse();
|
||||
req.Description.Should().BeNull();
|
||||
req.Selectors.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateExportProfileRequest_AllNull()
|
||||
{
|
||||
var req = new UpdateExportProfileRequest();
|
||||
|
||||
req.Name.Should().BeNull();
|
||||
req.Description.Should().BeNull();
|
||||
req.Selectors.Should().BeNull();
|
||||
req.OutputFormat.Should().BeNull();
|
||||
req.SigningEnabled.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartExportRunRequest_DefaultValues()
|
||||
{
|
||||
var req = new StartExportRunRequest();
|
||||
|
||||
req.CorrelationId.Should().BeNull();
|
||||
req.CallbackUrl.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportArtifact_Serialization_Roundtrip()
|
||||
{
|
||||
var artifact = new ExportArtifact(
|
||||
"art-001", "run-001", "sbom.cdx.json",
|
||||
"application/json", 1024, "sha256:abc",
|
||||
DateTimeOffset.Parse("2026-01-01T00:00:00Z"));
|
||||
|
||||
var json = JsonSerializer.Serialize(artifact);
|
||||
var deserialized = JsonSerializer.Deserialize<ExportArtifact>(json);
|
||||
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.ArtifactId.Should().Be("art-001");
|
||||
deserialized.Name.Should().Be("sbom.cdx.json");
|
||||
deserialized.Size.Should().Be(1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportArtifactListResponse_EmptyList()
|
||||
{
|
||||
var resp = new ExportArtifactListResponse([], 0);
|
||||
|
||||
resp.Artifacts.Should().BeEmpty();
|
||||
resp.Total.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyExportRunRequest_DefaultsAllTrue()
|
||||
{
|
||||
var req = new VerifyExportRunRequest();
|
||||
|
||||
req.CheckHashes.Should().BeTrue();
|
||||
req.CheckSignatures.Should().BeTrue();
|
||||
req.CheckManifest.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportVerificationResult_Verified()
|
||||
{
|
||||
var result = new ExportVerificationResult(
|
||||
"run-001", true, [], [], true, DateTimeOffset.UtcNow);
|
||||
|
||||
result.Verified.Should().BeTrue();
|
||||
result.ManifestValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HashVerificationEntry_Match()
|
||||
{
|
||||
var entry = new HashVerificationEntry(
|
||||
"sbom.json", "sha256:abc", "sha256:abc", true);
|
||||
|
||||
entry.Match.Should().BeTrue();
|
||||
entry.ArtifactName.Should().Be("sbom.json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HashVerificationEntry_Mismatch()
|
||||
{
|
||||
var entry = new HashVerificationEntry(
|
||||
"sbom.json", "sha256:abc", "sha256:def", false);
|
||||
|
||||
entry.Match.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignatureVerificationEntry_Valid()
|
||||
{
|
||||
var entry = new SignatureVerificationEntry(
|
||||
"signer-1", "ES256", true, null);
|
||||
|
||||
entry.Valid.Should().BeTrue();
|
||||
entry.Message.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportManifest_WithEntries()
|
||||
{
|
||||
var manifest = new ExportManifest(
|
||||
"run-001", "profile-001",
|
||||
[new ExportManifestEntry("file.json", "sha256:abc", 512, "application/json")],
|
||||
DateTimeOffset.UtcNow, "sha256:manifest-digest");
|
||||
|
||||
manifest.Artifacts.Should().HaveCount(1);
|
||||
manifest.Artifacts[0].Name.Should().Be("file.json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportAttestationStatus_Signed()
|
||||
{
|
||||
var status = new ExportAttestationStatus(
|
||||
"run-001", true, "signer-1", "ES256",
|
||||
DateTimeOffset.UtcNow, "log-entry-001");
|
||||
|
||||
status.Signed.Should().BeTrue();
|
||||
status.TransparencyLogEntryId.Should().Be("log-entry-001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCapability_Properties()
|
||||
{
|
||||
var cap = new ExportCapability(
|
||||
"Profiles", "Profile CRUD", "/v1/exports/profiles", true, true);
|
||||
|
||||
cap.Name.Should().Be("Profiles");
|
||||
cap.Available.Should().BeTrue();
|
||||
cap.RequiresAuth.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCapabilitySummary_TotalCapabilities()
|
||||
{
|
||||
var caps = new ExportCapabilitySummary(
|
||||
[
|
||||
new ExportCapability("A", "desc", "/a", true, false),
|
||||
new ExportCapability("B", "desc", "/b", false, true)
|
||||
],
|
||||
1, 1);
|
||||
|
||||
caps.TotalCapabilities.Should().Be(2);
|
||||
caps.TotalAvailable.Should().Be(1);
|
||||
caps.TotalUnavailable.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartExportRunResponse_Properties()
|
||||
{
|
||||
var resp = new StartExportRunResponse("run-001", "Queued", "profile-001");
|
||||
|
||||
resp.RunId.Should().Be("run-001");
|
||||
resp.Status.Should().Be("Queued");
|
||||
resp.ProfileId.Should().Be("profile-001");
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ExportSurfacingClientTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_NullHttp_Throws()
|
||||
{
|
||||
var act = () => new ExportSurfacingClient(null!, NullLogger<ExportSurfacingClient>.Instance);
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullLogger_Throws()
|
||||
{
|
||||
var act = () => new ExportSurfacingClient(new HttpClient(), null!);
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverCapabilities_ReturnsAllKnownCapabilities()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var result = await client.DiscoverCapabilitiesAsync();
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Capabilities.Should().NotBeEmpty();
|
||||
result.TotalCapabilities.Should().BeGreaterThan(10);
|
||||
result.TotalAvailable.Should().Be(result.TotalCapabilities); // all known are available
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverCapabilities_IncludesProfilesCapability()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var result = await client.DiscoverCapabilitiesAsync();
|
||||
|
||||
result.Capabilities.Should().Contain(c => c.Name == "Profiles");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverCapabilities_IncludesVerificationCapability()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var result = await client.DiscoverCapabilitiesAsync();
|
||||
|
||||
result.Capabilities.Should().Contain(c => c.Name == "Verification");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverCapabilities_IncludesAuditBundlesCapability()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var result = await client.DiscoverCapabilitiesAsync();
|
||||
|
||||
result.Capabilities.Should().Contain(c => c.Name == "Audit Bundles");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverCapabilities_OpenApiIsAnonymous()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var result = await client.DiscoverCapabilitiesAsync();
|
||||
|
||||
var openApi = result.Capabilities.First(c => c.Name == "OpenAPI Discovery");
|
||||
openApi.RequiresAuth.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateProfile_NullRequest_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.CreateProfileAsync(null!);
|
||||
await act.Should().ThrowAsync<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveProfile_EmptyId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.ArchiveProfileAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelRun_EmptyId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.CancelRunAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartRun_EmptyProfileId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.StartRunAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListArtifacts_EmptyRunId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.ListArtifactsAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetArtifact_NullIds_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.GetArtifactAsync("", "art-1");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyRun_EmptyRunId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.VerifyRunAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetManifest_EmptyRunId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.GetManifestAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAttestationStatus_EmptyRunId_Throws()
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var act = () => client.GetAttestationStatusAsync("");
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
private static ExportSurfacingClient CreateClient()
|
||||
{
|
||||
var http = new HttpClient { BaseAddress = new Uri("http://localhost:5000") };
|
||||
return new ExportSurfacingClient(http, NullLogger<ExportSurfacingClient>.Instance);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user