From 5684479938ce189462577edf3bab4a8bc2cb6729 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:14:56 +0200 Subject: [PATCH] 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) --- .../app/controller/SearchControllerIT.java | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java index 651a0cc7..7a7296e7 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java @@ -50,22 +50,24 @@ class SearchControllerIT extends AbstractPostgresIT { // Execution 1: COMPLETED, short duration, no errors ingest(""" { - "routeId": "search-route-1", "exchangeId": "ex-search-1", + "applicationId": "test-group", + "instanceId": "test-agent-search-it", + "routeId": "search-route-1", "correlationId": "corr-alpha", "status": "COMPLETED", "startTime": "2026-03-10T10:00:00Z", "endTime": "2026-03-10T10:00:00.050Z", "durationMs": 50, - "errorMessage": "", - "errorStackTrace": "", + "chunkSeq": 0, + "final": true, "processors": [ { + "seq": 1, "processorId": "proc-1", "processorType": "log", "status": "COMPLETED", "startTime": "2026-03-10T10:00:00Z", - "endTime": "2026-03-10T10:00:00.050Z", "durationMs": 50, "inputBody": "customer-123 order data", "outputBody": "processed customer-123", @@ -79,8 +81,10 @@ class SearchControllerIT extends AbstractPostgresIT { // Execution 2: FAILED with NullPointerException, medium duration ingest(""" { - "routeId": "search-route-2", "exchangeId": "ex-search-2", + "applicationId": "test-group", + "instanceId": "test-agent-search-it", + "routeId": "search-route-2", "correlationId": "corr-beta", "status": "FAILED", "startTime": "2026-03-10T12:00:00Z", @@ -88,6 +92,8 @@ class SearchControllerIT extends AbstractPostgresIT { "durationMs": 200, "errorMessage": "NullPointerException in OrderService", "errorStackTrace": "java.lang.NullPointerException\\n at com.example.OrderService.process(OrderService.java:42)", + "chunkSeq": 0, + "final": true, "processors": [] } """); @@ -95,15 +101,17 @@ class SearchControllerIT extends AbstractPostgresIT { // Execution 3: RUNNING, long duration, different time window ingest(""" { - "routeId": "search-route-3", "exchangeId": "ex-search-3", + "applicationId": "test-group", + "instanceId": "test-agent-search-it", + "routeId": "search-route-3", "correlationId": "corr-gamma", "status": "RUNNING", "startTime": "2026-03-11T08:00:00Z", "endTime": "2026-03-11T08:00:01Z", "durationMs": 1000, - "errorMessage": "", - "errorStackTrace": "", + "chunkSeq": 0, + "final": true, "processors": [] } """); @@ -111,8 +119,10 @@ class SearchControllerIT extends AbstractPostgresIT { // Execution 4: FAILED with MyException in stack trace ingest(""" { - "routeId": "search-route-4", "exchangeId": "ex-search-4", + "applicationId": "test-group", + "instanceId": "test-agent-search-it", + "routeId": "search-route-4", "correlationId": "corr-delta", "status": "FAILED", "startTime": "2026-03-10T14:00:00Z", @@ -120,18 +130,17 @@ class SearchControllerIT extends AbstractPostgresIT { "durationMs": 300, "errorMessage": "Processing failed", "errorStackTrace": "com.example.MyException: something broke\\n at com.example.Handler.handle(Handler.java:10)", + "chunkSeq": 0, + "final": true, "processors": [ { + "seq": 1, "processorId": "proc-4", "processorType": "bean", "status": "FAILED", "startTime": "2026-03-10T14:00:00Z", - "endTime": "2026-03-10T14:00:00.300Z", "durationMs": 300, - "inputBody": "", - "outputBody": "", - "inputHeaders": {"Content-Type": "text/plain"}, - "outputHeaders": {} + "inputHeaders": {"Content-Type": "text/plain"} } ] } @@ -141,28 +150,25 @@ class SearchControllerIT extends AbstractPostgresIT { for (int i = 5; i <= 10; i++) { ingest(String.format(""" { - "routeId": "search-route-%d", "exchangeId": "ex-search-%d", + "applicationId": "test-group", + "instanceId": "test-agent-search-it", + "routeId": "search-route-%d", "correlationId": "corr-page-%d", "status": "COMPLETED", "startTime": "2026-03-10T15:00:%02d.000Z", "endTime": "2026-03-10T15:00:%02d.100Z", "durationMs": 100, - "errorMessage": "", - "errorStackTrace": "", + "chunkSeq": 0, + "final": true, "processors": [] } """, i, i, i, i, i)); } - // Verify all data is in PostgreSQL (synchronous writes) - Integer count = jdbcTemplate.queryForObject( - "SELECT count(*) FROM executions WHERE route_id LIKE 'search-route-%'", - 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 + // Wait for async ingestion + search indexing via REST (no raw SQL). + // Probe the last seeded execution to avoid false positives from + // other test classes that may have written into the shared CH tables. await().atMost(30, SECONDS).untilAsserted(() -> { ResponseEntity r = searchGet("?correlationId=corr-page-10"); JsonNode body = objectMapper.readTree(r.getBody()); @@ -373,7 +379,9 @@ class SearchControllerIT extends AbstractPostgresIT { } private ResponseEntity 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( "/api/v1/environments/default/executions" + queryString, HttpMethod.GET,