using System.Reflection; using NetArchTest.Rules; using Xunit; using FluentAssertions; namespace StellaOps.Architecture.Tests; /// /// Architecture tests for module dependency rules. /// Enforces proper layering between Core, Infrastructure, and WebService assemblies. /// [Trait("Category", "Architecture")] public sealed class ModuleDependencyRulesTests { /// /// Core libraries must not depend on infrastructure (e.g., Postgres storage). /// Core should be pure business logic, infrastructure-agnostic. /// [Fact] public void CoreLibraries_MustNot_Depend_On_Infrastructure() { var coreAssemblies = GetAssembliesByPattern("Core"); if (!coreAssemblies.Any()) { return; } var result = Types.InAssemblies(coreAssemblies) .ShouldNot() .HaveDependencyOnAny( "StellaOps.*.Storage.Postgres", "StellaOps.*.Storage.Valkey", "Npgsql", "StackExchange.Redis") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Core libraries must not depend on infrastructure. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } /// /// WebServices may depend on Core and Storage, but not on other WebServices. /// Each WebService should be independently deployable. /// [Fact] public void WebServices_MustNot_Depend_On_Other_WebServices() { var webServiceAssemblies = GetAssembliesByPattern("WebService"); if (!webServiceAssemblies.Any()) { return; } foreach (var assembly in webServiceAssemblies) { var assemblyName = assembly.GetName().Name; var otherWebServices = webServiceAssemblies .Where(a => a.GetName().Name != assemblyName) .Select(a => a.GetName().Name!) .ToArray(); if (!otherWebServices.Any()) { continue; } var result = Types.InAssembly(assembly) .ShouldNot() .HaveDependencyOnAny(otherWebServices) .GetResult(); result.IsSuccessful.Should().BeTrue( $"WebService {assemblyName} must not depend on other WebServices. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } } /// /// Workers may depend on Core and Storage, but not directly on WebServices. /// Workers are background processes, independent of HTTP layer. /// [Fact] public void Workers_MustNot_Depend_On_WebServices() { var workerAssemblies = GetAssembliesByPattern("Worker"); if (!workerAssemblies.Any()) { return; } var result = Types.InAssemblies(workerAssemblies) .ShouldNot() .HaveDependencyOnAny("Microsoft.AspNetCore.Mvc", "StellaOps.*.WebService") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Worker assemblies must not depend on WebServices. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } /// /// Models/DTOs should not depend on infrastructure or services. /// [Fact] public void Models_MustNot_Depend_On_Services() { var modelAssemblies = GetAssembliesByPattern("Models"); if (!modelAssemblies.Any()) { return; } var result = Types.InAssemblies(modelAssemblies) .ShouldNot() .HaveDependencyOnAny( "StellaOps.*.Storage.*", "StellaOps.*.WebService", "Microsoft.AspNetCore.*") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Model assemblies must not depend on services. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } private static IEnumerable GetAssembliesByPattern(string pattern) { return AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.Contains(pattern) == true); } }