partly or unimplemented features - now implemented

This commit is contained in:
master
2026-02-09 08:53:51 +02:00
parent 1bf6bbf395
commit 4bdc298ec1
674 changed files with 90194 additions and 2271 deletions

View File

@@ -0,0 +1,421 @@
// -----------------------------------------------------------------------------
// PolicyDiffMergeEngineTests.cs
// Sprint: SPRINT_20260208_048_Policy_policy_interop_framework
// Task: T1 - Tests for diff/merge engine
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Text.Json;
using FluentAssertions;
using StellaOps.Policy.Interop.Abstractions;
using StellaOps.Policy.Interop.Contracts;
using StellaOps.Policy.Interop.DiffMerge;
using Xunit;
namespace StellaOps.Policy.Interop.Tests.DiffMerge;
public sealed class PolicyDiffMergeEngineTests
{
private readonly PolicyDiffMergeEngine _engine = new();
private static PolicyPackDocument LoadGoldenFixture()
{
var fixturePath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "golden-policy-pack-v2.json");
var json = File.ReadAllText(fixturePath);
return JsonSerializer.Deserialize<PolicyPackDocument>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
}
private static PolicyPackDocument CreateMinimalDoc(
string name = "test", string version = "1.0.0", string defaultAction = "block")
{
return new PolicyPackDocument
{
ApiVersion = PolicyPackDocument.ApiVersionV2,
Kind = PolicyPackDocument.KindPolicyPack,
Metadata = new PolicyPackMetadata { Name = name, Version = version },
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = defaultAction },
Gates = [],
Rules = []
}
};
}
#region Diff Tests
[Fact]
public void Diff_IdenticalDocuments_ReturnsNoChanges()
{
var doc = LoadGoldenFixture();
var result = _engine.Diff(doc, doc);
result.AreIdentical.Should().BeTrue();
result.Changes.Should().BeEmpty();
result.Summary.Total.Should().Be(0);
}
[Fact]
public void Diff_MetadataVersionChange_DetectsModification()
{
var baseline = CreateMinimalDoc(version: "1.0.0");
var updated = baseline with
{
Metadata = baseline.Metadata with { Version = "2.0.0" }
};
var result = _engine.Diff(baseline, updated);
result.AreIdentical.Should().BeFalse();
result.Summary.Modifications.Should().Be(1);
result.Changes.Should().ContainSingle(c =>
c.Path == "metadata.version" && c.ChangeType == PolicyChangeType.Modified);
}
[Fact]
public void Diff_SettingsChange_DetectsDefaultActionModification()
{
var baseline = CreateMinimalDoc(defaultAction: "block");
var updated = baseline with
{
Spec = baseline.Spec with
{
Settings = baseline.Spec.Settings with { DefaultAction = "warn" }
}
};
var result = _engine.Diff(baseline, updated);
result.AreIdentical.Should().BeFalse();
result.Changes.Should().ContainSingle(c =>
c.Path == "spec.settings.defaultAction" &&
c.OldValue!.ToString() == "block" &&
c.NewValue!.ToString() == "warn");
}
[Fact]
public void Diff_GateAdded_DetectsAddition()
{
var baseline = CreateMinimalDoc();
var updated = baseline with
{
Spec = baseline.Spec with
{
Gates =
[
new PolicyGateDefinition
{
Id = "new-gate",
Type = "CvssThresholdGate"
}
]
}
};
var result = _engine.Diff(baseline, updated);
result.Summary.Additions.Should().Be(1);
result.Changes.Should().ContainSingle(c =>
c.ChangeType == PolicyChangeType.Added && c.Category == "gate");
}
[Fact]
public void Diff_GateRemoved_DetectsRemoval()
{
var baseline = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates =
[
new PolicyGateDefinition
{
Id = "old-gate",
Type = "SbomPresenceGate"
}
],
Rules = []
}
};
var updated = baseline with
{
Spec = baseline.Spec with { Gates = [] }
};
var result = _engine.Diff(baseline, updated);
result.Summary.Removals.Should().Be(1);
result.Changes.Should().ContainSingle(c =>
c.ChangeType == PolicyChangeType.Removed && c.Category == "gate");
}
[Fact]
public void Diff_RuleActionChanged_DetectsModification()
{
var rule = new PolicyRuleDefinition
{
Name = "test-rule",
Action = "block",
Priority = 10
};
var baseline = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates = [],
Rules = [rule]
}
};
var updated = baseline with
{
Spec = baseline.Spec with
{
Rules = [rule with { Action = "warn" }]
}
};
var result = _engine.Diff(baseline, updated);
result.Changes.Should().Contain(c =>
c.Path == "spec.rules[test-rule].action" && c.ChangeType == PolicyChangeType.Modified);
}
[Fact]
public void Diff_GateConfigChanged_DetectsConfigModification()
{
var gate = new PolicyGateDefinition
{
Id = "cvss-gate",
Type = "CvssThresholdGate",
Config = new Dictionary<string, object?> { ["threshold"] = (JsonElement)JsonDocument.Parse("7.0").RootElement.Clone() }
};
var baseline = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates = [gate],
Rules = []
}
};
var updatedGate = gate with
{
Config = new Dictionary<string, object?> { ["threshold"] = (JsonElement)JsonDocument.Parse("9.0").RootElement.Clone() }
};
var updated = baseline with
{
Spec = baseline.Spec with { Gates = [updatedGate] }
};
var result = _engine.Diff(baseline, updated);
result.Changes.Should().Contain(c =>
c.Path == "spec.gates[cvss-gate].config.threshold" && c.ChangeType == PolicyChangeType.Modified);
}
[Fact]
public void Diff_GoldenFixture_AgainstItself_IsIdentical()
{
var doc = LoadGoldenFixture();
var result = _engine.Diff(doc, doc);
result.AreIdentical.Should().BeTrue();
}
[Fact]
public void Diff_MultipleChanges_ReturnsCorrectSummary()
{
var baseline = CreateMinimalDoc(name: "base", version: "1.0.0", defaultAction: "block");
var updated = CreateMinimalDoc(name: "updated", version: "2.0.0", defaultAction: "warn");
var result = _engine.Diff(baseline, updated);
result.Summary.Modifications.Should().Be(3); // name, version, defaultAction
result.Summary.Total.Should().Be(3);
}
#endregion
#region Merge Tests
[Fact]
public void Merge_IdenticalDocuments_ReturnsIdenticalResult()
{
var doc = CreateMinimalDoc();
var result = _engine.Merge(doc, doc);
result.Success.Should().BeTrue();
result.Document.Should().NotBeNull();
result.Conflicts.Should().BeEmpty();
}
[Fact]
public void Merge_OverlayWins_OverlayValuesPreferred()
{
var baseDoc = CreateMinimalDoc(defaultAction: "block");
var overlay = CreateMinimalDoc(defaultAction: "warn");
var result = _engine.Merge(baseDoc, overlay, PolicyMergeStrategy.OverlayWins);
result.Success.Should().BeTrue();
result.Document!.Spec.Settings.DefaultAction.Should().Be("warn");
}
[Fact]
public void Merge_BaseWins_BaseValuesPreferred()
{
var baseDoc = CreateMinimalDoc(defaultAction: "block");
var overlay = CreateMinimalDoc(defaultAction: "warn");
var result = _engine.Merge(baseDoc, overlay, PolicyMergeStrategy.BaseWins);
result.Success.Should().BeTrue();
result.Document!.Spec.Settings.DefaultAction.Should().Be("block");
}
[Fact]
public void Merge_FailOnConflict_ReportsConflicts()
{
var baseDoc = CreateMinimalDoc(defaultAction: "block");
var overlay = CreateMinimalDoc(defaultAction: "warn");
var result = _engine.Merge(baseDoc, overlay, PolicyMergeStrategy.FailOnConflict);
result.Success.Should().BeFalse();
result.Conflicts.Should().NotBeEmpty();
}
[Fact]
public void Merge_OverlayAddsNewGate_GateIncluded()
{
var baseDoc = CreateMinimalDoc();
var overlay = baseDoc with
{
Spec = baseDoc.Spec with
{
Gates =
[
new PolicyGateDefinition
{
Id = "overlay-gate",
Type = "CvssThresholdGate"
}
]
}
};
var result = _engine.Merge(baseDoc, overlay);
result.Success.Should().BeTrue();
result.Document!.Spec.Gates.Should().ContainSingle(g => g.Id == "overlay-gate");
}
[Fact]
public void Merge_OverlayAddsNewRule_RuleIncluded()
{
var baseDoc = CreateMinimalDoc();
var overlay = baseDoc with
{
Spec = baseDoc.Spec with
{
Rules =
[
new PolicyRuleDefinition
{
Name = "overlay-rule",
Action = "warn",
Priority = 50
}
]
}
};
var result = _engine.Merge(baseDoc, overlay);
result.Success.Should().BeTrue();
result.Document!.Spec.Rules.Should().ContainSingle(r => r.Name == "overlay-rule");
}
[Fact]
public void Merge_BothHaveGates_MergesAllGates()
{
var baseDoc = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates =
[
new PolicyGateDefinition { Id = "base-gate", Type = "SbomPresenceGate" }
],
Rules = []
}
};
var overlay = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates =
[
new PolicyGateDefinition { Id = "overlay-gate", Type = "CvssThresholdGate" }
],
Rules = []
}
};
var result = _engine.Merge(baseDoc, overlay);
result.Success.Should().BeTrue();
result.Document!.Spec.Gates.Should().HaveCount(2);
result.Document.Spec.Gates.Should().Contain(g => g.Id == "base-gate");
result.Document.Spec.Gates.Should().Contain(g => g.Id == "overlay-gate");
}
[Fact]
public void Merge_OverlayWins_OverridesMatchingGate()
{
var gate = new PolicyGateDefinition
{
Id = "shared-gate",
Type = "CvssThresholdGate",
Enabled = true
};
var baseDoc = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates = [gate],
Rules = []
}
};
var overlay = CreateMinimalDoc() with
{
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "block" },
Gates = [gate with { Enabled = false }],
Rules = []
}
};
var result = _engine.Merge(baseDoc, overlay, PolicyMergeStrategy.OverlayWins);
result.Success.Should().BeTrue();
result.Document!.Spec.Gates.Should().ContainSingle(g =>
g.Id == "shared-gate" && !g.Enabled);
}
#endregion
}

