stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -0,0 +1,51 @@
// <copyright file="AiAttestationServiceTests.ClaimAttestation.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task CreateClaimAttestation_CreatesAndRetrievesClaimAsync()
{
var claimAttestation = CreateSampleClaimAttestation();
var result = await _service.CreateClaimAttestationAsync(claimAttestation);
result.AttestationId.Should().Be(claimAttestation.ClaimId);
result.Digest.Should().StartWith("sha256:");
}
[Fact]
public async Task GetClaimAttestations_ReturnsClaimsForRunAsync()
{
var runId = "run-with-claims";
var claim1 = CreateSampleClaimAttestation() with { ClaimId = "claim-1", RunId = runId };
var claim2 = CreateSampleClaimAttestation() with { ClaimId = "claim-2", RunId = runId };
var claim3 = CreateSampleClaimAttestation() with { ClaimId = "claim-3", RunId = "other-run" };
await _service.CreateClaimAttestationAsync(claim1);
await _service.CreateClaimAttestationAsync(claim2);
await _service.CreateClaimAttestationAsync(claim3);
var claims = await _service.GetClaimAttestationsAsync(runId);
claims.Should().HaveCount(2);
claims.Should().AllSatisfy(c => c.RunId.Should().Be(runId));
}
[Fact]
public async Task VerifyClaimAttestation_ValidClaim_ReturnsValidAsync()
{
var claimAttestation = CreateSampleClaimAttestation();
await _service.CreateClaimAttestationAsync(claimAttestation, sign: true);
var result = await _service.VerifyClaimAttestationAsync(claimAttestation.ClaimId);
result.Valid.Should().BeTrue();
result.DigestValid.Should().BeTrue();
}
}

View File

@@ -0,0 +1,44 @@
// <copyright file="AiAttestationServiceTests.ListRecent.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task ListRecentAttestations_FiltersByTenantAsync()
{
var tenant1Run = CreateSampleRunAttestation() with { RunId = "run-t1", TenantId = "tenant-1" };
var tenant2Run = CreateSampleRunAttestation() with { RunId = "run-t2", TenantId = "tenant-2" };
await _service.CreateRunAttestationAsync(tenant1Run);
await _service.CreateRunAttestationAsync(tenant2Run);
var tenant1Attestations = await _service.ListRecentAttestationsAsync("tenant-1");
tenant1Attestations.Should().HaveCount(1);
tenant1Attestations[0].TenantId.Should().Be("tenant-1");
}
[Fact]
public async Task ListRecentAttestations_RespectsLimitAsync()
{
for (int i = 0; i < 10; i++)
{
var attestation = CreateSampleRunAttestation() with
{
RunId = $"run-{i}",
TenantId = "tenant-test"
};
await _service.CreateRunAttestationAsync(attestation);
_timeProvider.Advance(TimeSpan.FromSeconds(1));
}
var recent = await _service.ListRecentAttestationsAsync("tenant-test", limit: 5);
recent.Should().HaveCount(5);
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="AiAttestationServiceTests.RunAttestation.Create.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task CreateRunAttestation_WithSigning_ReturnsSignedResultAsync()
{
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation, sign: true);
result.AttestationId.Should().Be(attestation.RunId);
result.Signed.Should().BeTrue();
result.DsseEnvelope.Should().NotBeNullOrEmpty();
result.Digest.Should().StartWith("sha256:");
result.StorageUri.Should().Contain(attestation.RunId);
}
[Fact]
public async Task CreateRunAttestation_WithoutSigning_ReturnsUnsignedResultAsync()
{
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation, sign: false);
result.Signed.Should().BeFalse();
result.DsseEnvelope.Should().BeNull();
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="AiAttestationServiceTests.RunAttestation.Read.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task GetRunAttestation_AfterCreation_ReturnsAttestationAsync()
{
var attestation = CreateSampleRunAttestation();
await _service.CreateRunAttestationAsync(attestation);
var retrieved = await _service.GetRunAttestationAsync(attestation.RunId);
retrieved.Should().NotBeNull();
retrieved!.RunId.Should().Be(attestation.RunId);
retrieved.TenantId.Should().Be(attestation.TenantId);
retrieved.Model.Provider.Should().Be(attestation.Model.Provider);
}
[Fact]
public async Task GetRunAttestation_NotFound_ReturnsNullAsync()
{
var result = await _service.GetRunAttestationAsync("non-existent");
result.Should().BeNull();
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="AiAttestationServiceTests.RunAttestation.Verify.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task VerifyRunAttestation_ValidAttestation_ReturnsValidAsync()
{
var attestation = CreateSampleRunAttestation();
await _service.CreateRunAttestationAsync(attestation, sign: true);
var result = await _service.VerifyRunAttestationAsync(attestation.RunId);
result.Valid.Should().BeTrue();
result.DigestValid.Should().BeTrue();
result.SignatureValid.Should().BeTrue();
result.SigningKeyId.Should().NotBeNull();
}
[Fact]
public async Task VerifyRunAttestation_NotFound_ReturnsInvalidAsync()
{
var result = await _service.VerifyRunAttestationAsync("non-existent");
result.Valid.Should().BeFalse();
result.FailureReason.Should().Contain("not found");
}
}

View File

@@ -0,0 +1,22 @@
// <copyright file="AiAttestationServiceTests.TimeProvider.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiAttestationServiceTests
{
[Fact]
public async Task CreatedAt_UsesTimeProviderAsync()
{
var fixedTime = FixedUtcNow.AddHours(1);
_timeProvider.SetUtcNow(fixedTime);
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation);
result.CreatedAt.Should().Be(fixedTime);
}
}

View File

