Files
git.stella-ops.org/src/SbomService/StellaOps.SbomService.Tests/ScanJobEmitterServiceTests.cs
2026-01-13 18:53:39 +02:00

122 lines
4.2 KiB
C#

using System.Net;
using System.Text.Json;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Moq.Protected;
using StellaOps.SbomService.Models;
using StellaOps.SbomService.Services;
using Xunit;
namespace StellaOps.SbomService.Tests;
public sealed class ScanJobEmitterServiceTests
{
[Trait("Category", "Unit")]
[Fact]
public async Task SubmitScanAsync_RejectsDisallowedScannerHost()
{
// Arrange
var configuration = BuildConfiguration("http://blocked.example.com");
var httpClientFactory = new Mock<IHttpClientFactory>();
httpClientFactory
.Setup(f => f.CreateClient(It.IsAny<string>()))
.Returns(new HttpClient(new Mock<HttpMessageHandler>().Object));
var service = new ScanJobEmitterService(
httpClientFactory.Object,
configuration,
NullLogger<ScanJobEmitterService>.Instance,
Options.Create(new ScannerHttpOptions
{
AllowedHosts = new List<string> { "scanner.local" }
}),
new QueueGuidProvider(new[] { Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") }));
var request = new ScanJobRequest(
ImageReference: "library/nginx:latest",
Digest: "sha256:deadbeef",
Platform: "linux/amd64",
Force: false,
ClientRequestId: null,
SourceId: "source-1",
TriggerType: "manual");
// Act
var result = await service.SubmitScanAsync(request);
// Assert
result.Success.Should().BeFalse();
result.Error.Should().Contain("allowlisted");
}
[Trait("Category", "Unit")]
[Fact]
public async Task SubmitBatchScanAsync_UsesDeterministicRequestId()
{
// Arrange
var configuration = BuildConfiguration("http://scanner.local");
var handler = new Mock<HttpMessageHandler>();
HttpRequestMessage? captured = null;
handler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.Callback<HttpRequestMessage, CancellationToken>((request, _) => captured = request)
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{\"snapshot\":{\"id\":\"job-123\",\"status\":\"queued\"},\"created\":true}")
});
var httpClientFactory = new Mock<IHttpClientFactory>();
httpClientFactory
.Setup(f => f.CreateClient(It.IsAny<string>()))
.Returns(new HttpClient(handler.Object));
var service = new ScanJobEmitterService(
httpClientFactory.Object,
configuration,
NullLogger<ScanJobEmitterService>.Instance,
Options.Create(new ScannerHttpOptions
{
AllowedHosts = new List<string> { "scanner.local" }
}),
new QueueGuidProvider(new[] { Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb") }));
var images = new[]
{
new DiscoveredImage("library/nginx", "latest", "sha256:deadbeef")
};
// Act
var result = await service.SubmitBatchScanAsync("source-1", images);
// Assert
result.Submitted.Should().Be(1);
captured.Should().NotBeNull();
var payload = await captured!.Content!.ReadAsStringAsync();
using var doc = JsonDocument.Parse(payload);
var clientRequestId = doc.RootElement.GetProperty("clientRequestId").GetString();
clientRequestId.Should().Be("registry-source-1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
}
private static IConfiguration BuildConfiguration(string scannerUrl)
{
return new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["SbomService:ScannerUrl"] = scannerUrl,
["SbomService:BatchScanSize"] = "1",
["SbomService:BatchScanDelayMs"] = "0"
})
.Build();
}
}