up
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Formats.Cbor;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -10,6 +13,8 @@ namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class ProofSpineEndpointsTests
|
||||
{
|
||||
private const string CborContentType = "application/cbor";
|
||||
|
||||
[Fact]
|
||||
public async Task GetSpine_ReturnsSpine_WithVerification()
|
||||
{
|
||||
@@ -49,6 +54,42 @@ public sealed class ProofSpineEndpointsTests
|
||||
Assert.True(body.TryGetProperty("verification", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSpine_ReturnsCbor_WhenAcceptHeaderRequestsCbor()
|
||||
{
|
||||
await using var factory = new ScannerApplicationFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
|
||||
var builder = scope.ServiceProvider.GetRequiredService<ProofSpineBuilder>();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<IProofSpineRepository>();
|
||||
|
||||
var spine = await builder
|
||||
.ForArtifact("sha256:feedface")
|
||||
.ForVulnerability("CVE-2025-1001")
|
||||
.WithPolicyProfile("default")
|
||||
.WithScanRun("scan-cbor-001")
|
||||
.AddSbomSlice("sha256:sbom", new[] { "pkg:a", "pkg:b" }, toolId: "sbom", toolVersion: "1.0.0")
|
||||
.BuildAsync();
|
||||
|
||||
await repository.SaveAsync(spine);
|
||||
|
||||
var client = factory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/spines/{spine.SpineId}");
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(CborContentType));
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(CborContentType, response.Content.Headers.ContentType?.MediaType);
|
||||
|
||||
var cbor = await response.Content.ReadAsByteArrayAsync();
|
||||
var decoded = ReadRootMap(cbor);
|
||||
|
||||
Assert.Equal(spine.SpineId, decoded["spineId"]);
|
||||
Assert.True(decoded.ContainsKey("verification"));
|
||||
Assert.True(((List<object?>)decoded["segments"]!).Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListSpinesByScan_ReturnsSummaries_WithSegmentCount()
|
||||
{
|
||||
@@ -87,6 +128,44 @@ public sealed class ProofSpineEndpointsTests
|
||||
Assert.True(items[0].GetProperty("segmentCount").GetInt32() > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListSpinesByScan_ReturnsCbor_WhenAcceptHeaderRequestsCbor()
|
||||
{
|
||||
await using var factory = new ScannerApplicationFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
|
||||
var builder = scope.ServiceProvider.GetRequiredService<ProofSpineBuilder>();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<IProofSpineRepository>();
|
||||
|
||||
var spine = await builder
|
||||
.ForArtifact("sha256:feedface")
|
||||
.ForVulnerability("CVE-2025-1002")
|
||||
.WithPolicyProfile("default")
|
||||
.WithScanRun("scan-cbor-002")
|
||||
.AddSbomSlice("sha256:sbom", new[] { "pkg:a" }, toolId: "sbom", toolVersion: "1.0.0")
|
||||
.BuildAsync();
|
||||
|
||||
await repository.SaveAsync(spine);
|
||||
|
||||
var client = factory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/scans/scan-cbor-002/spines");
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(CborContentType));
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(CborContentType, response.Content.Headers.ContentType?.MediaType);
|
||||
|
||||
var cbor = await response.Content.ReadAsByteArrayAsync();
|
||||
var decoded = ReadRootMap(cbor);
|
||||
var items = (List<object?>)decoded["items"]!;
|
||||
|
||||
Assert.Single(items);
|
||||
var first = (Dictionary<string, object?>)items[0]!;
|
||||
Assert.Equal(spine.SpineId, first["spineId"]);
|
||||
Assert.True((int)first["segmentCount"]! > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSpine_ReturnsInvalidStatus_WhenSegmentTampered()
|
||||
{
|
||||
@@ -125,4 +204,90 @@ public sealed class ProofSpineEndpointsTests
|
||||
var segments = body.GetProperty("segments");
|
||||
Assert.Equal("invalid", segments[0].GetProperty("status").GetString());
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> ReadRootMap(byte[] cbor)
|
||||
{
|
||||
var reader = new CborReader(cbor, CborConformanceMode.Canonical);
|
||||
return ReadMap(reader);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> ReadMap(CborReader reader)
|
||||
{
|
||||
var length = reader.ReadStartMap();
|
||||
var result = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
|
||||
if (length is not null)
|
||||
{
|
||||
for (var i = 0; i < length.Value; i++)
|
||||
{
|
||||
var key = reader.ReadTextString();
|
||||
result[key] = ReadValue(reader);
|
||||
}
|
||||
reader.ReadEndMap();
|
||||
return result;
|
||||
}
|
||||
|
||||
while (reader.PeekState() != CborReaderState.EndMap)
|
||||
{
|
||||
var key = reader.ReadTextString();
|
||||
result[key] = ReadValue(reader);
|
||||
}
|
||||
|
||||
reader.ReadEndMap();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<object?> ReadArray(CborReader reader)
|
||||
{
|
||||
var length = reader.ReadStartArray();
|
||||
var result = new List<object?>();
|
||||
|
||||
if (length is not null)
|
||||
{
|
||||
for (var i = 0; i < length.Value; i++)
|
||||
{
|
||||
result.Add(ReadValue(reader));
|
||||
}
|
||||
reader.ReadEndArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
while (reader.PeekState() != CborReaderState.EndArray)
|
||||
{
|
||||
result.Add(ReadValue(reader));
|
||||
}
|
||||
|
||||
reader.ReadEndArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object? ReadValue(CborReader reader)
|
||||
{
|
||||
switch (reader.PeekState())
|
||||
{
|
||||
case CborReaderState.StartMap:
|
||||
return ReadMap(reader);
|
||||
case CborReaderState.StartArray:
|
||||
return ReadArray(reader);
|
||||
case CborReaderState.TextString:
|
||||
return reader.ReadTextString();
|
||||
case CborReaderState.UnsignedInteger:
|
||||
return (int)reader.ReadUInt64();
|
||||
case CborReaderState.NegativeInteger:
|
||||
return (int)reader.ReadInt64();
|
||||
case CborReaderState.DoublePrecisionFloat:
|
||||
return reader.ReadDouble();
|
||||
case CborReaderState.SinglePrecisionFloat:
|
||||
return reader.ReadSingle();
|
||||
case CborReaderState.HalfPrecisionFloat:
|
||||
return reader.ReadHalf();
|
||||
case CborReaderState.Boolean:
|
||||
return reader.ReadBoolean();
|
||||
case CborReaderState.Null:
|
||||
reader.ReadNull();
|
||||
return null;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected CBOR state {reader.PeekState()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user