Add tests for SBOM generation determinism across multiple formats
- 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.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Reflection;
|
||||
using NetArchTest.Rules;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace StellaOps.Architecture.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Architecture tests for lattice engine placement rules.
|
||||
/// Ensures lattice algorithms are only in Scanner.WebService, not in Concelier or Excititor.
|
||||
/// </summary>
|
||||
[Trait("Category", "Architecture")]
|
||||
public sealed class LatticeEngineRulesTests
|
||||
{
|
||||
private const string ScannerLatticeNamespace = "StellaOps.Scanner.Lattice";
|
||||
|
||||
/// <summary>
|
||||
/// Concelier modules must not reference Scanner lattice engine.
|
||||
/// Lattice decisions are made in Scanner, not in Concelier.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Concelier_MustNot_Reference_ScannerLattice()
|
||||
{
|
||||
var concelierAssemblies = GetAssembliesByPattern("StellaOps.Concelier");
|
||||
|
||||
if (!concelierAssemblies.Any())
|
||||
{
|
||||
// Skip if assemblies not loaded (test discovery phase)
|
||||
return;
|
||||
}
|
||||
|
||||
var result = Types.InAssemblies(concelierAssemblies)
|
||||
.ShouldNot()
|
||||
.HaveDependencyOn(ScannerLatticeNamespace)
|
||||
.GetResult();
|
||||
|
||||
result.IsSuccessful.Should().BeTrue(
|
||||
$"Concelier assemblies must not reference Scanner lattice. " +
|
||||
$"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty<string>())}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Excititor modules must not reference Scanner lattice engine.
|
||||
/// Excititor preserves prune source - does not evaluate lattice decisions.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Excititor_MustNot_Reference_ScannerLattice()
|
||||
{
|
||||
var excititorAssemblies = GetAssembliesByPattern("StellaOps.Excititor");
|
||||
|
||||
if (!excititorAssemblies.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = Types.InAssemblies(excititorAssemblies)
|
||||
.ShouldNot()
|
||||
.HaveDependencyOn(ScannerLatticeNamespace)
|
||||
.GetResult();
|
||||
|
||||
result.IsSuccessful.Should().BeTrue(
|
||||
$"Excititor assemblies must not reference Scanner lattice. " +
|
||||
$"Violations: {string.Join(", ", result.FailingTypeNames ?? Enumerable.Empty<string>())}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scanner.WebService MAY reference Scanner lattice engine (it's the authorized host).
|
||||
/// This test documents the allowed dependency.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ScannerWebService_May_Reference_ScannerLattice()
|
||||
{
|
||||
// This is a documentation test - Scanner.WebService is allowed to use lattice
|
||||
// The test validates that the architectural rule is correctly documented
|
||||
var allowedAssemblies = new[] { "StellaOps.Scanner.WebService" };
|
||||
|
||||
// Positive assertion: these assemblies ARE allowed to reference lattice
|
||||
allowedAssemblies.Should().Contain("StellaOps.Scanner.WebService",
|
||||
"Scanner.WebService is the authorized host for lattice algorithms");
|
||||
}
|
||||
|
||||
private static IEnumerable<Assembly> GetAssembliesByPattern(string pattern)
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(a => a.GetName().Name?.StartsWith(pattern) == true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.Reflection;
|
||||
using NetArchTest.Rules;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace StellaOps.Architecture.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Architecture tests for module dependency rules.
|
||||
/// Enforces proper layering between Core, Infrastructure, and WebService assemblies.
|
||||
/// </summary>
|
||||
[Trait("Category", "Architecture")]
|
||||
public sealed class ModuleDependencyRulesTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Core libraries must not depend on infrastructure (e.g., Postgres storage).
|
||||
/// Core should be pure business logic, infrastructure-agnostic.
|
||||
/// </summary>
|
||||
[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<string>())}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WebServices may depend on Core and Storage, but not on other WebServices.
|
||||
/// Each WebService should be independently deployable.
|
||||
/// </summary>
|
||||
[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<string>())}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Workers may depend on Core and Storage, but not directly on WebServices.
|
||||
/// Workers are background processes, independent of HTTP layer.
|
||||
/// </summary>
|
||||
[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<string>())}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Models/DTOs should not depend on infrastructure or services.
|
||||
/// </summary>
|
||||
[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<string>())}");
|
||||
}
|
||||
|
||||
private static IEnumerable<Assembly> GetAssembliesByPattern(string pattern)
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(a => a.GetName().Name?.Contains(pattern) == true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System.Reflection;
|
||||
using NetArchTest.Rules;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace StellaOps.Architecture.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Architecture tests for naming convention rules.
|
||||
/// Enforces consistent naming across the codebase.
|
||||
/// </summary>
|
||||
[Trait("Category", "Architecture")]
|
||||
public sealed class NamingConventionRulesTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test projects must end with .Tests.
|
||||
/// </summary>
|
||||
[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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin assemblies must follow naming pattern.
|
||||
/// </summary>
|
||||
[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.<Module>.Plugin.* or StellaOps.<Module>.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.<Module>.Plugin[s].*");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connector assemblies must follow naming pattern.
|
||||
/// </summary>
|
||||
[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.<Module>.Connector.* or StellaOps.<Module>.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.<Module>.Connector[.*]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage assemblies must follow naming pattern.
|
||||
/// </summary>
|
||||
[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.<Module>.Storage or StellaOps.<Module>.Storage.<Provider>
|
||||
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.<Module>.Storage[.<Provider>]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface types should start with 'I'.
|
||||
/// </summary>
|
||||
[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<string>())}");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.2" />
|
||||
<PackageReference Include="NetArchTest.Rules" Version="1.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Reference assemblies under test -->
|
||||
<ItemGroup>
|
||||
<!-- Core/Canonical -->
|
||||
<ProjectReference Include="..\..\..\src\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj" />
|
||||
|
||||
<!-- Scanner module (sample reference - expand as needed) -->
|
||||
<!-- Note: Add module references as architecture rules are implemented -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="FluentAssertions" />
|
||||
<Using Include="NetArchTest.Rules" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user