@@ -1,11 +1,9 @@
// <copyright file="AiAttestationServiceTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.AdvisoryAI.Attestation;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
@@ -15,184 +13,29 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests;
/// Tests for <see cref="AiAttestationService"/>.
/// </summary>
[Trait("Category", "Unit")]
public class AiAttestationServiceTests
public sealed partial class AiAttestationServiceTests
{
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private const string DefaultRunId = "run-001";
private const string DefaultClaimId = "claim-001";
private readonly FakeTimeProvider _timeProvider = new();
private readonly AiAttestationService _service;
public AiAttestationServiceTests()
{
_service = new AiAttestationService(
_timeProvider,
NullLogger<AiAttestationService>.Instance);
}
[Fact]
public async Task CreateRunAttestationAsync_WithSigning_ReturnsSignedResult()
{
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation, sign: true);
result.AttestationId.Should().Be(attestation.RunId);
result.Signed.Should().BeTrue();
result.DsseEnvelope.Should().NotBeNullOrEmpty();
result.Digest.Should().StartWith("sha256:");
result.StorageUri.Should().Contain(attestation.RunId);
}
[Fact]
public async Task CreateRunAttestationAsync_WithoutSigning_ReturnsUnsignedResult()
{
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation, sign: false);
result.Signed.Should().BeFalse();
result.DsseEnvelope.Should().BeNull();
}
[Fact]
public async Task GetRunAttestationAsync_AfterCreation_ReturnsAttestation()
{
var attestation = CreateSampleRunAttestation();
await _service.CreateRunAttestationAsync(attestation);
var retrieved = await _service.GetRunAttestationAsync(attestation.RunId);
retrieved.Should().NotBeNull();
retrieved!.RunId.Should().Be(attestation.RunId);
retrieved.TenantId.Should().Be(attestation.TenantId);
retrieved.Model.Provider.Should().Be(attestation.Model.Provider);
}
[Fact]
public async Task GetRunAttestationAsync_NotFound_ReturnsNull()
{
var result = await _service.GetRunAttestationAsync("non-existent");
result.Should().BeNull();
}
[Fact]
public async Task VerifyRunAttestationAsync_ValidAttestation_ReturnsValid()
{
var attestation = CreateSampleRunAttestation();
await _service.CreateRunAttestationAsync(attestation, sign: true);
var result = await _service.VerifyRunAttestationAsync(attestation.RunId);
result.Valid.Should().BeTrue();
result.DigestValid.Should().BeTrue();
result.SignatureValid.Should().BeTrue();
result.SigningKeyId.Should().NotBeNull();
}
[Fact]
public async Task VerifyRunAttestationAsync_NotFound_ReturnsInvalid()
{
var result = await _service.VerifyRunAttestationAsync("non-existent");
result.Valid.Should().BeFalse();
result.FailureReason.Should().Contain("not found");
}
[Fact]
public async Task CreateClaimAttestationAsync_CreatesAndRetrievesClaim()
{
var claimAttestation = CreateSampleClaimAttestation();
var result = await _service.CreateClaimAttestationAsync(claimAttestation);
result.AttestationId.Should().Be(claimAttestation.ClaimId);
result.Digest.Should().StartWith("sha256:");
}
[Fact]
public async Task GetClaimAttestationsAsync_ReturnsClaimsForRun()
{
var runId = "run-with-claims";
var claim1 = CreateSampleClaimAttestation() with { ClaimId = "claim-1", RunId = runId };
var claim2 = CreateSampleClaimAttestation() with { ClaimId = "claim-2", RunId = runId };
var claim3 = CreateSampleClaimAttestation() with { ClaimId = "claim-3", RunId = "other-run" };
await _service.CreateClaimAttestationAsync(claim1);
await _service.CreateClaimAttestationAsync(claim2);
await _service.CreateClaimAttestationAsync(claim3);
var claims = await _service.GetClaimAttestationsAsync(runId);
claims.Should().HaveCount(2);
claims.Should().AllSatisfy(c => c.RunId.Should().Be(runId));
}
[Fact]
public async Task VerifyClaimAttestationAsync_ValidClaim_ReturnsValid()
{
var claimAttestation = CreateSampleClaimAttestation();
await _service.CreateClaimAttestationAsync(claimAttestation, sign: true);
var result = await _service.VerifyClaimAttestationAsync(claimAttestation.ClaimId);
result.Valid.Should().BeTrue();
result.DigestValid.Should().BeTrue();
}
[Fact]
public async Task ListRecentAttestationsAsync_FiltersByTenant()
{
var tenant1Run = CreateSampleRunAttestation() with { RunId = "run-t1", TenantId = "tenant-1" };
var tenant2Run = CreateSampleRunAttestation() with { RunId = "run-t2", TenantId = "tenant-2" };
await _service.CreateRunAttestationAsync(tenant1Run);
await _service.CreateRunAttestationAsync(tenant2Run);
var tenant1Attestations = await _service.ListRecentAttestationsAsync("tenant-1");
tenant1Attestations.Should().HaveCount(1);
tenant1Attestations[0].TenantId.Should().Be("tenant-1");
}
[Fact]
public async Task ListRecentAttestationsAsync_RespectsLimit()
{
for (int i = 0; i < 10; i++)
{
var attestation = CreateSampleRunAttestation() with
{
RunId = $"run-{i}",
TenantId = "tenant-test"
};
await _service.CreateRunAttestationAsync(attestation);
_timeProvider.Advance(TimeSpan.FromSeconds(1));
}
var recent = await _service.ListRecentAttestationsAsync("tenant-test", limit: 5);
recent.Should().HaveCount(5);
}
[Fact]
public async Task CreatedAt_UsesTimeProvider()
{
var fixedTime = new DateTimeOffset(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
_timeProvider.SetUtcNow(fixedTime);
var attestation = CreateSampleRunAttestation();
var result = await _service.CreateRunAttestationAsync(attestation);
result.CreatedAt.Should().Be(fixedTime);
_timeProvider.SetUtcNow(FixedUtcNow);
_service = new AiAttestationService(_timeProvider, NullLogger<AiAttestationService>.Instance);
}
private static AiRunAttestation CreateSampleRunAttestation()
{
return new AiRunAttestation
{
RunId = $"run-{Guid.NewGuid():N}",
RunId = DefaultRunId,
TenantId = "tenant-test",
UserId = "user:test@example.com",
StartedAt = DateTimeOffset.Parse("2026-01-09T12:00:00Z"),
CompletedAt = DateTimeOffset.Parse("2026-01-09T12:05:00Z"),
StartedAt = FixedUtcNow,
CompletedAt = FixedUtcNow.AddMinutes(5),
Model = new AiModelInfo
{
Provider = "anthropic",
@@ -207,14 +50,14 @@ public class AiAttestationServiceTests
{
return new AiClaimAttestation
{
ClaimId = $"claim-{Guid.NewGuid():N}",
RunId = "run-xyz",
ClaimId = DefaultClaimId,
RunId = DefaultRunId,
TurnId = "turn-001",
TenantId = "tenant-test",
ClaimText = "Test claim",
ClaimDigest = "sha256:test",
GroundingScore = 0.85,
Timestamp = DateTimeOffset.Parse("2026-01-09T12:00:00Z"),
Timestamp = FixedUtcNow,
ContentDigest = "sha256:content-test"
};
}

View File

@@ -0,0 +1,34 @@
// <copyright file="AiClaimAttestationTests.Digest.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiClaimAttestationTests
{
[Fact]
public void ComputeDigest_SameClaim_ReturnsSameDigest()
{
var attestation = CreateSampleClaimAttestation();
var digest1 = attestation.ComputeDigest();
var digest2 = attestation.ComputeDigest();
digest1.Should().Be(digest2);
digest1.Should().StartWith("sha256:");
}
[Fact]
public void ComputeDigest_DifferentClaims_ReturnsDifferentDigests()
{
var attestation1 = CreateSampleClaimAttestation();
var attestation2 = attestation1 with { ClaimText = "Different claim text" };
var digest1 = attestation1.ComputeDigest();
var digest2 = attestation2.ComputeDigest();
digest1.Should().NotBe(digest2);
}
}

View File

@@ -0,0 +1,78 @@
// <copyright file="AiClaimAttestationTests.Evidence.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiClaimAttestationTests
{
[Fact]
public void FromClaimEvidence_CreatesValidAttestation()
{
var evidence = new ClaimEvidence
{
Text = "This component is affected by the vulnerability",
Position = 45,
Length = 47,
GroundedBy = ["stella://sbom/abc123", "stella://reach/api:func"],
GroundingScore = 0.95,
Verified = true,
Category = ClaimCategory.Factual
};
var attestation = AiClaimAttestation.FromClaimEvidence(
evidence,
runId: "run-123",
turnId: "turn-456",
tenantId: "tenant-xyz",
timestamp: FixedUtcNow);
attestation.ClaimText.Should().Be(evidence.Text);
attestation.RunId.Should().Be("run-123");
attestation.TurnId.Should().Be("turn-456");
attestation.TenantId.Should().Be("tenant-xyz");
attestation.GroundedBy.Should().HaveCount(2);
attestation.GroundingScore.Should().Be(0.95);
attestation.Verified.Should().BeTrue();
attestation.Category.Should().Be(ClaimCategory.Factual);
attestation.ClaimDigest.Should().StartWith("sha256:");
}
[Fact]
public void ClaimDigest_IsDeterministic()
{
var evidence1 = new ClaimEvidence
{
Text = "Same text",
Position = 0,
Length = 9,
GroundingScore = 0.9
};
var evidence2 = new ClaimEvidence
{
Text = "Same text",
Position = 100,
Length = 9,
GroundingScore = 0.5
};
var attestation1 = AiClaimAttestation.FromClaimEvidence(
evidence1,
"run-1",
"turn-1",
"tenant-1",
FixedUtcNow);
var attestation2 = AiClaimAttestation.FromClaimEvidence(
evidence2,
"run-2",
"turn-2",
"tenant-2",
FixedUtcNow);
attestation1.ClaimDigest.Should().Be(attestation2.ClaimDigest);
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="AiClaimAttestationTests.GroundedBy.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiClaimAttestationTests
{
[Fact]
public void Attestation_WithGrounding_PreservesEvidenceUris()
{
var groundedBy = ImmutableArray.Create(
"stella://sbom/abc123",
"stella://reach/api:vulnFunc",
"stella://vex/CVE-2023-44487");
var attestation = CreateSampleClaimAttestation() with
{
GroundedBy = groundedBy
};
attestation.GroundedBy.Should().HaveCount(3);
attestation.GroundedBy.Should().Contain("stella://sbom/abc123");
}
}

View File

@@ -0,0 +1,17 @@
// <copyright file="AiClaimAttestationTests.Predicate.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiClaimAttestationTests
{
[Fact]
public void PredicateType_IsCorrect()
{
AiClaimAttestation.PredicateType.Should().Be("https://stellaops.org/attestation/ai-claim/v1");
}
}

View File

@@ -1,9 +1,6 @@
// <copyright file="AiClaimAttestationTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
@@ -13,114 +10,9 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests;
/// Tests for <see cref="AiClaimAttestation"/>.
/// </summary>
[Trait("Category", "Unit")]
public class AiClaimAttestationTests
public sealed partial class AiClaimAttestationTests
{
[Fact]
public void PredicateType_IsCorrect()
{
AiClaimAttestation.PredicateType.Should().Be("https://stellaops.org/attestation/ai-claim/v1");
}
[Fact]
public void ComputeDigest_SameClaim_ReturnsSameDigest()
{
var attestation = CreateSampleClaimAttestation();
var digest1 = attestation.ComputeDigest();
var digest2 = attestation.ComputeDigest();
digest1.Should().Be(digest2);
digest1.Should().StartWith("sha256:");
}
[Fact]
public void ComputeDigest_DifferentClaims_ReturnsDifferentDigests()
{
var attestation1 = CreateSampleClaimAttestation();
var attestation2 = attestation1 with { ClaimText = "Different claim text" };
var digest1 = attestation1.ComputeDigest();
var digest2 = attestation2.ComputeDigest();
digest1.Should().NotBe(digest2);
}
[Fact]
public void FromClaimEvidence_CreatesValidAttestation()
{
var evidence = new ClaimEvidence
{
Text = "This component is affected by the vulnerability",
Position = 45,
Length = 47,
GroundedBy = ["stella://sbom/abc123", "stella://reach/api:func"],
GroundingScore = 0.95,
Verified = true,
Category = ClaimCategory.Factual
};
var attestation = AiClaimAttestation.FromClaimEvidence(
evidence,
runId: "run-123",
turnId: "turn-456",
tenantId: "tenant-xyz",
timestamp: DateTimeOffset.Parse("2026-01-09T12:00:00Z"));
attestation.ClaimText.Should().Be(evidence.Text);
attestation.RunId.Should().Be("run-123");
attestation.TurnId.Should().Be("turn-456");
attestation.TenantId.Should().Be("tenant-xyz");
attestation.GroundedBy.Should().HaveCount(2);
attestation.GroundingScore.Should().Be(0.95);
attestation.Verified.Should().BeTrue();
attestation.Category.Should().Be(ClaimCategory.Factual);
attestation.ClaimDigest.Should().StartWith("sha256:");
}
[Fact]
public void ClaimDigest_IsDeterministic()
{
var evidence1 = new ClaimEvidence
{
Text = "Same text",
Position = 0,
Length = 9,
GroundingScore = 0.9
};
var evidence2 = new ClaimEvidence
{
Text = "Same text",
Position = 100, // Different position
Length = 9,
GroundingScore = 0.5 // Different score
};
var attestation1 = AiClaimAttestation.FromClaimEvidence(
evidence1, "run-1", "turn-1", "tenant-1", DateTimeOffset.UtcNow);
var attestation2 = AiClaimAttestation.FromClaimEvidence(
evidence2, "run-2", "turn-2", "tenant-2", DateTimeOffset.UtcNow);
// ClaimDigest should be same because it's based on text only
attestation1.ClaimDigest.Should().Be(attestation2.ClaimDigest);
}
[Fact]
public void Attestation_WithGrounding_PreservesEvidenceUris()
{
var groundedBy = ImmutableArray.Create(
"stella://sbom/abc123",
"stella://reach/api:vulnFunc",
"stella://vex/CVE-2023-44487");
var attestation = CreateSampleClaimAttestation() with
{
GroundedBy = groundedBy
};
attestation.GroundedBy.Should().HaveCount(3);
attestation.GroundedBy.Should().Contain("stella://sbom/abc123");
}
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private static AiClaimAttestation CreateSampleClaimAttestation()
{
@@ -136,7 +28,7 @@ public class AiClaimAttestationTests
GroundedBy = ["stella://sbom/test"],
GroundingScore = 0.85,
Verified = true,
Timestamp = DateTimeOffset.Parse("2026-01-09T12:00:00Z"),
Timestamp = FixedUtcNow,
ContentDigest = "sha256:content123"
};
}

View File

@@ -0,0 +1,28 @@
// <copyright file="AiRunAttestationTests.Context.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiRunAttestationTests
{
[Fact]
public void Attestation_WithContext_PreservesContext()
{
var context = new AiRunContext
{
FindingId = "finding-123",
CveId = "CVE-2023-44487",
Component = "pkg:npm/http2@1.0.0"
};
var attestation = CreateSampleAttestation() with { Context = context };
attestation.Context.Should().NotBeNull();
attestation.Context!.FindingId.Should().Be("finding-123");
attestation.Context.CveId.Should().Be("CVE-2023-44487");
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="AiRunAttestationTests.Digest.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiRunAttestationTests
{
[Fact]
public void ComputeDigest_SameAttestation_ReturnsSameDigest()
{
var attestation = CreateSampleAttestation();
var digest1 = attestation.ComputeDigest();
var digest2 = attestation.ComputeDigest();
digest1.Should().Be(digest2);
digest1.Should().StartWith("sha256:");
}
[Fact]
public void ComputeDigest_DifferentAttestations_ReturnsDifferentDigests()
{
var attestation1 = CreateSampleAttestation();
var attestation2 = attestation1 with { RunId = "run-different" };
var digest1 = attestation1.ComputeDigest();
var digest2 = attestation2.ComputeDigest();
digest1.Should().NotBe(digest2);
}
}

View File

@@ -0,0 +1,25 @@
// <copyright file="AiRunAttestationTests.PredicateAndStatus.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiRunAttestationTests
{
[Fact]
public void PredicateType_IsCorrect()
{
AiRunAttestation.PredicateType.Should().Be("https://stellaops.org/attestation/ai-run/v1");
}
[Fact]
public void Attestation_DefaultStatus_IsCompleted()
{
var attestation = CreateSampleAttestation();
attestation.Status.Should().Be(AiRunStatus.Completed);
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="AiRunAttestationTests.Turns.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class AiRunAttestationTests
{
[Fact]
public void Attestation_WithTurns_PreservesOrder()
{
var turns = new[]
{
CreateTurn("turn-1", TurnRole.User, FixedUtcNow),
CreateTurn("turn-2", TurnRole.Assistant, FixedUtcNow.AddSeconds(5)),
CreateTurn("turn-3", TurnRole.User, FixedUtcNow.AddSeconds(10))
};
var attestation = CreateSampleAttestation() with
{
Turns = [.. turns]
};
attestation.Turns.Should().HaveCount(3);
attestation.Turns[0].TurnId.Should().Be("turn-1");
attestation.Turns[1].TurnId.Should().Be("turn-2");
attestation.Turns[2].TurnId.Should().Be("turn-3");
}
}

View File

@@ -1,9 +1,6 @@
// <copyright file="AiRunAttestationTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.AdvisoryAI.Attestation.Models;
using Xunit;
@@ -13,83 +10,9 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests;
/// Tests for <see cref="AiRunAttestation"/>.
/// </summary>
[Trait("Category", "Unit")]
public class AiRunAttestationTests
public sealed partial class AiRunAttestationTests
{
[Fact]
public void ComputeDigest_SameAttestation_ReturnsSameDigest()
{
var attestation = CreateSampleAttestation();
var digest1 = attestation.ComputeDigest();
var digest2 = attestation.ComputeDigest();
digest1.Should().Be(digest2);
digest1.Should().StartWith("sha256:");
}
[Fact]
public void ComputeDigest_DifferentAttestations_ReturnsDifferentDigests()
{
var attestation1 = CreateSampleAttestation();
var attestation2 = attestation1 with { RunId = "run-different" };
var digest1 = attestation1.ComputeDigest();
var digest2 = attestation2.ComputeDigest();
digest1.Should().NotBe(digest2);
}
[Fact]
public void PredicateType_IsCorrect()
{
AiRunAttestation.PredicateType.Should().Be("https://stellaops.org/attestation/ai-run/v1");
}
[Fact]
public void Attestation_WithTurns_PreservesOrder()
{
var turns = new[]
{
CreateTurn("turn-1", TurnRole.User, "2026-01-09T12:00:00Z"),
CreateTurn("turn-2", TurnRole.Assistant, "2026-01-09T12:00:05Z"),
CreateTurn("turn-3", TurnRole.User, "2026-01-09T12:00:10Z")
};
var attestation = CreateSampleAttestation() with
{
Turns = [.. turns]
};
attestation.Turns.Should().HaveCount(3);
attestation.Turns[0].TurnId.Should().Be("turn-1");
attestation.Turns[1].TurnId.Should().Be("turn-2");
attestation.Turns[2].TurnId.Should().Be("turn-3");
}
[Fact]
public void Attestation_WithContext_PreservesContext()
{
var context = new AiRunContext
{
FindingId = "finding-123",
CveId = "CVE-2023-44487",
Component = "pkg:npm/http2@1.0.0"
};
var attestation = CreateSampleAttestation() with { Context = context };
attestation.Context.Should().NotBeNull();
attestation.Context!.FindingId.Should().Be("finding-123");
attestation.Context.CveId.Should().Be("CVE-2023-44487");
}
[Fact]
public void Attestation_DefaultStatus_IsCompleted()
{
var attestation = CreateSampleAttestation();
attestation.Status.Should().Be(AiRunStatus.Completed);
}
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private static AiRunAttestation CreateSampleAttestation()
{
@@ -98,8 +21,8 @@ public class AiRunAttestationTests
RunId = "run-abc123",
TenantId = "tenant-xyz",
UserId = "user:alice@example.com",
StartedAt = DateTimeOffset.Parse("2026-01-09T12:00:00Z"),
CompletedAt = DateTimeOffset.Parse("2026-01-09T12:05:00Z"),
StartedAt = FixedUtcNow,
CompletedAt = FixedUtcNow.AddMinutes(5),
Model = new AiModelInfo
{
Provider = "anthropic",
@@ -110,14 +33,14 @@ public class AiRunAttestationTests
};
}
private static AiTurnSummary CreateTurn(string turnId, TurnRole role, string timestamp)
private static AiTurnSummary CreateTurn(string turnId, TurnRole role, DateTimeOffset timestamp)
{
return new AiTurnSummary
{
TurnId = turnId,
Role = role,
ContentDigest = $"sha256:turn-{turnId}",
Timestamp = DateTimeOffset.Parse(timestamp),
Timestamp = timestamp,
TokenCount = 100
};
}

View File

@@ -0,0 +1,48 @@
// <copyright file="InMemoryAiAttestationStoreTests.ClaimAttestations.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task StoreClaimAttestation_ThenRetrieve_WorksAsync()
{
var claim = CreateClaimAttestation("run-1", "turn-1");
await _store.StoreClaimAttestationAsync(claim, NoCancellation);
var claims = await _store.GetClaimAttestationsAsync("run-1", NoCancellation);
claims.Should().HaveCount(1);
claims[0].TurnId.Should().Be("turn-1");
}
[Fact]
public async Task GetClaimAttestations_MultiplePerRun_ReturnsAllAsync()
{
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), NoCancellation);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-2"), NoCancellation);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-3"), NoCancellation);
var claims = await _store.GetClaimAttestationsAsync("run-1", NoCancellation);
claims.Should().HaveCount(3);
}
[Fact]
public async Task GetClaimAttestationsByTurn_FiltersCorrectlyAsync()
{
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), NoCancellation);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-2"), NoCancellation);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), NoCancellation);
var turn1Claims = await _store.GetClaimAttestationsByTurnAsync("run-1", "turn-1", NoCancellation);
turn1Claims.Should().HaveCount(2);
turn1Claims.Should().OnlyContain(c => c.TurnId == "turn-1");
}
}

View File

@@ -0,0 +1,22 @@
// <copyright file="InMemoryAiAttestationStoreTests.Clear.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task Clear_RemovesAllDataAsync()
{
await _store.StoreRunAttestationAsync(CreateRunAttestation("run-1"), NoCancellation);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), NoCancellation);
_store.Clear();
_store.RunAttestationCount.Should().Be(0);
_store.ClaimAttestationCount.Should().Be(0);
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="InMemoryAiAttestationStoreTests.ContentDigest.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task GetByContentDigest_WorksAsync()
{
var claim = CreateClaimAttestation("run-1", "turn-1", "sha256:test123");
await _store.StoreClaimAttestationAsync(claim, NoCancellation);
var retrieved = await _store.GetByContentDigestAsync("sha256:test123", NoCancellation);
retrieved.Should().NotBeNull();
retrieved!.ContentDigest.Should().Be("sha256:test123");
}
[Fact]
public async Task GetByContentDigest_NotFound_ReturnsNullAsync()
{
var retrieved = await _store.GetByContentDigestAsync("sha256:nonexistent", NoCancellation);
retrieved.Should().BeNull();
}
}

View File

@@ -0,0 +1,22 @@
// <copyright file="InMemoryAiAttestationStoreTests.Envelopes.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task StoreSignedEnvelope_ThenRetrieve_WorksAsync()
{
var envelope = new { Type = "DSSE", Payload = "test" };
await _store.StoreSignedEnvelopeAsync("run-1", envelope, NoCancellation);
var retrieved = await _store.GetSignedEnvelopeAsync("run-1", NoCancellation);
retrieved.Should().NotBeNull();
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="InMemoryAiAttestationStoreTests.RunAttestations.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task StoreRunAttestation_ThenRetrieve_WorksAsync()
{
var attestation = CreateRunAttestation("run-1");
await _store.StoreRunAttestationAsync(attestation, NoCancellation);
var retrieved = await _store.GetRunAttestationAsync("run-1", NoCancellation);
retrieved.Should().NotBeNull();
retrieved!.RunId.Should().Be("run-1");
}
[Fact]
public async Task GetRunAttestation_NotFound_ReturnsNullAsync()
{
var retrieved = await _store.GetRunAttestationAsync("non-existent", NoCancellation);
retrieved.Should().BeNull();
}
[Fact]
public async Task Exists_WhenStored_ReturnsTrueAsync()
{
await _store.StoreRunAttestationAsync(CreateRunAttestation("run-1"), NoCancellation);
var exists = await _store.ExistsAsync("run-1", NoCancellation);
exists.Should().BeTrue();
}
[Fact]
public async Task Exists_WhenNotStored_ReturnsFalseAsync()
{
var exists = await _store.ExistsAsync("non-existent", NoCancellation);
exists.Should().BeFalse();
}
}

View File

@@ -0,0 +1,54 @@
// <copyright file="InMemoryAiAttestationStoreTests.TenantQueries.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class InMemoryAiAttestationStoreTests
{
[Fact]
public async Task GetByTenant_FiltersCorrectlyAsync()
{
var now = FixedUtcNow;
var att1 = CreateRunAttestation("run-1", "tenant-a", now.AddMinutes(-30));
var att2 = CreateRunAttestation("run-2", "tenant-a", now.AddMinutes(-10));
var att3 = CreateRunAttestation("run-3", "tenant-b", now.AddMinutes(-20));
await _store.StoreRunAttestationAsync(att1, NoCancellation);
await _store.StoreRunAttestationAsync(att2, NoCancellation);
await _store.StoreRunAttestationAsync(att3, NoCancellation);
var tenantAResults = await _store.GetByTenantAsync(
"tenant-a",
now.AddHours(-1),
now,
NoCancellation);
tenantAResults.Should().HaveCount(2);
tenantAResults.Should().OnlyContain(a => a.TenantId == "tenant-a");
}
[Fact]
public async Task GetByTenant_FiltersTimeRangeCorrectlyAsync()
{
var now = FixedUtcNow;
var att1 = CreateRunAttestation("run-1", "tenant-a", now.AddHours(-3));
var att2 = CreateRunAttestation("run-2", "tenant-a", now.AddHours(-1));
var att3 = CreateRunAttestation("run-3", "tenant-a", now.AddMinutes(-30));
await _store.StoreRunAttestationAsync(att1, NoCancellation);
await _store.StoreRunAttestationAsync(att2, NoCancellation);
await _store.StoreRunAttestationAsync(att3, NoCancellation);
var results = await _store.GetByTenantAsync(
"tenant-a",
now.AddHours(-2),
now,
NoCancellation);
results.Should().HaveCount(2);
results.Should().NotContain(a => a.RunId == "run-1");
}
}

View File

@@ -1,9 +1,8 @@
// <copyright file="InMemoryAiAttestationStoreTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using System.Threading;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.AdvisoryAI.Attestation.Models;
using StellaOps.AdvisoryAI.Attestation.Storage;
@@ -15,192 +14,33 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests;
/// Tests for <see cref="InMemoryAiAttestationStore"/>.
/// </summary>
[Trait("Category", "Unit")]
public class InMemoryAiAttestationStoreTests
public sealed partial class InMemoryAiAttestationStoreTests
{
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private static readonly CancellationToken NoCancellation = CancellationToken.None;
private const string DefaultTenantId = "test-tenant";
private readonly InMemoryAiAttestationStore _store;
private int _claimCounter;
public InMemoryAiAttestationStoreTests()
{
_store = new InMemoryAiAttestationStore(NullLogger<InMemoryAiAttestationStore>.Instance);
}
[Fact]
public async Task StoreRunAttestation_ThenRetrieve_Works()
{
var attestation = CreateRunAttestation("run-1");
await _store.StoreRunAttestationAsync(attestation, CancellationToken.None);
var retrieved = await _store.GetRunAttestationAsync("run-1", CancellationToken.None);
retrieved.Should().NotBeNull();
retrieved!.RunId.Should().Be("run-1");
}
[Fact]
public async Task GetRunAttestation_NotFound_ReturnsNull()
{
var retrieved = await _store.GetRunAttestationAsync("non-existent", CancellationToken.None);
retrieved.Should().BeNull();
}
[Fact]
public async Task StoreClaimAttestation_ThenRetrieve_Works()
{
var claim = CreateClaimAttestation("run-1", "turn-1");
await _store.StoreClaimAttestationAsync(claim, CancellationToken.None);
var claims = await _store.GetClaimAttestationsAsync("run-1", CancellationToken.None);
claims.Should().HaveCount(1);
claims[0].TurnId.Should().Be("turn-1");
}
[Fact]
public async Task GetClaimAttestations_MultiplePerRun_ReturnsAll()
{
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), CancellationToken.None);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-2"), CancellationToken.None);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-3"), CancellationToken.None);
var claims = await _store.GetClaimAttestationsAsync("run-1", CancellationToken.None);
claims.Should().HaveCount(3);
}
[Fact]
public async Task GetClaimAttestationsByTurn_FiltersCorrectly()
{
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), CancellationToken.None);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-2"), CancellationToken.None);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), CancellationToken.None);
var turn1Claims = await _store.GetClaimAttestationsByTurnAsync("run-1", "turn-1", CancellationToken.None);
turn1Claims.Should().HaveCount(2);
turn1Claims.Should().OnlyContain(c => c.TurnId == "turn-1");
}
[Fact]
public async Task Exists_WhenStored_ReturnsTrue()
{
await _store.StoreRunAttestationAsync(CreateRunAttestation("run-1"), CancellationToken.None);
var exists = await _store.ExistsAsync("run-1", CancellationToken.None);
exists.Should().BeTrue();
}
[Fact]
public async Task Exists_WhenNotStored_ReturnsFalse()
{
var exists = await _store.ExistsAsync("non-existent", CancellationToken.None);
exists.Should().BeFalse();
}
[Fact]
public async Task StoreSignedEnvelope_ThenRetrieve_Works()
{
var envelope = new { Type = "DSSE", Payload = "test" };
await _store.StoreSignedEnvelopeAsync("run-1", envelope, CancellationToken.None);
var retrieved = await _store.GetSignedEnvelopeAsync("run-1", CancellationToken.None);
retrieved.Should().NotBeNull();
}
[Fact]
public async Task GetByTenant_FiltersCorrectly()
{
var now = DateTimeOffset.UtcNow;
var att1 = CreateRunAttestation("run-1", "tenant-a", now.AddMinutes(-30));
var att2 = CreateRunAttestation("run-2", "tenant-a", now.AddMinutes(-10));
var att3 = CreateRunAttestation("run-3", "tenant-b", now.AddMinutes(-20));
await _store.StoreRunAttestationAsync(att1, CancellationToken.None);
await _store.StoreRunAttestationAsync(att2, CancellationToken.None);
await _store.StoreRunAttestationAsync(att3, CancellationToken.None);
var tenantAResults = await _store.GetByTenantAsync(
"tenant-a",
now.AddHours(-1),
now,
CancellationToken.None);
tenantAResults.Should().HaveCount(2);
tenantAResults.Should().OnlyContain(a => a.TenantId == "tenant-a");
}
[Fact]
public async Task GetByTenant_FiltersTimeRangeCorrectly()
{
var now = DateTimeOffset.UtcNow;
var att1 = CreateRunAttestation("run-1", "tenant-a", now.AddHours(-3));
var att2 = CreateRunAttestation("run-2", "tenant-a", now.AddHours(-1));
var att3 = CreateRunAttestation("run-3", "tenant-a", now.AddMinutes(-30));
await _store.StoreRunAttestationAsync(att1, CancellationToken.None);
await _store.StoreRunAttestationAsync(att2, CancellationToken.None);
await _store.StoreRunAttestationAsync(att3, CancellationToken.None);
var results = await _store.GetByTenantAsync(
"tenant-a",
now.AddHours(-2),
now,
CancellationToken.None);
results.Should().HaveCount(2);
results.Should().NotContain(a => a.RunId == "run-1");
}
[Fact]
public async Task GetByContentDigest_Works()
{
var claim = CreateClaimAttestation("run-1", "turn-1", "sha256:test123");
await _store.StoreClaimAttestationAsync(claim, CancellationToken.None);
var retrieved = await _store.GetByContentDigestAsync("sha256:test123", CancellationToken.None);
retrieved.Should().NotBeNull();
retrieved!.ContentDigest.Should().Be("sha256:test123");
}
[Fact]
public async Task GetByContentDigest_NotFound_ReturnsNull()
{
var retrieved = await _store.GetByContentDigestAsync("sha256:nonexistent", CancellationToken.None);
retrieved.Should().BeNull();
}
[Fact]
public async Task Clear_RemovesAllData()
{
await _store.StoreRunAttestationAsync(CreateRunAttestation("run-1"), CancellationToken.None);
await _store.StoreClaimAttestationAsync(CreateClaimAttestation("run-1", "turn-1"), CancellationToken.None);
_store.Clear();
_store.RunAttestationCount.Should().Be(0);
_store.ClaimAttestationCount.Should().Be(0);
}
private static AiRunAttestation CreateRunAttestation(
string runId,
string tenantId = "test-tenant",
string tenantId = DefaultTenantId,
DateTimeOffset? startedAt = null)
{
var start = startedAt ?? FixedUtcNow;
return new AiRunAttestation
{
RunId = runId,
TenantId = tenantId,
UserId = "user-1",
StartedAt = startedAt ?? DateTimeOffset.UtcNow,
CompletedAt = DateTimeOffset.UtcNow,
StartedAt = start,
CompletedAt = start.AddMinutes(5),
Model = new AiModelInfo
{
ModelId = "gpt-4",
@@ -210,20 +50,23 @@ public class InMemoryAiAttestationStoreTests
};
}
private static AiClaimAttestation CreateClaimAttestation(
private AiClaimAttestation CreateClaimAttestation(
string runId,
string turnId,
string? contentDigest = null)
{
var suffix = Interlocked.Increment(ref _claimCounter);
var claimId = $"claim-{runId}-{turnId}-{suffix:0000}";
return new AiClaimAttestation
{
ClaimId = $"claim-{Guid.NewGuid():N}",
ClaimId = claimId,
RunId = runId,
TurnId = turnId,
TenantId = "test-tenant",
TenantId = DefaultTenantId,
ClaimText = "Test claim text",
ClaimDigest = "sha256:claimhash",
Timestamp = DateTimeOffset.UtcNow,
ClaimDigest = $"sha256:{claimId}",
Timestamp = FixedUtcNow,
ClaimType = "vulnerability_assessment",
ContentDigest = contentDigest ?? $"sha256:{runId}-{turnId}"
};

View File

@@ -0,0 +1,38 @@
// <copyright file="AttestationServiceFixture.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Time.Testing;
using StellaOps.AdvisoryAI.Attestation;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
public sealed class AttestationServiceFixture : IAsyncLifetime
{
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private ServiceProvider _serviceProvider = null!;
public IAiAttestationService AttestationService { get; private set; } = null!;
public ValueTask InitializeAsync()
{
var services = new ServiceCollection();
services.AddLogging();
var timeProvider = new FakeTimeProvider();
timeProvider.SetUtcNow(FixedUtcNow);
services.AddAiAttestationServices(timeProvider);
services.AddInMemoryAiAttestationStore();
_serviceProvider = services.BuildServiceProvider();
AttestationService = _serviceProvider.GetRequiredService<IAiAttestationService>();
return ValueTask.CompletedTask;
}
public async ValueTask DisposeAsync()
{
await _serviceProvider.DisposeAsync();
}
}

View File

@@ -0,0 +1,53 @@
// <copyright file="AttestationServiceIntegrationTests.ClaimAttestations.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Linq;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
public sealed partial class AttestationServiceIntegrationTests
{
[Fact]
public async Task FullClaimAttestationFlow_CreateSignVerify_SucceedsAsync()
{
var runAttestation = CreateSampleRunAttestation("run-integration-002");
await _attestationService.CreateRunAttestationAsync(runAttestation);
var claimAttestation = CreateSampleClaimAttestation("claim-001", "run-integration-002", "turn-001");
var createResult = await _attestationService.CreateClaimAttestationAsync(claimAttestation, sign: true);
Assert.NotNull(createResult.Digest);
var claims = await _attestationService.GetClaimAttestationsAsync("run-integration-002");
Assert.Single(claims);
Assert.Equal("claim-001", claims[0].ClaimId);
var verifyResult = await _attestationService.VerifyClaimAttestationAsync("claim-001");
Assert.True(verifyResult.Valid);
}
[Fact]
public async Task StorageRoundTrip_MultipleClaimsPerRun_AllRetrievableAsync()
{
var runId = "run-multiclaim-001";
var run = CreateSampleRunAttestation(runId);
await _attestationService.CreateRunAttestationAsync(run);
var claims = Enumerable.Range(1, 3)
.Select(i => CreateSampleClaimAttestation($"claim-mc-{i:D3}", runId, $"turn-{i:D3}"))
.ToList();
foreach (var claim in claims)
{
var result = await _attestationService.CreateClaimAttestationAsync(claim);
Assert.NotNull(result.Digest);
}
var retrieved = await _attestationService.GetClaimAttestationsAsync(runId);
Assert.Equal(3, retrieved.Count);
}
}

View File

@@ -0,0 +1,50 @@
// <copyright file="AttestationServiceIntegrationTests.Query.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Linq;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
public sealed partial class AttestationServiceIntegrationTests
{
[Fact]
public async Task StorageRoundTrip_MultipleRuns_AllRetrievableAsync()
{
var runs = Enumerable.Range(1, 5)
.Select(i => CreateSampleRunAttestation($"run-roundtrip-{i:D3}"))
.ToList();
foreach (var run in runs)
{
var result = await _attestationService.CreateRunAttestationAsync(run);
Assert.NotNull(result.Digest);
}
foreach (var run in runs)
{
var retrieved = await _attestationService.GetRunAttestationAsync(run.RunId);
Assert.NotNull(retrieved);
Assert.Equal(run.RunId, retrieved.RunId);
}
}
[Fact]
public async Task QueryByTenant_ReturnsOnlyTenantRunsAsync()
{
var tenant1Run = CreateSampleRunAttestation("run-tenant1-001", tenantId: "tenant-1");
var tenant2Run = CreateSampleRunAttestation("run-tenant2-001", tenantId: "tenant-2");
await _attestationService.CreateRunAttestationAsync(tenant1Run);
await _attestationService.CreateRunAttestationAsync(tenant2Run);
var tenant1Runs = await _attestationService.ListRecentAttestationsAsync("tenant-1", limit: 10);
var tenant2Runs = await _attestationService.ListRecentAttestationsAsync("tenant-2", limit: 10);
Assert.Single(tenant1Runs);
Assert.Equal("run-tenant1-001", tenant1Runs[0].RunId);
Assert.Single(tenant2Runs);
Assert.Equal("run-tenant2-001", tenant2Runs[0].RunId);
}
}

View File

@@ -0,0 +1,46 @@
// <copyright file="AttestationServiceIntegrationTests.RunAttestations.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
public sealed partial class AttestationServiceIntegrationTests
{
[Fact]
public async Task FullRunAttestationFlow_CreateSignVerify_SucceedsAsync()
{
var attestation = CreateSampleRunAttestation("run-integration-001");
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: true);
Assert.NotNull(createResult.Digest);
Assert.StartsWith("sha256:", createResult.Digest);
var retrieved = await _attestationService.GetRunAttestationAsync("run-integration-001");
Assert.NotNull(retrieved);
Assert.Equal(attestation.RunId, retrieved.RunId);
Assert.Equal(attestation.TenantId, retrieved.TenantId);
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-integration-001");
Assert.True(verifyResult.Valid);
Assert.True(verifyResult.DigestValid);
}
[Fact]
public async Task UnsignedAttestation_VerifiesDigestOnlyAsync()
{
var attestation = CreateSampleRunAttestation("run-unsigned-001");
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: false);
Assert.NotNull(createResult.Digest);
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-unsigned-001");
Assert.True(verifyResult.Valid);
Assert.True(verifyResult.DigestValid);
Assert.Null(verifyResult.SignatureValid);
}
}

View File

@@ -0,0 +1,31 @@
// <copyright file="AttestationServiceIntegrationTests.Verification.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
public sealed partial class AttestationServiceIntegrationTests
{
[Fact]
public async Task VerificationFailure_TamperedContent_ReturnsInvalidAsync()
{
var attestation = CreateSampleRunAttestation("run-tamper-001");
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: true);
Assert.NotNull(createResult.Digest);
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-tamper-001");
Assert.True(verifyResult.Valid, "Original attestation should verify");
}
[Fact]
public async Task VerificationFailure_NonExistentRun_ReturnsInvalidAsync()
{
var verifyResult = await _attestationService.VerifyRunAttestationAsync("non-existent-run");
Assert.False(verifyResult.Valid);
Assert.Contains("not found", verifyResult.FailureReason, StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -1,11 +1,8 @@
// <copyright file="AttestationServiceIntegrationTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.AdvisoryAI.Attestation;
using StellaOps.AdvisoryAI.Attestation.Models;
using StellaOps.AdvisoryAI.Attestation.Storage;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
@@ -15,215 +12,14 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests.Integration;
/// Sprint: SPRINT_20260109_011_001 Task: AIAT-008
/// </summary>
[Trait("Category", "Integration")]
public sealed class AttestationServiceIntegrationTests : IAsyncLifetime
public sealed partial class AttestationServiceIntegrationTests : IClassFixture<AttestationServiceFixture>
{
private ServiceProvider _serviceProvider = null!;
private IAiAttestationService _attestationService = null!;
private IAiAttestationStore _store = null!;
private TimeProvider _timeProvider = null!;
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private readonly IAiAttestationService _attestationService;
public ValueTask InitializeAsync()
public AttestationServiceIntegrationTests(AttestationServiceFixture fixture)
{
var services = new ServiceCollection();
// Add logging
services.AddLogging();
// Register all attestation services
services.AddAiAttestationServices();
services.AddInMemoryAiAttestationStore();
_serviceProvider = services.BuildServiceProvider();
_attestationService = _serviceProvider.GetRequiredService<IAiAttestationService>();
_store = _serviceProvider.GetRequiredService<IAiAttestationStore>();
_timeProvider = _serviceProvider.GetRequiredService<TimeProvider>();
return ValueTask.CompletedTask;
}
public async ValueTask DisposeAsync()
{
await _serviceProvider.DisposeAsync();
}
[Fact]
public async Task FullRunAttestationFlow_CreateSignVerify_Succeeds()
{
// Arrange
var attestation = CreateSampleRunAttestation("run-integration-001");
// Act - Create and sign
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: true);
// Assert creation - result has Digest property
Assert.NotNull(createResult.Digest);
Assert.StartsWith("sha256:", createResult.Digest);
// Act - Retrieve
var retrieved = await _attestationService.GetRunAttestationAsync("run-integration-001");
// Assert retrieval
Assert.NotNull(retrieved);
Assert.Equal(attestation.RunId, retrieved.RunId);
Assert.Equal(attestation.TenantId, retrieved.TenantId);
// Act - Verify
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-integration-001");
// Assert verification
Assert.True(verifyResult.Valid);
Assert.True(verifyResult.DigestValid);
}
[Fact]
public async Task FullClaimAttestationFlow_CreateSignVerify_Succeeds()
{
// Arrange - Create parent run first
var runAttestation = CreateSampleRunAttestation("run-integration-002");
await _attestationService.CreateRunAttestationAsync(runAttestation);
var claimAttestation = CreateSampleClaimAttestation("claim-001", "run-integration-002", "turn-001");
// Act - Create and sign
var createResult = await _attestationService.CreateClaimAttestationAsync(claimAttestation, sign: true);
// Assert creation
Assert.NotNull(createResult.Digest);
// Act - Retrieve claims for run
var claims = await _attestationService.GetClaimAttestationsAsync("run-integration-002");
// Assert retrieval
Assert.Single(claims);
Assert.Equal("claim-001", claims[0].ClaimId);
// Act - Verify
var verifyResult = await _attestationService.VerifyClaimAttestationAsync("claim-001");
// Assert verification
Assert.True(verifyResult.Valid);
}
[Fact]
public async Task StorageRoundTrip_MultipleRuns_AllRetrievable()
{
// Arrange - Create multiple runs
var runs = Enumerable.Range(1, 5)
.Select(i => CreateSampleRunAttestation($"run-roundtrip-{i:D3}"))
.ToList();
// Act - Store all
foreach (var run in runs)
{
var result = await _attestationService.CreateRunAttestationAsync(run);
Assert.NotNull(result.Digest);
}
// Assert - All retrievable
foreach (var run in runs)
{
var retrieved = await _attestationService.GetRunAttestationAsync(run.RunId);
Assert.NotNull(retrieved);
Assert.Equal(run.RunId, retrieved.RunId);
}
}
[Fact]
public async Task StorageRoundTrip_MultipleClaimsPerRun_AllRetrievable()
{
// Arrange
var runId = "run-multiclaim-001";
var run = CreateSampleRunAttestation(runId);
await _attestationService.CreateRunAttestationAsync(run);
var claims = Enumerable.Range(1, 3)
.Select(i => CreateSampleClaimAttestation($"claim-mc-{i:D3}", runId, $"turn-{i:D3}"))
.ToList();
// Act - Store all claims
foreach (var claim in claims)
{
var result = await _attestationService.CreateClaimAttestationAsync(claim);
Assert.NotNull(result.Digest);
}
// Assert - All claims retrievable
var retrieved = await _attestationService.GetClaimAttestationsAsync(runId);
Assert.Equal(3, retrieved.Count);
}
[Fact]
public async Task QueryByTenant_ReturnsOnlyTenantRuns()
{
// Arrange
var tenant1Run = CreateSampleRunAttestation("run-tenant1-001", tenantId: "tenant-1");
var tenant2Run = CreateSampleRunAttestation("run-tenant2-001", tenantId: "tenant-2");
await _attestationService.CreateRunAttestationAsync(tenant1Run);
await _attestationService.CreateRunAttestationAsync(tenant2Run);
// Act
var tenant1Runs = await _attestationService.ListRecentAttestationsAsync("tenant-1", limit: 10);
var tenant2Runs = await _attestationService.ListRecentAttestationsAsync("tenant-2", limit: 10);
// Assert
Assert.Single(tenant1Runs);
Assert.Equal("run-tenant1-001", tenant1Runs[0].RunId);
Assert.Single(tenant2Runs);
Assert.Equal("run-tenant2-001", tenant2Runs[0].RunId);
}
[Fact]
public async Task VerificationFailure_TamperedContent_ReturnsInvalid()
{
// This test validates tamper detection, which requires the service
// to verify against stored digests. Currently the in-memory service
// uses its own internal storage, so this scenario tests what's possible.
// Arrange
var attestation = CreateSampleRunAttestation("run-tamper-001");
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: true);
Assert.NotNull(createResult.Digest);
// Act - Verify the original (should succeed)
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-tamper-001");
// Assert - Original should verify
Assert.True(verifyResult.Valid, "Original attestation should verify");
// Note: Full tamper detection (storing modified content and detecting mismatch)
// requires AIAT-008 implementation. For now we just verify the happy path.
}
[Fact]
public async Task VerificationFailure_NonExistentRun_ReturnsInvalid()
{
// Act
var verifyResult = await _attestationService.VerifyRunAttestationAsync("non-existent-run");
// Assert
Assert.False(verifyResult.Valid);
Assert.Contains("not found", verifyResult.FailureReason, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task UnsignedAttestation_VerifiesDigestOnly()
{
// Arrange
var attestation = CreateSampleRunAttestation("run-unsigned-001");
// Act - Create without signing
var createResult = await _attestationService.CreateRunAttestationAsync(attestation, sign: false);
Assert.NotNull(createResult.Digest);
// Act - Verify
var verifyResult = await _attestationService.VerifyRunAttestationAsync("run-unsigned-001");
// Assert
Assert.True(verifyResult.Valid);
Assert.True(verifyResult.DigestValid);
Assert.Null(verifyResult.SignatureValid); // No signature to verify
_attestationService = fixture.AttestationService;
}
private static AiRunAttestation CreateSampleRunAttestation(
@@ -242,8 +38,8 @@ public sealed class AttestationServiceIntegrationTests : IAsyncLifetime
Provider = "test-provider"
},
TotalTokens = 100,
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
CompletedAt = DateTimeOffset.UtcNow
StartedAt = FixedUtcNow.AddMinutes(-5),
CompletedAt = FixedUtcNow
};
}
@@ -262,7 +58,7 @@ public sealed class AttestationServiceIntegrationTests : IAsyncLifetime
ClaimText = "This is a test claim",
ClaimDigest = $"sha256:{claimId}",
ContentDigest = $"sha256:content-{claimId}",
Timestamp = DateTimeOffset.UtcNow
Timestamp = FixedUtcNow
};
}
}

