using System.Collections.Immutable; using System.Linq; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Concelier.Core.Linksets; using StellaOps.Scanner.Surface.Env; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Services; namespace StellaOps.Scanner.WebService.Tests; public sealed class LinksetResolverTests { [Fact] public async Task ResolveAsync_MapsSeveritiesAndConflicts() { var linkset = new AdvisoryLinkset( TenantId: "tenant-a", Source: "osv", AdvisoryId: "CVE-2025-0001", ObservationIds: ImmutableArray.Empty, Normalized: new AdvisoryLinksetNormalized( Purls: new[] { "pkg:npm/demo@1.0.0" }, Cpes: Array.Empty(), Versions: Array.Empty(), Ranges: Array.Empty>(), Severities: new[] { new Dictionary(StringComparer.Ordinal) { ["source"] = "nvd", ["type"] = "cvssv3", ["score"] = 9.8, ["vector"] = "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", ["labels"] = new Dictionary { ["preferred"] = "true" } } }), Provenance: null, Confidence: 0.91, Conflicts: new[] { new AdvisoryLinksetConflict("severity", "disagree", new[] { "cvssv2", "cvssv3" }) }, CreatedAt: DateTimeOffset.UtcNow, BuiltByJobId: "job-1"); var resolver = new LinksetResolver( new FakeLinksetQueryService(linkset), new FakeSurfaceEnvironment(), NullLogger.Instance); var result = await resolver.ResolveAsync(new[] { new PolicyPreviewFindingDto { Id = "CVE-2025-0001" } }, CancellationToken.None); var summary = Assert.Single(result); Assert.Equal("CVE-2025-0001", summary.AdvisoryId); Assert.Equal("osv", summary.Source); Assert.Equal(0.91, summary.Confidence); var severity = Assert.Single(summary.Severities!); Assert.Equal("nvd", severity.Source); Assert.Equal("cvssv3", severity.Type); Assert.Equal(9.8, severity.Score); Assert.Equal("AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", severity.Vector); Assert.NotNull(severity.Labels); var conflict = Assert.Single(summary.Conflicts!); Assert.Equal("severity", conflict.Field); Assert.Equal("disagree", conflict.Reason); } [Fact] public async Task ResolveAsync_ReturnsEmptyWhenNoIds() { var resolver = new LinksetResolver( new FakeLinksetQueryService(), new FakeSurfaceEnvironment(), NullLogger.Instance); var result = await resolver.ResolveAsync(Array.Empty(), CancellationToken.None); Assert.Empty(result); } private sealed class FakeLinksetQueryService : IAdvisoryLinksetQueryService { private readonly AdvisoryLinkset[] _linksets; public FakeLinksetQueryService(params AdvisoryLinkset[] linksets) { _linksets = linksets; } public Task QueryAsync(AdvisoryLinksetQueryOptions options, CancellationToken cancellationToken) { var matched = _linksets .Where(ls => options.AdvisoryIds?.Contains(ls.AdvisoryId, StringComparer.OrdinalIgnoreCase) == true) .ToImmutableArray(); return Task.FromResult(new AdvisoryLinksetQueryResult(matched, null, false)); } } private sealed class FakeSurfaceEnvironment : ISurfaceEnvironment { public SurfaceEnvironmentSettings Settings { get; } = new() { Tenant = "tenant-a" }; public IReadOnlyDictionary RawVariables { get; } = new Dictionary(StringComparer.Ordinal) { ["SCANNER__TENANT"] = "tenant-a" }; } }