d08e59eea7
RawTreeService.CreateDriverAsync / RenameDriverAsync / UpdateDriverAsync run DriverConfigSaveGate.ValidateNameAsync -> DriverInstanceIdentities.DescribeNameChangeProblemsAsync: - claim: the new name may not be the instance part (secret: segment or env: part) of a reference STORED on another same-type driver's DriverConfig or DeviceConfig - naming a driver 'gw' used to fail every sharer's next deploy; - pin: a driver may not be renamed away from a name its own references are named after (unless they also name its id or the new name) - the rename would free that name for every other driver's save / Test Connect / browse. Re-pointing to the id in the same config-modal save is allowed; a case-only rename is never refused. Messages name the other driver, id, device and field path, never a value. The driver + device reads are pinned on SQL Server by a [SqlServerFact]. Co-Authored-By: Claude <noreply@anthropic.com>
68 lines
3.3 KiB
C#
68 lines
3.3 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// The SQL-Server-backed half of <see cref="CredentialNameClaimSavePathTests"/> (lmxopcua#848): the driver + device
|
|
/// reads of <see cref="DriverInstanceIdentities.DescribeNameChangeProblemsAsync"/> 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).
|
|
/// </summary>
|
|
[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();
|
|
}
|
|
}
|