using Microsoft.EntityFrameworkCore; using TeamUp.Bootstrap; using TeamUp.Infrastructure.Persistence; using TeamUp.SharedKernel.Modularity; using Xunit; namespace TeamUp.ArchitectureTests; /// /// Guards persistence encapsulation and module shape. A module's DbContext/entities are its /// private business — never public — and each module exposes exactly one registration seam. /// public sealed class PersistenceEncapsulationTests { private static readonly System.Reflection.Assembly[] AllProductionAssemblies = [ typeof(IModule).Assembly, typeof(MigrationRunner).Assembly, typeof(ModuleCatalog).Assembly, .. ArchitectureFixture.ModuleAssemblies, ]; [Fact] public void No_DbContext_is_publicly_visible() { var publicContexts = AllProductionAssemblies .SelectMany(assembly => assembly.GetTypes()) .Where(type => typeof(DbContext).IsAssignableFrom(type) && type.IsPublic) .Select(type => type.FullName) .ToList(); Assert.True( publicContexts.Count == 0, $"A DbContext must be internal to its module. Public contexts found: {string.Join(", ", publicContexts)}"); } [Fact] public void Each_module_assembly_exposes_exactly_one_IModule() { foreach (var assembly in ArchitectureFixture.ModuleAssemblies) { var implementations = assembly.GetTypes() .Where(type => typeof(IModule).IsAssignableFrom(type) && type is { IsInterface: false, IsAbstract: false }) .ToList(); Assert.True( implementations.Count == 1, $"{assembly.GetName().Name} must expose exactly one IModule; found {implementations.Count}."); } } [Fact] public void ModuleCatalog_lists_every_module_with_a_unique_name() { var modules = ModuleCatalog.All; Assert.Equal(ArchitectureFixture.ModuleAssemblies.Length, modules.Count); var names = modules.Select(module => module.Name).ToList(); Assert.All(names, name => Assert.False(string.IsNullOrWhiteSpace(name))); Assert.Equal(names.Count, names.Distinct(StringComparer.Ordinal).Count()); } }