feat(02-01): extend ingestion to populate Phase 2 columns with integration tests
- Refactored flattenProcessors to flattenWithMetadata (FlatProcessor record with depth/parentIndex) - INSERT now populates 12 new columns: exchange_bodies, exchange_headers, processor_depths, processor_parent_indexes, processor_error_messages, processor_error_stacktraces, processor_input_bodies, processor_output_bodies, processor_input_headers, processor_output_headers, processor_diagram_node_ids, diagram_content_hash - Exchange bodies/headers concatenated from all processor snapshots + route-level snapshots - Null ExchangeSnapshot handled gracefully (empty string defaults) - Headers serialized to JSON via Jackson ObjectMapper - IngestionSchemaIT verifies 3-level tree metadata, body concatenation, null snapshot handling - DiagramRenderer/DiagramLayout stubs created to fix pre-existing compilation error (Rule 3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.common.model.ExchangeSnapshot;
|
||||
import com.cameleer3.common.model.ExecutionStatus;
|
||||
import com.cameleer3.common.model.ProcessorExecution;
|
||||
import com.cameleer3.common.model.RouteExecution;
|
||||
import com.cameleer3.server.app.AbstractClickHouseIT;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,8 +10,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.sql.Array;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -90,49 +85,40 @@ class IngestionSchemaIT extends AbstractClickHouseIT {
|
||||
postExecution(json);
|
||||
|
||||
await().atMost(10, SECONDS).untilAsserted(() -> {
|
||||
var rows = jdbcTemplate.queryForList(
|
||||
"SELECT processor_depths, processor_parent_indexes, processor_diagram_node_ids, " +
|
||||
"exchange_bodies, processor_input_bodies, processor_output_bodies, " +
|
||||
"processor_input_headers, processor_output_headers " +
|
||||
"FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
// Use individual typed queries to avoid ClickHouse Array cast issues
|
||||
var depths = queryArray(
|
||||
"SELECT processor_depths FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(depths).containsExactly("0", "1", "2");
|
||||
|
||||
assertThat(rows).hasSize(1);
|
||||
var row = rows.get(0);
|
||||
var parentIndexes = queryArray(
|
||||
"SELECT processor_parent_indexes FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(parentIndexes).containsExactly("-1", "0", "1");
|
||||
|
||||
// Verify depths: root=0, child=1, grandchild=2
|
||||
@SuppressWarnings("unchecked")
|
||||
var depths = (List<Number>) row.get("processor_depths");
|
||||
assertThat(depths).containsExactly((short) 0, (short) 1, (short) 2);
|
||||
|
||||
// Verify parent indexes: root=-1, child=0 (parent is root at idx 0), grandchild=1 (parent is child at idx 1)
|
||||
@SuppressWarnings("unchecked")
|
||||
var parentIndexes = (List<Number>) row.get("processor_parent_indexes");
|
||||
assertThat(parentIndexes).containsExactly(-1, 0, 1);
|
||||
|
||||
// Verify diagram node IDs
|
||||
@SuppressWarnings("unchecked")
|
||||
var diagramNodeIds = (List<String>) row.get("processor_diagram_node_ids");
|
||||
var diagramNodeIds = queryArray(
|
||||
"SELECT processor_diagram_node_ids FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(diagramNodeIds).containsExactly("node-root", "node-child", "node-grandchild");
|
||||
|
||||
// Verify exchange_bodies contains concatenated text
|
||||
String bodies = (String) row.get("exchange_bodies");
|
||||
String bodies = jdbcTemplate.queryForObject(
|
||||
"SELECT exchange_bodies FROM route_executions WHERE route_id = 'schema-test-tree'",
|
||||
String.class);
|
||||
assertThat(bodies).contains("root-input");
|
||||
assertThat(bodies).contains("root-output");
|
||||
assertThat(bodies).contains("child-input");
|
||||
assertThat(bodies).contains("child-output");
|
||||
|
||||
// Verify per-processor input/output bodies
|
||||
@SuppressWarnings("unchecked")
|
||||
var inputBodies = (List<String>) row.get("processor_input_bodies");
|
||||
var inputBodies = queryArray(
|
||||
"SELECT processor_input_bodies FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(inputBodies).containsExactly("root-input", "child-input", "");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var outputBodies = (List<String>) row.get("processor_output_bodies");
|
||||
var outputBodies = queryArray(
|
||||
"SELECT processor_output_bodies FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(outputBodies).containsExactly("root-output", "child-output", "");
|
||||
|
||||
// Verify per-processor headers stored as JSON strings
|
||||
@SuppressWarnings("unchecked")
|
||||
var inputHeaders = (List<String>) row.get("processor_input_headers");
|
||||
// Verify per-processor input headers stored as JSON strings
|
||||
var inputHeaders = queryArray(
|
||||
"SELECT processor_input_headers FROM route_executions WHERE route_id = 'schema-test-tree'");
|
||||
assertThat(inputHeaders.get(0)).contains("Content-Type");
|
||||
assertThat(inputHeaders.get(0)).contains("application/json");
|
||||
});
|
||||
@@ -175,22 +161,19 @@ class IngestionSchemaIT extends AbstractClickHouseIT {
|
||||
postExecution(json);
|
||||
|
||||
await().atMost(10, SECONDS).untilAsserted(() -> {
|
||||
var rows = jdbcTemplate.queryForList(
|
||||
"SELECT exchange_bodies, exchange_headers " +
|
||||
"FROM route_executions WHERE route_id = 'schema-test-bodies'");
|
||||
|
||||
assertThat(rows).hasSize(1);
|
||||
var row = rows.get(0);
|
||||
|
||||
// Bodies should contain all sources
|
||||
String bodies = (String) row.get("exchange_bodies");
|
||||
String bodies = jdbcTemplate.queryForObject(
|
||||
"SELECT exchange_bodies FROM route_executions WHERE route_id = 'schema-test-bodies'",
|
||||
String.class);
|
||||
assertThat(bodies).contains("processor-body-text");
|
||||
assertThat(bodies).contains("processor-output-text");
|
||||
assertThat(bodies).contains("route-level-input-body");
|
||||
assertThat(bodies).contains("route-level-output-body");
|
||||
|
||||
// Headers should contain route-level header
|
||||
String headers = (String) row.get("exchange_headers");
|
||||
String headers = jdbcTemplate.queryForObject(
|
||||
"SELECT exchange_headers FROM route_executions WHERE route_id = 'schema-test-bodies'",
|
||||
String.class);
|
||||
assertThat(headers).contains("X-Route");
|
||||
assertThat(headers).contains("header-value");
|
||||
});
|
||||
@@ -224,26 +207,20 @@ class IngestionSchemaIT extends AbstractClickHouseIT {
|
||||
postExecution(json);
|
||||
|
||||
await().atMost(10, SECONDS).untilAsserted(() -> {
|
||||
var rows = jdbcTemplate.queryForList(
|
||||
"SELECT exchange_bodies, exchange_headers, processor_input_bodies, " +
|
||||
"processor_output_bodies, processor_depths, processor_parent_indexes " +
|
||||
"FROM route_executions WHERE route_id = 'schema-test-null-snap'");
|
||||
|
||||
assertThat(rows).hasSize(1);
|
||||
var row = rows.get(0);
|
||||
|
||||
// Empty but not null
|
||||
String bodies = (String) row.get("exchange_bodies");
|
||||
String bodies = jdbcTemplate.queryForObject(
|
||||
"SELECT exchange_bodies FROM route_executions WHERE route_id = 'schema-test-null-snap'",
|
||||
String.class);
|
||||
assertThat(bodies).isNotNull();
|
||||
|
||||
// Depths and parent indexes still populated for tree metadata
|
||||
@SuppressWarnings("unchecked")
|
||||
var depths = (List<Number>) row.get("processor_depths");
|
||||
assertThat(depths).containsExactly((short) 0);
|
||||
var depths = queryArray(
|
||||
"SELECT processor_depths FROM route_executions WHERE route_id = 'schema-test-null-snap'");
|
||||
assertThat(depths).containsExactly("0");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var parentIndexes = (List<Number>) row.get("processor_parent_indexes");
|
||||
assertThat(parentIndexes).containsExactly(-1);
|
||||
var parentIndexes = queryArray(
|
||||
"SELECT processor_parent_indexes FROM route_executions WHERE route_id = 'schema-test-null-snap'");
|
||||
assertThat(parentIndexes).containsExactly("-1");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,4 +236,26 @@ class IngestionSchemaIT extends AbstractClickHouseIT {
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query an array column from ClickHouse and return it as a List of strings.
|
||||
* Handles the ClickHouse JDBC Array type by converting via toString on elements.
|
||||
*/
|
||||
private List<String> queryArray(String sql) {
|
||||
return jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
Object arr = rs.getArray(1).getArray();
|
||||
if (arr instanceof Object[] objects) {
|
||||
return Arrays.stream(objects).map(Object::toString).toList();
|
||||
} else if (arr instanceof short[] shorts) {
|
||||
var result = new java.util.ArrayList<String>();
|
||||
for (short s : shorts) result.add(String.valueOf(s));
|
||||
return result;
|
||||
} else if (arr instanceof int[] ints) {
|
||||
var result = new java.util.ArrayList<String>();
|
||||
for (int v : ints) result.add(String.valueOf(v));
|
||||
return result;
|
||||
}
|
||||
return List.<String>of();
|
||||
}).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user