feat: Implement PostgreSQL repositories for various entities
- Added BootstrapInviteRepository for managing bootstrap invites. - Added ClientRepository for handling OAuth/OpenID clients. - Introduced LoginAttemptRepository for logging login attempts. - Created OidcTokenRepository for managing OpenIddict tokens and refresh tokens. - Implemented RevocationExportStateRepository for persisting revocation export state. - Added RevocationRepository for managing revocations. - Introduced ServiceAccountRepository for handling service accounts.
This commit is contained in:
@@ -48,4 +48,73 @@ public class AirGapStateServiceTests
|
||||
Assert.False(status.State.Sealed);
|
||||
Assert.Equal(later, status.State.LastTransitionAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_persists_drift_baseline_seconds()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-5), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
|
||||
var state = await _service.SealAsync("tenant-drift", "policy-drift", anchor, budget, now);
|
||||
|
||||
Assert.Equal(300, state.DriftBaselineSeconds); // 5 minutes = 300 seconds
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_creates_default_content_budgets_when_not_provided()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-1), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = new StalenessBudget(120, 240);
|
||||
|
||||
var state = await _service.SealAsync("tenant-content", "policy-content", anchor, budget, now);
|
||||
|
||||
Assert.Contains("advisories", state.ContentBudgets.Keys);
|
||||
Assert.Contains("vex", state.ContentBudgets.Keys);
|
||||
Assert.Contains("policy", state.ContentBudgets.Keys);
|
||||
Assert.Equal(budget, state.ContentBudgets["advisories"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_uses_provided_content_budgets()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-1), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
var contentBudgets = new Dictionary<string, StalenessBudget>
|
||||
{
|
||||
{ "advisories", new StalenessBudget(30, 60) },
|
||||
{ "vex", new StalenessBudget(60, 120) }
|
||||
};
|
||||
|
||||
var state = await _service.SealAsync("tenant-custom", "policy-custom", anchor, budget, now, contentBudgets);
|
||||
|
||||
Assert.Equal(new StalenessBudget(30, 60), state.ContentBudgets["advisories"]);
|
||||
Assert.Equal(new StalenessBudget(60, 120), state.ContentBudgets["vex"]);
|
||||
Assert.Equal(budget, state.ContentBudgets["policy"]); // Falls back to default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_returns_per_content_staleness()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddSeconds(-45), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
var contentBudgets = new Dictionary<string, StalenessBudget>
|
||||
{
|
||||
{ "advisories", new StalenessBudget(30, 60) },
|
||||
{ "vex", new StalenessBudget(60, 120) },
|
||||
{ "policy", new StalenessBudget(100, 200) }
|
||||
};
|
||||
|
||||
await _service.SealAsync("tenant-content-status", "policy-content-status", anchor, budget, now, contentBudgets);
|
||||
var status = await _service.GetStatusAsync("tenant-content-status", now);
|
||||
|
||||
Assert.NotEmpty(status.ContentStaleness);
|
||||
Assert.True(status.ContentStaleness["advisories"].IsWarning); // 45s >= 30s warning
|
||||
Assert.False(status.ContentStaleness["advisories"].IsBreach); // 45s < 60s breach
|
||||
Assert.False(status.ContentStaleness["vex"].IsWarning); // 45s < 60s warning
|
||||
Assert.False(status.ContentStaleness["policy"].IsWarning); // 45s < 100s warning
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user