blocker move 1

This commit is contained in:
StellaOps Bot
2025-11-23 14:53:13 +02:00
parent 8d78dd219b
commit f47d2d1377
25 changed files with 4788 additions and 4007 deletions

View File

@@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.WebService.Contracts;
public sealed record ConcelierHealthResponse(
[property: JsonPropertyName("tenant")] string Tenant,
[property: JsonPropertyName("queueDepth")] int QueueDepth,
[property: JsonPropertyName("ingestLatencyP50Ms")] int IngestLatencyP50Ms,
[property: JsonPropertyName("ingestLatencyP99Ms")] int IngestLatencyP99Ms,
[property: JsonPropertyName("errorRate1h")] double ErrorRate1h,
[property: JsonPropertyName("sloBurnRate")] double SloBurnRate,
[property: JsonPropertyName("window")] string Window,
[property: JsonPropertyName("updatedAt")] string UpdatedAt);
public sealed record ConcelierTimelineEvent(
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("tenant")] string Tenant,
[property: JsonPropertyName("source")] string Source,
[property: JsonPropertyName("queueDepth")] int QueueDepth,
[property: JsonPropertyName("p50Ms")] int P50Ms,
[property: JsonPropertyName("p99Ms")] int P99Ms,
[property: JsonPropertyName("errors")] int Errors,
[property: JsonPropertyName("sloBurnRate")] double SloBurnRate,
[property: JsonPropertyName("traceId")] string? TraceId,
[property: JsonPropertyName("occurredAt")] string OccurredAt);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
using System.Diagnostics.Metrics;
namespace StellaOps.Concelier.WebService.Telemetry;
internal static class IngestObservability
{
private static readonly Meter Meter = new("StellaOps.Concelier.WebService", "1.0.0");
public static readonly Histogram<double> IngestLatencySeconds =
Meter.CreateHistogram<double>("concelier_ingest_latency_seconds", "s", "Ingest pipeline latency.");
public static readonly ObservableGauge<long> QueueDepth =
Meter.CreateObservableGauge("concelier_ingest_queue_depth", observeQueueDepth, "items", "Queued ingest items.");
public static readonly Counter<long> IngestErrorsTotal =
Meter.CreateCounter<long>("concelier_ingest_errors_total", "errors", "Ingest errors by reason.");
public static readonly ObservableGauge<double> SloBurnRate =
Meter.CreateObservableGauge("concelier_ingest_slo_burn_rate", observeSloBurn, "ratio", "SLO burn rate over window.");
private static long observeQueueDepth() => 0;
private static double observeSloBurn() => 0.0;
}

View File

@@ -0,0 +1,45 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace StellaOps.Concelier.WebService.Tests;
public class ConcelierHealthEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ConcelierHealthEndpointTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(_ => { });
}
[Fact]
public async Task Health_requires_tenant_header()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/obs/concelier/health");
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task Health_returns_payload()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-a");
var response = await client.GetAsync("/obs/concelier/health");
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<HealthResponse>();
payload.Should().NotBeNull();
payload!.tenant.Should().Be("tenant-a");
payload.queueDepth.Should().Be(0);
payload.window.Should().Be("5m");
}
private sealed record HealthResponse(string tenant, int queueDepth, int ingestLatencyP50Ms, int ingestLatencyP99Ms, double errorRate1h, double sloBurnRate, string window, string updatedAt);
}

View File

@@ -0,0 +1,46 @@
using System.Net;
using System.Net.Http.Headers;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace StellaOps.Concelier.WebService.Tests;
public class ConcelierTimelineEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ConcelierTimelineEndpointTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(_ => { });
}
[Fact]
public async Task Timeline_requires_tenant_header()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/obs/concelier/timeline");
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task Timeline_returns_sse_event()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-a");
using var request = new HttpRequestMessage(HttpMethod.Get, "/obs/concelier/timeline");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var firstLine = await reader.ReadLineAsync();
firstLine.Should().NotBeNull();
firstLine!.Should().StartWith("event: ingest.update");
}
}