using HSchool.Ai; using HSchool.Content; using HSchool.People; using HSchool.Protocol; using HSchool.Schedule; using HSchool.Simulation; namespace HSchool.Architecture.Tests; public class LayerBoundaryTests { private static readonly string[] LibraryFolders = [ "HSchool.Protocol", "HSchool.Content", "HSchool.People", "HSchool.Schedule", "HSchool.Ai", "HSchool.Simulation" ]; private static readonly string[] OldTestProjects = [ "HSchool.People.Tests", "HSchool.Ai.Tests", "HSchool.Schedule.Tests", "HSchool.Simulation.Tests" ]; [Fact] public void Protocol_DoesNotReferenceArchAspNetSocketsContentOrSimulation() { var names = References(typeof(ProtocolCodec)); AssertNoArchAspNetOrSockets(names); Assert.DoesNotContain("HSchool.Content", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.People", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Schedule", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Ai", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Simulation", names, StringComparer.Ordinal); } [Fact] public void Content_DoesNotReferenceArchAspNetSocketsProtocolPeopleScheduleAiOrSimulation() { var names = References(typeof(MapLayout)); AssertNoArchAspNetOrSockets(names); Assert.DoesNotContain("HSchool.Protocol", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.People", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Schedule", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Ai", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Simulation", names, StringComparer.Ordinal); } [Theory] [InlineData(typeof(RosterGenerator))] [InlineData(typeof(TimetablePlanner))] [InlineData(typeof(WalkGraph))] public void PeopleScheduleAi_DoNotReferenceArchAspNetOrSockets(Type marker) { var names = References(marker); AssertNoArchAspNetOrSockets(names); Assert.DoesNotContain("HSchool.Protocol", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Simulation", names, StringComparer.Ordinal); } [Fact] public void People_DoesNotReferenceScheduleOrAi() { var names = References(typeof(RosterGenerator)); Assert.DoesNotContain("HSchool.Schedule", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Ai", names, StringComparer.Ordinal); } [Fact] public void Schedule_DoesNotReferencePeopleOrAi() { var names = References(typeof(TimetablePlanner)); Assert.DoesNotContain("HSchool.People", names, StringComparer.Ordinal); Assert.DoesNotContain("HSchool.Ai", names, StringComparer.Ordinal); } [Fact] public void Simulation_DoesNotReferenceAspNetSocketsOrProtocol() { var names = References(typeof(School)); Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain("HSchool.Protocol", names, StringComparer.Ordinal); } [Fact] public void SixLibrarySources_DoNotUseWallClock() { foreach (var folder in LibraryFolders) { var root = Path.Combine(RepoRoot(), "src", folder); Assert.True(Directory.Exists(root), root); foreach (var path in SourceFiles(root)) { var text = File.ReadAllText(path); Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal); Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal); // Unused `using System.Net.Http` never becomes a referenced assembly. Assert.DoesNotContain("System.Net.Http", text, StringComparison.Ordinal); } } } [Fact] public void ArchitectureTests_DoNotReferenceHostOrNetArchTest() { var csproj = File.ReadAllText( Path.Combine(RepoRoot(), "tests", "HSchool.Architecture.Tests", "HSchool.Architecture.Tests.csproj")); Assert.DoesNotContain("HSchool.Server", csproj, StringComparison.Ordinal); Assert.DoesNotContain("HSchool.AppHost", csproj, StringComparison.Ordinal); Assert.DoesNotContain("NetArchTest", csproj, StringComparison.OrdinalIgnoreCase); } [Fact] public void PeopleAiScheduleSimulationTests_NoLongerCarryLayerBans() { foreach (var project in OldTestProjects) { var root = Path.Combine(RepoRoot(), "tests", project); Assert.True(Directory.Exists(root), root); foreach (var path in SourceFiles(root)) { var text = File.ReadAllText(path); Assert.DoesNotContain("Assembly_DoesNotReference", text, StringComparison.Ordinal); Assert.DoesNotContain("Sources_DoNotUseWallClock", text, StringComparison.Ordinal); } } } private static IEnumerable SourceFiles(string root) => Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories) .Where(path => path.Split(Path.DirectorySeparatorChar).All(part => part is not ("bin" or "obj"))); private static string[] References(Type marker) => [.. marker.Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!)]; private static void AssertNoArchAspNetOrSockets(IEnumerable names) { Assert.DoesNotContain(names, name => name.StartsWith("Arch", StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase)); } private static string RepoRoot() { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "h-school.sln"))) { dir = dir.Parent; } if (dir is null) { throw new InvalidOperationException("Could not find h-school.sln from the test output directory."); } return dir.FullName; } }