sprints completion. new product advisories prepared

This commit is contained in:
master
2026-01-16 16:30:03 +02:00
parent a927d924e3
commit 4ca3ce8fb4
255 changed files with 42434 additions and 1020 deletions

View File

@@ -0,0 +1,493 @@
// -----------------------------------------------------------------------------
// AiCodeGuardSignalContextExtensionsTests.cs
// Sprint: SPRINT_20260112_010_POLICY_ai_code_guard_policy
// Task: POLICY-AIGUARD-004 - Deterministic tests for AI Code Guard signal evaluation
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using StellaOps.Policy.AiCodeGuard;
using Xunit;
namespace StellaOps.PolicyDsl.Tests;
/// <summary>
/// Unit tests for AI Code Guard signal context extensions.
/// </summary>
public sealed class AiCodeGuardSignalContextExtensionsTests
{
#region Test Fixtures
private static IAiCodeGuardEvidenceProvider CreateEmptyProvider()
{
return new TestAiCodeGuardEvidenceProvider
{
Findings = ImmutableList<AiCodeGuardFinding>.Empty,
Overrides = ImmutableList<AiCodeGuardOverrideRecord>.Empty,
VerdictStatus = AiCodeGuardVerdictStatus.Pass,
AiGeneratedPercentage = null,
ScannerInfo = null
};
}
private static IAiCodeGuardEvidenceProvider CreateProviderWithFindings()
{
return new TestAiCodeGuardEvidenceProvider
{
Findings = ImmutableList.Create(
new AiCodeGuardFinding
{
Id = "finding-1",
Category = "InsecurePattern",
Severity = "high",
Confidence = 0.85,
RuleId = "guard/sql-injection",
FilePath = "src/database.cs",
StartLine = 42,
EndLine = 48,
Description = "Potential SQL injection in AI-generated code"
},
new AiCodeGuardFinding
{
Id = "finding-2",
Category = "AiGenerated",
Severity = "medium",
Confidence = 0.92,
RuleId = "guard/ai-detected",
FilePath = "src/utils.cs",
StartLine = 100,
EndLine = 120,
Description = "AI-generated code detected"
},
new AiCodeGuardFinding
{
Id = "finding-3",
Category = "Hallucination",
Severity = "critical",
Confidence = 0.78,
RuleId = "guard/api-hallucination",
FilePath = "src/api.cs",
StartLine = 200,
EndLine = 210,
Description = "Reference to non-existent API method"
}
),
Overrides = ImmutableList<AiCodeGuardOverrideRecord>.Empty,
VerdictStatus = AiCodeGuardVerdictStatus.Fail,
AiGeneratedPercentage = 42.5,
ScannerInfo = new AiCodeGuardScannerInfo
{
ScannerVersion = "1.0.0",
ModelVersion = "2024.1",
ConfidenceThreshold = 0.7,
EnabledCategories = ImmutableList.Create("AiGenerated", "InsecurePattern", "Hallucination")
}
};
}
private static IAiCodeGuardEvidenceProvider CreateProviderWithOverrides()
{
return new TestAiCodeGuardEvidenceProvider
{
Findings = ImmutableList.Create(
new AiCodeGuardFinding
{
Id = "finding-1",
Category = "InsecurePattern",
Severity = "high",
Confidence = 0.85,
RuleId = "guard/sql-injection",
FilePath = "src/database.cs",
StartLine = 42,
EndLine = 48,
Description = "Potential SQL injection in AI-generated code"
},
new AiCodeGuardFinding
{
Id = "finding-2",
Category = "AiGenerated",
Severity = "low",
Confidence = 0.92,
RuleId = "guard/ai-detected",
FilePath = "src/utils.cs",
StartLine = 100,
EndLine = 120,
Description = "AI-generated code detected"
}
),
Overrides = ImmutableList.Create(
new AiCodeGuardOverrideRecord
{
FindingId = "finding-1",
Action = "suppress",
Justification = "False positive - parameterized query is safe",
ApprovedBy = "security-team@example.com",
ApprovedAt = DateTimeOffset.UtcNow.AddDays(-7),
ExpiresAt = DateTimeOffset.UtcNow.AddDays(23)
}
),
VerdictStatus = AiCodeGuardVerdictStatus.PassWithWarnings,
AiGeneratedPercentage = 15.0,
ScannerInfo = null
};
}
#endregion
#region Basic Signal Tests
[Fact]
public void WithAiCodeGuardEvidence_EmptyProvider_SetsCorrectSignals()
{
// Arrange
var provider = CreateEmptyProvider();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.False(context.GetSignal<bool>("guard.has_finding"));
Assert.Equal(0, context.GetSignal<int>("guard.count"));
Assert.Equal("pass", context.GetSignal<string>("guard.verdict"));
Assert.Equal("allow", context.GetSignal<string>("guard.recommendation"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithFindings_SetsSeveritySignals()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.True(context.GetSignal<bool>("guard.has_finding"));
Assert.Equal(3, context.GetSignal<int>("guard.count"));
Assert.True(context.GetSignal<bool>("guard.severity.critical"));
Assert.True(context.GetSignal<bool>("guard.severity.high"));
Assert.True(context.GetSignal<bool>("guard.severity.medium"));
Assert.False(context.GetSignal<bool>("guard.severity.low"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithFindings_SetsCategorySignals()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.True(context.GetSignal<bool>("guard.category.insecure_pattern"));
Assert.True(context.GetSignal<bool>("guard.category.ai_generated"));
Assert.True(context.GetSignal<bool>("guard.category.hallucination"));
Assert.False(context.GetSignal<bool>("guard.category.license_risk"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithFindings_SetsVerdictSignals()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal("fail", context.GetSignal<string>("guard.verdict"));
Assert.True(context.GetSignal<bool>("guard.verdict.fail"));
Assert.False(context.GetSignal<bool>("guard.verdict.pass"));
Assert.Equal("block", context.GetSignal<string>("guard.recommendation"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithFindings_SetsAiPercentage()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal(42.5, context.GetSignal<double?>("guard.ai_percentage"));
}
#endregion
#region Override Tests
[Fact]
public void WithAiCodeGuardEvidence_WithOverrides_FiltersActiveFindingsCorrectly()
{
// Arrange
var provider = CreateProviderWithOverrides();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal(2, context.GetSignal<int>("guard.count")); // Total findings
Assert.Equal(1, context.GetSignal<int>("guard.active_count")); // After suppression
Assert.True(context.GetSignal<bool>("guard.has_active_finding"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithOverrides_SetsOverrideSignals()
{
// Arrange
var provider = CreateProviderWithOverrides();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal(1, context.GetSignal<int>("guard.override.count"));
Assert.Equal(1, context.GetSignal<int>("guard.override.active_count"));
Assert.Equal(0, context.GetSignal<int>("guard.override.expired_count"));
}
[Fact]
public void WithAiCodeGuardEvidence_WithOverrides_SetsCorrectVerdict()
{
// Arrange
var provider = CreateProviderWithOverrides();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal("passwithwarnings", context.GetSignal<string>("guard.verdict"));
Assert.True(context.GetSignal<bool>("guard.verdict.pass_with_warnings"));
Assert.Equal("review", context.GetSignal<string>("guard.recommendation"));
}
#endregion
#region Scanner Info Tests
[Fact]
public void WithAiCodeGuardEvidence_WithScannerInfo_SetsScannerSignals()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Equal("1.0.0", context.GetSignal<string>("guard.scanner.version"));
Assert.Equal("2024.1", context.GetSignal<string>("guard.scanner.model_version"));
Assert.Equal(0.7, context.GetSignal<double?>("guard.scanner.confidence_threshold"));
Assert.Equal(3, context.GetSignal<int>("guard.scanner.category_count"));
}
[Fact]
public void WithAiCodeGuardEvidence_NullScannerInfo_SetsNullScannerSignals()
{
// Arrange
var provider = CreateEmptyProvider();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
Assert.Null(context.GetSignal<string>("guard.scanner.version"));
Assert.Null(context.GetSignal<string>("guard.scanner.model_version"));
Assert.Equal(0, context.GetSignal<int>("guard.scanner.category_count"));
}
#endregion
#region Nested Object Tests
[Fact]
public void WithAiCodeGuardEvidence_SetsNestedGuardObject()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardEvidence(evidenceContext)
.Build();
// Assert
var guard = context.GetSignal<IReadOnlyDictionary<string, object?>>("guard");
Assert.NotNull(guard);
Assert.True((bool)guard["has_finding"]!);
Assert.Equal(3, guard["count"]);
var severity = guard["severity"] as IReadOnlyDictionary<string, object?>;
Assert.NotNull(severity);
Assert.True((bool)severity["critical"]!);
}
#endregion
#region Determinism Tests
[Fact]
public void CreateExplainTrace_IsDeterministic()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act - create trace multiple times
var trace1 = AiCodeGuardSignalBinder.CreateExplainTrace(evidenceContext);
var trace2 = AiCodeGuardSignalBinder.CreateExplainTrace(evidenceContext);
var trace3 = AiCodeGuardSignalBinder.CreateExplainTrace(evidenceContext);
// Assert - all traces should be identical
Assert.Equal(trace1, trace2);
Assert.Equal(trace2, trace3);
// Verify trace contains expected content
Assert.Contains("guard.verdict=Fail", trace1);
Assert.Contains("guard.total_findings=3", trace1);
Assert.Contains("guard.ai_percentage=42.5", trace1);
}
[Fact]
public void CreateFindingSummary_IsDeterministic()
{
// Arrange
var provider = CreateProviderWithFindings();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var summary1 = AiCodeGuardSignalBinder.CreateFindingSummary(evidenceContext);
var summary2 = AiCodeGuardSignalBinder.CreateFindingSummary(evidenceContext);
// Assert
Assert.Equal(summary1, summary2);
Assert.Contains("3 AI code guard finding(s)", summary1);
Assert.Contains("1 critical", summary1);
Assert.Contains("1 high", summary1);
Assert.Contains("1 medium", summary1);
Assert.Contains("AI-generated: 42.5%", summary1);
}
[Fact]
public void CreateFindingSummary_EmptyFindings_ReturnsNoFindings()
{
// Arrange
var provider = CreateEmptyProvider();
var evidenceContext = new AiCodeGuardEvidenceContext(provider);
// Act
var summary = AiCodeGuardSignalBinder.CreateFindingSummary(evidenceContext);
// Assert
Assert.Equal("No AI code guard findings detected.", summary);
}
#endregion
#region Simplified Result Tests
[Fact]
public void WithAiCodeGuardResult_SetsBasicSignals()
{
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardResult(
status: "fail",
totalFindings: 5,
criticalCount: 1,
highCount: 2,
mediumCount: 2,
aiPercentage: 25.0)
.Build();
// Assert
Assert.Equal("fail", context.GetSignal<string>("guard.verdict"));
Assert.Equal(5, context.GetSignal<int>("guard.count"));
Assert.True(context.GetSignal<bool>("guard.has_finding"));
Assert.True(context.GetSignal<bool>("guard.severity.critical"));
Assert.Equal(1, context.GetSignal<int>("guard.severity.critical_count"));
Assert.True(context.GetSignal<bool>("guard.severity.high"));
Assert.Equal(2, context.GetSignal<int>("guard.severity.high_count"));
Assert.Equal(25.0, context.GetSignal<double?>("guard.ai_percentage"));
Assert.Equal("block", context.GetSignal<string>("guard.recommendation"));
}
[Fact]
public void WithAiCodeGuardResult_PassStatus_SetsAllowRecommendation()
{
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardResult(
status: "pass",
totalFindings: 0)
.Build();
// Assert
Assert.Equal("pass", context.GetSignal<string>("guard.verdict"));
Assert.Equal("allow", context.GetSignal<string>("guard.recommendation"));
}
[Fact]
public void WithAiCodeGuardResult_WarningStatus_SetsReviewRecommendation()
{
// Act
var context = SignalContext.Builder()
.WithAiCodeGuardResult(
status: "pass_with_warnings",
totalFindings: 2,
mediumCount: 2)
.Build();
// Assert
Assert.Equal("pass_with_warnings", context.GetSignal<string>("guard.verdict"));
Assert.Equal("review", context.GetSignal<string>("guard.recommendation"));
}
#endregion
#region Test Provider Implementation
private sealed class TestAiCodeGuardEvidenceProvider : IAiCodeGuardEvidenceProvider
{
public ImmutableList<AiCodeGuardFinding> Findings { get; init; } = ImmutableList<AiCodeGuardFinding>.Empty;
public ImmutableList<AiCodeGuardOverrideRecord> Overrides { get; init; } = ImmutableList<AiCodeGuardOverrideRecord>.Empty;
public AiCodeGuardVerdictStatus VerdictStatus { get; init; }
public double? AiGeneratedPercentage { get; init; }
public AiCodeGuardScannerInfo? ScannerInfo { get; init; }
}
#endregion
}