View File

@@ -0,0 +1,151 @@
// -----------------------------------------------------------------------------
// YamlPolicyExporterTests.cs
// Sprint: SPRINT_20260208_048_Policy_policy_interop_framework
// Task: T1 - Tests for YAML export
// -----------------------------------------------------------------------------
using System.Text.Json;
using FluentAssertions;
using StellaOps.Policy.Interop.Contracts;
using StellaOps.Policy.Interop.Export;
using Xunit;
namespace StellaOps.Policy.Interop.Tests.Export;
public sealed class YamlPolicyExporterTests
{
private readonly YamlPolicyExporter _exporter = new();
private static PolicyPackDocument LoadGoldenFixture()
{
var fixturePath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "golden-policy-pack-v2.json");
var json = File.ReadAllText(fixturePath);
return JsonSerializer.Deserialize<PolicyPackDocument>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
}
[Fact]
public async Task ExportToYaml_ProducesValidOutput()
{
var doc = LoadGoldenFixture();
var request = new PolicyExportRequest { Format = PolicyFormats.Yaml };
var result = await _exporter.ExportToYamlAsync(doc, request);
result.Success.Should().BeTrue();
result.YamlContent.Should().NotBeNullOrEmpty();
result.Digest.Should().StartWith("sha256:");
}
[Fact]
public async Task ExportToYaml_ContainsApiVersionAndKind()
{
var doc = LoadGoldenFixture();
var request = new PolicyExportRequest { Format = PolicyFormats.Yaml };
var result = await _exporter.ExportToYamlAsync(doc, request);
result.YamlContent.Should().Contain("apiVersion: policy.stellaops.io/v2");
result.YamlContent.Should().Contain("kind: PolicyPack");
}
[Fact]
public async Task ExportToYaml_IsDeterministic()
{
var doc = LoadGoldenFixture();
var request = new PolicyExportRequest { Format = PolicyFormats.Yaml };
var result1 = await _exporter.ExportToYamlAsync(doc, request);
var result2 = await _exporter.ExportToYamlAsync(doc, request);
result1.Digest.Should().Be(result2.Digest);
result1.YamlContent.Should().Be(result2.YamlContent);
}
[Fact]
public async Task ExportToYaml_WithEnvironment_MergesConfig()
{
var doc = LoadGoldenFixture();
var request = new PolicyExportRequest { Format = PolicyFormats.Yaml, Environment = "staging" };
var result = await _exporter.ExportToYamlAsync(doc, request);
// Environment-specific config is merged; environments key should not appear
result.YamlContent.Should().NotContain("environments:");
}
[Fact]
public async Task ExportToYaml_WithoutRemediation_StripsHints()
{
var doc = LoadGoldenFixture();
var request = new PolicyExportRequest { Format = PolicyFormats.Yaml, IncludeRemediation = false };
var result = await _exporter.ExportToYamlAsync(doc, request);
result.YamlContent.Should().NotContain("remediation:");
}
[Fact]
public void SerializeCanonical_ProducesDeterministicBytes()
{
var doc = LoadGoldenFixture();
var bytes1 = YamlPolicyExporter.SerializeCanonical(doc);
var bytes2 = YamlPolicyExporter.SerializeCanonical(doc);
bytes1.Should().BeEquivalentTo(bytes2);
}
[Fact]
public void SerializeToYaml_PreservesGateIds()
{
var doc = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(doc);
yaml.Should().Contain("cvss-threshold");
yaml.Should().Contain("signature-required");
yaml.Should().Contain("evidence-freshness");
yaml.Should().Contain("sbom-presence");
yaml.Should().Contain("minimum-confidence");
}
[Fact]
public void SerializeToYaml_PreservesRuleNames()
{
var doc = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(doc);
yaml.Should().Contain("require-dsse-signature");
yaml.Should().Contain("require-rekor-proof");
yaml.Should().Contain("require-sbom-digest");
yaml.Should().Contain("require-freshness-tst");
}
[Fact]
public void SerializeToYaml_MinimalDocument_Succeeds()
{
var doc = new PolicyPackDocument
{
ApiVersion = PolicyPackDocument.ApiVersionV2,
Kind = PolicyPackDocument.KindPolicyPack,
Metadata = new PolicyPackMetadata
{
Name = "minimal",
Version = "1.0.0"
},
Spec = new PolicyPackSpec
{
Settings = new PolicyPackSettings { DefaultAction = "allow" },
Gates = [],
Rules = []
}
};
var yaml = YamlPolicyExporter.SerializeToYaml(doc);
yaml.Should().Contain("name: minimal");
yaml.Should().Contain("defaultAction: allow");
}
}

