test(communication)+test(sitecallaudit): cap parallelism, delta/collection-serialize gauge asserts, replace sleep-then-assert, stop leaked 100ms tickers (arch-review 2026-08-19 P8-5 + P8-6, register rows 40/82/95/96/102 patterns)
This commit is contained in:
@@ -114,6 +114,20 @@ public class SiteCallAuditPurgeTests : TestKit
|
||||
NullLogger<SiteCallAuditActor>.Instance)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Row-82/row-95 house pattern: every test in this file arms a
|
||||
/// <c>PurgeIntervalOverride = 100ms</c> ticker. Left running past the test's own
|
||||
/// assertions it keeps firing against the recording repo — and, worse, against a
|
||||
/// scope-per-tick DI container whose lifetime the test no longer controls — for the
|
||||
/// rest of the assembly's run, stranding later tests. Stop it deterministically.
|
||||
/// </summary>
|
||||
private void StopTicker(IActorRef actor)
|
||||
{
|
||||
Watch(actor);
|
||||
Sys.Stop(actor);
|
||||
ExpectTerminated(actor, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 1. PurgeTick_CallsPurgeTerminal_WithRetentionThreshold
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -124,21 +138,27 @@ public class SiteCallAuditPurgeTests : TestKit
|
||||
var repo = new RecordingRepo { RowsDeletedPerCall = 7 };
|
||||
// Non-default retention (30 days) so the assertion isn't accidentally
|
||||
// satisfied by the 365-day default.
|
||||
CreateActor(repo, FastPurgeOptions(retentionDays: 30));
|
||||
var actor = CreateActor(repo, FastPurgeOptions(retentionDays: 30));
|
||||
try
|
||||
{
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1,
|
||||
$"expected >= 1 PurgeTerminalAsync call, got {repo.PurgeThresholds.Count}"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1,
|
||||
$"expected >= 1 PurgeTerminalAsync call, got {repo.PurgeThresholds.Count}"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
// The threshold the actor passed must be ~UtcNow - 30 days. 1-minute
|
||||
// slack covers scheduling jitter between the tick firing and the assert.
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(30);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
// The threshold the actor passed must be ~UtcNow - 30 days. 1-minute
|
||||
// slack covers scheduling jitter between the tick firing and the assert.
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(30);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
StopTicker(actor);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -149,18 +169,24 @@ public class SiteCallAuditPurgeTests : TestKit
|
||||
public void PurgeTick_DefaultRetention_Uses365DayThreshold()
|
||||
{
|
||||
var repo = new RecordingRepo();
|
||||
CreateActor(repo, FastPurgeOptions()); // default 365 days
|
||||
var actor = CreateActor(repo, FastPurgeOptions()); // default 365 days
|
||||
try
|
||||
{
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(365);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(365);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
StopTicker(actor);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -171,16 +197,22 @@ public class SiteCallAuditPurgeTests : TestKit
|
||||
public void PurgeTick_PurgeThrows_ActorStaysAlive_RetriesNextTick()
|
||||
{
|
||||
var repo = new PurgeThrowingRepo();
|
||||
CreateActor(repo, FastPurgeOptions());
|
||||
|
||||
// The singleton must NOT die on a purge fault — a second tick must still
|
||||
// arrive (continue-on-error). Two purge calls prove the actor survived
|
||||
// the first throw and the timer kept ticking.
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeCallCount >= 2,
|
||||
$"expected >= 2 purge attempts (actor survived the throw), got {repo.PurgeCallCount}"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
var actor = CreateActor(repo, FastPurgeOptions());
|
||||
try
|
||||
{
|
||||
// The singleton must NOT die on a purge fault — a second tick must still
|
||||
// arrive (continue-on-error). Two purge calls prove the actor survived
|
||||
// the first throw and the timer kept ticking.
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeCallCount >= 2,
|
||||
$"expected >= 2 purge attempts (actor survived the throw), got {repo.PurgeCallCount}"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
}
|
||||
finally
|
||||
{
|
||||
StopTicker(actor);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -206,24 +238,30 @@ public class SiteCallAuditPurgeTests : TestKit
|
||||
.BuildServiceProvider();
|
||||
|
||||
var options = FastPurgeOptions(retentionDays: 30);
|
||||
Sys.ActorOf(Props.Create(() => new SiteCallAuditActor(
|
||||
var actor = Sys.ActorOf(Props.Create(() => new SiteCallAuditActor(
|
||||
provider,
|
||||
options,
|
||||
NullLogger<SiteCallAuditActor>.Instance)));
|
||||
try
|
||||
{
|
||||
// No reconciliation collaborators were registered, yet the purge tick
|
||||
// must still fire on the production path.
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1,
|
||||
"purge timer must arm even when the reconciliation collaborators are absent "
|
||||
+ $"(SiteCallAudit-007), got {repo.PurgeThresholds.Count} purge calls"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
// No reconciliation collaborators were registered, yet the purge tick
|
||||
// must still fire on the production path.
|
||||
AwaitAssert(
|
||||
() => Assert.True(repo.PurgeThresholds.Count >= 1,
|
||||
"purge timer must arm even when the reconciliation collaborators are absent "
|
||||
+ $"(SiteCallAudit-007), got {repo.PurgeThresholds.Count} purge calls"),
|
||||
duration: TimeSpan.FromSeconds(3),
|
||||
interval: TimeSpan.FromMilliseconds(50));
|
||||
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(30);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
var threshold = repo.PurgeThresholds[0];
|
||||
var expected = DateTime.UtcNow - TimeSpan.FromDays(30);
|
||||
Assert.True(
|
||||
Math.Abs((threshold - expected).TotalMinutes) < 1.0,
|
||||
$"purge threshold {threshold:o} should be within 1 minute of {expected:o}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
StopTicker(actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user