using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Admin.Services; namespace ZB.MOM.WW.OtOpcUa.Admin.Tests; [Trait("Category", "Unit")] public sealed class UnsImpactAnalyzerTests { private static UnsTreeSnapshot TwoAreaSnapshot() => new() { DraftGenerationId = 1, RevisionToken = new DraftRevisionToken("rev-1"), Areas = [ new UnsAreaSummary("area-pack", "Packaging", ["line-oven", "line-wrap"]), new UnsAreaSummary("area-asm", "Assembly", ["line-weld"]), ], Lines = [ new UnsLineSummary("line-oven", "Oven-2", EquipmentCount: 14, TagCount: 237), new UnsLineSummary("line-wrap", "Wrapper", EquipmentCount: 3, TagCount: 40), new UnsLineSummary("line-weld", "Welder", EquipmentCount: 5, TagCount: 80), ], }; [Fact] public void LineMove_Counts_Affected_Equipment_And_Tags() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( Kind: UnsMoveKind.LineMove, SourceClusterId: "c1", TargetClusterId: "c1", SourceLineId: "line-oven", TargetAreaId: "area-asm"); var preview = UnsImpactAnalyzer.Analyze(snapshot, move); preview.AffectedEquipmentCount.ShouldBe(14); preview.AffectedTagCount.ShouldBe(237); preview.RevisionToken.Value.ShouldBe("rev-1"); preview.HumanReadableSummary.ShouldContain("'Oven-2'"); preview.HumanReadableSummary.ShouldContain("'Assembly'"); } [Fact] public void CrossCluster_LineMove_Throws() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( Kind: UnsMoveKind.LineMove, SourceClusterId: "c1", TargetClusterId: "c2", SourceLineId: "line-oven", TargetAreaId: "area-asm"); Should.Throw( () => UnsImpactAnalyzer.Analyze(snapshot, move)); } [Fact] public void LineMove_With_UnknownSource_Throws_Validation() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( UnsMoveKind.LineMove, "c1", "c1", SourceLineId: "line-does-not-exist", TargetAreaId: "area-asm"); Should.Throw( () => UnsImpactAnalyzer.Analyze(snapshot, move)); } [Fact] public void LineMove_With_UnknownTarget_Throws_Validation() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( UnsMoveKind.LineMove, "c1", "c1", SourceLineId: "line-oven", TargetAreaId: "area-nowhere"); Should.Throw( () => UnsImpactAnalyzer.Analyze(snapshot, move)); } [Fact] public void LineMove_To_Area_WithSameName_Warns_AboutAmbiguity() { var snapshot = new UnsTreeSnapshot { DraftGenerationId = 1, RevisionToken = new DraftRevisionToken("rev-1"), Areas = [ new UnsAreaSummary("area-a", "Packaging", ["line-1"]), new UnsAreaSummary("area-b", "Assembly", ["line-2"]), ], Lines = [ new UnsLineSummary("line-1", "Oven", 10, 100), new UnsLineSummary("line-2", "Oven", 5, 50), ], }; var move = new UnsMoveOperation( UnsMoveKind.LineMove, "c1", "c1", SourceLineId: "line-1", TargetAreaId: "area-b"); var preview = UnsImpactAnalyzer.Analyze(snapshot, move); preview.CascadeWarnings.ShouldContain(w => w.Contains("already has a line named 'Oven'")); } [Fact] public void AreaRename_Cascades_AcrossAllLines() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( Kind: UnsMoveKind.AreaRename, SourceClusterId: "c1", TargetClusterId: "c1", SourceAreaId: "area-pack", NewName: "Packaging-West"); var preview = UnsImpactAnalyzer.Analyze(snapshot, move); preview.AffectedEquipmentCount.ShouldBe(14 + 3, "sum of lines in 'Packaging'"); preview.AffectedTagCount.ShouldBe(237 + 40); preview.HumanReadableSummary.ShouldContain("'Packaging-West'"); } [Fact] public void LineMerge_CrossArea_Warns() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( Kind: UnsMoveKind.LineMerge, SourceClusterId: "c1", TargetClusterId: "c1", SourceLineId: "line-oven", TargetLineId: "line-weld"); var preview = UnsImpactAnalyzer.Analyze(snapshot, move); preview.AffectedEquipmentCount.ShouldBe(14); preview.CascadeWarnings.ShouldContain(w => w.Contains("different areas")); } [Fact] public void LineMerge_SameArea_NoWarning() { var snapshot = TwoAreaSnapshot(); var move = new UnsMoveOperation( Kind: UnsMoveKind.LineMerge, SourceClusterId: "c1", TargetClusterId: "c1", SourceLineId: "line-oven", TargetLineId: "line-wrap"); var preview = UnsImpactAnalyzer.Analyze(snapshot, move); preview.CascadeWarnings.ShouldBeEmpty(); } [Fact] public void DraftRevisionToken_Matches_OnEqualValues() { var a = new DraftRevisionToken("rev-1"); var b = new DraftRevisionToken("rev-1"); var c = new DraftRevisionToken("rev-2"); a.Matches(b).ShouldBeTrue(); a.Matches(c).ShouldBeFalse(); a.Matches(null).ShouldBeFalse(); } }