fix(router): ship audit bundle frontdoor cutover
This commit is contained in:
@@ -313,6 +313,30 @@ public sealed class GatewayOptionsValidatorTests
|
||||
Assert.Contains("regex", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_DuplicateExactRoutePath_Throws()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Routes.Add(new StellaOpsRoute
|
||||
{
|
||||
Type = StellaOpsRouteType.ReverseProxy,
|
||||
Path = "/v1/audit-bundles",
|
||||
TranslatesTo = "http://exportcenter.stella-ops.local/v1/audit-bundles"
|
||||
});
|
||||
options.Routes.Add(new StellaOpsRoute
|
||||
{
|
||||
Type = StellaOpsRouteType.ReverseProxy,
|
||||
Path = "/v1/audit-bundles",
|
||||
TranslatesTo = "http://evidencelocker.stella-ops.local/v1/audit-bundles"
|
||||
});
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
GatewayOptionsValidator.Validate(options));
|
||||
|
||||
Assert.Contains("Duplicate route path", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("/v1/audit-bundles", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidRegex_DoesNotThrow()
|
||||
{
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
@@ -198,6 +200,87 @@ public sealed class AspNetRouterRequestDispatcherTests
|
||||
Assert.Contains("\"timeWindow\":\"24h\"", responseBody, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchAsync_BindsAuditBundleCreateRequest_WithAcceptedUnionResult()
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
var app = builder.Build();
|
||||
app.MapPost(
|
||||
"/v1/audit-bundles",
|
||||
Results<Accepted<AuditBundleAcceptedResponse>, BadRequest<AuditBundleErrorEnvelope>> (
|
||||
[FromBody] AuditBundleCreateRequest body) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(body.Subject.Name))
|
||||
{
|
||||
return TypedResults.BadRequest(
|
||||
new AuditBundleErrorEnvelope(
|
||||
new AuditBundleErrorDetail("INVALID_REQUEST", "Subject name is required")));
|
||||
}
|
||||
|
||||
return TypedResults.Accepted(
|
||||
$"/v1/audit-bundles/{body.Subject.Name}",
|
||||
new AuditBundleAcceptedResponse(
|
||||
$"bndl-{body.Subject.Name}",
|
||||
"Pending",
|
||||
$"/v1/audit-bundles/{body.Subject.Name}",
|
||||
30));
|
||||
});
|
||||
|
||||
var endpointRouteBuilder = (IEndpointRouteBuilder)app;
|
||||
var endpointDataSource = new StaticEndpointDataSource(
|
||||
endpointRouteBuilder.DataSources.SelectMany(static dataSource => dataSource.Endpoints).ToArray());
|
||||
var dispatcher = new AspNetRouterRequestDispatcher(
|
||||
app.Services,
|
||||
endpointDataSource,
|
||||
new StellaRouterBridgeOptions
|
||||
{
|
||||
ServiceName = "exportcenter",
|
||||
Version = "1.0.0-alpha1",
|
||||
Region = "local",
|
||||
AuthorizationTrustMode = GatewayAuthorizationTrustMode.ServiceEnforced
|
||||
},
|
||||
NullLogger<AspNetRouterRequestDispatcher>.Instance);
|
||||
|
||||
var transportFrame = FrameConverter.ToFrame(new RequestFrame
|
||||
{
|
||||
RequestId = "req-audit-1",
|
||||
Method = "POST",
|
||||
Path = "/v1/audit-bundles",
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
["content-type"] = "application/json"
|
||||
},
|
||||
Payload = Encoding.UTF8.GetBytes("""
|
||||
{
|
||||
"subject": {
|
||||
"type": "IMAGE",
|
||||
"name": "asset-review-prod",
|
||||
"digest": {
|
||||
"sha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
||||
}
|
||||
},
|
||||
"includeContent": {
|
||||
"vulnReports": true,
|
||||
"sbom": true,
|
||||
"vexDecisions": true,
|
||||
"policyEvaluations": true,
|
||||
"attestations": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
});
|
||||
|
||||
var roundTrippedRequest = FrameConverter.ToRequestFrame(transportFrame);
|
||||
Assert.NotNull(roundTrippedRequest);
|
||||
|
||||
var response = await dispatcher.DispatchAsync(roundTrippedRequest!);
|
||||
var responseBody = Encoding.UTF8.GetString(response.Payload.ToArray());
|
||||
|
||||
Assert.Equal(StatusCodes.Status202Accepted, response.StatusCode);
|
||||
Assert.Contains("\"bundleId\":\"bndl-asset-review-prod\"", responseBody, StringComparison.Ordinal);
|
||||
Assert.Contains("\"status\":\"Pending\"", responseBody, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static AspNetRouterRequestDispatcher CreateDispatcher(RouteEndpoint endpoint, StellaRouterBridgeOptions options)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -227,4 +310,27 @@ public sealed class AspNetRouterRequestDispatcherTests
|
||||
}
|
||||
|
||||
private sealed record PreferencesBody(string[] Regions, string[] Environments, string TimeWindow);
|
||||
private sealed record AuditBundleCreateRequest(
|
||||
AuditBundleSubjectRef Subject,
|
||||
AuditBundleTimeWindow? TimeWindow,
|
||||
AuditBundleContentSelection IncludeContent,
|
||||
string? CallbackUrl = null);
|
||||
private sealed record AuditBundleSubjectRef(
|
||||
string Type,
|
||||
string Name,
|
||||
IReadOnlyDictionary<string, string> Digest);
|
||||
private sealed record AuditBundleTimeWindow(DateTimeOffset? From, DateTimeOffset? To);
|
||||
private sealed record AuditBundleContentSelection(
|
||||
bool VulnReports = true,
|
||||
bool Sbom = true,
|
||||
bool VexDecisions = true,
|
||||
bool PolicyEvaluations = true,
|
||||
bool Attestations = true);
|
||||
private sealed record AuditBundleAcceptedResponse(
|
||||
string BundleId,
|
||||
string Status,
|
||||
string StatusUrl,
|
||||
int? EstimatedCompletionSeconds);
|
||||
private sealed record AuditBundleErrorEnvelope(AuditBundleErrorDetail Error);
|
||||
private sealed record AuditBundleErrorDetail(string Code, string Message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user