using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening; namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests; /// /// Tests for the read-only authoring resolve service (M9/T26a). The resolved /// inherited member set MUST agree with what /// produces on deploy — the precedence is exercised both directly and via a /// cross-check against the flattener. /// public class TemplateInheritanceResolverTests { private static TemplateAttribute Attr(string name, int id, int templateId, string? value = null, bool isInherited = false, bool isLocked = false, bool lockedInDerived = false) => new(name) { Id = id, TemplateId = templateId, DataType = DataType.String, Value = value, IsInherited = isInherited, IsLocked = isLocked, LockedInDerived = lockedInDerived }; // ── (a) A→B→C chain: resolving C returns members inherited transitively from A ── [Fact] public void Resolve_ThreeLevelChain_IncludesTransitivelyInheritedGrandparentMember() { var a = new Template("A") { Id = 1 }; a.Attributes.Add(Attr("FromGrandparent", id: 10, templateId: 1, value: "gp")); var b = new Template("B") { Id = 2, ParentTemplateId = 1 }; b.Attributes.Add(Attr("FromParent", id: 20, templateId: 2, value: "p")); var c = new Template("C") { Id = 3, ParentTemplateId = 2 }; c.Attributes.Add(Attr("FromChild", id: 30, templateId: 3, value: "c")); var resolved = TemplateInheritanceResolver.Resolve(3, new[] { a, b, c }); Assert.Equal(3, resolved.Attributes.Count); var fromGp = Assert.Single(resolved.Attributes, m => m.Name == "FromGrandparent"); Assert.True(fromGp.IsInherited); Assert.Equal(1, fromGp.OriginTemplateId); Assert.Equal("A", fromGp.OriginTemplateName); Assert.Equal("gp", fromGp.EffectiveValue); var fromParent = Assert.Single(resolved.Attributes, m => m.Name == "FromParent"); Assert.True(fromParent.IsInherited); Assert.Equal("B", fromParent.OriginTemplateName); var fromChild = Assert.Single(resolved.Attributes, m => m.Name == "FromChild"); Assert.False(fromChild.IsInherited); Assert.Equal(3, fromChild.OriginTemplateId); } // ── (b) a base member ADDED to A after C was created appears in C's resolved set ── [Fact] public void Resolve_BaseMemberAddedAfterDerivedCreated_AppearsInResolvedSet() { // C was created carrying a single inherited placeholder for "Original". // "AddedLater" was then added to the grandparent A; it has NO stored row // on C, yet must surface in C's freshly-resolved set. var a = new Template("A") { Id = 1 }; a.Attributes.Add(Attr("Original", id: 10, templateId: 1, value: "v1")); a.Attributes.Add(Attr("AddedLater", id: 11, templateId: 1, value: "new")); var b = new Template("B") { Id = 2, ParentTemplateId = 1 }; var c = new Template("C") { Id = 3, ParentTemplateId = 2 }; // Stale placeholder for the one member C knew about at creation time. c.Attributes.Add(Attr("Original", id: 30, templateId: 3, value: "v1", isInherited: true)); var resolved = TemplateInheritanceResolver.Resolve(3, new[] { a, b, c }); Assert.Contains(resolved.Attributes, m => m.Name == "AddedLater" && m.EffectiveValue == "new" && m.IsInherited); Assert.Contains(resolved.Attributes, m => m.Name == "Original"); } // ── (c) a locked member is flagged ── [Fact] public void Resolve_LockedAndBaseLockedMembers_Flagged() { var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Locked", id: 10, templateId: 1, value: "x", isLocked: true)); baseT.Attributes.Add(Attr("BaseLocked", id: 11, templateId: 1, value: "y", lockedInDerived: true)); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); var locked = Assert.Single(resolved.Attributes, m => m.Name == "Locked"); Assert.True(locked.IsLocked); var baseLocked = Assert.Single(resolved.Attributes, m => m.Name == "BaseLocked"); Assert.True(baseLocked.IsBaseLocked); Assert.True(baseLocked.IsInherited); } // ── (d) own override wins over inherited ── [Fact] public void Resolve_OwnOverride_WinsOverInherited() { var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Speed", id: 10, templateId: 1, value: "100")); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; // Explicit override (IsInherited=false) — must win, value 200, origin = derived. derived.Attributes.Add(Attr("Speed", id: 20, templateId: 2, value: "200", isInherited: false)); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); var speed = Assert.Single(resolved.Attributes, m => m.Name == "Speed"); Assert.Equal("200", speed.EffectiveValue); Assert.Equal(2, speed.OriginTemplateId); Assert.False(speed.IsInherited); } [Fact] public void Resolve_InheritedPlaceholder_DoesNotShadowLiveBaseValue() { // Derived carries an IsInherited placeholder whose value is stale ("old"). // The base value has since changed to "fresh"; the live base value wins. var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Speed", id: 10, templateId: 1, value: "fresh")); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; derived.Attributes.Add(Attr("Speed", id: 20, templateId: 2, value: "old", isInherited: true)); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); var speed = Assert.Single(resolved.Attributes, m => m.Name == "Speed"); Assert.Equal("fresh", speed.EffectiveValue); Assert.True(speed.IsInherited); Assert.Equal(1, speed.OriginTemplateId); // base supplied the live value } // ── (e) staleness summary true when stored rows differ; false when in sync ── [Fact] public void Resolve_StalenessSummary_TrueWhenBaseAddedMemberMissingFromStoredRows() { var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Original", id: 10, templateId: 1, value: "v")); baseT.Attributes.Add(Attr("AddedLater", id: 11, templateId: 1, value: "new")); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; // Only the original placeholder is stored; "AddedLater" is missing → stale. derived.Attributes.Add(Attr("Original", id: 20, templateId: 2, value: "v", isInherited: true)); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); Assert.True(resolved.Staleness.IsStale); Assert.Equal(1, resolved.Staleness.DifferingMemberCount); } [Fact] public void Resolve_StalenessSummary_FalseWhenStoredRowsInSync() { var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Speed", id: 10, templateId: 1, value: "100")); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; // Stored placeholder matches the live base value exactly. derived.Attributes.Add(Attr("Speed", id: 20, templateId: 2, value: "100", isInherited: true)); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); Assert.False(resolved.Staleness.IsStale); Assert.Equal(0, resolved.Staleness.DifferingMemberCount); } [Fact] public void Resolve_StalenessSummary_TrueWhenStoredInheritedValueIsStale() { var baseT = new Template("Base") { Id = 1 }; baseT.Attributes.Add(Attr("Speed", id: 10, templateId: 1, value: "fresh")); var derived = new Template("Derived") { Id = 2, ParentTemplateId = 1 }; derived.Attributes.Add(Attr("Speed", id: 20, templateId: 2, value: "old", isInherited: true)); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); Assert.True(resolved.Staleness.IsStale); Assert.Equal(1, resolved.Staleness.DifferingMemberCount); } // ── (f) a composition-derived template resolves sanely ── [Fact] public void Resolve_CompositionDerivedTemplate_ResolvesOwnAndInheritedMembers() { // IsDerived templates back a composition slot; they still inherit from a // base via ParentTemplateId. The resolver must handle them like any // other derived template (inheritance only — composition is the // flattener's concern). var baseT = new Template("ComposedBase") { Id = 1 }; baseT.Attributes.Add(Attr("Pressure", id: 10, templateId: 1, value: "10")); var derived = new Template("Slot") { Id = 2, ParentTemplateId = 1, IsDerived = true, OwnerCompositionId = 99 }; derived.Attributes.Add(Attr("LocalTweak", id: 20, templateId: 2, value: "t")); var resolved = TemplateInheritanceResolver.Resolve(2, new[] { baseT, derived }); Assert.Equal(2, resolved.TemplateId); Assert.Contains(resolved.Attributes, m => m.Name == "Pressure" && m.IsInherited); Assert.Contains(resolved.Attributes, m => m.Name == "LocalTweak" && !m.IsInherited); } [Fact] public void Resolve_UnknownTemplate_ReturnsEmptySet() { var resolved = TemplateInheritanceResolver.Resolve(999, Array.Empty