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
Policy Lint & Smoke / policy-lint (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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Policy.Engine.DeterminismGuard;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Tests.DeterminismGuard;
|
||||
|
||||
public sealed class DeterminismGuardTests
|
||||
{
|
||||
#region ProhibitedPatternAnalyzer Tests
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsDateTimeNow()
|
||||
{
|
||||
// Arrange
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = """
|
||||
public class Test
|
||||
{
|
||||
public DateTime GetTime() => DateTime.Now;
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
// Assert
|
||||
result.Passed.Should().BeFalse();
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "DateTime.Now" &&
|
||||
v.Category == DeterminismViolationCategory.WallClock);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsDateTimeUtcNow()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "var now = DateTime.UtcNow;";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "DateTime.UtcNow");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsRandomClass()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "var rng = new Random();";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "Random" &&
|
||||
v.Category == DeterminismViolationCategory.RandomNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsGuidNewGuid()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "var id = Guid.NewGuid();";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "Guid.NewGuid" &&
|
||||
v.Category == DeterminismViolationCategory.GuidGeneration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsHttpClient()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "private readonly HttpClient _client = new();";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "HttpClient" &&
|
||||
v.Category == DeterminismViolationCategory.NetworkAccess &&
|
||||
v.Severity == DeterminismViolationSeverity.Critical);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsFileOperations()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = """
|
||||
var content = File.ReadAllText("test.txt");
|
||||
File.WriteAllText("out.txt", content);
|
||||
""";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().HaveCount(2);
|
||||
result.Violations.Should().Contain(v => v.ViolationType == "File.Read");
|
||||
result.Violations.Should().Contain(v => v.ViolationType == "File.Write");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_DetectsEnvironmentVariableAccess()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "var path = Environment.GetEnvironmentVariable(\"PATH\");";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v =>
|
||||
v.ViolationType == "Environment.GetEnvironmentVariable");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_IgnoresComments()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = """
|
||||
// DateTime.Now is not allowed
|
||||
/* DateTime.UtcNow either */
|
||||
* Random comment
|
||||
""";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().BeEmpty();
|
||||
result.Passed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_RespectsExcludePatterns()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = "var now = DateTime.Now;";
|
||||
var options = DeterminismGuardOptions.Default with
|
||||
{
|
||||
ExcludePatterns = ["test.cs"]
|
||||
};
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", options);
|
||||
|
||||
result.Passed.Should().BeTrue();
|
||||
result.Violations.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_PassesCleanCode()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = """
|
||||
public class PolicyEvaluator
|
||||
{
|
||||
public bool Evaluate(PolicyContext context)
|
||||
{
|
||||
return context.Severity.Score > 7.0m;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "evaluator.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Passed.Should().BeTrue();
|
||||
result.Violations.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeSource_TracksLineNumbers()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var source = """
|
||||
public class Test
|
||||
{
|
||||
public void Method()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var result = analyzer.AnalyzeSource(source, "test.cs", DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().ContainSingle(v => v.LineNumber == 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzeMultiple_AggregatesViolations()
|
||||
{
|
||||
var analyzer = new ProhibitedPatternAnalyzer();
|
||||
var sources = new[]
|
||||
{
|
||||
("file1.cs", "var now = DateTime.Now;"),
|
||||
("file2.cs", "var rng = new Random();"),
|
||||
("file3.cs", "var id = Guid.NewGuid();")
|
||||
};
|
||||
|
||||
var result = analyzer.AnalyzeMultiple(
|
||||
sources.Select(s => (s.Item2, s.Item1)),
|
||||
DeterminismGuardOptions.Default);
|
||||
|
||||
result.Violations.Should().HaveCount(3);
|
||||
result.Violations.Select(v => v.SourceFile).Should()
|
||||
.BeEquivalentTo(["file1.cs", "file2.cs", "file3.cs"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeterminismGuardService Tests
|
||||
|
||||
[Fact]
|
||||
public void CreateScope_ReturnsFixedTimestamp()
|
||||
{
|
||||
var guard = new DeterminismGuardService();
|
||||
var timestamp = new DateTimeOffset(2025, 1, 15, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
using var scope = guard.CreateScope("test-scope", timestamp);
|
||||
|
||||
scope.GetTimestamp().Should().Be(timestamp);
|
||||
scope.EvaluationTimestamp.Should().Be(timestamp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateScope_TracksViolations()
|
||||
{
|
||||
var guard = new DeterminismGuardService();
|
||||
using var scope = guard.CreateScope("test-scope", DateTimeOffset.UtcNow);
|
||||
|
||||
var violation = new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "Test",
|
||||
Message = "Test violation",
|
||||
Severity = DeterminismViolationSeverity.Warning
|
||||
};
|
||||
|
||||
scope.ReportViolation(violation);
|
||||
|
||||
scope.GetViolations().Should().ContainSingle(v => v.Message == "Test violation");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateScope_ThrowsOnBlockingViolationWhenEnforcementEnabled()
|
||||
{
|
||||
var options = new DeterminismGuardOptions
|
||||
{
|
||||
EnforcementEnabled = true,
|
||||
FailOnSeverity = DeterminismViolationSeverity.Error
|
||||
};
|
||||
var guard = new DeterminismGuardService(options);
|
||||
using var scope = guard.CreateScope("test-scope", DateTimeOffset.UtcNow);
|
||||
|
||||
var violation = new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "Test",
|
||||
Message = "Blocking violation",
|
||||
Severity = DeterminismViolationSeverity.Error
|
||||
};
|
||||
|
||||
var act = () => scope.ReportViolation(violation);
|
||||
|
||||
act.Should().Throw<DeterminismViolationException>()
|
||||
.Which.Violation.Should().Be(violation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateScope_DoesNotThrowWhenEnforcementDisabled()
|
||||
{
|
||||
var options = new DeterminismGuardOptions
|
||||
{
|
||||
EnforcementEnabled = false
|
||||
};
|
||||
var guard = new DeterminismGuardService(options);
|
||||
using var scope = guard.CreateScope("test-scope", DateTimeOffset.UtcNow);
|
||||
|
||||
var violation = new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "Test",
|
||||
Message = "Should not throw",
|
||||
Severity = DeterminismViolationSeverity.Critical
|
||||
};
|
||||
|
||||
var act = () => scope.ReportViolation(violation);
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Complete_ReturnsAnalysisResult()
|
||||
{
|
||||
var guard = new DeterminismGuardService();
|
||||
using var scope = guard.CreateScope("test-scope", DateTimeOffset.UtcNow);
|
||||
|
||||
scope.ReportViolation(new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.RandomNumber,
|
||||
ViolationType = "Test",
|
||||
Message = "Warning violation",
|
||||
Severity = DeterminismViolationSeverity.Warning
|
||||
});
|
||||
|
||||
var result = scope.Complete();
|
||||
|
||||
result.Passed.Should().BeTrue(); // Only warnings, no errors
|
||||
result.Violations.Should().HaveCount(1);
|
||||
result.CountBySeverity.Should().ContainKey(DeterminismViolationSeverity.Warning);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeterministicTimeProvider Tests
|
||||
|
||||
[Fact]
|
||||
public void DeterministicTimeProvider_ReturnsFixedTimestamp()
|
||||
{
|
||||
var fixedTime = new DateTimeOffset(2025, 6, 15, 10, 30, 0, TimeSpan.Zero);
|
||||
var provider = new DeterministicTimeProvider(fixedTime);
|
||||
|
||||
provider.GetUtcNow().Should().Be(fixedTime);
|
||||
provider.GetUtcNow().Should().Be(fixedTime); // Same value on repeated calls
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeterministicTimeProvider_ReturnsUtcTimeZone()
|
||||
{
|
||||
var provider = new DeterministicTimeProvider(DateTimeOffset.UtcNow);
|
||||
|
||||
provider.LocalTimeZone.Should().Be(TimeZoneInfo.Utc);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GuardedPolicyEvaluator Tests
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_ReturnsResultWithViolations()
|
||||
{
|
||||
var evaluator = new GuardedPolicyEvaluator();
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
var result = evaluator.Evaluate("test-scope", timestamp, scope =>
|
||||
{
|
||||
scope.ReportViolation(new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "Test",
|
||||
Message = "Test warning",
|
||||
Severity = DeterminismViolationSeverity.Warning
|
||||
});
|
||||
return 42;
|
||||
});
|
||||
|
||||
result.Succeeded.Should().BeTrue();
|
||||
result.Result.Should().Be(42);
|
||||
result.HasViolations.Should().BeTrue();
|
||||
result.Violations.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_CapturesBlockingViolation()
|
||||
{
|
||||
var options = new DeterminismGuardOptions
|
||||
{
|
||||
EnforcementEnabled = true,
|
||||
FailOnSeverity = DeterminismViolationSeverity.Error
|
||||
};
|
||||
var evaluator = new GuardedPolicyEvaluator(options);
|
||||
|
||||
var result = evaluator.Evaluate("test-scope", DateTimeOffset.UtcNow, scope =>
|
||||
{
|
||||
scope.ReportViolation(new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.NetworkAccess,
|
||||
ViolationType = "HttpClient",
|
||||
Message = "Network access blocked",
|
||||
Severity = DeterminismViolationSeverity.Critical
|
||||
});
|
||||
return "should not return";
|
||||
});
|
||||
|
||||
result.Succeeded.Should().BeFalse();
|
||||
result.WasBlocked.Should().BeTrue();
|
||||
result.BlockingViolation.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePolicySource_ReturnsViolations()
|
||||
{
|
||||
var evaluator = new GuardedPolicyEvaluator();
|
||||
var source = "var now = DateTime.Now;";
|
||||
|
||||
var result = evaluator.ValidatePolicySource(source, "policy.cs");
|
||||
|
||||
result.Violations.Should().ContainSingle();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateAsync_WorksWithAsyncCode()
|
||||
{
|
||||
var evaluator = new GuardedPolicyEvaluator();
|
||||
|
||||
var result = await evaluator.EvaluateAsync("async-scope", DateTimeOffset.UtcNow, async scope =>
|
||||
{
|
||||
await Task.Delay(1);
|
||||
return "async result";
|
||||
});
|
||||
|
||||
result.Succeeded.Should().BeTrue();
|
||||
result.Result.Should().Be("async result");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeterminismGuardOptions Tests
|
||||
|
||||
[Fact]
|
||||
public void Default_HasEnforcementEnabled()
|
||||
{
|
||||
DeterminismGuardOptions.Default.EnforcementEnabled.Should().BeTrue();
|
||||
DeterminismGuardOptions.Default.FailOnSeverity.Should().Be(DeterminismViolationSeverity.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Development_HasEnforcementDisabled()
|
||||
{
|
||||
DeterminismGuardOptions.Development.EnforcementEnabled.Should().BeFalse();
|
||||
DeterminismGuardOptions.Development.FailOnSeverity.Should().Be(DeterminismViolationSeverity.Critical);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user