fix(test): rewrite SearchControllerIT seed to chunks + fix GET auth scope

Largest Cluster B test: seeded 10 executions via the legacy RouteExecution
shape which ChunkIngestionController silently degenerates to empty chunks,
then verified via a Postgres SELECT against a ClickHouse table. Both
failure modes addressed:
- All 10 seed payloads are now ExecutionChunk envelopes (chunkSeq=0,
  final=true, flat processors[]).
- Pipeline visibility probe is the env-scoped search REST endpoint
  (polling for the last corr-page-10 row).
- searchGet() helper was using the AGENT token; env-scoped read
  endpoints require VIEWER+, so it now uses viewerJwt (matches what
  searchPost already did).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-21 22:14:56 +02:00
parent a6e7458adb
commit 5684479938

View File

@@ -50,22 +50,24 @@ class SearchControllerIT extends AbstractPostgresIT {
// Execution 1: COMPLETED, short duration, no errors // Execution 1: COMPLETED, short duration, no errors
ingest(""" ingest("""
{ {
"routeId": "search-route-1",
"exchangeId": "ex-search-1", "exchangeId": "ex-search-1",
"applicationId": "test-group",
"instanceId": "test-agent-search-it",
"routeId": "search-route-1",
"correlationId": "corr-alpha", "correlationId": "corr-alpha",
"status": "COMPLETED", "status": "COMPLETED",
"startTime": "2026-03-10T10:00:00Z", "startTime": "2026-03-10T10:00:00Z",
"endTime": "2026-03-10T10:00:00.050Z", "endTime": "2026-03-10T10:00:00.050Z",
"durationMs": 50, "durationMs": 50,
"errorMessage": "", "chunkSeq": 0,
"errorStackTrace": "", "final": true,
"processors": [ "processors": [
{ {
"seq": 1,
"processorId": "proc-1", "processorId": "proc-1",
"processorType": "log", "processorType": "log",
"status": "COMPLETED", "status": "COMPLETED",
"startTime": "2026-03-10T10:00:00Z", "startTime": "2026-03-10T10:00:00Z",
"endTime": "2026-03-10T10:00:00.050Z",
"durationMs": 50, "durationMs": 50,
"inputBody": "customer-123 order data", "inputBody": "customer-123 order data",
"outputBody": "processed customer-123", "outputBody": "processed customer-123",
@@ -79,8 +81,10 @@ class SearchControllerIT extends AbstractPostgresIT {
// Execution 2: FAILED with NullPointerException, medium duration // Execution 2: FAILED with NullPointerException, medium duration
ingest(""" ingest("""
{ {
"routeId": "search-route-2",
"exchangeId": "ex-search-2", "exchangeId": "ex-search-2",
"applicationId": "test-group",
"instanceId": "test-agent-search-it",
"routeId": "search-route-2",
"correlationId": "corr-beta", "correlationId": "corr-beta",
"status": "FAILED", "status": "FAILED",
"startTime": "2026-03-10T12:00:00Z", "startTime": "2026-03-10T12:00:00Z",
@@ -88,6 +92,8 @@ class SearchControllerIT extends AbstractPostgresIT {
"durationMs": 200, "durationMs": 200,
"errorMessage": "NullPointerException in OrderService", "errorMessage": "NullPointerException in OrderService",
"errorStackTrace": "java.lang.NullPointerException\\n at com.example.OrderService.process(OrderService.java:42)", "errorStackTrace": "java.lang.NullPointerException\\n at com.example.OrderService.process(OrderService.java:42)",
"chunkSeq": 0,
"final": true,
"processors": [] "processors": []
} }
"""); """);
@@ -95,15 +101,17 @@ class SearchControllerIT extends AbstractPostgresIT {
// Execution 3: RUNNING, long duration, different time window // Execution 3: RUNNING, long duration, different time window
ingest(""" ingest("""
{ {
"routeId": "search-route-3",
"exchangeId": "ex-search-3", "exchangeId": "ex-search-3",
"applicationId": "test-group",
"instanceId": "test-agent-search-it",
"routeId": "search-route-3",
"correlationId": "corr-gamma", "correlationId": "corr-gamma",
"status": "RUNNING", "status": "RUNNING",
"startTime": "2026-03-11T08:00:00Z", "startTime": "2026-03-11T08:00:00Z",
"endTime": "2026-03-11T08:00:01Z", "endTime": "2026-03-11T08:00:01Z",
"durationMs": 1000, "durationMs": 1000,
"errorMessage": "", "chunkSeq": 0,
"errorStackTrace": "", "final": true,
"processors": [] "processors": []
} }
"""); """);
@@ -111,8 +119,10 @@ class SearchControllerIT extends AbstractPostgresIT {
// Execution 4: FAILED with MyException in stack trace // Execution 4: FAILED with MyException in stack trace
ingest(""" ingest("""
{ {
"routeId": "search-route-4",
"exchangeId": "ex-search-4", "exchangeId": "ex-search-4",
"applicationId": "test-group",
"instanceId": "test-agent-search-it",
"routeId": "search-route-4",
"correlationId": "corr-delta", "correlationId": "corr-delta",
"status": "FAILED", "status": "FAILED",
"startTime": "2026-03-10T14:00:00Z", "startTime": "2026-03-10T14:00:00Z",
@@ -120,18 +130,17 @@ class SearchControllerIT extends AbstractPostgresIT {
"durationMs": 300, "durationMs": 300,
"errorMessage": "Processing failed", "errorMessage": "Processing failed",
"errorStackTrace": "com.example.MyException: something broke\\n at com.example.Handler.handle(Handler.java:10)", "errorStackTrace": "com.example.MyException: something broke\\n at com.example.Handler.handle(Handler.java:10)",
"chunkSeq": 0,
"final": true,
"processors": [ "processors": [
{ {
"seq": 1,
"processorId": "proc-4", "processorId": "proc-4",
"processorType": "bean", "processorType": "bean",
"status": "FAILED", "status": "FAILED",
"startTime": "2026-03-10T14:00:00Z", "startTime": "2026-03-10T14:00:00Z",
"endTime": "2026-03-10T14:00:00.300Z",
"durationMs": 300, "durationMs": 300,
"inputBody": "", "inputHeaders": {"Content-Type": "text/plain"}
"outputBody": "",
"inputHeaders": {"Content-Type": "text/plain"},
"outputHeaders": {}
} }
] ]
} }
@@ -141,28 +150,25 @@ class SearchControllerIT extends AbstractPostgresIT {
for (int i = 5; i <= 10; i++) { for (int i = 5; i <= 10; i++) {
ingest(String.format(""" ingest(String.format("""
{ {
"routeId": "search-route-%d",
"exchangeId": "ex-search-%d", "exchangeId": "ex-search-%d",
"applicationId": "test-group",
"instanceId": "test-agent-search-it",
"routeId": "search-route-%d",
"correlationId": "corr-page-%d", "correlationId": "corr-page-%d",
"status": "COMPLETED", "status": "COMPLETED",
"startTime": "2026-03-10T15:00:%02d.000Z", "startTime": "2026-03-10T15:00:%02d.000Z",
"endTime": "2026-03-10T15:00:%02d.100Z", "endTime": "2026-03-10T15:00:%02d.100Z",
"durationMs": 100, "durationMs": 100,
"errorMessage": "", "chunkSeq": 0,
"errorStackTrace": "", "final": true,
"processors": [] "processors": []
} }
""", i, i, i, i, i)); """, i, i, i, i, i));
} }
// Verify all data is in PostgreSQL (synchronous writes) // Wait for async ingestion + search indexing via REST (no raw SQL).
Integer count = jdbcTemplate.queryForObject( // Probe the last seeded execution to avoid false positives from
"SELECT count(*) FROM executions WHERE route_id LIKE 'search-route-%'", // other test classes that may have written into the shared CH tables.
Integer.class);
assertThat(count).isEqualTo(10);
// Wait for async search indexing (debounce + index time)
// Check for last seeded execution specifically to avoid false positives from other test classes
await().atMost(30, SECONDS).untilAsserted(() -> { await().atMost(30, SECONDS).untilAsserted(() -> {
ResponseEntity<String> r = searchGet("?correlationId=corr-page-10"); ResponseEntity<String> r = searchGet("?correlationId=corr-page-10");
JsonNode body = objectMapper.readTree(r.getBody()); JsonNode body = objectMapper.readTree(r.getBody());
@@ -373,7 +379,9 @@ class SearchControllerIT extends AbstractPostgresIT {
} }
private ResponseEntity<String> searchGet(String queryString) { private ResponseEntity<String> searchGet(String queryString) {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt); // GET /api/v1/environments/*/executions/** requires VIEWER+ — use the
// viewer token, not the agent token (agent would get 403 FORBIDDEN).
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
return restTemplate.exchange( return restTemplate.exchange(
"/api/v1/environments/default/executions" + queryString, "/api/v1/environments/default/executions" + queryString,
HttpMethod.GET, HttpMethod.GET,