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:
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user