feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies. - Documented roles and guidelines in AGENTS.md for Scheduler module. - Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs. - Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics. - Developed API endpoints for managing resolver jobs and retrieving metrics. - Defined models for resolver job requests and responses. - Integrated dependency injection for resolver job services. - Implemented ImpactIndexSnapshot for persisting impact index data. - Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring. - Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService. - Created dotnet-filter.sh script to handle command-line arguments for dotnet. - Established nuget-prime project for managing package downloads.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace StellaOps.Concelier.WebService.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal linkset document used only for seeding the Mongo collection in WebService integration tests.
|
||||
/// Matches the shape written by the linkset ingestion pipeline.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetDocument
|
||||
{
|
||||
[BsonElement("tenantId")]
|
||||
public string TenantId { get; init; } = string.Empty;
|
||||
|
||||
[BsonElement("source")]
|
||||
public string Source { get; init; } = string.Empty;
|
||||
|
||||
[BsonElement("advisoryId")]
|
||||
public string AdvisoryId { get; init; } = string.Empty;
|
||||
|
||||
[BsonElement("observations")]
|
||||
public IReadOnlyList<string> Observations { get; init; } = Array.Empty<string>();
|
||||
|
||||
[BsonElement("createdAt")]
|
||||
public DateTime CreatedAt { get; init; }
|
||||
|
||||
[BsonElement("normalized")]
|
||||
public AdvisoryLinksetNormalizedDocument Normalized { get; init; } = new();
|
||||
}
|
||||
|
||||
internal sealed class AdvisoryLinksetNormalizedDocument
|
||||
{
|
||||
[BsonElement("purls")]
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
|
||||
[BsonElement("versions")]
|
||||
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shape used when reading /linksets responses in WebService endpoint tests.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetQueryResponse
|
||||
{
|
||||
public AdvisoryLinksetResponse[] Linksets { get; init; } = Array.Empty<AdvisoryLinksetResponse>();
|
||||
public bool HasMore { get; init; }
|
||||
public string? NextCursor { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class AdvisoryLinksetResponse
|
||||
{
|
||||
public string AdvisoryId { get; init; } = string.Empty;
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -33,6 +33,7 @@ using StellaOps.Concelier.Merge.Services;
|
||||
using StellaOps.Concelier.Storage.Mongo;
|
||||
using StellaOps.Concelier.Storage.Mongo.Advisories;
|
||||
using StellaOps.Concelier.Storage.Mongo.Observations;
|
||||
using StellaOps.Concelier.Storage.Mongo.Linksets;
|
||||
using StellaOps.Concelier.Core.Raw;
|
||||
using StellaOps.Concelier.WebService.Jobs;
|
||||
using StellaOps.Concelier.WebService.Options;
|
||||
@@ -376,13 +377,12 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
var root = document.RootElement;
|
||||
|
||||
Assert.Equal("CVE-2025-0001", root.GetProperty("advisoryKey").GetString());
|
||||
Assert.False(string.IsNullOrWhiteSpace(root.GetProperty("fingerprint").GetString()));
|
||||
Assert.Equal(1, root.GetProperty("total").GetInt32());
|
||||
Assert.False(root.GetProperty("truncated").GetBoolean());
|
||||
|
||||
var entry = Assert.Single(root.GetProperty("entries").EnumerateArray());
|
||||
Assert.Equal("workaround", entry.GetProperty("type").GetString());
|
||||
Assert.Equal("tenant-a:chunk:newest", entry.GetProperty("documentId").GetString());
|
||||
Assert.Equal("/references/0", entry.GetProperty("fieldPath").GetString());
|
||||
Assert.False(string.IsNullOrWhiteSpace(entry.GetProperty("chunkId").GetString()));
|
||||
|
||||
var content = entry.GetProperty("content");
|
||||
@@ -391,6 +391,8 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
Assert.Equal("https://vendor.example/workaround", content.GetProperty("url").GetString());
|
||||
|
||||
var provenance = entry.GetProperty("provenance");
|
||||
Assert.Equal("tenant-a:chunk:newest", provenance.GetProperty("documentId").GetString());
|
||||
Assert.Equal("/references/0", provenance.GetProperty("observationPath").GetString());
|
||||
Assert.Equal("nvd", provenance.GetProperty("source").GetString());
|
||||
Assert.Equal("workaround", provenance.GetProperty("kind").GetString());
|
||||
Assert.Equal("tenant-a:chunk:newest", provenance.GetProperty("value").GetString());
|
||||
@@ -638,6 +640,9 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
|
||||
using var client = _factory.CreateClient();
|
||||
|
||||
long expectedSegments = 0;
|
||||
string expectedTruncatedTag = "false";
|
||||
|
||||
var metrics = await CaptureMetricsAsync(
|
||||
AdvisoryAiMetrics.MeterName,
|
||||
new[]
|
||||
@@ -654,6 +659,13 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
var first = await client.GetAsync(url);
|
||||
first.EnsureSuccessStatusCode();
|
||||
|
||||
using (var firstDocument = await first.Content.ReadFromJsonAsync<JsonDocument>())
|
||||
{
|
||||
Assert.NotNull(firstDocument);
|
||||
expectedSegments = firstDocument!.RootElement.GetProperty("entries").GetArrayLength();
|
||||
expectedTruncatedTag = firstDocument.RootElement.GetProperty("truncated").GetBoolean() ? "true" : "false";
|
||||
}
|
||||
|
||||
var second = await client.GetAsync(url);
|
||||
second.EnsureSuccessStatusCode();
|
||||
});
|
||||
@@ -679,7 +691,11 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
|
||||
Assert.True(metrics.TryGetValue("advisory_ai_chunk_segments", out var segmentMeasurements));
|
||||
Assert.Equal(2, segmentMeasurements!.Count);
|
||||
Assert.Contains(segmentMeasurements!, measurement => GetTagValue(measurement, "truncated") == "false");
|
||||
Assert.All(segmentMeasurements!, measurement =>
|
||||
{
|
||||
Assert.Equal(expectedSegments, measurement.Value);
|
||||
Assert.Equal(expectedTruncatedTag, GetTagValue(measurement, "truncated"));
|
||||
});
|
||||
|
||||
Assert.True(metrics.TryGetValue("advisory_ai_chunk_sources", out var sourceMeasurements));
|
||||
Assert.Equal(2, sourceMeasurements!.Count);
|
||||
@@ -2522,6 +2538,7 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
Array.Empty<string>(),
|
||||
references,
|
||||
Array.Empty<string>(),
|
||||
Array.Empty<string>(),
|
||||
new Dictionary<string, string> { ["note"] = "ingest-test" }));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user