Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -35,4 +35,7 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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(" "));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Policy.Scoring/StellaOps.Policy.Scoring.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user