up
This commit is contained in:
@@ -7,11 +7,23 @@ internal sealed class InMemoryReceiptRepository : IReceiptRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CvssScoreReceipt> _store = new();
|
||||
|
||||
public Task<CvssScoreReceipt> SaveAsync(CvssScoreReceipt receipt, CancellationToken cancellationToken = default)
|
||||
public Task<CvssScoreReceipt> SaveAsync(string tenantId, CvssScoreReceipt receipt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_store[receipt.ReceiptId] = receipt;
|
||||
return Task.FromResult(receipt);
|
||||
}
|
||||
|
||||
public bool Contains(string receiptId) => _store.ContainsKey(receiptId);
|
||||
|
||||
public Task<CvssScoreReceipt?> GetAsync(string tenantId, string receiptId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_store.TryGetValue(receiptId, out var receipt);
|
||||
return Task.FromResult(receipt);
|
||||
}
|
||||
|
||||
public Task<CvssScoreReceipt> UpdateAsync(string tenantId, CvssScoreReceipt receipt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_store[receipt.ReceiptId] = receipt;
|
||||
return Task.FromResult(receipt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Policy.Scoring.Tests.Fakes;
|
||||
|
||||
internal static class TestKeys
|
||||
{
|
||||
// Ed25519 test key material from Attestor envelope tests
|
||||
public static readonly byte[] Ed25519PrivateExpanded = Convert.FromHexString(
|
||||
"9D61B19DEFFD5A60BA844AF492EC2CC4" +
|
||||
"4449C5697B326919703BAC031CAE7F60D75A980182B10AB7D54BFED3C964073A" +
|
||||
"0EE172F3DAA62325AF021A68F707511A");
|
||||
|
||||
public static readonly byte[] Ed25519Public = Convert.FromHexString(
|
||||
"D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F707511A");
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
using StellaOps.Policy.Scoring.Engine;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
using StellaOps.Policy.Scoring.Tests.Fakes;
|
||||
@@ -70,6 +71,54 @@ public sealed class ReceiptBuilderTests
|
||||
_repository.Contains(receipt1.ReceiptId).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_WithSigningKey_AttachesDsseReference()
|
||||
{
|
||||
// Arrange
|
||||
var signingKey = EnvelopeKey.CreateEd25519Signer(TestKeys.Ed25519PrivateExpanded, TestKeys.Ed25519Public, "test-key");
|
||||
|
||||
var policy = new CvssPolicy
|
||||
{
|
||||
PolicyId = "default",
|
||||
Version = "1.0.0",
|
||||
Name = "Default",
|
||||
EffectiveFrom = DateTimeOffset.UtcNow,
|
||||
Hash = "abc123"
|
||||
};
|
||||
|
||||
var request = new CreateReceiptRequest
|
||||
{
|
||||
VulnerabilityId = "CVE-2025-0003",
|
||||
TenantId = "tenant-c",
|
||||
CreatedBy = "tester",
|
||||
Policy = policy,
|
||||
BaseMetrics = new CvssBaseMetrics
|
||||
{
|
||||
AttackVector = AttackVector.Network,
|
||||
AttackComplexity = AttackComplexity.Low,
|
||||
AttackRequirements = AttackRequirements.None,
|
||||
PrivilegesRequired = PrivilegesRequired.None,
|
||||
UserInteraction = UserInteraction.None,
|
||||
VulnerableSystemConfidentiality = ImpactMetricValue.High,
|
||||
VulnerableSystemIntegrity = ImpactMetricValue.High,
|
||||
VulnerableSystemAvailability = ImpactMetricValue.High,
|
||||
SubsequentSystemConfidentiality = ImpactMetricValue.High,
|
||||
SubsequentSystemIntegrity = ImpactMetricValue.High,
|
||||
SubsequentSystemAvailability = ImpactMetricValue.High
|
||||
},
|
||||
SigningKey = signingKey
|
||||
};
|
||||
|
||||
var builder = new ReceiptBuilder(_engine, _repository);
|
||||
|
||||
// Act
|
||||
var receipt = await builder.CreateAsync(request);
|
||||
|
||||
// Assert
|
||||
receipt.AttestationRefs.Should().NotBeEmpty();
|
||||
receipt.AttestationRefs[0].Should().StartWith("dsse:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_EnforcesEvidenceRequirements()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Reflection;
|
||||
using StellaOps.Infrastructure.Postgres.Testing;
|
||||
using StellaOps.Policy.Storage.Postgres;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL integration test fixture for the Policy module.
|
||||
/// Runs migrations from embedded resources and provides test isolation.
|
||||
/// </summary>
|
||||
public sealed class PolicyPostgresFixture : PostgresIntegrationFixture, ICollectionFixture<PolicyPostgresFixture>
|
||||
{
|
||||
protected override Assembly? GetMigrationAssembly()
|
||||
=> typeof(PolicyDataSource).Assembly;
|
||||
|
||||
protected override string GetModuleName() => "Policy";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection definition for Policy PostgreSQL integration tests.
|
||||
/// Tests in this collection share a single PostgreSQL container instance.
|
||||
/// </summary>
|
||||
[CollectionDefinition(Name)]
|
||||
public sealed class PolicyPostgresCollection : ICollectionFixture<PolicyPostgresFixture>
|
||||
{
|
||||
public const string Name = "PolicyPostgres";
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy.Scoring;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
using StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Tests;
|
||||
using StellaOps.Policy.Storage.Postgres;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(PolicyPostgresCollection.Name)]
|
||||
public sealed class PostgresReceiptRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly PolicyPostgresFixture _fixture;
|
||||
private readonly PostgresReceiptRepository _repository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
|
||||
public PostgresReceiptRepositoryTests(PolicyPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance);
|
||||
_repository = new PostgresReceiptRepository(dataSource, NullLogger<PostgresReceiptRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAndGet_RoundTripsReceipt()
|
||||
{
|
||||
var receipt = CreateReceipt(_tenantId);
|
||||
|
||||
var saved = await _repository.SaveAsync(_tenantId, receipt);
|
||||
var fetched = await _repository.GetAsync(_tenantId, saved.ReceiptId);
|
||||
|
||||
fetched.Should().NotBeNull();
|
||||
fetched!.ReceiptId.Should().Be(saved.ReceiptId);
|
||||
fetched.InputHash.Should().Be(saved.InputHash);
|
||||
fetched.PolicyRef.PolicyId.Should().Be("default");
|
||||
fetched.AttestationRefs.Should().BeEquivalentTo(saved.AttestationRefs);
|
||||
}
|
||||
|
||||
private static CvssScoreReceipt CreateReceipt(string tenantId)
|
||||
{
|
||||
var baseMetrics = new CvssBaseMetrics
|
||||
{
|
||||
AttackVector = AttackVector.Network,
|
||||
AttackComplexity = AttackComplexity.Low,
|
||||
AttackRequirements = AttackRequirements.None,
|
||||
PrivilegesRequired = PrivilegesRequired.None,
|
||||
UserInteraction = UserInteraction.None,
|
||||
VulnerableSystemConfidentiality = ImpactMetricValue.High,
|
||||
VulnerableSystemIntegrity = ImpactMetricValue.High,
|
||||
VulnerableSystemAvailability = ImpactMetricValue.High,
|
||||
SubsequentSystemConfidentiality = ImpactMetricValue.High,
|
||||
SubsequentSystemIntegrity = ImpactMetricValue.High,
|
||||
SubsequentSystemAvailability = ImpactMetricValue.High
|
||||
};
|
||||
|
||||
var scores = new CvssScores
|
||||
{
|
||||
BaseScore = 10.0,
|
||||
ThreatScore = null,
|
||||
EnvironmentalScore = null,
|
||||
FullScore = null,
|
||||
EffectiveScore = 10.0,
|
||||
EffectiveScoreType = EffectiveScoreType.Base
|
||||
};
|
||||
|
||||
return new CvssScoreReceipt
|
||||
{
|
||||
ReceiptId = Guid.NewGuid().ToString(),
|
||||
SchemaVersion = "1.0.0",
|
||||
Format = "stella.ops/cvssReceipt@v1",
|
||||
VulnerabilityId = "CVE-2025-0001",
|
||||
TenantId = tenantId,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
CreatedBy = "tester",
|
||||
CvssVersion = "4.0",
|
||||
BaseMetrics = baseMetrics,
|
||||
ThreatMetrics = null,
|
||||
EnvironmentalMetrics = null,
|
||||
SupplementalMetrics = null,
|
||||
Scores = scores,
|
||||
VectorString = "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
|
||||
Severity = CvssSeverity.Critical,
|
||||
PolicyRef = new CvssPolicyReference
|
||||
{
|
||||
PolicyId = "default",
|
||||
Version = "1.0.0",
|
||||
Hash = "abc123",
|
||||
ActivatedAt = null
|
||||
},
|
||||
Evidence = ImmutableList<CvssEvidenceItem>.Empty,
|
||||
AttestationRefs = ImmutableList<string>.Empty,
|
||||
InputHash = "hash123",
|
||||
History = ImmutableList<ReceiptHistoryEntry>.Empty,
|
||||
AmendsReceiptId = null,
|
||||
IsActive = true,
|
||||
SupersededReason = null
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Policy.Storage.Postgres\StellaOps.Policy.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Policy.Scoring\StellaOps.Policy.Scoring.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user