test(02-04): add failing DiagramLinkingIT for diagram hash linking

- Test 1: diagram_content_hash populated with SHA-256 when RouteGraph exists
- Test 2: diagram_content_hash empty when no RouteGraph exists for route

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-11 16:51:34 +01:00
parent 9db053ee59
commit ea9d81213f

View File

@@ -0,0 +1,152 @@
package com.cameleer3.server.app.storage;
import com.cameleer3.server.app.AbstractClickHouseIT;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
/**
* Integration test proving that diagram_content_hash is populated during
* execution ingestion when a RouteGraph exists for the same route+agent.
*/
class DiagramLinkingIT extends AbstractClickHouseIT {
@Autowired
private TestRestTemplate restTemplate;
@Test
void diagramHashPopulated_whenRouteGraphExistsBeforeExecution() {
// 1. Ingest a RouteGraph for route "diagram-link-route" via the diagrams endpoint
String graphJson = """
{
"routeId": "diagram-link-route",
"description": "Linking test diagram",
"version": 1,
"nodes": [
{"id": "n1", "type": "ENDPOINT", "label": "direct:start"},
{"id": "n2", "type": "BEAN", "label": "myBean"}
],
"edges": [
{"source": "n1", "target": "n2", "edgeType": "FLOW"}
],
"processorNodeMapping": {}
}
""";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Cameleer-Protocol-Version", "1");
ResponseEntity<String> diagramResponse = restTemplate.postForEntity(
"/api/v1/data/diagrams",
new HttpEntity<>(graphJson, headers),
String.class);
assertThat(diagramResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
// 2. Wait for diagram to be flushed to ClickHouse before ingesting execution
await().atMost(10, SECONDS).untilAsserted(() -> {
String hash = jdbcTemplate.queryForObject(
"SELECT content_hash FROM route_diagrams WHERE route_id = 'diagram-link-route' LIMIT 1",
String.class);
assertThat(hash).isNotNull().isNotEmpty();
});
// 3. Ingest a RouteExecution for the same routeId
String executionJson = """
{
"routeId": "diagram-link-route",
"exchangeId": "ex-diag-link-1",
"correlationId": "corr-diag-link-1",
"status": "COMPLETED",
"startTime": "2026-03-11T10:00:00Z",
"endTime": "2026-03-11T10:00:01Z",
"durationMs": 1000,
"processors": [
{
"processorId": "proc-1",
"processorType": "bean",
"status": "COMPLETED",
"startTime": "2026-03-11T10:00:00Z",
"endTime": "2026-03-11T10:00:00.500Z",
"durationMs": 500,
"children": []
}
]
}
""";
ResponseEntity<String> execResponse = restTemplate.postForEntity(
"/api/v1/data/executions",
new HttpEntity<>(executionJson, headers),
String.class);
assertThat(execResponse.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
// 4. Verify diagram_content_hash is a non-empty SHA-256 hash (64 hex chars)
await().atMost(10, SECONDS).untilAsserted(() -> {
String hash = jdbcTemplate.queryForObject(
"SELECT diagram_content_hash FROM route_executions WHERE route_id = 'diagram-link-route'",
String.class);
assertThat(hash)
.isNotNull()
.isNotEmpty()
.hasSize(64) // SHA-256 hex = 64 characters
.matches("[a-f0-9]{64}");
});
}
@Test
void diagramHashEmpty_whenNoRouteGraphExists() {
// Ingest a RouteExecution for a route with NO prior diagram
String executionJson = """
{
"routeId": "no-diagram-route",
"exchangeId": "ex-no-diag-1",
"correlationId": "corr-no-diag-1",
"status": "COMPLETED",
"startTime": "2026-03-11T10:00:00Z",
"endTime": "2026-03-11T10:00:01Z",
"durationMs": 1000,
"processors": [
{
"processorId": "proc-no-diag",
"processorType": "log",
"status": "COMPLETED",
"startTime": "2026-03-11T10:00:00Z",
"endTime": "2026-03-11T10:00:00.500Z",
"durationMs": 500,
"children": []
}
]
}
""";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Cameleer-Protocol-Version", "1");
ResponseEntity<String> response = restTemplate.postForEntity(
"/api/v1/data/executions",
new HttpEntity<>(executionJson, headers),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
// Verify diagram_content_hash is empty string (graceful fallback)
await().atMost(10, SECONDS).untilAsserted(() -> {
String hash = jdbcTemplate.queryForObject(
"SELECT diagram_content_hash FROM route_executions WHERE route_id = 'no-diagram-route'",
String.class);
assertThat(hash)
.isNotNull()
.isEmpty();
});
}
}