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));
}
}