using System.Text.Json; using ZB.MOM.WW.ScadaBridge.CLI.Commands; using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates; namespace ZB.MOM.WW.ScadaBridge.CLI.Tests; /// /// Tests for the compact template list/get table projection (followup #6): /// the full per-template attribute/alarm/script dumps are collapsed to counts so table /// output is usable in a terminal, while the array/object shape is preserved. /// /// /// Two server shapes must both project correctly: template get still returns a /// full Template entity with child ARRAYS, while template list returns /// database-projected rows carrying pre-computed COUNT /// scalars and no arrays at all. Reading only the arrays made every listed template /// render as zeros. /// /// public class TemplateTableProjectionTests { private const string ListJson = """ [ { "id": 3, "name": "MESReceiver", "description": "base", "parentTemplateId": null, "isDerived": false, "attributes": [ {"id":1},{"id":2},{"id":3} ], "alarms": [ {"id":10} ], "scripts": [ {"id":20},{"id":21} ], "compositions": [], "nativeAlarmSources": [] }, { "id": 5, "name": "LeftMESReceiver", "description": null, "parentTemplateId": 3, "isDerived": false, "attributes": [ {"id":1} ], "alarms": [], "scripts": [], "compositions": [ {"id":99} ], "nativeAlarmSources": [ {"id":7} ] } ] """; [Fact] public void ProjectSummary_Array_DropsMemberArraysAndKeepsCounts() { var compact = TemplateTableProjection.ProjectSummary(ListJson); using var doc = JsonDocument.Parse(compact); var root = doc.RootElement; Assert.Equal(JsonValueKind.Array, root.ValueKind); Assert.Equal(2, root.GetArrayLength()); var first = root[0]; Assert.Equal(3, first.GetProperty("id").GetInt32()); Assert.Equal("MESReceiver", first.GetProperty("name").GetString()); Assert.Equal("base", first.GetProperty("description").GetString()); Assert.Equal(JsonValueKind.Null, first.GetProperty("parentTemplateId").ValueKind); Assert.Equal(3, first.GetProperty("#attrs").GetInt32()); Assert.Equal(1, first.GetProperty("#alarms").GetInt32()); Assert.Equal(2, first.GetProperty("#scripts").GetInt32()); Assert.Equal(0, first.GetProperty("#comps").GetInt32()); Assert.Equal(0, first.GetProperty("#nativeAlarms").GetInt32()); // The full member arrays must NOT survive — that is the whole point of the projection. Assert.False(first.TryGetProperty("attributes", out _)); Assert.False(first.TryGetProperty("scripts", out _)); var second = root[1]; Assert.Equal(5, second.GetProperty("id").GetInt32()); Assert.Equal(3, second.GetProperty("parentTemplateId").GetInt32()); Assert.Equal(1, second.GetProperty("#comps").GetInt32()); Assert.Equal(1, second.GetProperty("#nativeAlarms").GetInt32()); // A null description stays null (it is not invented). Assert.Equal(JsonValueKind.Null, second.GetProperty("description").ValueKind); } [Fact] public void ProjectSummary_SingleObject_ProducesCompactObject() { const string getJson = """ { "id": 7, "name": "ReactorSide", "description": "d", "parentTemplateId": null, "isDerived": false, "attributes": [ {"id":1},{"id":2} ], "alarms": [], "scripts": [], "compositions": [], "nativeAlarmSources": [] } """; var compact = TemplateTableProjection.ProjectSummary(getJson); using var doc = JsonDocument.Parse(compact); var root = doc.RootElement; Assert.Equal(JsonValueKind.Object, root.ValueKind); Assert.Equal(7, root.GetProperty("id").GetInt32()); Assert.Equal(2, root.GetProperty("#attrs").GetInt32()); Assert.False(root.TryGetProperty("attributes", out _)); } /// /// The template list shape. Serialised from the REAL /// record rather than hand-written JSON, so a /// rename of one of its count properties fails this test instead of silently /// putting zeros back on every row. /// [Fact] public void ProjectSummary_SummaryRows_ReadsCountScalars() { var rows = new[] { new TemplateSummary( Id: 3, Name: "MESReceiver", Description: "base", ParentTemplateId: null, FolderId: null, IsDerived: false, OwnerCompositionId: null, AttributeCount: 3, AlarmCount: 1, ScriptCount: 2, CompositionCount: 0, NativeAlarmSourceCount: 0), new TemplateSummary( Id: 5, Name: "LeftMESReceiver", Description: null, ParentTemplateId: 3, FolderId: 2, IsDerived: false, OwnerCompositionId: null, AttributeCount: 1, AlarmCount: 0, ScriptCount: 0, CompositionCount: 1, NativeAlarmSourceCount: 1), }; var json = JsonSerializer.Serialize(rows, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var compact = TemplateTableProjection.ProjectSummary(json); using var doc = JsonDocument.Parse(compact); var root = doc.RootElement; Assert.Equal(2, root.GetArrayLength()); var first = root[0]; Assert.Equal(3, first.GetProperty("id").GetInt32()); Assert.Equal("MESReceiver", first.GetProperty("name").GetString()); Assert.Equal(3, first.GetProperty("#attrs").GetInt32()); Assert.Equal(1, first.GetProperty("#alarms").GetInt32()); Assert.Equal(2, first.GetProperty("#scripts").GetInt32()); Assert.Equal(0, first.GetProperty("#comps").GetInt32()); Assert.Equal(0, first.GetProperty("#nativeAlarms").GetInt32()); var second = root[1]; Assert.Equal(5, second.GetProperty("id").GetInt32()); Assert.Equal(3, second.GetProperty("parentTemplateId").GetInt32()); Assert.Equal(1, second.GetProperty("#attrs").GetInt32()); Assert.Equal(1, second.GetProperty("#comps").GetInt32()); Assert.Equal(1, second.GetProperty("#nativeAlarms").GetInt32()); } /// /// A count scalar wins over a child array when (hypothetically) both are /// present, so a payload that gains summary fields never regresses to array /// counting. /// [Fact] public void ProjectSummary_PrefersCountScalarOverArray() { const string bothJson = """ { "id": 1, "name": "T", "attributeCount": 42, "attributes": [ {"id":1} ] } """; var compact = TemplateTableProjection.ProjectSummary(bothJson); using var doc = JsonDocument.Parse(compact); Assert.Equal(42, doc.RootElement.GetProperty("#attrs").GetInt32()); } [Fact] public void ProjectSummary_NonJson_ReturnedVerbatim() { const string notJson = "proxy error"; Assert.Equal(notJson, TemplateTableProjection.ProjectSummary(notJson)); } [Fact] public void ProjectSummary_IsSubstantiallySmallerThanFullDump() { // Sanity check that the projection actually shrinks output (the reported symptom // was ~171 KB table dumps). A template with a fat attribute array should collapse. var fatAttributes = string.Join(",", Enumerable.Range(0, 200).Select(i => $"{{\"id\":{i},\"name\":\"Attr{i}\",\"dataType\":\"String\",\"value\":\"some long-ish default value {i}\"}}")); var fullJson = $$""" [ { "id": 1, "name": "T", "description": "d", "parentTemplateId": null, "isDerived": false, "attributes": [ {{fatAttributes}} ], "alarms": [], "scripts": [], "compositions": [], "nativeAlarmSources": [] } ] """; var compact = TemplateTableProjection.ProjectSummary(fullJson); Assert.True(compact.Length * 4 < fullJson.Length, $"Expected compact ({compact.Length}) to be far smaller than full ({fullJson.Length})."); using var doc = JsonDocument.Parse(compact); Assert.Equal(200, doc.RootElement[0].GetProperty("#attrs").GetInt32()); } }