tests fixes and sprints work

This commit is contained in:
master
2026-01-22 19:08:46 +02:00
parent c32fff8f86
commit 726d70dc7f
881 changed files with 134434 additions and 6228 deletions

View File

@@ -0,0 +1,328 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Services;
namespace StellaOps.Platform.WebService.Endpoints;
public static class AnalyticsEndpoints
{
public static IEndpointRouteBuilder MapAnalyticsEndpoints(this IEndpointRouteBuilder app)
{
var analytics = app.MapGroup("/api/analytics")
.WithTags("Analytics");
analytics.MapGet("/suppliers", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] SuppliersQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetSuppliersAsync(
requestContext!,
query.Limit,
query.Environment,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsSupplierConcentration>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsSuppliers")
.WithSummary("Get supplier concentration analytics")
.WithDescription("Returns the top suppliers by component and artifact exposure.")
.Produces<PlatformListResponse<AnalyticsSupplierConcentration>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/licenses", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] EnvironmentQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetLicensesAsync(
requestContext!,
query.Environment,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsLicenseDistribution>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsLicenses")
.WithSummary("Get license distribution analytics")
.WithDescription("Returns component and artifact counts grouped by license.")
.Produces<PlatformListResponse<AnalyticsLicenseDistribution>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/vulnerabilities", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] VulnerabilityQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetVulnerabilitiesAsync(
requestContext!,
query.Environment,
query.MinSeverity,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsVulnerabilityExposure>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsVulnerabilities")
.WithSummary("Get vulnerability exposure analytics")
.WithDescription("Returns vulnerability exposure by severity, filtered by environment and minimum severity.")
.Produces<PlatformListResponse<AnalyticsVulnerabilityExposure>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/backlog", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] EnvironmentQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetFixableBacklogAsync(
requestContext!,
query.Environment,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsFixableBacklogItem>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsBacklog")
.WithSummary("Get fixable vulnerability backlog")
.WithDescription("Returns vulnerabilities with available fixes, filtered by environment.")
.Produces<PlatformListResponse<AnalyticsFixableBacklogItem>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/attestation-coverage", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] EnvironmentQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetAttestationCoverageAsync(
requestContext!,
query.Environment,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsAttestationCoverage>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsAttestationCoverage")
.WithSummary("Get attestation coverage analytics")
.WithDescription("Returns attestation coverage gaps by environment.")
.Produces<PlatformListResponse<AnalyticsAttestationCoverage>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/trends/vulnerabilities", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] TrendQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetVulnerabilityTrendsAsync(
requestContext!,
query.Environment,
query.Days,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsVulnerabilityTrendPoint>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsVulnerabilityTrends")
.WithSummary("Get vulnerability trend analytics")
.WithDescription("Returns daily vulnerability trend points for a time window.")
.Produces<PlatformListResponse<AnalyticsVulnerabilityTrendPoint>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/trends/components", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformAnalyticsService service,
[AsParameters] TrendQuery query,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!service.IsConfigured)
{
return AnalyticsUnavailable();
}
var result = await service.GetComponentTrendsAsync(
requestContext!,
query.Environment,
query.Days,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<AnalyticsComponentTrendPoint>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value,
result.Value.Count));
}).WithName("GetAnalyticsComponentTrends")
.WithSummary("Get component trend analytics")
.WithDescription("Returns daily component trend points for a time window.")
.Produces<PlatformListResponse<AnalyticsComponentTrendPoint>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
return app;
}
private static bool TryResolveContext(
HttpContext context,
PlatformRequestContextResolver resolver,
out PlatformRequestContext? requestContext,
out IResult? failure)
{
if (resolver.TryResolve(context, out requestContext, out var error))
{
failure = null;
return true;
}
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
return false;
}
private static IResult AnalyticsUnavailable()
{
return Results.Problem(
title: "analytics_not_configured",
detail: "Analytics storage is not configured for this service.",
statusCode: StatusCodes.Status503ServiceUnavailable);
}
private sealed record SuppliersQuery(int? Limit, string? Environment);
private sealed record EnvironmentQuery(string? Environment);
private sealed record VulnerabilityQuery(
string? Environment,
[property: FromQuery(Name = "minSeverity")] string? MinSeverity);
private sealed record TrendQuery(
string? Environment,
int? Days);
}