- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism. - Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions. - Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests. - Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
167 lines
5.2 KiB
C#
167 lines
5.2 KiB
C#
using System.Reflection;
|
|
using NetArchTest.Rules;
|
|
using Xunit;
|
|
using FluentAssertions;
|
|
|
|
namespace StellaOps.Architecture.Tests;
|
|
|
|
/// <summary>
|
|
/// Architecture tests for forbidden package rules.
|
|
/// Enforces compliance constraints on library usage.
|
|
/// </summary>
|
|
[Trait("Category", "Architecture")]
|
|
public sealed class ForbiddenPackageRulesTests
|
|
{
|
|
/// <summary>
|
|
/// No direct Redis library usage - only Valkey-compatible clients allowed.
|
|
/// </summary>
|
|
[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<string>())}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// No MongoDB usage - deprecated per Sprint 4400.
|
|
/// </summary>
|
|
[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<string>())}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Crypto libraries must be plugin-based - no direct BouncyCastle references in core.
|
|
/// </summary>
|
|
[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<string>())}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// No Newtonsoft.Json in new code - use System.Text.Json or StellaOps.Canonical.Json.
|
|
/// </summary>
|
|
[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
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// No Entity Framework - use Dapper or raw Npgsql.
|
|
/// </summary>
|
|
[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<string>())}");
|
|
}
|
|
|
|
private static IEnumerable<Assembly> GetStellaOpsAssemblies()
|
|
{
|
|
return AppDomain.CurrentDomain.GetAssemblies()
|
|
.Where(a => a.GetName().Name?.StartsWith("StellaOps") == true);
|
|
}
|
|
|
|
private static IEnumerable<Assembly> GetCoreAssemblies()
|
|
{
|
|
return AppDomain.CurrentDomain.GetAssemblies()
|
|
.Where(a => a.GetName().Name?.Contains("Core") == true &&
|
|
a.GetName().Name?.StartsWith("StellaOps") == true);
|
|
}
|
|
}
|