using Microsoft.EntityFrameworkCore; 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; using ZB.MOM.WW.OtOpcUa.Configuration.Validation; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// The SQL-Server-backed half of (lmxopcua#848): the driver + device /// reads of must translate on the real provider, /// not only run as LINQ-to-Objects under EF InMemory (the #815 live gate's crash-looping boot came from exactly that gap). /// [Collection(nameof(AdminUiSqlServerCollection))] public sealed class CredentialNameClaimSqlServerTests(AdminUiSqlServerFixture sql) { private static CancellationToken Ct => TestContext.Current.CancellationToken; [SqlServerFact] public async Task Create_and_rename_are_checked_against_stored_driver_and_device_references() { // 8 hex chars: unique per run in the shared database, short enough for the id columns. var token = Guid.NewGuid().ToString("N")[..8]; var clusterId = $"C-{token}"; var sharerId = $"DRV-s{token}"; await using (var db = await sql.Factory.CreateDbContextAsync(Ct)) { db.ServerClusters.Add(new ServerCluster { ClusterId = clusterId, Name = $"Cluster {token}", Enterprise = "zb", Site = "warsaw-west", NodeCount = 1, RedundancyMode = RedundancyMode.None, CreatedBy = "test", }); db.DriverInstances.Add(new DriverInstance { DriverInstanceId = sharerId, ClusterId = clusterId, Name = $"Sharer{token}", DriverType = DriverTypeNames.Galaxy, DriverConfig = "{\"Gateway\":{\"ApiKeySecretRef\":\"secret:galaxy/gw" + token + "/k\"}}", }); db.Devices.Add(new Device { DeviceId = $"DEV-s{token}", DriverInstanceId = sharerId, Name = "Device1", DeviceConfig = "{\"AlarmAck\":{\"FallbackPasswordSecretRef\":\"env:OTOPCUA_GALAXY_SHARER" + token.ToUpperInvariant() + "_ACK\"}}", }); await db.SaveChangesAsync(Ct); } var svc = new RawTreeService(sql.Factory); // Claim (driver-level secret: reference). var create = await svc.CreateDriverAsync(clusterId, null, $"gw{token}", DriverTypeNames.Galaxy, "{}", Ct); create.Ok.ShouldBeFalse(); create.Error!.ShouldContain("lmxopcua#848"); // Pin (device-level env: reference named after the sharer's own name). byte[] rv; await using (var db = await sql.Factory.CreateDbContextAsync(Ct)) rv = await db.DriverInstances.AsNoTracking().Where(d => d.DriverInstanceId == sharerId).Select(d => d.RowVersion).SingleAsync(Ct); var rename = await svc.RenameDriverAsync(sharerId, $"Moved{token}", rv, Ct); rename.Ok.ShouldBeFalse(); rename.Error!.ShouldContain("device 'Device1'"); // Positive control: an unclaimed name creates. (await svc.CreateDriverAsync(clusterId, null, $"free{token}", DriverTypeNames.Galaxy, "{}", Ct)).Ok.ShouldBeTrue(); } }