From a7c1d7f7cb3b5e51308584ef80a9eeb7fb517ee1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 10 Jun 2026 08:08:13 -0400 Subject: [PATCH] test(adminui): cover divergent-prefix {{equip}} rejection; use EquipToken constant in message --- .../Uns/IUnsTreeService.cs | 6 ++-- .../Uns/UnsTreeService.cs | 6 ++-- .../VirtualTagEquipTokenValidationTests.cs | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs index f75486f6..3bff22e0 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs @@ -375,7 +375,8 @@ public interface IUnsTreeService /// The owning equipment. /// The operator-editable virtual-tag fields. /// A token to cancel the operation. - /// Success, or one of the guard failures. + /// Success, or one of the guard failures; or if the chosen script uses the {{equip}} + /// token but the equipment has no derivable single tag base. Task CreateVirtualTagAsync(string equipmentId, VirtualTagInput input, CancellationToken ct = default); /// @@ -389,7 +390,8 @@ public interface IUnsTreeService /// The new operator-editable virtual-tag fields. /// The concurrency token the caller last read. /// A token to cancel the operation. - /// Success, a missing-row failure, a guard failure, or a concurrency failure. + /// Success, a missing-row failure, a guard failure, or a concurrency failure; or if the + /// chosen script uses the {{equip}} token but the equipment has no derivable single tag base. Task UpdateVirtualTagAsync(string virtualTagId, VirtualTagInput input, byte[] rowVersion, CancellationToken ct = default); /// diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs index f719f16f..8632ba13 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs @@ -908,12 +908,10 @@ public sealed class UnsTreeService(IDbContextFactory dbF var fullNames = configs.Select(TagConfigFullName.Extract); if (EquipmentScriptPaths.DeriveEquipmentBase(fullNames) is null) { - // NOTE: literal {{equip}} must survive — in a C# interpolated string `{{`/`}}` - // collapse to single braces, so keep the token text in a NON-interpolated segment. return new UnsMutationResult(false, $"Equipment '{equipmentId}' has no single tag base, so the " - + "{{equip}} token can't be resolved. Add at least one driver tag under this " - + "equipment (all sharing one object prefix), or remove {{equip}} from the script."); + + $"{EquipmentScriptPaths.EquipToken} token can't be resolved. Add at least one driver tag under this " + + $"equipment (all sharing one object prefix), or remove {EquipmentScriptPaths.EquipToken} from the script."); } return null; } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/VirtualTagEquipTokenValidationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/VirtualTagEquipTokenValidationTests.cs index 5fff5e3b..58922c56 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/VirtualTagEquipTokenValidationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/VirtualTagEquipTokenValidationTests.cs @@ -138,6 +138,41 @@ public sealed class VirtualTagEquipTokenValidationTests result.Error.ShouldBeNull(); } + /// + /// {{equip}} script + TWO driver tags whose FullNames have DIFFERENT object prefixes + /// (no single base can be derived) → create rejected, error names equipment + token. + /// + [Fact] + public async Task Create_equip_token_with_divergent_prefixes_rejected() + { + var (service, dbName) = Fresh(); + Seed(dbName, EquipBaseScript, tagFullName: "TestMachine_001.X"); + using (var db = UnsTreeTestDb.CreateNamed(dbName)) + { + db.Tags.Add(new Tag + { + TagId = "TAG-2", + DriverInstanceId = "DRV-1", + EquipmentId = "EQ-1", + Name = "y", + DataType = "Float", + AccessLevel = TagAccessLevel.Read, + TagConfig = "{\"FullName\":\"DelmiaReceiver_001.Y\"}", + }); + db.SaveChanges(); + } + + var result = await service.CreateVirtualTagAsync("EQ-1", Input()); + + result.Ok.ShouldBeFalse(); + result.Error.ShouldNotBeNull(); + result.Error.ShouldContain("EQ-1"); + result.Error.ShouldContain("{{equip}}"); + + using var verifyDb = UnsTreeTestDb.CreateNamed(dbName); + verifyDb.VirtualTags.Any(v => v.VirtualTagId == "VTAG-1").ShouldBeFalse(); + } + // ----- Update ----- /// {{equip}} script + a derivable base → update succeeds.