diff --git a/cdc-service/src/main/java/com/xtrmetl/cdc/controller/CdcController.java b/cdc-service/src/main/java/com/xtrmetl/cdc/controller/CdcController.java index 621af2df..b40ddf80 100644 --- a/cdc-service/src/main/java/com/xtrmetl/cdc/controller/CdcController.java +++ b/cdc-service/src/main/java/com/xtrmetl/cdc/controller/CdcController.java @@ -108,13 +108,27 @@ private Map sourceEntry(com.xtrmetl.cdc.spi.CdcSourceConnector s return entry; } + /** + * Requests CDC engine startup and exposes only a stable public failure contract. + * + * @return {@code 200} when startup is accepted, or {@code 500} without internal startup diagnostics + */ @PostMapping("/start") @Observed(name = "cdc.start", contextualName = "cdc-start") public ResponseEntity startCdc() { - cdcService.start(); - return ResponseEntity.ok("CDC process started"); + try { + cdcService.start(); + return ResponseEntity.ok("CDC process started"); + } catch (RuntimeException e) { + return ResponseEntity.internalServerError().body("CDC process could not be started"); + } } + /** + * Requests CDC engine shutdown and exposes only a stable public failure contract. + * + * @return {@code 200} when shutdown succeeds, or {@code 500} without internal shutdown diagnostics + */ @PostMapping("/stop") @Observed(name = "cdc.stop", contextualName = "cdc-stop") public ResponseEntity stopCdc() { @@ -122,7 +136,7 @@ public ResponseEntity stopCdc() { cdcService.stop(); return ResponseEntity.ok("CDC process stopped"); } catch (IOException e) { - return ResponseEntity.internalServerError().body("Error stopping CDC process: " + e.getMessage()); + return ResponseEntity.internalServerError().body("CDC process could not be stopped"); } } } diff --git a/cdc-service/src/test/java/com/xtrmetl/cdc/controller/CdcControllerTest.java b/cdc-service/src/test/java/com/xtrmetl/cdc/controller/CdcControllerTest.java index 08cf2f5d..3a4eca0d 100644 --- a/cdc-service/src/test/java/com/xtrmetl/cdc/controller/CdcControllerTest.java +++ b/cdc-service/src/test/java/com/xtrmetl/cdc/controller/CdcControllerTest.java @@ -69,6 +69,21 @@ void testStartCdc() { verify(cdcService, times(1)).start(); } + @Test + void testStartCdcWithExceptionDoesNotExposeInternalDiagnostics() { + String sensitiveDiagnostic = + "Failed to initialize jdbc:postgresql://db.internal/prod?user=cdc&password=driver-secret"; + doThrow(new IllegalStateException(sensitiveDiagnostic)).when(cdcService).start(); + + ResponseEntity response = cdcController.startCdc(); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals("CDC process could not be started", response.getBody()); + assertFalse(response.getBody().contains("driver-secret")); + assertFalse(response.getBody().contains("jdbc:postgresql://")); + verify(cdcService, times(1)).start(); + } + @Test void testStopCdc() throws IOException { ResponseEntity response = cdcController.stopCdc(); @@ -79,13 +94,17 @@ void testStopCdc() throws IOException { } @Test - void testStopCdcWithException() throws IOException { - doThrow(new IOException("Error stopping CDC")).when(cdcService).stop(); + void testStopCdcWithExceptionDoesNotExposeInternalDiagnostics() throws IOException { + String sensitiveDiagnostic = + "Error closing offset store at jdbc:postgresql://db.internal/prod?password=driver-secret"; + doThrow(new IOException(sensitiveDiagnostic)).when(cdcService).stop(); ResponseEntity response = cdcController.stopCdc(); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertEquals("Error stopping CDC process: Error stopping CDC", response.getBody()); + assertEquals("CDC process could not be stopped", response.getBody()); + assertFalse(response.getBody().contains("driver-secret")); + assertFalse(response.getBody().contains("jdbc:postgresql://")); verify(cdcService, times(1)).stop(); }