View File

@@ -79,8 +79,30 @@ public class FormatDetectorTests
[Fact]
public void DetectFromExtension_UnknownExtension_ReturnsNull()
{
FormatDetector.DetectFromExtension("policy.yaml").Should().BeNull();
FormatDetector.DetectFromExtension("policy.txt").Should().BeNull();
FormatDetector.DetectFromExtension("policy.xml").Should().BeNull();
}
[Fact]
public void DetectFromExtension_YamlFile_ReturnsYaml()
{
FormatDetector.DetectFromExtension("policy.yaml").Should().Be(PolicyFormats.Yaml);
FormatDetector.DetectFromExtension("policy.yml").Should().Be(PolicyFormats.Yaml);
FormatDetector.DetectFromExtension("/path/to/my-policy.yaml").Should().Be(PolicyFormats.Yaml);
}
[Fact]
public void Detect_YamlContent_WithApiVersion_ReturnsYaml()
{
var content = "apiVersion: policy.stellaops.io/v2\nkind: PolicyPack\n";
FormatDetector.Detect(content).Should().Be(PolicyFormats.Yaml);
}
[Fact]
public void Detect_YamlContent_WithDocumentSeparator_ReturnsYaml()
{
var content = "---\napiVersion: policy.stellaops.io/v2\n";
FormatDetector.Detect(content).Should().Be(PolicyFormats.Yaml);
}
[Fact]

