using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// Shared in-memory fixture for UnsTreeService structural tests. Builds an /// over a named InMemory database and seeds a small, /// deterministic UNS hierarchy: enterprise "zb" across two clusters (MAIN populated, /// SITE-A intentionally empty), plus tags and a virtual tag on one equipment node so /// the per-equipment count joins can be exercised. /// internal static class UnsTreeTestDb { /// The equipment that carries the seeded tags and virtual tag. public const string SeededEquipmentId = "EQ-000000000001"; /// The cluster that has no areas, used to cover the empty-cluster case. public const string EmptyClusterId = "SITE-A"; /// The populated cluster with the area→line→equipment path. public const string PopulatedClusterId = "MAIN"; /// Creates a context over a fresh, uniquely-named InMemory database. public static OtOpcUaConfigDbContext Create() => CreateNamed($"uns-{Guid.NewGuid():N}"); /// Creates a context bound to the supplied InMemory database name. public static OtOpcUaConfigDbContext CreateNamed(string name) => new(new DbContextOptionsBuilder() .UseInMemoryDatabase(name) .Options); /// /// Returns an whose contexts all share the /// supplied InMemory database name, so data seeded by is visible /// to the service under test. /// public static IDbContextFactory Factory(string name) => new NamedFactory(name); /// Seeds the fixture into the supplied (already-bound) context and saves. public static void Seed(OtOpcUaConfigDbContext db) { // Two clusters under the same enterprise; only MAIN gets a hierarchy. db.ServerClusters.Add(new ServerCluster { ClusterId = PopulatedClusterId, Name = "Main", Enterprise = "zb", Site = "warsaw-west", RedundancyMode = RedundancyMode.None, CreatedBy = "test", }); db.ServerClusters.Add(new ServerCluster { ClusterId = EmptyClusterId, Name = "Site A", Enterprise = "zb", Site = "site-a", RedundancyMode = RedundancyMode.None, CreatedBy = "test", }); // MAIN: one area → one line → one equipment. db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-1", ClusterId = PopulatedClusterId, Name = "assembly", }); db.UnsLines.Add(new UnsLine { UnsLineId = "LINE-1", UnsAreaId = "AREA-1", Name = "line-a", }); db.Equipment.Add(new Equipment { EquipmentId = SeededEquipmentId, EquipmentUuid = Guid.NewGuid(), UnsLineId = "LINE-1", Name = "machine-1", MachineCode = "machine_001", }); // Two driver tags + one virtual tag on the seeded equipment → ChildCount 3. db.Tags.Add(new Tag { TagId = "TAG-1", DriverInstanceId = "DRV-1", EquipmentId = SeededEquipmentId, Name = "speed", DataType = "Float", AccessLevel = TagAccessLevel.Read, TagConfig = "{}", }); db.Tags.Add(new Tag { TagId = "TAG-2", DriverInstanceId = "DRV-1", EquipmentId = SeededEquipmentId, Name = "running", DataType = "Boolean", AccessLevel = TagAccessLevel.Read, TagConfig = "{}", }); // A tag with no equipment must be ignored by the count query. db.Tags.Add(new Tag { TagId = "TAG-ORPHAN", DriverInstanceId = "DRV-1", EquipmentId = null, Name = "orphan", DataType = "Int32", AccessLevel = TagAccessLevel.Read, TagConfig = "{}", }); db.VirtualTags.Add(new VirtualTag { VirtualTagId = "VTAG-1", EquipmentId = SeededEquipmentId, Name = "computed", DataType = "Double", ScriptId = "SCRIPT-1", }); db.SaveChanges(); } /// Seeds the fixture into a context bound to the supplied InMemory database name. public static void SeedNamed(string name) { using var db = CreateNamed(name); Seed(db); } /// /// Minimal that hands back contexts sharing a /// single InMemory database name — the test-side stand-in for the runtime pooled factory. /// private sealed class NamedFactory(string name) : IDbContextFactory { public OtOpcUaConfigDbContext CreateDbContext() => CreateNamed(name); public Task CreateDbContextAsync(CancellationToken cancellationToken = default) => Task.FromResult(CreateNamed(name)); } }