using System.Reflection; using NetArchTest.Rules; using Xunit; using FluentAssertions; namespace StellaOps.Architecture.Tests; /// /// Architecture tests for naming convention rules. /// Enforces consistent naming across the codebase. /// [Trait("Category", "Architecture")] public sealed class NamingConventionRulesTests { /// /// Test projects must end with .Tests. /// [Fact] public void TestProjects_MustEndWith_Tests() { var testAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.StartsWith("StellaOps") == true) .Where(a => ContainsTestTypes(a)); foreach (var assembly in testAssemblies) { var name = assembly.GetName().Name; // If it has test types, it should end with .Tests if (!name!.EndsWith(".Tests")) { // Check if it's in a known test location pattern var isValidTestAssembly = name.Contains("Test") || name.EndsWith(".Tests") || name.Contains("Testing"); isValidTestAssembly.Should().BeTrue( $"Assembly {name} contains tests but doesn't follow naming convention (.Tests suffix)"); } } } /// /// Plugin assemblies must follow naming pattern. /// [Fact] public void Plugins_MustFollow_NamingPattern() { var pluginAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.Contains("Plugin") == true && a.GetName().Name?.StartsWith("StellaOps") == true); foreach (var assembly in pluginAssemblies) { var name = assembly.GetName().Name!; // Valid patterns: StellaOps..Plugin.* or StellaOps..Plugins.* var isValidPluginName = System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Plugin\.\w+$") || System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Plugins\.\w+$") || System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Plugin$"); isValidPluginName.Should().BeTrue( $"Plugin assembly {name} doesn't follow naming pattern StellaOps..Plugin[s].*"); } } /// /// Connector assemblies must follow naming pattern. /// [Fact] public void Connectors_MustFollow_NamingPattern() { var connectorAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.Contains("Connector") == true && a.GetName().Name?.StartsWith("StellaOps") == true); foreach (var assembly in connectorAssemblies) { var name = assembly.GetName().Name!; // Valid patterns: StellaOps..Connector.* or StellaOps..Connector var isValidConnectorName = System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Connector\.\w+$") || System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Connector$") || System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Connector\.Common$"); isValidConnectorName.Should().BeTrue( $"Connector assembly {name} doesn't follow naming pattern StellaOps..Connector[.*]"); } } /// /// Storage assemblies must follow naming pattern. /// [Fact] public void Storage_MustFollow_NamingPattern() { var storageAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.Contains("Storage") == true && a.GetName().Name?.StartsWith("StellaOps") == true); foreach (var assembly in storageAssemblies) { var name = assembly.GetName().Name!; // Valid patterns: StellaOps..Storage or StellaOps..Storage. var isValidStorageName = System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Storage$") || System.Text.RegularExpressions.Regex.IsMatch(name, @"^StellaOps\.\w+\.Storage\.\w+$"); isValidStorageName.Should().BeTrue( $"Storage assembly {name} doesn't follow naming pattern StellaOps..Storage[.]"); } } /// /// Interface types should start with 'I'. /// [Fact] public void Interfaces_MustStartWith_I() { var stellaOpsAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.GetName().Name?.StartsWith("StellaOps") == true && !a.GetName().Name?.Contains("Test") == true); if (!stellaOpsAssemblies.Any()) { return; } var result = Types.InAssemblies(stellaOpsAssemblies) .That() .AreInterfaces() .Should() .HaveNameStartingWith("I") .GetResult(); result.IsSuccessful.Should().BeTrue( $"Interface types must start with 'I'. " + $"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty())}"); } private static bool ContainsTestTypes(Assembly assembly) { try { return assembly.GetTypes() .Any(t => t.GetMethods() .Any(m => m.GetCustomAttributes(typeof(FactAttribute), false).Any() || m.GetCustomAttributes(typeof(TheoryAttribute), false).Any())); } catch { return false; } } }