From 7b6d6bb80528319613d9ee6a9b33c71302d17de3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 9 Jul 2026 17:05:02 -0400 Subject: [PATCH] =?UTF-8?q?test(transport):=20harden=20T5=20native-source?= =?UTF-8?q?=20coverage=20=E2=80=94=20no-change=20zero-audit=20+=20IsInheri?= =?UTF-8?q?ted/LockedInDerived=20fidelity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up from the T5 code review: assert an idempotent Overwrite emits zero TemplateNativeAlarmSource* audit rows, and round-trip IsInherited/LockedInDerived as true through both the serializer and the importer BuildTemplate path. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj --- .../Import/NativeAlarmSourceImportTests.cs | 44 +++++++++++++++++-- .../Serialization/EntitySerializerTests.cs | 4 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/Import/NativeAlarmSourceImportTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/Import/NativeAlarmSourceImportTests.cs index 2d6b9600..6c1a77ac 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/Import/NativeAlarmSourceImportTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/Import/NativeAlarmSourceImportTests.cs @@ -103,15 +103,17 @@ public sealed class NativeAlarmSourceImportTests : IDisposable string connection = "OpcUaPrimary", string sourceRef = "ns=3;s=Boiler.Alarm", string? filter = "severity>500", - bool locked = false) => new(name) + bool locked = false, + bool inherited = false, + bool lockedInDerived = false) => new(name) { Description = "boiler native alarms", ConnectionName = connection, SourceReference = sourceRef, ConditionFilter = filter, IsLocked = locked, - IsInherited = false, - LockedInDerived = false, + IsInherited = inherited, + LockedInDerived = lockedInDerived, }; [Fact] @@ -121,7 +123,8 @@ public sealed class NativeAlarmSourceImportTests : IDisposable { var ctx = scope.ServiceProvider.GetRequiredService(); var t = new Template("Pump") { Description = "tpl" }; - t.NativeAlarmSources.Add(MakeSource(locked: true)); + // Set every flag non-default so BuildTemplate's fidelity is exercised. + t.NativeAlarmSources.Add(MakeSource(locked: true, inherited: true, lockedInDerived: true)); ctx.Templates.Add(t); await ctx.SaveChangesAsync(); } @@ -155,6 +158,39 @@ public sealed class NativeAlarmSourceImportTests : IDisposable Assert.Equal("ns=3;s=Boiler.Alarm", src.SourceReference); Assert.Equal("severity>500", src.ConditionFilter); Assert.True(src.IsLocked); + Assert.True(src.IsInherited); + Assert.True(src.LockedInDerived); + } + } + + [Fact] + public async Task Import_Overwrite_NoChange_EmitsNoNativeSourceAudit() + { + // An idempotent Overwrite (bundle == target) must produce ZERO + // TemplateNativeAlarmSource* audit rows — the "no audit noise" invariant. + await using (var scope = _provider.CreateAsyncScope()) + { + var ctx = scope.ServiceProvider.GetRequiredService(); + var t = new Template("Pump") { Description = "same" }; + t.NativeAlarmSources.Add(MakeSource("Boiler", locked: true, lockedInDerived: true)); + ctx.Templates.Add(t); + await ctx.SaveChangesAsync(); + } + var sessionId = await ExportAllTemplatesAndLoadAsync(); + + await using (var scope = _provider.CreateAsyncScope()) + { + var importer = scope.ServiceProvider.GetRequiredService(); + await importer.ApplyAsync(sessionId, + new List { new("Template", "Pump", ResolutionAction.Overwrite, null) }, + user: "bob"); + } + + await using (var scope = _provider.CreateAsyncScope()) + { + var ctx = scope.ServiceProvider.GetRequiredService(); + Assert.False(await ctx.AuditLogEntries.AnyAsync(a => + a.Action.StartsWith("TemplateNativeAlarmSource"))); } } diff --git a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Serialization/EntitySerializerTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Serialization/EntitySerializerTests.cs index 57c5eda8..1846aab7 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Serialization/EntitySerializerTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Serialization/EntitySerializerTests.cs @@ -215,7 +215,7 @@ public sealed class EntitySerializerTests SourceReference = "ns=3;s=Boiler.Alarm", ConditionFilter = "severity>500", IsLocked = true, - IsInherited = false, + IsInherited = true, LockedInDerived = true, }); @@ -230,6 +230,7 @@ public sealed class EntitySerializerTests Assert.Equal("ns=3;s=Boiler.Alarm", dtoSrc.SourceReference); Assert.Equal("severity>500", dtoSrc.ConditionFilter); Assert.True(dtoSrc.IsLocked); + Assert.True(dtoSrc.IsInherited); Assert.True(dtoSrc.LockedInDerived); var rtSrc = Assert.Single(Assert.Single(sut.FromBundleContent(dto).Templates).NativeAlarmSources); @@ -239,6 +240,7 @@ public sealed class EntitySerializerTests Assert.Equal("ns=3;s=Boiler.Alarm", rtSrc.SourceReference); Assert.Equal("severity>500", rtSrc.ConditionFilter); Assert.True(rtSrc.IsLocked); + Assert.True(rtSrc.IsInherited); Assert.True(rtSrc.LockedInDerived); }