diff --git a/archreview/plans/R2-03-vt-failure-quality-plan.md.tasks.json b/archreview/plans/R2-03-vt-failure-quality-plan.md.tasks.json index 1ec95919..ec8ebe41 100644 --- a/archreview/plans/R2-03-vt-failure-quality-plan.md.tasks.json +++ b/archreview/plans/R2-03-vt-failure-quality-plan.md.tasks.json @@ -34,7 +34,7 @@ { "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)", - "status": "pending", + "status": "completed", "blockedBy": [ "R2-03-T3" ] diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs index 1a9913ce..a63d9ed5 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs @@ -142,6 +142,44 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase parent.ExpectMsg().Quality.ShouldBe(OpcUaQuality.Bad); } + /// 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. + [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)); + } + + /// 02/S13 inputs-ready gate: once every declared dependency has arrived at least once, a + /// failure publishes exactly one Bad EvaluationResult. + [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().Quality.ShouldBe(OpcUaQuality.Bad); + } + /// Test evaluator returning a fixed sequence of results, one per Evaluate call; the last /// result repeats once the queue is drained. private sealed class QueuedEvaluator : IVirtualTagEvaluator