This commit is contained in:
master
2026-02-04 19:59:20 +02:00
parent 557feefdc3
commit 5548cf83bf
1479 changed files with 53557 additions and 40339 deletions

View File

@@ -1,4 +1,5 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.AirGap.Controller.Domain;
@@ -6,18 +7,27 @@ using StellaOps.AirGap.Persistence.Postgres;
using StellaOps.AirGap.Persistence.Postgres.Repositories;
using StellaOps.AirGap.Time.Models;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.TestKit;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Persistence.Tests;
[Collection(AirGapPostgresCollection.Name)]
public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
[Trait("Category", TestCategories.Integration)]
[Trait("BlastRadius", TestCategories.BlastRadius.Persistence)]
public sealed partial class PostgresAirGapStateStoreTests : IAsyncLifetime
{
private static readonly DateTimeOffset AnchorTime =
new(2025, 5, 1, 8, 30, 0, TimeSpan.Zero);
private static readonly DateTimeOffset TransitionTime =
new(2025, 5, 1, 8, 45, 0, TimeSpan.Zero);
private readonly AirGapPostgresFixture _fixture;
private readonly PostgresAirGapStateStore _store;
private readonly AirGapDataSource _dataSource;
private readonly string _tenantId = "tenant-" + Guid.NewGuid().ToString("N")[..8];
private const string TenantId = "tenant-store-01";
public PostgresAirGapStateStoreTests(AirGapPostgresFixture fixture)
{
@@ -43,133 +53,26 @@ public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
await _dataSource.DisposeAsync();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_ReturnsDefaultStateForNewTenant()
private static AirGapState CreateState(
string tenantId,
string stateId,
bool sealed_ = false,
string? policyHash = null,
IReadOnlyDictionary<string, StalenessBudget>? budgets = null,
TimeAnchor? timeAnchor = null)
{
// Act
var state = await _store.GetAsync(_tenantId);
// Assert
state.Should().NotBeNull();
state.TenantId.Should().Be(_tenantId);
state.Sealed.Should().BeFalse();
state.PolicyHash.Should().BeNull();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAndGet_RoundTripsState()
{
// Arrange
var timeAnchor = new TimeAnchor(
DateTimeOffset.UtcNow,
"tsa.example.com",
"RFC3161",
"sha256:fingerprint123",
"sha256:tokendigest456");
var state = new AirGapState
return new AirGapState
{
Id = Guid.NewGuid().ToString("N"),
TenantId = _tenantId,
Sealed = true,
PolicyHash = "sha256:policy789",
TimeAnchor = timeAnchor,
LastTransitionAt = DateTimeOffset.UtcNow,
Id = stateId,
TenantId = tenantId,
Sealed = sealed_,
PolicyHash = policyHash,
TimeAnchor = timeAnchor ?? TimeAnchor.Unknown,
LastTransitionAt = TransitionTime,
StalenessBudget = new StalenessBudget(1800, 3600),
DriftBaselineSeconds = 5,
ContentBudgets = new Dictionary<string, StalenessBudget>
{
["advisories"] = new StalenessBudget(7200, 14400),
["vex"] = new StalenessBudget(3600, 7200)
}
ContentBudgets = budgets ??
new Dictionary<string, StalenessBudget>(StringComparer.OrdinalIgnoreCase)
};
// Act
await _store.SetAsync(state);
var fetched = await _store.GetAsync(_tenantId);
// Assert
fetched.Should().NotBeNull();
fetched.Sealed.Should().BeTrue();
fetched.PolicyHash.Should().Be("sha256:policy789");
fetched.TimeAnchor.Source.Should().Be("tsa.example.com");
fetched.TimeAnchor.Format.Should().Be("RFC3161");
fetched.StalenessBudget.WarningSeconds.Should().Be(1800);
fetched.StalenessBudget.BreachSeconds.Should().Be(3600);
fetched.DriftBaselineSeconds.Should().Be(5);
fetched.ContentBudgets.Should().HaveCount(2);
fetched.ContentBudgets["advisories"].WarningSeconds.Should().Be(7200);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_UpdatesExistingState()
{
// Arrange
var state1 = new AirGapState
{
Id = Guid.NewGuid().ToString("N"),
TenantId = _tenantId,
Sealed = false,
TimeAnchor = TimeAnchor.Unknown,
StalenessBudget = StalenessBudget.Default
};
var state2 = new AirGapState
{
Id = state1.Id,
TenantId = _tenantId,
Sealed = true,
PolicyHash = "sha256:updated",
TimeAnchor = new TimeAnchor(DateTimeOffset.UtcNow, "updated-source", "rfc3161", "", ""),
LastTransitionAt = DateTimeOffset.UtcNow,
StalenessBudget = new StalenessBudget(600, 1200)
};
// Act
await _store.SetAsync(state1);
await _store.SetAsync(state2);
var fetched = await _store.GetAsync(_tenantId);
// Assert
fetched.Sealed.Should().BeTrue();
fetched.PolicyHash.Should().Be("sha256:updated");
fetched.TimeAnchor.Source.Should().Be("updated-source");
fetched.StalenessBudget.WarningSeconds.Should().Be(600);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_PersistsContentBudgets()
{
// Arrange
var state = new AirGapState
{
Id = Guid.NewGuid().ToString("N"),
TenantId = _tenantId,
TimeAnchor = TimeAnchor.Unknown,
StalenessBudget = StalenessBudget.Default,
ContentBudgets = new Dictionary<string, StalenessBudget>
{
["advisories"] = new StalenessBudget(3600, 7200),
["vex"] = new StalenessBudget(1800, 3600),
["policy"] = new StalenessBudget(900, 1800)
}
};
// Act
await _store.SetAsync(state);
var fetched = await _store.GetAsync(_tenantId);
// Assert
fetched.ContentBudgets.Should().HaveCount(3);
fetched.ContentBudgets.Should().ContainKey("advisories");
fetched.ContentBudgets.Should().ContainKey("vex");
fetched.ContentBudgets.Should().ContainKey("policy");
}
}