using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.AdminUI.Acls; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; 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.Acls; /// /// Fixture for — two clusters, so cross-cluster scoping is testable, and /// one deliberately broken UNS chain, so "the row exists but no cluster owns it" is testable /// separately from "the row does not exist". /// internal static class AclScopeTestDb { public const string ClusterId = "MAIN"; public const string OtherClusterId = "SIDE"; public const string FolderId = "RF-plcs"; public const string DriverId = "DRV-modbus"; public const string DeviceId = "DEV-1"; public const string TagGroupId = "TG-motors"; public const string TagId = "TAG-speed"; public const string AreaId = "AREA-1"; public const string LineId = "LINE-1"; public const string EquipmentId = "EQ-1"; /// An equipment whose line does not exist — present, but owned by no cluster. public const string OrphanEquipmentId = "EQ-orphan"; /// A driver in the OTHER cluster, for the cross-cluster rule. public const string OtherDriverId = "DRV-side"; public const string LivePrincipal = "scadabridge-dcl"; public const string StalePrincipal = "retired-consumer"; /// The clock the fixture's key expiries are expressed against. public static readonly DateTime Now = new(2026, 8, 1, 12, 0, 0, DateTimeKind.Utc); public static OtOpcUaConfigDbContext CreateNamed(string name) => new(new DbContextOptionsBuilder().UseInMemoryDatabase(name).Options); public static IDbContextFactory Factory(string name) => new NamedFactory(name); /// Seeds a fresh uniquely-named InMemory database and returns a service over it. public static AclScopeService NewService(TimeProvider? time = null) { var name = $"acl-{Guid.NewGuid():N}"; using (var db = CreateNamed(name)) { Seed(db); } var factory = Factory(name); return new AclScopeService(factory, new RawTreeService(factory), new UnsTreeService(factory), time); } public static void Seed(OtOpcUaConfigDbContext db) { db.ServerClusters.Add(new ServerCluster { ClusterId = ClusterId, Name = "Main", Enterprise = "zb", Site = "warsaw-west", RedundancyMode = RedundancyMode.None, CreatedBy = "test", }); db.ServerClusters.Add(new ServerCluster { ClusterId = OtherClusterId, Name = "Side", Enterprise = "zb", Site = "krakow", RedundancyMode = RedundancyMode.None, CreatedBy = "test", }); // MAIN raw: folder → driver → device → group → tag. db.RawFolders.Add(new RawFolder { RawFolderId = FolderId, ClusterId = ClusterId, ParentRawFolderId = null, Name = "PLCs", }); db.DriverInstances.Add(new DriverInstance { DriverInstanceId = DriverId, ClusterId = ClusterId, RawFolderId = FolderId, Name = "modbus-1", DriverType = "Modbus", DriverConfig = "{}", }); db.Devices.Add(new Device { DeviceId = DeviceId, DriverInstanceId = DriverId, Name = "dev1", DeviceConfig = "{}", }); db.TagGroups.Add(new TagGroup { TagGroupId = TagGroupId, DeviceId = DeviceId, ParentTagGroupId = null, Name = "Motors", }); db.Tags.Add(new Tag { TagId = TagId, DeviceId = DeviceId, TagGroupId = TagGroupId, Name = "speed", DataType = "Float", AccessLevel = TagAccessLevel.ReadWrite, TagConfig = "{}", }); // SIDE raw: one driver, so a cross-cluster scope has something real to point at. db.DriverInstances.Add(new DriverInstance { DriverInstanceId = OtherDriverId, ClusterId = OtherClusterId, RawFolderId = null, Name = "s7-side", DriverType = "S7", DriverConfig = "{}", }); // MAIN UNS: area → line → equipment. db.UnsAreas.Add(new UnsArea { UnsAreaId = AreaId, ClusterId = ClusterId, Name = "Packaging" }); db.UnsLines.Add(new UnsLine { UnsLineId = LineId, UnsAreaId = AreaId, Name = "Line 1" }); db.Equipment.Add(new Equipment { EquipmentId = EquipmentId, EquipmentUuid = Guid.NewGuid(), UnsLineId = LineId, Name = "packer-1", MachineCode = "packer_001", }); // An equipment whose line was deleted: it exists, but nothing owns it. db.Equipment.Add(new Equipment { EquipmentId = OrphanEquipmentId, EquipmentUuid = Guid.NewGuid(), UnsLineId = "LINE-GONE", Name = "stranded", MachineCode = "stranded_001", }); db.ApiKeys.Add(Key("k1", LivePrincipal, enabled: true, expires: null)); db.ApiKeys.Add(Key("k2", LivePrincipal, enabled: true, expires: null)); db.ApiKeys.Add(Key("k3", LivePrincipal, enabled: false, expires: null)); db.ApiKeys.Add(Key("k4", StalePrincipal, enabled: true, expires: Now.AddDays(-1))); db.SaveChanges(); } private static ApiKey Key(string id, string principal, bool enabled, DateTime? expires) => new() { ApiKeyId = id, Name = id, SecretHash = "not-a-secret", Principal = principal, Enabled = enabled, CreatedUtc = Now.AddDays(-30), ExpiresUtc = expires, }; private sealed class NamedFactory(string name) : IDbContextFactory { public OtOpcUaConfigDbContext CreateDbContext() => CreateNamed(name); public Task CreateDbContextAsync(CancellationToken cancellationToken = default) => Task.FromResult(CreateNamed(name)); } }