Merge all changes
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
## Release History
|
||||
; Unshipped analyzer releases
|
||||
|
||||
### Unreleased
|
||||
### New Rules
|
||||
|
||||
- CONCELIER0004: Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces; require sandboxed `IHttpClientFactory` to enforce allow/deny lists. Exempts test assemblies and uses symbol-based namespace matching.
|
||||
Rule ID | Category | Severity | Notes
|
||||
--------|----------|----------|------
|
||||
CONCELIER0004 | Sandbox | Warning | Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
id: DiagnosticId,
|
||||
title: "Connector HTTP clients must use sandboxed factory",
|
||||
messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors.",
|
||||
messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors",
|
||||
category: "Sandbox",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
@@ -73,7 +73,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
return assemblyName.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase)
|
||||
return assemblyName!.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase)
|
||||
|| assemblyName.EndsWith(".Test", StringComparison.OrdinalIgnoreCase)
|
||||
|| assemblyName.EndsWith(".Testing", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
## Release History
|
||||
; Unshipped analyzer releases
|
||||
|
||||
### Unreleased
|
||||
### New Rules
|
||||
|
||||
#### New Rules
|
||||
|
||||
Rule ID | Title | Notes
|
||||
--------|-------|------
|
||||
CONCELIER0002 | Legacy merge service usage detected | Flags references to `AdvisoryMergeService` and `AddMergeModule`.
|
||||
Rule ID | Category | Severity | Notes
|
||||
--------|----------|----------|------
|
||||
CONCELIER0002 | Usage | Warning | Legacy merge service usage detected - flags references to `AdvisoryMergeService` and `AddMergeModule`
|
||||
|
||||
@@ -185,6 +185,11 @@ public sealed class AstraConnector : IFeedConnector
|
||||
/// </remarks>
|
||||
private async Task<string> FetchOvalDatabaseAsync(string version, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_fetchService is null || _rawDocumentStorage is null)
|
||||
{
|
||||
throw new InvalidOperationException("Fetch and raw document storage services are required for OVAL database fetch");
|
||||
}
|
||||
|
||||
var uri = _options.BuildOvalDatabaseUri(version);
|
||||
|
||||
_logger.LogDebug("Fetching OVAL database for Astra Linux {Version} from {Uri}", version, uri);
|
||||
@@ -197,18 +202,24 @@ public sealed class AstraConnector : IFeedConnector
|
||||
|
||||
var result = await _fetchService.FetchAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!result.IsSuccess || result.Document is null)
|
||||
if (result is null || !result.IsSuccess || result.Document is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to fetch OVAL database for version {version}");
|
||||
}
|
||||
|
||||
if (!result.Document.PayloadId.HasValue)
|
||||
var document = result.Document;
|
||||
|
||||
if (!document.PayloadId.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException($"OVAL database document for version {version} has no payload");
|
||||
}
|
||||
|
||||
// Download the raw XML content
|
||||
var payloadBytes = await _rawDocumentStorage.DownloadAsync(result.Document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
|
||||
var payloadBytes = await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
|
||||
if (payloadBytes is null)
|
||||
{
|
||||
throw new InvalidOperationException($"OVAL database payload for version {version} not found");
|
||||
}
|
||||
return System.Text.Encoding.UTF8.GetString(payloadBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ public sealed class CccsHtmlParser
|
||||
|
||||
var candidate = href.Trim();
|
||||
var hasAbsolute = Uri.TryCreate(candidate, UriKind.Absolute, out var absolute);
|
||||
if (!hasAbsolute || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
|
||||
if (!hasAbsolute || absolute is null || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (baseUri is null || !Uri.TryCreate(baseUri, candidate, out absolute))
|
||||
{
|
||||
|
||||
@@ -177,7 +177,7 @@ public sealed class CertCcConnector : IFeedConnector
|
||||
await _documentStore.UpdateStatusAsync(result.Document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!shouldProcessNotes)
|
||||
if (!shouldProcessNotes || result.Document is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ internal sealed record CertCcCursor(
|
||||
}
|
||||
|
||||
var bytes = binary.AsByteArray;
|
||||
if (bytes.Length == 16)
|
||||
if (bytes is not null && bytes.Length == 16)
|
||||
{
|
||||
guid = new Guid(bytes);
|
||||
return true;
|
||||
|
||||
@@ -605,7 +605,7 @@ public sealed class DebianConnector : IFeedConnector
|
||||
{
|
||||
["advisoryId"] = dto.AdvisoryId,
|
||||
["sourcePackage"] = dto.SourcePackage,
|
||||
["title"] = dto.Title,
|
||||
["title"] = dto.Title ?? string.Empty,
|
||||
["description"] = dto.Description ?? string.Empty,
|
||||
["cves"] = new DocumentArray(dto.CveIds),
|
||||
["packages"] = packages,
|
||||
|
||||
@@ -29,7 +29,12 @@ internal static class RedHatMapper
|
||||
ArgumentNullException.ThrowIfNull(payload);
|
||||
|
||||
var csaf = JsonSerializer.Deserialize<RedHatCsafEnvelope>(payload.RootElement.GetRawText(), SerializerOptions);
|
||||
var documentSection = csaf?.Document;
|
||||
if (csaf is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentSection = csaf.Document;
|
||||
if (documentSection is null)
|
||||
{
|
||||
return null;
|
||||
@@ -722,7 +727,7 @@ internal sealed class RedHatProductIndex
|
||||
return new RedHatProductIndex(products);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string productId, out RedHatProductNode node)
|
||||
public bool TryGetValue(string productId, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out RedHatProductNode? node)
|
||||
=> _products.TryGetValue(productId, out node);
|
||||
|
||||
private static void Traverse(RedHatProductBranch? branch, IDictionary<string, RedHatProductNode> products)
|
||||
|
||||
@@ -464,7 +464,7 @@ public sealed class EpssConnector : IFeedConnector
|
||||
["epss.file"] = GetSnapshotFileName(fetchResult.SnapshotDate)
|
||||
};
|
||||
|
||||
if (_options.AirgapMode)
|
||||
if (_options.AirgapMode && fetchResult.Content is not null)
|
||||
{
|
||||
TryApplyBundleManifest(fetchResult.SnapshotDate, fetchResult.Content, metadata);
|
||||
}
|
||||
@@ -473,6 +473,11 @@ public sealed class EpssConnector : IFeedConnector
|
||||
// Use existing ID or derive deterministic ID from source + uri
|
||||
var recordId = existing?.Id ?? ComputeDeterministicId(SourceName, fetchResult.SourceUri);
|
||||
|
||||
if (fetchResult.Content is null)
|
||||
{
|
||||
throw new InvalidOperationException($"EPSS fetch returned null content for {fetchResult.SourceUri}");
|
||||
}
|
||||
|
||||
await _rawDocumentStorage.UploadAsync(
|
||||
SourceName,
|
||||
fetchResult.SourceUri,
|
||||
|
||||
@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Ics.Cisa.Configuration;
|
||||
using StellaOps.Concelier.Connector.Ics.Cisa.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Normalization.SemVer;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
@@ -7,7 +7,6 @@ using StellaOps.Concelier.Normalization.Cvss;
|
||||
using StellaOps.Concelier.Normalization.Identifiers;
|
||||
using StellaOps.Concelier.Normalization.Text;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.JpFlags;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Jvn.Internal;
|
||||
@@ -68,7 +67,7 @@ internal static class JvnAdvisoryMapper
|
||||
var flag = new JpFlagRecord(
|
||||
detail.VulnerabilityId,
|
||||
JvnConnectorPlugin.SourceName,
|
||||
detail.JvnCategory,
|
||||
detail.JvnCategory ?? string.Empty,
|
||||
vendorStatus,
|
||||
timeProvider.GetUtcNow());
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ using StellaOps.Concelier.Connector.Jvn.Configuration;
|
||||
using StellaOps.Concelier.Connector.Jvn.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.JpFlags;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Kev.Configuration;
|
||||
using StellaOps.Concelier.Connector.Kev.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.Kisa.Configuration;
|
||||
using StellaOps.Concelier.Connector.Kisa.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using StellaOps.Concelier.Normalization.Identifiers;
|
||||
using StellaOps.Concelier.Normalization.Text;
|
||||
using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Osv.Internal;
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Ru.Bdu.Configuration;
|
||||
using StellaOps.Concelier.Connector.Ru.Bdu.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Plugin;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Ru.Nkcki.Configuration;
|
||||
using StellaOps.Concelier.Connector.Ru.Nkcki.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Plugin;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.StellaOpsMirror.Settings;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Plugin;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Vndr.Adobe.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Adobe.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
@@ -16,8 +16,6 @@ using StellaOps.Concelier.Connector.Common.Fetch;
|
||||
using StellaOps.Concelier.Connector.Vndr.Apple.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
@@ -118,23 +118,24 @@ internal static class AppleIndexParser
|
||||
return entries.Count == 0 ? Array.Empty<AppleIndexEntry>() : entries;
|
||||
}
|
||||
|
||||
private static bool TryResolveDetailUri(AppleIndexEntryDto dto, Uri baseUri, out Uri uri)
|
||||
private static bool TryResolveDetailUri(AppleIndexEntryDto dto, Uri baseUri, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Uri? uri)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dto.DetailUrl) && Uri.TryCreate(dto.DetailUrl, UriKind.Absolute, out uri))
|
||||
if (!string.IsNullOrWhiteSpace(dto.DetailUrl) && Uri.TryCreate(dto.DetailUrl, UriKind.Absolute, out var parsedUri))
|
||||
{
|
||||
uri = parsedUri;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dto.ArticleId))
|
||||
{
|
||||
uri = default!;
|
||||
uri = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var article = dto.ArticleId.Trim();
|
||||
if (article.Length == 0)
|
||||
{
|
||||
uri = default!;
|
||||
uri = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Apple.Internal;
|
||||
|
||||
@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.Vndr.Chromium.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Chromium.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
@@ -69,7 +69,7 @@ internal sealed record ChromiumCursor(
|
||||
public ChromiumCursor WithFetchCache(IDictionary<string, ChromiumFetchCacheEntry> cache)
|
||||
=> this with { FetchCache = cache is null ? new Dictionary<string, ChromiumFetchCacheEntry>(StringComparer.Ordinal) : new Dictionary<string, ChromiumFetchCacheEntry>(cache, StringComparer.Ordinal) };
|
||||
|
||||
public bool TryGetFetchCache(string key, out ChromiumFetchCacheEntry entry)
|
||||
public bool TryGetFetchCache(string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out ChromiumFetchCacheEntry? entry)
|
||||
=> FetchCache.TryGetValue(key, out entry);
|
||||
|
||||
private static DateTimeOffset? ReadDateTime(DocumentValue value)
|
||||
|
||||
@@ -12,8 +12,6 @@ using StellaOps.Concelier.Connector.Vndr.Cisco.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Cisco.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Normalization.SemVer;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Cisco.Internal;
|
||||
|
||||
@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Vndr.Msrc.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Msrc.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Oracle.Internal;
|
||||
|
||||
@@ -5,7 +5,6 @@ using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using NpgsqlTypes;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Models;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
@@ -127,8 +128,14 @@ public sealed class AdvisoryLinksetCacheRepository
|
||||
|
||||
if (cursor is null)
|
||||
{
|
||||
AddParameter(cmd, "cursor_created_at", DBNull.Value);
|
||||
AddParameter(cmd, "cursor_advisory_id", DBNull.Value);
|
||||
cmd.Parameters.Add(new NpgsqlParameter("cursor_created_at", NpgsqlDbType.TimestampTz)
|
||||
{
|
||||
Value = DBNull.Value
|
||||
});
|
||||
cmd.Parameters.Add(new NpgsqlParameter("cursor_advisory_id", NpgsqlDbType.Text)
|
||||
{
|
||||
Value = DBNull.Value
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace StellaOps.Concelier.ConfigDiff.Tests;
|
||||
[Trait("BlastRadius", TestCategories.BlastRadius.Advisories)]
|
||||
public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
{
|
||||
private static readonly DateTimeOffset SnapshotTimestamp =
|
||||
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcelierConfigDiffTests"/> class.
|
||||
/// </summary>
|
||||
@@ -61,7 +64,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
async config => await GetDownloadBehaviorAsync(config),
|
||||
async config => await GetRetryBehaviorAsync(config),
|
||||
async config => await GetParseBehaviorAsync(config)
|
||||
]);
|
||||
],
|
||||
ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue(
|
||||
@@ -93,7 +97,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
changedConfig,
|
||||
getBehavior: async config => await CaptureRetryBehaviorAsync(config),
|
||||
computeDelta: ComputeBehaviorSnapshotDelta,
|
||||
expectedDelta: expectedDelta);
|
||||
expectedDelta: expectedDelta,
|
||||
ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue(
|
||||
@@ -120,7 +125,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
async config => await GetCacheBehaviorAsync(config),
|
||||
async config => await GetRetryBehaviorAsync(config),
|
||||
async config => await GetParseBehaviorAsync(config)
|
||||
]);
|
||||
],
|
||||
ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue(
|
||||
@@ -152,7 +158,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
changedConfig,
|
||||
getBehavior: async config => await CaptureValidationBehaviorAsync(config),
|
||||
computeDelta: ComputeBehaviorSnapshotDelta,
|
||||
expectedDelta: expectedDelta);
|
||||
expectedDelta: expectedDelta,
|
||||
ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
@@ -186,11 +193,11 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
ConfigurationId: $"retry-{config.RetryCount}",
|
||||
Behaviors:
|
||||
[
|
||||
new CapturedBehavior("MaxRetryAttempts", config.RetryCount.ToString(), DateTimeOffset.UtcNow),
|
||||
new CapturedBehavior("MaxRetryAttempts", config.RetryCount.ToString(), SnapshotTimestamp),
|
||||
new CapturedBehavior("FailureRecoveryWindow",
|
||||
config.RetryCount > 3 ? "increase" : "standard", DateTimeOffset.UtcNow)
|
||||
config.RetryCount > 3 ? "increase" : "standard", SnapshotTimestamp)
|
||||
],
|
||||
CapturedAt: DateTimeOffset.UtcNow);
|
||||
CapturedAt: SnapshotTimestamp);
|
||||
|
||||
return Task.FromResult(snapshot);
|
||||
}
|
||||
@@ -202,11 +209,11 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
|
||||
Behaviors:
|
||||
[
|
||||
new CapturedBehavior("ValidationStrictness",
|
||||
config.StrictValidation ? "strict" : "relaxed", DateTimeOffset.UtcNow),
|
||||
config.StrictValidation ? "strict" : "relaxed", SnapshotTimestamp),
|
||||
new CapturedBehavior("RejectionRate",
|
||||
config.StrictValidation ? "increase" : "standard", DateTimeOffset.UtcNow)
|
||||
config.StrictValidation ? "increase" : "standard", SnapshotTimestamp)
|
||||
],
|
||||
CapturedAt: DateTimeOffset.UtcNow);
|
||||
CapturedAt: SnapshotTimestamp);
|
||||
|
||||
return Task.FromResult(snapshot);
|
||||
}
|
||||
|
||||
@@ -51,9 +51,10 @@ public sealed class AcscConnectorFetchTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
var stateValue = state!;
|
||||
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
|
||||
var feeds = state.Cursor.GetValue("feeds").AsDocumentObject;
|
||||
var feeds = stateValue.Cursor.GetValue("feeds").AsDocumentObject;
|
||||
Assert.True(feeds.TryGetValue("alerts", out var published));
|
||||
Assert.Equal(DateTime.Parse("2025-10-11T05:30:00Z").ToUniversalTime(), published.ToUniversalTime());
|
||||
|
||||
@@ -90,9 +91,10 @@ public sealed class AcscConnectorFetchTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal("Relay", state!.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
var stateValue = state!;
|
||||
Assert.Equal("Relay", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
|
||||
var feeds = state.Cursor.GetValue("feeds").AsDocumentObject;
|
||||
var feeds = stateValue.Cursor.GetValue("feeds").AsDocumentObject;
|
||||
Assert.True(feeds.TryGetValue("alerts", out var published));
|
||||
Assert.Equal(DateTime.Parse("2025-10-11T00:00:00Z").ToUniversalTime(), published.ToUniversalTime());
|
||||
|
||||
@@ -142,7 +144,8 @@ public sealed class AcscConnectorFetchTests
|
||||
|
||||
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
var stateValue = state!;
|
||||
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
|
||||
Assert.Collection(harness.Handler.Requests,
|
||||
request =>
|
||||
@@ -175,7 +178,8 @@ public sealed class AcscConnectorFetchTests
|
||||
|
||||
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
var stateValue = state!;
|
||||
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
|
||||
Assert.Empty(harness.Handler.Requests);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,8 +80,9 @@ public sealed class AcscConnectorParseTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.DoesNotContain(document.Id.ToString(), state!.Cursor.GetValue("pendingDocuments").AsDocumentArray.Select(v => v.AsString));
|
||||
Assert.Contains(document.Id.ToString(), state.Cursor.GetValue("pendingMappings").AsDocumentArray.Select(v => v.AsString));
|
||||
var stateValue = state!;
|
||||
Assert.DoesNotContain(document.Id.ToString(), stateValue.Cursor.GetValue("pendingDocuments").AsDocumentArray.Select(v => v.AsString));
|
||||
Assert.Contains(document.Id.ToString(), stateValue.Cursor.GetValue("pendingMappings").AsDocumentArray.Select(v => v.AsString));
|
||||
|
||||
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
@@ -103,7 +104,8 @@ public sealed class AcscConnectorParseTests
|
||||
|
||||
state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.GetValue("pendingMappings").AsDocumentArray.Count == 0);
|
||||
stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.GetValue("pendingMappings").AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -18,7 +18,6 @@ using StellaOps.Concelier.Connector.Common.Http;
|
||||
using StellaOps.Concelier.Connector.Common.Cursors;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
@@ -110,9 +109,10 @@ public sealed class CertCcConnectorFetchTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var stateValue = state!;
|
||||
|
||||
DocumentValue summaryValue;
|
||||
Assert.True(state!.Cursor.TryGetValue("summary", out summaryValue));
|
||||
Assert.True(stateValue.Cursor.TryGetValue("summary", out summaryValue));
|
||||
var summaryDocument = Assert.IsType<DocumentObject>(summaryValue);
|
||||
Assert.True(summaryDocument.TryGetValue("start", out _));
|
||||
Assert.True(summaryDocument.TryGetValue("end", out _));
|
||||
|
||||
@@ -23,7 +23,6 @@ using StellaOps.Concelier.Connector.Common.Cursors;
|
||||
using StellaOps.Concelier.Connector.Common.Http;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
|
||||
@@ -22,7 +22,6 @@ using StellaOps.Concelier.Connector.Common.Cursors;
|
||||
using StellaOps.Concelier.Connector.Common.Http;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
@@ -135,8 +134,9 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
state.Should().NotBeNull();
|
||||
state!.Cursor.Should().NotBeNull();
|
||||
var pendingNotesCount = state.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue)
|
||||
var stateValue = state!;
|
||||
stateValue.Cursor.Should().NotBeNull();
|
||||
var pendingNotesCount = stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue)
|
||||
? pendingNotesValue!.AsDocumentArray.Count
|
||||
: 0;
|
||||
pendingNotesCount.Should().Be(0);
|
||||
@@ -216,10 +216,11 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
state.Should().NotBeNull();
|
||||
state!.FailCount.Should().BeGreaterThan(0);
|
||||
state.BackoffUntil.Should().NotBeNull();
|
||||
state.BackoffUntil.Should().BeAfter(_timeProvider.GetUtcNow());
|
||||
state.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
|
||||
var stateValue = state!;
|
||||
stateValue.FailCount.Should().BeGreaterThan(0);
|
||||
stateValue.BackoffUntil.Should().NotBeNull();
|
||||
stateValue.BackoffUntil.Should().BeAfter(_timeProvider.GetUtcNow());
|
||||
stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
|
||||
pendingNotesValue!.AsDocumentArray.Should().Contain(value => value.AsString == "294418");
|
||||
var pendingSummaries = state.Cursor.TryGetValue("pendingSummaries", out var pendingSummariesValue)
|
||||
? pendingSummariesValue!.AsDocumentArray.Count
|
||||
@@ -259,11 +260,12 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
state.Should().NotBeNull();
|
||||
state!.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
|
||||
var stateValue = state!;
|
||||
stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
|
||||
pendingNotesValue!.AsDocumentArray.Should().BeEmpty();
|
||||
state.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue).Should().BeTrue();
|
||||
stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue).Should().BeTrue();
|
||||
pendingDocsValue!.AsDocumentArray.Should().BeEmpty();
|
||||
state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue).Should().BeTrue();
|
||||
stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue).Should().BeTrue();
|
||||
pendingMappingsValue!.AsDocumentArray.Should().BeEmpty();
|
||||
}
|
||||
|
||||
@@ -298,11 +300,12 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
state.Should().NotBeNull();
|
||||
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
var stateValue = state!;
|
||||
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
? pendingDocsValue!.AsDocumentArray.Count
|
||||
: 0;
|
||||
pendingDocuments.Should().BeGreaterThan(0);
|
||||
var pendingMappings = state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue)
|
||||
var pendingMappings = stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue)
|
||||
? pendingMappingsValue!.AsDocumentArray.Count
|
||||
: 0;
|
||||
pendingMappings.Should().Be(0);
|
||||
|
||||
@@ -4,7 +4,6 @@ using StellaOps.Concelier.Documents;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.CertCc.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.CertCc.Tests.Internal;
|
||||
|
||||
@@ -95,11 +95,12 @@ public sealed class CertFrConnectorTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal(1, state!.FailCount);
|
||||
Assert.NotNull(state.LastFailureReason);
|
||||
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.NotNull(state.BackoffUntil);
|
||||
Assert.True(state.BackoffUntil > harness.TimeProvider.GetUtcNow());
|
||||
var stateValue = state!;
|
||||
Assert.Equal(1, stateValue.FailCount);
|
||||
Assert.NotNull(stateValue.LastFailureReason);
|
||||
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.NotNull(stateValue.BackoffUntil);
|
||||
Assert.True(stateValue.BackoffUntil > harness.TimeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -139,8 +140,9 @@ public sealed class CertFrConnectorTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -182,8 +184,9 @@ public sealed class CertFrConnectorTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
private async Task<ConnectorTestHarness> BuildHarnessAsync()
|
||||
|
||||
@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Testing;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.CertIn.Tests;
|
||||
@@ -100,7 +98,8 @@ public sealed class CertInConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pending));
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pending));
|
||||
Assert.Empty(pending.AsDocumentArray);
|
||||
}
|
||||
|
||||
@@ -131,11 +130,12 @@ public sealed class CertInConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal(1, state!.FailCount);
|
||||
Assert.NotNull(state.LastFailureReason);
|
||||
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.True(state.BackoffUntil.HasValue);
|
||||
Assert.True(state.BackoffUntil!.Value > _timeProvider.GetUtcNow());
|
||||
var stateValue = state!;
|
||||
Assert.Equal(1, stateValue.FailCount);
|
||||
Assert.NotNull(stateValue.LastFailureReason);
|
||||
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.True(stateValue.BackoffUntil.HasValue);
|
||||
Assert.True(stateValue.BackoffUntil!.Value > _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -209,9 +209,9 @@ public sealed class CertInConnectorTests : IAsyncLifetime
|
||||
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
|
||||
Assert.Empty(pendingDocs.AsDocumentArray);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
|
||||
Assert.Empty(pendingMappings.AsDocumentArray);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -263,10 +263,11 @@ public sealed class CertInConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Empty(pendingDocs.AsDocumentArray);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Empty(pendingMappings.AsDocumentArray);
|
||||
}
|
||||
|
||||
private async Task EnsureServiceProviderAsync(CertInOptions template)
|
||||
|
||||
@@ -14,7 +14,6 @@ using StellaOps.Concelier.Core.Aoc;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Concelier.RawModels;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Common.Tests;
|
||||
|
||||
@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Common.Fetch;
|
||||
using StellaOps.Concelier.Connector.Common.State;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Common.Tests;
|
||||
@@ -105,9 +104,10 @@ public sealed class SourceStateSeedProcessorTests : IAsyncLifetime
|
||||
|
||||
var state = await _stateRepository.TryGetAsync("vndr.test", CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal(_timeProvider.GetUtcNow().UtcDateTime, state!.LastSuccess);
|
||||
var stateValue = state!;
|
||||
Assert.Equal(_timeProvider.GetUtcNow().UtcDateTime, stateValue.LastSuccess);
|
||||
|
||||
var cursor = state.Cursor;
|
||||
var cursor = stateValue.Cursor;
|
||||
var pendingDocs = cursor["pendingDocuments"].AsDocumentArray.Select(v => Guid.Parse(v.AsString)).ToList();
|
||||
Assert.Contains(documentId, pendingDocs);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<!-- This is a shared test library, not a standalone test project - disable ConcelierTestInfra to avoid duplicate types (CS0436) when referenced by actual test projects -->
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
|
||||
@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Cve.Internal;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Cve.Tests;
|
||||
|
||||
|
||||
@@ -56,9 +56,10 @@ public sealed class AlpineConnectorTests
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(AlpineConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
&& pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings)
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings)
|
||||
&& pendingMappings.AsDocumentArray.Count == 0);
|
||||
|
||||
harness.Handler.AssertNoPendingResponses();
|
||||
|
||||
@@ -22,8 +22,6 @@ using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Connector.Distro.Debian.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -107,8 +107,9 @@ public sealed class RedHatConnectorHarnessTests : IAsyncLifetime
|
||||
|
||||
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings) && pendingMappings.AsDocumentArray.Count == 0);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings) && pendingMappings.AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => ValueTask.CompletedTask;
|
||||
|
||||
@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Distro.RedHat.Internal;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Plugin;
|
||||
@@ -170,8 +168,9 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
|
||||
|
||||
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs2) && pendingDocs2.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings2) && pendingMappings2.AsDocumentArray.Count == 0);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs2) && pendingDocs2.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings2) && pendingMappings2.AsDocumentArray.Count == 0);
|
||||
|
||||
const string fetchKind = "source:redhat:fetch";
|
||||
const string parseKind = "source:redhat:parse";
|
||||
@@ -241,8 +240,9 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
|
||||
|
||||
state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs3) && pendingDocs3.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings3) && pendingMappings3.AsDocumentArray.Count == 0);
|
||||
stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs3) && pendingDocs3.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings3) && pendingMappings3.AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -338,7 +338,8 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
|
||||
|
||||
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocs = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
var stateValue = state!;
|
||||
var pendingDocs = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
? pendingDocsValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.NotEmpty(pendingDocs);
|
||||
@@ -368,9 +369,10 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
|
||||
var stateRepository = resumeProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var finalState = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(finalState);
|
||||
var finalPendingDocs = finalState!.Cursor.TryGetValue("pendingDocuments", out var docsValue) ? docsValue.AsDocumentArray : new DocumentArray();
|
||||
var finalStateValue = finalState!;
|
||||
var finalPendingDocs = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var docsValue) ? docsValue.AsDocumentArray : new DocumentArray();
|
||||
Assert.Empty(finalPendingDocs);
|
||||
var finalPendingMappings = finalState.Cursor.TryGetValue("pendingMappings", out var mappingsValue) ? mappingsValue.AsDocumentArray : new DocumentArray();
|
||||
var finalPendingMappings = finalStateValue.Cursor.TryGetValue("pendingMappings", out var mappingsValue) ? mappingsValue.AsDocumentArray : new DocumentArray();
|
||||
Assert.Empty(finalPendingMappings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,10 +142,11 @@ public sealed class GhsaConnectorTests : IAsyncLifetime
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(GhsaConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var stateValue = state!;
|
||||
|
||||
Assert.True(state!.Cursor.TryGetValue("currentWindowStart", out var startValue));
|
||||
Assert.True(state.Cursor.TryGetValue("currentWindowEnd", out var endValue));
|
||||
Assert.True(state.Cursor.TryGetValue("nextPage", out var nextPageValue));
|
||||
Assert.True(stateValue.Cursor.TryGetValue("currentWindowStart", out var startValue));
|
||||
Assert.True(stateValue.Cursor.TryGetValue("currentWindowEnd", out var endValue));
|
||||
Assert.True(stateValue.Cursor.TryGetValue("nextPage", out var nextPageValue));
|
||||
|
||||
Assert.Equal(since.UtcDateTime, startValue.ToUniversalTime());
|
||||
Assert.Equal(until.UtcDateTime, endValue.ToUniversalTime());
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed class IcsCisaConnectorTests
|
||||
|
||||
var icsma = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "ICSMA-25-045-01");
|
||||
Assert.Contains("CVE-2025-11111", icsma.Aliases);
|
||||
var icsmaMitigation = Assert.Single(icsma.References.Where(reference => reference.Kind == "mitigation"));
|
||||
var icsmaMitigation = Assert.Single(icsma.References, reference => reference.Kind == "mitigation");
|
||||
Assert.Contains("Contact HealthTech support", icsmaMitigation.Summary, StringComparison.Ordinal);
|
||||
Assert.Contains(icsma.References, reference => reference.Url == "https://www.cisa.gov/sites/default/files/2025-10/ICSMA-25-045-01_Supplement.pdf");
|
||||
var infusionPackage = Assert.Single(icsma.AffectedPackages, package => string.Equals(package.Identifier, "InfusionManager", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -21,8 +21,6 @@ using StellaOps.Concelier.Connector.Ics.Kaspersky;
|
||||
using StellaOps.Concelier.Connector.Ics.Kaspersky.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
|
||||
@@ -97,7 +95,8 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pending)
|
||||
var stateValue = state!;
|
||||
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pending)
|
||||
? pending.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Empty(pendingDocuments);
|
||||
@@ -130,11 +129,12 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal(1, state!.FailCount);
|
||||
Assert.NotNull(state.LastFailureReason);
|
||||
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.True(state.BackoffUntil.HasValue);
|
||||
Assert.True(state.BackoffUntil!.Value > _timeProvider.GetUtcNow());
|
||||
var stateValue = state!;
|
||||
Assert.Equal(1, stateValue.FailCount);
|
||||
Assert.NotNull(stateValue.LastFailureReason);
|
||||
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
|
||||
Assert.True(stateValue.BackoffUntil.HasValue);
|
||||
Assert.True(stateValue.BackoffUntil!.Value > _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -202,10 +202,11 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Empty(pendingDocs.AsDocumentArray);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Empty(pendingMappings.AsDocumentArray);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -20,8 +20,6 @@ using StellaOps.Concelier.Connector.Jvn;
|
||||
using StellaOps.Concelier.Connector.Jvn.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.JpFlags;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
@@ -123,7 +121,8 @@ public sealed class JvnConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(JvnConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Empty(pendingDocs.AsDocumentArray);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,11 @@ public sealed class KevConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(KevConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal("2025.10.09", state!.Cursor.TryGetValue("catalogVersion", out var versionValue) ? versionValue.AsString : null);
|
||||
Assert.True(state.Cursor.TryGetValue("catalogReleased", out var releasedValue) && releasedValue.DocumentType is DocumentType.DateTime);
|
||||
Assert.True(IsEmptyArray(state.Cursor, "pendingDocuments"));
|
||||
Assert.True(IsEmptyArray(state.Cursor, "pendingMappings"));
|
||||
var stateValue = state!;
|
||||
Assert.Equal("2025.10.09", stateValue.Cursor.TryGetValue("catalogVersion", out var versionValue) ? versionValue.AsString : null);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("catalogReleased", out var releasedValue) && releasedValue.DocumentType is DocumentType.DateTime);
|
||||
Assert.True(IsEmptyArray(stateValue.Cursor, "pendingDocuments"));
|
||||
Assert.True(IsEmptyArray(stateValue.Cursor, "pendingMappings"));
|
||||
}
|
||||
|
||||
private async Task<ServiceProvider> BuildServiceProviderAsync()
|
||||
|
||||
@@ -23,8 +23,6 @@ using StellaOps.Concelier.Connector.Kisa.Internal;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
|
||||
@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Nvd;
|
||||
using StellaOps.Concelier.Connector.Nvd.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using System.Net;
|
||||
@@ -74,15 +73,18 @@ public sealed class NvdConnectorHarnessTests : IAsyncLifetime
|
||||
|
||||
var firstDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, firstUri.ToString(), CancellationToken.None);
|
||||
Assert.NotNull(firstDocument);
|
||||
Assert.Equal("0", firstDocument!.Metadata["startIndex"]);
|
||||
Assert.NotNull(firstDocument!.Metadata);
|
||||
Assert.Equal("0", firstDocument.Metadata["startIndex"]);
|
||||
|
||||
var secondDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, secondUri.ToString(), CancellationToken.None);
|
||||
Assert.NotNull(secondDocument);
|
||||
Assert.Equal("2", secondDocument!.Metadata["startIndex"]);
|
||||
Assert.NotNull(secondDocument!.Metadata);
|
||||
Assert.Equal("2", secondDocument.Metadata["startIndex"]);
|
||||
|
||||
var thirdDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, thirdUri.ToString(), CancellationToken.None);
|
||||
Assert.NotNull(thirdDocument);
|
||||
Assert.Equal("4", thirdDocument!.Metadata["startIndex"]);
|
||||
Assert.NotNull(thirdDocument!.Metadata);
|
||||
Assert.Equal("4", thirdDocument.Metadata["startIndex"]);
|
||||
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
|
||||
@@ -22,8 +22,6 @@ using StellaOps.Concelier.Connector.Nvd.Configuration;
|
||||
using StellaOps.Concelier.Connector.Nvd.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.ChangeHistory;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
@@ -146,7 +144,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var documentStore = provider.GetRequiredService<IDocumentStore>();
|
||||
var finalState = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(finalState);
|
||||
var pendingDocuments = finalState!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
var finalStateValue = finalState!;
|
||||
var pendingDocuments = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
? pendingDocs.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Empty(pendingDocuments);
|
||||
@@ -188,7 +187,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
var stateValue = state!;
|
||||
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
|
||||
? pendingDocs.AsDocumentArray.Select(v => Guid.Parse(v.AsString)).ToArray()
|
||||
: Array.Empty<Guid>();
|
||||
Assert.Equal(3, pendingDocuments.Length);
|
||||
@@ -280,8 +280,9 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var stateValue = state!;
|
||||
|
||||
var cursorDocument = state!.Cursor;
|
||||
var cursorDocument = stateValue.Cursor;
|
||||
var lastWindowEnd = cursorDocument.TryGetValue("windowEnd", out var endValue) ? ReadDateTime(endValue) : (DateTimeOffset?)null;
|
||||
var startCandidate = (lastWindowEnd ?? windowEnd) - options.WindowOverlap;
|
||||
var backfillLimit = now - options.InitialBackfill;
|
||||
@@ -350,9 +351,10 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocs = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue) ? pendingDocsValue.AsDocumentArray : new DocumentArray();
|
||||
var stateValue = state!;
|
||||
var pendingDocs = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue) ? pendingDocsValue.AsDocumentArray : new DocumentArray();
|
||||
Assert.Empty(pendingDocs);
|
||||
var pendingMappings = state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue) ? pendingMappingsValue.AsDocumentArray : new DocumentArray();
|
||||
var pendingMappings = stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue) ? pendingMappingsValue.AsDocumentArray : new DocumentArray();
|
||||
Assert.Empty(pendingMappings);
|
||||
|
||||
Assert.Equal(1, collector.GetValue("nvd.fetch.documents"));
|
||||
@@ -462,7 +464,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var stateRepository = fetchProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pending = state!.Cursor.TryGetValue("pendingDocuments", out var value)
|
||||
var stateValue = state!;
|
||||
var pending = stateValue.Cursor.TryGetValue("pendingDocuments", out var value)
|
||||
? value.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.NotEmpty(pending);
|
||||
@@ -492,7 +495,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var stateRepository = resumeProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var finalState = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(finalState);
|
||||
var cursor = finalState!.Cursor;
|
||||
var finalStateValue = finalState!;
|
||||
var cursor = finalStateValue.Cursor;
|
||||
var finalPendingDocs = cursor.TryGetValue("pendingDocuments", out var pendingDocs) ? pendingDocs.AsDocumentArray : new DocumentArray();
|
||||
Assert.Empty(finalPendingDocs);
|
||||
var finalPendingMappings = cursor.TryGetValue("pendingMappings", out var pendingMappings) ? pendingMappings.AsDocumentArray : new DocumentArray();
|
||||
|
||||
@@ -3,7 +3,6 @@ using StellaOps.Concelier.Documents;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Osv.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Osv.Tests;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Osv;
|
||||
using StellaOps.Concelier.Connector.Osv.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Osv;
|
||||
using StellaOps.Concelier.Connector.Osv.Internal;
|
||||
using StellaOps.Concelier.Normalization.Identifiers;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Osv.Tests;
|
||||
|
||||
@@ -7,7 +7,6 @@ using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Osv;
|
||||
using StellaOps.Concelier.Connector.Osv.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Connector.Common;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ using StellaOps.Concelier.Connector.Ru.Bdu.Configuration;
|
||||
using StellaOps.Concelier.Connector.Ru.Bdu.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
@@ -21,7 +21,6 @@ using StellaOps.Concelier.Connector.Ru.Nkcki;
|
||||
using StellaOps.Concelier.Connector.Ru.Nkcki.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
|
||||
@@ -20,8 +20,6 @@ using StellaOps.Concelier.Connector.StellaOpsMirror.Internal;
|
||||
using StellaOps.Concelier.Connector.StellaOpsMirror.Settings;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
@@ -23,8 +23,6 @@ using StellaOps.Concelier.Connector.Vndr.Adobe;
|
||||
using StellaOps.Concelier.Connector.Vndr.Adobe.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
@@ -132,7 +130,8 @@ public sealed class AdobeConnectorFetchTests : IAsyncLifetime
|
||||
|
||||
var state = await stateRepository.TryGetAsync(VndrAdobeConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var cursor = state!.Cursor;
|
||||
var stateValue = state!;
|
||||
var cursor = stateValue.Cursor;
|
||||
Assert.True(!cursor.TryGetValue("pendingDocuments", out _)
|
||||
|| cursor.GetValue("pendingDocuments").AsDocumentArray.Count == 0);
|
||||
Assert.True(!cursor.TryGetValue("pendingMappings", out _)
|
||||
@@ -314,8 +313,9 @@ public sealed class AdobeConnectorFetchTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(VndrAdobeConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMap) && pendingMap.AsDocumentArray.Count == 0);
|
||||
var stateValue = state!;
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
|
||||
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMap) && pendingMap.AsDocumentArray.Count == 0);
|
||||
}
|
||||
|
||||
private async Task<ServiceProvider> BuildServiceProviderAsync(CannedHttpMessageHandler handler)
|
||||
|
||||
@@ -19,7 +19,6 @@ using StellaOps.Concelier.Connector.Vndr.Chromium;
|
||||
using StellaOps.Concelier.Connector.Vndr.Chromium.Configuration;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Concelier.Testing;
|
||||
|
||||
@@ -137,7 +136,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
var stateValue = state!;
|
||||
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
? pendingDocsValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Empty(pendingDocuments);
|
||||
@@ -167,7 +167,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
|
||||
var stateRepository = fetchProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
var stateValue = state!;
|
||||
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
? pendingDocsValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.NotEmpty(pendingDocuments);
|
||||
@@ -180,7 +181,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
|
||||
var stateRepositoryBefore = resumeProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var resumeState = await stateRepositoryBefore.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(resumeState);
|
||||
var resumePendingDocs = resumeState!.Cursor.TryGetValue("pendingDocuments", out var resumePendingValue)
|
||||
var resumeStateValue = resumeState!;
|
||||
var resumePendingDocs = resumeStateValue.Cursor.TryGetValue("pendingDocuments", out var resumePendingValue)
|
||||
? resumePendingValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Equal(pendingDocumentIds.Length, resumePendingDocs.Count);
|
||||
@@ -202,12 +204,13 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
|
||||
var stateRepositoryAfter = resumeProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var finalState = await stateRepositoryAfter.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(finalState);
|
||||
var finalPending = finalState!.Cursor.TryGetValue("pendingDocuments", out var finalPendingDocs)
|
||||
var finalStateValue = finalState!;
|
||||
var finalPending = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var finalPendingDocs)
|
||||
? finalPendingDocs.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Empty(finalPending);
|
||||
|
||||
var finalPendingMappings = finalState.Cursor.TryGetValue("pendingMappings", out var finalPendingMappingsValue)
|
||||
var finalPendingMappings = finalStateValue.Cursor.TryGetValue("pendingMappings", out var finalPendingMappingsValue)
|
||||
? finalPendingMappingsValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
Assert.Empty(finalPendingMappings);
|
||||
@@ -246,7 +249,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
var cursor = state!.Cursor;
|
||||
var stateValue = state!;
|
||||
var cursor = stateValue.Cursor;
|
||||
var pendingDocuments = cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
|
||||
? pendingDocsValue.AsDocumentArray
|
||||
: new DocumentArray();
|
||||
|
||||
@@ -8,7 +8,6 @@ using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Vndr.Cisco;
|
||||
using StellaOps.Concelier.Connector.Vndr.Cisco.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
|
||||
@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Vndr.Msrc.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Msrc.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
@@ -74,7 +72,8 @@ public sealed class MsrcConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(MsrcConnectorPlugin.SourceName, CancellationToken.None);
|
||||
state.Should().NotBeNull();
|
||||
state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs).Should().BeTrue();
|
||||
var stateValue = state!;
|
||||
stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs).Should().BeTrue();
|
||||
pendingDocs!.AsDocumentArray.Should().BeEmpty();
|
||||
|
||||
var documentStore = provider.GetRequiredService<IDocumentStore>();
|
||||
|
||||
@@ -24,8 +24,6 @@ using StellaOps.Concelier.Connector.Vndr.Oracle.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Oracle.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Concelier.Testing;
|
||||
|
||||
@@ -23,8 +23,6 @@ using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
|
||||
using StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Storage.PsirtFlags;
|
||||
using StellaOps.Concelier.Testing;
|
||||
@@ -98,9 +96,10 @@ public sealed class VmwareConnectorTests : IAsyncLifetime
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(VmwareConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
Assert.Empty(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) ? pendingDocs.AsDocumentArray : new DocumentArray());
|
||||
Assert.Empty(state.Cursor.TryGetValue("pendingMappings", out var pendingMaps) ? pendingMaps.AsDocumentArray : new DocumentArray());
|
||||
var cursorSnapshot = VmwareCursor.FromDocument(state.Cursor);
|
||||
var stateValue = state!;
|
||||
Assert.Empty(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) ? pendingDocs.AsDocumentArray : new DocumentArray());
|
||||
Assert.Empty(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMaps) ? pendingMaps.AsDocumentArray : new DocumentArray());
|
||||
var cursorSnapshot = VmwareCursor.FromDocument(stateValue.Cursor);
|
||||
_output.WriteLine($"Initial fetch cache entries: {cursorSnapshot.FetchCache.Count}");
|
||||
foreach (var entry in cursorSnapshot.FetchCache)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,6 @@ using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Vndr.Vmware;
|
||||
using StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Tests;
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class EvidenceBundleAttestationBuilderTests
|
||||
manifestPath,
|
||||
transparencyPath,
|
||||
"git:test-sha"),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("evidence-bundle-m0", claims.SubjectName);
|
||||
Assert.StartsWith("sha256:", claims.SubjectDigest);
|
||||
@@ -62,7 +62,7 @@ public sealed class EvidenceBundleAttestationBuilderTests
|
||||
var builder = new EvidenceBundleAttestationBuilder();
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
builder.BuildAsync(new EvidenceBundleAttestationRequest(tempTar, tempManifest, null, "git:test"), CancellationToken.None));
|
||||
builder.BuildAsync(new EvidenceBundleAttestationRequest(tempTar, tempManifest, null, "git:test"), TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains("Tenant must be lowercase", ex.Message);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public sealed class EvidenceBundleAttestationBuilderTests
|
||||
var builder = new EvidenceBundleAttestationBuilder();
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
builder.BuildAsync(new EvidenceBundleAttestationRequest(tempTar, tempManifest, null, "git:test"), CancellationToken.None));
|
||||
builder.BuildAsync(new EvidenceBundleAttestationRequest(tempTar, tempManifest, null, "git:test"), TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains("Tenant must be present", ex.Message);
|
||||
}
|
||||
|
||||
@@ -230,11 +230,11 @@ public sealed class CanonicalMergerTests
|
||||
var reference = Assert.Single(result.Advisory.References);
|
||||
Assert.Equal("https://example.com/path/resource?a=1&b=2", reference.Url);
|
||||
|
||||
var unionDecision = Assert.Single(result.Decisions.Where(decision => decision.Field == "references"));
|
||||
var unionDecision = Assert.Single(result.Decisions, decision => decision.Field == "references");
|
||||
Assert.Null(unionDecision.SelectedSource);
|
||||
Assert.Equal("union", unionDecision.DecisionReason);
|
||||
|
||||
var itemDecision = Assert.Single(result.Decisions.Where(decision => decision.Field.StartsWith("references[", StringComparison.OrdinalIgnoreCase)));
|
||||
var itemDecision = Assert.Single(result.Decisions, decision => decision.Field.StartsWith("references[", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Equal("osv", itemDecision.SelectedSource);
|
||||
Assert.Equal("freshness_override", itemDecision.DecisionReason);
|
||||
Assert.Contains("https://example.com/path/resource?a=1&b=2", itemDecision.Field, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class VulnExplorerTelemetryTests
|
||||
|
||||
var service = new AdvisoryObservationQueryService(new TestObservationLookup(observations));
|
||||
|
||||
await service.QueryAsync(new AdvisoryObservationQueryOptions("tenant-a"), CancellationToken.None);
|
||||
await service.QueryAsync(new AdvisoryObservationQueryOptions("tenant-a"), TestContext.Current.CancellationToken);
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class AdvisoryEventLogTests
|
||||
AsOf: DateTimeOffset.Parse("2025-10-03T00:00:00Z"),
|
||||
InputDocumentIds: new[] { Guid.Parse("11111111-1111-1111-1111-111111111111") });
|
||||
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(new[] { statementInput }), CancellationToken.None);
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(new[] { statementInput }), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(repository.InsertedStatements);
|
||||
var entry = repository.InsertedStatements.Single();
|
||||
@@ -87,7 +87,7 @@ public sealed class AdvisoryEventLogTests
|
||||
AsOf: DateTimeOffset.Parse("2025-11-10T12:00:00Z"),
|
||||
InputDocumentIds: Array.Empty<Guid>());
|
||||
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(new[] { statementInput }), CancellationToken.None);
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(new[] { statementInput }), TestContext.Current.CancellationToken);
|
||||
|
||||
var entry = Assert.Single(repository.InsertedStatements);
|
||||
Assert.NotNull(entry.Provenance);
|
||||
@@ -112,7 +112,7 @@ public sealed class AdvisoryEventLogTests
|
||||
AsOf: DateTimeOffset.Parse("2025-10-04T00:00:00Z"),
|
||||
StatementIds: new[] { Guid.Parse("22222222-2222-2222-2222-222222222222") });
|
||||
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(Array.Empty<AdvisoryStatementInput>(), new[] { conflictInput }), CancellationToken.None);
|
||||
await log.AppendAsync(new AdvisoryEventAppendRequest(Array.Empty<AdvisoryStatementInput>(), new[] { conflictInput }), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(repository.InsertedConflicts);
|
||||
var entry = repository.InsertedConflicts.Single();
|
||||
@@ -147,7 +147,7 @@ public sealed class AdvisoryEventLogTests
|
||||
|
||||
await log.AppendAsync(
|
||||
new AdvisoryEventAppendRequest(Array.Empty<AdvisoryStatementInput>(), new[] { conflictInput }),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var entry = Assert.Single(repository.InsertedConflicts);
|
||||
Assert.Equal(
|
||||
@@ -225,7 +225,7 @@ public sealed class AdvisoryEventLogTests
|
||||
RecordedAt: DateTimeOffset.Parse("2025-10-04T03:00:00Z"),
|
||||
StatementIds: ImmutableArray<Guid>.Empty));
|
||||
|
||||
var replay = await log.ReplayAsync("CVE-2025-0001", asOf: null, CancellationToken.None);
|
||||
var replay = await log.ReplayAsync("CVE-2025-0001", asOf: null, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("cve-2025-0001", replay.VulnerabilityKey);
|
||||
Assert.Collection(
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class JobCoordinatorTests
|
||||
new TestTimeProvider(),
|
||||
diagnostics);
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, new Dictionary<string, object?> { ["foo"] = "bar" }, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, new Dictionary<string, object?> { ["foo"] = "bar" }, "unit-test", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(JobTriggerOutcome.Accepted, result.Outcome);
|
||||
var completed = await jobStore.Completion.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
@@ -100,7 +100,7 @@ public sealed class JobCoordinatorTests
|
||||
new TestTimeProvider(),
|
||||
diagnostics);
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(JobTriggerOutcome.Accepted, result.Outcome);
|
||||
var completed = await jobStore.Completion.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
@@ -151,7 +151,7 @@ public sealed class JobCoordinatorTests
|
||||
new TestTimeProvider(),
|
||||
diagnostics);
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(JobTriggerOutcome.Accepted, result.Outcome);
|
||||
var completed = await jobStore.Completion.Task.WaitAsync(TimeSpan.FromSeconds(6));
|
||||
@@ -195,7 +195,7 @@ public sealed class JobCoordinatorTests
|
||||
new TestTimeProvider(),
|
||||
diagnostics);
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(JobTriggerOutcome.AlreadyRunning, result.Outcome);
|
||||
Assert.False(jobStore.CreatedRuns.Any());
|
||||
@@ -237,7 +237,7 @@ public sealed class JobCoordinatorTests
|
||||
["bad"] = new object(),
|
||||
};
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, parameters, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, parameters, "unit-test", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(JobTriggerOutcome.InvalidParameters, result.Outcome);
|
||||
Assert.Contains("unsupported", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
|
||||
@@ -280,7 +280,7 @@ public sealed class JobCoordinatorTests
|
||||
new TestTimeProvider(),
|
||||
diagnostics);
|
||||
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", CancellationToken.None);
|
||||
var result = await coordinator.TriggerAsync(definition.Kind, null, "unit-test", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(JobTriggerOutcome.Accepted, result.Outcome);
|
||||
|
||||
var completed = await jobStore.Completion.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class AdvisoryLinksetQueryServiceTests
|
||||
var lookup = new FakeLinksetLookup(linksets);
|
||||
var service = new AdvisoryLinksetQueryService(lookup);
|
||||
|
||||
var firstPage = await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 2), CancellationToken.None);
|
||||
var firstPage = await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 2), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, firstPage.Linksets.Length);
|
||||
Assert.True(firstPage.HasMore);
|
||||
@@ -40,7 +40,7 @@ public sealed class AdvisoryLinksetQueryServiceTests
|
||||
Assert.Equal("adv-003", firstPage.Linksets[0].AdvisoryId);
|
||||
Assert.Equal("pkg:npm/a", firstPage.Linksets[0].Normalized?.Purls?.First());
|
||||
|
||||
var secondPage = await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 2, Cursor: firstPage.NextCursor), CancellationToken.None);
|
||||
var secondPage = await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 2, Cursor: firstPage.NextCursor), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(secondPage.Linksets);
|
||||
Assert.False(secondPage.HasMore);
|
||||
@@ -56,7 +56,7 @@ public sealed class AdvisoryLinksetQueryServiceTests
|
||||
|
||||
await Assert.ThrowsAsync<FormatException>(async () =>
|
||||
{
|
||||
await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 1, Cursor: "not-base64"), CancellationToken.None);
|
||||
await service.QueryAsync(new AdvisoryLinksetQueryOptions("tenant", Limit: 1, Cursor: "not-base64"), TestContext.Current.CancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public sealed class NoisePriorServiceTests
|
||||
|
||||
var result = await service.RecomputeAsync(
|
||||
new NoisePriorComputationRequest("CVE-9999-0001"),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("cve-9999-0001", result.VulnerabilityKey);
|
||||
Assert.Single(result.Summaries);
|
||||
@@ -135,7 +135,7 @@ public sealed class NoisePriorServiceTests
|
||||
|
||||
var result = await service.RecomputeAsync(
|
||||
new NoisePriorComputationRequest("cve-2025-1111"),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var summary = Assert.Single(result.Summaries);
|
||||
Assert.Equal(1.0, summary.Probability);
|
||||
@@ -170,13 +170,13 @@ public sealed class NoisePriorServiceTests
|
||||
|
||||
await service.RecomputeAsync(
|
||||
new NoisePriorComputationRequest("CVE-2025-2000"),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var summaries = await service.GetByPackageAsync(
|
||||
" SemVer ",
|
||||
"pkg:npm/example",
|
||||
" linux ",
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(summaries);
|
||||
Assert.Equal("semver", summaries[0].PackageType);
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
||||
var lookup = new InMemoryLookup(observations);
|
||||
var service = new AdvisoryObservationQueryService(lookup);
|
||||
|
||||
var result = await service.QueryAsync(new AdvisoryObservationQueryOptions("tenant-a"), CancellationToken.None);
|
||||
var result = await service.QueryAsync(new AdvisoryObservationQueryOptions("tenant-a"), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, result.Observations.Length);
|
||||
Assert.Equal("tenant-a:osv:beta:1", result.Observations[0].ObservationId);
|
||||
@@ -118,7 +118,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
||||
|
||||
var result = await service.QueryAsync(
|
||||
new AdvisoryObservationQueryOptions("TEnant-A", aliases: new[] { " CVE-2025-0001 ", "CVE-2025-9999" }),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, result.Observations.Length);
|
||||
Assert.All(result.Observations, observation =>
|
||||
@@ -164,7 +164,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
||||
purls: new[] { "pkg:pypi/package-b@2.0.0" },
|
||||
cpes: new[] { "cpe:/a:vendor:product:2.0" });
|
||||
|
||||
var result = await service.QueryAsync(options, CancellationToken.None);
|
||||
var result = await service.QueryAsync(options, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Observations);
|
||||
Assert.Equal("tenant-a:ghsa:beta:1", result.Observations[0].ObservationId);
|
||||
@@ -212,7 +212,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
||||
|
||||
var firstPage = await service.QueryAsync(
|
||||
new AdvisoryObservationQueryOptions("tenant-a", limit: 2),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, firstPage.Observations.Length);
|
||||
Assert.True(firstPage.HasMore);
|
||||
@@ -220,7 +220,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
||||
|
||||
var secondPage = await service.QueryAsync(
|
||||
new AdvisoryObservationQueryOptions("tenant-a", limit: 2, cursor: firstPage.NextCursor),
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(secondPage.Observations);
|
||||
Assert.False(secondPage.HasMore);
|
||||
|
||||
@@ -10,9 +10,9 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
var record = CreateRegistryRecord("tenant-1", "connector-1");
|
||||
|
||||
await store.UpsertAsync(record, CancellationToken.None);
|
||||
await store.UpsertAsync(record, TestContext.Current.CancellationToken);
|
||||
|
||||
var retrieved = await store.GetAsync("tenant-1", "connector-1", CancellationToken.None);
|
||||
var retrieved = await store.GetAsync("tenant-1", "connector-1", TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(retrieved);
|
||||
Assert.Equal("tenant-1", retrieved.Tenant);
|
||||
Assert.Equal("connector-1", retrieved.ConnectorId);
|
||||
@@ -25,10 +25,10 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var record1 = CreateRegistryRecord("tenant-1", "connector-1", source: "nvd");
|
||||
var record2 = CreateRegistryRecord("tenant-1", "connector-1", source: "osv");
|
||||
|
||||
await store.UpsertAsync(record1, CancellationToken.None);
|
||||
await store.UpsertAsync(record2, CancellationToken.None);
|
||||
await store.UpsertAsync(record1, TestContext.Current.CancellationToken);
|
||||
await store.UpsertAsync(record2, TestContext.Current.CancellationToken);
|
||||
|
||||
var retrieved = await store.GetAsync("tenant-1", "connector-1", CancellationToken.None);
|
||||
var retrieved = await store.GetAsync("tenant-1", "connector-1", TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(retrieved);
|
||||
Assert.Equal("osv", retrieved.Source);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
{
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
|
||||
var retrieved = await store.GetAsync("tenant-1", "nonexistent", CancellationToken.None);
|
||||
var retrieved = await store.GetAsync("tenant-1", "nonexistent", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Null(retrieved);
|
||||
}
|
||||
@@ -47,11 +47,11 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
public async Task ListAsync_ReturnsRecordsForTenant()
|
||||
{
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-a"), CancellationToken.None);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-b"), CancellationToken.None);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-2", "connector-c"), CancellationToken.None);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-a"), TestContext.Current.CancellationToken);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-b"), TestContext.Current.CancellationToken);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-2", "connector-c"), TestContext.Current.CancellationToken);
|
||||
|
||||
var records = await store.ListAsync("tenant-1", CancellationToken.None);
|
||||
var records = await store.ListAsync("tenant-1", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, records.Count);
|
||||
Assert.All(records, r => Assert.Equal("tenant-1", r.Tenant));
|
||||
@@ -61,10 +61,10 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
public async Task ListAsync_ReturnsOrderedByConnectorId()
|
||||
{
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "zzz-connector"), CancellationToken.None);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "aaa-connector"), CancellationToken.None);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "zzz-connector"), TestContext.Current.CancellationToken);
|
||||
await store.UpsertAsync(CreateRegistryRecord("tenant-1", "aaa-connector"), TestContext.Current.CancellationToken);
|
||||
|
||||
var records = await store.ListAsync("tenant-1", CancellationToken.None);
|
||||
var records = await store.ListAsync("tenant-1", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("aaa-connector", records[0].ConnectorId);
|
||||
Assert.Equal("zzz-connector", records[1].ConnectorId);
|
||||
@@ -80,9 +80,9 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
OrchestratorHeartbeatStatus.Running, 50, 10,
|
||||
null, null, null, null, DateTimeOffset.UtcNow);
|
||||
|
||||
await store.AppendHeartbeatAsync(heartbeat, CancellationToken.None);
|
||||
await store.AppendHeartbeatAsync(heartbeat, TestContext.Current.CancellationToken);
|
||||
|
||||
var latest = await store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, CancellationToken.None);
|
||||
var latest = await store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(latest);
|
||||
Assert.Equal(1, latest.Sequence);
|
||||
Assert.Equal(OrchestratorHeartbeatStatus.Running, latest.Status);
|
||||
@@ -95,11 +95,11 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var runId = Guid.NewGuid();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 1, OrchestratorHeartbeatStatus.Starting, now), CancellationToken.None);
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 3, OrchestratorHeartbeatStatus.Succeeded, now.AddMinutes(2)), CancellationToken.None);
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 2, OrchestratorHeartbeatStatus.Running, now.AddMinutes(1)), CancellationToken.None);
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 1, OrchestratorHeartbeatStatus.Starting, now), TestContext.Current.CancellationToken);
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 3, OrchestratorHeartbeatStatus.Succeeded, now.AddMinutes(2)), TestContext.Current.CancellationToken);
|
||||
await store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 2, OrchestratorHeartbeatStatus.Running, now.AddMinutes(1)), TestContext.Current.CancellationToken);
|
||||
|
||||
var latest = await store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, CancellationToken.None);
|
||||
var latest = await store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(latest);
|
||||
Assert.Equal(3, latest.Sequence);
|
||||
@@ -116,9 +116,9 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
OrchestratorCommandKind.Pause, null, null,
|
||||
DateTimeOffset.UtcNow, null);
|
||||
|
||||
await store.EnqueueCommandAsync(command, CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, null, CancellationToken.None);
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, null, TestContext.Current.CancellationToken);
|
||||
Assert.Single(commands);
|
||||
Assert.Equal(OrchestratorCommandKind.Pause, commands[0].Command);
|
||||
}
|
||||
@@ -130,11 +130,11 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var runId = Guid.NewGuid();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 1, OrchestratorCommandKind.Pause, now), CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 2, OrchestratorCommandKind.Resume, now), CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 3, OrchestratorCommandKind.Throttle, now), CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 1, OrchestratorCommandKind.Pause, now), TestContext.Current.CancellationToken);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 2, OrchestratorCommandKind.Resume, now), TestContext.Current.CancellationToken);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 3, OrchestratorCommandKind.Throttle, now), TestContext.Current.CancellationToken);
|
||||
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, 1, CancellationToken.None);
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, 1, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, commands.Count);
|
||||
Assert.Equal(2, commands[0].Sequence);
|
||||
@@ -150,10 +150,10 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var expired = now.AddMinutes(-5);
|
||||
var future = now.AddMinutes(5);
|
||||
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 1, OrchestratorCommandKind.Pause, now, expired), CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 2, OrchestratorCommandKind.Resume, now, future), CancellationToken.None);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 1, OrchestratorCommandKind.Pause, now, expired), TestContext.Current.CancellationToken);
|
||||
await store.EnqueueCommandAsync(CreateCommand("tenant-1", "connector-1", runId, 2, OrchestratorCommandKind.Resume, now, future), TestContext.Current.CancellationToken);
|
||||
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, null, CancellationToken.None);
|
||||
var commands = await store.GetPendingCommandsAsync("tenant-1", "connector-1", runId, null, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(commands);
|
||||
Assert.Equal(2, commands[0].Sequence);
|
||||
@@ -171,9 +171,9 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
"dsse-hash",
|
||||
DateTimeOffset.UtcNow);
|
||||
|
||||
await store.StoreManifestAsync(manifest, CancellationToken.None);
|
||||
await store.StoreManifestAsync(manifest, TestContext.Current.CancellationToken);
|
||||
|
||||
var retrieved = await store.GetManifestAsync("tenant-1", "connector-1", runId, CancellationToken.None);
|
||||
var retrieved = await store.GetManifestAsync("tenant-1", "connector-1", runId, TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(retrieved);
|
||||
Assert.Equal(runId, retrieved.RunId);
|
||||
Assert.Equal(2, retrieved.ArtifactHashes.Count);
|
||||
@@ -185,7 +185,7 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
{
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
|
||||
var manifest = await store.GetManifestAsync("tenant-1", "connector-1", Guid.NewGuid(), CancellationToken.None);
|
||||
var manifest = await store.GetManifestAsync("tenant-1", "connector-1", Guid.NewGuid(), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Null(manifest);
|
||||
}
|
||||
@@ -196,13 +196,13 @@ public sealed class OrchestratorRegistryStoreTests
|
||||
var store = new InMemoryOrchestratorRegistryStore();
|
||||
var runId = Guid.NewGuid();
|
||||
|
||||
store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-1"), CancellationToken.None).Wait();
|
||||
store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 1, OrchestratorHeartbeatStatus.Running, DateTimeOffset.UtcNow), CancellationToken.None).Wait();
|
||||
store.UpsertAsync(CreateRegistryRecord("tenant-1", "connector-1"), TestContext.Current.CancellationToken).Wait();
|
||||
store.AppendHeartbeatAsync(CreateHeartbeat("tenant-1", "connector-1", runId, 1, OrchestratorHeartbeatStatus.Running, DateTimeOffset.UtcNow), TestContext.Current.CancellationToken).Wait();
|
||||
|
||||
store.Clear();
|
||||
|
||||
Assert.Null(store.GetAsync("tenant-1", "connector-1", CancellationToken.None).Result);
|
||||
Assert.Null(store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, CancellationToken.None).Result);
|
||||
Assert.Null(store.GetAsync("tenant-1", "connector-1", TestContext.Current.CancellationToken).Result);
|
||||
Assert.Null(store.GetLatestHeartbeatAsync("tenant-1", "connector-1", runId, TestContext.Current.CancellationToken).Result);
|
||||
}
|
||||
|
||||
private static OrchestratorRegistryRecord CreateRegistryRecord(string tenant, string connectorId, string source = "nvd")
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
var expectedResult = new AdvisoryRawUpsertResult(true, CreateRecord(storedDocument));
|
||||
repository.NextResult = expectedResult;
|
||||
|
||||
var result = await service.IngestAsync(document, CancellationToken.None);
|
||||
var result = await service.IngestAsync(document, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(repository.CapturedDocument);
|
||||
Assert.Null(repository.CapturedDocument!.Supersedes);
|
||||
@@ -55,7 +55,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
var expectedResult = new AdvisoryRawUpsertResult(false, CreateRecord(existingDocument));
|
||||
repository.NextResult = expectedResult;
|
||||
|
||||
var result = await service.IngestAsync(CreateDocument(), CancellationToken.None);
|
||||
var result = await service.IngestAsync(CreateDocument(), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.False(result.Inserted);
|
||||
Assert.Same(expectedResult.Record, result.Record);
|
||||
@@ -70,7 +70,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
|
||||
var measurements = await CollectCounterMeasurementsAsync(
|
||||
"ingestion_write_total",
|
||||
() => service.IngestAsync(CreateDocument(), CancellationToken.None));
|
||||
() => service.IngestAsync(CreateDocument(), TestContext.Current.CancellationToken));
|
||||
|
||||
Assert.Contains(
|
||||
measurements,
|
||||
@@ -89,7 +89,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
async () =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConcelierAocGuardException>(
|
||||
() => service.IngestAsync(CreateDocument(), CancellationToken.None));
|
||||
() => service.IngestAsync(CreateDocument(), TestContext.Current.CancellationToken));
|
||||
});
|
||||
|
||||
Assert.Contains(
|
||||
@@ -102,7 +102,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
async () =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConcelierAocGuardException>(
|
||||
() => service.IngestAsync(CreateDocument(), CancellationToken.None));
|
||||
() => service.IngestAsync(CreateDocument(), TestContext.Current.CancellationToken));
|
||||
});
|
||||
|
||||
Assert.Contains(
|
||||
@@ -125,7 +125,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
|
||||
repository.NextResult = new AdvisoryRawUpsertResult(true, CreateRecord(document));
|
||||
|
||||
await service.IngestAsync(document, CancellationToken.None);
|
||||
await service.IngestAsync(document, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(repository.CapturedDocument);
|
||||
Assert.True(aliasSeries.SequenceEqual(repository.CapturedDocument!.Identifiers.Aliases));
|
||||
@@ -148,7 +148,7 @@ public sealed class AdvisoryRawServiceTests
|
||||
"Tenant-Example",
|
||||
"ghsa-aaaa-bbbb-cccc",
|
||||
new[] { "Vendor-X", " " },
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(results);
|
||||
Assert.Equal("tenant-example", repository.CapturedTenant);
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class AffectedSymbolProviderTests
|
||||
_timeProvider,
|
||||
NullLogger<AffectedSymbolProvider>.Instance);
|
||||
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", CancellationToken.None);
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("tenant-1", result.TenantId);
|
||||
Assert.Equal("CVE-2024-0001", result.AdvisoryId);
|
||||
@@ -53,9 +53,9 @@ public sealed class AffectedSymbolProviderTests
|
||||
module: "lodash",
|
||||
versionRange: "<4.17.21");
|
||||
|
||||
await store.StoreAsync([symbol], CancellationToken.None);
|
||||
await store.StoreAsync([symbol], TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", CancellationToken.None);
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Symbols);
|
||||
Assert.Equal("lodash.template", result.Symbols[0].Symbol);
|
||||
@@ -84,9 +84,9 @@ public sealed class AffectedSymbolProviderTests
|
||||
AffectedSymbol.Method("tenant-1", "CVE-2024-0001", "obs-3", "method1", "ClassName", nvdProvenance, _timeProvider.GetUtcNow())
|
||||
};
|
||||
|
||||
await store.StoreAsync(symbols, CancellationToken.None);
|
||||
await store.StoreAsync(symbols, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", CancellationToken.None);
|
||||
var result = await provider.GetByAdvisoryAsync("tenant-1", "CVE-2024-0001", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(3, result.Symbols.Length);
|
||||
Assert.Equal(2, result.SourceSummaries.Length);
|
||||
@@ -121,9 +121,9 @@ public sealed class AffectedSymbolProviderTests
|
||||
extractedAt: _timeProvider.GetUtcNow(),
|
||||
purl: "pkg:npm/express@4.18.0");
|
||||
|
||||
await store.StoreAsync([symbol], CancellationToken.None);
|
||||
await store.StoreAsync([symbol], TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await provider.GetByPackageAsync("tenant-1", "pkg:npm/express@4.18.0", CancellationToken.None);
|
||||
var result = await provider.GetByPackageAsync("tenant-1", "pkg:npm/express@4.18.0", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Symbols);
|
||||
Assert.Equal("express.render", result.Symbols[0].Symbol);
|
||||
@@ -146,10 +146,10 @@ public sealed class AffectedSymbolProviderTests
|
||||
AffectedSymbol.Function("tenant-1", "CVE-2024-0002", "obs-2", "func2", provenance, _timeProvider.GetUtcNow())
|
||||
};
|
||||
|
||||
await store.StoreAsync(symbols, CancellationToken.None);
|
||||
await store.StoreAsync(symbols, TestContext.Current.CancellationToken);
|
||||
|
||||
var options = AffectedSymbolQueryOptions.ForAdvisory("tenant-1", "CVE-2024-0001");
|
||||
var result = await provider.QueryAsync(options, CancellationToken.None);
|
||||
var result = await provider.QueryAsync(options, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(1, result.TotalCount);
|
||||
Assert.Single(result.Symbols);
|
||||
@@ -173,12 +173,12 @@ public sealed class AffectedSymbolProviderTests
|
||||
AffectedSymbol.Method("tenant-1", "CVE-2024-0001", "obs-2", "method1", "Class1", provenance, _timeProvider.GetUtcNow())
|
||||
};
|
||||
|
||||
await store.StoreAsync(symbols, CancellationToken.None);
|
||||
await store.StoreAsync(symbols, TestContext.Current.CancellationToken);
|
||||
|
||||
var options = new AffectedSymbolQueryOptions(
|
||||
TenantId: "tenant-1",
|
||||
SymbolTypes: [AffectedSymbolType.Method]);
|
||||
var result = await provider.QueryAsync(options, CancellationToken.None);
|
||||
var result = await provider.QueryAsync(options, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(1, result.TotalCount);
|
||||
Assert.Equal(AffectedSymbolType.Method, result.Symbols[0].SymbolType);
|
||||
@@ -200,13 +200,13 @@ public sealed class AffectedSymbolProviderTests
|
||||
"tenant-1", "CVE-2024-0001", $"obs-{i}", $"func{i}", provenance, _timeProvider.GetUtcNow()))
|
||||
.ToList();
|
||||
|
||||
await store.StoreAsync(symbols, CancellationToken.None);
|
||||
await store.StoreAsync(symbols, TestContext.Current.CancellationToken);
|
||||
|
||||
var options = new AffectedSymbolQueryOptions(
|
||||
TenantId: "tenant-1",
|
||||
Limit: 3,
|
||||
Offset: 2);
|
||||
var result = await provider.QueryAsync(options, CancellationToken.None);
|
||||
var result = await provider.QueryAsync(options, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(10, result.TotalCount);
|
||||
Assert.Equal(3, result.Symbols.Length);
|
||||
@@ -230,12 +230,12 @@ public sealed class AffectedSymbolProviderTests
|
||||
AffectedSymbol.Function("tenant-1", "CVE-2024-0002", "obs-2", "func2", provenance, _timeProvider.GetUtcNow())
|
||||
};
|
||||
|
||||
await store.StoreAsync(symbols, CancellationToken.None);
|
||||
await store.StoreAsync(symbols, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await provider.GetByAdvisoriesBatchAsync(
|
||||
"tenant-1",
|
||||
["CVE-2024-0001", "CVE-2024-0002", "CVE-2024-0003"],
|
||||
CancellationToken.None);
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Single(result["CVE-2024-0001"].Symbols);
|
||||
@@ -256,10 +256,10 @@ public sealed class AffectedSymbolProviderTests
|
||||
var symbol = AffectedSymbol.Function(
|
||||
"tenant-1", "CVE-2024-0001", "obs-1", "func1", provenance, _timeProvider.GetUtcNow());
|
||||
|
||||
await store.StoreAsync([symbol], CancellationToken.None);
|
||||
await store.StoreAsync([symbol], TestContext.Current.CancellationToken);
|
||||
|
||||
var exists = await provider.HasSymbolsAsync("tenant-1", "CVE-2024-0001", CancellationToken.None);
|
||||
var notExists = await provider.HasSymbolsAsync("tenant-1", "CVE-2024-9999", CancellationToken.None);
|
||||
var exists = await provider.HasSymbolsAsync("tenant-1", "CVE-2024-0001", TestContext.Current.CancellationToken);
|
||||
var notExists = await provider.HasSymbolsAsync("tenant-1", "CVE-2024-9999", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(exists);
|
||||
Assert.False(notExists);
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class UnknownStateLedgerTests
|
||||
});
|
||||
|
||||
var request = new UnknownStateLedgerRequest("CVE-2025-1111", advisory, observedAt);
|
||||
var result = await ledger.RecordAsync(request, CancellationToken.None);
|
||||
var result = await ledger.RecordAsync(request, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(advisory.Provenance.IsDefaultOrEmpty);
|
||||
Assert.Equal("cve-2025-1111", result.VulnerabilityKey);
|
||||
@@ -62,7 +62,7 @@ public sealed class UnknownStateLedgerTests
|
||||
Assert.Equal("medium", fixMarker.ConfidenceBand);
|
||||
Assert.Contains("explicit fixed version", fixMarker.Evidence, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var repositoryMarkers = await repository.GetByVulnerabilityAsync("cve-2025-1111", CancellationToken.None);
|
||||
var repositoryMarkers = await repository.GetByVulnerabilityAsync("cve-2025-1111", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(3, repositoryMarkers.Count);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class UnknownStateLedgerTests
|
||||
}),
|
||||
});
|
||||
|
||||
var result = await ledger.RecordAsync(new UnknownStateLedgerRequest("GHSA-1234", advisory, observedAt), CancellationToken.None);
|
||||
var result = await ledger.RecordAsync(new UnknownStateLedgerRequest("GHSA-1234", advisory, observedAt), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("ghsa-1234", result.VulnerabilityKey);
|
||||
Assert.Empty(result.Markers);
|
||||
@@ -113,7 +113,7 @@ public sealed class UnknownStateLedgerTests
|
||||
repository.Stored["cve-2025-0001"] = new List<UnknownStateSnapshot> { snapshot };
|
||||
|
||||
var ledger = new UnknownStateLedger(repository);
|
||||
var markers = await ledger.GetByVulnerabilityAsync("CVE-2025-0001", CancellationToken.None);
|
||||
var markers = await ledger.GetByVulnerabilityAsync("CVE-2025-0001", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(markers);
|
||||
Assert.Equal(snapshot, markers[0]);
|
||||
|
||||
@@ -302,7 +302,8 @@ public sealed class MergePropertyTests
|
||||
// Assert - merge provenance trace should contain all original sources
|
||||
var mergeProvenance = result.Provenance.FirstOrDefault(p => p.Source == "merge");
|
||||
mergeProvenance.Should().NotBeNull();
|
||||
mergeProvenance!.Value.ToLowerInvariant().Should().Contain("redhat");
|
||||
Assert.NotNull(mergeProvenance);
|
||||
mergeProvenance.Value.ToLowerInvariant().Should().Contain("redhat");
|
||||
mergeProvenance.Value.ToLowerInvariant().Should().Contain("ghsa");
|
||||
mergeProvenance.Value.ToLowerInvariant().Should().Contain("osv");
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ public sealed class AdvisoryCanonicalRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new AdvisoryCanonicalRepository(_dataSource, NullLogger<AdvisoryCanonicalRepository>.Instance);
|
||||
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
#region GetByIdAsync Tests
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ public sealed class AdvisoryIdempotencyTests : IAsyncLifetime
|
||||
{
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
|
||||
var options = _fixture.Fixture.CreateOptions();
|
||||
var options = _fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_advisoryRepository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
_sourceStateRepository = new SourceStateRepository(_dataSource, NullLogger<SourceStateRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Fact]
|
||||
public async Task UpsertAsync_SameAdvisoryKey_Twice_NosDuplicates()
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class AdvisoryRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
|
||||
_repository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
@@ -36,7 +36,7 @@ public sealed class AdvisoryRepositoryTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
// Description: Model S1 migration tests for Concelier.Storage
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
using Dapper;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Infrastructure.Postgres.Migrations;
|
||||
using Npgsql;
|
||||
using StellaOps.TestKit;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace StellaOps.Concelier.Persistence.Tests;
|
||||
|
||||
@@ -31,19 +33,34 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
_container = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:16-alpine")
|
||||
.WithDatabase("concelier_migration_test")
|
||||
.WithUsername("postgres")
|
||||
.WithPassword("postgres")
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
_container = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:16-alpine")
|
||||
.WithDatabase("concelier_migration_test")
|
||||
.WithUsername("postgres")
|
||||
.WithPassword("postgres")
|
||||
.Build();
|
||||
|
||||
await _container.StartAsync();
|
||||
await _container.StartAsync();
|
||||
}
|
||||
catch (ArgumentException ex) when (ShouldSkipForMissingDocker(ex))
|
||||
{
|
||||
await SafeDisposeAsync();
|
||||
throw SkipException.ForSkip(
|
||||
$"Postgres migration tests require Docker/Testcontainers. Skipping because the container failed to start: {ex.Message}");
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
await SafeDisposeAsync();
|
||||
throw SkipException.ForSkip(
|
||||
$"Postgres migration tests require Docker/Testcontainers. Skipping because the container startup timed out: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _container.DisposeAsync();
|
||||
await SafeDisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -61,7 +78,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
var tables = await connection.QueryAsync<string>(
|
||||
@"SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
WHERE table_schema = 'vuln'
|
||||
ORDER BY table_name");
|
||||
|
||||
var tableList = tables.ToList();
|
||||
@@ -69,7 +86,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
// Verify critical Concelier tables exist
|
||||
tableList.Should().Contain("advisories", "advisories table should exist");
|
||||
tableList.Should().Contain("sources", "sources table should exist");
|
||||
tableList.Should().Contain("__migrations", "Migration tracking table should exist");
|
||||
tableList.Should().Contain("schema_migrations", "Migration tracking table should exist");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -84,7 +101,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
await connection.OpenAsync();
|
||||
|
||||
var migrationsApplied = await connection.QueryAsync<string>(
|
||||
"SELECT migration_id FROM __migrations ORDER BY applied_at");
|
||||
"SELECT migration_name FROM vuln.schema_migrations ORDER BY applied_at");
|
||||
|
||||
var migrationList = migrationsApplied.ToList();
|
||||
migrationList.Should().NotBeEmpty("migrations should be tracked");
|
||||
@@ -109,11 +126,11 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
await connection.OpenAsync();
|
||||
|
||||
var migrationCount = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(*) FROM __migrations");
|
||||
"SELECT COUNT(*) FROM vuln.schema_migrations");
|
||||
|
||||
// Count unique migrations
|
||||
var uniqueMigrations = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(DISTINCT migration_id) FROM __migrations");
|
||||
"SELECT COUNT(DISTINCT migration_name) FROM vuln.schema_migrations");
|
||||
|
||||
migrationCount.Should().Be(uniqueMigrations,
|
||||
"each migration should only be recorded once");
|
||||
@@ -132,7 +149,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
var indexes = await connection.QueryAsync<string>(
|
||||
@"SELECT indexname FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
WHERE schemaname = 'vuln'
|
||||
ORDER BY indexname");
|
||||
|
||||
var indexList = indexes.ToList();
|
||||
@@ -152,7 +169,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
var advisoryColumns = await connection.QueryAsync<string>(
|
||||
@"SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = 'advisories' AND table_schema = 'public'
|
||||
WHERE table_name = 'advisories' AND table_schema = 'vuln'
|
||||
ORDER BY ordinal_position");
|
||||
|
||||
var columnList = advisoryColumns.ToList();
|
||||
@@ -177,7 +194,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
var sourceColumns = await connection.QueryAsync<string>(
|
||||
@"SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = 'sources' AND table_schema = 'public'
|
||||
WHERE table_name = 'sources' AND table_schema = 'vuln'
|
||||
ORDER BY ordinal_position");
|
||||
|
||||
var columnList = sourceColumns.ToList();
|
||||
@@ -195,49 +212,15 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
// Arrange
|
||||
var connectionString = _container.GetConnectionString();
|
||||
|
||||
// Act - Apply migrations in sequence
|
||||
var migrationFiles = GetMigrationFiles();
|
||||
// Act - Apply migrations from scratch
|
||||
await ApplyAllMigrationsAsync(connectionString);
|
||||
|
||||
// Assert - at least some migrations should be applied (if any exist)
|
||||
await using var connection = new NpgsqlConnection(connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Create migration tracking table first
|
||||
await connection.ExecuteAsync(@"
|
||||
CREATE TABLE IF NOT EXISTS __migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
migration_id TEXT NOT NULL UNIQUE,
|
||||
applied_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)");
|
||||
|
||||
// Apply each migration in order
|
||||
int appliedCount = 0;
|
||||
foreach (var migrationFile in migrationFiles.OrderBy(f => f))
|
||||
{
|
||||
var migrationId = Path.GetFileName(migrationFile);
|
||||
|
||||
// Check if already applied
|
||||
var alreadyApplied = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(*) FROM __migrations WHERE migration_id = @Id",
|
||||
new { Id = migrationId });
|
||||
|
||||
if (alreadyApplied > 0)
|
||||
continue;
|
||||
|
||||
// Apply migration
|
||||
var sql = GetMigrationContent(migrationFile);
|
||||
if (!string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
await connection.ExecuteAsync(sql);
|
||||
await connection.ExecuteAsync(
|
||||
"INSERT INTO __migrations (migration_id) VALUES (@Id)",
|
||||
new { Id = migrationId });
|
||||
appliedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Assert - at least some migrations should be applied (if any exist)
|
||||
var totalMigrations = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(*) FROM __migrations");
|
||||
"SELECT COUNT(*) FROM vuln.schema_migrations");
|
||||
|
||||
// This test passes even if no migrations exist yet
|
||||
totalMigrations.Should().BeGreaterThanOrEqualTo(0);
|
||||
@@ -258,7 +241,7 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
@"SELECT tc.constraint_name
|
||||
FROM information_schema.table_constraints tc
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND tc.table_schema = 'public'
|
||||
AND tc.table_schema = 'vuln'
|
||||
ORDER BY tc.constraint_name");
|
||||
|
||||
var fkList = foreignKeys.ToList();
|
||||
@@ -268,63 +251,31 @@ public sealed class ConcelierMigrationTests : IAsyncLifetime
|
||||
|
||||
private async Task ApplyAllMigrationsAsync(string connectionString)
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(connectionString);
|
||||
await connection.OpenAsync();
|
||||
var runner = new MigrationRunner(
|
||||
connectionString,
|
||||
ConcelierDataSource.DefaultSchemaName,
|
||||
"Concelier",
|
||||
NullLogger.Instance);
|
||||
|
||||
// Create migration tracking table
|
||||
await connection.ExecuteAsync(@"
|
||||
CREATE TABLE IF NOT EXISTS __migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
migration_id TEXT NOT NULL UNIQUE,
|
||||
applied_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)");
|
||||
await runner.RunFromAssemblyAsync(
|
||||
typeof(ConcelierDataSource).Assembly,
|
||||
resourcePrefix: null,
|
||||
options: null,
|
||||
cancellationToken: default);
|
||||
}
|
||||
|
||||
// Get and apply all migrations
|
||||
var migrationFiles = GetMigrationFiles();
|
||||
|
||||
foreach (var migrationFile in migrationFiles.OrderBy(f => f))
|
||||
private async ValueTask SafeDisposeAsync()
|
||||
{
|
||||
if (_container != null)
|
||||
{
|
||||
var migrationId = Path.GetFileName(migrationFile);
|
||||
|
||||
// Skip if already applied
|
||||
var alreadyApplied = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(*) FROM __migrations WHERE migration_id = @Id",
|
||||
new { Id = migrationId });
|
||||
|
||||
if (alreadyApplied > 0)
|
||||
continue;
|
||||
|
||||
// Apply migration
|
||||
var sql = GetMigrationContent(migrationFile);
|
||||
if (!string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
await connection.ExecuteAsync(sql);
|
||||
await connection.ExecuteAsync(
|
||||
"INSERT INTO __migrations (migration_id) VALUES (@Id)",
|
||||
new { Id = migrationId });
|
||||
}
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetMigrationFiles()
|
||||
private static bool ShouldSkipForMissingDocker(ArgumentException exception)
|
||||
{
|
||||
var assembly = typeof(ConcelierDataSource).Assembly;
|
||||
var resourceNames = assembly.GetManifestResourceNames()
|
||||
.Where(n => n.Contains("Migrations") && n.EndsWith(".sql"))
|
||||
.OrderBy(n => n);
|
||||
|
||||
return resourceNames;
|
||||
}
|
||||
|
||||
private static string GetMigrationContent(string resourceName)
|
||||
{
|
||||
var assembly = typeof(ConcelierDataSource).Assembly;
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
return string.Empty;
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
return reader.ReadToEnd();
|
||||
return string.Equals(exception.ParamName, "DockerEndpointAuthConfig", StringComparison.Ordinal)
|
||||
|| exception.Message.Contains("Docker is either not running", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Reflection;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Infrastructure.Postgres.Testing;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Persistence.Tests;
|
||||
@@ -15,13 +16,21 @@ public sealed class ConcelierPostgresFixture : PostgresIntegrationFixture, IColl
|
||||
=> typeof(ConcelierDataSource).Assembly;
|
||||
|
||||
protected override string GetModuleName() => "Concelier";
|
||||
|
||||
public PostgresOptions CreateOptions()
|
||||
{
|
||||
var options = Fixture.CreateOptions();
|
||||
options.MaxPoolSize = 10;
|
||||
options.MinPoolSize = 0;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection definition for Concelier PostgreSQL integration tests.
|
||||
/// Tests in this collection share a single PostgreSQL container instance.
|
||||
/// </summary>
|
||||
[CollectionDefinition(Name)]
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class ConcelierPostgresCollection : ICollectionFixture<ConcelierPostgresFixture>
|
||||
{
|
||||
public const string Name = "ConcelierPostgres";
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class ConcelierQueryDeterminismTests : IAsyncLifetime
|
||||
{
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
|
||||
var options = _fixture.Fixture.CreateOptions();
|
||||
var options = _fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_advisoryRepository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
@@ -52,7 +52,7 @@ public sealed class ConcelierQueryDeterminismTests : IAsyncLifetime
|
||||
_affectedRepository = new AdvisoryAffectedRepository(_dataSource, NullLogger<AdvisoryAffectedRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Fact]
|
||||
public async Task GetModifiedSinceAsync_MultipleQueries_ReturnsDeterministicOrder()
|
||||
|
||||
@@ -9,8 +9,10 @@ using FluentAssertions;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Core.Canonical;
|
||||
using StellaOps.Concelier.Interest;
|
||||
using StellaOps.Concelier.Interest.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
@@ -27,18 +29,20 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
private readonly ConcelierPostgresFixture _fixture;
|
||||
private readonly ConcelierDataSource _dataSource;
|
||||
private readonly InterestScoreRepository _repository;
|
||||
private readonly AdvisoryCanonicalRepository _canonicalRepository;
|
||||
|
||||
public InterestScoreRepositoryTests(ConcelierPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new InterestScoreRepository(_dataSource, NullLogger<InterestScoreRepository>.Instance);
|
||||
_canonicalRepository = new AdvisoryCanonicalRepository(_dataSource, NullLogger<AdvisoryCanonicalRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
#region GetByCanonicalIdAsync Tests
|
||||
|
||||
@@ -48,7 +52,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var score = CreateTestScore();
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -84,9 +88,9 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score1 = CreateTestScore();
|
||||
var score2 = CreateTestScore();
|
||||
var score3 = CreateTestScore();
|
||||
await _repository.SaveAsync(score1);
|
||||
await _repository.SaveAsync(score2);
|
||||
await _repository.SaveAsync(score3);
|
||||
await SaveScoreAsync(score1);
|
||||
await SaveScoreAsync(score2);
|
||||
await SaveScoreAsync(score3);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetByCanonicalIdsAsync([score1.CanonicalId, score3.CanonicalId]);
|
||||
@@ -132,7 +136,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(score: 0.75, reasons: ["in_sbom", "reachable", "deployed"]);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -148,7 +152,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
var canonicalId = Guid.NewGuid();
|
||||
var original = CreateTestScore(canonicalId: canonicalId, score: 0.5, reasons: ["in_sbom"]);
|
||||
await _repository.SaveAsync(original);
|
||||
await SaveScoreAsync(original);
|
||||
|
||||
var updated = CreateTestScore(
|
||||
canonicalId: canonicalId,
|
||||
@@ -156,7 +160,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
reasons: ["in_sbom", "reachable", "deployed", "no_vex_na"]);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(updated);
|
||||
await SaveScoreAsync(updated);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(canonicalId);
|
||||
@@ -174,7 +178,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(lastSeenInBuild: buildId);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -190,7 +194,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(lastSeenInBuild: null);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -206,7 +210,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(reasons: []);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -231,7 +235,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
};
|
||||
|
||||
// Act
|
||||
await _repository.SaveManyAsync(scores);
|
||||
await SaveScoresAsync(scores);
|
||||
|
||||
// Assert
|
||||
var count = await _repository.CountAsync();
|
||||
@@ -245,7 +249,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
var canonicalId = Guid.NewGuid();
|
||||
var original = CreateTestScore(canonicalId: canonicalId, score: 0.3);
|
||||
await _repository.SaveAsync(original);
|
||||
await SaveScoreAsync(original);
|
||||
|
||||
var scores = new[]
|
||||
{
|
||||
@@ -254,7 +258,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
};
|
||||
|
||||
// Act
|
||||
await _repository.SaveManyAsync(scores);
|
||||
await SaveScoresAsync(scores);
|
||||
|
||||
// Assert
|
||||
var count = await _repository.CountAsync();
|
||||
@@ -269,7 +273,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
public async Task SaveManyAsync_ShouldHandleEmptyInput()
|
||||
{
|
||||
// Act - should not throw
|
||||
await _repository.SaveManyAsync([]);
|
||||
await SaveScoresAsync([]);
|
||||
|
||||
// Assert
|
||||
var count = await _repository.CountAsync();
|
||||
@@ -286,7 +290,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var score = CreateTestScore();
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Verify exists
|
||||
var exists = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -324,9 +328,9 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var lowScore2 = CreateTestScore(score: 0.15, computedAt: oldDate);
|
||||
var highScore = CreateTestScore(score: 0.8, computedAt: oldDate);
|
||||
|
||||
await _repository.SaveAsync(lowScore1);
|
||||
await _repository.SaveAsync(lowScore2);
|
||||
await _repository.SaveAsync(highScore);
|
||||
await SaveScoreAsync(lowScore1);
|
||||
await SaveScoreAsync(lowScore2);
|
||||
await SaveScoreAsync(highScore);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetLowScoreCanonicalIdsAsync(
|
||||
@@ -349,8 +353,8 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var oldScore = CreateTestScore(score: 0.1, computedAt: DateTimeOffset.UtcNow.AddDays(-10));
|
||||
var recentScore = CreateTestScore(score: 0.1, computedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
await _repository.SaveAsync(oldScore);
|
||||
await _repository.SaveAsync(recentScore);
|
||||
await SaveScoreAsync(oldScore);
|
||||
await SaveScoreAsync(recentScore);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetLowScoreCanonicalIdsAsync(
|
||||
@@ -371,7 +375,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var oldDate = DateTimeOffset.UtcNow.AddDays(-10);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.1, computedAt: oldDate));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.1, computedAt: oldDate));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -397,9 +401,9 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var highScore2 = CreateTestScore(score: 0.75);
|
||||
var lowScore = CreateTestScore(score: 0.3);
|
||||
|
||||
await _repository.SaveAsync(highScore1);
|
||||
await _repository.SaveAsync(highScore2);
|
||||
await _repository.SaveAsync(lowScore);
|
||||
await SaveScoreAsync(highScore1);
|
||||
await SaveScoreAsync(highScore2);
|
||||
await SaveScoreAsync(lowScore);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetHighScoreCanonicalIdsAsync(
|
||||
@@ -420,7 +424,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.8));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.8));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -445,9 +449,9 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var medium = CreateTestScore(score: 0.5);
|
||||
var high = CreateTestScore(score: 0.9);
|
||||
|
||||
await _repository.SaveAsync(low);
|
||||
await _repository.SaveAsync(medium);
|
||||
await _repository.SaveAsync(high);
|
||||
await SaveScoreAsync(low);
|
||||
await SaveScoreAsync(medium);
|
||||
await SaveScoreAsync(high);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetTopScoresAsync(limit: 10);
|
||||
@@ -466,7 +470,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.1 * (i + 1)));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.1 * (i + 1)));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -487,7 +491,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
// Arrange
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.1 * (i + 1)));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.1 * (i + 1)));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -516,8 +520,8 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var stale = CreateTestScore(computedAt: DateTimeOffset.UtcNow.AddDays(-30));
|
||||
var fresh = CreateTestScore(computedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
await _repository.SaveAsync(stale);
|
||||
await _repository.SaveAsync(fresh);
|
||||
await SaveScoreAsync(stale);
|
||||
await SaveScoreAsync(fresh);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetStaleCanonicalIdsAsync(
|
||||
@@ -537,7 +541,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var oldDate = DateTimeOffset.UtcNow.AddDays(-30);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await _repository.SaveAsync(CreateTestScore(computedAt: oldDate));
|
||||
await SaveScoreAsync(CreateTestScore(computedAt: oldDate));
|
||||
}
|
||||
|
||||
// Act
|
||||
@@ -558,9 +562,9 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
public async Task CountAsync_ShouldReturnTotalCount()
|
||||
{
|
||||
// Arrange
|
||||
await _repository.SaveAsync(CreateTestScore());
|
||||
await _repository.SaveAsync(CreateTestScore());
|
||||
await _repository.SaveAsync(CreateTestScore());
|
||||
await SaveScoreAsync(CreateTestScore());
|
||||
await SaveScoreAsync(CreateTestScore());
|
||||
await SaveScoreAsync(CreateTestScore());
|
||||
|
||||
// Act
|
||||
var count = await _repository.CountAsync();
|
||||
@@ -590,15 +594,15 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange - create scores in different tiers
|
||||
// High tier (>= 0.7)
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.9));
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.8));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.9));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.8));
|
||||
// Medium tier (0.4 - 0.7)
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.5));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.5));
|
||||
// Low tier (0.2 - 0.4)
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.3));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.3));
|
||||
// None tier (< 0.2)
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.1));
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.05));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.1));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.05));
|
||||
|
||||
// Act
|
||||
var distribution = await _repository.GetDistributionAsync();
|
||||
@@ -635,8 +639,8 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
public async Task GetScoreDistributionAsync_ShouldBeAliasForGetDistributionAsync()
|
||||
{
|
||||
// Arrange
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.9));
|
||||
await _repository.SaveAsync(CreateTestScore(score: 0.5));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.9));
|
||||
await SaveScoreAsync(CreateTestScore(score: 0.5));
|
||||
|
||||
// Act
|
||||
var distribution1 = await _repository.GetDistributionAsync();
|
||||
@@ -660,7 +664,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(score: 1.0);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -675,7 +679,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(score: 0.0);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -691,7 +695,7 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var score = CreateTestScore(reasons: reasons);
|
||||
|
||||
// Act
|
||||
await _repository.SaveAsync(score);
|
||||
await SaveScoreAsync(score);
|
||||
|
||||
// Assert
|
||||
var result = await _repository.GetByCanonicalIdAsync(score.CanonicalId);
|
||||
@@ -706,8 +710,8 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
var older = CreateTestScore(score: 0.8, computedAt: DateTimeOffset.UtcNow.AddHours(-1));
|
||||
var newer = CreateTestScore(score: 0.8, computedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
await _repository.SaveAsync(older);
|
||||
await _repository.SaveAsync(newer);
|
||||
await SaveScoreAsync(older);
|
||||
await SaveScoreAsync(newer);
|
||||
|
||||
// Act
|
||||
var result = await _repository.GetTopScoresAsync(limit: 10);
|
||||
@@ -723,6 +727,49 @@ public sealed class InterestScoreRepositoryTests : IAsyncLifetime
|
||||
|
||||
#region Test Helpers
|
||||
|
||||
private static readonly DateTimeOffset CanonicalSeedTimestamp =
|
||||
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private async Task SaveScoreAsync(InterestScore score)
|
||||
{
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
await _repository.SaveAsync(score);
|
||||
}
|
||||
|
||||
private async Task SaveScoresAsync(IReadOnlyCollection<InterestScore> scores)
|
||||
{
|
||||
foreach (var score in scores)
|
||||
{
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
}
|
||||
|
||||
await _repository.SaveManyAsync(scores);
|
||||
}
|
||||
|
||||
private async Task SeedCanonicalAsync(Guid canonicalId, CanonicalStatus status = CanonicalStatus.Active)
|
||||
{
|
||||
var entity = new AdvisoryCanonicalEntity
|
||||
{
|
||||
Id = canonicalId,
|
||||
Cve = $"CVE-2024-{canonicalId.ToString("N")[..5]}",
|
||||
AffectsKey = "pkg:npm/test@1.0.0",
|
||||
MergeHash = $"sha256:{canonicalId:N}",
|
||||
Status = CanonicalStatusToString(status),
|
||||
CreatedAt = CanonicalSeedTimestamp,
|
||||
UpdatedAt = CanonicalSeedTimestamp,
|
||||
};
|
||||
|
||||
await _canonicalRepository.UpsertAsync(entity);
|
||||
}
|
||||
|
||||
private static string CanonicalStatusToString(CanonicalStatus status) => status switch
|
||||
{
|
||||
CanonicalStatus.Active => "active",
|
||||
CanonicalStatus.Stub => "stub",
|
||||
CanonicalStatus.Withdrawn => "withdrawn",
|
||||
_ => "active"
|
||||
};
|
||||
|
||||
private static InterestScore CreateTestScore(
|
||||
Guid? canonicalId = null,
|
||||
double score = 0.5,
|
||||
|
||||
@@ -14,6 +14,7 @@ using StellaOps.Concelier.Cache.Valkey;
|
||||
using StellaOps.Concelier.Core.Canonical;
|
||||
using StellaOps.Concelier.Interest;
|
||||
using StellaOps.Concelier.Interest.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
@@ -30,6 +31,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
private readonly ConcelierPostgresFixture _fixture;
|
||||
private readonly ConcelierDataSource _dataSource;
|
||||
private readonly InterestScoreRepository _repository;
|
||||
private readonly AdvisoryCanonicalRepository _canonicalRepository;
|
||||
private readonly Mock<IAdvisoryCacheService> _cacheServiceMock;
|
||||
private readonly Mock<ICanonicalAdvisoryStore> _advisoryStoreMock;
|
||||
private readonly InterestScoreCalculator _calculator;
|
||||
@@ -40,9 +42,10 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new InterestScoreRepository(_dataSource, NullLogger<InterestScoreRepository>.Instance);
|
||||
_canonicalRepository = new AdvisoryCanonicalRepository(_dataSource, NullLogger<AdvisoryCanonicalRepository>.Instance);
|
||||
|
||||
_cacheServiceMock = new Mock<IAdvisoryCacheService>();
|
||||
_advisoryStoreMock = new Mock<ICanonicalAdvisoryStore>();
|
||||
@@ -83,7 +86,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
return new ValueTask(_fixture.TruncateAllTablesAsync());
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
#region ComputeScoreAsync Tests
|
||||
|
||||
@@ -120,9 +123,10 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var score = await _service.ComputeScoreAsync(canonicalId);
|
||||
|
||||
// Assert
|
||||
score.Score.Should().Be(0.45); // in_sbom (0.30) + no_vex_na (0.15)
|
||||
score.Score.Should().Be(0.55); // in_sbom (0.30) + no_vex_na (0.15) + recent (0.10)
|
||||
score.Reasons.Should().Contain("in_sbom");
|
||||
score.Reasons.Should().Contain("no_vex_na");
|
||||
score.Reasons.Should().Contain("recent");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -142,11 +146,12 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var score = await _service.ComputeScoreAsync(canonicalId);
|
||||
|
||||
// Assert
|
||||
score.Score.Should().Be(0.90); // in_sbom (0.30) + reachable (0.25) + deployed (0.20) + no_vex_na (0.15)
|
||||
score.Score.Should().Be(1.00); // in_sbom + reachable + deployed + no_vex_na + recent
|
||||
score.Reasons.Should().Contain("in_sbom");
|
||||
score.Reasons.Should().Contain("reachable");
|
||||
score.Reasons.Should().Contain("deployed");
|
||||
score.Reasons.Should().Contain("no_vex_na");
|
||||
score.Reasons.Should().Contain("recent");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -173,9 +178,10 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var score = await _service.ComputeScoreAsync(canonicalId);
|
||||
|
||||
// Assert
|
||||
score.Score.Should().Be(0.30); // Only in_sbom, no no_vex_na
|
||||
score.Score.Should().Be(0.40); // in_sbom + recent, no no_vex_na
|
||||
score.Reasons.Should().Contain("in_sbom");
|
||||
score.Reasons.Should().NotContain("no_vex_na");
|
||||
score.Reasons.Should().Contain("recent");
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -194,6 +200,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
Reasons = ["in_sbom", "reachable", "deployed"],
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
|
||||
// Act
|
||||
await _service.UpdateScoreAsync(score);
|
||||
@@ -217,6 +224,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
Reasons = ["in_sbom"],
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
|
||||
// Act
|
||||
await _service.UpdateScoreAsync(score);
|
||||
@@ -236,6 +244,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var canonicalId = Guid.NewGuid();
|
||||
await SeedCanonicalAsync(canonicalId);
|
||||
var initialScore = new InterestScore
|
||||
{
|
||||
CanonicalId = canonicalId,
|
||||
@@ -278,6 +287,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
Reasons = ["in_sbom", "deployed"],
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
await _repository.SaveAsync(score);
|
||||
|
||||
// Act
|
||||
@@ -311,6 +321,9 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var id1 = Guid.NewGuid();
|
||||
var id2 = Guid.NewGuid();
|
||||
var id3 = Guid.NewGuid();
|
||||
await SeedCanonicalAsync(id1);
|
||||
await SeedCanonicalAsync(id2);
|
||||
await SeedCanonicalAsync(id3);
|
||||
|
||||
// Setup signals for different scores
|
||||
await _service.RecordSbomMatchAsync(id1, "sha256:a", "pkg:npm/a@1.0.0");
|
||||
@@ -327,8 +340,8 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var score2 = await _repository.GetByCanonicalIdAsync(id2);
|
||||
var score3 = await _repository.GetByCanonicalIdAsync(id3);
|
||||
|
||||
score1!.Score.Should().Be(0.45); // in_sbom + no_vex_na
|
||||
score2!.Score.Should().Be(0.70); // in_sbom + reachable + no_vex_na
|
||||
score1!.Score.Should().Be(0.55); // in_sbom + no_vex_na + recent
|
||||
score2!.Score.Should().Be(0.80); // in_sbom + reachable + no_vex_na + recent
|
||||
score3!.Score.Should().Be(0.15); // only no_vex_na
|
||||
}
|
||||
|
||||
@@ -339,6 +352,8 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
// Arrange
|
||||
var id1 = Guid.NewGuid();
|
||||
var id2 = Guid.NewGuid();
|
||||
await SeedCanonicalAsync(id1);
|
||||
await SeedCanonicalAsync(id2);
|
||||
|
||||
await _service.RecordSbomMatchAsync(id1, "sha256:a", "pkg:npm/a@1.0.0");
|
||||
await _service.RecordSbomMatchAsync(id2, "sha256:b", "pkg:npm/b@1.0.0");
|
||||
@@ -374,6 +389,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
|
||||
foreach (var score in scores)
|
||||
{
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
await _repository.SaveAsync(score);
|
||||
}
|
||||
|
||||
@@ -398,14 +414,24 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
// High tier
|
||||
await _repository.SaveAsync(CreateScore(0.9));
|
||||
await _repository.SaveAsync(CreateScore(0.8));
|
||||
var highScore1 = CreateScore(0.9);
|
||||
var highScore2 = CreateScore(0.8);
|
||||
await SeedCanonicalAsync(highScore1.CanonicalId);
|
||||
await SeedCanonicalAsync(highScore2.CanonicalId);
|
||||
await _repository.SaveAsync(highScore1);
|
||||
await _repository.SaveAsync(highScore2);
|
||||
// Medium tier
|
||||
await _repository.SaveAsync(CreateScore(0.5));
|
||||
var mediumScore = CreateScore(0.5);
|
||||
await SeedCanonicalAsync(mediumScore.CanonicalId);
|
||||
await _repository.SaveAsync(mediumScore);
|
||||
// Low tier
|
||||
await _repository.SaveAsync(CreateScore(0.3));
|
||||
var lowScore = CreateScore(0.3);
|
||||
await SeedCanonicalAsync(lowScore.CanonicalId);
|
||||
await _repository.SaveAsync(lowScore);
|
||||
// None tier
|
||||
await _repository.SaveAsync(CreateScore(0.1));
|
||||
var noneScore = CreateScore(0.1);
|
||||
await SeedCanonicalAsync(noneScore.CanonicalId);
|
||||
await _repository.SaveAsync(noneScore);
|
||||
|
||||
// Act
|
||||
var distribution = await _service.GetDistributionAsync();
|
||||
@@ -431,6 +457,9 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var lowScore1 = CreateScore(0.1, oldDate);
|
||||
var lowScore2 = CreateScore(0.15, oldDate);
|
||||
var highScore = CreateScore(0.8, oldDate);
|
||||
await SeedCanonicalAsync(lowScore1.CanonicalId);
|
||||
await SeedCanonicalAsync(lowScore2.CanonicalId);
|
||||
await SeedCanonicalAsync(highScore.CanonicalId);
|
||||
|
||||
await _repository.SaveAsync(lowScore1);
|
||||
await _repository.SaveAsync(lowScore2);
|
||||
@@ -460,6 +489,8 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
// Arrange - one old, one recent
|
||||
var lowOld = CreateScore(0.1, DateTimeOffset.UtcNow.AddDays(-60));
|
||||
var lowRecent = CreateScore(0.1, DateTimeOffset.UtcNow.AddDays(-5));
|
||||
await SeedCanonicalAsync(lowOld.CanonicalId);
|
||||
await SeedCanonicalAsync(lowRecent.CanonicalId);
|
||||
|
||||
await _repository.SaveAsync(lowOld);
|
||||
await _repository.SaveAsync(lowRecent);
|
||||
@@ -491,6 +522,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var highScore = CreateScore(0.8);
|
||||
await SeedCanonicalAsync(highScore.CanonicalId);
|
||||
await _repository.SaveAsync(highScore);
|
||||
|
||||
var stubAdvisory = CreateMockCanonicalAdvisory(highScore.CanonicalId, CanonicalStatus.Stub);
|
||||
@@ -517,6 +549,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var highScore = CreateScore(0.8);
|
||||
await SeedCanonicalAsync(highScore.CanonicalId);
|
||||
await _repository.SaveAsync(highScore);
|
||||
|
||||
var activeAdvisory = CreateMockCanonicalAdvisory(highScore.CanonicalId, CanonicalStatus.Active);
|
||||
@@ -544,6 +577,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// Arrange
|
||||
var canonicalId = Guid.NewGuid();
|
||||
await SeedCanonicalAsync(canonicalId);
|
||||
|
||||
// Act 1: Record SBOM match
|
||||
await _service.RecordSbomMatchAsync(
|
||||
@@ -562,21 +596,22 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
// Assert: Verify in database
|
||||
var dbScore = await _repository.GetByCanonicalIdAsync(canonicalId);
|
||||
dbScore.Should().NotBeNull();
|
||||
dbScore!.Score.Should().Be(0.90);
|
||||
dbScore!.Score.Should().Be(1.00);
|
||||
dbScore.Reasons.Should().Contain("in_sbom");
|
||||
dbScore.Reasons.Should().Contain("reachable");
|
||||
dbScore.Reasons.Should().Contain("deployed");
|
||||
dbScore.Reasons.Should().Contain("no_vex_na");
|
||||
dbScore.Reasons.Should().Contain("recent");
|
||||
|
||||
// Assert: Verify cache was updated
|
||||
_cacheServiceMock.Verify(
|
||||
x => x.UpdateScoreAsync(canonicalId.ToString(), 0.90, It.IsAny<CancellationToken>()),
|
||||
x => x.UpdateScoreAsync(canonicalId.ToString(), 1.00, It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
|
||||
// Act 4: Retrieve via service
|
||||
var retrievedScore = await _service.GetScoreAsync(canonicalId);
|
||||
retrievedScore.Should().NotBeNull();
|
||||
retrievedScore!.Score.Should().Be(0.90);
|
||||
retrievedScore!.Score.Should().Be(1.00);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -596,7 +631,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
|
||||
// Compute initial score
|
||||
var initialScore = await _service.ComputeScoreAsync(canonicalId);
|
||||
initialScore.Score.Should().Be(0.90);
|
||||
initialScore.Score.Should().Be(1.00);
|
||||
|
||||
// Act: Add VEX not_affected statement
|
||||
await _service.RecordVexStatementAsync(
|
||||
@@ -612,7 +647,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
var reducedScore = await _service.ComputeScoreAsync(canonicalId);
|
||||
|
||||
// Assert: Score should be reduced (no no_vex_na factor)
|
||||
reducedScore.Score.Should().Be(0.75);
|
||||
reducedScore.Score.Should().Be(0.85);
|
||||
reducedScore.Reasons.Should().NotContain("no_vex_na");
|
||||
}
|
||||
|
||||
@@ -641,6 +676,7 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
Reasons = ["in_sbom"],
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
await SeedCanonicalAsync(score.CanonicalId);
|
||||
|
||||
// Act
|
||||
await serviceWithCacheDisabled.UpdateScoreAsync(score);
|
||||
@@ -659,6 +695,33 @@ public sealed class InterestScoringServiceIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Test Helpers
|
||||
|
||||
private static readonly DateTimeOffset CanonicalSeedTimestamp =
|
||||
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private async Task SeedCanonicalAsync(Guid canonicalId, CanonicalStatus status = CanonicalStatus.Active)
|
||||
{
|
||||
var entity = new AdvisoryCanonicalEntity
|
||||
{
|
||||
Id = canonicalId,
|
||||
Cve = $"CVE-2024-{canonicalId.ToString("N")[..5]}",
|
||||
AffectsKey = "pkg:npm/test@1.0.0",
|
||||
MergeHash = $"sha256:{canonicalId:N}",
|
||||
Status = CanonicalStatusToString(status),
|
||||
CreatedAt = CanonicalSeedTimestamp,
|
||||
UpdatedAt = CanonicalSeedTimestamp,
|
||||
};
|
||||
|
||||
await _canonicalRepository.UpsertAsync(entity);
|
||||
}
|
||||
|
||||
private static string CanonicalStatusToString(CanonicalStatus status) => status switch
|
||||
{
|
||||
CanonicalStatus.Active => "active",
|
||||
CanonicalStatus.Stub => "stub",
|
||||
CanonicalStatus.Withdrawn => "withdrawn",
|
||||
_ => "active"
|
||||
};
|
||||
|
||||
private static InterestScore CreateScore(double score, DateTimeOffset? computedAt = null)
|
||||
{
|
||||
return new InterestScore
|
||||
|
||||
@@ -24,14 +24,14 @@ public sealed class KevFlagRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_advisoryRepository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
_repository = new KevFlagRepository(_dataSource, NullLogger<KevFlagRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -14,20 +14,21 @@ namespace StellaOps.Concelier.Persistence.Tests.Linksets;
|
||||
public sealed class AdvisoryLinksetCacheRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly ConcelierPostgresFixture _fixture;
|
||||
private readonly ConcelierDataSource _dataSource;
|
||||
private readonly AdvisoryLinksetCacheRepository _repository;
|
||||
|
||||
public AdvisoryLinksetCacheRepositoryTests(ConcelierPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
PostgresOptions options = fixture.Fixture.CreateOptions();
|
||||
PostgresOptions options = fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new AdvisoryLinksetCacheRepository(dataSource, NullLogger<AdvisoryLinksetCacheRepository>.Instance);
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new AdvisoryLinksetCacheRepository(_dataSource, NullLogger<AdvisoryLinksetCacheRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Fact]
|
||||
public async Task Upsert_NormalizesTenantAndReplaces()
|
||||
@@ -36,10 +37,16 @@ public sealed class AdvisoryLinksetCacheRepositoryTests : IAsyncLifetime
|
||||
var initial = BuildLinkset("Tenant-A", "ghsa", "GHSA-1", new[] { "obs-1" }, createdAt, confidence: 0.5);
|
||||
var replacement = BuildLinkset("tenant-a", "ghsa", "GHSA-1", new[] { "obs-2" }, createdAt.AddMinutes(5), confidence: 0.9);
|
||||
|
||||
await _repository.UpsertAsync(initial, CancellationToken.None);
|
||||
await _repository.UpsertAsync(replacement, CancellationToken.None);
|
||||
await _repository.UpsertAsync(initial, TestContext.Current.CancellationToken);
|
||||
await _repository.UpsertAsync(replacement, TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _repository.FindByTenantAsync("TENANT-A", null, null, cursor: null, limit: 10, CancellationToken.None);
|
||||
var results = await _repository.FindByTenantAsync(
|
||||
"TENANT-A",
|
||||
Array.Empty<string>(),
|
||||
Array.Empty<string>(),
|
||||
cursor: null,
|
||||
limit: 10,
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
results.Should().ContainSingle();
|
||||
results[0].TenantId.Should().Be("tenant-a");
|
||||
@@ -61,14 +68,26 @@ public sealed class AdvisoryLinksetCacheRepositoryTests : IAsyncLifetime
|
||||
|
||||
foreach (var linkset in linksets)
|
||||
{
|
||||
await _repository.UpsertAsync(linkset, CancellationToken.None);
|
||||
await _repository.UpsertAsync(linkset, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var firstPage = await _repository.FindByTenantAsync("tenant", null, null, cursor: null, limit: 10, CancellationToken.None);
|
||||
var firstPage = await _repository.FindByTenantAsync(
|
||||
"tenant",
|
||||
Array.Empty<string>(),
|
||||
Array.Empty<string>(),
|
||||
cursor: null,
|
||||
limit: 10,
|
||||
TestContext.Current.CancellationToken);
|
||||
firstPage.Select(ls => ls.AdvisoryId).Should().ContainInOrder("ADV-001", "ADV-002", "ADV-003");
|
||||
|
||||
var cursor = new AdvisoryLinksetCursor(firstPage[1].CreatedAt, firstPage[1].AdvisoryId);
|
||||
var secondPage = await _repository.FindByTenantAsync("tenant", null, null, cursor, limit: 10, CancellationToken.None);
|
||||
var secondPage = await _repository.FindByTenantAsync(
|
||||
"tenant",
|
||||
Array.Empty<string>(),
|
||||
Array.Empty<string>(),
|
||||
cursor,
|
||||
limit: 10,
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
secondPage.Should().ContainSingle();
|
||||
secondPage[0].AdvisoryId.Should().Be("ADV-003");
|
||||
@@ -112,9 +131,15 @@ public sealed class AdvisoryLinksetCacheRepositoryTests : IAsyncLifetime
|
||||
CreatedAt: DateTimeOffset.Parse("2025-11-20T00:00:00Z"),
|
||||
BuiltByJobId: "job-42");
|
||||
|
||||
await _repository.UpsertAsync(linkset, CancellationToken.None);
|
||||
await _repository.UpsertAsync(linkset, TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _repository.FindByTenantAsync("tenant-x", new[] { "GHSA-9999" }, null, cursor: null, limit: 1, CancellationToken.None);
|
||||
var results = await _repository.FindByTenantAsync(
|
||||
"tenant-x",
|
||||
new[] { "GHSA-9999" },
|
||||
Array.Empty<string>(),
|
||||
cursor: null,
|
||||
limit: 1,
|
||||
TestContext.Current.CancellationToken);
|
||||
results.Should().ContainSingle();
|
||||
|
||||
var cached = results[0];
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class MergeEventRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_advisoryRepository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
@@ -33,7 +33,7 @@ public sealed class MergeEventRepositoryTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -31,14 +31,14 @@ public sealed class AdvisoryPerformanceTests : IAsyncLifetime
|
||||
_fixture = fixture;
|
||||
_output = output;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
|
||||
_repository = new AdvisoryRepository(_dataSource, NullLogger<AdvisoryRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark bulk advisory insertion performance.
|
||||
@@ -375,7 +375,7 @@ public sealed class AdvisoryPerformanceTests : IAsyncLifetime
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
AdvisoryKey = key,
|
||||
PrimaryVulnId = $"CVE-2025-{key.GetHashCode():X8}"[..20],
|
||||
PrimaryVulnId = BuildPrimaryVulnId(key),
|
||||
Title = title ?? $"Test Advisory {key}",
|
||||
Severity = "medium",
|
||||
Summary = $"Summary for {key}",
|
||||
@@ -385,6 +385,12 @@ public sealed class AdvisoryPerformanceTests : IAsyncLifetime
|
||||
Provenance = $$$"""{"source": "performance-test", "key": "{{{key}}}"}"""
|
||||
};
|
||||
|
||||
private static string BuildPrimaryVulnId(string key)
|
||||
{
|
||||
var baseId = $"CVE-2025-{key}";
|
||||
return baseId.Length >= 20 ? baseId[..20] : baseId.PadRight(20, '0');
|
||||
}
|
||||
|
||||
private static List<AdvisoryAliasEntity> CreateTestAliases(Guid advisoryId, string cve) =>
|
||||
[
|
||||
new AdvisoryAliasEntity
|
||||
|
||||
@@ -34,13 +34,13 @@ public sealed class ProvenanceScopeRepositoryTests : IAsyncLifetime
|
||||
public ProvenanceScopeRepositoryTests(ConcelierPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new ProvenanceScopeRepository(_dataSource, NullLogger<ProvenanceScopeRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
#region Migration Validation
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Models;
|
||||
using StellaOps.Concelier.Persistence.Postgres;
|
||||
using StellaOps.Concelier.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
@@ -33,7 +32,7 @@ public sealed class RepositoryIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
|
||||
@@ -53,7 +52,7 @@ public sealed class RepositoryIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -23,13 +23,13 @@ public sealed class SourceRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -24,14 +24,14 @@ public sealed class SourceStateRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
|
||||
_repository = new SourceStateRepository(_dataSource, NullLogger<SourceStateRepository>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -36,14 +36,14 @@ public sealed class SyncLedgerRepositoryTests : IAsyncLifetime
|
||||
public SyncLedgerRepositoryTests(ConcelierPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
var options = fixture.CreateOptions();
|
||||
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
|
||||
_repository = new SyncLedgerRepository(_dataSource, NullLogger<SyncLedgerRepository>.Instance);
|
||||
_policyService = new SitePolicyEnforcementService(_repository, NullLogger<SitePolicyEnforcementService>.Instance);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
|
||||
|
||||
#region Task 3: Migration Validation
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user