using System.IO; using System.Reflection; using Xunit; namespace Recordingtest.Architecture.Tests; /// /// Enforces the 3-tier separation rule from CLAUDE.md §8.1: /// /// Generic → no SUT assembly references at all /// HmEG-aware → HmEG.dll only, no per-app assembly /// App-specific → anything (not checked here; not referenced by this project) /// /// These tests walk on each /// compiled output and fail if a forbidden reference appears. The test /// project itself does NOT reference any Sut/* project, so a physical build /// error would catch accidental upward references even before these tests /// run — this file is the explicit contract. /// public class DependencyGraphTests { private static readonly string[] ForbiddenAppAssemblies = { "Editor03.PluginInterface", "Editor02.HmEGAppManager", "EditorCore", }; private const string HmegAssembly = "HmEG"; private static readonly string[] GenericAssemblies = { "Recordingtest.Bridge.Abstractions", "Recordingtest.Recorder", "Recordingtest.Player", "Recordingtest.Normalizer", "Recordingtest.DiffReporter", "Recordingtest.Runner", "Recordingtest.SutProber", }; private static readonly string[] HmegAwareAssemblies = { "Recordingtest.Hmeg.Bridge", "Recordingtest.Hmeg.Catalog", "Recordingtest.Hmeg.Bridge.Client", }; [Theory] [InlineData("Recordingtest.Bridge.Abstractions")] [InlineData("Recordingtest.Recorder")] [InlineData("Recordingtest.Player")] [InlineData("Recordingtest.Normalizer")] [InlineData("Recordingtest.DiffReporter")] [InlineData("Recordingtest.Runner")] [InlineData("Recordingtest.SutProber")] public void Generic_Tier_Does_Not_Reference_Hmeg_Or_AppAssemblies(string assemblyName) { var refs = LoadReferences(assemblyName); Assert.DoesNotContain(HmegAssembly, refs); foreach (var app in ForbiddenAppAssemblies) { Assert.DoesNotContain(app, refs); } } [Theory] [InlineData("Recordingtest.Hmeg.Bridge")] [InlineData("Recordingtest.Hmeg.Catalog")] [InlineData("Recordingtest.Hmeg.Bridge.Client")] public void HmegAware_Tier_Does_Not_Reference_AppAssemblies(string assemblyName) { var refs = LoadReferences(assemblyName); foreach (var app in ForbiddenAppAssemblies) { Assert.DoesNotContain(app, refs); } } [Fact] public void Hmeg_Bridge_References_HmEG_Dll() { // HmEG-aware tier is expected to reference HmEG.dll. This is the // positive check that pairs with the App-specific forbidden list. var refs = LoadReferences("Recordingtest.Hmeg.Bridge"); Assert.Contains(HmegAssembly, refs); } private static IReadOnlySet LoadReferences(string assemblyName) { // Locate the assembly via a type we know belongs to it. Each tier // member has at least one public type; we use GenericAssemblies/ // HmegAwareAssemblies as lookups by name. Assembly? asm = null; foreach (var loaded in AppDomain.CurrentDomain.GetAssemblies()) { if (loaded.GetName().Name == assemblyName) { asm = loaded; break; } } if (asm is null) { // Force-load by trying to resolve a type: the test project has a // ProjectReference to the target, so the binary is next to ours. var dir = Path.GetDirectoryName(typeof(DependencyGraphTests).Assembly.Location)!; var path = Path.Combine(dir, assemblyName + ".dll"); asm = Assembly.LoadFrom(path); } return new HashSet( asm.GetReferencedAssemblies().Select(n => n.Name ?? string.Empty), StringComparer.Ordinal); } }