using ZB.MOM.WW.ScadaBridge.Commons.Types; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment; /// /// MV-14: the Instance Configure attribute-override panel uses the shared /// AttributeListEditor for a List attribute (whole-list replacement; the /// element type is fixed by the base attribute, so the type select is hidden via /// ShowElementType="false"). Loading an existing override decodes its JSON /// into rows; saving encodes the rows back to canonical JSON with a pre-submit /// round-trip guard; clearing removes the override row. InstanceConfigure /// is a heavyweight page (multiple injected services incl. InstanceService /// and the flattening pipeline), so — consistent with the native-alarm and /// template-editor coverage — these are structural assertions over the component /// source that pin the wiring, plus a real codec round-trip mirroring what the /// page does on load/save. /// public class InstanceConfigureListOverrideTests { private static string InstanceConfigureMarkup { get { var dir = AppContext.BaseDirectory; for (var i = 0; i < 6 && dir is not null; i++) dir = Directory.GetParent(dir)?.FullName; return File.ReadAllText(Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI", "Components", "Pages", "Deployment", "InstanceConfigure.razor")); } } [Fact] public void ListOverride_RevealsSharedEditor_WithElementTypeHidden() { var markup = InstanceConfigureMarkup; // Conditional reveal on a List attribute. Assert.Contains("attr.DataType == DataType.List", markup); Assert.Contains(" OnListRowsChanged(attr.Name, r))\"", markup); } [Fact] public void ListOverride_DecodesOnLoad_AndEncodesOnSaveWithGuard() { var markup = InstanceConfigureMarkup; // Load: effective value (existing override JSON or template default) // decoded into rows via the shared codec, malformed → empty rows. Assert.Contains("DecodeListRows(", markup); Assert.Contains("catch (FormatException)", markup); // Save: rows encoded to canonical JSON + round-trip Decode guard. Assert.Contains("AttributeValueCodec.Encode(GetListRows(", markup); Assert.Contains("AttributeValueCodec.Decode(json, DataType.List, elementType)", markup); Assert.Contains("_overrideErrors", markup); } [Fact] public void ListOverride_ClearRemovesTheOverrideRow() { var markup = InstanceConfigureMarkup; Assert.Contains("ClearListOverride", markup); // Repository-direct delete (the page only edits InstanceConfigure; no new // server method) — same pattern as native-alarm-source overrides. Assert.Contains("DeleteInstanceAttributeOverrideAsync", markup); Assert.Contains("HasOverrideRow", markup); } [Fact] public void NonListOverride_KeepsSingleInputUx() { var markup = InstanceConfigureMarkup; // The scalar path still binds the single text input via the existing helpers. Assert.Contains("GetOverrideValue(attr.Name)", markup); Assert.Contains("OnOverrideChanged(attr.Name, e)", markup); } [Fact] public void EncodedRows_RoundTripThroughCodec_AsThePageDoes() { // Mirrors the load (Decode → rows) / save (Encode → JSON) cycle the page runs. var json = AttributeValueCodec.Encode(new List { "10", "20", "30" }); var decoded = AttributeValueCodec.Decode(json, DataType.List, DataType.Int32); var list = Assert.IsType>(decoded); Assert.Equal(new[] { 10, 20, 30 }, list); // The re-encoded form is stable, so a clean override round-trips losslessly. var roundTrip = AttributeValueCodec.Encode(decoded); Assert.Equal(json, roundTrip); } [Fact] public void MalformedListElement_SurfacesFormatException_ForInlineError() { // The pre-submit guard catches this and shows it inline rather than crashing. var json = AttributeValueCodec.Encode(new List { "1", "not-a-number" }); Assert.Throws( () => AttributeValueCodec.Decode(json, DataType.List, DataType.Int32)); } }