feat(management): opportunistic expiry sweep on secured-write list (arch-review S2)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -613,6 +613,42 @@ public class SecuredWriteHandlerTests : TestKit, IDisposable
|
||||
Assert.Contains("not found", updated.ExecutionError);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Opportunistic expiry sweep on list — Task 16 (arch-review S2)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void List_SweepsStalePendingRows_MarksExpired_CallsTryMarkExpiredOnceForStaleOnly()
|
||||
{
|
||||
var fresh = new PendingSecuredWrite
|
||||
{
|
||||
Id = 1, SiteId = "SITE1", ConnectionName = "Mx1", TagPath = "Tag.A",
|
||||
ValueJson = "true", ValueType = "Boolean", Status = "Pending",
|
||||
OperatorUser = "alice", SubmittedAtUtc = DateTime.UtcNow.AddHours(-1)
|
||||
};
|
||||
var stale = new PendingSecuredWrite
|
||||
{
|
||||
Id = 2, SiteId = "SITE1", ConnectionName = "Mx1", TagPath = "Tag.B",
|
||||
ValueJson = "false", ValueType = "Boolean", Status = "Pending",
|
||||
OperatorUser = "alice", SubmittedAtUtc = DateTime.UtcNow.AddHours(-25) // TTL default 24h
|
||||
};
|
||||
_securedWriteRepo.QueryAsync(null, null, 0, 200, Arg.Any<CancellationToken>())
|
||||
.Returns(new List<PendingSecuredWrite> { fresh, stale });
|
||||
_securedWriteRepo.TryMarkExpiredAsync(2, Arg.Any<DateTime>(), Arg.Any<CancellationToken>())
|
||||
.Returns(true);
|
||||
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new ListSecuredWritesCommand(null, null), "carol", "Operator"));
|
||||
|
||||
var resp = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
// The stale row surfaces as Expired in the projection; the fresh row stays Pending.
|
||||
Assert.Contains("\"id\":2", resp.JsonData);
|
||||
Assert.Contains("Expired", resp.JsonData);
|
||||
// Exactly one CAS — only the overdue row.
|
||||
_securedWriteRepo.Received(1).TryMarkExpiredAsync(2, Arg.Any<DateTime>(), Arg.Any<CancellationToken>());
|
||||
_securedWriteRepo.DidNotReceive().TryMarkExpiredAsync(1, Arg.Any<DateTime>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Server-side TTL expiry — Task 15 (arch-review S2)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user