143 lines
4.9 KiB
C#
143 lines
4.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Scheduler.ImpactIndex;
|
|
using StellaOps.Scheduler.Models;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scheduler.ImpactIndex.Tests;
|
|
|
|
public sealed class FixtureImpactIndexTests
|
|
{
|
|
[Fact]
|
|
public async Task ResolveByPurls_UsesEmbeddedFixtures()
|
|
{
|
|
var selector = new Selector(SelectorScope.AllImages);
|
|
var (impactIndex, loggerFactory) = CreateImpactIndex();
|
|
using var _ = loggerFactory;
|
|
|
|
var result = await impactIndex.ResolveByPurlsAsync(
|
|
new[] { "pkg:apk/alpine/openssl@3.2.2-r0?arch=x86_64" },
|
|
usageOnly: false,
|
|
selector);
|
|
|
|
result.UsageOnly.Should().BeFalse();
|
|
result.Images.Should().ContainSingle();
|
|
|
|
var image = result.Images.Single();
|
|
image.ImageDigest.Should().Be("sha256:8f47d7c6b538c0d9533b78913cba3d5e671e7c4b4e7c6a2bb9a1a1c4d4f8e123");
|
|
image.Registry.Should().Be("docker.io");
|
|
image.Repository.Should().Be("library/nginx");
|
|
image.Tags.Should().ContainSingle(tag => tag == "1.25.4");
|
|
image.UsedByEntrypoint.Should().BeTrue();
|
|
|
|
result.GeneratedAt.Should().Be(DateTimeOffset.Parse("2025-10-19T00:00:00Z"));
|
|
result.SchemaVersion.Should().Be(SchedulerSchemaVersions.ImpactSet);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveByPurls_UsageOnlyFiltersInventoryOnlyComponents()
|
|
{
|
|
var selector = new Selector(SelectorScope.AllImages);
|
|
var (impactIndex, loggerFactory) = CreateImpactIndex();
|
|
using var _ = loggerFactory;
|
|
|
|
var inventoryOnlyPurl = "pkg:apk/alpine/pcre2@10.42-r1?arch=x86_64";
|
|
|
|
var runtimeResult = await impactIndex.ResolveByPurlsAsync(
|
|
new[] { inventoryOnlyPurl },
|
|
usageOnly: true,
|
|
selector);
|
|
|
|
runtimeResult.Images.Should().BeEmpty();
|
|
|
|
var inventoryResult = await impactIndex.ResolveByPurlsAsync(
|
|
new[] { inventoryOnlyPurl },
|
|
usageOnly: false,
|
|
selector);
|
|
|
|
inventoryResult.Images.Should().ContainSingle();
|
|
inventoryResult.Images.Single().UsedByEntrypoint.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveAll_ReturnsDeterministicFixtureSet()
|
|
{
|
|
var selector = new Selector(SelectorScope.AllImages);
|
|
var (impactIndex, loggerFactory) = CreateImpactIndex();
|
|
using var _ = loggerFactory;
|
|
|
|
var first = await impactIndex.ResolveAllAsync(selector, usageOnly: false);
|
|
first.Images.Should().HaveCount(6);
|
|
|
|
var second = await impactIndex.ResolveAllAsync(selector, usageOnly: false);
|
|
second.Images.Should().HaveCount(6);
|
|
second.Images.Should().Equal(first.Images);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveByVulnerabilities_ReturnsEmptySet()
|
|
{
|
|
var selector = new Selector(SelectorScope.AllImages);
|
|
var (impactIndex, loggerFactory) = CreateImpactIndex();
|
|
using var _ = loggerFactory;
|
|
|
|
var result = await impactIndex.ResolveByVulnerabilitiesAsync(
|
|
new[] { "CVE-2025-0001" },
|
|
usageOnly: false,
|
|
selector);
|
|
|
|
result.Images.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FixtureDirectoryOption_LoadsFromFileSystem()
|
|
{
|
|
var selector = new Selector(SelectorScope.AllImages);
|
|
var samplesDirectory = LocateSamplesDirectory();
|
|
var (impactIndex, loggerFactory) = CreateImpactIndex(options =>
|
|
{
|
|
options.FixtureDirectory = samplesDirectory;
|
|
});
|
|
using var _ = loggerFactory;
|
|
|
|
var result = await impactIndex.ResolveAllAsync(selector, usageOnly: false);
|
|
|
|
result.Images.Should().HaveCount(6);
|
|
}
|
|
|
|
private static (FixtureImpactIndex ImpactIndex, ILoggerFactory LoggerFactory) CreateImpactIndex(
|
|
Action<ImpactIndexStubOptions>? configure = null)
|
|
{
|
|
var options = new ImpactIndexStubOptions();
|
|
configure?.Invoke(options);
|
|
|
|
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
|
var logger = loggerFactory.CreateLogger<FixtureImpactIndex>();
|
|
|
|
var impactIndex = new FixtureImpactIndex(options, TimeProvider.System, logger);
|
|
return (impactIndex, loggerFactory);
|
|
}
|
|
|
|
private static string LocateSamplesDirectory()
|
|
{
|
|
var current = AppContext.BaseDirectory;
|
|
|
|
while (!string.IsNullOrWhiteSpace(current))
|
|
{
|
|
var candidate = Path.Combine(current, "samples", "scanner", "images");
|
|
if (Directory.Exists(candidate))
|
|
{
|
|
return candidate;
|
|
}
|
|
|
|
current = Directory.GetParent(current)?.FullName;
|
|
}
|
|
|
|
throw new InvalidOperationException("Unable to locate 'samples/scanner/images'.");
|
|
}
|
|
}
|