fix(test): rewrite Execution/Metrics ControllerITs to chunks + REST verify
Same pattern as DetailControllerIT:
- ExecutionControllerIT: all four tests now post ExecutionChunk envelopes
(chunkSeq=0, final=true) carrying instanceId/applicationId. Flush
visibility check pivoted from PG SELECT → env-scoped search REST.
- MetricsControllerIT: postMetrics_dataAppearsAfterFlush now stamps
collectedAt at now() and verifies through GET /environments/{env}/
agents/{id}/metrics with the default 1h lookback, looking for a
non-zero bucket on the metric name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,15 @@ package com.cameleer.server.app.controller;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -15,6 +18,11 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* POST /api/v1/data/executions is owned by ChunkIngestionController (the
|
||||
* legacy ExecutionController is @ConditionalOnMissingBean(ChunkAccumulator)
|
||||
* and never binds). All payloads here are ExecutionChunk envelopes.
|
||||
*/
|
||||
class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
|
||||
@Autowired
|
||||
@@ -23,27 +31,33 @@ class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
@Autowired
|
||||
private TestSecurityHelper securityHelper;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private HttpHeaders authHeaders;
|
||||
private HttpHeaders viewerHeaders;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
String jwt = securityHelper.registerTestAgent("test-agent-execution-it");
|
||||
authHeaders = securityHelper.authHeaders(jwt);
|
||||
viewerHeaders = securityHelper.authHeadersNoBody(securityHelper.viewerToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void postSingleExecution_returns202() {
|
||||
String json = """
|
||||
{
|
||||
"routeId": "route-1",
|
||||
"exchangeId": "exchange-1",
|
||||
"applicationId": "test-group",
|
||||
"instanceId": "test-agent-execution-it",
|
||||
"routeId": "route-1",
|
||||
"correlationId": "corr-1",
|
||||
"status": "COMPLETED",
|
||||
"startTime": "2026-03-11T10:00:00Z",
|
||||
"endTime": "2026-03-11T10:00:01Z",
|
||||
"durationMs": 1000,
|
||||
"errorMessage": "",
|
||||
"errorStackTrace": "",
|
||||
"chunkSeq": 0,
|
||||
"final": true,
|
||||
"processors": []
|
||||
}
|
||||
""";
|
||||
@@ -60,22 +74,30 @@ class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
void postArrayOfExecutions_returns202() {
|
||||
String json = """
|
||||
[{
|
||||
"routeId": "route-2",
|
||||
"exchangeId": "exchange-2",
|
||||
"applicationId": "test-group",
|
||||
"instanceId": "test-agent-execution-it",
|
||||
"routeId": "route-2",
|
||||
"status": "COMPLETED",
|
||||
"startTime": "2026-03-11T10:00:00Z",
|
||||
"endTime": "2026-03-11T10:00:01Z",
|
||||
"durationMs": 1000,
|
||||
"chunkSeq": 0,
|
||||
"final": true,
|
||||
"processors": []
|
||||
},
|
||||
{
|
||||
"routeId": "route-3",
|
||||
"exchangeId": "exchange-3",
|
||||
"applicationId": "test-group",
|
||||
"instanceId": "test-agent-execution-it",
|
||||
"routeId": "route-3",
|
||||
"status": "FAILED",
|
||||
"startTime": "2026-03-11T10:00:00Z",
|
||||
"endTime": "2026-03-11T10:00:02Z",
|
||||
"durationMs": 2000,
|
||||
"errorMessage": "Something went wrong",
|
||||
"chunkSeq": 0,
|
||||
"final": true,
|
||||
"processors": []
|
||||
}]
|
||||
""";
|
||||
@@ -92,13 +114,17 @@ class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
void postExecution_dataAppearsAfterFlush() {
|
||||
String json = """
|
||||
{
|
||||
"routeId": "flush-test-route",
|
||||
"exchangeId": "flush-exchange-1",
|
||||
"applicationId": "test-group",
|
||||
"instanceId": "test-agent-execution-it",
|
||||
"routeId": "flush-test-route",
|
||||
"correlationId": "flush-corr-1",
|
||||
"status": "COMPLETED",
|
||||
"startTime": "2026-03-11T10:00:00Z",
|
||||
"endTime": "2026-03-11T10:00:01Z",
|
||||
"durationMs": 1000,
|
||||
"chunkSeq": 0,
|
||||
"final": true,
|
||||
"processors": []
|
||||
}
|
||||
""";
|
||||
@@ -108,11 +134,17 @@ class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
new HttpEntity<>(json, authHeaders),
|
||||
String.class);
|
||||
|
||||
await().atMost(10, SECONDS).untilAsserted(() -> {
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT count(*) FROM executions WHERE route_id = 'flush-test-route'",
|
||||
Integer.class);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1);
|
||||
// Executions live in ClickHouse; drive the visibility check through
|
||||
// the REST search API (env-scoped), never through raw SQL.
|
||||
await().atMost(15, SECONDS).untilAsserted(() -> {
|
||||
ResponseEntity<String> r = restTemplate.exchange(
|
||||
"/api/v1/environments/default/executions?correlationId=flush-corr-1",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(viewerHeaders),
|
||||
String.class);
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
JsonNode body = objectMapper.readTree(r.getBody());
|
||||
assertThat(body.get("total").asLong()).isGreaterThanOrEqualTo(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,11 +152,15 @@ class ExecutionControllerIT extends AbstractPostgresIT {
|
||||
void postExecution_unknownFieldsAccepted() {
|
||||
String json = """
|
||||
{
|
||||
"routeId": "route-unk",
|
||||
"exchangeId": "exchange-unk",
|
||||
"applicationId": "test-group",
|
||||
"instanceId": "test-agent-execution-it",
|
||||
"routeId": "route-unk",
|
||||
"status": "COMPLETED",
|
||||
"startTime": "2026-03-11T10:00:00Z",
|
||||
"durationMs": 500,
|
||||
"chunkSeq": 0,
|
||||
"final": true,
|
||||
"unknownField": "should-be-ignored",
|
||||
"anotherUnknown": 42,
|
||||
"processors": []
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.cameleer.server.app.controller;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -23,12 +26,18 @@ class MetricsControllerIT extends AbstractPostgresIT {
|
||||
@Autowired
|
||||
private TestSecurityHelper securityHelper;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private HttpHeaders authHeaders;
|
||||
private HttpHeaders viewerHeaders;
|
||||
private String agentId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
String jwt = securityHelper.registerTestAgent("test-agent-metrics-it");
|
||||
agentId = "test-agent-metrics-it";
|
||||
String jwt = securityHelper.registerTestAgent(agentId);
|
||||
authHeaders = securityHelper.authHeaders(jwt);
|
||||
viewerHeaders = securityHelper.authHeadersNoBody(securityHelper.viewerToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -53,26 +62,43 @@ class MetricsControllerIT extends AbstractPostgresIT {
|
||||
|
||||
@Test
|
||||
void postMetrics_dataAppearsAfterFlush() {
|
||||
// Post fresh now-stamped metrics so the default 1h lookback window of
|
||||
// GET /agents/{id}/metrics sees them deterministically.
|
||||
java.time.Instant now = java.time.Instant.now();
|
||||
String json = """
|
||||
[{
|
||||
"instanceId": "agent-flush-test",
|
||||
"collectedAt": "2026-03-11T10:00:00Z",
|
||||
"instanceId": "%s",
|
||||
"collectedAt": "%s",
|
||||
"metricName": "memory.used",
|
||||
"metricValue": 1024.0,
|
||||
"tags": {}
|
||||
}]
|
||||
""";
|
||||
""".formatted(agentId, now.toString());
|
||||
|
||||
restTemplate.postForEntity(
|
||||
ResponseEntity<String> ingestResponse = restTemplate.postForEntity(
|
||||
"/api/v1/data/metrics",
|
||||
new HttpEntity<>(json, authHeaders),
|
||||
String.class);
|
||||
assertThat(ingestResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
|
||||
await().atMost(10, SECONDS).untilAsserted(() -> {
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT count(*) FROM agent_metrics WHERE instance_id = 'agent-flush-test'",
|
||||
Integer.class);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1);
|
||||
// agent_metrics lives in ClickHouse; drive the visibility check through
|
||||
// the env-scoped REST metrics endpoint, never through raw SQL.
|
||||
await().atMost(15, SECONDS).untilAsserted(() -> {
|
||||
ResponseEntity<String> r = restTemplate.exchange(
|
||||
"/api/v1/environments/default/agents/" + agentId
|
||||
+ "/metrics?names=memory.used",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(viewerHeaders),
|
||||
String.class);
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
JsonNode body = objectMapper.readTree(r.getBody());
|
||||
JsonNode series = body.path("metrics").path("memory.used");
|
||||
assertThat(series.isArray()).isTrue();
|
||||
long nonZero = 0;
|
||||
for (JsonNode bucket : series) {
|
||||
if (bucket.get("value").asDouble() > 0) nonZero++;
|
||||
}
|
||||
assertThat(nonZero).isGreaterThanOrEqualTo(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user