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);
}
}