part #2
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Replay.Loaders;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public sealed class FeedSnapshotLoaderTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_InvalidDigest_ThrowsFormatException()
|
||||
{
|
||||
var loader = new FeedSnapshotLoader(new FeedStorageStub(null), NullLogger<FeedSnapshotLoader>.Instance);
|
||||
var digest = SnapshotTestData.CreateInvalidDigest('a');
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(digest);
|
||||
|
||||
await action.Should().ThrowAsync<FormatException>();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_ShortDigest_ThrowsFormatException()
|
||||
{
|
||||
var loader = new FeedSnapshotLoader(new FeedStorageStub(null), NullLogger<FeedSnapshotLoader>.Instance);
|
||||
var digest = new string('a', 63);
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(digest);
|
||||
|
||||
await action.Should().ThrowAsync<FormatException>();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_DigestMismatch_ThrowsException()
|
||||
{
|
||||
var snapshot = SnapshotTestData.CreateFeedSnapshot(SnapshotTestData.CreateValidDigest('b'));
|
||||
var actualDigest = SnapshotTestData.ComputeDigest(snapshot);
|
||||
var expectedDigest = SnapshotTestData.CreateDifferentDigest(actualDigest);
|
||||
var loader = new FeedSnapshotLoader(new FeedStorageStub(snapshot), NullLogger<FeedSnapshotLoader>.Instance);
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(expectedDigest);
|
||||
|
||||
await action.Should().ThrowAsync<DigestMismatchException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
internal sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _now;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset now) => _now = now;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _now;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Replay.Loaders;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public sealed class PolicySnapshotLoaderTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_InvalidDigest_ThrowsFormatException()
|
||||
{
|
||||
var loader = new PolicySnapshotLoader(new PolicyStorageStub(null), NullLogger<PolicySnapshotLoader>.Instance);
|
||||
var digest = SnapshotTestData.CreateInvalidDigest('c');
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(digest);
|
||||
|
||||
await action.Should().ThrowAsync<FormatException>();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_ShortDigest_ThrowsFormatException()
|
||||
{
|
||||
var loader = new PolicySnapshotLoader(new PolicyStorageStub(null), NullLogger<PolicySnapshotLoader>.Instance);
|
||||
var digest = new string('c', 10);
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(digest);
|
||||
|
||||
await action.Should().ThrowAsync<FormatException>();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadByDigestAsync_DigestMismatch_ThrowsException()
|
||||
{
|
||||
var snapshot = SnapshotTestData.CreatePolicySnapshot(SnapshotTestData.CreateValidDigest('d'));
|
||||
var actualDigest = SnapshotTestData.ComputeDigest(snapshot);
|
||||
var expectedDigest = SnapshotTestData.CreateDifferentDigest(actualDigest);
|
||||
var loader = new PolicySnapshotLoader(new PolicyStorageStub(snapshot), NullLogger<PolicySnapshotLoader>.Instance);
|
||||
|
||||
var action = () => loader.LoadByDigestAsync(expectedDigest);
|
||||
|
||||
await action.Should().ThrowAsync<DigestMismatchException>();
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,17 @@ internal static class ReplayEngineTestFixtures
|
||||
new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
internal static readonly DateTimeOffset FixedFeedSnapshotAt = FixedTimestamp.AddHours(-1);
|
||||
|
||||
internal static ReplayEngine CreateEngine()
|
||||
internal static ReplayEngine CreateEngine() =>
|
||||
CreateEngine(new FixedTimeProvider(FixedTimestamp));
|
||||
|
||||
internal static ReplayEngine CreateEngine(TimeProvider timeProvider)
|
||||
{
|
||||
return new ReplayEngine(
|
||||
new FakeFeedLoader(),
|
||||
new FakePolicyLoader(),
|
||||
new FakeScannerFactory(),
|
||||
NullLogger<ReplayEngine>.Instance);
|
||||
NullLogger<ReplayEngine>.Instance,
|
||||
timeProvider);
|
||||
}
|
||||
|
||||
internal static RunManifest CreateManifest()
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Engine;
|
||||
using StellaOps.Replay.Models;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public partial class ReplayEngineTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_IdenticalResults_ReturnsTrue()
|
||||
{
|
||||
var engine = ReplayEngineTestFixtures.CreateEngine();
|
||||
var result1 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
var result2 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
|
||||
var check = engine.CheckDeterminism(result1, result2);
|
||||
|
||||
check.IsDeterministic.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_DifferentResults_ReturnsDifferences()
|
||||
{
|
||||
var engine = ReplayEngineTestFixtures.CreateEngine();
|
||||
var result1 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictJson = "{\"score\":100}",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
var result2 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictJson = "{\"score\":99}",
|
||||
VerdictDigest = "def456",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
|
||||
var check = engine.CheckDeterminism(result1, result2);
|
||||
|
||||
check.IsDeterministic.Should().BeFalse();
|
||||
check.Differences.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Models;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public partial class ReplayEngineTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Replay_InvalidManifest_UsesTimeProviderAsync()
|
||||
{
|
||||
var fixedTime = new DateTimeOffset(2026, 1, 2, 0, 0, 0, TimeSpan.Zero);
|
||||
var engine = ReplayEngineTestFixtures.CreateEngine(new FixedTimeProvider(fixedTime));
|
||||
var manifest = ReplayEngineTestFixtures.CreateManifest() with { RunId = "" };
|
||||
|
||||
var result = await engine.ReplayAsync(manifest, new ReplayOptions());
|
||||
|
||||
result.Success.Should().BeFalse();
|
||||
result.ExecutedAt.Should().Be(fixedTime);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public class ReplayEngineTests
|
||||
public partial class ReplayEngineTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
@@ -38,57 +38,4 @@ public class ReplayEngineTests
|
||||
|
||||
result1.VerdictDigest.Should().NotBe(result2.VerdictDigest);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_IdenticalResults_ReturnsTrue()
|
||||
{
|
||||
var engine = ReplayEngineTestFixtures.CreateEngine();
|
||||
var result1 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
var result2 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
|
||||
var check = engine.CheckDeterminism(result1, result2);
|
||||
|
||||
check.IsDeterministic.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_DifferentResults_ReturnsDifferences()
|
||||
{
|
||||
var engine = ReplayEngineTestFixtures.CreateEngine();
|
||||
var result1 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictJson = "{\"score\":100}",
|
||||
VerdictDigest = "abc123",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
var result2 = new ReplayResult
|
||||
{
|
||||
RunId = "1",
|
||||
VerdictJson = "{\"score\":99}",
|
||||
VerdictDigest = "def456",
|
||||
Success = true,
|
||||
ExecutedAt = ReplayEngineTestFixtures.FixedTimestamp
|
||||
};
|
||||
|
||||
var check = engine.CheckDeterminism(result1, result2);
|
||||
|
||||
check.IsDeterministic.Should().BeFalse();
|
||||
check.Differences.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using StellaOps.Replay.Loaders;
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
internal sealed class FeedStorageStub : IFeedStorage
|
||||
{
|
||||
private readonly FeedSnapshot? _snapshot;
|
||||
|
||||
public FeedStorageStub(FeedSnapshot? snapshot) => _snapshot = snapshot;
|
||||
|
||||
public Task<FeedSnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default)
|
||||
=> Task.FromResult(_snapshot);
|
||||
}
|
||||
|
||||
internal sealed class PolicyStorageStub : IPolicyStorage
|
||||
{
|
||||
private readonly PolicySnapshot? _snapshot;
|
||||
|
||||
public PolicyStorageStub(PolicySnapshot? snapshot) => _snapshot = snapshot;
|
||||
|
||||
public Task<PolicySnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default)
|
||||
=> Task.FromResult(_snapshot);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using StellaOps.Canonicalization.Json;
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
internal static class SnapshotTestData
|
||||
{
|
||||
internal static readonly DateTimeOffset FixedSnapshotAt =
|
||||
new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
internal static FeedSnapshot CreateFeedSnapshot(string digest) =>
|
||||
new FeedSnapshot("nvd", "v1", digest, FixedSnapshotAt);
|
||||
|
||||
internal static PolicySnapshot CreatePolicySnapshot(string digest) =>
|
||||
new PolicySnapshot("1.0.0", digest, ImmutableArray<string>.Empty);
|
||||
|
||||
internal static string ComputeDigest<T>(T value)
|
||||
{
|
||||
var json = CanonicalJsonSerializer.Serialize(value);
|
||||
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(json))).ToLowerInvariant();
|
||||
}
|
||||
|
||||
internal static string CreateValidDigest(char fill) => new string(fill, 64);
|
||||
|
||||
internal static string CreateInvalidDigest(char fill) => new string(fill, 63) + "g";
|
||||
|
||||
internal static string CreateDifferentDigest(string digest)
|
||||
{
|
||||
var last = digest[^1];
|
||||
var replacement = last == 'a' ? 'b' : 'a';
|
||||
return digest[..^1] + replacement;
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| REMED-03 | DONE | Tier 0 remediation (usings sorted, deterministic test data, warnings as errors); dotnet test passed 2026-02-02. |
|
||||
| REMED-04 | DONE | Async naming updates; ConfigureAwait(false) skipped in tests per xUnit1030; dotnet test passed 2026-02-02. |
|
||||
| REMED-05 | DONE | File split to keep tests <= 100 lines; dotnet test passed 2026-02-02. |
|
||||
| REMED-07 | DONE | 2026-02-05; replay tests split into partials, loader validation/digest mismatch coverage + failure timestamp test added; dotnet test src/__Libraries/__Tests/StellaOps.Replay.Tests/StellaOps.Replay.Tests.csproj passed (11 tests). |
|
||||
|
||||
Reference in New Issue
Block a user