fix(siteruntime): MV-8 review fixes (construct list inside try; dictionary attr lookup; test hygiene)

This commit is contained in:
Joseph Doherty
2026-06-16 15:48:25 -04:00
parent 4765706e94
commit 96e817a7e1
2 changed files with 76 additions and 12 deletions
@@ -583,6 +583,49 @@ public class InstanceActorTests : TestKit, IDisposable
ExpectNoTerminated(actor, TimeSpan.FromMilliseconds(500));
}
/// <summary>
/// MV-8 design contract: a failed coercion keeps the PRIOR value. A good
/// array is delivered first and stored as a typed list; a subsequent array
/// with a non-coercible element must NOT overwrite that value — the stored
/// value stays the prior list while the quality flips to <c>Bad</c>.
/// </summary>
[Fact]
public void InstanceActor_DataSourcedListAttribute_BadCoercion_PreservesPriorValue()
{
const string tag = "ns=3;s=Pump.Keep";
var config = new FlattenedConfiguration
{
InstanceUniqueName = "Pump-ListKeep",
Attributes =
[
new ResolvedAttribute
{
CanonicalName = "Counts", Value = null,
DataType = "List", ElementDataType = "Int32",
DataSourceReference = tag, BoundDataConnectionName = "PLC"
}
]
};
var dcl = CreateTestProbe();
var actor = CreateInstanceActorWithDcl("Pump-ListKeep", config, dcl);
// (1) A good array establishes the prior value.
actor.Tell(new TagValueUpdate("PLC", tag, new[] { 1, 2, 3 }, QualityCode.Good, DateTimeOffset.UtcNow));
// (2) A second array with a non-coercible element must not overwrite it.
actor.Tell(new TagValueUpdate("PLC", tag, new object[] { 4, "not-a-number", 6 }, QualityCode.Good, DateTimeOffset.UtcNow));
// (3) The stored value is still the prior list; quality is Bad.
actor.Tell(new GetAttributeRequest("corr-keep", "Pump-ListKeep", "Counts", DateTimeOffset.UtcNow));
var response = ExpectMsg<GetAttributeResponse>(TimeSpan.FromSeconds(5));
Assert.True(response.Found);
Assert.Equal("Bad", response.Quality);
var list = Assert.IsType<List<int>>(response.Value);
Assert.Equal(new[] { 1, 2, 3 }, list);
}
/// <summary>
/// MV-8 guard: scalar (non-List) data-sourced attributes keep the existing
/// pass-through behaviour — a scalar value is stored unchanged.
@@ -620,7 +663,7 @@ public class InstanceActorTests : TestKit, IDisposable
private void ExpectNoTerminated(IActorRef actor, TimeSpan within)
{
// The actor is Watch()ed; assert no Terminated arrives in the window.
// (Liveness is also proven by the preceding successful GetAttributeResponse.)
ExpectNoMsg(within);
Assert.False(actor.IsNobody());
}
}