Merge branch 'phase/56-architecture-tests'

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	docs/phases/10-craft/README.md
This commit is contained in:
Leonid Pershin
2026-08-20 12:54:53 +03:00
11 changed files with 195 additions and 87 deletions
-21
View File
@@ -188,27 +188,6 @@ public class WalkingTests
}
}
[Fact]
public void Assembly_DoesNotReferenceArchAspNetOrSockets()
{
var names = typeof(WalkGraph).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
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));
}
[Fact]
public void Sources_DoNotUseWallClock()
{
var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.Ai");
foreach (var path in Directory.EnumerateFiles(root, "*.cs"))
{
var text = File.ReadAllText(path);
Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal);
Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal);
}
}
private static (DayPlan Clear, DayPlan Weather, int Extra) PlansWithExtra(Func<DefCatalog, int> extraOf)
{
var (catalog, map) = Fixtures.Vanilla();
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>HSchool.Architecture.Tests</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\HSchool.Ai\HSchool.Ai.csproj" />
<ProjectReference Include="..\..\src\HSchool.Content\HSchool.Content.csproj" />
<ProjectReference Include="..\..\src\HSchool.People\HSchool.People.csproj" />
<ProjectReference Include="..\..\src\HSchool.Protocol\HSchool.Protocol.csproj" />
<ProjectReference Include="..\..\src\HSchool.Schedule\HSchool.Schedule.csproj" />
<ProjectReference Include="..\..\src\HSchool.Simulation\HSchool.Simulation.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>
@@ -0,0 +1,131 @@
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_DoesNotReferenceArchAspNetSocketsProtocolOrSimulation()
{
var names = References(typeof(MapLayout));
AssertNoArchAspNetOrSockets(names);
Assert.DoesNotContain("HSchool.Protocol", 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)
{
AssertNoArchAspNetOrSockets(References(marker));
}
[Fact]
public void Simulation_DoesNotReferenceAspNetOrSockets()
{
var names = References(typeof(School));
Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase));
}
[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 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<string> 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<string> 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;
}
}
@@ -143,27 +143,6 @@ public class RosterGeneratorTests
Assert.False(RosterFit.Matches(loaded, SchoolDemand.From(Fixtures.Catalog(), Fixtures.Classrooms(2))));
}
[Fact]
public void Assembly_DoesNotReferenceArchAspNetOrSockets()
{
var names = typeof(RosterGenerator).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
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));
}
[Fact]
public void Sources_DoNotUseWallClock()
{
var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.People");
foreach (var path in Directory.EnumerateFiles(root, "*.cs"))
{
var text = File.ReadAllText(path);
Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal);
Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal);
}
}
private static void AssertFilled(Roster roster, int classrooms)
{
Assert.Equal(classrooms, roster.Classes.Count);
@@ -125,27 +125,6 @@ public class TimetablePlannerTests
Assert.Equal(Snapshot(a), Snapshot(b));
}
[Fact]
public void Assembly_DoesNotReferenceArchAspNetOrSockets()
{
var names = typeof(TimetablePlanner).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
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));
}
[Fact]
public void Sources_DoNotUseWallClock()
{
var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.Schedule");
foreach (var path in Directory.EnumerateFiles(root, "*.cs"))
{
var text = File.ReadAllText(path);
Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal);
Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal);
}
}
/// <summary>
/// The four bans alone allowed a legal but unusable week. Taking the first free slot day by
/// day gave a full school 77 lessons on Monday and 16 on Friday, with three foreign-language
@@ -274,14 +274,6 @@ public class PresenceTests
"stale day plans after an assign that did not change headcount left the campus empty");
}
[Fact]
public void Simulation_DoesNotReferenceSockets()
{
var names = typeof(School).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase));
}
private static School UnstaffedDuringLessonThree()
{
var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc);