refactor(transport): single-source template child equality — diff and overwrite-sync can no longer drift
New TemplateChildEquality is the one place "has this template child changed?" is decided, comparing the COMPLETE writable field set per entity (attributes now incl. ElementDataType + LockedInDerived; alarms incl. the on-trigger script by name + LockedInDerived; scripts incl. MinTimeBetweenRuns/ExecutionTimeoutSeconds + LockedInDerived; native sources per T5). ArtifactDiff.CompareTemplate and all four BundleImporter.SyncTemplate*Async predicates now route through it — closing the class of bug where Preview reported Identical for an artifact an Overwrite would mutate. The attribute sync passes a value-normalised DTO so List-value re-imports stay idempotent; the alarm on-trigger change now surfaces in both diff and sync. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -150,6 +150,91 @@ public sealed class ArtifactDiffTests
|
||||
Assert.Contains(("add", "new2"), hunks);
|
||||
}
|
||||
|
||||
// ============ #05-T6: single-source child equality — diff sees the full field set ============
|
||||
|
||||
[Fact]
|
||||
public void CompareTemplate_AttributeElementDataTypeChange_IsModified()
|
||||
{
|
||||
var existing = MakeTemplate("T1");
|
||||
existing.Attributes.Add(new TemplateAttribute("L")
|
||||
{
|
||||
DataType = DataType.List,
|
||||
ElementDataType = DataType.Int32,
|
||||
Value = "[1,2]",
|
||||
});
|
||||
var incoming = MakeTemplateDto("T1", attributes:
|
||||
[
|
||||
new TemplateAttributeDto("L", "[1,2]", DataType.List, false, null, null, DataType.String),
|
||||
]);
|
||||
|
||||
var item = _diff.CompareTemplate(incoming, existing);
|
||||
|
||||
Assert.Equal(ConflictKind.Modified, item.Kind);
|
||||
ChangeFor(item, "Attributes.L"); // ElementDataType diverged — previously missed.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTemplate_LockedInDerivedChange_IsModified()
|
||||
{
|
||||
var existing = MakeTemplate("T1");
|
||||
existing.Attributes.Add(new TemplateAttribute("L")
|
||||
{
|
||||
DataType = DataType.Int32,
|
||||
Value = "1",
|
||||
LockedInDerived = false,
|
||||
});
|
||||
var incoming = MakeTemplateDto("T1", attributes:
|
||||
[
|
||||
new TemplateAttributeDto("L", "1", DataType.Int32, false, null, null, null, LockedInDerived: true),
|
||||
]);
|
||||
|
||||
var item = _diff.CompareTemplate(incoming, existing);
|
||||
|
||||
Assert.Equal(ConflictKind.Modified, item.Kind);
|
||||
ChangeFor(item, "Attributes.L"); // LockedInDerived diverged — previously missed.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTemplate_ScriptExecutionTimeoutChange_IsModified()
|
||||
{
|
||||
var existing = MakeTemplate("T1");
|
||||
existing.Scripts.Add(new TemplateScript("S", "return 1;") { ExecutionTimeoutSeconds = 30 });
|
||||
var incoming = MakeTemplateDto("T1", scripts:
|
||||
[
|
||||
// identical code; only ExecutionTimeoutSeconds differs.
|
||||
new TemplateScriptDto("S", "return 1;", null, null, null, null, false, null, ExecutionTimeoutSeconds: 60),
|
||||
]);
|
||||
|
||||
var item = _diff.CompareTemplate(incoming, existing);
|
||||
|
||||
Assert.Equal(ConflictKind.Modified, item.Kind);
|
||||
ChangeFor(item, "Scripts.S"); // ExecutionTimeoutSeconds diverged — previously missed.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTemplate_AlarmOnTriggerScriptChange_IsModified()
|
||||
{
|
||||
var existing = MakeTemplate("T1");
|
||||
existing.Scripts.Add(new TemplateScript("Run", "return 1;") { Id = 1 });
|
||||
existing.Alarms.Add(new TemplateAlarm("A")
|
||||
{
|
||||
TriggerType = AlarmTriggerType.RangeViolation,
|
||||
OnTriggerScriptId = 1, // resolves to "Run"
|
||||
});
|
||||
var incoming = MakeTemplateDto("T1",
|
||||
scripts: [new TemplateScriptDto("Run", "return 1;", null, null, null, null, false, null)],
|
||||
alarms:
|
||||
[
|
||||
// same alarm fields but no on-trigger script (was "Run").
|
||||
new TemplateAlarmDto("A", null, 0, AlarmTriggerType.RangeViolation, null, false, OnTriggerScriptName: null),
|
||||
]);
|
||||
|
||||
var item = _diff.CompareTemplate(incoming, existing);
|
||||
|
||||
Assert.Equal(ConflictKind.Modified, item.Kind);
|
||||
ChangeFor(item, "Alarms.A"); // OnTriggerScriptName diverged — previously missed.
|
||||
}
|
||||
|
||||
// ============ M8: CompareSite ============
|
||||
|
||||
[Fact]
|
||||
@@ -433,14 +518,18 @@ public sealed class ArtifactDiffTests
|
||||
private static Commons.Entities.Templates.Template MakeTemplate(string name) =>
|
||||
new(name);
|
||||
|
||||
private static TemplateDto MakeTemplateDto(string name, IReadOnlyList<TemplateScriptDto>? scripts = null) =>
|
||||
private static TemplateDto MakeTemplateDto(
|
||||
string name,
|
||||
IReadOnlyList<TemplateScriptDto>? scripts = null,
|
||||
IReadOnlyList<TemplateAttributeDto>? attributes = null,
|
||||
IReadOnlyList<TemplateAlarmDto>? alarms = null) =>
|
||||
new(
|
||||
Name: name,
|
||||
FolderName: null,
|
||||
BaseTemplateName: null,
|
||||
Description: null,
|
||||
Attributes: Array.Empty<TemplateAttributeDto>(),
|
||||
Alarms: Array.Empty<TemplateAlarmDto>(),
|
||||
Attributes: attributes ?? Array.Empty<TemplateAttributeDto>(),
|
||||
Alarms: alarms ?? Array.Empty<TemplateAlarmDto>(),
|
||||
Scripts: scripts ?? Array.Empty<TemplateScriptDto>(),
|
||||
Compositions: Array.Empty<TemplateCompositionDto>());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user