fix(templateengine+centralui): resolve follow-ups #3 (derived-template collisions) and #7 (sandbox batch/wait surface)
#3 — CollisionDetector counted a derived template's IsInherited placeholder rows as a distinct origin from the parent members the inheritance walk re-adds, reporting a spurious "Naming collision" for every inherited row and blocking any attribute/composition add to a derived template. CollectDirectMembers now skips IsInherited rows on the direct-template and inherited-parent walks; it keeps them for the composed-module walk, where placeholders are the sole representation of a derived module's inherited members (that walk does not climb the composed template's parent chain). #7 — SandboxAttributeAccessor (Central UI Test-Run host) omitted WriteBatchAndWaitAsync / WaitAsync / WaitForAsync, so the editor false-flagged valid instance scripts with CS1061 even though `template validate` and the deploy gate accept them. Added the five overloads mirroring the runtime AttributeAccessor; they throw a labelled ScriptSandboxException if run in Test Run (the central sandbox has no device-batch / event-waiter transport). Tests: +3 CollisionDetector unit + 1 end-to-end TemplateService (derived add now succeeds); +2 ScriptAnalysisService diagnose-clean. Each new test verified to fail without its fix with the exact user-facing symptom. Full suites green (TemplateEngine.Tests 438, CentralUI.Tests 866). Docs: Component-TemplateEngine.md (inherited-placeholder collision rule), Component-ScriptAnalysis.md (third sandbox surface + its compile-clean guard), known-issues tracker #3/#7 marked resolved and the minor note promoted to #8.
This commit is contained in:
@@ -220,6 +220,37 @@ public class TemplateServiceTests
|
||||
Assert.Equal(1, result.Value.TemplateId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAttribute_ToDerivedTemplateWithInheritedPlaceholders_Succeeds()
|
||||
{
|
||||
// Follow-up #3 (end-to-end). Adding a member to a derived template used to
|
||||
// fail: CloneTemplateWithNewAttribute carries the child's IsInherited
|
||||
// placeholder rows into DetectCollisions, which counted each one twice (child
|
||||
// origin + the parent the inheritance walk re-adds) and reported a spurious
|
||||
// "Naming collision" for every inherited attribute — there was no supported
|
||||
// API path to extend a derived template. The new attribute must now save.
|
||||
var parent = new Template("ReactorSide") { Id = 7 };
|
||||
parent.Attributes.Add(new TemplateAttribute("DeltaVac") { Id = 70, TemplateId = 7, DataType = DataType.Float });
|
||||
parent.Attributes.Add(new TemplateAttribute("ResultType") { Id = 71, TemplateId = 7, DataType = DataType.String });
|
||||
|
||||
var child = new Template("LeftReactorSide") { Id = 8, ParentTemplateId = 7 };
|
||||
child.Attributes.Add(new TemplateAttribute("DeltaVac") { Id = 80, TemplateId = 8, DataType = DataType.Float, IsInherited = true });
|
||||
child.Attributes.Add(new TemplateAttribute("ResultType") { Id = 81, TemplateId = 8, DataType = DataType.String, IsInherited = true });
|
||||
|
||||
_repoMock.Setup(r => r.GetTemplateByIdAsync(8, It.IsAny<CancellationToken>())).ReturnsAsync(child);
|
||||
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<Template> { parent, child });
|
||||
|
||||
var attr = new TemplateAttribute("MoveInType") { DataType = DataType.String, Value = "" };
|
||||
var result = await _service.AddAttributeAsync(8, attr, "admin");
|
||||
|
||||
// Guard the message: Result.Error throws on a success result, so only read
|
||||
// it when the add actually failed (which is the case this test regresses).
|
||||
Assert.True(result.IsSuccess, result.IsFailure ? result.Error : null);
|
||||
Assert.Equal("MoveInType", result.Value.Name);
|
||||
Assert.Equal(8, result.Value.TemplateId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAttribute_DuplicateName_Fails()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user