using Microsoft.EntityFrameworkCore; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// Secrets-adoption (G-2c) regression guard: the AdminUI driver-config editor persists DriverConfig /// as an OPAQUE JSON string through (see UpdateDriverAsync / /// CreateDriverAsyncentity.DriverConfig = driverConfigJson verbatim, and /// LoadDriverForEditAsync reads it back verbatim). A secret:/env:/file: /// reference stored in a driver secret field (OpcUaClient Password/UserCertificatePassword, /// Galaxy Gateway.ApiKeySecretRef) MUST survive save→reload as the literal ref string — the save /// path must never resolve-then-resave cleartext (which would re-leak the secret into the config store and /// defeat G-2). Secret resolution belongs ONLY at driver-instantiation / Test-connect time (Tasks 7-8), /// never on the AdminUI persistence path — and indeed takes no secret resolver /// (its only dependency is the DbContext factory), so resolution is structurally impossible here. /// /// Reuses the shared InMemory fixture, matching the WP3 driver-config tests. /// /// [Trait("Category", "Unit")] public sealed class RawTreeServiceSecretRefRoundTripTests { private static (RawTreeService Service, string DbName) Seeded() { var name = RawTreeTestDb.SeedFresh(); return (new RawTreeService(RawTreeTestDb.Factory(name)), name); } private static byte[] RowVersionOf(string dbName, string driverId) { using var db = RawTreeTestDb.CreateNamed(dbName); return db.DriverInstances.Single(d => d.DriverInstanceId == driverId).RowVersion; } [Fact] public async Task UpdateDriver_preserves_OpcUaClient_secret_refs_verbatim_on_save_and_reload() { var (service, dbName) = Seeded(); var rv = RowVersionOf(dbName, RawTreeTestDb.FolderedDriverId); // The editor emits the raw ref strings the user typed — they must NOT be resolved on save. const string config = "{\"Password\":\"secret:opcua/x/pw\",\"UserCertificatePassword\":\"secret:opcua/x/certpw\"}"; var result = await service.UpdateDriverAsync( RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv); result.Ok.ShouldBeTrue(); // Reload through the same seam the modal uses. var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId); reloaded.ShouldNotBeNull(); // Byte-identical: nothing on the save path rewrote the string. reloaded!.DriverConfig.ShouldBe(config); // The literal refs survived (not resolved to cleartext, not stripped). reloaded.DriverConfig.ShouldContain("secret:opcua/x/pw"); reloaded.DriverConfig.ShouldContain("secret:opcua/x/certpw"); // And the raw column agrees with the projection. using var db = RawTreeTestDb.CreateNamed(dbName); db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.FolderedDriverId) .DriverConfig.ShouldBe(config); } [Fact] public async Task CreateDriver_preserves_Galaxy_ApiKeySecretRef_verbatim_on_save_and_reload() { var (service, dbName) = Seeded(); const string config = "{\"Gateway\":{\"ApiKeySecretRef\":\"secret:galaxy/x/apikey\"}}"; var created = await service.CreateDriverAsync( RawTreeTestDb.ClusterId, RawTreeTestDb.RootFolderId, "galaxy-1", "Galaxy", config); created.Ok.ShouldBeTrue(); created.CreatedId.ShouldNotBeNull(); var reloaded = await service.LoadDriverForEditAsync(created.CreatedId!); reloaded.ShouldNotBeNull(); reloaded!.DriverConfig.ShouldBe(config); reloaded.DriverConfig.ShouldContain("secret:galaxy/x/apikey"); using var db = RawTreeTestDb.CreateNamed(dbName); db.DriverInstances.Single(d => d.DriverInstanceId == created.CreatedId) .DriverConfig.ShouldBe(config); } [Fact] public async Task UpdateDriver_preserves_env_and_file_ref_forms_verbatim() { var (service, dbName) = Seeded(); var rv = RowVersionOf(dbName, RawTreeTestDb.FolderedDriverId); const string config = "{\"Password\":\"env:OPCUA_PW\",\"UserCertificatePassword\":\"file:/run/secrets/certpw\"}"; var result = await service.UpdateDriverAsync( RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv); result.Ok.ShouldBeTrue(); var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId); reloaded!.DriverConfig.ShouldBe(config); reloaded.DriverConfig.ShouldContain("env:OPCUA_PW"); reloaded.DriverConfig.ShouldContain("file:/run/secrets/certpw"); } }