save progress

This commit is contained in:
StellaOps Bot
2026-01-03 00:47:24 +02:00
parent 3f197814c5
commit ca578801fd
319 changed files with 32478 additions and 2202 deletions

View File

@@ -12,7 +12,7 @@ public sealed class VerdictManifestBuilderTests
public void Build_CreatesValidManifest()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-01-01T12:00:00Z"));
var builder = new VerdictManifestBuilder(() => "test-manifest-id", clock)
var builder = new VerdictManifestBuilder(() => "test-manifest-id")
.WithTenant("tenant-1")
.WithAsset("sha256:abc123", "CVE-2024-1234")
.WithInputs(
@@ -61,7 +61,7 @@ public sealed class VerdictManifestBuilderTests
VerdictManifest BuildManifest(int seed)
{
return new VerdictManifestBuilder(() => "fixed-id", TimeProvider.System)
return new VerdictManifestBuilder(() => "fixed-id")
.WithTenant("tenant")
.WithAsset("sha256:asset", "CVE-2024-0001")
.WithInputs(
@@ -106,7 +106,7 @@ public sealed class VerdictManifestBuilderTests
{
var clock = DateTimeOffset.Parse("2025-01-01T00:00:00Z");
var manifestA = new VerdictManifestBuilder(() => "id", TimeProvider.System)
var manifestA = new VerdictManifestBuilder(() => "id")
.WithTenant("t")
.WithAsset("sha256:a", "CVE-1")
.WithInputs(
@@ -119,7 +119,7 @@ public sealed class VerdictManifestBuilderTests
.WithClock(clock)
.Build();
var manifestB = new VerdictManifestBuilder(() => "id", TimeProvider.System)
var manifestB = new VerdictManifestBuilder(() => "id")
.WithTenant("t")
.WithAsset("sha256:a", "CVE-1")
.WithInputs(
@@ -151,7 +151,7 @@ public sealed class VerdictManifestBuilderTests
public void Build_NormalizesVulnerabilityIdToUpperCase()
{
var clock = DateTimeOffset.Parse("2025-01-01T00:00:00Z");
var manifest = new VerdictManifestBuilder(() => "id", TimeProvider.System)
var manifest = new VerdictManifestBuilder(() => "id")
.WithTenant("t")
.WithAsset("sha256:a", "cve-2024-1234")
.WithInputs(

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Threading;
using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Authority.Persistence.Documents;
using StellaOps.Authority.Persistence.InMemory.Stores;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Authority.Persistence.Tests;
public sealed class InMemoryStoreTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BootstrapInviteStore_AssignsIdAndTimestamps()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-01-02T12:00:00Z"));
var idGenerator = new TestIdGenerator("invite-001");
var store = new InMemoryBootstrapInviteStore(clock, idGenerator);
var document = new AuthorityBootstrapInviteDocument
{
Token = "token-1",
Type = "bootstrap",
ExpiresAt = clock.GetUtcNow().AddHours(1),
};
var created = await store.CreateAsync(document, CancellationToken.None);
created.Id.Should().Be("invite-001");
created.CreatedAt.Should().Be(clock.GetUtcNow());
created.IssuedAt.Should().Be(clock.GetUtcNow());
created.Status.Should().Be(AuthorityBootstrapInviteStatuses.Pending);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ServiceAccountStore_UpsertUsesClockAndIdGenerator()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-02-05T08:30:00Z"));
var idGenerator = new TestIdGenerator("svc-001");
var store = new InMemoryServiceAccountStore(clock, idGenerator);
var document = new AuthorityServiceAccountDocument
{
AccountId = "svc-1",
Tenant = "tenant-1",
DisplayName = "Service",
};
await store.UpsertAsync(document, CancellationToken.None);
var fetched = await store.FindByAccountIdAsync("svc-1", CancellationToken.None);
fetched.Should().NotBeNull();
fetched!.Id.Should().Be("svc-001");
fetched.CreatedAt.Should().Be(clock.GetUtcNow());
fetched.UpdatedAt.Should().Be(clock.GetUtcNow());
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RefreshTokenStore_ConsumeUsesClock()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-03-01T09:15:00Z"));
var idGenerator = new TestIdGenerator("refresh-001");
var store = new InMemoryRefreshTokenStore(clock, idGenerator);
var document = new AuthorityRefreshTokenDocument
{
TokenId = "token-1",
SubjectId = "subject-1",
};
await store.UpsertAsync(document, CancellationToken.None);
var consumed = await store.ConsumeAsync(document.TokenId, CancellationToken.None);
consumed.Should().BeTrue();
var fetched = await store.FindByTokenIdAsync(document.TokenId, CancellationToken.None);
fetched!.ConsumedAt.Should().Be(clock.GetUtcNow());
}
private sealed class TestIdGenerator : IAuthorityInMemoryIdGenerator
{
private readonly Queue<string> _ids;
public TestIdGenerator(params string[] ids)
{
_ids = new Queue<string>(ids);
}
public string NextId()
{
if (_ids.Count == 0)
{
throw new InvalidOperationException("No more IDs available.");
}
return _ids.Dequeue();
}
}
}

View File

@@ -1,6 +1,7 @@
using StellaOps.Authority.Persistence.Postgres.Models;
using StellaOps.Authority.Persistence.Postgres.Repositories;
using System.Collections.Concurrent;
using System.Text.Json;
namespace StellaOps.Authority.Persistence.Tests.TestDoubles;
@@ -146,6 +147,9 @@ internal sealed class InMemoryUserRepository : IUserRepository
public Task<UserEntity?> GetByUsernameAsync(string tenantId, string username, CancellationToken cancellationToken = default)
=> Task.FromResult(_users.Values.FirstOrDefault(u => u.TenantId == tenantId && u.Username == username));
public Task<UserEntity?> GetBySubjectIdAsync(string tenantId, string subjectId, CancellationToken cancellationToken = default)
=> Task.FromResult(_users.Values.FirstOrDefault(u => u.TenantId == tenantId && MatchesSubject(u.Metadata, subjectId)));
public Task<UserEntity?> GetByEmailAsync(string tenantId, string email, CancellationToken cancellationToken = default)
=> Task.FromResult(_users.Values.FirstOrDefault(u => u.TenantId == tenantId && u.Email == email));
@@ -198,6 +202,34 @@ internal sealed class InMemoryUserRepository : IUserRepository
}
public IReadOnlyCollection<UserEntity> Snapshot() => _users.Values.ToList();
private static bool MatchesSubject(string? metadataJson, string subjectId)
{
if (string.IsNullOrWhiteSpace(metadataJson) || string.IsNullOrWhiteSpace(subjectId))
{
return false;
}
try
{
using var document = JsonDocument.Parse(metadataJson);
if (document.RootElement.ValueKind != JsonValueKind.Object)
{
return false;
}
if (document.RootElement.TryGetProperty("subjectId", out var subjectElement)
&& subjectElement.ValueKind == JsonValueKind.String)
{
return string.Equals(subjectElement.GetString(), subjectId, StringComparison.Ordinal);
}
}
catch
{
}
return false;
}
}
internal static class AuthorityCloneHelpers

View File

@@ -0,0 +1,135 @@
using System.Collections.Immutable;
using System.Threading;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Npgsql;
using StellaOps.Authority.Core.Verdicts;
using StellaOps.Authority.Persistence.Postgres;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Authority.Persistence.Tests;
[Collection(AuthorityPostgresCollection.Name)]
public sealed class VerdictManifestStoreTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly PostgresVerdictManifestStore _store;
public VerdictManifestStoreTests(AuthorityPostgresFixture fixture)
{
_fixture = fixture;
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_store = new PostgresVerdictManifestStore(_dataSource);
}
public async ValueTask InitializeAsync()
{
await _fixture.TruncateAllTablesAsync();
}
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAndGetById_RoundTripsManifest()
{
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T10:00:00Z");
var manifest = CreateManifest("tenant-1", "manifest-001", evaluatedAt, VexStatus.NotAffected);
await _store.StoreAsync(manifest);
var fetched = await _store.GetByIdAsync(manifest.Tenant, manifest.ManifestId);
fetched.Should().NotBeNull();
fetched!.ManifestId.Should().Be(manifest.ManifestId);
fetched.AssetDigest.Should().Be(manifest.AssetDigest);
fetched.VulnerabilityId.Should().Be(manifest.VulnerabilityId);
fetched.PolicyHash.Should().Be(manifest.PolicyHash);
fetched.LatticeVersion.Should().Be(manifest.LatticeVersion);
fetched.EvaluatedAt.Should().Be(evaluatedAt);
fetched.Result.Status.Should().Be(VexStatus.NotAffected);
fetched.Inputs.SbomDigests.Should().Contain("sbom:sha256:aaa");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAsync_WritesStringEnumJson()
{
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T11:00:00Z");
var manifest = CreateManifest("tenant-2", "manifest-002", evaluatedAt, VexStatus.UnderInvestigation);
await _store.StoreAsync(manifest);
await using var conn = await _dataSource.OpenConnectionAsync(manifest.Tenant, "reader", CancellationToken.None);
await using var cmd = new NpgsqlCommand("""
SELECT result_json::text
FROM verdict_manifests
WHERE tenant = @tenant AND manifest_id = @manifestId
""", conn)
{
CommandTimeout = _dataSource.CommandTimeoutSeconds,
};
cmd.Parameters.AddWithValue("tenant", manifest.Tenant);
cmd.Parameters.AddWithValue("manifestId", manifest.ManifestId);
var json = (string?)await cmd.ExecuteScalarAsync();
json.Should().NotBeNull();
json.Should().Contain("\"status\":\"under_investigation\"");
}
private static VerdictManifest CreateManifest(string tenant, string manifestId, DateTimeOffset evaluatedAt, VexStatus status)
{
var inputs = new VerdictInputs
{
SbomDigests = ImmutableArray.Create("sbom:sha256:aaa"),
VulnFeedSnapshotIds = ImmutableArray.Create("feed:1"),
VexDocumentDigests = ImmutableArray.Create("vex:1"),
ReachabilityGraphIds = ImmutableArray.Create("graph:1"),
ClockCutoff = evaluatedAt.AddMinutes(-5),
};
var result = new VerdictResult
{
Status = status,
Confidence = 0.82,
Explanations = ImmutableArray.Create(new VerdictExplanation
{
SourceId = "source-1",
Reason = "policy-pass",
ProvenanceScore = 0.9,
CoverageScore = 0.8,
ReplayabilityScore = 0.95,
StrengthMultiplier = 1.0,
FreshnessMultiplier = 0.97,
ClaimScore = 0.88,
AssertedStatus = status,
Accepted = true,
}),
EvidenceRefs = ImmutableArray.Create("evidence-1"),
HasConflicts = false,
RequiresReplayProof = false,
};
var manifest = new VerdictManifest
{
ManifestId = manifestId,
Tenant = tenant,
AssetDigest = "sha256:asset-1",
VulnerabilityId = "CVE-2025-0001",
Inputs = inputs,
Result = result,
PolicyHash = "policy-hash-1",
LatticeVersion = "lattice-1",
EvaluatedAt = evaluatedAt,
ManifestDigest = string.Empty,
SignatureBase64 = null,
RekorLogId = null,
};
var digest = VerdictManifestSerializer.ComputeDigest(manifest);
return manifest with { ManifestDigest = digest };
}
}