using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// lmxopcua#815 at the SAVE path (RawTreeService): a Galaxy / OpcUaClient credential reference outside the /// driver's namespace never reaches the config DB, through driver and device create + update. /// [Trait("Category", "Unit")] public sealed class CredentialReferenceSavePathTests { private static CancellationToken Ct => TestContext.Current.CancellationToken; [Theory] [InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://evil:5001","ApiKeySecretRef":"env:ConnectionStrings__ConfigDb"}}""")] [InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"ApiKeySecretRef":"file:appsettings.json"}}""")] [InlineData(DriverTypeNames.Galaxy, """{"AlarmAck":{"FallbackPasswordSecretRef":"secret:otopcua/apikeys/ab/galaxy-ack-password"}}""")] [InlineData(DriverTypeNames.OpcUaClient, """{"EndpointUrl":"opc.tcp://evil:4840","Password":"secret:otopcua/historian/api-key"}""")] [InlineData(DriverTypeNames.OpcUaClient, """{"UserCertificatePassword":"env:ServerHistorian__ApiKey"}""")] public async Task CreateDriver_refuses_an_out_of_namespace_reference_and_persists_nothing(string type, string config) { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); var result = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "d815", type, config, Ct); result.Ok.ShouldBeFalse(); result.Error!.ShouldContain("lmxopcua#815"); using var db = RawTreeTestDb.CreateNamed(name); db.DriverInstances.ShouldNotContain(d => d.Name == "d815"); } [Fact] public async Task Update_paths_refuse_and_keep_the_stored_config() { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); var created = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "gx", DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://gw:5001","ApiKeySecretRef":"secret:galaxy/gx/apikey"}}""", Ct); created.Ok.ShouldBeTrue(created.Error); byte[] rv; string deviceId; byte[] deviceRv; using (var db = RawTreeTestDb.CreateNamed(name)) { rv = db.DriverInstances.Single(d => d.DriverInstanceId == created.CreatedId).RowVersion; var device = db.Devices.Single(d => d.DriverInstanceId == created.CreatedId); deviceId = device.DeviceId; deviceRv = device.RowVersion; } (await svc.UpdateDriverAsync(created.CreatedId!, "gx", """{"Gateway":{"ApiKeySecretRef":"env:ConnectionStrings__ConfigDb"}}""", null, rv, Ct)).Ok.ShouldBeFalse(); (await svc.UpdateDeviceAsync(deviceId, "dev", """{"Gateway":{"ApiKeySecretRef":"env:ConnectionStrings__ConfigDb"}}""", true, deviceRv, Ct)).Ok.ShouldBeFalse(); (await svc.CreateDeviceAsync(created.CreatedId!, "dev2", """{"Gateway":{"ApiKeySecretRef":"secret:otopcua/historian/api-key"}}""", Ct)).Ok.ShouldBeFalse(); using var check = RawTreeTestDb.CreateNamed(name); check.DriverInstances.Single(d => d.DriverInstanceId == created.CreatedId).DriverConfig.ShouldContain("secret:galaxy/gx/apikey"); check.Devices.Where(d => d.DriverInstanceId == created.CreatedId) .ShouldAllBe(d => !d.DeviceConfig.Contains("ConnectionStrings") && !d.DeviceConfig.Contains("historian")); } [Theory] [InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"ApiKeySecretRef":"env:GALAXY_MXGW_API_KEY"},"AlarmAck":{"FallbackPasswordSecretRef":"secret:otopcua/galaxy/ack-fallback"}}""")] [InlineData(DriverTypeNames.OpcUaClient, """{"Password":"literal-password","UserCertificatePassword":"secret:opcuaclient/plant/cert"}""")] public async Task Namespaced_references_save(string type, string config) { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); var result = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "ok815", type, config, Ct); result.Ok.ShouldBeTrue(result.Error); } }