Add unit tests and implementations for MongoDB index models and OpenAPI metadata
- Implemented `MongoIndexModelTests` to verify index models for various stores. - Created `OpenApiMetadataFactory` with methods to generate OpenAPI metadata. - Added tests for `OpenApiMetadataFactory` to ensure expected defaults and URL overrides. - Introduced `ObserverSurfaceSecrets` and `WebhookSurfaceSecrets` for managing secrets. - Developed `RuntimeSurfaceFsClient` and `WebhookSurfaceFsClient` for manifest retrieval. - Added dependency injection tests for `SurfaceEnvironmentRegistration` in both Observer and Webhook contexts. - Implemented tests for secret resolution in `ObserverSurfaceSecretsTests` and `WebhookSurfaceSecretsTests`. - Created `EnsureLinkNotMergeCollectionsMigrationTests` to validate MongoDB migration logic. - Added project files for MongoDB tests and NuGet package mirroring.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Storage.Mongo;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.Excititor.WebService.Services;
|
||||
|
||||
internal interface IVexEvidenceChunkService
|
||||
{
|
||||
Task<VexEvidenceChunkResult> QueryAsync(VexEvidenceChunkRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
internal sealed record VexEvidenceChunkRequest(
|
||||
string Tenant,
|
||||
string VulnerabilityId,
|
||||
string ProductKey,
|
||||
ImmutableHashSet<string> ProviderIds,
|
||||
ImmutableHashSet<VexClaimStatus> Statuses,
|
||||
DateTimeOffset? Since,
|
||||
int Limit);
|
||||
|
||||
internal sealed record VexEvidenceChunkResult(
|
||||
IReadOnlyList<VexEvidenceChunkResponse> Chunks,
|
||||
bool Truncated,
|
||||
int TotalCount,
|
||||
DateTimeOffset GeneratedAtUtc);
|
||||
|
||||
internal sealed class VexEvidenceChunkService : IVexEvidenceChunkService
|
||||
{
|
||||
private readonly IVexClaimStore _claimStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public VexEvidenceChunkService(IVexClaimStore claimStore, TimeProvider timeProvider)
|
||||
{
|
||||
_claimStore = claimStore ?? throw new ArgumentNullException(nameof(claimStore));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
}
|
||||
|
||||
public async Task<VexEvidenceChunkResult> QueryAsync(VexEvidenceChunkRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var claims = await _claimStore
|
||||
.FindAsync(request.VulnerabilityId, request.ProductKey, request.Since, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var filtered = claims
|
||||
.Where(claim => MatchesProvider(claim, request.ProviderIds))
|
||||
.Where(claim => MatchesStatus(claim, request.Statuses))
|
||||
.OrderByDescending(claim => claim.LastSeen)
|
||||
.ToList();
|
||||
|
||||
var total = filtered.Count;
|
||||
if (filtered.Count > request.Limit)
|
||||
{
|
||||
filtered = filtered.Take(request.Limit).ToList();
|
||||
}
|
||||
|
||||
var chunks = filtered
|
||||
.Select(MapChunk)
|
||||
.ToList();
|
||||
|
||||
return new VexEvidenceChunkResult(
|
||||
chunks,
|
||||
total > request.Limit,
|
||||
total,
|
||||
_timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
private static bool MatchesProvider(VexClaim claim, ImmutableHashSet<string> providers)
|
||||
=> providers.Count == 0 || providers.Contains(claim.ProviderId, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static bool MatchesStatus(VexClaim claim, ImmutableHashSet<VexClaimStatus> statuses)
|
||||
=> statuses.Count == 0 || statuses.Contains(claim.Status);
|
||||
|
||||
private static VexEvidenceChunkResponse MapChunk(VexClaim claim)
|
||||
{
|
||||
var observationId = string.Create(CultureInfo.InvariantCulture, $"{claim.ProviderId}:{claim.Document.Digest}");
|
||||
var linksetId = string.Create(CultureInfo.InvariantCulture, $"{claim.VulnerabilityId}:{claim.Product.Key}");
|
||||
|
||||
var scope = new VexEvidenceChunkScope(
|
||||
claim.Product.Key,
|
||||
claim.Product.Name,
|
||||
claim.Product.Version,
|
||||
claim.Product.Purl,
|
||||
claim.Product.Cpe,
|
||||
claim.Product.ComponentIdentifiers);
|
||||
|
||||
var document = new VexEvidenceChunkDocument(
|
||||
claim.Document.Digest,
|
||||
claim.Document.Format.ToString().ToLowerInvariant(),
|
||||
claim.Document.SourceUri.ToString(),
|
||||
claim.Document.Revision);
|
||||
|
||||
var signature = claim.Document.Signature is null
|
||||
? null
|
||||
: new VexEvidenceChunkSignature(
|
||||
claim.Document.Signature.Type,
|
||||
claim.Document.Signature.Subject,
|
||||
claim.Document.Signature.Issuer,
|
||||
claim.Document.Signature.KeyId,
|
||||
claim.Document.Signature.VerifiedAt,
|
||||
claim.Document.Signature.TransparencyLogReference);
|
||||
|
||||
var scopeScore = claim.Confidence?.Score ?? claim.Signals?.Severity?.Score;
|
||||
|
||||
return new VexEvidenceChunkResponse(
|
||||
observationId,
|
||||
linksetId,
|
||||
claim.VulnerabilityId,
|
||||
claim.Product.Key,
|
||||
claim.ProviderId,
|
||||
claim.Status.ToString(),
|
||||
claim.Justification?.ToString(),
|
||||
claim.Detail,
|
||||
scopeScore,
|
||||
claim.FirstSeen,
|
||||
claim.LastSeen,
|
||||
scope,
|
||||
document,
|
||||
signature,
|
||||
claim.AdditionalMetadata);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user