Refactor code structure and optimize performance across multiple modules

This commit is contained in:
StellaOps Bot
2025-12-26 20:03:22 +02:00
parent c786faae84
commit b4fc66feb6
3353 changed files with 88254 additions and 1590657 deletions

View File

@@ -1,11 +1,13 @@
using Xunit;
using StellaOps.Policy.Engine.AdvisoryAI;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class AdvisoryAiKnobsServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Get_ReturnsDefaultsWithHash()
{
var service = new AdvisoryAiKnobsService(TimeProvider.System);
@@ -15,7 +17,8 @@ public sealed class AdvisoryAiKnobsServiceTests
Assert.False(string.IsNullOrWhiteSpace(profile.ProfileHash));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Set_NormalizesOrdering()
{
var service = new AdvisoryAiKnobsService(TimeProvider.System);

View File

@@ -2,11 +2,13 @@ using Xunit;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Services;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class EvidenceSummaryServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Summarize_BuildsDeterministicSummary()
{
var timeProvider = new FixedTimeProvider(new DateTimeOffset(2025, 11, 26, 0, 0, 0, TimeSpan.Zero));
@@ -33,7 +35,8 @@ public sealed class EvidenceSummaryServiceTests
response.Summary.Signals);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Summarize_RequiresEvidenceHash()
{
var timeProvider = new FixedTimeProvider(DateTimeOffset.UnixEpoch);

View File

@@ -3,11 +3,13 @@ using Microsoft.Extensions.Time.Testing;
using StellaOps.Policy.Engine.Ledger;
using StellaOps.Policy.Engine.Orchestration;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class LedgerExportServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BuildAsync_ProducesOrderedNdjson()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T15:00:00Z"));

View File

@@ -2,11 +2,13 @@ using Xunit;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Policy.Engine.Orchestration;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class OrchestratorJobServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitAsync_NormalizesOrderingAndHashes()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T10:00:00Z"));
@@ -39,7 +41,8 @@ public sealed class OrchestratorJobServiceTests
Assert.False(string.IsNullOrWhiteSpace(job.DeterminismHash));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitAsync_IsDeterministicAcrossOrdering()
{
var requestedAt = DateTimeOffset.Parse("2025-11-24T11:00:00Z");
@@ -75,7 +78,8 @@ public sealed class OrchestratorJobServiceTests
Assert.Equal(first.DeterminismHash, second.DeterminismHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Preview_DoesNotPersist()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T12:00:00Z"));

View File

@@ -4,11 +4,13 @@ using StellaOps.Policy.Engine.Overlay;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Streaming;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class OverlayProjectionServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BuildSnapshotAsync_ProducesHeaderAndSortedProjections()
{
var service = new OverlayProjectionService(new PolicyEvaluationService(), TimeProvider.System);

View File

@@ -6,13 +6,15 @@ using StellaOps.Policy.Engine.Tests.Fakes;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Streaming;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PathScopeSimulationBridgeServiceTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SimulateAsync_OrdersByInputAndProducesMetrics()
{
var bridge = CreateBridge();
@@ -37,7 +39,8 @@ public sealed class PathScopeSimulationBridgeServiceTests
Assert.Equal(2, result.Metrics.Evaluated);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SimulateAsync_WhatIfProducesDeltas()
{
var bridge = CreateBridge();
@@ -57,7 +60,8 @@ public sealed class PathScopeSimulationBridgeServiceTests
Assert.Single(result.Deltas!);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SimulateAsync_PublishesEventsAndSavesOverlays()
{
var sink = new FakeOverlayEventSink();

View File

@@ -3,11 +3,13 @@ using System.Linq;
using System.Threading.Tasks;
using StellaOps.Policy.Engine.Streaming;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PathScopeSimulationServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StreamAsync_ReturnsDeterministicOrdering()
{
var service = new PathScopeSimulationService();
@@ -32,7 +34,8 @@ public sealed class PathScopeSimulationServiceTests
Assert.Contains("\"filePath\":\"b/file.js\"", lines[1]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StreamAsync_ThrowsOnMissingTarget()
{
var service = new PathScopeSimulationService();

View File

@@ -6,11 +6,13 @@ using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Engine.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public class PolicyActivationAuditorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RecordActivation_WhenDisabled_DoesNothing()
{
var options = new PolicyEngineOptions();
@@ -24,7 +26,8 @@ public class PolicyActivationAuditorTests
Assert.Empty(logger.Entries);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RecordActivation_WhenEnabled_WritesScopedLog()
{
var options = new PolicyEngineOptions();

View File

@@ -2,11 +2,13 @@ using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Engine.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public class PolicyActivationSettingsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ResolveRequirement_WhenForceEnabled_IgnoresRequest()
{
var options = new PolicyEngineOptions();
@@ -17,7 +19,8 @@ public class PolicyActivationSettingsTests
Assert.True(settings.ResolveRequirement(null));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ResolveRequirement_UsesRequestedValue_WhenProvided()
{
var options = new PolicyEngineOptions();
@@ -27,7 +30,8 @@ public class PolicyActivationSettingsTests
Assert.False(settings.ResolveRequirement(false));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ResolveRequirement_FallsBackToDefault_WhenRequestMissing()
{
var options = new PolicyEngineOptions();

View File

@@ -8,6 +8,7 @@ using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Engine.Services;
using StellaOps.PolicyDsl;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyBundleServiceTests
@@ -18,7 +19,8 @@ public sealed class PolicyBundleServiceTests
}
""";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_SucceedsAndStoresBundle()
{
var services = CreateServices();
@@ -32,7 +34,8 @@ public sealed class PolicyBundleServiceTests
Assert.True(response.SizeBytes > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_FailsWithBadSyntax()
{
var services = CreateServices();
@@ -45,7 +48,8 @@ public sealed class PolicyBundleServiceTests
Assert.NotEmpty(response.Diagnostics);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_ReturnsAocMetadata()
{
var services = CreateServices();
@@ -63,7 +67,8 @@ public sealed class PolicyBundleServiceTests
Assert.True(response.AocMetadata.ComplexityScore >= 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_IncludesProvenanceWhenProvided()
{
var services = CreateServices();
@@ -95,7 +100,8 @@ public sealed class PolicyBundleServiceTests
Assert.Equal("main", bundle.AocMetadata.Provenance.Branch);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_NullAocMetadataOnFailure()
{
var services = CreateServices();
@@ -107,7 +113,8 @@ public sealed class PolicyBundleServiceTests
Assert.Null(response.AocMetadata);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompileAndStoreAsync_SourceDigestIsDeterministic()
{
var services = CreateServices();

View File

@@ -7,6 +7,7 @@ using StellaOps.Policy.Engine.Services;
using StellaOps.PolicyDsl;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyCompilationServiceTests
@@ -27,7 +28,8 @@ public sealed class PolicyCompilationServiceTests
}
""";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_ReturnsComplexityReport_WhenWithinLimits()
{
var service = CreateService(maxComplexityScore: 1000, maxDurationMilliseconds: 1000, simulatedDurationMilliseconds: 12.3);
@@ -44,7 +46,8 @@ public sealed class PolicyCompilationServiceTests
Assert.True(result.Diagnostics.IsDefaultOrEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_Fails_WhenComplexityExceedsThreshold()
{
var service = CreateService(maxComplexityScore: 1, maxDurationMilliseconds: 1000, simulatedDurationMilliseconds: 2);
@@ -60,7 +63,8 @@ public sealed class PolicyCompilationServiceTests
Assert.Equal(PolicyIssueSeverity.Error, diagnostic.Severity);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_Fails_WhenDurationExceedsThreshold()
{
var service = CreateService(maxComplexityScore: 1000, maxDurationMilliseconds: 1, simulatedDurationMilliseconds: 5.2);

View File

@@ -5,11 +5,13 @@ using StellaOps.PolicyDsl;
using Xunit;
using Xunit.Sdk;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyCompilerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_BaselinePolicy_Succeeds()
{
const string source = """
@@ -79,7 +81,8 @@ public sealed class PolicyCompilerTests
Assert.Equal("status", firstAction.Target[0]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_MissingBecause_ReportsDiagnostic()
{
const string source = """

View File

@@ -8,6 +8,7 @@ using StellaOps.Policy.Engine.Snapshots;
using StellaOps.Policy.Engine.TrustWeighting;
using StellaOps.Policy.Engine.Violations;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyDecisionServiceTests
@@ -78,7 +79,8 @@ public sealed class PolicyDecisionServiceTests
return (decisionService, snapshot.SnapshotId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ReturnsDecisionsWithEvidence()
{
var (service, snapshotId) = BuildService();
@@ -97,7 +99,8 @@ public sealed class PolicyDecisionServiceTests
});
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_BuildsSummaryStatistics()
{
var (service, snapshotId) = BuildService();
@@ -110,7 +113,8 @@ public sealed class PolicyDecisionServiceTests
Assert.NotEmpty(response.Summary.TopSeveritySources);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_FiltersById()
{
var (service, snapshotId) = BuildService();
@@ -124,7 +128,8 @@ public sealed class PolicyDecisionServiceTests
Assert.Equal("CVE-2021-44228", response.Decisions[0].AdvisoryId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_FiltersByTenant()
{
var (service, snapshotId) = BuildService();
@@ -137,7 +142,8 @@ public sealed class PolicyDecisionServiceTests
Assert.All(response.Decisions, d => Assert.Equal("acme", d.TenantId));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_LimitsTopSources()
{
var (service, snapshotId) = BuildService();
@@ -153,7 +159,8 @@ public sealed class PolicyDecisionServiceTests
});
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ExcludesEvidenceWhenNotRequested()
{
var (service, snapshotId) = BuildService();
@@ -166,7 +173,8 @@ public sealed class PolicyDecisionServiceTests
Assert.All(response.Decisions, d => Assert.Null(d.Evidence));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ReturnsDeterministicOrder()
{
var (service, snapshotId) = BuildService();
@@ -180,7 +188,8 @@ public sealed class PolicyDecisionServiceTests
response2.Decisions.Select(d => d.ComponentPurl));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ThrowsOnEmptySnapshotId()
{
var (service, _) = BuildService();
@@ -189,7 +198,8 @@ public sealed class PolicyDecisionServiceTests
await Assert.ThrowsAsync<ArgumentException>(() => service.GetDecisionsAsync(request));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_TopSourcesHaveRanks()
{
var (service, snapshotId) = BuildService();

View File

@@ -15,6 +15,7 @@ using StellaOps.Policy.Unknowns.Services;
using Xunit;
using Xunit.Sdk;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyEvaluatorTests
@@ -81,7 +82,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
private readonly PolicyCompiler compiler = new();
private readonly PolicyEvaluationService evaluationService = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_BlockCriticalRuleMatches()
{
var document = CompileBaseline();
@@ -94,7 +96,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("blocked", result.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_EscalateAdjustsSeverity()
{
var document = CompileBaseline();
@@ -108,7 +111,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("Critical", result.Severity);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_VexOverrideSetsStatusAndAnnotation()
{
var document = CompileBaseline();
@@ -127,7 +131,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("stmt-001", result.Annotations["winning_statement"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_WarnRuleEmitsWarning()
{
var document = CompileBaseline();
@@ -145,7 +150,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Contains(result.Warnings, message => message.Contains("EOL", StringComparison.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_ExceptionSuppressesCriticalFinding()
{
var document = CompileBaseline();
@@ -183,7 +189,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("suppressed", result.Annotations["exception.status"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_ExceptionDowngradesSeverity()
{
var document = CompileBaseline();
@@ -223,7 +230,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("Medium", result.Annotations["exception.severity"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MoreSpecificExceptionWins()
{
var document = CompileBaseline();
@@ -283,7 +291,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("alice", result.Annotations["exception.meta.requestedBy"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_RubyDevComponentBlocked()
{
var document = CompileBaseline();
@@ -309,7 +318,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Equal("blocked", result.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_RubyGitComponentWarns()
{
var document = CompileBaseline();
@@ -337,7 +347,8 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
Assert.Contains(result.Warnings, warning => warning.Contains("Git-sourced", StringComparison.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_UnknownBudgetExceeded_BlocksEvaluation()
{
var document = CompileBaseline();
@@ -602,7 +613,8 @@ policy "macOS Security Policy" syntax "stella-dsl@1" {
}
""";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MacOs_UnsignedAppBlocked()
{
var document = compiler.Compile(MacOsPolicy);
@@ -632,7 +644,8 @@ policy "macOS Security Policy" syntax "stella-dsl@1" {
Assert.Equal("blocked", result.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MacOs_SignedAppPasses()
{
var document = compiler.Compile(MacOsPolicy);
@@ -660,7 +673,8 @@ policy "macOS Security Policy" syntax "stella-dsl@1" {
Assert.False(result.Matched && result.Status == "blocked");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MacOs_HighRiskEntitlementsWarns()
{
var document = compiler.Compile(MacOsPolicy);
@@ -693,7 +707,8 @@ policy "macOS Security Policy" syntax "stella-dsl@1" {
Assert.Equal("warned", result.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MacOs_CategoryMatchesCameraAccess()
{
var document = compiler.Compile(MacOsPolicy);
@@ -727,7 +742,8 @@ policy "macOS Security Policy" syntax "stella-dsl@1" {
result.Status == "warned");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MacOs_HardenedRuntimeWarnsWhenMissing()
{
var document = compiler.Compile(MacOsPolicy);

View File

@@ -2,13 +2,15 @@ using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public class PolicyPackRepositoryTests
{
private readonly InMemoryPolicyPackRepository repository = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_WithSingleApprover_ActivatesImmediately()
{
await repository.CreateAsync("pack-1", "Pack", CancellationToken.None);
@@ -22,7 +24,8 @@ public class PolicyPackRepositoryTests
Assert.Single(result.Revision.Approvals);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_WithTwoPersonRequirement_ReturnsPendingUntilSecondApproval()
{
await repository.CreateAsync("pack-2", "Pack", CancellationToken.None);

View File

@@ -12,6 +12,7 @@ using StellaOps.Policy.Engine.Signals.Entropy;
using StellaOps.PolicyDsl;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyRuntimeEvaluationServiceTests
@@ -38,7 +39,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
}
""";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ReturnsDecisionFromCompiledPolicy()
{
var harness = CreateHarness();
@@ -55,7 +57,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.False(response.Cached);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_UsesCacheOnSecondCall()
{
var harness = CreateHarness();
@@ -75,7 +78,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal(response1.CorrelationId, response2.CorrelationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_BypassCacheWhenRequested()
{
var harness = CreateHarness();
@@ -93,7 +97,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.False(response2.Cached);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ThrowsOnMissingBundle()
{
var harness = CreateHarness();
@@ -103,7 +108,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
() => harness.Service.EvaluateAsync(request, CancellationToken.None));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_GeneratesDeterministicCorrelationId()
{
var harness = CreateHarness();
@@ -123,7 +129,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal(response1.CorrelationId, response2.CorrelationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateBatchAsync_ReturnsMultipleResults()
{
var harness = CreateHarness();
@@ -141,7 +148,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal(3, responses.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateBatchAsync_UsesCacheForDuplicates()
{
var harness = CreateHarness();
@@ -164,7 +172,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Contains(responses, r => !r.Cached);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_DifferentContextsGetDifferentCacheKeys()
{
var harness = CreateHarness();
@@ -183,7 +192,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.NotEqual(response1.CorrelationId, response2.CorrelationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_EnrichesReachabilityFromFacts()
{
const string policy = """
@@ -232,7 +242,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal("warn", response.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_GatesUnreachableWithoutEvidenceRef_ToUnderInvestigation()
{
const string policy = """
@@ -286,7 +297,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal("under_investigation", response.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_GatesUnreachableWithLowConfidence_ToUnderInvestigation()
{
const string policy = """
@@ -340,7 +352,8 @@ public sealed class PolicyRuntimeEvaluationServiceTests
Assert.Equal("under_investigation", response.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_AllowsUnreachableWithEvidenceRefAndHighConfidence()
{
const string policy = """

View File

@@ -3,11 +3,13 @@ using System.Collections.Immutable;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Services;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyRuntimeEvaluatorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ReturnsDeterministicDecisionAndCaches()
{
var repo = new InMemoryPolicyPackRepository();
@@ -35,7 +37,8 @@ public sealed class PolicyRuntimeEvaluatorTests
Assert.Equal(1, first.Version);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ThrowsWhenBundleMissing()
{
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository());

View File

@@ -2,11 +2,13 @@ using Xunit;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Policy.Engine.Orchestration;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyWorkerServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExecuteAsync_ReturnsDeterministicResults()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T13:00:00Z"));
@@ -45,7 +47,8 @@ public sealed class PolicyWorkerServiceTests
Assert.Equal(result.ResultHash, fetched!.ResultHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExecuteAsync_IsIdempotentOnRetry()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T14:00:00Z"));

View File

@@ -12,6 +12,7 @@ using StellaOps.Policy.Engine.Options;
using StellaOps.Provcache;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
/// <summary>
@@ -49,7 +50,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
NullLogger<ProvcachePolicyEvaluationCache>.Instance);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_CacheHit_ReturnsEntry()
{
// Arrange
@@ -69,7 +71,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
result.Source.Should().Be(CacheSource.Redis);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_CacheMiss_ReturnsNull()
{
// Arrange
@@ -88,7 +91,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
result.Source.Should().Be(CacheSource.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_BypassHeader_SkipsCache()
{
// Arrange
@@ -106,7 +110,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
Times.Never);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_StoresEntryInProvcache()
{
// Arrange
@@ -127,7 +132,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
Times.Once);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_FailureDoesNotThrow()
{
// Arrange
@@ -142,7 +148,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
await _cache.SetAsync(key, entry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InvalidateAsync_CallsProvcache()
{
// Arrange
@@ -161,7 +168,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
Times.Once);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InvalidateByPolicyDigestAsync_InvalidatesAllMatchingEntries()
{
// Arrange
@@ -194,7 +202,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
Times.Once);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBatchAsync_ProcessesAllKeys()
{
// Arrange
@@ -224,7 +233,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
result.NotFound.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetStats_ReturnsAccumulatedStatistics()
{
// Act
@@ -235,7 +245,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
stats.TotalRequests.Should().BeGreaterThanOrEqualTo(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_WhenProvcacheThrows_TreatsAsMiss()
{
// Arrange
@@ -253,7 +264,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
result.Entry.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VeriKey_Construction_IsDeterministic()
{
// Arrange
@@ -334,7 +346,8 @@ public sealed class ProvcachePolicyEvaluationCacheTests
/// </summary>
public sealed class CacheBypassAccessorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HttpCacheBypassAccessor_NoHeader_ReturnsFalse()
{
// Arrange
@@ -348,7 +361,8 @@ public sealed class CacheBypassAccessorTests
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HttpCacheBypassAccessor_BypassHeaderTrue_ReturnsTrue()
{
// Arrange
@@ -363,7 +377,8 @@ public sealed class CacheBypassAccessorTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HttpCacheBypassAccessor_BypassHeaderFalse_ReturnsFalse()
{
// Arrange
@@ -378,7 +393,8 @@ public sealed class CacheBypassAccessorTests
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HttpCacheBypassAccessor_RefreshHeaderTrue_ReturnsTrue()
{
// Arrange
@@ -393,7 +409,8 @@ public sealed class CacheBypassAccessorTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HttpCacheBypassAccessor_BypassDisabledInOptions_AlwaysReturnsFalse()
{
// Arrange
@@ -408,7 +425,8 @@ public sealed class CacheBypassAccessorTests
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NullCacheBypassAccessor_AlwaysReturnsFalse()
{
// Arrange

View File

@@ -4,11 +4,13 @@ using StellaOps.Policy.Engine.Ledger;
using StellaOps.Policy.Engine.Orchestration;
using StellaOps.Policy.Engine.Snapshots;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class SnapshotServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_ProducesSnapshotFromLedger()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T16:00:00Z"));

View File

@@ -1,11 +1,13 @@
using Xunit;
using StellaOps.Policy.Engine.TrustWeighting;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class TrustWeightingServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Get_ReturnsDefaultsWithHash()
{
var service = new TrustWeightingService(TimeProvider.System);
@@ -16,7 +18,8 @@ public sealed class TrustWeightingServiceTests
Assert.False(string.IsNullOrWhiteSpace(profile.ProfileHash));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Set_NormalizesOrderingAndScale()
{
var service = new TrustWeightingService(TimeProvider.System);

View File

@@ -6,6 +6,7 @@ using StellaOps.Policy.Engine.Snapshots;
using StellaOps.Policy.Engine.TrustWeighting;
using StellaOps.Policy.Engine.Violations;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class ViolationServicesTests
@@ -62,7 +63,8 @@ public sealed class ViolationServicesTests
return (eventService, fusionService, conflictService, snapshot.SnapshotId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EmitAsync_BuildsEvents()
{
var (eventService, _, _, snapshotId) = BuildPipeline();
@@ -73,7 +75,8 @@ public sealed class ViolationServicesTests
Assert.All(events, e => Assert.Equal("policy.violation.detected", e.ViolationCode));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FuseAsync_ProducesWeightedSeverity()
{
var (eventService, fusionService, _, snapshotId) = BuildPipeline();
@@ -85,7 +88,8 @@ public sealed class ViolationServicesTests
Assert.All(fused, f => Assert.False(string.IsNullOrWhiteSpace(f.SeverityFused)));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConflictsAsync_DetectsDivergentSeverities()
{
var (eventService, fusionService, conflictService, snapshotId) = BuildPipeline();

View File

@@ -5,11 +5,13 @@ using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
public sealed class EvidenceRequirementValidatorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_NoHooks_ReturnsValid()
{
var validator = CreateValidator(new StubHookRegistry([]));
@@ -21,7 +23,8 @@ public sealed class EvidenceRequirementValidatorTests
result.MissingEvidence.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_MissingEvidence_ReturnsInvalid()
{
var hooks = ImmutableArray.Create(new EvidenceHook
@@ -41,7 +44,8 @@ public sealed class EvidenceRequirementValidatorTests
result.MissingEvidence.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_TrustScoreTooLow_ReturnsInvalid()
{
var hooks = ImmutableArray.Create(new EvidenceHook

View File

@@ -3,11 +3,13 @@ using FluentAssertions;
using StellaOps.Policy.Exceptions.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
public sealed class EvidenceRequirementsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceRequirements_ShouldBeSatisfied_WhenAllMandatoryHooksValid()
{
var hooks = ImmutableArray.Create(
@@ -47,7 +49,8 @@ public sealed class EvidenceRequirementsTests
requirements.MissingEvidence.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceRequirements_ShouldReportMissing_WhenMandatoryHookMissing()
{
var hooks = ImmutableArray.Create(new EvidenceHook

View File

@@ -6,6 +6,7 @@ using StellaOps.Policy.Exceptions.Repositories;
using StellaOps.Policy.Exceptions.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
/// <summary>
@@ -22,7 +23,8 @@ public sealed class ExceptionEvaluatorTests
_evaluator = new ExceptionEvaluator(_repositoryMock.Object);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenNoExceptionsFound_ShouldReturnNoMatch()
{
// Arrange
@@ -45,7 +47,8 @@ public sealed class ExceptionEvaluatorTests
result.PrimaryRationale.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionMatchesVulnerability_ShouldReturnMatch()
{
// Arrange
@@ -73,7 +76,8 @@ public sealed class ExceptionEvaluatorTests
result.PrimaryRationale.Should().Contain("false positive");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionMatchesArtifactDigest_ShouldReturnMatch()
{
// Arrange
@@ -97,7 +101,8 @@ public sealed class ExceptionEvaluatorTests
result.MatchingExceptions.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionMatchesPolicyRule_ShouldReturnMatch()
{
// Arrange
@@ -119,7 +124,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionHasWrongVulnerabilityId_ShouldNotMatch()
{
// Arrange
@@ -141,7 +147,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionHasWrongArtifactDigest_ShouldNotMatch()
{
// Arrange
@@ -163,7 +170,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenEnvironmentDoesNotMatch_ShouldNotMatch()
{
// Arrange
@@ -188,7 +196,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenEnvironmentMatches_ShouldReturnMatch()
{
// Arrange
@@ -213,7 +222,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenExceptionHasEmptyEnvironments_ShouldMatchAny()
{
// Arrange
@@ -238,7 +248,8 @@ public sealed class ExceptionEvaluatorTests
result.HasException.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WithMultipleMatchingExceptions_ShouldReturnMostSpecificFirst()
{
// Arrange
@@ -271,7 +282,8 @@ public sealed class ExceptionEvaluatorTests
result.MatchingExceptions[0].ExceptionId.Should().Be("EXC-SPECIFIC");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ShouldCollectAllEvidenceRefs()
{
// Arrange
@@ -302,7 +314,8 @@ public sealed class ExceptionEvaluatorTests
result.AllEvidenceRefs.Should().Contain(["sha256:evidence1", "sha256:evidence2", "sha256:evidence3"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateBatchAsync_ShouldEvaluateAllContexts()
{
// Arrange
@@ -330,7 +343,8 @@ public sealed class ExceptionEvaluatorTests
results[2].HasException.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_WhenPurlPatternMatchesExactly_ShouldReturnMatch()
{
// Arrange

View File

@@ -3,6 +3,7 @@ using FluentAssertions;
using StellaOps.Policy.Exceptions.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
/// <summary>
@@ -10,7 +11,8 @@ namespace StellaOps.Policy.Exceptions.Tests;
/// </summary>
public sealed class ExceptionEventTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForCreated_ShouldCreateCorrectEvent()
{
// Arrange
@@ -36,7 +38,8 @@ public sealed class ExceptionEventTests
evt.OccurredAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForCreated_WithoutDescription_ShouldUseDefault()
{
// Act
@@ -46,7 +49,8 @@ public sealed class ExceptionEventTests
evt.Description.Should().Be("Exception created");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForApproved_ShouldCreateCorrectEvent()
{
// Arrange
@@ -70,7 +74,8 @@ public sealed class ExceptionEventTests
evt.NewStatus.Should().Be(ExceptionStatus.Approved);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForApproved_WithoutDescription_ShouldIncludeActorId()
{
// Act
@@ -80,7 +85,8 @@ public sealed class ExceptionEventTests
evt.Description.Should().Contain("approver@example.com");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForActivated_ShouldCreateCorrectEvent()
{
// Arrange
@@ -100,7 +106,8 @@ public sealed class ExceptionEventTests
evt.Description.Should().Be("Exception activated");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForRevoked_ShouldCreateCorrectEvent()
{
// Arrange
@@ -123,7 +130,8 @@ public sealed class ExceptionEventTests
evt.Details["reason"].Should().Be(reason);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForExpired_ShouldCreateCorrectEvent()
{
// Arrange
@@ -142,7 +150,8 @@ public sealed class ExceptionEventTests
evt.Description.Should().Be("Exception expired automatically");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForExtended_ShouldCreateCorrectEvent()
{
// Arrange
@@ -166,7 +175,8 @@ public sealed class ExceptionEventTests
evt.Details.Should().ContainKey("new_expiry");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ForExtended_WithoutReason_ShouldIncludeDates()
{
// Arrange
@@ -181,7 +191,8 @@ public sealed class ExceptionEventTests
evt.Description.Should().Contain("to");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ExceptionEventType.Created)]
[InlineData(ExceptionEventType.Updated)]
[InlineData(ExceptionEventType.Approved)]
@@ -198,7 +209,8 @@ public sealed class ExceptionEventTests
Enum.IsDefined(eventType).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllFactoryMethods_ShouldGenerateUniqueEventIds()
{
// Act
@@ -216,7 +228,8 @@ public sealed class ExceptionEventTests
events.Select(e => e.EventId).Distinct().Should().HaveCount(events.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllFactoryMethods_ShouldSetOccurredAtToNow()
{
// Arrange
@@ -238,7 +251,8 @@ public sealed class ExceptionEventTests
/// </summary>
public sealed class ExceptionHistoryTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionHistory_WithEvents_ShouldCalculateCorrectStats()
{
// Arrange
@@ -262,7 +276,8 @@ public sealed class ExceptionHistoryTests
history.LastEventAt.Should().Be(events[2].OccurredAt);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionHistory_WithNoEvents_ShouldReturnNullTimestamps()
{
// Arrange & Act
@@ -278,7 +293,8 @@ public sealed class ExceptionHistoryTests
history.LastEventAt.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionHistory_WithSingleEvent_ShouldHaveSameFirstAndLast()
{
// Arrange

View File

@@ -3,6 +3,7 @@ using FluentAssertions;
using StellaOps.Policy.Exceptions.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
/// <summary>
@@ -10,7 +11,8 @@ namespace StellaOps.Policy.Exceptions.Tests;
/// </summary>
public sealed class ExceptionObjectTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_WithValidScope_ShouldBeValid()
{
// Arrange & Act
@@ -23,7 +25,8 @@ public sealed class ExceptionObjectTests
scope.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithNoConstraints_ShouldBeInvalid()
{
// Arrange & Act
@@ -33,7 +36,8 @@ public sealed class ExceptionObjectTests
scope.IsValid.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithArtifactDigest_ShouldBeValid()
{
// Arrange & Act
@@ -46,7 +50,8 @@ public sealed class ExceptionObjectTests
scope.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithPurlPattern_ShouldBeValid()
{
// Arrange & Act
@@ -59,7 +64,8 @@ public sealed class ExceptionObjectTests
scope.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithPolicyRuleId_ShouldBeValid()
{
// Arrange & Act
@@ -72,7 +78,8 @@ public sealed class ExceptionObjectTests
scope.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsEffective_WhenActiveAndNotExpired_ShouldBeTrue()
{
// Arrange
@@ -85,7 +92,8 @@ public sealed class ExceptionObjectTests
exception.HasExpired.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsEffective_WhenActiveButExpired_ShouldBeFalse()
{
// Arrange
@@ -98,7 +106,8 @@ public sealed class ExceptionObjectTests
exception.HasExpired.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsEffective_WhenProposed_ShouldBeFalse()
{
// Arrange
@@ -110,7 +119,8 @@ public sealed class ExceptionObjectTests
exception.IsEffective.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsEffective_WhenRevoked_ShouldBeFalse()
{
// Arrange
@@ -122,7 +132,8 @@ public sealed class ExceptionObjectTests
exception.IsEffective.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsEffective_WhenExpiredStatus_ShouldBeFalse()
{
// Arrange
@@ -134,7 +145,8 @@ public sealed class ExceptionObjectTests
exception.IsEffective.Should().BeFalse();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ExceptionStatus.Proposed)]
[InlineData(ExceptionStatus.Approved)]
[InlineData(ExceptionStatus.Active)]
@@ -149,7 +161,8 @@ public sealed class ExceptionObjectTests
exception.Status.Should().Be(status);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ExceptionType.Vulnerability)]
[InlineData(ExceptionType.Policy)]
[InlineData(ExceptionType.Unknown)]
@@ -163,7 +176,8 @@ public sealed class ExceptionObjectTests
exception.Type.Should().Be(type);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ExceptionReason.FalsePositive)]
[InlineData(ExceptionReason.AcceptedRisk)]
[InlineData(ExceptionReason.CompensatingControl)]
@@ -183,7 +197,8 @@ public sealed class ExceptionObjectTests
exception.ReasonCode.Should().Be(reason);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_WithMultipleApprovers_ShouldStoreAll()
{
// Arrange
@@ -195,7 +210,8 @@ public sealed class ExceptionObjectTests
exception.ApproverIds.Should().Contain(["approver1", "approver2", "approver3"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_WithEvidenceRefs_ShouldStoreAll()
{
// Arrange
@@ -210,7 +226,8 @@ public sealed class ExceptionObjectTests
exception.EvidenceRefs.Should().Contain("sha256:evidence1hash");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_IsBlockedByRecheck_WhenBlockTriggered_ShouldBeTrue()
{
// Arrange
@@ -227,7 +244,8 @@ public sealed class ExceptionObjectTests
exception.RequiresReapproval.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_RequiresReapproval_WhenReapprovalTriggered_ShouldBeTrue()
{
// Arrange
@@ -244,7 +262,8 @@ public sealed class ExceptionObjectTests
exception.IsBlockedByRecheck.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionObject_WithMetadata_ShouldStoreKeyValuePairs()
{
// Arrange
@@ -260,7 +279,8 @@ public sealed class ExceptionObjectTests
exception.Metadata["priority"].Should().Be("high");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithEnvironments_ShouldStoreAll()
{
// Arrange
@@ -275,7 +295,8 @@ public sealed class ExceptionObjectTests
scope.Environments.Should().Contain(["prod", "staging", "dev"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExceptionScope_WithTenantId_ShouldStoreValue()
{
// Arrange

View File

@@ -4,11 +4,13 @@ using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
public sealed class RecheckEvaluationServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_NoPolicy_ReturnsNoTrigger()
{
var service = new RecheckEvaluationService();
@@ -26,7 +28,8 @@ public sealed class RecheckEvaluationServiceTests
result.RecommendedAction.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_EpssAbove_Triggers()
{
var service = new RecheckEvaluationService();
@@ -60,7 +63,8 @@ public sealed class RecheckEvaluationServiceTests
result.RecommendedAction.Should().Be(RecheckAction.RequireReapproval);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_EnvironmentScope_FiltersConditions()
{
var service = new RecheckEvaluationService();
@@ -92,7 +96,8 @@ public sealed class RecheckEvaluationServiceTests
result.IsTriggered.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ActionPriority_PicksBlock()
{
var service = new RecheckEvaluationService();
@@ -133,7 +138,8 @@ public sealed class RecheckEvaluationServiceTests
result.RecommendedAction.Should().Be(RecheckAction.Block);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EvaluateAsync_ExpiryWithin_UsesThreshold()
{
var service = new RecheckEvaluationService();

View File

@@ -22,5 +22,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Policy.Exceptions/StellaOps.Policy.Exceptions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -26,11 +26,14 @@ using StellaOps.Policy.Gateway.Services;
using Xunit;
using Xunit.Sdk;
using StellaOps.TestKit;
namespace StellaOps.Policy.Gateway.Tests;
public sealed class GatewayActivationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_UsesServiceTokenFallback_And_RecordsMetrics()
{
await using var factory = new PolicyGatewayWebApplicationFactory();
@@ -114,7 +117,8 @@ public sealed class GatewayActivationTests
measurement.Source == "service");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_CompletesDualControlWorkflow()
{
await using var factory = new PolicyGatewayWebApplicationFactory();
@@ -154,7 +158,8 @@ public sealed class GatewayActivationTests
Assert.Equal(2, recordingHandler.RequestCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_RecordsMetrics_WhenUpstreamReturnsUnauthorized()
{
await using var factory = new PolicyGatewayWebApplicationFactory();
@@ -240,7 +245,8 @@ public sealed class GatewayActivationTests
measurement.Source == "service");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_RecordsMetrics_WhenUpstreamReturnsBadGateway()
{
await using var factory = new PolicyGatewayWebApplicationFactory();
@@ -326,7 +332,8 @@ public sealed class GatewayActivationTests
measurement.Source == "service");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_RetriesOnTooManyRequests()
{
await using var factory = new PolicyGatewayWebApplicationFactory();

View File

@@ -21,11 +21,14 @@ using StellaOps.Policy.Gateway.Options;
using StellaOps.Policy.Gateway.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Gateway.Tests;
public class PolicyEngineClientTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ActivateRevision_UsesServiceTokenWhenForwardingContextMissing()
{
var options = CreateGatewayOptions();
@@ -61,7 +64,8 @@ public class PolicyEngineClientTests
Assert.Equal(1, tokenClient.RequestCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Metrics_RecordActivation_EmitsExpectedTags()
{
using var metrics = new PolicyGatewayMetrics();

View File

@@ -12,11 +12,14 @@ using StellaOps.Policy.Gateway.Options;
using StellaOps.Policy.Gateway.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Gateway.Tests;
public sealed class PolicyGatewayDpopProofGeneratorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProof_Throws_WhenDpopDisabled()
{
var options = CreateGatewayOptions();
@@ -34,7 +37,8 @@ public sealed class PolicyGatewayDpopProofGeneratorTests
Assert.Equal("DPoP proof requested while DPoP is disabled.", exception.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProof_Throws_WhenKeyFileMissing()
{
var tempRoot = Directory.CreateTempSubdirectory();
@@ -61,7 +65,8 @@ public sealed class PolicyGatewayDpopProofGeneratorTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProof_UsesConfiguredAlgorithmAndEmbedsTokenHash()
{
var tempRoot = Directory.CreateTempSubdirectory();

View File

@@ -10,11 +10,12 @@
<ItemGroup>
<ProjectReference Include="../../StellaOps.Policy.Gateway/StellaOps.Policy.Gateway.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using FluentAssertions;
using YamlDotNet.Serialization;
using StellaOps.TestKit;
namespace StellaOps.Policy.Pack.Tests;
public class EnvironmentOverrideTests
@@ -21,7 +22,8 @@ public class EnvironmentOverrideTests
.Build();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("production.yaml")]
[InlineData("staging.yaml")]
[InlineData("development.yaml")]
@@ -31,7 +33,8 @@ public class EnvironmentOverrideTests
File.Exists(overridePath).Should().BeTrue($"{fileName} should exist");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("production.yaml", "production")]
[InlineData("staging.yaml", "staging")]
[InlineData("development.yaml", "development")]
@@ -45,7 +48,8 @@ public class EnvironmentOverrideTests
metadata!["environment"].Should().Be(expectedEnv);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("production.yaml")]
[InlineData("staging.yaml")]
[InlineData("development.yaml")]
@@ -58,7 +62,8 @@ public class EnvironmentOverrideTests
policy["kind"].Should().Be("PolicyOverride");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("production.yaml")]
[InlineData("staging.yaml")]
[InlineData("development.yaml")]
@@ -73,7 +78,8 @@ public class EnvironmentOverrideTests
metadata["parent"].Should().Be("starter-day1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DevelopmentOverride_DowngradesBlockingRulesToWarnings()
{
var overridePath = Path.Combine(_overridesPath, "development.yaml");
@@ -101,7 +107,8 @@ public class EnvironmentOverrideTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DevelopmentOverride_HasHigherUnknownsThreshold()
{
var overridePath = Path.Combine(_overridesPath, "development.yaml");
@@ -116,7 +123,8 @@ public class EnvironmentOverrideTests
threshold.Should().BeGreaterThan(0.05, "Development should have a higher unknowns threshold than production default");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DevelopmentOverride_DisablesSigningRequirements()
{
var overridePath = Path.Combine(_overridesPath, "development.yaml");
@@ -140,7 +148,8 @@ public class EnvironmentOverrideTests
};
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProductionOverride_HasStricterSettings()
{
var overridePath = Path.Combine(_overridesPath, "production.yaml");
@@ -162,7 +171,8 @@ public class EnvironmentOverrideTests
ParseBool(settings["requireSignedVerdict"]).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProductionOverride_HasAdditionalExceptionApprovalRule()
{
var overridePath = Path.Combine(_overridesPath, "production.yaml");
@@ -181,7 +191,8 @@ public class EnvironmentOverrideTests
exceptionRule.Should().NotBeNull("Production should have exception approval rule");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StagingOverride_HasModerateSettings()
{
var overridePath = Path.Combine(_overridesPath, "staging.yaml");

View File

@@ -9,6 +9,7 @@ using Json.Schema;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using StellaOps.TestKit;
namespace StellaOps.Policy.Pack.Tests;
public class PolicyPackSchemaTests
@@ -41,14 +42,16 @@ public class PolicyPackSchemaTests
return JsonNode.Parse(jsonString)!;
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_Exists()
{
var schemaPath = Path.Combine(_testDataPath, "policy-pack.schema.json");
File.Exists(schemaPath).Should().BeTrue("policy-pack.schema.json should exist");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_IsValidJsonSchema()
{
_schema.Should().NotBeNull("Schema should be parseable");
@@ -89,7 +92,8 @@ public class PolicyPackSchemaTests
result.IsValid ? "" : $"{fileName} should validate against schema. Errors: {FormatErrors(result)}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_RequiresApiVersion()
{
var invalidPolicy = JsonNode.Parse("""
@@ -104,7 +108,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy without apiVersion should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_RequiresKind()
{
var invalidPolicy = JsonNode.Parse("""
@@ -119,7 +124,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy without kind should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_RequiresMetadata()
{
var invalidPolicy = JsonNode.Parse("""
@@ -134,7 +140,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy without metadata should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_RequiresSpec()
{
var invalidPolicy = JsonNode.Parse("""
@@ -149,7 +156,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy without spec should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_ValidatesApiVersionFormat()
{
var invalidPolicy = JsonNode.Parse("""
@@ -165,7 +173,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy with invalid apiVersion format should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_ValidatesKindEnum()
{
var invalidPolicy = JsonNode.Parse("""
@@ -181,7 +190,8 @@ public class PolicyPackSchemaTests
result.IsValid.Should().BeFalse("Policy with invalid kind should fail validation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_AcceptsValidPolicyPack()
{
var validPolicy = JsonNode.Parse("""
@@ -214,7 +224,8 @@ public class PolicyPackSchemaTests
result.IsValid ? "" : $"Valid policy should pass validation. Errors: {FormatErrors(result)}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_AcceptsValidPolicyOverride()
{
var validOverride = JsonNode.Parse("""
@@ -246,7 +257,8 @@ public class PolicyPackSchemaTests
result.IsValid ? "" : $"Valid override should pass validation. Errors: {FormatErrors(result)}");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("allow")]
[InlineData("warn")]
[InlineData("block")]

View File

@@ -8,6 +8,7 @@ using FluentAssertions;
using Json.Schema;
using YamlDotNet.Serialization;
using StellaOps.TestKit;
namespace StellaOps.Policy.Pack.Tests;
public class StarterPolicyPackTests
@@ -23,14 +24,16 @@ public class StarterPolicyPackTests
.Build();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_Exists()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
File.Exists(policyPath).Should().BeTrue("starter-day1.yaml should exist");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasValidYamlStructure()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -40,7 +43,8 @@ public class StarterPolicyPackTests
act.Should().NotThrow("YAML should be valid and parseable");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasRequiredFields()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -53,7 +57,8 @@ public class StarterPolicyPackTests
policy.Should().ContainKey("spec", "Policy should have spec field");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasCorrectApiVersion()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -63,7 +68,8 @@ public class StarterPolicyPackTests
policy["apiVersion"].Should().Be("policy.stellaops.io/v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasCorrectKind()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -73,7 +79,8 @@ public class StarterPolicyPackTests
policy["kind"].Should().Be("PolicyPack");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasValidMetadata()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -90,7 +97,8 @@ public class StarterPolicyPackTests
metadata["version"].ToString().Should().MatchRegex(@"^\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?$", "version should be semver");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasRulesSection()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -106,7 +114,8 @@ public class StarterPolicyPackTests
rules!.Should().HaveCountGreaterThan(0, "Policy should have at least one rule");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasSettingsSection()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");
@@ -122,7 +131,8 @@ public class StarterPolicyPackTests
settings!.Should().ContainKey("defaultAction");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("block-reachable-high-critical")]
[InlineData("warn-reachable-medium")]
[InlineData("allow-unreachable")]
@@ -146,7 +156,8 @@ public class StarterPolicyPackTests
ruleNames.Should().Contain(ruleName, $"Policy should contain rule '{ruleName}'");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StarterDay1Policy_HasDefaultAllowRuleWithLowestPriority()
{
var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml");

View File

@@ -35,4 +35,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,11 +3,14 @@ using System.Text.Json;
using StellaOps.Policy.RiskProfile.Canonicalization;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.RiskProfile.Tests;
public class RiskProfileCanonicalizerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_SortsSignalsAndOverrides()
{
const string input = """
@@ -39,7 +42,8 @@ public class RiskProfileCanonicalizerTests
Assert.Equal(expected, canonical);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeDigest_IgnoresOrderingNoise()
{
const string a = """
@@ -55,7 +59,8 @@ public class RiskProfileCanonicalizerTests
Assert.Equal(hashA, hashB);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Merge_ReplacesSignalsAndWeights()
{
const string baseProfile = """

View File

@@ -2,13 +2,15 @@ using System.Text.Json;
using StellaOps.Policy.RiskProfile.Validation;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.RiskProfile.Tests;
public class RiskProfileValidatorTests
{
private readonly RiskProfileValidator _validator = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Valid_profile_passes_schema()
{
var profile = """
@@ -43,7 +45,8 @@ public class RiskProfileValidatorTests
Assert.True(result.IsValid, string.Join(" | ", result.Errors ?? Array.Empty<string>()));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Missing_required_fields_fails_schema()
{
var invalidProfile = """
@@ -56,7 +59,8 @@ public class RiskProfileValidatorTests
Assert.NotEmpty(result.Errors);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Empty_payload_throws()
{
Assert.Throws<ArgumentException>(() => _validator.Validate(" "));

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="../../StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using StellaOps.Policy.Scoring.Engine;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
/// <summary>
@@ -11,7 +12,8 @@ public sealed class CvssMultiVersionEngineTests
{
#region CVSS v2 Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV2_ComputeFromVector_HighSeverity_ReturnsCorrectScore()
{
// Arrange - CVE-2002-0392 Apache Chunked-Encoding
@@ -27,7 +29,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().Be("High");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV2_ComputeFromVector_MediumSeverity_ReturnsCorrectScore()
{
// Arrange
@@ -43,7 +46,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().Be("Medium");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV2_ComputeFromVector_WithTemporal_ReducesScore()
{
// Arrange
@@ -60,7 +64,8 @@ public sealed class CvssMultiVersionEngineTests
temporalResult.TemporalScore.Should().BeLessThan(baseResult.BaseScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV2_IsValidVector_ValidVector_ReturnsTrue()
{
var engine = new CvssV2Engine();
@@ -68,7 +73,8 @@ public sealed class CvssMultiVersionEngineTests
engine.IsValidVector("CVSS2#AV:N/AC:L/Au:N/C:C/I:C/A:C").Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV2_IsValidVector_InvalidVector_ReturnsFalse()
{
var engine = new CvssV2Engine();
@@ -81,7 +87,8 @@ public sealed class CvssMultiVersionEngineTests
#region CVSS v3 Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_ComputeFromVector_CriticalSeverity_ReturnsCorrectScore()
{
// Arrange - Maximum severity vector
@@ -97,7 +104,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().Be("Critical");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_ComputeFromVector_HighSeverity_ReturnsCorrectScore()
{
// Arrange
@@ -113,7 +121,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().Be("Critical");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_ComputeFromVector_MediumSeverity_ReturnsCorrectScore()
{
// Arrange
@@ -128,7 +137,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().BeOneOf("Low", "Medium");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_ComputeFromVector_V30_ParsesCorrectly()
{
// Arrange
@@ -143,7 +153,8 @@ public sealed class CvssMultiVersionEngineTests
result.BaseScore.Should().BeGreaterThan(9.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_IsValidVector_ValidVector_ReturnsTrue()
{
var engine = new CvssV3Engine(CvssVersion.V3_1);
@@ -151,7 +162,8 @@ public sealed class CvssMultiVersionEngineTests
engine.IsValidVector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H").Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_IsValidVector_InvalidVector_ReturnsFalse()
{
var engine = new CvssV3Engine(CvssVersion.V3_1);
@@ -160,7 +172,8 @@ public sealed class CvssMultiVersionEngineTests
engine.IsValidVector("").Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssV3_ScopeChanged_AffectsScore()
{
// Arrange
@@ -180,7 +193,8 @@ public sealed class CvssMultiVersionEngineTests
#region Factory Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_DetectVersion_V4_DetectsCorrectly()
{
var factory = new CvssEngineFactory();
@@ -188,7 +202,8 @@ public sealed class CvssMultiVersionEngineTests
version.Should().Be(CvssVersion.V4_0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_DetectVersion_V31_DetectsCorrectly()
{
var factory = new CvssEngineFactory();
@@ -196,7 +211,8 @@ public sealed class CvssMultiVersionEngineTests
version.Should().Be(CvssVersion.V3_1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_DetectVersion_V30_DetectsCorrectly()
{
var factory = new CvssEngineFactory();
@@ -204,7 +220,8 @@ public sealed class CvssMultiVersionEngineTests
version.Should().Be(CvssVersion.V3_0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_DetectVersion_V2_DetectsCorrectly()
{
var factory = new CvssEngineFactory();
@@ -212,7 +229,8 @@ public sealed class CvssMultiVersionEngineTests
factory.DetectVersion("CVSS2#AV:N/AC:L/Au:N/C:C/I:C/A:C").Should().Be(CvssVersion.V2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_DetectVersion_Invalid_ReturnsNull()
{
var factory = new CvssEngineFactory();
@@ -221,7 +239,8 @@ public sealed class CvssMultiVersionEngineTests
factory.DetectVersion(null!).Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_Create_V2_ReturnsCorrectEngine()
{
var factory = new CvssEngineFactory();
@@ -229,7 +248,8 @@ public sealed class CvssMultiVersionEngineTests
engine.Version.Should().Be(CvssVersion.V2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_Create_V31_ReturnsCorrectEngine()
{
var factory = new CvssEngineFactory();
@@ -237,7 +257,8 @@ public sealed class CvssMultiVersionEngineTests
engine.Version.Should().Be(CvssVersion.V3_1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_Create_V40_ReturnsCorrectEngine()
{
var factory = new CvssEngineFactory();
@@ -245,7 +266,8 @@ public sealed class CvssMultiVersionEngineTests
engine.Version.Should().Be(CvssVersion.V4_0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_ComputeFromVector_AutoDetects()
{
var factory = new CvssEngineFactory();
@@ -261,7 +283,8 @@ public sealed class CvssMultiVersionEngineTests
v31Result.BaseScore.Should().BeGreaterThan(9.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CvssEngineFactory_ComputeFromVector_InvalidVector_ThrowsException()
{
var factory = new CvssEngineFactory();
@@ -273,7 +296,8 @@ public sealed class CvssMultiVersionEngineTests
#region Cross-Version Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllEngines_SameInput_ReturnsDeterministicOutput()
{
var factory = new CvssEngineFactory();
@@ -297,7 +321,8 @@ public sealed class CvssMultiVersionEngineTests
#region Real-World CVE Vector Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 9.8, "Critical")] // Log4Shell style
[InlineData("CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", 6.1, "Medium")] // XSS style
[InlineData("CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", 7.8, "High")] // Local privilege escalation
@@ -310,7 +335,8 @@ public sealed class CvssMultiVersionEngineTests
result.Severity.Should().Be(expectedSeverity);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("AV:N/AC:L/Au:N/C:C/I:C/A:C", 10.0, "High")] // Remote code execution
[InlineData("AV:N/AC:M/Au:N/C:P/I:P/A:P", 6.8, "Medium")] // Moderate network vuln
[InlineData("AV:L/AC:L/Au:N/C:P/I:N/A:N", 2.1, "Low")] // Local info disclosure

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Scoring.Receipts;
using StellaOps.Policy.Scoring.Tests.Fakes;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
/// <summary>
@@ -18,7 +19,8 @@ public sealed class CvssPipelineIntegrationTests
#region Full Pipeline Tests - V4 Receipt
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FullPipeline_V4_CreatesReceiptWithDeterministicHash()
{
// Arrange
@@ -53,7 +55,8 @@ public sealed class CvssPipelineIntegrationTests
receipt.InputHash.Should().HaveLength(64); // SHA-256 hex
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FullPipeline_V4_WithThreatMetrics_AdjustsScore()
{
// Arrange
@@ -100,7 +103,8 @@ public sealed class CvssPipelineIntegrationTests
#region Cross-Version Factory Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("AV:N/AC:L/Au:N/C:C/I:C/A:C", CvssVersion.V2, 10.0)]
[InlineData("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", CvssVersion.V3_1, 10.0)]
[InlineData("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", CvssVersion.V4_0, 10.0)]
@@ -114,7 +118,8 @@ public sealed class CvssPipelineIntegrationTests
result.BaseScore.Should().Be(expectedScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CrossVersion_AllVersions_ReturnCorrectSeverityLabels()
{
// Arrange - Maximum severity vectors for each version
@@ -137,7 +142,8 @@ public sealed class CvssPipelineIntegrationTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInput_ProducesSameInputHash()
{
// Arrange
@@ -184,7 +190,8 @@ public sealed class CvssPipelineIntegrationTests
receipt1.Severity.Should().Be(receipt2.Severity);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Determinism_EngineScoring_IsIdempotent()
{
// Arrange
@@ -217,7 +224,8 @@ public sealed class CvssPipelineIntegrationTests
#region Version Detection Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("AV:N/AC:L/Au:N/C:C/I:C/A:C", CvssVersion.V2)]
[InlineData("CVSS2#AV:N/AC:L/Au:N/C:C/I:C/A:C", CvssVersion.V2)]
[InlineData("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", CvssVersion.V3_0)]
@@ -232,7 +240,8 @@ public sealed class CvssPipelineIntegrationTests
detected.Should().Be(expectedVersion);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData("invalid")]
[InlineData("CVSS:5.0/AV:N")]
@@ -250,7 +259,8 @@ public sealed class CvssPipelineIntegrationTests
#region Error Handling Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ErrorHandling_InvalidVector_ThrowsArgumentException()
{
// Act & Assert
@@ -258,7 +268,8 @@ public sealed class CvssPipelineIntegrationTests
.Should().Throw<ArgumentException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ErrorHandling_NullVector_ThrowsException()
{
// Act & Assert
@@ -270,7 +281,8 @@ public sealed class CvssPipelineIntegrationTests
#region Real-World CVE Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("CVE-2021-44228", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", 10.0, "Critical")] // Log4Shell
[InlineData("CVE-2022-22965", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 9.8, "Critical")] // Spring4Shell
[InlineData("CVE-2014-0160", "AV:N/AC:L/Au:N/C:P/I:N/A:N", 5.0, "Medium")] // Heartbleed (V2)
@@ -291,7 +303,8 @@ public sealed class CvssPipelineIntegrationTests
#region Severity Threshold Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0.0, CvssSeverity.None)]
[InlineData(0.1, CvssSeverity.Low)]
[InlineData(3.9, CvssSeverity.Low)]

View File

@@ -4,13 +4,16 @@ using FluentAssertions;
using StellaOps.Policy.Scoring.Policies;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
public sealed class CvssPolicyLoaderTests
{
private readonly CvssPolicyLoader _loader = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Load_ValidPolicy_ComputesDeterministicHashAndReturnsPolicy()
{
// Arrange
@@ -43,7 +46,8 @@ public sealed class CvssPolicyLoaderTests
roundTrip.Policy!.Hash.Should().Be(result.Hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Load_InvalidPolicy_ReturnsValidationErrors()
{
// Arrange: missing required fields

View File

@@ -14,7 +14,8 @@ public sealed class CvssV4EngineTests
#region Base Score Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_MaximumSeverity_ReturnsScore10()
{
// Arrange - Highest severity: Network/Low/None/None/None/High across all impacts
@@ -29,7 +30,8 @@ public sealed class CvssV4EngineTests
scores.EffectiveScoreType.Should().Be(EffectiveScoreType.Base);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_MinimumSeverity_ReturnsLowScore()
{
// Arrange - Lowest severity: Physical/High/Present/High/Active/None across all impacts
@@ -43,7 +45,8 @@ public sealed class CvssV4EngineTests
scores.EffectiveScore.Should().BeLessThan(2.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_MediumSeverity_ReturnsScoreInRange()
{
// Arrange - Medium severity combination
@@ -73,7 +76,8 @@ public sealed class CvssV4EngineTests
#region Threat Score Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithAttackedThreat_ReturnsThreatScore()
{
// Arrange
@@ -89,7 +93,8 @@ public sealed class CvssV4EngineTests
scores.EffectiveScoreType.Should().Be(EffectiveScoreType.Threat);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithProofOfConceptThreat_ReducesScore()
{
// Arrange
@@ -105,7 +110,8 @@ public sealed class CvssV4EngineTests
scores.ThreatScore.Value.Should().BeGreaterThan(9.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithUnreportedThreat_ReducesScoreMore()
{
// Arrange
@@ -120,7 +126,8 @@ public sealed class CvssV4EngineTests
scores.ThreatScore!.Value.Should().BeLessThan(9.5); // Unreported = 0.91 multiplier
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithNotDefinedThreat_ReturnsOnlyBaseScore()
{
// Arrange
@@ -139,7 +146,8 @@ public sealed class CvssV4EngineTests
#region Environmental Score Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithHighSecurityRequirements_IncreasesScore()
{
// Arrange
@@ -160,7 +168,8 @@ public sealed class CvssV4EngineTests
scoresWithEnv.EnvironmentalScore!.Value.Should().BeGreaterThan(scoresWithoutEnv.BaseScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithLowSecurityRequirements_DecreasesScore()
{
// Arrange
@@ -181,7 +190,8 @@ public sealed class CvssV4EngineTests
scoresWithEnv.EnvironmentalScore!.Value.Should().BeLessThan(scoresWithoutEnv.BaseScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithModifiedMetrics_AppliesModifications()
{
// Arrange - Start with network-based vuln, modify to local
@@ -204,7 +214,8 @@ public sealed class CvssV4EngineTests
#region Full Score Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_WithAllMetrics_ReturnsFullScore()
{
// Arrange
@@ -227,7 +238,8 @@ public sealed class CvssV4EngineTests
#region Vector String Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildVectorString_BaseOnly_ReturnsCorrectFormat()
{
// Arrange
@@ -251,7 +263,8 @@ public sealed class CvssV4EngineTests
vector.Should().Contain("SA:H");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildVectorString_WithThreat_IncludesThreatMetric()
{
// Arrange
@@ -265,7 +278,8 @@ public sealed class CvssV4EngineTests
vector.Should().Contain("E:A");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseVector_ValidVector_ReturnsCorrectMetrics()
{
// Arrange
@@ -288,7 +302,8 @@ public sealed class CvssV4EngineTests
result.BaseMetrics.SubsequentSystemAvailability.Should().Be(ImpactMetricValue.High);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseVector_WithThreat_ParsesThreatMetric()
{
// Arrange
@@ -302,7 +317,8 @@ public sealed class CvssV4EngineTests
result.ThreatMetrics!.ExploitMaturity.Should().Be(ExploitMaturity.Attacked);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseVector_InvalidPrefix_ThrowsArgumentException()
{
// Arrange
@@ -313,7 +329,8 @@ public sealed class CvssV4EngineTests
.Should().Throw<ArgumentException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseVector_MissingMetric_ThrowsArgumentException()
{
// Arrange - Missing AV metric
@@ -328,7 +345,8 @@ public sealed class CvssV4EngineTests
#region Severity Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0.0, CvssSeverity.None)]
[InlineData(0.1, CvssSeverity.Low)]
[InlineData(3.9, CvssSeverity.Low)]
@@ -347,7 +365,8 @@ public sealed class CvssV4EngineTests
severity.Should().Be(expected);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetSeverity_CustomThresholds_UsesCustomValues()
{
// Arrange
@@ -370,7 +389,8 @@ public sealed class CvssV4EngineTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeScores_SameInput_ReturnsSameOutput()
{
// Arrange
@@ -387,7 +407,8 @@ public sealed class CvssV4EngineTests
scores1.EffectiveScore.Should().Be(scores3.EffectiveScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildVectorString_SameInput_ReturnsSameOutput()
{
// Arrange
@@ -401,7 +422,8 @@ public sealed class CvssV4EngineTests
vector1.Should().Be(vector2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Roundtrip_BuildAndParse_PreservesMetrics()
{
// Arrange
@@ -429,7 +451,8 @@ public sealed class CvssV4EngineTests
/// <summary>
/// Tests using sample vectors from FIRST CVSS v4.0 examples.
/// </summary>
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", 10.0)]
[InlineData("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", 9.4)]
[InlineData("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N", 6.8)]
@@ -438,6 +461,7 @@ public sealed class CvssV4EngineTests
// Arrange
var metricSet = _engine.ParseVector(vector);
using StellaOps.TestKit;
// Act
var scores = _engine.ComputeScores(metricSet.BaseMetrics);

View File

@@ -2,11 +2,13 @@ using FluentAssertions;
using StellaOps.Policy.Scoring.Engine;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
public class CvssVectorInteropTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "CVSS:4.0/AV:N/AC:L/PR:N/UI:N/VC:H/VI:H/VA:H")]
[InlineData("CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", "CVSS:4.0/AV:L/AC:H/PR:H/UI:R/VC:L/VI:L/VA:L")]
public void ConvertV31ToV4_ProducesDeterministicVector(string v31, string expectedPrefix)

View File

@@ -4,6 +4,7 @@ using StellaOps.Policy.Scoring.Engine;
using Xunit;
using Xunit.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
/// <summary>
@@ -31,7 +32,8 @@ public sealed class MacroVectorLookupTests
#region Completeness Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LookupTable_ContainsAtLeast324Entries()
{
// Assert - The lookup table may contain more entries than the theoretical 324
@@ -40,7 +42,8 @@ public sealed class MacroVectorLookupTests
MacroVectorLookup.EntryCount.Should().BeGreaterThanOrEqualTo(324);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllMacroVectorCombinations_ExistInLookupTable()
{
// Arrange
@@ -68,7 +71,8 @@ public sealed class MacroVectorLookupTests
missing.Should().BeEmpty($"All combinations should have precise scores. Missing: {string.Join(", ", missing.Take(10))}...");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllMacroVectorCombinations_ReturnValidScores()
{
// Arrange & Act
@@ -98,7 +102,8 @@ public sealed class MacroVectorLookupTests
#region Boundary Value Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("000000", 10.0)] // Maximum severity
[InlineData("222222", 0.0)] // Minimum severity (or very low)
public void BoundaryMacroVectors_ReturnExpectedScores(string macroVector, double expectedScore)
@@ -110,7 +115,8 @@ public sealed class MacroVectorLookupTests
score.Should().Be(expectedScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MaximumSeverityMacroVector_ReturnsScore10()
{
// Arrange
@@ -123,7 +129,8 @@ public sealed class MacroVectorLookupTests
score.Should().Be(10.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MinimumSeverityMacroVector_ReturnsVeryLowScore()
{
// Arrange
@@ -136,7 +143,8 @@ public sealed class MacroVectorLookupTests
score.Should().BeLessThanOrEqualTo(1.0);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("000000", "100000")] // EQ1 increase reduces score
[InlineData("000000", "010000")] // EQ2 increase reduces score
[InlineData("000000", "001000")] // EQ3 increase reduces score
@@ -158,7 +166,8 @@ public sealed class MacroVectorLookupTests
#region Score Progression Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreProgression_EQ1Increase_ReducesScoreMonotonically()
{
// Test that for fixed EQ2-EQ6, increasing EQ1 reduces score
@@ -181,7 +190,8 @@ public sealed class MacroVectorLookupTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreProgression_EQ2Increase_ReducesScoreMonotonically()
{
// Test that for fixed EQ1, EQ3-EQ6, increasing EQ2 reduces score
@@ -205,7 +215,8 @@ public sealed class MacroVectorLookupTests
#region Invalid Input Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("12345")] // Too short
@@ -219,7 +230,8 @@ public sealed class MacroVectorLookupTests
score.Should().Be(0.0);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("300000")] // EQ1 out of range
[InlineData("020000")] // Valid but testing fallback path
[InlineData("ABCDEF")] // Non-numeric
@@ -234,7 +246,8 @@ public sealed class MacroVectorLookupTests
score.Should().BeLessThanOrEqualTo(10.0);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("300000")] // EQ1 = 3 (invalid)
[InlineData("030000")] // EQ2 = 3 (invalid, max is 1)
[InlineData("003000")] // EQ3 = 3 (invalid)
@@ -255,7 +268,8 @@ public sealed class MacroVectorLookupTests
#region HasPreciseScore Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("000000", true)]
[InlineData("111111", true)]
[InlineData("222222", true)]
@@ -270,7 +284,8 @@ public sealed class MacroVectorLookupTests
result.Should().Be(expected);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("300000")] // Invalid EQ1
[InlineData("ABCDEF")] // Non-numeric
[InlineData("12345")] // Too short
@@ -287,7 +302,8 @@ public sealed class MacroVectorLookupTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetBaseScore_SameInput_ReturnsSameOutput()
{
// Arrange
@@ -303,7 +319,8 @@ public sealed class MacroVectorLookupTests
score2.Should().Be(score3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllScores_AreRoundedToOneDecimal()
{
// Act & Assert
@@ -326,7 +343,8 @@ public sealed class MacroVectorLookupTests
#region Performance Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetBaseScore_10000Lookups_CompletesInUnderOneMillisecond()
{
// Arrange
@@ -356,7 +374,8 @@ public sealed class MacroVectorLookupTests
sw.Elapsed.TotalMilliseconds.Should().BeLessThan(100, "10000 lookups should complete in under 100ms");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllCombinations_LookupPerformance()
{
// Arrange
@@ -383,7 +402,8 @@ public sealed class MacroVectorLookupTests
/// Tests against FIRST CVSS v4.0 calculator reference scores.
/// These scores are verified against the official calculator.
/// </summary>
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("000000", 10.0)] // Max severity
[InlineData("000001", 9.7)] // One step from max
[InlineData("000010", 9.3)]
@@ -412,7 +432,8 @@ public sealed class MacroVectorLookupTests
#region Score Distribution Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreDistribution_HasReasonableSpread()
{
// Arrange & Act
@@ -437,7 +458,8 @@ public sealed class MacroVectorLookupTests
uniqueScores.Should().BeGreaterThan(50, "Should have diverse score values");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreDistribution_ByCategory()
{
// Arrange & Act

View File

@@ -7,6 +7,7 @@ using StellaOps.Policy.Scoring.Receipts;
using StellaOps.Policy.Scoring.Tests.Fakes;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Scoring.Tests;
public sealed class ReceiptBuilderTests
@@ -14,7 +15,8 @@ public sealed class ReceiptBuilderTests
private readonly ICvssV4Engine _engine = new CvssV4Engine();
private readonly InMemoryReceiptRepository _repository = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_ComputesDeterministicHashAndStoresReceipt()
{
// Arrange
@@ -72,7 +74,8 @@ public sealed class ReceiptBuilderTests
_repository.Contains(receipt1.ReceiptId).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_InputHashIgnoresPropertyOrder()
{
var policy = new CvssPolicy
@@ -140,7 +143,8 @@ public sealed class ReceiptBuilderTests
r1.InputHash.Should().Be(r2.InputHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithSigningKey_AttachesDsseReference()
{
// Arrange
@@ -188,7 +192,8 @@ public sealed class ReceiptBuilderTests
receipt.AttestationRefs[0].Should().StartWith("dsse:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_EnforcesEvidenceRequirements()
{
// Arrange

View File

@@ -25,5 +25,6 @@
<ItemGroup>
<ProjectReference Include="../../StellaOps.Policy.Scoring/StellaOps.Policy.Scoring.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -62,7 +63,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsEvaluationRun()
{
// Arrange
@@ -88,7 +90,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
fetched.Status.Should().Be(EvaluationStatus.Pending);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByProjectId_ReturnsProjectEvaluations()
{
// Arrange
@@ -103,7 +106,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
runs[0].ProjectId.Should().Be("project-abc");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByArtifactId_ReturnsArtifactEvaluations()
{
// Arrange
@@ -125,7 +129,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
runs[0].ArtifactId.Should().Be(artifactId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByStatus_ReturnsRunsWithStatus()
{
// Arrange
@@ -149,7 +154,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
pendingRuns[0].ProjectId.Should().Be("project-1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRecent_ReturnsRecentEvaluations()
{
// Arrange
@@ -163,7 +169,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
recentRuns.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MarkStarted_UpdatesStatusAndStartedAt()
{
// Arrange
@@ -180,7 +187,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
fetched.StartedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MarkCompleted_UpdatesAllCompletionFields()
{
// Arrange
@@ -213,7 +221,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
fetched.CompletedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MarkFailed_SetsErrorMessage()
{
// Arrange
@@ -231,7 +240,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
fetched.ErrorMessage.Should().Be("Policy engine timeout");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetStats_ReturnsCorrectStatistics()
{
// Arrange

View File

@@ -7,6 +7,7 @@ using StellaOps.Policy.Exceptions.Repositories;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
@@ -32,7 +33,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_ShouldPersistExceptionAndCreateEvent()
{
// Arrange
@@ -53,7 +55,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[0].ActorId.Should().Be("test-actor");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_WhenExists_ShouldReturnException()
{
// Arrange
@@ -73,7 +76,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
fetched.ReasonCode.Should().Be(ExceptionReason.AcceptedRisk);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_WhenNotExists_ShouldReturnNull()
{
// Act
@@ -83,7 +87,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
fetched.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_ShouldIncrementVersionAndCreateEvent()
{
// Arrange
@@ -116,7 +121,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[1].EventType.Should().Be(ExceptionEventType.Approved);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_WithConcurrencyConflict_ShouldThrow()
{
// Arrange
@@ -135,7 +141,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
_repository.UpdateAsync(staleUpdate, ExceptionEventType.Updated, "updater"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_ShouldFilterByStatus()
{
// Arrange
@@ -156,7 +163,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-FILTER-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_ShouldFilterByType()
{
// Arrange
@@ -175,7 +183,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-TYPE-002");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_ShouldFilterByVulnerabilityId()
{
// Arrange
@@ -194,7 +203,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-VID-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_ShouldSupportPagination()
{
// Arrange
@@ -211,7 +221,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveByScopeAsync_ShouldMatchVulnerabilityId()
{
// Arrange
@@ -228,7 +239,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-SCOPE-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveByScopeAsync_ShouldExcludeInactiveExceptions()
{
// Arrange
@@ -247,7 +259,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetExpiringAsync_ShouldReturnExceptionsExpiringSoon()
{
// Arrange
@@ -269,7 +282,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-EXPIRING-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetExpiredActiveAsync_ShouldReturnExpiredButActiveExceptions()
{
// Arrange - Create with past expiry
@@ -287,7 +301,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-EXPIRED-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetHistoryAsync_ShouldReturnEventsInOrder()
{
// Arrange
@@ -325,7 +340,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[2].SequenceNumber.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetCountsAsync_ShouldReturnCorrectCounts()
{
// Arrange
@@ -347,7 +363,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
counts.ExpiringSoon.Should().BeGreaterOrEqualTo(1); // At least the one expiring in 3 days
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithMetadata_ShouldPersistCorrectly()
{
// Arrange
@@ -369,7 +386,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
fetched.Metadata["ticket"].Should().Be("SEC-123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithEvidenceRefs_ShouldPersistCorrectly()
{
// Arrange
@@ -390,7 +408,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
fetched.EvidenceRefs.Should().Contain("https://evidence.example.com/doc1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithCompensatingControls_ShouldPersistCorrectly()
{
// Arrange
@@ -409,7 +428,8 @@ public sealed class ExceptionObjectRepositoryTests : IAsyncLifetime
fetched.CompensatingControls.Should().Contain("WAF blocking malicious patterns");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithEnvironments_ShouldPersistCorrectly()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -27,7 +28,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsException()
{
// Arrange
@@ -55,7 +57,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched.Status.Should().Be(ExceptionStatus.Active);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByName_ReturnsCorrectException()
{
// Arrange
@@ -70,7 +73,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(exception.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_ReturnsAllExceptionsForTenant()
{
// Arrange
@@ -87,7 +91,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
exceptions.Select(e => e.Name).Should().Contain(["exception1", "exception2"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_FiltersByStatus()
{
// Arrange
@@ -111,7 +116,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
activeExceptions[0].Name.Should().Be("active");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveForProject_ReturnsProjectExceptions()
{
// Arrange
@@ -144,7 +150,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
exceptions[0].Name.Should().Be("project-exception");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveForRule_ReturnsRuleExceptions()
{
// Arrange
@@ -167,7 +174,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
exceptions[0].Name.Should().Be("rule-exception");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Update_ModifiesException()
{
// Arrange
@@ -192,7 +200,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched.Description.Should().Be("Updated description");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Approve_SetsApprovalDetails()
{
// Arrange
@@ -209,7 +218,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched.ApprovedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Revoke_SetsRevokedStatusAndDetails()
{
// Arrange
@@ -227,7 +237,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched.RevokedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Expire_ExpiresOldExceptions()
{
// Arrange - Create an exception that expires in the past
@@ -251,7 +262,8 @@ public sealed class ExceptionRepositoryTests : IAsyncLifetime
fetched!.Status.Should().Be(ExceptionStatus.Expired);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesException()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -29,7 +30,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsPack()
{
// Arrange
@@ -54,7 +56,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
fetched.DisplayName.Should().Be("Security Baseline Pack");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByName_ReturnsCorrectPack()
{
// Arrange
@@ -69,7 +72,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(pack.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_ReturnsAllPacksForTenant()
{
// Arrange
@@ -86,7 +90,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
packs.Select(p => p.Name).Should().Contain(["pack1", "pack2"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_ExcludesDeprecated()
{
// Arrange
@@ -109,7 +114,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
packs[0].Name.Should().Be("active");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBuiltin_ReturnsOnlyBuiltinPacks()
{
// Arrange
@@ -132,7 +138,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
builtinPacks[0].Name.Should().Be("builtin");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Update_ModifiesPack()
{
// Arrange
@@ -157,7 +164,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
fetched.Description.Should().Be("Updated description");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetActiveVersion_UpdatesActiveVersion()
{
// Arrange
@@ -174,7 +182,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
fetched!.ActiveVersion.Should().Be(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishAndActivateVersions_FollowsWorkflow()
{
// Arrange
@@ -208,7 +217,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
finalPack!.ActiveVersion.Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Deprecate_MarksParkAsDeprecated()
{
// Arrange
@@ -224,7 +234,8 @@ public sealed class PackRepositoryTests : IAsyncLifetime
fetched!.IsDeprecated.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesPack()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
@@ -37,7 +38,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_CreateUpdateActivate_MaintainsVersionIntegrity()
{
// Arrange - Create initial pack
@@ -70,7 +72,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
afterV3!.ActiveVersion.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_RollbackVersion_RestoresPreviousVersion()
{
// Arrange - Create pack at version 3
@@ -93,7 +96,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
afterRollback!.ActiveVersion.Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_MultiplePacksDifferentVersions_Isolated()
{
// Arrange - Create multiple packs with different versions
@@ -125,7 +129,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
fetchedPack2!.ActiveVersion.Should().Be(5);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_DeprecatedPackVersionStillReadable()
{
// Arrange - Create and deprecate pack
@@ -149,7 +154,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
deprecated.ActiveVersion.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_ConcurrentVersionUpdates_LastWriteWins()
{
// Arrange - Create pack
@@ -177,7 +183,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
final!.ActiveVersion.Should().BeOneOf(2, 3, 4);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_DeterministicOrdering_VersionsReturnConsistently()
{
// Arrange - Create multiple packs
@@ -208,7 +215,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
names2.Should().Equal(names3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_UpdateTimestampProgresses_OnVersionChange()
{
// Arrange
@@ -234,7 +242,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
updated!.UpdatedAt.Should().BeOnOrAfter(initialUpdatedAt);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_ZeroVersionAllowed_AsInitialState()
{
// Arrange - Create pack with version 0 (no active version)
@@ -255,7 +264,8 @@ public sealed class PackVersioningWorkflowTests : IAsyncLifetime
fetched!.ActiveVersion.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionWorkflow_BuiltinPackVersioning_WorksLikeCustomPacks()
{
// Arrange - Create builtin pack

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -27,7 +28,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Create_ReturnsGeneratedId()
{
// Arrange
@@ -47,7 +49,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
id.Should().BeGreaterThan(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsAuditEntriesOrderedByCreatedAtDesc()
{
// Arrange
@@ -65,7 +68,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
audits[0].Action.Should().Be("action2"); // Most recent first
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByResource_ReturnsResourceAudits()
{
// Arrange
@@ -87,7 +91,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
audits[0].ResourceId.Should().Be(resourceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByResource_WithoutResourceId_ReturnsAllOfType()
{
// Arrange
@@ -113,7 +118,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
audits.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByCorrelationId_ReturnsCorrelatedAudits()
{
// Arrange
@@ -143,7 +149,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
audits.Should().AllSatisfy(a => a.CorrelationId.Should().Be(correlationId));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Create_StoresJsonbValues()
{
// Arrange
@@ -166,7 +173,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
audits[0].NewValue.Should().Contain("8.0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteOld_RemovesOldAudits()
{
// Arrange

View File

@@ -10,6 +10,8 @@ using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
@@ -49,7 +51,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
await _dataSource.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecordAsync_ShouldPersist()
{
// Arrange
@@ -64,7 +67,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
result.FindingId.Should().Be("FIND-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecordBatchAsync_ShouldPersistMultiple()
{
// Arrange
@@ -82,7 +86,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
result.Should().HaveCount(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecordBatchAsync_EmptyReturnsEmpty()
{
// Act
@@ -92,7 +97,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
result.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByExceptionIdAsync_ReturnsMatches()
{
// Arrange
@@ -108,7 +114,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
results.Should().AllSatisfy(r => r.ExceptionId.Should().Be("EXC-G1"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFindingIdAsync_ReturnsMatches()
{
// Arrange
@@ -124,7 +131,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
results.Should().AllSatisfy(r => r.FindingId.Should().Be("FIND-G1"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByVulnerabilityIdAsync_ReturnsMatches()
{
// Arrange
@@ -140,7 +148,8 @@ public sealed class PostgresExceptionApplicationRepositoryTests : IAsyncLifetime
results.Should().AllSatisfy(r => r.VulnerabilityId.Should().Be("CVE-2024-1234"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CountAsync_WithFilter_ReturnsFiltered()
{
// Arrange

View File

@@ -7,6 +7,7 @@ using StellaOps.Policy.Exceptions.Repositories;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
@@ -35,7 +36,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region Create Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithValidException_PersistsException()
{
// Arrange
@@ -50,7 +52,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
created.Version.Should().Be(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_PersistsRecheckTrackingFields()
{
// Arrange
@@ -89,7 +92,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
fetched.LastRecheckAt.Should().BeCloseTo(exception.LastRecheckAt!.Value, TimeSpan.FromSeconds(1));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_RecordsCreatedEvent()
{
// Arrange
@@ -106,7 +110,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[0].NewStatus.Should().Be(ExceptionStatus.Proposed);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithClientInfo_IncludesInEvent()
{
// Arrange
@@ -120,7 +125,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[0].ClientInfo.Should().Be("192.168.1.1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_WithWrongVersion_ThrowsException()
{
// Arrange
@@ -135,7 +141,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region GetById Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_WithExistingException_ReturnsException()
{
// Arrange
@@ -152,7 +159,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
fetched.Status.Should().Be(ExceptionStatus.Proposed);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_WithNonExistingException_ReturnsNull()
{
// Act
@@ -166,7 +174,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region Update Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_WithValidVersion_UpdatesException()
{
// Arrange
@@ -191,7 +200,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
result.Status.Should().Be(ExceptionStatus.Approved);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_RecordsEvent()
{
// Arrange
@@ -218,7 +228,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[1].NewStatus.Should().Be(ExceptionStatus.Approved);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_WithWrongVersion_ThrowsConcurrencyException()
{
// Arrange
@@ -242,7 +253,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region Query Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_FiltersByStatus()
{
// Arrange
@@ -265,7 +277,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be(proposed.ExceptionId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_FiltersByVulnerabilityId()
{
// Arrange
@@ -284,7 +297,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
results[0].Scope.VulnerabilityId.Should().Be("CVE-2024-001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByFilterAsync_SupportsPagination()
{
// Arrange
@@ -305,7 +319,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
page2.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveByScopeAsync_FindsMatchingActiveExceptions()
{
// Arrange
@@ -323,7 +338,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
results.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveByScopeAsync_ExcludesExpiredExceptions()
{
// Arrange
@@ -342,7 +358,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetExpiringAsync_FindsExceptionsWithinHorizon()
{
// Arrange
@@ -370,7 +387,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
results[0].ExceptionId.Should().Be("EXC-EXPIRING");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetExpiredActiveAsync_FindsExpiredActiveExceptions()
{
// Arrange
@@ -402,7 +420,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region History and Counts Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetHistoryAsync_ReturnsChronologicalEvents()
{
// Arrange
@@ -440,7 +459,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
history.Events[2].SequenceNumber.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetHistoryAsync_ForNonExistent_ReturnsEmptyHistory()
{
// Act
@@ -451,7 +471,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
history.Events.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetCountsAsync_ReturnsCorrectCounts()
{
// Arrange
@@ -489,7 +510,8 @@ public sealed class PostgresExceptionObjectRepositoryTests : IAsyncLifetime
#region Concurrent Update Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConcurrentUpdates_FailsWithConcurrencyException()
{
// Arrange

View File

@@ -9,6 +9,7 @@ using StellaOps.Policy.Storage.Postgres.Tests;
using StellaOps.Policy.Storage.Postgres;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -31,7 +32,8 @@ public sealed class PostgresReceiptRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SaveAndGet_RoundTripsReceipt()
{
var receipt = CreateReceipt(_tenantId);

View File

@@ -5,6 +5,8 @@ using Npgsql;
using StellaOps.Policy.Storage.Postgres;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -26,7 +28,8 @@ public sealed class RecheckEvidenceMigrationTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Migration_CreatesRecheckAndEvidenceTables()
{
await using var connection = await _dataSource.OpenConnectionAsync("default", "reader", CancellationToken.None);

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -27,7 +28,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsRiskProfile()
{
// Arrange
@@ -56,7 +58,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched.IsActive.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActiveByName_ReturnsActiveVersion()
{
// Arrange
@@ -88,7 +91,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched.IsActive.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_ReturnsProfilesForTenant()
{
// Arrange
@@ -105,7 +109,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
profiles.Select(p => p.Name).Should().Contain(["profile1", "profile2"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAll_FiltersActiveOnly()
{
// Arrange
@@ -128,7 +133,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
activeProfiles[0].Name.Should().Be("active");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetVersionsByName_ReturnsAllVersions()
{
// Arrange
@@ -159,7 +165,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
versions.Select(v => v.Version).Should().Contain([1, 2]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Update_ModifiesProfile()
{
// Arrange
@@ -185,7 +192,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched.Thresholds.Should().Contain("8.0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateVersion_CreatesNewVersion()
{
// Arrange
@@ -211,7 +219,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
originalAfter!.IsActive.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Activate_SetsProfileAsActive()
{
// Arrange
@@ -233,7 +242,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched!.IsActive.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Deactivate_SetsProfileAsInactive()
{
// Arrange
@@ -249,7 +259,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched!.IsActive.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesProfile()
{
// Arrange
@@ -265,7 +276,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
fetched.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateVersion_HistoryRemainsQueryableAndOrdered()
{
// Arrange
@@ -303,7 +315,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
active!.Version.Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Activate_RevertsToPriorVersionAndDeactivatesCurrent()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
/// <summary>
@@ -36,7 +37,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_CreateMultipleVersions_AllVersionsRetrievable()
{
// Arrange - Create profile with multiple versions
@@ -67,7 +69,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
allVersions.Select(p => p.Version).Should().BeEquivalentTo([1, 2, 3, 4, 5]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_OnlyOneActivePerName_Enforced()
{
// Arrange - Create profile versions where only one should be active
@@ -102,7 +105,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
active.IsActive.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_ActivateOlderVersion_DeactivatesNewer()
{
// Arrange - Create two versions, v2 active
@@ -136,7 +140,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
fetchedV1!.IsActive.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_CreateVersion_IncreasesVersionNumber()
{
// Arrange - Create initial profile
@@ -171,7 +176,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
created.Thresholds.Should().Contain("8.5");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_GetVersionsByName_OrderedByVersion()
{
// Arrange - Create versions out of order
@@ -212,7 +218,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
versions[2].Version.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_DeterministicOrdering_ConsistentResults()
{
// Arrange - Create multiple profiles with multiple versions
@@ -245,7 +252,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
keys2.Should().Equal(keys3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_ThresholdsAndWeights_PreservedAcrossVersions()
{
// Arrange
@@ -293,7 +301,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
fetchedV2.ScoringWeights.Should().Be(v2Weights);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_DeleteOldVersion_NewerVersionsRemain()
{
// Arrange
@@ -328,7 +337,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
remaining[0].Version.Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_MultiTenant_VersionsIsolated()
{
// Arrange - Create same profile name in different tenants
@@ -367,7 +377,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
tenant2Profile.Thresholds.Should().Contain("\"tenant\": \"2\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_DeactivateActiveVersion_NoActiveRemains()
{
// Arrange
@@ -396,7 +407,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
fetched!.IsActive.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_UpdateDescription_DoesNotAffectVersion()
{
// Arrange
@@ -432,7 +444,8 @@ public sealed class RiskProfileVersionHistoryTests : IAsyncLifetime
fetched.Description.Should().Be("Updated description");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VersionHistory_TimestampsTracked_OnCreationAndUpdate()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -67,7 +68,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsRule()
{
// Arrange
@@ -96,7 +98,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
fetched.Severity.Should().Be(RuleSeverity.High);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByName_ReturnsCorrectRule()
{
// Arrange
@@ -111,7 +114,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(rule.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateBatch_CreatesMultipleRules()
{
// Arrange
@@ -129,7 +133,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
count.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByPackVersionId_ReturnsAllRulesForVersion()
{
// Arrange
@@ -146,7 +151,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
rules.Select(r => r.Name).Should().Contain(["rule1", "rule2"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySeverity_ReturnsRulesWithSeverity()
{
// Arrange
@@ -179,7 +185,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
criticalRules[0].Name.Should().Be("critical-rule");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByCategory_ReturnsRulesInCategory()
{
// Arrange
@@ -212,7 +219,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
securityRules[0].Name.Should().Be("security-rule");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByTag_ReturnsRulesWithTag()
{
// Arrange
@@ -245,7 +253,8 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
containerRules[0].Name.Should().Be("container-rule");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CountByPackVersionId_ReturnsCorrectCount()
{
// Arrange

View File

@@ -6,6 +6,8 @@ using StellaOps.Policy.Unknowns.Models;
using StellaOps.Policy.Unknowns.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
[Collection(PolicyPostgresCollection.Name)]
@@ -27,7 +29,8 @@ public sealed class UnknownsRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public async Task DisposeAsync() => await _dataSource.DisposeAsync();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetById_RoundTripsReasonCodeAndEvidence()
{
await using var connection = await _dataSource.OpenConnectionAsync(_tenantId.ToString());
@@ -56,7 +59,8 @@ public sealed class UnknownsRepositoryTests : IAsyncLifetime
fetched.Assumptions.Should().ContainSingle("assume-dynamic-imports");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateAsync_PersistsReasonCodeAndAssumptions()
{
await using var connection = await _dataSource.OpenConnectionAsync(_tenantId.ToString());

View File

@@ -5,11 +5,14 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public sealed class PolicyBinderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Bind_ValidYaml_ReturnsSuccess()
{
const string yaml = """
@@ -29,7 +32,8 @@ public sealed class PolicyBinderTests
Assert.Empty(result.Issues);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Bind_ExceptionsConfigured_ParsesDefinitions()
{
const string yaml = """
@@ -78,7 +82,8 @@ public sealed class PolicyBinderTests
Assert.True(routing[0].RequireMfa);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Bind_ExceptionDowngradeMissingSeverity_ReturnsError()
{
const string yaml = """
@@ -99,7 +104,8 @@ public sealed class PolicyBinderTests
Assert.Contains(result.Issues, issue => issue.Code == "policy.exceptions.effect.downgrade.missingSeverity");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Bind_InvalidSeverity_ReturnsError()
{
const string yaml = """
@@ -116,7 +122,8 @@ public sealed class PolicyBinderTests
Assert.Contains(result.Issues, issue => issue.Code == "policy.severity.invalid");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Cli_StrictMode_FailsOnWarnings()
{
const string yaml = """

View File

@@ -1,11 +1,13 @@
using System.Collections.Immutable;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public sealed class PolicyEvaluationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvaluateFinding_AppliesTrustAndReachabilityWeights()
{
var action = new PolicyAction(PolicyActionType.Block, null, null, null, false);
@@ -50,7 +52,8 @@ public sealed class PolicyEvaluationTests
Assert.Equal("BlockMedium", explanation.RuleName);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvaluateFinding_QuietWithRequireVexAppliesQuietPenalty()
{
var ignoreOptions = new PolicyIgnoreOptions(null, null);
@@ -99,7 +102,8 @@ public sealed class PolicyEvaluationTests
Assert.Equal(PolicyVerdictStatus.Ignored, explanation!.Decision);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvaluateFinding_UnknownSeverityComputesConfidence()
{
var action = new PolicyAction(PolicyActionType.Block, null, null, null, false);

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Time.Testing;
using Xunit;
using Xunit.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public sealed class PolicyPreviewServiceTests
@@ -18,7 +19,8 @@ public sealed class PolicyPreviewServiceTests
_output = output ?? throw new ArgumentNullException(nameof(output));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PreviewAsync_ComputesDiffs_ForBlockingRule()
{
const string yaml = """
@@ -63,7 +65,8 @@ rules:
Assert.Equal(PolicyVerdictStatus.Pass, response.Diffs.First(diff => diff.Projected.FindingId == "finding-2").Projected.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PreviewAsync_UsesProposedPolicy_WhenProvided()
{
const string yaml = """
@@ -103,7 +106,8 @@ rules:
Assert.Equal(1, response.ChangedCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PreviewAsync_ReturnsIssues_WhenPolicyInvalid()
{
var snapshotRepo = new InMemoryPolicySnapshotRepository();
@@ -125,7 +129,8 @@ rules:
Assert.NotEmpty(response.Issues);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PreviewAsync_QuietWithoutVexDowngradesToWarn()
{
const string yaml = """

View File

@@ -2,11 +2,14 @@ using System;
using System.IO;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public sealed class PolicyScoringConfigTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadDefaultReturnsConfig()
{
var config = PolicyScoringConfigBinder.LoadDefault();
@@ -21,7 +24,8 @@ public sealed class PolicyScoringConfigTests
Assert.Equal("high", config.UnknownConfidence.Bands[0].Name);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BindRejectsEmptyContent()
{
var result = PolicyScoringConfigBinder.Bind(string.Empty, PolicyDocumentFormat.Json);
@@ -29,7 +33,8 @@ public sealed class PolicyScoringConfigTests
Assert.NotEmpty(result.Issues);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BindRejectsInvalidSchema()
{
const string json = """
@@ -47,7 +52,8 @@ public sealed class PolicyScoringConfigTests
Assert.Null(result.Config);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultResourceDigestMatchesGolden()
{
var assembly = typeof(PolicyScoringConfig).Assembly;

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public sealed class PolicySnapshotStoreTests
@@ -17,7 +18,8 @@ rules:
action: block
""";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SaveAsync_CreatesNewSnapshotAndAuditEntry()
{
var snapshotRepo = new InMemoryPolicySnapshotRepository();
@@ -47,7 +49,8 @@ rules:
Assert.Equal("rev-1", audits[0].RevisionId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SaveAsync_DoesNotCreateNewRevisionWhenDigestUnchanged()
{
var snapshotRepo = new InMemoryPolicySnapshotRepository();
@@ -72,7 +75,8 @@ rules:
Assert.Single(audits);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SaveAsync_ReturnsFailureWhenValidationFails()
{
var snapshotRepo = new InMemoryPolicySnapshotRepository();

View File

@@ -4,11 +4,14 @@ using System.Threading.Tasks;
using StellaOps.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public class PolicyValidationCliTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunAsync_EmitsCanonicalDigest_OnValidPolicy()
{
var tmp = Path.GetTempFileName();

View File

@@ -1,11 +1,13 @@
using StellaOps.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public class SplCanonicalizerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_SortsStatementsActionsAndConditions()
{
const string input = """
@@ -54,7 +56,8 @@ public class SplCanonicalizerTests
Assert.Equal(expected, canonical);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeDigest_IgnoresOrderingNoise()
{
const string versionA = """
@@ -71,7 +74,8 @@ public class SplCanonicalizerTests
Assert.Equal(hashA, hashB);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeDigest_DetectsContentChange()
{
const string baseDoc = """

View File

@@ -2,11 +2,14 @@ using System.Text.Json;
using StellaOps.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public class SplLayeringEngineTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Merge_ReplacesStatementsById_AndKeepsBaseOnes()
{
const string baseDoc = """
@@ -24,7 +27,8 @@ public class SplLayeringEngineTests
Assert.Equal(expected, merged);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Merge_MergesMetadataAndDefaultEffect()
{
const string baseDoc = """
@@ -42,7 +46,8 @@ public class SplLayeringEngineTests
Assert.Equal(expected, merged);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Merge_PreservesUnknownTopLevelAndSpecFields()
{
const string baseDoc = """

View File

@@ -2,11 +2,13 @@ using System.Collections.Immutable;
using StellaOps.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public class SplMigrationToolTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ToSplPolicyJson_ConvertsRulesAndMetadata()
{
var rule = PolicyRule.Create(
@@ -44,7 +46,8 @@ public class SplMigrationToolTests
Assert.Equal(expected, spl);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ToSplPolicyJson_UsesOverlaySafeIdsAndAudits()
{
var rule = PolicyRule.Create(

View File

@@ -2,11 +2,14 @@ using System.Text.Json;
using StellaOps.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Tests;
public class SplSchemaResourceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_IncludesReachabilityAndExploitability()
{
var schema = SplSchemaResource.GetSchema();

View File

@@ -25,6 +25,7 @@
<ProjectReference Include="../../__Libraries/StellaOps.Policy/StellaOps.Policy.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Policy.Exceptions/StellaOps.Policy.Exceptions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,6 +8,7 @@
using FluentAssertions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.PolicyDsl.Tests;
/// <summary>
@@ -17,7 +18,8 @@ public class DslCompletionProviderTests
{
#region Catalog Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionCatalog_ReturnsNonNullCatalog()
{
// Act
@@ -27,7 +29,8 @@ public class DslCompletionProviderTests
catalog.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsScoreFields()
{
// Arrange
@@ -43,7 +46,8 @@ public class DslCompletionProviderTests
catalog.ScoreFields.Should().Contain(f => f.Label == "reachability");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsScoreBuckets()
{
// Arrange
@@ -58,7 +62,8 @@ public class DslCompletionProviderTests
catalog.ScoreBuckets.Should().Contain(b => b.Label == "Watchlist");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsScoreFlags()
{
// Arrange
@@ -73,7 +78,8 @@ public class DslCompletionProviderTests
catalog.ScoreFlags.Should().Contain(f => f.Label == "unreachable");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsAllDimensionAliases()
{
// Arrange
@@ -96,7 +102,8 @@ public class DslCompletionProviderTests
catalog.ScoreFields.Should().Contain(f => f.Label == "mitigation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsVexStatuses()
{
// Arrange
@@ -109,7 +116,8 @@ public class DslCompletionProviderTests
catalog.VexStatuses.Should().Contain(s => s.Label == "fixed");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_ContainsKeywordsAndFunctions()
{
// Arrange
@@ -132,7 +140,8 @@ public class DslCompletionProviderTests
#region Context-Based Completion Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_ScoreDot_ReturnsScoreFields()
{
// Arrange
@@ -150,7 +159,8 @@ public class DslCompletionProviderTests
DslCompletionProvider.GetCompletionCatalog().ScoreFields.Any(sf => sf.Label == c.Label));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_SbomDot_ReturnsSbomFields()
{
// Arrange
@@ -166,7 +176,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "version");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_AdvisoryDot_ReturnsAdvisoryFields()
{
// Arrange
@@ -182,7 +193,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "severity");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_VexDot_ReturnsVexFields()
{
// Arrange
@@ -198,7 +210,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "any");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_ScoreBucketEquals_ReturnsBuckets()
{
// Arrange
@@ -215,7 +228,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "Watchlist");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_ScoreBucketEqualsQuote_ReturnsBuckets()
{
// Arrange
@@ -229,7 +243,8 @@ public class DslCompletionProviderTests
completions.Should().HaveCount(4);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_ScoreFlagsContains_ReturnsFlags()
{
// Arrange
@@ -245,7 +260,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "vendor-na");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_StatusEquals_ReturnsVexStatuses()
{
// Arrange
@@ -261,7 +277,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "fixed");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_JustificationEquals_ReturnsJustifications()
{
// Arrange
@@ -276,7 +293,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "vulnerable_code_not_present");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_AfterThen_ReturnsActions()
{
// Arrange
@@ -292,7 +310,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "escalate");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_AfterElse_ReturnsActions()
{
// Arrange
@@ -307,7 +326,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "defer");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_EmptyContext_ReturnsAllTopLevel()
{
// Arrange
@@ -332,7 +352,8 @@ public class DslCompletionProviderTests
#region CompletionItem Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreValueField_HasCorrectDocumentation()
{
// Arrange
@@ -347,7 +368,8 @@ public class DslCompletionProviderTests
valueField.Kind.Should().Be(DslCompletionKind.Field);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreBucketField_HasCorrectDocumentation()
{
// Arrange
@@ -363,7 +385,8 @@ public class DslCompletionProviderTests
bucketField.Documentation.Should().Contain("Watchlist");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreFlags_AllHaveQuotedInsertText()
{
// Arrange
@@ -377,7 +400,8 @@ public class DslCompletionProviderTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreBuckets_AllHaveQuotedInsertText()
{
// Arrange
@@ -391,7 +415,8 @@ public class DslCompletionProviderTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SnippetCompletions_HaveSnippetFlag()
{
// Arrange
@@ -403,7 +428,8 @@ public class DslCompletionProviderTests
policyKeyword.InsertText.Should().Contain("${1:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SimpleFields_DoNotHaveSnippetFlag()
{
// Arrange
@@ -419,7 +445,8 @@ public class DslCompletionProviderTests
#region Edge Cases
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_NullContext_ThrowsArgumentNullException()
{
// Act & Assert
@@ -427,7 +454,8 @@ public class DslCompletionProviderTests
action.Should().Throw<ArgumentNullException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_CaseInsensitive_ScoreBucket()
{
// Arrange - mixed case
@@ -441,7 +469,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "ActNow");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetCompletionsForContext_MultipleContextsInLine_ReturnsCorrectCompletions()
{
// Arrange - score.value already used, now typing score.bucket
@@ -455,7 +484,8 @@ public class DslCompletionProviderTests
completions.Should().Contain(c => c.Label == "ActNow");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Catalog_IsSingleton()
{
// Act

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using StellaOps.PolicyDsl;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.PolicyDsl.Tests;
/// <summary>
@@ -11,7 +12,8 @@ public class PolicyCompilerTests
{
private readonly PolicyCompiler _compiler = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_MinimalPolicy_Succeeds()
{
// Arrange - rule name is an identifier, not a string; then block has no braces; := for assignment
@@ -38,7 +40,8 @@ public class PolicyCompilerTests
result.Checksum.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_WithMetadata_ParsesCorrectly()
{
// Arrange
@@ -66,7 +69,8 @@ public class PolicyCompilerTests
result.Document.Metadata.Should().ContainKey("author");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_WithProfile_ParsesCorrectly()
{
// Arrange
@@ -93,7 +97,8 @@ public class PolicyCompilerTests
result.Document.Profiles[0].Name.Should().Be("standard");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_EmptySource_ReturnsError()
{
// Arrange
@@ -107,7 +112,8 @@ public class PolicyCompilerTests
result.Diagnostics.Should().NotBeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_InvalidSyntax_ReturnsError()
{
// Arrange
@@ -123,7 +129,8 @@ public class PolicyCompilerTests
result.Success.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_SameSource_ProducesSameChecksum()
{
// Arrange
@@ -148,7 +155,8 @@ public class PolicyCompilerTests
result1.Checksum.Should().Be(result2.Checksum);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compile_DifferentSource_ProducesDifferentChecksum()
{
// Arrange

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using StellaOps.PolicyDsl;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.PolicyDsl.Tests;
/// <summary>
@@ -11,7 +12,8 @@ public class PolicyEngineTests
{
private readonly PolicyEngineFactory _factory = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_RuleMatches_ReturnsMatchedRules()
{
// Arrange
@@ -40,7 +42,8 @@ public class PolicyEngineTests
evalResult.PolicyChecksum.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_RuleDoesNotMatch_ExecutesElseBranch()
{
// Arrange
@@ -72,7 +75,8 @@ public class PolicyEngineTests
evalResult.Actions[0].WasElseBranch.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_MultipleRules_EvaluatesInPriorityOrder()
{
// Arrange
@@ -106,7 +110,8 @@ public class PolicyEngineTests
evalResult.MatchedRules[1].Should().Be("low_priority");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_WithAndCondition_MatchesWhenBothTrue()
{
// Arrange
@@ -135,7 +140,8 @@ public class PolicyEngineTests
evalResult.MatchedRules.Should().Contain("combined");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_WithOrCondition_MatchesWhenEitherTrue()
{
// Arrange
@@ -163,7 +169,8 @@ public class PolicyEngineTests
evalResult.MatchedRules.Should().Contain("either");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_WithNotCondition_InvertsResult()
{
// Arrange

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using StellaOps.PolicyDsl;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.PolicyDsl.Tests;
/// <summary>
@@ -9,7 +10,8 @@ namespace StellaOps.PolicyDsl.Tests;
/// </summary>
public class SignalContextTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithSignal_SetsSignalValue()
{
// Arrange & Act
@@ -21,7 +23,8 @@ public class SignalContextTests
context.GetSignal("test").Should().Be("value");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithFlag_SetsBooleanSignal()
{
// Arrange & Act
@@ -33,7 +36,8 @@ public class SignalContextTests
context.GetSignal<bool>("enabled").Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithNumber_SetsDecimalSignal()
{
// Arrange & Act
@@ -45,7 +49,8 @@ public class SignalContextTests
context.GetSignal<decimal>("score").Should().Be(0.95m);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithString_SetsStringSignal()
{
// Arrange & Act
@@ -57,7 +62,8 @@ public class SignalContextTests
context.GetSignal<string>("name").Should().Be("test");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithFinding_SetsNestedFindingObject()
{
// Arrange & Act
@@ -74,7 +80,8 @@ public class SignalContextTests
finding["cve_id"].Should().Be("CVE-2024-1234");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithReachability_SetsNestedReachabilityObject()
{
// Arrange & Act
@@ -91,7 +98,8 @@ public class SignalContextTests
reachability["has_runtime_evidence"].Should().Be(true);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Builder_WithTrustScore_SetsTrustSignals()
{
// Arrange & Act
@@ -104,7 +112,8 @@ public class SignalContextTests
context.GetSignal<bool>("trust_verified").Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SetSignal_UpdatesExistingValue()
{
// Arrange
@@ -118,7 +127,8 @@ public class SignalContextTests
context.GetSignal("key").Should().Be("value2");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RemoveSignal_RemovesExistingSignal()
{
// Arrange
@@ -132,7 +142,8 @@ public class SignalContextTests
context.HasSignal("key").Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Clone_CreatesIndependentCopy()
{
// Arrange
@@ -149,7 +160,8 @@ public class SignalContextTests
clone.GetSignal("key").Should().Be("modified");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SignalNames_ReturnsAllSignalKeys()
{
// Arrange
@@ -163,7 +175,8 @@ public class SignalContextTests
context.SignalNames.Should().BeEquivalentTo(new[] { "a", "b", "c" });
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Signals_ReturnsReadOnlyDictionary()
{
// Arrange

View File

@@ -28,6 +28,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../StellaOps.PolicyDsl/StellaOps.PolicyDsl.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="TestData\*.dsl">