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);
}
}