using System.Reflection; using NetArchTest.Rules; using Xunit; using FluentAssertions; namespace StellaOps.Architecture.Tests; /// /// Architecture tests for forbidden package rules. /// Enforces compliance constraints on library usage. /// [Trait("Category", "Architecture")] public sealed class ForbiddenPackageRulesTests { /// /// No direct Redis library usage - only Valkey-compatible clients allowed. /// [Fact] public void Assemblies_MustNot_Use_Direct_Redis_Clients() { var stellaOpsAssemblies = GetStellaOpsAssemblies(); if (!stellaOpsAssemblies.Any()) { return; } // ServiceStack.Redis and similar direct Redis clients are forbidden // StackExchange.Redis is allowed as it's Valkey-compatible var forbiddenRedisPackages = new[] { "ServiceStack.Redis", "CSRedis", "FreeRedis" }; var result = Types.InAssemblies(stellaOpsAssemblies) .ShouldNot() .HaveDependencyOnAny(forbiddenRedisPackages) .GetResult(); result.IsSuccessful.Should().BeTrue( $"StellaOps assemblies must not use non-Valkey-compatible Redis clients. " + $"Use StackExchange.Redis (Valkey-compatible). " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } /// /// No MongoDB usage - deprecated per Sprint 4400. /// [Fact] public void Assemblies_MustNot_Use_MongoDB() { var stellaOpsAssemblies = GetStellaOpsAssemblies(); if (!stellaOpsAssemblies.Any()) { return; } var result = Types.InAssemblies(stellaOpsAssemblies) .ShouldNot() .HaveDependencyOnAny( "MongoDB.Driver", "MongoDB.Bson", "MongoDb.*") .GetResult(); result.IsSuccessful.Should().BeTrue( $"MongoDB is deprecated (Sprint 4400). Use PostgreSQL. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } /// /// Crypto libraries must be plugin-based - no direct BouncyCastle references in core. /// [Fact] public void CoreAssemblies_MustNot_Reference_BouncyCastle_Directly() { var coreAssemblies = GetCoreAssemblies(); if (!coreAssemblies.Any()) { return; } // Core assemblies should use crypto through plugin abstraction var result = Types.InAssemblies(coreAssemblies) .ShouldNot() .HaveDependencyOnAny( "Org.BouncyCastle.*", "BouncyCastle.*") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Core assemblies must not reference BouncyCastle directly. " + $"Use crypto plugins instead. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } /// /// No Newtonsoft.Json in new code - use System.Text.Json or StellaOps.Canonical.Json. /// [Fact] public void Assemblies_Should_Prefer_SystemTextJson() { var stellaOpsAssemblies = GetStellaOpsAssemblies() .Where(a => !a.GetName().Name?.Contains("Test") ?? false); if (!stellaOpsAssemblies.Any()) { return; } // This is a warning-level check, not a hard requirement // Some interop scenarios may require Newtonsoft var result = Types.InAssemblies(stellaOpsAssemblies) .That() .HaveDependencyOn("Newtonsoft.Json") .GetTypes(); // Log but don't fail - this is advisory if (result.Any()) { // Advisory: consider migrating to System.Text.Json } } /// /// No Entity Framework - use Dapper or raw Npgsql. /// [Fact] public void Assemblies_MustNot_Use_EntityFramework() { var stellaOpsAssemblies = GetStellaOpsAssemblies(); if (!stellaOpsAssemblies.Any()) { return; } var result = Types.InAssemblies(stellaOpsAssemblies) .ShouldNot() .HaveDependencyOnAny( "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.*") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Entity Framework is not used in StellaOps. Use Dapper or Npgsql. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } private static IEnumerable GetStellaOpsAssemblies() { return AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.StartsWith("StellaOps") == true); } private static IEnumerable GetCoreAssemblies() { return AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.Contains("Core") == true && a.GetName().Name?.StartsWith("StellaOps") == true); } }