feat(scanner): Implement Deno analyzer and associated tests
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added Deno analyzer with comprehensive metadata and evidence structure. - Created a detailed implementation plan for Sprint 130 focusing on Deno analyzer. - Introduced AdvisoryAiGuardrailOptions for managing guardrail configurations. - Developed GuardrailPhraseLoader for loading blocked phrases from JSON files. - Implemented tests for AdvisoryGuardrailOptions binding and phrase loading. - Enhanced telemetry for Advisory AI with metrics tracking. - Added VexObservationProjectionService for querying VEX observations. - Created extensive tests for VexObservationProjectionService functionality. - Introduced Ruby language analyzer with tests for simple and complex workspaces. - Added Ruby application fixtures for testing purposes.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Storage.Mongo;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexObservationProjectionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task QueryAsync_FiltersByProviderAndStatus()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 11, 10, 12, 0, 0, TimeSpan.Zero);
|
||||
var claims = new[]
|
||||
{
|
||||
CreateClaim("provider-a", VexClaimStatus.Affected, now.AddHours(-6), now.AddHours(-5)),
|
||||
CreateClaim("provider-b", VexClaimStatus.NotAffected, now.AddHours(-4), now.AddHours(-3))
|
||||
};
|
||||
|
||||
var store = new FakeClaimStore(claims);
|
||||
var service = new VexObservationProjectionService(store, new FixedTimeProvider(now));
|
||||
var request = new VexObservationProjectionRequest(
|
||||
Tenant: "tenant-a",
|
||||
VulnerabilityId: "CVE-2025-0001",
|
||||
ProductKey: "pkg:docker/demo",
|
||||
ProviderIds: ImmutableHashSet.Create("provider-b"),
|
||||
Statuses: ImmutableHashSet.Create(VexClaimStatus.NotAffected),
|
||||
Since: null,
|
||||
Limit: 10);
|
||||
|
||||
var result = await service.QueryAsync(request, CancellationToken.None);
|
||||
|
||||
result.Truncated.Should().BeFalse();
|
||||
result.TotalCount.Should().Be(1);
|
||||
result.GeneratedAtUtc.Should().Be(now);
|
||||
var statement = result.Statements.Single();
|
||||
statement.ProviderId.Should().Be("provider-b");
|
||||
statement.Status.Should().Be(VexClaimStatus.NotAffected);
|
||||
statement.Justification.Should().Be(VexJustification.ComponentNotPresent);
|
||||
statement.Anchors.Should().ContainSingle().Which.Should().Be("/statements/0");
|
||||
statement.Scope.ComponentIdentifiers.Should().Contain("demo:component");
|
||||
statement.Document.Digest.Should().Contain("provider-b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryAsync_TruncatesWhenLimitExceeded()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var claims = Enumerable.Range(0, 3)
|
||||
.Select(index => CreateClaim($"provider-{index}", VexClaimStatus.NotAffected, now.AddHours(-index - 2), now.AddHours(-index - 1)))
|
||||
.ToArray();
|
||||
|
||||
var store = new FakeClaimStore(claims);
|
||||
var service = new VexObservationProjectionService(store, new FixedTimeProvider(now));
|
||||
var request = new VexObservationProjectionRequest(
|
||||
Tenant: "tenant-a",
|
||||
VulnerabilityId: "CVE-2025-0001",
|
||||
ProductKey: "pkg:docker/demo",
|
||||
ProviderIds: ImmutableHashSet<string>.Empty,
|
||||
Statuses: ImmutableHashSet<VexClaimStatus>.Empty,
|
||||
Since: null,
|
||||
Limit: 2);
|
||||
|
||||
var result = await service.QueryAsync(request, CancellationToken.None);
|
||||
|
||||
result.Truncated.Should().BeTrue();
|
||||
result.TotalCount.Should().Be(3);
|
||||
result.Statements.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
private static VexClaim CreateClaim(string providerId, VexClaimStatus status, DateTimeOffset firstSeen, DateTimeOffset lastSeen)
|
||||
{
|
||||
var product = new VexProduct(
|
||||
key: "pkg:docker/demo",
|
||||
name: "demo",
|
||||
version: "1.0.0",
|
||||
purl: "pkg:docker/demo@1.0.0",
|
||||
cpe: "cpe:/a:demo:demo:1.0.0",
|
||||
componentIdentifiers: new[] { "demo:component" });
|
||||
|
||||
var document = new VexClaimDocument(
|
||||
VexDocumentFormat.Csaf,
|
||||
$"sha256:{providerId}",
|
||||
new Uri("https://example.org/vex.json"),
|
||||
revision: "v1");
|
||||
|
||||
var metadata = ImmutableDictionary<string, string>.Empty.Add("json_pointer", "/statements/0");
|
||||
|
||||
return new VexClaim(
|
||||
"CVE-2025-0001",
|
||||
providerId,
|
||||
product,
|
||||
status,
|
||||
document,
|
||||
firstSeen,
|
||||
lastSeen,
|
||||
justification: VexJustification.ComponentNotPresent,
|
||||
detail: "not affected",
|
||||
confidence: null,
|
||||
signals: null,
|
||||
additionalMetadata: metadata);
|
||||
}
|
||||
|
||||
private sealed class FakeClaimStore : IVexClaimStore
|
||||
{
|
||||
private readonly IReadOnlyCollection<VexClaim> _claims;
|
||||
|
||||
public FakeClaimStore(IReadOnlyCollection<VexClaim> claims)
|
||||
{
|
||||
_claims = claims;
|
||||
}
|
||||
|
||||
public ValueTask AppendAsync(IEnumerable<VexClaim> claims, DateTimeOffset observedAt, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public ValueTask<IReadOnlyCollection<VexClaim>> FindAsync(string vulnerabilityId, string productKey, DateTimeOffset? since, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
||||
{
|
||||
var query = _claims
|
||||
.Where(claim => string.Equals(claim.VulnerabilityId, vulnerabilityId, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(claim => string.Equals(claim.Product.Key, productKey, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (since.HasValue)
|
||||
{
|
||||
query = query.Where(claim => claim.LastSeen >= since.Value);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult<IReadOnlyCollection<VexClaim>>(query.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _timestamp;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset timestamp)
|
||||
{
|
||||
_timestamp = timestamp;
|
||||
}
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _timestamp;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user