Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,172 @@
using FluentAssertions;
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 Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Persistence.Tests;
[Collection(AirGapPostgresCollection.Name)]
public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
{
private readonly AirGapPostgresFixture _fixture;
private readonly PostgresAirGapStateStore _store;
private readonly AirGapDataSource _dataSource;
private readonly string _tenantId = "tenant-" + Guid.NewGuid().ToString("N")[..8];
public PostgresAirGapStateStoreTests(AirGapPostgresFixture fixture)
{
_fixture = fixture;
var options = Options.Create(new PostgresOptions
{
ConnectionString = fixture.ConnectionString,
SchemaName = AirGapDataSource.DefaultSchemaName,
AutoMigrate = false
});
_dataSource = new AirGapDataSource(options, NullLogger<AirGapDataSource>.Instance);
_store = new PostgresAirGapStateStore(_dataSource, NullLogger<PostgresAirGapStateStore>.Instance);
}
public async Task InitializeAsync()
{
await _fixture.TruncateAllTablesAsync();
}
public async Task DisposeAsync()
{
await _dataSource.DisposeAsync();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_ReturnsDefaultStateForNewTenant()
{
// 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
{
Id = Guid.NewGuid().ToString("N"),
TenantId = _tenantId,
Sealed = true,
PolicyHash = "sha256:policy789",
TimeAnchor = timeAnchor,
LastTransitionAt = DateTimeOffset.UtcNow,
StalenessBudget = new StalenessBudget(1800, 3600),
DriftBaselineSeconds = 5,
ContentBudgets = new Dictionary<string, StalenessBudget>
{
["advisories"] = new StalenessBudget(7200, 14400),
["vex"] = new StalenessBudget(3600, 7200)
}
};
// 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");
}
}