part #2
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user