fix tests. new product advisories enhancements
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Globalization;
|
||||
@@ -21,6 +23,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.InMemoryRunner;
|
||||
@@ -38,6 +41,11 @@ using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage.Observations;
|
||||
using StellaOps.Concelier.Storage.Linksets;
|
||||
using StellaOps.Concelier.Core.Raw;
|
||||
using StellaOps.Concelier.Core.Observations;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Concelier.Models.Observations;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.RawModels;
|
||||
using StellaOps.Concelier.WebService.Jobs;
|
||||
using StellaOps.Concelier.WebService.Options;
|
||||
using StellaOps.Concelier.WebService.Contracts;
|
||||
@@ -51,6 +59,9 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using StellaOps.Concelier.WebService.Diagnostics;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
using DsseProvenance = StellaOps.Provenance.DsseProvenance;
|
||||
using TrustInfo = StellaOps.Provenance.TrustInfo;
|
||||
using DocumentObject = StellaOps.Concelier.Documents.DocumentObject;
|
||||
|
||||
namespace StellaOps.Concelier.WebService.Tests;
|
||||
|
||||
@@ -72,6 +83,9 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
|
||||
public ValueTask InitializeAsync()
|
||||
{
|
||||
_runner = InMemoryDbRunner.Start();
|
||||
// Use an empty connection string - the factory sets a default Postgres connection string
|
||||
// and the stub services bypass actual database operations
|
||||
_factory = new ConcelierApplicationFactory(string.Empty);
|
||||
WarmupFactory(_factory);
|
||||
return ValueTask.CompletedTask;
|
||||
@@ -80,6 +94,7 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_factory.Dispose();
|
||||
_runner.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -2033,6 +2048,8 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLETRACING", "false");
|
||||
Environment.SetEnvironmentVariable("CONCELIER_TELEMETRY__ENABLEMETRICS", "false");
|
||||
Environment.SetEnvironmentVariable("CONCELIER_SKIP_OPTIONS_VALIDATION", "1");
|
||||
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Testing");
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Testing");
|
||||
const string EvidenceRootKey = "CONCELIER_EVIDENCE__ROOT";
|
||||
var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "..", ".."));
|
||||
_additionalPreviousEnvironment[EvidenceRootKey] = Environment.GetEnvironmentVariable(EvidenceRootKey);
|
||||
@@ -2074,6 +2091,8 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseEnvironment("Testing");
|
||||
|
||||
builder.ConfigureAppConfiguration((context, configurationBuilder) =>
|
||||
{
|
||||
var settings = new Dictionary<string, string?>
|
||||
@@ -2091,9 +2110,30 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
// Remove ConcelierDataSource to skip Postgres initialization during tests
|
||||
// This allows tests to run without a real database connection
|
||||
services.RemoveAll<ConcelierDataSource>();
|
||||
|
||||
services.AddSingleton<ILeaseStore, Fixtures.TestLeaseStore>();
|
||||
services.AddSingleton<StubJobCoordinator>();
|
||||
services.AddSingleton<IJobCoordinator>(sp => sp.GetRequiredService<StubJobCoordinator>());
|
||||
|
||||
// Register stubs for services required by AdvisoryRawService and AdvisoryObservationQueryService
|
||||
services.RemoveAll<IAdvisoryRawService>();
|
||||
services.AddSingleton<IAdvisoryRawService, StubAdvisoryRawService>();
|
||||
services.RemoveAll<IAdvisoryObservationLookup>();
|
||||
services.AddSingleton<IAdvisoryObservationLookup, StubAdvisoryObservationLookup>();
|
||||
services.RemoveAll<IAdvisoryObservationQueryService>();
|
||||
services.AddSingleton<IAdvisoryObservationQueryService, StubAdvisoryObservationQueryService>();
|
||||
|
||||
// Register stubs for storage and event log services
|
||||
services.RemoveAll<IStorageDatabase>();
|
||||
services.AddSingleton<IStorageDatabase>(new StorageDatabase("test"));
|
||||
services.RemoveAll<IAdvisoryStore>();
|
||||
services.AddSingleton<IAdvisoryStore, StubAdvisoryStore>();
|
||||
services.RemoveAll<IAdvisoryEventLog>();
|
||||
services.AddSingleton<IAdvisoryEventLog, StubAdvisoryEventLog>();
|
||||
|
||||
services.PostConfigure<ConcelierOptions>(options =>
|
||||
{
|
||||
options.PostgresStorage ??= new ConcelierOptions.PostgresStorageOptions();
|
||||
@@ -2309,6 +2349,188 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StubAdvisoryRawService : IAdvisoryRawService
|
||||
{
|
||||
public Task<AdvisoryRawUpsertResult> IngestAsync(AdvisoryRawDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var record = new AdvisoryRawRecord(Guid.NewGuid().ToString("D"), document, DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch);
|
||||
return Task.FromResult(new AdvisoryRawUpsertResult(true, record));
|
||||
}
|
||||
|
||||
public Task<AdvisoryRawRecord?> FindByIdAsync(string tenant, string id, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return Task.FromResult<AdvisoryRawRecord?>(null);
|
||||
}
|
||||
|
||||
public Task<AdvisoryRawQueryResult> QueryAsync(AdvisoryRawQueryOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return Task.FromResult(new AdvisoryRawQueryResult(Array.Empty<AdvisoryRawRecord>(), null, false));
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<AdvisoryRawRecord>> FindByAdvisoryKeyAsync(
|
||||
string tenant,
|
||||
string advisoryKey,
|
||||
IReadOnlyCollection<string> sourceVendors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return Task.FromResult<IReadOnlyList<AdvisoryRawRecord>>(Array.Empty<AdvisoryRawRecord>());
|
||||
}
|
||||
|
||||
public Task<AdvisoryRawVerificationResult> VerifyAsync(AdvisoryRawVerificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return Task.FromResult(new AdvisoryRawVerificationResult(
|
||||
request.Tenant,
|
||||
request.Since,
|
||||
request.Until,
|
||||
0,
|
||||
Array.Empty<AdvisoryRawVerificationViolation>(),
|
||||
false));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StubAdvisoryObservationLookup : IAdvisoryObservationLookup
|
||||
{
|
||||
public ValueTask<IReadOnlyList<AdvisoryObservation>> ListByTenantAsync(
|
||||
string tenant,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return ValueTask.FromResult<IReadOnlyList<AdvisoryObservation>>(Array.Empty<AdvisoryObservation>());
|
||||
}
|
||||
|
||||
public ValueTask<IReadOnlyList<AdvisoryObservation>> FindByFiltersAsync(
|
||||
string tenant,
|
||||
IReadOnlyCollection<string> observationIds,
|
||||
IReadOnlyCollection<string> aliases,
|
||||
IReadOnlyCollection<string> purls,
|
||||
IReadOnlyCollection<string> cpes,
|
||||
AdvisoryObservationCursor? cursor,
|
||||
int limit,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return ValueTask.FromResult<IReadOnlyList<AdvisoryObservation>>(Array.Empty<AdvisoryObservation>());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StubAdvisoryObservationQueryService : IAdvisoryObservationQueryService
|
||||
{
|
||||
public ValueTask<AdvisoryObservationQueryResult> QueryAsync(
|
||||
AdvisoryObservationQueryOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var emptyLinkset = new AdvisoryObservationLinksetAggregate(
|
||||
System.Collections.Immutable.ImmutableArray<string>.Empty,
|
||||
System.Collections.Immutable.ImmutableArray<string>.Empty,
|
||||
System.Collections.Immutable.ImmutableArray<string>.Empty,
|
||||
System.Collections.Immutable.ImmutableArray<AdvisoryObservationReference>.Empty);
|
||||
|
||||
return ValueTask.FromResult(new AdvisoryObservationQueryResult(
|
||||
System.Collections.Immutable.ImmutableArray<AdvisoryObservation>.Empty,
|
||||
emptyLinkset,
|
||||
null,
|
||||
false));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StubAdvisoryEventLog : IAdvisoryEventLog
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, List<AdvisoryStatementInput>> _statements = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public ValueTask AppendAsync(AdvisoryEventAppendRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var statement in request.Statements)
|
||||
{
|
||||
var list = _statements.GetOrAdd(statement.VulnerabilityKey, _ => new List<AdvisoryStatementInput>());
|
||||
lock (list)
|
||||
{
|
||||
list.Add(statement);
|
||||
}
|
||||
}
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<AdvisoryReplay> ReplayAsync(string vulnerabilityKey, DateTimeOffset? asOf, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_statements.TryGetValue(vulnerabilityKey, out var statements) && statements.Count > 0)
|
||||
{
|
||||
var snapshots = statements
|
||||
.Select(s => new AdvisoryStatementSnapshot(
|
||||
s.StatementId ?? Guid.NewGuid(),
|
||||
s.VulnerabilityKey,
|
||||
s.AdvisoryKey ?? s.Advisory.AdvisoryKey,
|
||||
s.Advisory,
|
||||
System.Collections.Immutable.ImmutableArray<byte>.Empty,
|
||||
s.AsOf,
|
||||
DateTimeOffset.UtcNow,
|
||||
System.Collections.Immutable.ImmutableArray<Guid>.Empty))
|
||||
.ToImmutableArray();
|
||||
|
||||
return ValueTask.FromResult(new AdvisoryReplay(
|
||||
vulnerabilityKey,
|
||||
asOf,
|
||||
snapshots,
|
||||
System.Collections.Immutable.ImmutableArray<AdvisoryConflictSnapshot>.Empty));
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(new AdvisoryReplay(
|
||||
vulnerabilityKey,
|
||||
asOf,
|
||||
System.Collections.Immutable.ImmutableArray<AdvisoryStatementSnapshot>.Empty,
|
||||
System.Collections.Immutable.ImmutableArray<AdvisoryConflictSnapshot>.Empty));
|
||||
}
|
||||
|
||||
public ValueTask AttachStatementProvenanceAsync(Guid statementId, DsseProvenance provenance, TrustInfo trust, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class StubAdvisoryStore : IAdvisoryStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Advisory> _advisories = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Task UpsertAsync(Advisory advisory, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_advisories[advisory.AdvisoryKey] = advisory;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<Advisory?> FindAsync(string advisoryKey, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_advisories.TryGetValue(advisoryKey, out var advisory);
|
||||
return Task.FromResult<Advisory?>(advisory);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<Advisory>> GetRecentAsync(int limit, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var result = _advisories.Values
|
||||
.OrderByDescending(a => a.Modified ?? a.Published ?? DateTimeOffset.MinValue)
|
||||
.Take(limit)
|
||||
.ToArray();
|
||||
return Task.FromResult<IReadOnlyList<Advisory>>(result);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<Advisory> StreamAsync([System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var advisory in _advisories.Values.OrderBy(a => a.AdvisoryKey, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
yield return advisory;
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user