test(transport): harden T5 native-source coverage — no-change zero-audit + IsInherited/LockedInDerived fidelity
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
This commit is contained in:
+40
-4
@@ -103,15 +103,17 @@ public sealed class NativeAlarmSourceImportTests : IDisposable
|
|||||||
string connection = "OpcUaPrimary",
|
string connection = "OpcUaPrimary",
|
||||||
string sourceRef = "ns=3;s=Boiler.Alarm",
|
string sourceRef = "ns=3;s=Boiler.Alarm",
|
||||||
string? filter = "severity>500",
|
string? filter = "severity>500",
|
||||||
bool locked = false) => new(name)
|
bool locked = false,
|
||||||
|
bool inherited = false,
|
||||||
|
bool lockedInDerived = false) => new(name)
|
||||||
{
|
{
|
||||||
Description = "boiler native alarms",
|
Description = "boiler native alarms",
|
||||||
ConnectionName = connection,
|
ConnectionName = connection,
|
||||||
SourceReference = sourceRef,
|
SourceReference = sourceRef,
|
||||||
ConditionFilter = filter,
|
ConditionFilter = filter,
|
||||||
IsLocked = locked,
|
IsLocked = locked,
|
||||||
IsInherited = false,
|
IsInherited = inherited,
|
||||||
LockedInDerived = false,
|
LockedInDerived = lockedInDerived,
|
||||||
};
|
};
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -121,7 +123,8 @@ public sealed class NativeAlarmSourceImportTests : IDisposable
|
|||||||
{
|
{
|
||||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||||
var t = new Template("Pump") { Description = "tpl" };
|
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);
|
ctx.Templates.Add(t);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
@@ -155,6 +158,39 @@ public sealed class NativeAlarmSourceImportTests : IDisposable
|
|||||||
Assert.Equal("ns=3;s=Boiler.Alarm", src.SourceReference);
|
Assert.Equal("ns=3;s=Boiler.Alarm", src.SourceReference);
|
||||||
Assert.Equal("severity>500", src.ConditionFilter);
|
Assert.Equal("severity>500", src.ConditionFilter);
|
||||||
Assert.True(src.IsLocked);
|
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<ScadaBridgeDbContext>();
|
||||||
|
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<IBundleImporter>();
|
||||||
|
await importer.ApplyAsync(sessionId,
|
||||||
|
new List<ImportResolution> { new("Template", "Pump", ResolutionAction.Overwrite, null) },
|
||||||
|
user: "bob");
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var scope = _provider.CreateAsyncScope())
|
||||||
|
{
|
||||||
|
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||||
|
Assert.False(await ctx.AuditLogEntries.AnyAsync(a =>
|
||||||
|
a.Action.StartsWith("TemplateNativeAlarmSource")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ public sealed class EntitySerializerTests
|
|||||||
SourceReference = "ns=3;s=Boiler.Alarm",
|
SourceReference = "ns=3;s=Boiler.Alarm",
|
||||||
ConditionFilter = "severity>500",
|
ConditionFilter = "severity>500",
|
||||||
IsLocked = true,
|
IsLocked = true,
|
||||||
IsInherited = false,
|
IsInherited = true,
|
||||||
LockedInDerived = true,
|
LockedInDerived = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,6 +230,7 @@ public sealed class EntitySerializerTests
|
|||||||
Assert.Equal("ns=3;s=Boiler.Alarm", dtoSrc.SourceReference);
|
Assert.Equal("ns=3;s=Boiler.Alarm", dtoSrc.SourceReference);
|
||||||
Assert.Equal("severity>500", dtoSrc.ConditionFilter);
|
Assert.Equal("severity>500", dtoSrc.ConditionFilter);
|
||||||
Assert.True(dtoSrc.IsLocked);
|
Assert.True(dtoSrc.IsLocked);
|
||||||
|
Assert.True(dtoSrc.IsInherited);
|
||||||
Assert.True(dtoSrc.LockedInDerived);
|
Assert.True(dtoSrc.LockedInDerived);
|
||||||
|
|
||||||
var rtSrc = Assert.Single(Assert.Single(sut.FromBundleContent(dto).Templates).NativeAlarmSources);
|
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("ns=3;s=Boiler.Alarm", rtSrc.SourceReference);
|
||||||
Assert.Equal("severity>500", rtSrc.ConditionFilter);
|
Assert.Equal("severity>500", rtSrc.ConditionFilter);
|
||||||
Assert.True(rtSrc.IsLocked);
|
Assert.True(rtSrc.IsLocked);
|
||||||
|
Assert.True(rtSrc.IsInherited);
|
||||||
Assert.True(rtSrc.LockedInDerived);
|
Assert.True(rtSrc.LockedInDerived);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user