feat(02-03): detail controller, tree reconstruction, processor snapshot endpoint
- Implement findRawById and findProcessorSnapshot in ClickHouseExecutionRepository
- DetailController with GET /executions/{id} returning nested processor tree
- GET /executions/{id}/processors/{index}/snapshot for per-processor exchange data
- 5 unit tests for tree reconstruction (linear, branching, multiple roots, empty)
- 6 integration tests for detail endpoint, snapshot, and 404 handling
- Added assertj and mockito test dependencies to core module
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package com.cameleer3.server.core.detail;
|
||||
|
||||
import com.cameleer3.server.core.storage.ExecutionRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DetailService#reconstructTree} logic.
|
||||
* <p>
|
||||
* Verifies correct parent-child wiring from flat parallel arrays.
|
||||
*/
|
||||
class TreeReconstructionTest {
|
||||
|
||||
private final DetailService detailService = new DetailService(mock(ExecutionRepository.class));
|
||||
|
||||
private static final Instant NOW = Instant.parse("2026-03-10T10:00:00Z");
|
||||
|
||||
@Test
|
||||
void linearChain_rootChildGrandchild() {
|
||||
// [root, child, grandchild], depths=[0,1,2], parents=[-1,0,1]
|
||||
List<ProcessorNode> roots = detailService.reconstructTree(
|
||||
new String[]{"root", "child", "grandchild"},
|
||||
new String[]{"log", "bean", "to"},
|
||||
new String[]{"COMPLETED", "COMPLETED", "COMPLETED"},
|
||||
new Instant[]{NOW, NOW, NOW},
|
||||
new Instant[]{NOW, NOW, NOW},
|
||||
new long[]{10, 20, 30},
|
||||
new String[]{"n1", "n2", "n3"},
|
||||
new String[]{"", "", ""},
|
||||
new String[]{"", "", ""},
|
||||
new int[]{0, 1, 2},
|
||||
new int[]{-1, 0, 1}
|
||||
);
|
||||
|
||||
assertThat(roots).hasSize(1);
|
||||
ProcessorNode root = roots.get(0);
|
||||
assertThat(root.getProcessorId()).isEqualTo("root");
|
||||
assertThat(root.getChildren()).hasSize(1);
|
||||
|
||||
ProcessorNode child = root.getChildren().get(0);
|
||||
assertThat(child.getProcessorId()).isEqualTo("child");
|
||||
assertThat(child.getChildren()).hasSize(1);
|
||||
|
||||
ProcessorNode grandchild = child.getChildren().get(0);
|
||||
assertThat(grandchild.getProcessorId()).isEqualTo("grandchild");
|
||||
assertThat(grandchild.getChildren()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleRoots_noNesting() {
|
||||
// [A, B, C], depths=[0,0,0], parents=[-1,-1,-1]
|
||||
List<ProcessorNode> roots = detailService.reconstructTree(
|
||||
new String[]{"A", "B", "C"},
|
||||
new String[]{"log", "log", "log"},
|
||||
new String[]{"COMPLETED", "COMPLETED", "COMPLETED"},
|
||||
new Instant[]{NOW, NOW, NOW},
|
||||
new Instant[]{NOW, NOW, NOW},
|
||||
new long[]{10, 20, 30},
|
||||
new String[]{"n1", "n2", "n3"},
|
||||
new String[]{"", "", ""},
|
||||
new String[]{"", "", ""},
|
||||
new int[]{0, 0, 0},
|
||||
new int[]{-1, -1, -1}
|
||||
);
|
||||
|
||||
assertThat(roots).hasSize(3);
|
||||
assertThat(roots.get(0).getProcessorId()).isEqualTo("A");
|
||||
assertThat(roots.get(1).getProcessorId()).isEqualTo("B");
|
||||
assertThat(roots.get(2).getProcessorId()).isEqualTo("C");
|
||||
roots.forEach(r -> assertThat(r.getChildren()).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void branchingTree_parentWithTwoChildren_secondChildHasGrandchild() {
|
||||
// [parent, child1, child2, grandchild], depths=[0,1,1,2], parents=[-1,0,0,2]
|
||||
List<ProcessorNode> roots = detailService.reconstructTree(
|
||||
new String[]{"parent", "child1", "child2", "grandchild"},
|
||||
new String[]{"split", "log", "bean", "to"},
|
||||
new String[]{"COMPLETED", "COMPLETED", "COMPLETED", "COMPLETED"},
|
||||
new Instant[]{NOW, NOW, NOW, NOW},
|
||||
new Instant[]{NOW, NOW, NOW, NOW},
|
||||
new long[]{100, 20, 30, 5},
|
||||
new String[]{"n1", "n2", "n3", "n4"},
|
||||
new String[]{"", "", "", ""},
|
||||
new String[]{"", "", "", ""},
|
||||
new int[]{0, 1, 1, 2},
|
||||
new int[]{-1, 0, 0, 2}
|
||||
);
|
||||
|
||||
assertThat(roots).hasSize(1);
|
||||
ProcessorNode parent = roots.get(0);
|
||||
assertThat(parent.getProcessorId()).isEqualTo("parent");
|
||||
assertThat(parent.getChildren()).hasSize(2);
|
||||
|
||||
ProcessorNode child1 = parent.getChildren().get(0);
|
||||
assertThat(child1.getProcessorId()).isEqualTo("child1");
|
||||
assertThat(child1.getChildren()).isEmpty();
|
||||
|
||||
ProcessorNode child2 = parent.getChildren().get(1);
|
||||
assertThat(child2.getProcessorId()).isEqualTo("child2");
|
||||
assertThat(child2.getChildren()).hasSize(1);
|
||||
|
||||
ProcessorNode grandchild = child2.getChildren().get(0);
|
||||
assertThat(grandchild.getProcessorId()).isEqualTo("grandchild");
|
||||
assertThat(grandchild.getChildren()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyArrays_producesEmptyList() {
|
||||
List<ProcessorNode> roots = detailService.reconstructTree(
|
||||
new String[]{},
|
||||
new String[]{},
|
||||
new String[]{},
|
||||
new Instant[]{},
|
||||
new Instant[]{},
|
||||
new long[]{},
|
||||
new String[]{},
|
||||
new String[]{},
|
||||
new String[]{},
|
||||
new int[]{},
|
||||
new int[]{}
|
||||
);
|
||||
|
||||
assertThat(roots).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullArrays_producesEmptyList() {
|
||||
List<ProcessorNode> roots = detailService.reconstructTree(
|
||||
null, null, null, null, null, null, null, null, null, null, null
|
||||
);
|
||||
|
||||
assertThat(roots).isEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user