Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,217 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scheduler.ImpactIndex;
namespace StellaOps.Scheduler.Worker.Tests;
public sealed class ImpactTargetingServiceTests
{
[Fact]
public async Task ResolveByPurlsAsync_DeduplicatesKeysAndInvokesIndex()
{
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var expected = CreateEmptyImpactSet(selector, usageOnly: false);
IEnumerable<string>? capturedKeys = null;
var index = new StubImpactIndex
{
OnResolveByPurls = (purls, usageOnly, sel, _) =>
{
capturedKeys = purls.ToArray();
Assert.False(usageOnly);
Assert.Equal(selector, sel);
return ValueTask.FromResult(expected);
}
};
var service = new ImpactTargetingService(index);
var result = await service.ResolveByPurlsAsync(
new[] { "pkg:npm/a", "pkg:npm/A ", null!, "pkg:npm/b" },
usageOnly: false,
selector);
Assert.Equal(expected, result);
Assert.Equal(new[] { "pkg:npm/a", "pkg:npm/b" }, capturedKeys);
}
[Fact]
public async Task ResolveByVulnerabilitiesAsync_ReturnsEmptyWhenNoIds()
{
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var index = new StubImpactIndex();
var service = new ImpactTargetingService(index);
var result = await service.ResolveByVulnerabilitiesAsync(Array.Empty<string>(), usageOnly: true, selector);
Assert.Empty(result.Images);
Assert.True(result.UsageOnly);
Assert.Null(index.LastVulnerabilityIds);
}
[Fact]
public async Task ResolveAllAsync_DelegatesToIndex()
{
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var expected = CreateEmptyImpactSet(selector, usageOnly: true);
var index = new StubImpactIndex
{
OnResolveAll = (sel, usageOnly, _) =>
{
Assert.Equal(selector, sel);
Assert.True(usageOnly);
return ValueTask.FromResult(expected);
}
};
var service = new ImpactTargetingService(index);
var result = await service.ResolveAllAsync(selector, usageOnly: true);
Assert.Equal(expected, result);
}
[Fact]
public async Task ResolveByPurlsAsync_DeduplicatesImpactImagesByDigest()
{
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var indexResult = new ImpactSet(
selector,
new[]
{
new ImpactImage(
"sha256:111",
"registry-1",
"repo/app",
namespaces: new[] { "team-a" },
tags: new[] { "v1" },
usedByEntrypoint: false,
labels: new[] { KeyValuePair.Create("env", "prod") }),
new ImpactImage(
"sha256:111",
"registry-1",
"repo/app",
namespaces: new[] { "team-b" },
tags: new[] { "v2" },
usedByEntrypoint: true,
labels: new[]
{
KeyValuePair.Create("env", "prod"),
KeyValuePair.Create("component", "api")
})
},
usageOnly: false,
DateTimeOffset.UtcNow,
total: 2,
snapshotId: "snap-1",
schemaVersion: SchedulerSchemaVersions.ImpactSet);
var index = new StubImpactIndex
{
OnResolveByPurls = (_, _, _, _) => ValueTask.FromResult(indexResult)
};
var service = new ImpactTargetingService(index);
var result = await service.ResolveByPurlsAsync(new[] { "pkg:npm/a" }, usageOnly: false, selector);
Assert.Single(result.Images);
var image = result.Images[0];
Assert.Equal("sha256:111", image.ImageDigest);
Assert.Equal(new[] { "team-a", "team-b" }, image.Namespaces);
Assert.Equal(new[] { "v1", "v2" }, image.Tags);
Assert.True(image.UsedByEntrypoint);
Assert.Equal("registry-1", image.Registry);
Assert.Equal("repo/app", image.Repository);
Assert.Equal(2, result.Total);
Assert.Equal("prod", image.Labels["env"]);
Assert.Equal("api", image.Labels["component"]);
}
[Fact]
public async Task ResolveByPurlsAsync_FiltersImagesBySelectorConstraints()
{
var selector = new Selector(
SelectorScope.ByNamespace,
tenantId: "tenant-alpha",
namespaces: new[] { "team-a" },
includeTags: new[] { "prod-*" },
labels: new[] { new LabelSelector("env", new[] { "prod" }) });
var matching = new ImpactImage(
"sha256:aaa",
"registry-1",
"repo/app",
namespaces: new[] { "team-a" },
tags: new[] { "prod-202510" },
usedByEntrypoint: true,
labels: new[] { KeyValuePair.Create("env", "prod") });
var nonMatching = new ImpactImage(
"sha256:bbb",
"registry-1",
"repo/app",
namespaces: new[] { "team-b" },
tags: new[] { "dev" },
usedByEntrypoint: false,
labels: new[] { KeyValuePair.Create("env", "dev") });
var indexResult = new ImpactSet(
selector,
new[] { matching, nonMatching },
usageOnly: true,
DateTimeOffset.UtcNow,
total: 2,
snapshotId: null,
schemaVersion: SchedulerSchemaVersions.ImpactSet);
var index = new StubImpactIndex
{
OnResolveByPurls = (_, _, _, _) => ValueTask.FromResult(indexResult)
};
var service = new ImpactTargetingService(index);
var result = await service.ResolveByPurlsAsync(new[] { "pkg:npm/a" }, usageOnly: true, selector);
Assert.Single(result.Images);
Assert.Equal("sha256:aaa", result.Images[0].ImageDigest);
}
private static ImpactSet CreateEmptyImpactSet(Selector selector, bool usageOnly)
=> new(
selector,
ImmutableArray<ImpactImage>.Empty,
usageOnly,
DateTimeOffset.UtcNow,
0,
snapshotId: null,
schemaVersion: SchedulerSchemaVersions.ImpactSet);
private sealed class StubImpactIndex : IImpactIndex
{
public Func<IEnumerable<string>, bool, Selector, CancellationToken, ValueTask<ImpactSet>>? OnResolveByPurls { get; set; }
public Func<IEnumerable<string>, bool, Selector, CancellationToken, ValueTask<ImpactSet>>? OnResolveByVulnerabilities { get; set; }
public Func<Selector, bool, CancellationToken, ValueTask<ImpactSet>>? OnResolveAll { get; set; }
public IEnumerable<string>? LastVulnerabilityIds { get; private set; }
public ValueTask<ImpactSet> ResolveByPurlsAsync(IEnumerable<string> purls, bool usageOnly, Selector selector, CancellationToken cancellationToken = default)
=> OnResolveByPurls?.Invoke(purls, usageOnly, selector, cancellationToken)
?? ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly));
public ValueTask<ImpactSet> ResolveByVulnerabilitiesAsync(IEnumerable<string> vulnerabilityIds, bool usageOnly, Selector selector, CancellationToken cancellationToken = default)
{
LastVulnerabilityIds = vulnerabilityIds;
return OnResolveByVulnerabilities?.Invoke(vulnerabilityIds, usageOnly, selector, cancellationToken)
?? ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly));
}
public ValueTask<ImpactSet> ResolveAllAsync(Selector selector, bool usageOnly, CancellationToken cancellationToken = default)
=> OnResolveAll?.Invoke(selector, usageOnly, cancellationToken)
?? ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly));
}
}