View File

@@ -0,0 +1,150 @@
// -----------------------------------------------------------------------------
// YamlPolicyImporterTests.cs
// Sprint: SPRINT_20260208_048_Policy_policy_interop_framework
// Task: T1 - Tests for YAML import
// -----------------------------------------------------------------------------
using System.Text.Json;
using FluentAssertions;
using StellaOps.Policy.Interop.Contracts;
using StellaOps.Policy.Interop.Export;
using StellaOps.Policy.Interop.Import;
using Xunit;
namespace StellaOps.Policy.Interop.Tests.Import;
public sealed class YamlPolicyImporterTests
{
private readonly YamlPolicyImporter _importer = new();
private static PolicyPackDocument LoadGoldenFixture()
{
var fixturePath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "golden-policy-pack-v2.json");
var json = File.ReadAllText(fixturePath);
return JsonSerializer.Deserialize<PolicyPackDocument>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
}
[Fact]
public async Task ImportFromYaml_ValidDocument_Succeeds()
{
// Export golden fixture to YAML, then re-import
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Success.Should().BeTrue();
result.DetectedFormat.Should().Be(PolicyFormats.Yaml);
result.Document.Should().NotBeNull();
}
[Fact]
public async Task ImportFromYaml_PreservesApiVersion()
{
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Document!.ApiVersion.Should().Be(PolicyPackDocument.ApiVersionV2);
}
[Fact]
public async Task ImportFromYaml_PreservesGateCount()
{
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.GateCount.Should().Be(original.Spec.Gates.Count);
}
[Fact]
public async Task ImportFromYaml_PreservesRuleCount()
{
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.RuleCount.Should().Be(original.Spec.Rules.Count);
}
[Fact]
public async Task ImportFromYaml_InvalidYaml_ReturnsDiagnostic()
{
var invalidYaml = "invalid: yaml:\n bad: [\nincomplete";
var result = await _importer.ImportFromStringAsync(invalidYaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Success.Should().BeFalse();
result.DetectedFormat.Should().Be(PolicyFormats.Yaml);
result.Diagnostics.Should().Contain(d => d.Code == "YAML_PARSE_ERROR");
}
[Fact]
public async Task ImportFromYaml_EmptyContent_ReturnsDiagnostic()
{
var result = await _importer.ImportFromStringAsync("",
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Success.Should().BeFalse();
result.DetectedFormat.Should().Be(PolicyFormats.Yaml);
}
[Fact]
public async Task ImportFromYaml_PreservesMetadataName()
{
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Document!.Metadata.Name.Should().Be(original.Metadata.Name);
}
[Fact]
public async Task ImportFromYaml_MinimalDocument_Succeeds()
{
var yaml = """
apiVersion: policy.stellaops.io/v2
kind: PolicyPack
metadata:
name: test-minimal
version: "1.0.0"
spec:
settings:
defaultAction: allow
gates: []
rules: []
""";
var result = await _importer.ImportFromStringAsync(yaml,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Success.Should().BeTrue();
result.Document!.Metadata.Name.Should().Be("test-minimal");
result.Document.Spec.Settings.DefaultAction.Should().Be("allow");
}
[Fact]
public async Task ImportFromYaml_Stream_Succeeds()
{
var original = LoadGoldenFixture();
var yaml = YamlPolicyExporter.SerializeToYaml(original);
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml));
var result = await _importer.ImportAsync(stream,
new PolicyImportOptions { Format = PolicyFormats.Yaml });
result.Success.Should().BeTrue();
}
}

View File

@@ -14,6 +14,7 @@
<PackageReference Include="FluentAssertions" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="JsonSchema.Net" />
<PackageReference Include="YamlDotNet" />
</ItemGroup>
<ItemGroup>