fix(template-engine): resolve TemplateEngine-011,013,014 — remove dead converter, duplicate-id-safe cycle detection, unified deletion logic; TemplateEngine-012 deferred

This commit is contained in:
Joseph Doherty
2026-05-16 22:32:30 -04:00
parent 9e2416b34c
commit adb5e75ec3
9 changed files with 274 additions and 98 deletions

View File

@@ -153,4 +153,77 @@ public class CycleDetectorTests
Assert.Null(result);
}
// ========================================================================
// TemplateEngine-013: robustness against duplicate Ids and Id 0
// ========================================================================
[Fact]
public void DetectInheritanceCycle_DuplicateIdsInList_DoesNotThrow()
{
// Two not-yet-saved templates both carry Id == 0. ToDictionary(t => t.Id)
// would throw ArgumentException; the detector must tolerate it.
var templateA = new Template("A") { Id = 0 };
var templateB = new Template("B") { Id = 0 };
var saved = new Template("Saved") { Id = 1 };
var all = new List<Template> { templateA, templateB, saved };
var ex = Record.Exception(() => CycleDetector.DetectInheritanceCycle(1, 0, all));
Assert.Null(ex);
}
[Fact]
public void DetectCompositionCycle_DuplicateIdsInList_DoesNotThrow()
{
var templateA = new Template("A") { Id = 0 };
var templateB = new Template("B") { Id = 0 };
var all = new List<Template> { templateA, templateB };
var ex = Record.Exception(() => CycleDetector.DetectCompositionCycle(1, 2, all));
Assert.Null(ex);
}
[Fact]
public void DetectCrossGraphCycle_DuplicateIdsInList_DoesNotThrow()
{
var templateA = new Template("A") { Id = 0 };
var templateB = new Template("B") { Id = 0 };
var all = new List<Template> { templateA, templateB };
var ex = Record.Exception(() => CycleDetector.DetectCrossGraphCycle(5, 1, 2, all));
Assert.Null(ex);
}
[Fact]
public void DetectInheritanceCycle_RealIdZero_StillDetectsCycle()
{
// A template legitimately stored with Id 0 (in-memory / test scenario):
// a self-inheritance attempt must still be detected, not skipped as
// "no parent" by a 0-as-sentinel overload.
var template = new Template("Zero") { Id = 0 };
var all = new List<Template> { template };
var result = CycleDetector.DetectInheritanceCycle(0, 0, all);
Assert.NotNull(result);
Assert.Contains("itself", result);
}
[Fact]
public void DetectInheritanceCycle_ParentChainThroughIdZero_DetectsCycle()
{
// Child(1) -> parent Zero(0) -> parent Child(1): a cycle running through
// a template whose real Id is 0 must be detected, not silently skipped.
var zero = new Template("Zero") { Id = 0, ParentTemplateId = 1 };
var child = new Template("Child") { Id = 1, ParentTemplateId = null };
var all = new List<Template> { zero, child };
var result = CycleDetector.DetectInheritanceCycle(1, 0, all);
Assert.NotNull(result);
Assert.Contains("cycle", result, StringComparison.OrdinalIgnoreCase);
}
}