356 lines
12 KiB
C#
356 lines
12 KiB
C#
using System.Text.Json;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Aoc;
|
|
using StellaOps.Concelier.Core.Aoc;
|
|
|
|
namespace StellaOps.Concelier.WebService.Tests.Aoc;
|
|
|
|
/// <summary>
|
|
/// Regression tests ensuring AOC verify consistently emits ERR_AOC_001 and maintains
|
|
/// mapper/guard parity across all violation scenarios.
|
|
/// Per CONCELIER-WEB-AOC-19-007.
|
|
/// </summary>
|
|
public sealed class AocVerifyRegressionTests
|
|
{
|
|
private static readonly AocGuardOptions GuardOptions = AocGuardOptions.Default;
|
|
|
|
[Fact]
|
|
public void Verify_ForbiddenField_EmitsErrAoc001()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithForbiddenField("severity", "high");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
var violation = Assert.Single(result.Violations.Where(v => v.Path == "/severity"));
|
|
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
|
|
Assert.Equal(AocViolationCode.ForbiddenField, violation.Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("severity")]
|
|
[InlineData("cvss")]
|
|
[InlineData("cvss_vector")]
|
|
[InlineData("merged_from")]
|
|
[InlineData("consensus_provider")]
|
|
[InlineData("reachability")]
|
|
[InlineData("asset_criticality")]
|
|
[InlineData("risk_score")]
|
|
public void Verify_AllForbiddenFields_EmitErrAoc001(string forbiddenField)
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithForbiddenField(forbiddenField, "forbidden_value");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
var violation = result.Violations.FirstOrDefault(v => v.Path == $"/{forbiddenField}");
|
|
Assert.NotNull(violation);
|
|
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
|
|
Assert.Equal(AocViolationCode.ForbiddenField, violation.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_DerivedField_EmitsErrAoc006()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithDerivedField("effective_status", "affected");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
var violation = result.Violations.FirstOrDefault(v =>
|
|
v.Path == "/effective_status" && v.ErrorCode == "ERR_AOC_006");
|
|
Assert.NotNull(violation);
|
|
Assert.Equal(AocViolationCode.DerivedFindingDetected, violation.Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("effective_status")]
|
|
[InlineData("effective_range")]
|
|
[InlineData("effective_severity")]
|
|
[InlineData("effective_cvss")]
|
|
public void Verify_AllDerivedFields_EmitErrAoc006(string derivedField)
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithDerivedField(derivedField, "derived_value");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
var violation = result.Violations.FirstOrDefault(v =>
|
|
v.Path == $"/{derivedField}" && v.ErrorCode == "ERR_AOC_006");
|
|
Assert.NotNull(violation);
|
|
Assert.Equal(AocViolationCode.DerivedFindingDetected, violation.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_UnknownField_EmitsErrAoc007()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithUnknownField("completely_unknown_field", "some_value");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
var violation = Assert.Single(result.Violations.Where(v =>
|
|
v.Path == "/completely_unknown_field" && v.ErrorCode == "ERR_AOC_007"));
|
|
Assert.Equal(AocViolationCode.UnknownField, violation.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_MergeAttempt_EmitsErrAoc002()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithMergedFrom(["obs-1", "obs-2"]);
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
// merged_from triggers ERR_AOC_001 (forbidden field)
|
|
var violation = result.Violations.FirstOrDefault(v => v.Path == "/merged_from");
|
|
Assert.NotNull(violation);
|
|
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_MultipleViolations_EmitsAllErrorCodes()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithMultipleViolations();
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.False(result.IsValid);
|
|
|
|
// Should have ERR_AOC_001 for forbidden field
|
|
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_001");
|
|
|
|
// Should have ERR_AOC_006 for derived field
|
|
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_006");
|
|
|
|
// Should have ERR_AOC_007 for unknown field
|
|
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_007");
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ValidDocument_NoViolations()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateValidJson();
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
Assert.True(result.IsValid);
|
|
Assert.Empty(result.Violations);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ErrorCodeConsistency_AcrossMultipleRuns()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithForbiddenField("severity", "critical");
|
|
|
|
// Run validation multiple times
|
|
var results = Enumerable.Range(0, 10)
|
|
.Select(_ => guard.Validate(json.RootElement, GuardOptions))
|
|
.ToList();
|
|
|
|
// All should produce same error code
|
|
var allErrorCodes = results
|
|
.SelectMany(r => r.Violations)
|
|
.Select(v => v.ErrorCode)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
Assert.Single(allErrorCodes);
|
|
Assert.Equal("ERR_AOC_001", allErrorCodes[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_PathConsistency_AcrossMultipleRuns()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithForbiddenField("cvss", "9.8");
|
|
|
|
// Run validation multiple times
|
|
var results = Enumerable.Range(0, 10)
|
|
.Select(_ => guard.Validate(json.RootElement, GuardOptions))
|
|
.ToList();
|
|
|
|
// All should produce same path
|
|
var allPaths = results
|
|
.SelectMany(r => r.Violations)
|
|
.Select(v => v.Path)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
Assert.Single(allPaths);
|
|
Assert.Equal("/cvss", allPaths[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_MapperGuardParity_ValidationResultsMatch()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var validator = new AdvisorySchemaValidator(guard, Options.Create(GuardOptions));
|
|
|
|
// Create document with forbidden field
|
|
var json = CreateJsonWithForbiddenField("severity", "high");
|
|
|
|
// Validate with guard directly
|
|
var guardResult = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
// Both should detect the violation
|
|
Assert.False(guardResult.IsValid);
|
|
Assert.Contains(guardResult.Violations, v =>
|
|
v.ErrorCode == "ERR_AOC_001" && v.Path == "/severity");
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ViolationMessage_ContainsMeaningfulDetails()
|
|
{
|
|
var guard = new AocWriteGuard();
|
|
var json = CreateJsonWithForbiddenField("severity", "high");
|
|
|
|
var result = guard.Validate(json.RootElement, GuardOptions);
|
|
|
|
var violation = result.Violations.First(v => v.ErrorCode == "ERR_AOC_001");
|
|
|
|
// Message should not be empty
|
|
Assert.False(string.IsNullOrWhiteSpace(violation.Message));
|
|
|
|
// Path should be correct
|
|
Assert.Equal("/severity", violation.Path);
|
|
}
|
|
|
|
private static JsonDocument CreateJsonWithForbiddenField(string field, string value)
|
|
{
|
|
return JsonDocument.Parse($$"""
|
|
{
|
|
"tenant": "test",
|
|
"{{field}}": "{{value}}",
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
|
|
private static JsonDocument CreateJsonWithDerivedField(string field, string value)
|
|
{
|
|
return JsonDocument.Parse($$"""
|
|
{
|
|
"tenant": "test",
|
|
"{{field}}": "{{value}}",
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
|
|
private static JsonDocument CreateJsonWithUnknownField(string field, string value)
|
|
{
|
|
return JsonDocument.Parse($$"""
|
|
{
|
|
"tenant": "test",
|
|
"{{field}}": "{{value}}",
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
|
|
private static JsonDocument CreateJsonWithMergedFrom(string[] mergedFrom)
|
|
{
|
|
var mergedArray = string.Join(", ", mergedFrom.Select(m => $"\"{m}\""));
|
|
return JsonDocument.Parse($$"""
|
|
{
|
|
"tenant": "test",
|
|
"merged_from": [{{mergedArray}}],
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
|
|
private static JsonDocument CreateJsonWithMultipleViolations()
|
|
{
|
|
return JsonDocument.Parse("""
|
|
{
|
|
"tenant": "test",
|
|
"severity": "high",
|
|
"effective_status": "affected",
|
|
"unknown_custom_field": "value",
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
|
|
private static JsonDocument CreateValidJson()
|
|
{
|
|
return JsonDocument.Parse("""
|
|
{
|
|
"tenant": "test",
|
|
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
|
|
"upstream": {
|
|
"upstream_id": "CVE-2024-0001",
|
|
"content_hash": "sha256:abc",
|
|
"retrieved_at": "2024-01-01T00:00:00Z",
|
|
"signature": {"present": false},
|
|
"provenance": {}
|
|
},
|
|
"content": {"format": "OSV", "raw": {}},
|
|
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
|
|
"linkset": {}
|
|
}
|
|
""");
|
|
}
|
|
}
|