chore(sprints): archive 20260226 advisories and expand deterministic tests
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Replay.Core;
|
||||
using StellaOps.Scanner.Cache;
|
||||
using StellaOps.Scanner.Cache.Abstractions;
|
||||
using StellaOps.Scanner.Cache.FileCas;
|
||||
using StellaOps.Scanner.Core;
|
||||
using StellaOps.Scanner.ProofSpine;
|
||||
using StellaOps.Scanner.Reachability.Slices;
|
||||
using StellaOps.Scanner.WebService.Services;
|
||||
using StellaOps.TestKit;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class SliceQueryServiceRetrievalTests : IDisposable
|
||||
{
|
||||
private readonly string _tempRoot;
|
||||
private readonly IFileContentAddressableStore _cas;
|
||||
private readonly SliceQueryService _service;
|
||||
|
||||
public SliceQueryServiceRetrievalTests()
|
||||
{
|
||||
_tempRoot = Path.Combine(Path.GetTempPath(), $"stella-slice-query-{Guid.NewGuid():N}");
|
||||
|
||||
var scannerCacheOptions = Microsoft.Extensions.Options.Options.Create(new ScannerCacheOptions
|
||||
{
|
||||
RootPath = _tempRoot,
|
||||
FileTtl = TimeSpan.FromDays(1),
|
||||
MaxBytes = 1024 * 1024 * 10
|
||||
});
|
||||
_cas = new FileContentAddressableStore(
|
||||
scannerCacheOptions,
|
||||
NullLogger<FileContentAddressableStore>.Instance,
|
||||
TimeProvider.System);
|
||||
|
||||
var cryptoHash = CryptoHashFactory.CreateDefault();
|
||||
var sliceHasher = new SliceHasher(cryptoHash);
|
||||
var sliceSigner = new SliceDsseSigner(
|
||||
new TestDsseSigningService(),
|
||||
new TestCryptoProfile(),
|
||||
sliceHasher,
|
||||
TimeProvider.System);
|
||||
var casStorage = new SliceCasStorage(sliceHasher, sliceSigner, cryptoHash);
|
||||
|
||||
_service = new SliceQueryService(
|
||||
cache: new SliceCache(Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions())),
|
||||
extractor: new SliceExtractor(new VerdictComputer()),
|
||||
casStorage: casStorage,
|
||||
diffComputer: new StellaOps.Scanner.Reachability.Slices.Replay.SliceDiffComputer(),
|
||||
hasher: sliceHasher,
|
||||
cas: _cas,
|
||||
scannerCacheOptions: scannerCacheOptions,
|
||||
scanRepo: new NullScanMetadataRepository(),
|
||||
timeProvider: TimeProvider.System,
|
||||
options: Microsoft.Extensions.Options.Options.Create(new SliceQueryServiceOptions()),
|
||||
logger: NullLogger<SliceQueryService>.Instance);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceAsync_WhenSliceExistsInCas_ReturnsSlice()
|
||||
{
|
||||
const string digestHex = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(CreateSlice("scan-a"));
|
||||
await _cas.PutAsync(new FileCasPutRequest(digestHex, new MemoryStream(bytes)));
|
||||
|
||||
var result = await _service.GetSliceAsync($"sha256:{digestHex}");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("scan-a", result!.Manifest.ScanId);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceAsync_WhenSliceMissingInCas_ReturnsNull()
|
||||
{
|
||||
var result = await _service.GetSliceAsync("sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceAsync_WhenSlicePayloadCorrupt_ThrowsDeterministicError()
|
||||
{
|
||||
const string digestHex = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
|
||||
await _cas.PutAsync(new FileCasPutRequest(digestHex, new MemoryStream(Encoding.UTF8.GetBytes("not-json"))));
|
||||
|
||||
var action = async () => await _service.GetSliceAsync($"sha256:{digestHex}");
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(action);
|
||||
Assert.Contains("corrupt", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceDsseAsync_WhenEnvelopeExists_ReturnsEnvelope()
|
||||
{
|
||||
const string digestHex = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
|
||||
var envelope = new DsseEnvelope(
|
||||
PayloadType: "application/vnd.stellaops.slice+json",
|
||||
Payload: Convert.ToBase64String(Encoding.UTF8.GetBytes("{}")),
|
||||
Signatures: [new DsseSignature("k1", "s1")]);
|
||||
var envelopeBytes = JsonSerializer.SerializeToUtf8Bytes(envelope);
|
||||
await _cas.PutAsync(new FileCasPutRequest($"{digestHex}.dsse", new MemoryStream(envelopeBytes)));
|
||||
|
||||
var result = await _service.GetSliceDsseAsync($"sha256:{digestHex}");
|
||||
|
||||
Assert.NotNull(result);
|
||||
var typed = Assert.IsType<DsseEnvelope>(result);
|
||||
Assert.Equal("application/vnd.stellaops.slice+json", typed.PayloadType);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceDsseAsync_WhenEnvelopeCorrupt_ThrowsDeterministicError()
|
||||
{
|
||||
const string digestHex = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
||||
await _cas.PutAsync(new FileCasPutRequest($"{digestHex}.dsse", new MemoryStream(Encoding.UTF8.GetBytes("{broken-json"))));
|
||||
|
||||
var action = async () => await _service.GetSliceDsseAsync($"sha256:{digestHex}");
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(action);
|
||||
Assert.Contains("corrupt", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSliceDsseAsync_WhenEnvelopeMissing_ReturnsNull()
|
||||
{
|
||||
var result = await _service.GetSliceDsseAsync("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(_tempRoot, recursive: true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static ReachabilitySlice CreateSlice(string scanId)
|
||||
{
|
||||
return new ReachabilitySlice
|
||||
{
|
||||
Inputs = new SliceInputs
|
||||
{
|
||||
GraphDigest = "sha256:graph"
|
||||
},
|
||||
Query = new SliceQuery
|
||||
{
|
||||
CveId = "CVE-2026-0001",
|
||||
TargetSymbols = ImmutableArray.Create("target")
|
||||
},
|
||||
Subgraph = new SliceSubgraph
|
||||
{
|
||||
Nodes = ImmutableArray<SliceNode>.Empty,
|
||||
Edges = ImmutableArray<SliceEdge>.Empty
|
||||
},
|
||||
Verdict = new SliceVerdict
|
||||
{
|
||||
Status = SliceVerdictStatus.Unknown,
|
||||
Confidence = 0.4
|
||||
},
|
||||
Manifest = ScanManifest.CreateBuilder(scanId, "sha256:artifact")
|
||||
.WithScannerVersion("1.0.0")
|
||||
.WithWorkerVersion("1.0.0")
|
||||
.WithConcelierSnapshot("sha256:concelier")
|
||||
.WithExcititorSnapshot("sha256:excititor")
|
||||
.WithLatticePolicyHash("sha256:policy")
|
||||
.Build()
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class TestCryptoProfile : ICryptoProfile
|
||||
{
|
||||
public string KeyId => "test-key";
|
||||
|
||||
public string Algorithm => "hs256";
|
||||
}
|
||||
|
||||
private sealed class TestDsseSigningService : IDsseSigningService
|
||||
{
|
||||
public Task<DsseEnvelope> SignAsync(object payload, string payloadType, ICryptoProfile cryptoProfile, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var payloadBytes = CanonicalJson.SerializeToUtf8Bytes(payload);
|
||||
var envelope = new DsseEnvelope(
|
||||
PayloadType: payloadType,
|
||||
Payload: Convert.ToBase64String(payloadBytes),
|
||||
Signatures: [new DsseSignature(cryptoProfile.KeyId, "sig")]);
|
||||
return Task.FromResult(envelope);
|
||||
}
|
||||
|
||||
public Task<DsseVerificationOutcome> VerifyAsync(DsseEnvelope envelope, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new DsseVerificationOutcome(true, true, null));
|
||||
}
|
||||
|
||||
private sealed class NullScanMetadataRepository : IScanMetadataRepository
|
||||
{
|
||||
public Task<ScanMetadata?> GetScanMetadataAsync(string scanId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<ScanMetadata?>(null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user