using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Pins the dependency shape of ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts: it must not /// reference the OPC UA client SDK. /// /// Why this matters beyond tidiness. Deferment-register G-7 wired /// into the deploy-time TagConfig gate, which /// made ControlPlane project-reference this Contracts assembly. ControlPlane is loaded by /// every admin- and driver-role host, so whatever Contracts drags in, they all carry. Contracts /// used to hold NamespaceMap — ISession/NodeId-typed throughout — and with it a /// OPCFoundation.NetStandard.Opc.Ua.Client PackageReference; NamespaceMap moved to the /// sibling .Session leaf so the parse-only half could stay light (the same shape as /// Calculation.Contracts: Core.Abstractions and nothing else). /// /// /// Direction matters: this is an ABSENCE assertion, and that is what makes it sound here. /// The assembly manifest is an unreliable witness for PRESENCE — the C# compiler elides a reference /// nothing in the IL actually uses, so "assembly X is referenced" can be false while the csproj says /// otherwise (the trap EquipmentTagConfigInspectorCoverageTests documents, which is why that /// one scans the output directory instead). Absence has no such hole in the direction that matters: /// the moment someone adds Opc.Ua-typed code to Contracts, the compiler MUST emit the reference and /// this test goes red. A csproj PackageReference that nothing consumes is elided and slips past — /// accepted deliberately, because an unused package reference is not the failure being guarded. /// /// /// Falsified 2026-08-09 by temporarily adding an Opc.Ua.NodeId-typed member to Contracts: /// the assertion failed naming Opc.Ua.Core, and passed again once removed. /// /// public sealed class ContractsIsDependencyLightTests { /// The Contracts assembly's manifest must name no OPC UA SDK assembly. [Fact] public void The_Contracts_assembly_does_not_reference_the_OPC_UA_SDK() { var contracts = typeof(OpcUaClientTagDefinitionFactory).Assembly; // Positive control: name the assembly under test, so a rename/retarget cannot silently point this // at something else that trivially satisfies the assertion below. contracts.GetName().Name.ShouldBe("ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts"); var opcUa = contracts.GetReferencedAssemblies() .Select(a => a.Name ?? string.Empty) .Where(n => n.StartsWith("Opc.Ua", StringComparison.OrdinalIgnoreCase)) .OrderBy(n => n, StringComparer.Ordinal) .ToArray(); opcUa.ShouldBeEmpty( "OpcUaClient.Contracts must stay free of the OPC UA client SDK — ControlPlane project-references " + "it for the G-7 deploy-time TagConfig gate, so every host that loads the control plane would " + "carry the stack. Put SDK-typed code in Driver.OpcUaClient.Session instead. Found: " + string.Join(", ", opcUa)); } /// /// The counterpart the assertion above is meaningful against: the SDK-typed half really did land /// somewhere, and that somewhere does reference the SDK. Without this, "no Opc.Ua reference /// anywhere" would read as a pass. /// [Fact] public void The_Session_assembly_is_where_the_OPC_UA_SDK_reference_lives() { var session = typeof(NamespaceMap).Assembly; session.GetName().Name.ShouldBe("ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Session"); session.GetReferencedAssemblies() .Select(a => a.Name ?? string.Empty) .ShouldContain(n => n.StartsWith("Opc.Ua", StringComparison.OrdinalIgnoreCase)); } }