using Xunit; namespace KArtSell.ArchitectureTests; public sealed class RepositoryRulesTests { [Fact] public void Prohibited_source_patterns_are_not_introduced() { var repositoryRoot = FindRepositoryRoot(); var sourceFiles = Directory.EnumerateFiles(repositoryRoot, "*.cs", SearchOption.AllDirectories) .Where(x => !IsGeneratedOrTestOutput(x)) .ToArray(); AssertNoPattern(sourceFiles, "IGenericRepository", "Generic repository is prohibited."); AssertNoPattern(sourceFiles, "DateTime.Now", "Use IClock and MarketCalendar."); AssertNoPattern(sourceFiles, "DateTime.UtcNow", "Use IClock and MarketCalendar."); AssertNoPattern(sourceFiles, "IServiceProvider.GetService", "Service locator is prohibited."); AssertNoPattern(sourceFiles, "AllowAnonymous()", "Module endpoints cannot be anonymous."); } [Fact] public void Domain_files_do_not_reference_infrastructure_frameworks() { var root = FindRepositoryRoot(); var domainFiles = Directory.EnumerateFiles( Path.Combine(root, "src"), "*.cs", SearchOption.AllDirectories) .Where(path => path.Contains( $"{Path.DirectorySeparatorChar}Domain{Path.DirectorySeparatorChar}", StringComparison.Ordinal)) .ToArray(); foreach (var prohibited in new[] { "using Dapper", "using Npgsql", "using FastEndpoints", "using Hangfire", "HttpContext", "DbConnection", "IServiceCollection" }) { AssertNoPattern(domainFiles, prohibited, $"Domain cannot depend on infrastructure framework: {prohibited}."); } } [Fact] public void Sql_does_not_use_select_star_or_unqualified_signal_tables() { var repositoryRoot = FindRepositoryRoot(); var files = Directory.EnumerateFiles(repositoryRoot, "*.*", SearchOption.AllDirectories) .Where(x => x.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".sql", StringComparison.OrdinalIgnoreCase)) .Where(x => !IsGeneratedOrTestOutput(x)) .ToArray(); var selectStar = files.Where(x => File.ReadAllText(x).Contains("select *", StringComparison.OrdinalIgnoreCase)).ToArray(); Assert.True(selectStar.Length == 0, "SELECT * is prohibited: " + string.Join(", ", selectStar)); var unqualified = files.Where(x => { var text = File.ReadAllText(x); return text.Contains(" from signal_decision", StringComparison.OrdinalIgnoreCase) || text.Contains(" into signal_decision", StringComparison.OrdinalIgnoreCase) || text.Contains(" from evidence_snapshot", StringComparison.OrdinalIgnoreCase); }).ToArray(); Assert.True(unqualified.Length == 0, "SignalEngine SQL must be schema-qualified: " + string.Join(", ", unqualified)); } [Fact] public void Every_module_endpoint_declares_roles_or_policies() { var root = FindRepositoryRoot(); var endpoints = Directory.EnumerateFiles( Path.Combine(root, "src"), "Endpoint.cs", SearchOption.AllDirectories) .Where(path => path.Contains( $"{Path.DirectorySeparatorChar}Modules{Path.DirectorySeparatorChar}", StringComparison.Ordinal)) .ToArray(); var violations = endpoints.Where(path => { var text = File.ReadAllText(path); return !text.Contains("Roles(", StringComparison.Ordinal) && !text.Contains("Policies(", StringComparison.Ordinal); }).ToArray(); Assert.True(violations.Length == 0, "Every module endpoint must declare Roles or Policies: " + string.Join(", ", violations)); } [Fact] public void Accidental_placeholder_files_are_not_committed() { var root = FindRepositoryRoot(); var names = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories) .Where(path => !IsGeneratedOrTestOutput(path)) .Where(path => Path.GetFileName(path).Equals("testfile", StringComparison.OrdinalIgnoreCase) || Path.GetFileName(path).EndsWith(".tmp", StringComparison.OrdinalIgnoreCase)) .ToArray(); Assert.True(names.Length == 0, "Placeholder files are prohibited: " + string.Join(", ", names)); } private static void AssertNoPattern(IEnumerable files, string pattern, string message) { var violations = files .Where(path => File.ReadAllText(path).Contains(pattern, StringComparison.Ordinal)) .ToArray(); Assert.True(violations.Length == 0, message + " " + string.Join(", ", violations)); } private static bool IsGeneratedOrTestOutput(string path) => path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal) || path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal) || path.Contains($"{Path.DirectorySeparatorChar}tests{Path.DirectorySeparatorChar}", StringComparison.Ordinal) || path.Contains($"{Path.DirectorySeparatorChar}attachments{Path.DirectorySeparatorChar}", StringComparison.Ordinal) || path.Contains($"{Path.DirectorySeparatorChar}research{Path.DirectorySeparatorChar}original{Path.DirectorySeparatorChar}", StringComparison.Ordinal); private static string FindRepositoryRoot() { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "Directory.Build.props"))) { directory = directory.Parent; } return directory?.FullName ?? throw new InvalidOperationException("Repository root not found."); } }