using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// Verifies builds the Enterprise→Cluster→ /// Area→Line→Equipment structure from the config database, including per-equipment tag/ /// virtual-tag counts and the retention of empty clusters. /// [Trait("Category", "Unit")] public sealed class UnsTreeServiceStructureTests { private static UnsTreeService SeededService() { var dbName = $"uns-{Guid.NewGuid():N}"; UnsTreeTestDb.SeedNamed(dbName); return new UnsTreeService(UnsTreeTestDb.Factory(dbName)); } /// Root enterprise "zb" carries both clusters, and MAIN exposes the full /// area→line→equipment path with the expected kinds and keys. [Fact] public async Task LoadStructure_builds_full_hierarchy() { var service = SeededService(); var roots = await service.LoadStructureAsync(); var enterprise = roots.ShouldHaveSingleItem(); enterprise.Kind.ShouldBe(UnsNodeKind.Enterprise); enterprise.DisplayName.ShouldBe("zb"); enterprise.Children.Count.ShouldBe(2); enterprise.Children.ShouldAllBe(c => c.Kind == UnsNodeKind.Cluster); var main = enterprise.Children.Single(c => c.ClusterId == UnsTreeTestDb.PopulatedClusterId); main.Kind.ShouldBe(UnsNodeKind.Cluster); var area = main.Children.ShouldHaveSingleItem(); area.Kind.ShouldBe(UnsNodeKind.Area); area.Key.ShouldBe("area:AREA-1"); area.EntityId.ShouldBe("AREA-1"); var line = area.Children.ShouldHaveSingleItem(); line.Kind.ShouldBe(UnsNodeKind.Line); line.Key.ShouldBe("line:LINE-1"); line.EntityId.ShouldBe("LINE-1"); var equipment = line.Children.ShouldHaveSingleItem(); equipment.Kind.ShouldBe(UnsNodeKind.Equipment); equipment.Key.ShouldBe($"eq:{UnsTreeTestDb.SeededEquipmentId}"); equipment.EntityId.ShouldBe(UnsTreeTestDb.SeededEquipmentId); equipment.ClusterId.ShouldBe(UnsTreeTestDb.PopulatedClusterId); } /// The seeded equipment node's badge count equals tags + virtual tags and it is /// flagged as lazily expandable. [Fact] public async Task LoadStructure_counts_tags_and_vtags_per_equipment() { var service = SeededService(); var roots = await service.LoadStructureAsync(); var equipment = roots .Single() .Children.Single(c => c.ClusterId == UnsTreeTestDb.PopulatedClusterId) .Children.Single() .Children.Single() .Children.Single(); // Seed: 2 driver tags + 1 virtual tag (the orphan tag has no equipment and is excluded). equipment.ChildCount.ShouldBe(3); equipment.HasLazyChildren.ShouldBeTrue(); } /// An empty cluster (no areas) is still rendered as a Cluster node with no children. [Fact] public async Task LoadStructure_includes_empty_clusters() { var service = SeededService(); var roots = await service.LoadStructureAsync(); var siteA = roots .Single() .Children.Single(c => c.ClusterId == UnsTreeTestDb.EmptyClusterId); siteA.Kind.ShouldBe(UnsNodeKind.Cluster); siteA.Children.ShouldBeEmpty(); siteA.ChildCount.ShouldBe(0); } }