test(runtime): pin VT inputs-ready gate — no Bad flash before all dependencies arrive (02/S13)

This commit is contained in:
Joseph Doherty
2026-07-13 09:55:04 -04:00
parent 987459fab3
commit f2006adb79
2 changed files with 39 additions and 1 deletions
@@ -34,7 +34,7 @@
{ {
"id": "R2-03-T5", "id": "R2-03-T5",
"subject": "S13 cold-start suppression guards: missing-dep failure publishes no Bad; failure after all deps arrived publishes Bad (inputs-ready gate)", "subject": "S13 cold-start suppression guards: missing-dep failure publishes no Bad; failure after all deps arrived publishes Bad (inputs-ready gate)",
"status": "pending", "status": "completed",
"blockedBy": [ "blockedBy": [
"R2-03-T3" "R2-03-T3"
] ]
@@ -142,6 +142,44 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase
parent.ExpectMsg<VirtualTagActor.EvaluationResult>().Quality.ShouldBe(OpcUaQuality.Bad); parent.ExpectMsg<VirtualTagActor.EvaluationResult>().Quality.ShouldBe(OpcUaQuality.Bad);
} }
/// <summary>02/S13 inputs-ready gate: a multi-dep script that fails before all its declared
/// dependencies have arrived publishes NO Bad — the node stays at the materialiser's
/// BadWaitingForInitialData, avoiding a false Bad flash during deploy/respawn warm-up.</summary>
[Fact]
public void Cold_start_failure_with_missing_dependency_publishes_no_Bad()
{
var parent = CreateTestProbe();
var actor = parent.ChildActorOf(VirtualTagActor.Props(
"vt-1", "expr",
evaluator: new FailingEvaluator("boom"),
dependencyRefs: new[] { "a", "b" }));
// Only "a" has arrived — "b" is still missing, so the gate suppresses the Bad publish.
actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow));
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
}
/// <summary>02/S13 inputs-ready gate: once every declared dependency has arrived at least once, a
/// failure publishes exactly one Bad EvaluationResult.</summary>
[Fact]
public void Failure_after_all_dependencies_arrived_publishes_Bad()
{
var parent = CreateTestProbe();
var actor = parent.ChildActorOf(VirtualTagActor.Props(
"vt-1", "expr",
evaluator: new FailingEvaluator("boom"),
dependencyRefs: new[] { "a", "b" }));
actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow));
// Still missing "b" — no publish yet.
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
// "b" arrives — all refs now seen, so the failure degrades to Bad.
actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 2, DateTime.UtcNow));
parent.ExpectMsg<VirtualTagActor.EvaluationResult>().Quality.ShouldBe(OpcUaQuality.Bad);
}
/// <summary>Test evaluator returning a fixed sequence of results, one per Evaluate call; the last /// <summary>Test evaluator returning a fixed sequence of results, one per Evaluate call; the last
/// result repeats once the queue is drained.</summary> /// result repeats once the queue is drained.</summary>
private sealed class QueuedEvaluator : IVirtualTagEvaluator private sealed class QueuedEvaluator : IVirtualTagEvaluator