using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// Verifies the load-for-edit projections on that prefill the /// Area/Line/Equipment edit modals and carry the concurrency token back for last-write-wins saves. /// [Trait("Category", "Unit")] public sealed class UnsTreeServiceLoadEditTests { private static (UnsTreeService Service, string DbName) Seeded() { var dbName = $"uns-loadedit-{Guid.NewGuid():N}"; UnsTreeTestDb.SeedNamed(dbName); return (new UnsTreeService(UnsTreeTestDb.Factory(dbName)), dbName); } /// Loading a seeded area maps its fields, owning cluster, and a non-empty RowVersion. [Fact] public async Task LoadArea_returns_dto() { var (service, _) = Seeded(); var dto = await service.LoadAreaAsync("AREA-1"); dto.ShouldNotBeNull(); dto.UnsAreaId.ShouldBe("AREA-1"); dto.Name.ShouldBe("assembly"); dto.ClusterId.ShouldBe(UnsTreeTestDb.PopulatedClusterId); dto.RowVersion.ShouldNotBeNull(); } /// Loading a missing area returns null. [Fact] public async Task LoadArea_missing_returns_null() { var (service, _) = Seeded(); (await service.LoadAreaAsync("NOPE")).ShouldBeNull(); } /// Loading a seeded line maps its fields, parent area, and a non-empty RowVersion. [Fact] public async Task LoadLine_returns_dto() { var (service, _) = Seeded(); var dto = await service.LoadLineAsync("LINE-1"); dto.ShouldNotBeNull(); dto.UnsLineId.ShouldBe("LINE-1"); dto.UnsAreaId.ShouldBe("AREA-1"); dto.Name.ShouldBe("line-a"); dto.RowVersion.ShouldNotBeNull(); } /// Loading a missing line returns null. [Fact] public async Task LoadLine_missing_returns_null() { var (service, _) = Seeded(); (await service.LoadLineAsync("NOPE")).ShouldBeNull(); } /// Loading the seeded equipment maps its identity fields, line, and a non-empty RowVersion. [Fact] public async Task LoadEquipment_returns_dto() { var (service, _) = Seeded(); var dto = await service.LoadEquipmentAsync(UnsTreeTestDb.SeededEquipmentId); dto.ShouldNotBeNull(); dto.EquipmentId.ShouldBe(UnsTreeTestDb.SeededEquipmentId); dto.Name.ShouldBe("machine-1"); dto.MachineCode.ShouldBe("machine_001"); dto.UnsLineId.ShouldBe("LINE-1"); dto.RowVersion.ShouldNotBeNull(); } /// Loading a missing equipment returns null. [Fact] public async Task LoadEquipment_missing_returns_null() { var (service, _) = Seeded(); (await service.LoadEquipmentAsync("NOPE")).ShouldBeNull(); } /// /// LoadDriversForCluster returns every driver in the cluster (any namespace kind), ordered by id, /// with the "{Id} — {Name} ({DriverType})" display; drivers in other clusters are excluded. /// [Fact] public async Task LoadDriversForCluster_returns_cluster_drivers() { var (service, dbName) = Seeded(); await using (var db = UnsTreeTestDb.CreateNamed(dbName)) { db.DriverInstances.Add(new DriverInstance { DriverInstanceId = "DRV-B", ClusterId = UnsTreeTestDb.PopulatedClusterId, NamespaceId = "NS-1", Name = "modbus-b", DriverType = "ModbusTcp", DriverConfig = "{}", }); db.DriverInstances.Add(new DriverInstance { DriverInstanceId = "DRV-A", ClusterId = UnsTreeTestDb.PopulatedClusterId, NamespaceId = "NS-1", Name = "galaxy-a", DriverType = "Galaxy", DriverConfig = "{}", }); // A driver in a different cluster must be excluded. db.DriverInstances.Add(new DriverInstance { DriverInstanceId = "DRV-OTHER", ClusterId = UnsTreeTestDb.EmptyClusterId, NamespaceId = "NS-2", Name = "other", DriverType = "S7", DriverConfig = "{}", }); await db.SaveChangesAsync(); } var drivers = await service.LoadDriversForClusterAsync(UnsTreeTestDb.PopulatedClusterId); drivers.Count.ShouldBe(2); drivers[0].DriverInstanceId.ShouldBe("DRV-A"); drivers[0].Display.ShouldBe("DRV-A — galaxy-a (Galaxy)"); drivers[1].DriverInstanceId.ShouldBe("DRV-B"); drivers[1].Display.ShouldBe("DRV-B — modbus-b (ModbusTcp)"); } /// Loading a seeded tag maps its fields, owning equipment, and a non-empty RowVersion. [Fact] public async Task LoadTag_returns_dto() { var (service, _) = Seeded(); var dto = await service.LoadTagAsync("TAG-1"); dto.ShouldNotBeNull(); dto.TagId.ShouldBe("TAG-1"); dto.EquipmentId.ShouldBe(UnsTreeTestDb.SeededEquipmentId); dto.Name.ShouldBe("speed"); dto.DriverInstanceId.ShouldBe("DRV-1"); dto.DataType.ShouldBe("Float"); dto.AccessLevel.ShouldBe(TagAccessLevel.Read); dto.TagConfig.ShouldBe("{}"); dto.RowVersion.ShouldNotBeNull(); } /// Loading a missing tag returns null. [Fact] public async Task LoadTag_missing_returns_null() { var (service, _) = Seeded(); (await service.LoadTagAsync("NOPE")).ShouldBeNull(); } /// Loading a seeded virtual tag maps its fields, owning equipment, and a non-empty RowVersion. [Fact] public async Task LoadVirtualTag_returns_dto() { var (service, _) = Seeded(); var dto = await service.LoadVirtualTagAsync("VTAG-1"); dto.ShouldNotBeNull(); dto.VirtualTagId.ShouldBe("VTAG-1"); dto.EquipmentId.ShouldBe(UnsTreeTestDb.SeededEquipmentId); dto.Name.ShouldBe("computed"); dto.DataType.ShouldBe("Double"); dto.ScriptId.ShouldBe("SCRIPT-1"); dto.RowVersion.ShouldNotBeNull(); } /// Loading a missing virtual tag returns null. [Fact] public async Task LoadVirtualTag_missing_returns_null() { var (service, _) = Seeded(); (await service.LoadVirtualTagAsync("NOPE")).ShouldBeNull(); } }