using System; using System.Collections.Generic; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using StellaOps.AirGap.Controller.Domain; 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; namespace StellaOps.AirGap.Persistence.Tests; [Collection(AirGapPostgresCollection.Name)] [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 const string TenantId = "tenant-store-01"; public PostgresAirGapStateStoreTests(AirGapPostgresFixture fixture) { _fixture = fixture; var options = Options.Create(new PostgresOptions { ConnectionString = fixture.ConnectionString, SchemaName = fixture.SchemaName, AutoMigrate = false }); _dataSource = new AirGapDataSource(options, NullLogger.Instance); _store = new PostgresAirGapStateStore(_dataSource, NullLogger.Instance); } public async ValueTask InitializeAsync() { await _fixture.TruncateAllTablesAsync(); } public async ValueTask DisposeAsync() { await _dataSource.DisposeAsync(); } private static AirGapState CreateState( string tenantId, string stateId, bool sealed_ = false, string? policyHash = null, IReadOnlyDictionary? budgets = null, TimeAnchor? timeAnchor = null) { return new AirGapState { Id = stateId, TenantId = tenantId, Sealed = sealed_, PolicyHash = policyHash, TimeAnchor = timeAnchor ?? TimeAnchor.Unknown, LastTransitionAt = TransitionTime, StalenessBudget = new StalenessBudget(1800, 3600), DriftBaselineSeconds = 5, ContentBudgets = budgets ?? new Dictionary(StringComparer.OrdinalIgnoreCase) }; } }