up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using StellaOps.Policy.Engine.ConsoleSurface;
|
||||
using StellaOps.Policy.Engine.Simulation;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Tests.ConsoleSurface;
|
||||
|
||||
public sealed class ConsoleSimulationDiffServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compute_IsDeterministic_AndCarriesMetadata()
|
||||
{
|
||||
var analytics = new SimulationAnalyticsService();
|
||||
var service = new ConsoleSimulationDiffService(analytics);
|
||||
|
||||
var request = new ConsoleSimulationDiffRequest(
|
||||
BaselinePolicyVersion: "2025.11.24",
|
||||
CandidatePolicyVersion: "2025.12.02",
|
||||
ArtifactScope: new[]
|
||||
{
|
||||
new ConsoleArtifactScope("sha256:abc", "pkg:npm/foo@1.0.0"),
|
||||
new ConsoleArtifactScope("sha256:def", "pkg:npm/bar@2.0.0")
|
||||
},
|
||||
Filters: new ConsoleSimulationFilters(new[] { "high", "critical" }, new[] { "RULE-1234" }),
|
||||
Budget: new ConsoleSimulationBudget(maxFindings: 10, maxExplainSamples: 5),
|
||||
EvaluationTimestamp: new DateTimeOffset(2025, 12, 2, 0, 0, 0, TimeSpan.Zero));
|
||||
|
||||
var first = service.Compute(request);
|
||||
var second = service.Compute(request);
|
||||
|
||||
Assert.Equal(first, second); // deterministic
|
||||
Assert.Equal("console-policy-23-001", first.SchemaVersion);
|
||||
Assert.True(first.Summary.After.Total > 0);
|
||||
Assert.True(first.Summary.Before.Total > 0);
|
||||
Assert.NotEmpty(first.RuleImpact);
|
||||
Assert.True(first.Samples.Findings.Length <= 10);
|
||||
Assert.Equal(request.EvaluationTimestamp, first.Provenance.EvaluationTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -344,17 +344,19 @@ policy "Baseline Production Policy" syntax "stella-dsl@1" {
|
||||
|
||||
private static PolicyEvaluationContext CreateContext(string severity, string exposure, PolicyEvaluationExceptions? exceptions = null)
|
||||
{
|
||||
return new PolicyEvaluationContext(
|
||||
new PolicyEvaluationSeverity(severity),
|
||||
new PolicyEvaluationEnvironment(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["exposure"] = exposure
|
||||
}.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)),
|
||||
new PolicyEvaluationAdvisory("GHSA", ImmutableDictionary<string, string>.Empty),
|
||||
PolicyEvaluationVexEvidence.Empty,
|
||||
PolicyEvaluationSbom.Empty,
|
||||
exceptions ?? PolicyEvaluationExceptions.Empty);
|
||||
}
|
||||
return new PolicyEvaluationContext(
|
||||
new PolicyEvaluationSeverity(severity),
|
||||
new PolicyEvaluationEnvironment(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["exposure"] = exposure
|
||||
}.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)),
|
||||
new PolicyEvaluationAdvisory("GHSA", ImmutableDictionary<string, string>.Empty),
|
||||
PolicyEvaluationVexEvidence.Empty,
|
||||
PolicyEvaluationSbom.Empty,
|
||||
exceptions ?? PolicyEvaluationExceptions.Empty,
|
||||
PolicyEvaluationReachability.Unknown,
|
||||
PolicyEvaluationEntropy.Unknown);
|
||||
}
|
||||
|
||||
private static string Describe(ImmutableArray<PolicyIssue> issues) =>
|
||||
string.Join(" | ", issues.Select(issue => $"{issue.Severity}:{issue.Code}:{issue.Message}"));
|
||||
|
||||
@@ -8,6 +8,7 @@ using StellaOps.Policy.Engine.Evaluation;
|
||||
using StellaOps.Policy.Engine.ReachabilityFacts;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Engine.Signals.Entropy;
|
||||
using StellaOps.PolicyDsl;
|
||||
using Xunit;
|
||||
|
||||
@@ -250,6 +251,9 @@ public sealed class PolicyRuntimeEvaluationServiceTests
|
||||
Sbom: PolicyEvaluationSbom.Empty,
|
||||
Exceptions: PolicyEvaluationExceptions.Empty,
|
||||
Reachability: PolicyEvaluationReachability.Unknown,
|
||||
EntropyLayerSummary: null,
|
||||
EntropyReport: null,
|
||||
ProvenanceAttested: null,
|
||||
EvaluationTimestamp: new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
BypassCache: false);
|
||||
}
|
||||
@@ -262,6 +266,7 @@ public sealed class PolicyRuntimeEvaluationServiceTests
|
||||
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());
|
||||
var cache = new InMemoryPolicyEvaluationCache(cacheLogger, TimeProvider.System, options);
|
||||
var evaluator = new PolicyEvaluator();
|
||||
var entropy = new EntropyPenaltyCalculator(options, NullLogger<EntropyPenaltyCalculator>.Instance);
|
||||
|
||||
var reachabilityStore = new InMemoryReachabilityFactsStore(TimeProvider.System);
|
||||
var reachabilityCache = new InMemoryReachabilityFactsOverlayCache(
|
||||
@@ -281,6 +286,7 @@ public sealed class PolicyRuntimeEvaluationServiceTests
|
||||
cache,
|
||||
evaluator,
|
||||
reachabilityService,
|
||||
entropy,
|
||||
TimeProvider.System,
|
||||
serviceLogger);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public sealed class PackRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly PolicyPostgresFixture _fixture;
|
||||
private readonly PackRepository _repository;
|
||||
private readonly PackVersionRepository _packVersionRepository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
|
||||
public PackRepositoryTests(PolicyPostgresFixture fixture)
|
||||
@@ -22,6 +23,7 @@ public sealed class PackRepositoryTests : IAsyncLifetime
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance);
|
||||
_repository = new PackRepository(dataSource, NullLogger<PackRepository>.Instance);
|
||||
_packVersionRepository = new PackVersionRepository(dataSource, NullLogger<PackVersionRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
@@ -161,14 +163,49 @@ public sealed class PackRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
var pack = CreatePack("version-test");
|
||||
await _repository.CreateAsync(pack);
|
||||
await CreatePackVersionAsync(pack.Id, 1, publish: true);
|
||||
|
||||
// Act
|
||||
var result = await _repository.SetActiveVersionAsync(_tenantId, pack.Id, 2);
|
||||
var result = await _repository.SetActiveVersionAsync(_tenantId, pack.Id, 1);
|
||||
var fetched = await _repository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
fetched!.ActiveVersion.Should().Be(2);
|
||||
fetched!.ActiveVersion.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAndActivateVersions_FollowsWorkflow()
|
||||
{
|
||||
// Arrange
|
||||
var pack = CreatePack("workflow-pack");
|
||||
await _repository.CreateAsync(pack);
|
||||
|
||||
// Unpublished version cannot be activated
|
||||
var version1 = await CreatePackVersionAsync(pack.Id, 1);
|
||||
var activationBeforePublish = await _repository.SetActiveVersionAsync(_tenantId, pack.Id, 1);
|
||||
activationBeforePublish.Should().BeFalse();
|
||||
|
||||
// Publish v1 and activate
|
||||
await _packVersionRepository.PublishAsync(version1.Id, "tester");
|
||||
await _repository.SetActiveVersionAsync(_tenantId, pack.Id, 1);
|
||||
var activeAfterV1 = await _repository.GetByIdAsync(_tenantId, pack.Id);
|
||||
activeAfterV1!.ActiveVersion.Should().Be(1);
|
||||
|
||||
// Create and publish v2, then promote to active
|
||||
var nextVersion = await _packVersionRepository.GetNextVersionAsync(pack.Id);
|
||||
var version2 = await CreatePackVersionAsync(pack.Id, nextVersion);
|
||||
await _packVersionRepository.PublishAsync(version2.Id, "tester");
|
||||
await _repository.SetActiveVersionAsync(_tenantId, pack.Id, version2.Version);
|
||||
|
||||
// Assert ordering and active marker
|
||||
var versions = await _packVersionRepository.GetByPackIdAsync(pack.Id, publishedOnly: true);
|
||||
versions.Select(v => v.Version).Should().ContainInOrder(new[] { 2, 1 });
|
||||
var latest = await _packVersionRepository.GetLatestAsync(pack.Id);
|
||||
latest!.Version.Should().Be(2);
|
||||
|
||||
var finalPack = await _repository.GetByIdAsync(_tenantId, pack.Id);
|
||||
finalPack!.ActiveVersion.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -210,4 +247,27 @@ public sealed class PackRepositoryTests : IAsyncLifetime
|
||||
Name = name,
|
||||
IsBuiltin = false
|
||||
};
|
||||
|
||||
private async Task<PackVersionEntity> CreatePackVersionAsync(Guid packId, int version, bool publish = false)
|
||||
{
|
||||
var packVersion = new PackVersionEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PackId = packId,
|
||||
Version = version,
|
||||
Description = $"v{version}",
|
||||
RulesHash = $"rules-hash-{version}",
|
||||
IsPublished = false
|
||||
};
|
||||
|
||||
var created = await _packVersionRepository.CreateAsync(packVersion);
|
||||
|
||||
if (publish)
|
||||
{
|
||||
await _packVersionRepository.PublishAsync(created.Id, "tester");
|
||||
created = (await _packVersionRepository.GetByIdAsync(created.Id))!;
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var original = CreateProfile("version-create");
|
||||
await _repository.CreateAsync(original);
|
||||
original = await _repository.CreateAsync(original);
|
||||
|
||||
// Act
|
||||
var newVersion = new RiskProfileEntity
|
||||
@@ -207,6 +207,8 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
|
||||
// Assert
|
||||
created.Should().NotBeNull();
|
||||
created.Version.Should().Be(2);
|
||||
var originalAfter = await _repository.GetByIdAsync(_tenantId, original.Id);
|
||||
originalAfter!.IsActive.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -263,12 +265,84 @@ public sealed class RiskProfileRepositoryTests : IAsyncLifetime
|
||||
fetched.Should().BeNull();
|
||||
}
|
||||
|
||||
private RiskProfileEntity CreateProfile(string name) => new()
|
||||
[Fact]
|
||||
public async Task CreateVersion_HistoryRemainsQueryableAndOrdered()
|
||||
{
|
||||
// Arrange
|
||||
var v1 = await _repository.CreateAsync(CreateProfile(
|
||||
name: "history-profile",
|
||||
thresholds: "{\"critical\":9.0}",
|
||||
scoringWeights: "{\"vulnerability\":1.0}"));
|
||||
|
||||
var v2 = new RiskProfileEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "history-profile",
|
||||
DisplayName = "History V2",
|
||||
Description = "Second revision with tuned thresholds",
|
||||
Thresholds = "{\"critical\":8.0,\"high\":6.5}",
|
||||
ScoringWeights = "{\"vulnerability\":0.9}",
|
||||
Exemptions = "[]",
|
||||
Metadata = "{\"source\":\"unit-test\"}"
|
||||
};
|
||||
|
||||
// Act
|
||||
var createdV2 = await _repository.CreateVersionAsync(_tenantId, "history-profile", v2);
|
||||
|
||||
// Assert
|
||||
createdV2.Version.Should().Be(2);
|
||||
createdV2.IsActive.Should().BeTrue();
|
||||
|
||||
var versions = await _repository.GetVersionsByNameAsync(_tenantId, "history-profile");
|
||||
versions.Select(x => x.Version).Should().ContainInOrder(new[] { 2, 1 });
|
||||
versions.Single(x => x.Version == 1).IsActive.Should().BeFalse();
|
||||
versions.Single(x => x.Version == 1).Thresholds.Should().Contain("9.0");
|
||||
|
||||
var active = await _repository.GetActiveByNameAsync(_tenantId, "history-profile");
|
||||
active!.Version.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Activate_RevertsToPriorVersionAndDeactivatesCurrent()
|
||||
{
|
||||
// Arrange
|
||||
var v1 = await _repository.CreateAsync(CreateProfile("toggle-profile"));
|
||||
var v2 = await _repository.CreateVersionAsync(_tenantId, "toggle-profile", new RiskProfileEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "toggle-profile",
|
||||
DisplayName = "Toggle V2",
|
||||
Thresholds = "{\"critical\":8.5}"
|
||||
});
|
||||
|
||||
// Act
|
||||
var activated = await _repository.ActivateAsync(_tenantId, v1.Id);
|
||||
|
||||
// Assert
|
||||
activated.Should().BeTrue();
|
||||
var versions = await _repository.GetVersionsByNameAsync(_tenantId, "toggle-profile");
|
||||
versions.Single(x => x.Id == v1.Id).IsActive.Should().BeTrue();
|
||||
versions.Single(x => x.Id == v2.Id).IsActive.Should().BeFalse();
|
||||
|
||||
var active = await _repository.GetActiveByNameAsync(_tenantId, "toggle-profile");
|
||||
active!.Id.Should().Be(v1.Id);
|
||||
}
|
||||
|
||||
private RiskProfileEntity CreateProfile(
|
||||
string name,
|
||||
int version = 1,
|
||||
bool isActive = true,
|
||||
string? thresholds = null,
|
||||
string? scoringWeights = null) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = name,
|
||||
Version = 1,
|
||||
IsActive = true
|
||||
Version = version,
|
||||
IsActive = isActive,
|
||||
Thresholds = thresholds ?? "{}",
|
||||
ScoringWeights = scoringWeights ?? "{}"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user