using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using StellaOps.Scheduler.Models; using StellaOps.Scheduler.Worker; using StellaOps.Scheduler.Worker.Options; using StellaOps.Scheduler.Worker.Policy; using Xunit; namespace StellaOps.Scheduler.Worker.Tests; public sealed class PolicyRunTargetingServiceTests { [Fact] public async Task EnsureTargetsAsync_ReturnsUnchanged_ForNonIncrementalJob() { var service = CreateService(); var job = CreateJob(mode: PolicyRunMode.Full); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Unchanged, result.Status); Assert.Equal(job, result.Job); } [Fact] public async Task EnsureTargetsAsync_ReturnsUnchanged_WhenSbomSetAlreadyPresent() { var service = CreateService(); var inputs = new PolicyRunInputs(sbomSet: new[] { "sbom:S-1" }); var job = CreateJob(inputs: inputs); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Unchanged, result.Status); } [Fact] public async Task EnsureTargetsAsync_ReturnsNoWork_WhenNoCandidatesResolved() { var impact = new StubImpactTargetingService(); var service = CreateService(impact); var metadata = ImmutableSortedDictionary.Empty.Add("delta.purls", "pkg:npm/leftpad"); var job = CreateJob(metadata: metadata, inputs: PolicyRunInputs.Empty); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.NoWork, result.Status); Assert.Equal("no_matches", result.Reason); } [Fact] public async Task EnsureTargetsAsync_TargetsDirectSboms() { var service = CreateService(); var metadata = ImmutableSortedDictionary.Empty.Add("delta.sboms", "sbom:S-2, sbom:S-1, sbom:S-2"); var job = CreateJob(metadata: metadata, inputs: PolicyRunInputs.Empty); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Targeted, result.Status); Assert.Equal(new[] { "sbom:S-1", "sbom:S-2" }, result.Job.Inputs.SbomSet); } [Fact] public async Task EnsureTargetsAsync_TargetsUsingImpactIndex() { var impact = new StubImpactTargetingService { OnResolveByPurls = (keys, usageOnly, selector, _) => { var image = new ImpactImage( "sha256:111", "registry", "repo", labels: ImmutableSortedDictionary.Create(StringComparer.Ordinal).Add("sbomId", "sbom:S-42")); var impactSet = new ImpactSet( selector, new[] { image }, usageOnly, DateTimeOffset.UtcNow, total: 1, snapshotId: null, schemaVersion: SchedulerSchemaVersions.ImpactSet); return ValueTask.FromResult(impactSet); } }; var service = CreateService(impact); var metadata = ImmutableSortedDictionary.Empty.Add("delta.purls", "pkg:npm/example"); var job = CreateJob(metadata: metadata, inputs: PolicyRunInputs.Empty); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Targeted, result.Status); Assert.Equal(new[] { "sbom:S-42" }, result.Job.Inputs.SbomSet); } [Fact] public async Task EnsureTargetsAsync_FallsBack_WhenLimitExceeded() { var service = CreateService(configure: options => options.MaxSboms = 1); var metadata = ImmutableSortedDictionary.Empty.Add("delta.sboms", "sbom:S-1,sbom:S-2"); var job = CreateJob(metadata: metadata, inputs: PolicyRunInputs.Empty); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Unchanged, result.Status); } [Fact] public async Task EnsureTargetsAsync_FallbacksToDigest_WhenLabelMissing() { var impact = new StubImpactTargetingService { OnResolveByVulnerabilities = (ids, usageOnly, selector, _) => { var image = new ImpactImage("sha256:aaa", "registry", "repo"); var impactSet = new ImpactSet( selector, new[] { image }, usageOnly, DateTimeOffset.UtcNow, total: 1, snapshotId: null, schemaVersion: SchedulerSchemaVersions.ImpactSet); return ValueTask.FromResult(impactSet); } }; var service = CreateService(impact); var metadata = ImmutableSortedDictionary.Empty.Add("delta.vulns", "CVE-2025-1234"); var job = CreateJob(metadata: metadata, inputs: PolicyRunInputs.Empty); var result = await service.EnsureTargetsAsync(job, CancellationToken.None); Assert.Equal(PolicyRunTargetingStatus.Targeted, result.Status); Assert.Equal(new[] { "sbom:sha256:aaa" }, result.Job.Inputs.SbomSet); } private static PolicyRunTargetingService CreateService( IImpactTargetingService? impact = null, Action? configure = null) { impact ??= new StubImpactTargetingService(); var options = CreateOptions(configure); return new PolicyRunTargetingService( impact, Microsoft.Extensions.Options.Options.Create(options), timeProvider: null, NullLogger.Instance); } private static SchedulerWorkerOptions CreateOptions(Action? configure) { var options = new SchedulerWorkerOptions { Policy = { Api = { BaseAddress = new Uri("https://policy.example.com"), RunsPath = "/runs", SimulatePath = "/simulate" } } }; configure?.Invoke(options.Policy.Targeting); return options; } private static PolicyRunJob CreateJob( PolicyRunMode mode = PolicyRunMode.Incremental, ImmutableSortedDictionary? metadata = null, PolicyRunInputs? inputs = null) { return new PolicyRunJob( SchemaVersion: SchedulerSchemaVersions.PolicyRunJob, Id: "job-1", TenantId: "tenant-alpha", PolicyId: "P-7", PolicyVersion: 4, Mode: mode, Priority: PolicyRunPriority.Normal, PriorityRank: 0, RunId: null, RequestedBy: null, CorrelationId: null, Metadata: metadata ?? ImmutableSortedDictionary.Empty, Inputs: inputs ?? PolicyRunInputs.Empty, QueuedAt: DateTimeOffset.UtcNow, Status: PolicyRunJobStatus.Dispatching, AttemptCount: 0, LastAttemptAt: null, LastError: null, CreatedAt: DateTimeOffset.UtcNow, UpdatedAt: DateTimeOffset.UtcNow, AvailableAt: DateTimeOffset.UtcNow, SubmittedAt: null, CompletedAt: null, LeaseOwner: "lease", LeaseExpiresAt: DateTimeOffset.UtcNow.AddMinutes(1), CancellationRequested: false, CancellationRequestedAt: null, CancellationReason: null, CancelledAt: null); } private sealed class StubImpactTargetingService : IImpactTargetingService { public Func, bool, Selector, CancellationToken, ValueTask>? OnResolveByPurls { get; set; } public Func, bool, Selector, CancellationToken, ValueTask>? OnResolveByVulnerabilities { get; set; } public ValueTask ResolveByPurlsAsync(IEnumerable productKeys, bool usageOnly, Selector selector, CancellationToken cancellationToken = default) { if (OnResolveByPurls is null) { return ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly)); } return OnResolveByPurls(productKeys, usageOnly, selector, cancellationToken); } public ValueTask ResolveByVulnerabilitiesAsync(IEnumerable vulnerabilityIds, bool usageOnly, Selector selector, CancellationToken cancellationToken = default) { if (OnResolveByVulnerabilities is null) { return ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly)); } return OnResolveByVulnerabilities(vulnerabilityIds, usageOnly, selector, cancellationToken); } public ValueTask ResolveAllAsync(Selector selector, bool usageOnly, CancellationToken cancellationToken = default) => ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly)); private static ImpactSet CreateEmptyImpactSet(Selector selector, bool usageOnly) { return new ImpactSet( selector, ImmutableArray.Empty, usageOnly, DateTimeOffset.UtcNow, total: 0, snapshotId: null, schemaVersion: SchedulerSchemaVersions.ImpactSet); } } }