feat(config-db): secured-write TryMarkExpiredAsync CAS + CountAsync (arch-review S2/P3)

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 05:00:23 -04:00
parent 5cfa85ea91
commit 0fad6817c2
3 changed files with 124 additions and 0 deletions
@@ -109,6 +109,61 @@ public class SecuredWriteRepositoryTests : IClassFixture<MsSqlMigrationFixture>
Assert.Contains(all, p => p.Id == executedId);
}
[SkippableFact]
public async Task TryMarkExpiredAsync_PendingRow_FlipsToExpired_Once()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var siteId = NewSiteId();
await using var context = CreateContext();
var repo = new SecuredWriteRepository(context);
var id = await repo.AddAsync(NewRow(siteId, status: "Pending"));
Assert.True(await repo.TryMarkExpiredAsync(id, DateTime.UtcNow));
// CAS: a second caller loses because the row is no longer Pending.
Assert.False(await repo.TryMarkExpiredAsync(id, DateTime.UtcNow));
await using var readContext = CreateContext();
var row = await new SecuredWriteRepository(readContext).GetAsync(id);
Assert.NotNull(row);
Assert.Equal("Expired", row!.Status);
Assert.NotNull(row.DecidedAtUtc);
Assert.Null(row.VerifierUser); // system decision, no verifier
}
[SkippableFact]
public async Task TryMarkExpiredAsync_DecidedRow_ReturnsFalse()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var siteId = NewSiteId();
await using var context = CreateContext();
var repo = new SecuredWriteRepository(context);
var id = await repo.AddAsync(NewRow(siteId, status: "Pending"));
Assert.True(await repo.TryMarkApprovedAsync(id, "ver.bob", null, DateTime.UtcNow));
// Already Approved -> the conditional WHERE Status='Pending' matches no row.
Assert.False(await repo.TryMarkExpiredAsync(id, DateTime.UtcNow));
}
[SkippableFact]
public async Task CountAsync_FiltersLikeQueryAsync()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var siteId = NewSiteId();
await using var context = CreateContext();
var repo = new SecuredWriteRepository(context);
await repo.AddAsync(NewRow(siteId, status: "Pending"));
await repo.AddAsync(NewRow(siteId, status: "Pending"));
Assert.Equal(2, await repo.CountAsync(status: "Pending", siteId: siteId));
Assert.Equal(0, await repo.CountAsync(status: "Executed", siteId: siteId));
}
// --- helpers ------------------------------------------------------------
private ScadaBridgeDbContext CreateContext()