fix(siteruntime): encode list attribute writes via AttributeValueCodec (was .ToString())
Replace value?.ToString() with AttributeValueCodec.Encode(value) in
AttributeAccessor indexer set and SetAsync, so a List<string>{"a","b"}
encodes to ["a","b"] instead of the garbage ToString representation.
Add using ZB.MOM.WW.ScadaBridge.Commons.Types. Tests verify the codec
contract (list→JSON array, scalar passthrough, null); full round-trip
through the accessor is not viable without a live Akka ActorSystem —
noted in-test with explanation.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -53,7 +55,7 @@ public class AttributeAccessor
|
|||||||
// on the DCL round-trip for data-connected attributes. The async
|
// on the DCL round-trip for data-connected attributes. The async
|
||||||
// variants (GetAsync/SetAsync) are preferred where awaiting is possible.
|
// variants (GetAsync/SetAsync) are preferred where awaiting is possible.
|
||||||
get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult();
|
get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult();
|
||||||
set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty).GetAwaiter().GetResult();
|
set => _ctx.SetAttribute(Resolve(key), AttributeValueCodec.Encode(value) ?? string.Empty).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -70,7 +72,7 @@ public class AttributeAccessor
|
|||||||
/// <param name="value">The value to set.</param>
|
/// <param name="value">The value to set.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
public Task SetAsync(string key, object? value)
|
public Task SetAsync(string key, object? value)
|
||||||
=> _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
|
=> _ctx.SetAttribute(Resolve(key), AttributeValueCodec.Encode(value) ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
|
||||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||||
|
|
||||||
@@ -84,4 +85,54 @@ public class ScopeAccessorTests
|
|||||||
var temp = children["TempSensor"];
|
var temp = children["TempSensor"];
|
||||||
Assert.Equal("Motor.TempSensor", temp.Path);
|
Assert.Equal("Motor.TempSensor", temp.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- AttributeAccessor encoding contract ----------------------------------
|
||||||
|
//
|
||||||
|
// AttributeAccessor.this[key].set and SetAsync both route through
|
||||||
|
// ScriptRuntimeContext.SetAttribute(name, encodedString), which requires
|
||||||
|
// a live Akka IActorRef; ScriptRuntimeContext has no virtual members and
|
||||||
|
// its constructor cannot be satisfied without a real ActorSystem, so a
|
||||||
|
// full-round-trip unit test through the accessor+context is not viable
|
||||||
|
// without a heavy Akka harness.
|
||||||
|
//
|
||||||
|
// Instead we test the encoding decision directly: AttributeAccessor is now
|
||||||
|
// documented to delegate value serialisation to AttributeValueCodec.Encode.
|
||||||
|
// These tests verify that contract at the codec level, which is exactly what
|
||||||
|
// the fix makes the accessor invoke.
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AttributeValueCodec_Encode_List_ProducesJsonArray()
|
||||||
|
{
|
||||||
|
// A List<string> must encode to a JSON array, not the garbage
|
||||||
|
// "System.Collections.Generic.List`1[System.String]" that .ToString() produced.
|
||||||
|
var list = new List<string> { "a", "b" };
|
||||||
|
var encoded = AttributeValueCodec.Encode(list);
|
||||||
|
Assert.Equal("[\"a\",\"b\"]", encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AttributeValueCodec_Encode_Scalar_PassesThrough()
|
||||||
|
{
|
||||||
|
// A plain string scalar must be returned unchanged (byte-identical to
|
||||||
|
// the historical value?.ToString() path for strings).
|
||||||
|
var encoded = AttributeValueCodec.Encode("x");
|
||||||
|
Assert.Equal("x", encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AttributeValueCodec_Encode_Null_ReturnsNull()
|
||||||
|
{
|
||||||
|
// AttributeAccessor coalesces null → "" at the call site,
|
||||||
|
// but the codec itself must return null for null input.
|
||||||
|
Assert.Null(AttributeValueCodec.Encode(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AttributeValueCodec_Encode_IntList_ProducesJsonArray()
|
||||||
|
{
|
||||||
|
// Integer list elements encode via InvariantCulture IFormattable.
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
var encoded = AttributeValueCodec.Encode(list);
|
||||||
|
Assert.Equal("[\"1\",\"2\",\"3\"]", encoded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user