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,148 @@
using System;
using System.IO;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Serialization;
using Xunit.Sdk;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class PlatformEventSamplesTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
[Theory]
[InlineData("scanner.event.report.ready@1.sample.json", OrchestratorEventKinds.ScannerReportReady)]
[InlineData("scanner.event.scan.completed@1.sample.json", OrchestratorEventKinds.ScannerScanCompleted)]
public void PlatformEventSamplesStayCanonical(string fileName, string expectedKind)
{
var json = LoadSample(fileName);
var orchestratorEvent = DeserializeOrchestratorEvent(json, expectedKind);
Assert.NotNull(orchestratorEvent);
Assert.Equal(expectedKind, orchestratorEvent.Kind);
Assert.Equal(1, orchestratorEvent.Version);
Assert.NotEqual(Guid.Empty, orchestratorEvent.EventId);
Assert.NotNull(orchestratorEvent.Payload);
AssertCanonical(json, orchestratorEvent);
AssertReportConsistency(orchestratorEvent);
}
private static void AssertCanonical(string originalJson, OrchestratorEvent orchestratorEvent)
{
var canonicalJson = OrchestratorEventSerializer.Serialize(orchestratorEvent);
var originalNode = JsonNode.Parse(originalJson) ?? throw new InvalidOperationException("Sample JSON must not be null.");
var canonicalNode = JsonNode.Parse(canonicalJson) ?? throw new InvalidOperationException("Canonical JSON must not be null.");
if (!JsonNode.DeepEquals(originalNode, canonicalNode))
{
throw new Xunit.Sdk.XunitException($"Platform event sample must remain canonical.\nOriginal: {originalJson}\nCanonical: {canonicalJson}");
}
}
private static void AssertReportConsistency(OrchestratorEvent orchestratorEvent)
{
switch (orchestratorEvent.Payload)
{
case ReportReadyEventPayload ready:
Assert.Equal(ready.ReportId, ready.Report.ReportId);
Assert.Equal(ready.ScanId, ready.Report.ReportId);
AssertDsseMatchesReport(ready.Dsse, ready.Report);
Assert.False(string.IsNullOrWhiteSpace(ready.Links.Ui));
Assert.False(string.IsNullOrWhiteSpace(ready.Links.Report));
Assert.False(string.IsNullOrWhiteSpace(ready.Links.Attestation));
break;
case ScanCompletedEventPayload completed:
Assert.Equal(completed.ReportId, completed.Report.ReportId);
Assert.Equal(completed.ScanId, completed.Report.ReportId);
AssertDsseMatchesReport(completed.Dsse, completed.Report);
Assert.NotEmpty(completed.Findings);
Assert.False(string.IsNullOrWhiteSpace(completed.Links.Ui));
Assert.False(string.IsNullOrWhiteSpace(completed.Links.Report));
Assert.False(string.IsNullOrWhiteSpace(completed.Links.Attestation));
break;
default:
throw new InvalidOperationException($"Unexpected payload type {orchestratorEvent.Payload.GetType().Name}.");
}
}
private static void AssertDsseMatchesReport(DsseEnvelopeDto? envelope, ReportDocumentDto report)
{
Assert.NotNull(envelope);
var canonicalReportBytes = JsonSerializer.SerializeToUtf8Bytes(report, SerializerOptions);
var expectedPayload = Convert.ToBase64String(canonicalReportBytes);
Assert.Equal(expectedPayload, envelope.Payload);
}
private static OrchestratorEvent DeserializeOrchestratorEvent(string json, string expectedKind)
{
var root = JsonNode.Parse(json)?.AsObject() ?? throw new InvalidOperationException("Sample JSON must not be null.");
var attributes = root["attributes"] is JsonObject attrObj && attrObj.Count > 0
? attrObj.ToImmutableDictionary(
pair => pair.Key,
pair => pair.Value?.GetValue<string>() ?? string.Empty,
StringComparer.Ordinal).ToImmutableSortedDictionary(StringComparer.Ordinal)
: null;
OrchestratorEventScope? scope = null;
if (root["scope"] is JsonObject scopeObj)
{
scope = new OrchestratorEventScope
{
Namespace = scopeObj["namespace"]?.GetValue<string>(),
Repo = scopeObj["repo"]?.GetValue<string>() ?? string.Empty,
Digest = scopeObj["digest"]?.GetValue<string>() ?? string.Empty,
Component = scopeObj["component"]?.GetValue<string>(),
Image = scopeObj["image"]?.GetValue<string>()
};
}
var payloadNode = root["payload"] ?? throw new InvalidOperationException("Payload node missing.");
OrchestratorEventPayload payload = expectedKind switch
{
OrchestratorEventKinds.ScannerReportReady => payloadNode.Deserialize<ReportReadyEventPayload>(SerializerOptions)
?? throw new InvalidOperationException("Unable to deserialize report ready payload."),
OrchestratorEventKinds.ScannerScanCompleted => payloadNode.Deserialize<ScanCompletedEventPayload>(SerializerOptions)
?? throw new InvalidOperationException("Unable to deserialize scan completed payload."),
_ => throw new InvalidOperationException("Unexpected event kind.")
};
return new OrchestratorEvent
{
EventId = Guid.Parse(root["eventId"]!.GetValue<string>()),
Kind = root["kind"]!.GetValue<string>(),
Version = root["version"]?.GetValue<int>() ?? 1,
Tenant = root["tenant"]!.GetValue<string>(),
OccurredAt = DateTimeOffset.Parse(root["occurredAt"]!.GetValue<string>(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
RecordedAt = root["recordedAt"] is JsonValue recordedAtNode
? DateTimeOffset.Parse(recordedAtNode.GetValue<string>(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)
: null,
Source = root["source"]?.GetValue<string>() ?? string.Empty,
IdempotencyKey = root["idempotencyKey"]?.GetValue<string>() ?? string.Empty,
CorrelationId = root["correlationId"]?.GetValue<string>(),
TraceId = root["traceId"]?.GetValue<string>(),
SpanId = root["spanId"]?.GetValue<string>(),
Scope = scope,
Attributes = attributes,
Payload = payload
};
}
private static string LoadSample(string fileName)
{
var path = Path.Combine(AppContext.BaseDirectory, fileName);
Assert.True(File.Exists(path), $"Sample file not found at '{path}'.");
return File.ReadAllText(path);
}
}