122 lines
4.2 KiB
C#
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();
|
|
}
|
|
}
|