From 10074824dc7cbc97ac097ae0bf51a4a74c70a8fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:52:47 +0900 Subject: [PATCH 1/2] test(cdc): replay PostgreSQL SPI config truth on current develop --- ...resDebeziumCdcSourceConfigurationTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java diff --git a/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java b/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java new file mode 100644 index 00000000..a65766fa --- /dev/null +++ b/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java @@ -0,0 +1,43 @@ +package com.xtrmetl.cdc.spi; + +import com.xtrmetl.cdc.service.CdcService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.ObjectProvider; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * Guards the PostgreSQL source SPI against silently ignored per-call configuration. + */ +class PostgresDebeziumCdcSourceConfigurationTest { + + @Test + void rejectsCallerConfigurationBeforeStartingDeploymentConfiguredService() { + CdcService service = mock(CdcService.class); + @SuppressWarnings("unchecked") + ObjectProvider provider = mock(ObjectProvider.class); + when(provider.getIfAvailable()).thenReturn(service); + PostgresDebeziumCdcSource source = new PostgresDebeziumCdcSource(provider); + String rejectedSecret = "buyer-secret-password-8472"; + + IllegalArgumentException failure = assertThrows( + IllegalArgumentException.class, + () -> source.start(Map.of("database.password", rejectedSecret)) + ); + + assertEquals( + "postgres-debezium uses deployment-owned configuration; per-call config must be empty", + failure.getMessage() + ); + assertFalse(failure.getMessage().contains(rejectedSecret)); + assertFalse(failure.getMessage().contains("database.password")); + verifyNoInteractions(service); + } +} From 690f6ed6eae00b11c732f126393df99303c2739e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:11:40 +0900 Subject: [PATCH 2/2] fix(cdc): reject ignored PostgreSQL SPI config --- .../cdc/spi/PostgresDebeziumCdcSource.java | 34 +++++++++++++++++-- ...resDebeziumCdcSourceConfigurationTest.java | 31 +++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/cdc-service/src/main/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSource.java b/cdc-service/src/main/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSource.java index dcc0e653..673a4578 100644 --- a/cdc-service/src/main/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSource.java +++ b/cdc-service/src/main/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSource.java @@ -11,8 +11,12 @@ /** * Adapter for the live Debezium PostgreSQL capture path owned by {@link CdcService}. * - *

{@link #start(Map)} / {@link #stop()} delegate to the service when available so the SPI - * is a real control surface for the supported source type.

+ *

The live service reads connection and Debezium settings from deployment configuration. Per-call + * SPI configuration is intentionally unsupported: callers must pass an empty map so values such as + * credentials are never accepted and then silently ignored.

+ * + *

{@link #start(Map)} and {@link #stop()} delegate to the service when available so the SPI is a + * real control surface for the supported source type.

*/ @Component public final class PostgresDebeziumCdcSource implements CdcSourceConnector { @@ -21,6 +25,11 @@ public final class PostgresDebeziumCdcSource implements CdcSourceConnector { private final ObjectProvider cdcService; + /** + * Creates the PostgreSQL CDC adapter backed by the deployment-configured live service. + * + * @param cdcService provider for the live CDC service + */ public PostgresDebeziumCdcSource(ObjectProvider cdcService) { this.cdcService = cdcService; } @@ -67,13 +76,34 @@ public SourceCapabilities capabilities() { return new SourceCapabilities("debezium-embedded", Set.of("postgresql"), false); } + /** + * Validates the per-call configuration accepted by this adapter. + * + *

The live PostgreSQL capture service is configured by the deployment, not by this SPI call. + * An empty map is therefore the only valid value.

+ * + * @param config per-call settings; must be non-null and empty + * @throws IllegalArgumentException when {@code config} is null or contains any entry + */ @Override public void validate(Map config) { if (config == null) { throw new IllegalArgumentException("config must not be null"); } + if (!config.isEmpty()) { + throw new IllegalArgumentException( + "postgres-debezium uses deployment-owned configuration; per-call config must be empty" + ); + } } + /** + * Starts the deployment-configured PostgreSQL CDC service. + * + * @param config per-call settings; must be non-null and empty + * @throws IllegalArgumentException when {@code config} is null or contains any entry + * @throws IllegalStateException when the live {@link CdcService} is unavailable + */ @Override public void start(Map config) { validate(config); diff --git a/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java b/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java index a65766fa..8d97a210 100644 --- a/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java +++ b/cdc-service/src/test/java/com/xtrmetl/cdc/spi/PostgresDebeziumCdcSourceConfigurationTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -40,4 +41,34 @@ void rejectsCallerConfigurationBeforeStartingDeploymentConfiguredService() { assertFalse(failure.getMessage().contains("database.password")); verifyNoInteractions(service); } + + @Test + void rejectsNullConfigurationBeforeServiceLookup() { + CdcService service = mock(CdcService.class); + @SuppressWarnings("unchecked") + ObjectProvider provider = mock(ObjectProvider.class); + PostgresDebeziumCdcSource source = new PostgresDebeziumCdcSource(provider); + + IllegalArgumentException failure = assertThrows( + IllegalArgumentException.class, + () -> source.start(null) + ); + + assertEquals("config must not be null", failure.getMessage()); + verifyNoInteractions(provider, service); + } + + @Test + void emptyConfigurationStartsDeploymentConfiguredService() { + CdcService service = mock(CdcService.class); + @SuppressWarnings("unchecked") + ObjectProvider provider = mock(ObjectProvider.class); + when(provider.getIfAvailable()).thenReturn(service); + PostgresDebeziumCdcSource source = new PostgresDebeziumCdcSource(provider); + + source.start(Map.of()); + + verify(provider).getIfAvailable(); + verify(service).start(); + } }