113 lines
4.0 KiB
C#
113 lines
4.0 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using StellaOps.Scanner.Storage.ObjectStore;
|
|
using StellaOps.Scanner.WebService.Contracts;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scanner.WebService.Tests;
|
|
|
|
public sealed class SbomEndpointsTests
|
|
{
|
|
[Fact]
|
|
public async Task SubmitSbomAcceptsCycloneDxJson()
|
|
{
|
|
using var secrets = new TestSurfaceSecretsScope();
|
|
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
|
|
{
|
|
configuration["scanner:authority:enabled"] = "false";
|
|
}, configureServices: services =>
|
|
{
|
|
services.RemoveAll<IArtifactObjectStore>();
|
|
services.AddSingleton<IArtifactObjectStore>(new InMemoryArtifactObjectStore());
|
|
});
|
|
|
|
using var client = factory.CreateClient();
|
|
var scanId = await CreateScanAsync(client);
|
|
|
|
var sbomJson = """
|
|
{
|
|
"bomFormat": "CycloneDX",
|
|
"specVersion": "1.6",
|
|
"version": 1,
|
|
"components": []
|
|
}
|
|
""";
|
|
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/scans/{scanId}/sbom")
|
|
{
|
|
Content = new StringContent(sbomJson, Encoding.UTF8, "application/vnd.cyclonedx+json")
|
|
};
|
|
|
|
var response = await client.SendAsync(request);
|
|
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
|
|
|
|
var payload = await response.Content.ReadFromJsonAsync<SbomAcceptedResponseDto>();
|
|
Assert.NotNull(payload);
|
|
Assert.False(string.IsNullOrWhiteSpace(payload!.SbomId));
|
|
Assert.Equal("cyclonedx", payload.Format);
|
|
Assert.Equal(0, payload.ComponentCount);
|
|
Assert.StartsWith("sha256:", payload.Digest, StringComparison.Ordinal);
|
|
}
|
|
|
|
private static async Task<string> CreateScanAsync(HttpClient client)
|
|
{
|
|
var response = await client.PostAsJsonAsync("/api/v1/scans", new ScanSubmitRequest
|
|
{
|
|
Image = new ScanImageDescriptor
|
|
{
|
|
Reference = "example.com/demo:1.0",
|
|
Digest = "sha256:0123456789abcdef"
|
|
}
|
|
});
|
|
|
|
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
|
|
|
|
var payload = await response.Content.ReadFromJsonAsync<ScanSubmitResponse>();
|
|
Assert.NotNull(payload);
|
|
Assert.False(string.IsNullOrWhiteSpace(payload!.ScanId));
|
|
return payload.ScanId;
|
|
}
|
|
|
|
private sealed class InMemoryArtifactObjectStore : IArtifactObjectStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, byte[]> _objects = new(StringComparer.Ordinal);
|
|
|
|
public async Task PutAsync(ArtifactObjectDescriptor descriptor, Stream content, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(descriptor);
|
|
ArgumentNullException.ThrowIfNull(content);
|
|
|
|
using var buffer = new MemoryStream();
|
|
await content.CopyToAsync(buffer, cancellationToken).ConfigureAwait(false);
|
|
|
|
var key = $"{descriptor.Bucket}:{descriptor.Key}";
|
|
_objects[key] = buffer.ToArray();
|
|
}
|
|
|
|
public Task<Stream?> GetAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(descriptor);
|
|
|
|
var key = $"{descriptor.Bucket}:{descriptor.Key}";
|
|
if (!_objects.TryGetValue(key, out var bytes))
|
|
{
|
|
return Task.FromResult<Stream?>(null);
|
|
}
|
|
|
|
return Task.FromResult<Stream?>(new MemoryStream(bytes, writable: false));
|
|
}
|
|
|
|
public Task DeleteAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(descriptor);
|
|
var key = $"{descriptor.Bucket}:{descriptor.Key}";
|
|
_objects.TryRemove(key, out _);
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
}
|