View File

@@ -0,0 +1,26 @@
// <copyright file="PromptTemplateRegistryTests.GetAllTemplates.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class PromptTemplateRegistryTests
{
[Fact]
public void GetAllTemplates_ReturnsAllLatestVersions()
{
_registry.Register("template-a", "1.0.0", "Template A v1");
_registry.Register("template-a", "1.1.0", "Template A v2");
_registry.Register("template-b", "1.0.0", "Template B");
_registry.Register("template-c", "2.0.0", "Template C");
var all = _registry.GetAllTemplates();
all.Should().HaveCount(3);
all.Should().Contain(t => t.Name == "template-a" && t.Version == "1.1.0");
all.Should().Contain(t => t.Name == "template-b");
all.Should().Contain(t => t.Name == "template-c");
}
}

View File

@@ -0,0 +1,31 @@
// <copyright file="PromptTemplateRegistryTests.GetTemplateInfo.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class PromptTemplateRegistryTests
{
[Fact]
public void GetTemplateInfo_ByVersion_ReturnsCorrectVersion()
{
_registry.Register("vuln-explanation", "1.0.0", "Template v1");
_registry.Register("vuln-explanation", "1.1.0", "Template v2");
var v1 = _registry.GetTemplateInfo("vuln-explanation", "1.0.0");
var v2 = _registry.GetTemplateInfo("vuln-explanation", "1.1.0");
v1!.Version.Should().Be("1.0.0");
v2!.Version.Should().Be("1.1.0");
}
[Fact]
public void GetTemplateInfo_NotFound_ReturnsNull()
{
var info = _registry.GetTemplateInfo("non-existent");
info.Should().BeNull();
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="PromptTemplateRegistryTests.GuardClauses.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class PromptTemplateRegistryTests
{
[Fact]
public void Register_NullName_Throws()
{
var act = () => _registry.Register(null!, "1.0.0", "content");
act.Should().Throw<ArgumentException>();
}
[Fact]
public void Register_EmptyVersion_Throws()
{
var act = () => _registry.Register("name", string.Empty, "content");
act.Should().Throw<ArgumentException>();
}
[Fact]
public void Register_EmptyTemplate_Throws()
{
var act = () => _registry.Register("name", "1.0.0", string.Empty);
act.Should().Throw<ArgumentException>();
}
}

View File

@@ -0,0 +1,59 @@
// <copyright file="PromptTemplateRegistryTests.Register.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class PromptTemplateRegistryTests
{
[Fact]
public void Register_ValidTemplate_StoresWithDigest()
{
_registry.Register("vuln-explanation", "1.0.0", "Explain this vulnerability: {{cve}}");
var info = _registry.GetTemplateInfo("vuln-explanation");
info.Should().NotBeNull();
info!.Name.Should().Be("vuln-explanation");
info.Version.Should().Be("1.0.0");
info.Digest.Should().StartWith("sha256:");
}
[Fact]
public void Register_SameTemplateTwice_UpdatesVersion()
{
_registry.Register("vuln-explanation", "1.0.0", "Template v1");
_registry.Register("vuln-explanation", "1.1.0", "Template v2");
var info = _registry.GetTemplateInfo("vuln-explanation");
info!.Version.Should().Be("1.1.0");
}
[Fact]
public void Register_DifferentContent_ProducesDifferentDigests()
{
_registry.Register("template-1", "1.0.0", "Content A");
_registry.Register("template-2", "1.0.0", "Content B");
var info1 = _registry.GetTemplateInfo("template-1");
var info2 = _registry.GetTemplateInfo("template-2");
info1!.Digest.Should().NotBe(info2!.Digest);
}
[Fact]
public void Register_SameContent_ProducesSameDigest()
{
const string content = "Same content";
_registry.Register("template-1", "1.0.0", content);
_registry.Register("template-2", "1.0.0", content);
var info1 = _registry.GetTemplateInfo("template-1");
var info2 = _registry.GetTemplateInfo("template-2");
info1!.Digest.Should().Be(info2!.Digest);
}
}

View File

@@ -0,0 +1,40 @@
// <copyright file="PromptTemplateRegistryTests.VerifyHash.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
public sealed partial class PromptTemplateRegistryTests
{
[Fact]
public void VerifyHash_MatchingHash_ReturnsTrue()
{
const string template = "Test template content";
_registry.Register("test", "1.0.0", template);
var info = _registry.GetTemplateInfo("test");
var result = _registry.VerifyHash("test", info!.Digest);
result.Should().BeTrue();
}
[Fact]
public void VerifyHash_NonMatchingHash_ReturnsFalse()
{
_registry.Register("test", "1.0.0", "Test template content");
var result = _registry.VerifyHash("test", "sha256:wronghash");
result.Should().BeFalse();
}
[Fact]
public void VerifyHash_NotFound_ReturnsFalse()
{
var result = _registry.VerifyHash("non-existent", "sha256:anyhash");
result.Should().BeFalse();
}
}

View File

@@ -1,10 +1,9 @@
// <copyright file="PromptTemplateRegistryTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.AdvisoryAI.Attestation;
using Xunit;
namespace StellaOps.AdvisoryAI.Attestation.Tests;
@@ -13,155 +12,17 @@ namespace StellaOps.AdvisoryAI.Attestation.Tests;
/// Tests for <see cref="PromptTemplateRegistry"/>.
/// </summary>
[Trait("Category", "Unit")]
public class PromptTemplateRegistryTests
public sealed partial class PromptTemplateRegistryTests
{
private static readonly DateTimeOffset FixedUtcNow = new(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
private readonly FakeTimeProvider _timeProvider = new();
private readonly PromptTemplateRegistry _registry;
public PromptTemplateRegistryTests()
{
_timeProvider.SetUtcNow(FixedUtcNow);
_registry = new PromptTemplateRegistry(
_timeProvider,
NullLogger<PromptTemplateRegistry>.Instance);
}
[Fact]
public void Register_ValidTemplate_StoresWithDigest()
{
_registry.Register("vuln-explanation", "1.0.0", "Explain this vulnerability: {{cve}}");
var info = _registry.GetTemplateInfo("vuln-explanation");
info.Should().NotBeNull();
info!.Name.Should().Be("vuln-explanation");
info.Version.Should().Be("1.0.0");
info.Digest.Should().StartWith("sha256:");
}
[Fact]
public void Register_SameTemplateTwice_UpdatesVersion()
{
_registry.Register("vuln-explanation", "1.0.0", "Template v1");
_registry.Register("vuln-explanation", "1.1.0", "Template v2");
var info = _registry.GetTemplateInfo("vuln-explanation");
info!.Version.Should().Be("1.1.0");
}
[Fact]
public void GetTemplateInfo_ByVersion_ReturnsCorrectVersion()
{
_registry.Register("vuln-explanation", "1.0.0", "Template v1");
_registry.Register("vuln-explanation", "1.1.0", "Template v2");
var v1 = _registry.GetTemplateInfo("vuln-explanation", "1.0.0");
var v2 = _registry.GetTemplateInfo("vuln-explanation", "1.1.0");
v1!.Version.Should().Be("1.0.0");
v2!.Version.Should().Be("1.1.0");
}
[Fact]
public void GetTemplateInfo_NotFound_ReturnsNull()
{
var info = _registry.GetTemplateInfo("non-existent");
info.Should().BeNull();
}
[Fact]
public void VerifyHash_MatchingHash_ReturnsTrue()
{
const string template = "Test template content";
_registry.Register("test", "1.0.0", template);
var info = _registry.GetTemplateInfo("test");
var result = _registry.VerifyHash("test", info!.Digest);
result.Should().BeTrue();
}
[Fact]
public void VerifyHash_NonMatchingHash_ReturnsFalse()
{
_registry.Register("test", "1.0.0", "Test template content");
var result = _registry.VerifyHash("test", "sha256:wronghash");
result.Should().BeFalse();
}
[Fact]
public void VerifyHash_NotFound_ReturnsFalse()
{
var result = _registry.VerifyHash("non-existent", "sha256:anyhash");
result.Should().BeFalse();
}
[Fact]
public void GetAllTemplates_ReturnsAllLatestVersions()
{
_registry.Register("template-a", "1.0.0", "Template A v1");
_registry.Register("template-a", "1.1.0", "Template A v2");
_registry.Register("template-b", "1.0.0", "Template B");
_registry.Register("template-c", "2.0.0", "Template C");
var all = _registry.GetAllTemplates();
all.Should().HaveCount(3);
all.Should().Contain(t => t.Name == "template-a" && t.Version == "1.1.0");
all.Should().Contain(t => t.Name == "template-b");
all.Should().Contain(t => t.Name == "template-c");
}
[Fact]
public void Register_DifferentContent_ProducesDifferentDigests()
{
_registry.Register("template-1", "1.0.0", "Content A");
_registry.Register("template-2", "1.0.0", "Content B");
var info1 = _registry.GetTemplateInfo("template-1");
var info2 = _registry.GetTemplateInfo("template-2");
info1!.Digest.Should().NotBe(info2!.Digest);
}
[Fact]
public void Register_SameContent_ProducesSameDigest()
{
const string content = "Same content";
_registry.Register("template-1", "1.0.0", content);
_registry.Register("template-2", "1.0.0", content);
var info1 = _registry.GetTemplateInfo("template-1");
var info2 = _registry.GetTemplateInfo("template-2");
info1!.Digest.Should().Be(info2!.Digest);
}
[Fact]
public void Register_NullName_Throws()
{
var act = () => _registry.Register(null!, "1.0.0", "content");
act.Should().Throw<ArgumentException>();
}
[Fact]
public void Register_EmptyVersion_Throws()
{
var act = () => _registry.Register("name", "", "content");
act.Should().Throw<ArgumentException>();
}
[Fact]
public void Register_EmptyTemplate_Throws()
{
var act = () => _registry.Register("name", "1.0.0", "");
act.Should().Throw<ArgumentException>();
}
}

View File

@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/__Libraries/__Tests/StellaOps.AdvisoryAI.Attestation.Tests/StellaOps.AdvisoryAI.Attestation.Tests.md. |
| REMED-05 | DONE | Remediation checklist completed: file splits, async naming, deterministic fixtures, service locator removal. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| REMED-08 | DONE | Split tests <= 100 lines; deterministic fixtures/time/IDs; async naming; DI fixture for integration tests; ConfigureAwait(false) skipped per xUnit1030; dotnet test passed 2026-02-02 (58 tests). |