fix(siteruntime): reject SetStaticAttribute with malformed list value (no silent poison persist)
This commit is contained in:
@@ -840,4 +840,92 @@ public class InstanceActorTests : TestKit, IDisposable
|
||||
Assert.Equal("Good", response.Quality);
|
||||
Assert.Equal("Main Pump", response.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MV-7 (review fix): a SetStaticAttribute write whose value fails to decode as
|
||||
/// a list (e.g. truncated JSON) on a List attribute must be REJECTED — reply
|
||||
/// Success=false with a clear error and persist NOTHING. The script path always
|
||||
/// pre-encodes valid JSON, but the Inbound API / direct-command path can submit
|
||||
/// an arbitrary value, so a malformed value must not silently null the in-memory
|
||||
/// value, publish "Good" quality, and durably persist a poison override.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InstanceActor_SetStaticListAttribute_Malformed_Rejected_NotPersisted()
|
||||
{
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Pump-BadSet",
|
||||
Attributes =
|
||||
[
|
||||
new ResolvedAttribute
|
||||
{
|
||||
CanonicalName = "Labels", Value = "[\"a\",\"b\"]",
|
||||
DataType = "List", ElementDataType = "String"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var actor = CreateInstanceActor("Pump-BadSet", config);
|
||||
|
||||
// Submit a malformed list value (truncated JSON).
|
||||
actor.Tell(new SetStaticAttributeCommand(
|
||||
"corr-bad-set", "Pump-BadSet", "Labels", "[\"a\"", DateTimeOffset.UtcNow));
|
||||
var setResponse = ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(5));
|
||||
Assert.False(setResponse.Success);
|
||||
Assert.False(string.IsNullOrWhiteSpace(setResponse.ErrorMessage));
|
||||
|
||||
// The poison value must NOT have been persisted as a static override.
|
||||
await Task.Delay(500);
|
||||
var overrides = await _storage.GetStaticOverridesAsync("Pump-BadSet");
|
||||
Assert.Empty(overrides);
|
||||
|
||||
// A subsequent read returns the untouched config default — not the poison value.
|
||||
actor.Tell(new GetAttributeRequest("corr-bad-get", "Pump-BadSet", "Labels", DateTimeOffset.UtcNow));
|
||||
var getResponse = ExpectMsg<GetAttributeResponse>(TimeSpan.FromSeconds(5));
|
||||
Assert.True(getResponse.Found);
|
||||
var list = Assert.IsType<List<string>>(getResponse.Value);
|
||||
Assert.Equal(new[] { "a", "b" }, list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MV-7 (review fix): an empty-list value "[]" decodes to a non-null empty list
|
||||
/// and must be accepted (NOT mistaken for a malformed value, which also decodes
|
||||
/// to null). This pins the boundary between the "clearing/empty" and "poison"
|
||||
/// cases that both surface as a null decode result.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InstanceActor_SetStaticListAttribute_EmptyList_Accepted()
|
||||
{
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Pump-EmptySet",
|
||||
Attributes =
|
||||
[
|
||||
new ResolvedAttribute
|
||||
{
|
||||
CanonicalName = "Labels", Value = "[\"a\",\"b\"]",
|
||||
DataType = "List", ElementDataType = "String"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var actor = CreateInstanceActor("Pump-EmptySet", config);
|
||||
|
||||
actor.Tell(new SetStaticAttributeCommand(
|
||||
"corr-empty-set", "Pump-EmptySet", "Labels", "[]", DateTimeOffset.UtcNow));
|
||||
var setResponse = ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(5));
|
||||
Assert.True(setResponse.Success);
|
||||
|
||||
actor.Tell(new GetAttributeRequest("corr-empty-get", "Pump-EmptySet", "Labels", DateTimeOffset.UtcNow));
|
||||
var getResponse = ExpectMsg<GetAttributeResponse>(TimeSpan.FromSeconds(5));
|
||||
Assert.True(getResponse.Found);
|
||||
var list = Assert.IsType<List<string>>(getResponse.Value);
|
||||
Assert.Empty(list);
|
||||
|
||||
// The canonical JSON "[]" is persisted unchanged.
|
||||
await Task.Delay(500);
|
||||
var overrides = await _storage.GetStaticOverridesAsync("Pump-EmptySet");
|
||||
Assert.Single(overrides);
|
||||
Assert.Equal("[]", overrides["Labels"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user