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:
hsiegeln
2026-03-11 16:29:53 +01:00
parent 82a190c8e2
commit 0615a9851d
5 changed files with 626 additions and 1 deletions

View File

@@ -0,0 +1,54 @@
package com.cameleer3.server.app.controller;
import com.cameleer3.server.app.storage.ClickHouseExecutionRepository;
import com.cameleer3.server.core.detail.DetailService;
import com.cameleer3.server.core.detail.ExecutionDetail;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* Endpoints for retrieving execution details and processor snapshots.
* <p>
* The detail endpoint returns a nested processor tree reconstructed from
* flat parallel arrays stored in ClickHouse. The snapshot endpoint returns
* per-processor exchange data (bodies and headers).
*/
@RestController
@RequestMapping("/api/v1/executions")
@Tag(name = "Detail", description = "Execution detail and processor snapshot endpoints")
public class DetailController {
private final DetailService detailService;
private final ClickHouseExecutionRepository executionRepository;
public DetailController(DetailService detailService,
ClickHouseExecutionRepository executionRepository) {
this.detailService = detailService;
this.executionRepository = executionRepository;
}
@GetMapping("/{executionId}")
@Operation(summary = "Get execution detail with nested processor tree")
public ResponseEntity<ExecutionDetail> getDetail(@PathVariable String executionId) {
return detailService.getDetail(executionId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{executionId}/processors/{index}/snapshot")
@Operation(summary = "Get exchange snapshot for a specific processor")
public ResponseEntity<Map<String, String>> getProcessorSnapshot(
@PathVariable String executionId,
@PathVariable int index) {
return executionRepository.findProcessorSnapshot(executionId, index)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}