diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 000000000..0f8745d63 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,91 @@ +name: Integration Tests + +# Boots selected sample apps against real Postgres / MongoDB containers (Testcontainers) and +# exercises synapse's controller, data-adapter, exception, and api-docs surfaces end-to-end. +# +# Default run: push or PR to develop -> runs against develop. +# Custom run: workflow_dispatch with a `branch` input -> checks out that branch and tests it. +# This is how a contributor verifies a branch (e.g. a synapse-data-mongodb patch) doesn't break +# the routes/objects the framework ships, without setting up Docker locally. + +on: + push: + branches: [develop] + pull_request: + branches: [develop] + workflow_dispatch: + inputs: + branch: + description: 'Branch to integration-test (defaults to develop)' + required: false + default: 'develop' + type: string + +# Opt into Node.js 24 for the @v4 javascript actions below (checkout, setup-java, cache, +# upload-artifact). Node 20 will be removed from the runner in September 2026; opting in +# now silences the deprecation warning and avoids a forced switch later. +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + integration-tests: + permissions: + contents: read + checks: write # required by mikepenz/action-junit-report to publish the test-results check + pull-requests: write + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'zulu' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Install reactor (so sample apps resolve the checked-out synapse jars) + run: mvn -B -DskipTests -DskipITs install + + - name: Run integration tests + # The previous step already installed every reactor module to ~/.m2, so we don't need + # `-am` here; the listed sample modules pull their synapse deps from the local repo. + # `-fae` (fail-at-end) keeps Maven going across modules so a failure in one sample + # doesn't hide the result of the other. + run: | + mvn -B -fae -P integration-tests verify \ + -pl service/service-samples/sample-service-rest-postgres-book,service/service-samples/sample-service-reactive-mongodb-book + + - name: Publish test report + # Always runs so the summary table is available on both green and red runs. The action + # itself reports failures back to the job, so the previous Maven step can stay green if + # we ever want to inspect reports without failing the workflow. + uses: mikepenz/action-junit-report@v5 + if: always() + with: + report_paths: '**/target/failsafe-reports/TEST-*.xml' + check_name: Integration Test Results + detailed_summary: true + include_passed: true + require_tests: true + fail_on_failure: false # the Maven verify step already fails the job on test failures + + - name: Upload Failsafe reports on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: failsafe-reports + path: | + **/target/failsafe-reports/ + **/target/surefire-reports/ + retention-days: 14 diff --git a/pom.xml b/pom.xml index a8e1b90fe..06c8aecfc 100644 --- a/pom.xml +++ b/pom.xml @@ -612,6 +612,21 @@ testcontainers 1.21.2 + + org.testcontainers + junit-jupiter + 1.21.2 + + + org.testcontainers + postgresql + 1.21.2 + + + org.testcontainers + mongodb + 1.21.2 + @@ -1344,5 +1359,22 @@ + + + integration-tests + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + false + + + + + + diff --git a/service/service-samples/sample-service-reactive-mongodb-book/pom.xml b/service/service-samples/sample-service-reactive-mongodb-book/pom.xml index 8294fe28d..6fc153149 100644 --- a/service/service-samples/sample-service-reactive-mongodb-book/pom.xml +++ b/service/service-samples/sample-service-reactive-mongodb-book/pom.xml @@ -34,6 +34,37 @@ io.americanexpress.synapse sample-data-mongodb-reactive + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + mongodb + test + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + diff --git a/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java new file mode 100644 index 000000000..d3d96ae68 --- /dev/null +++ b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import io.americanexpress.data.book.repository.BookRepository; +import io.americanexpress.service.book.rest.BookApplication; +import io.americanexpress.service.book.rest.model.CreateBookRequest; +import io.americanexpress.service.book.rest.model.ReadBookRequest; +import io.americanexpress.service.book.rest.model.ReadBookResponse; +import io.americanexpress.service.book.rest.model.UpdateBookRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest(classes = BookApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient +class BookCrudIT { + + @DynamicPropertySource + static void props(DynamicPropertyRegistry registry) { + BookMongoContainer.registerProperties(registry); + } + + @Autowired + private WebTestClient web; + + @Autowired + private BookRepository bookRepository; + + @BeforeEach + void resetCollection() { + bookRepository.deleteAll().block(); + } + + @Test + void createReadUpdateDelete_givenValidBook_endToEndAgainstRealMongo() { + CreateBookRequest create = new CreateBookRequest(); + create.setTitle("Reactive Synapse"); + create.setAuthor("synapse-it"); + + web.post().uri("/v1/books") + .bodyValue(create) + .exchange() + .expectStatus().isCreated(); + + // BaseReadFluxReactiveController exposes POST /v1/books/multiple_results — Flux response + ReadBookRequest read = new ReadBookRequest(); + web.post().uri("/v1/books/multiple_results") + .bodyValue(read) + .exchange() + .expectStatus().isOk() + .expectBodyList(ReadBookResponse.class) + .hasSize(1) + .value(books -> { + ReadBookResponse first = books.get(0); + org.junit.jupiter.api.Assertions.assertEquals("Reactive Synapse", first.getTitle()); + org.junit.jupiter.api.Assertions.assertEquals("synapse-it", first.getAuthor()); + }); + + UpdateBookRequest update = new UpdateBookRequest(); + update.setTitle("Reactive Synapse"); + update.setAuthor("synapse-it"); + update.setNumberOfCopies(7); + web.put().uri("/v1/books") + .bodyValue(update) + .exchange() + .expectStatus().is2xxSuccessful(); + + // Verify the update landed in the data adapter + org.junit.jupiter.api.Assertions.assertEquals( + 7, + bookRepository.findByTitleAndAuthor("Reactive Synapse", "synapse-it") + .block() + .getNumberOfCopies()); + + web.delete().uri("/v1/books/Reactive Synapse") + .exchange() + .expectStatus().isNoContent(); + + org.junit.jupiter.api.Assertions.assertEquals(0L, bookRepository.count().block()); + } +} diff --git a/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java new file mode 100644 index 000000000..9e20f37de --- /dev/null +++ b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import io.americanexpress.data.book.repository.BookRepository; +import io.americanexpress.service.book.rest.BookApplication; +import io.americanexpress.service.book.rest.model.CreateBookRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest(classes = BookApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient +class BookExceptionEnvelopeIT { + + @DynamicPropertySource + static void props(DynamicPropertyRegistry registry) { + BookMongoContainer.registerProperties(registry); + } + + @Autowired + private WebTestClient web; + + @Autowired + private BookRepository bookRepository; + + @BeforeEach + void resetCollection() { + bookRepository.deleteAll().block(); + } + + @Test + void create_givenBlankRequiredFields_returnsBadRequest() { + // CreateBookRequest title and author are @NotBlank — sending blanks must be rejected. + CreateBookRequest invalid = new CreateBookRequest(); + invalid.setTitle(""); + invalid.setAuthor(""); + + web.post().uri("/v1/books") + .bodyValue(invalid) + .exchange() + .expectStatus().isBadRequest(); + } + + @Test + void create_givenMalformedJson_returnsBadRequest() { + web.post().uri("/v1/books") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{not-valid-json") + .exchange() + .expectStatus().is4xxClientError(); + } +} diff --git a/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookMongoContainer.java b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookMongoContainer.java new file mode 100644 index 000000000..9f1a66237 --- /dev/null +++ b/service/service-samples/sample-service-reactive-mongodb-book/src/test/java/io/americanexpress/service/book/rest/integration/BookMongoContainer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Singleton MongoDB container shared across every IT in this module so Spring's + * test-context cache can reuse one application context for all suites. + */ +final class BookMongoContainer { + + private static final MongoDBContainer INSTANCE = + new MongoDBContainer(DockerImageName.parse("mongo:7")); + + static { + INSTANCE.start(); + } + + private BookMongoContainer() {} + + static void registerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.mongodb.uri", INSTANCE::getReplicaSetUrl); + registry.add("spring.data.mongodb.database", () -> "synapse_it"); + } +} diff --git a/service/service-samples/sample-service-rest-postgres-book/pom.xml b/service/service-samples/sample-service-rest-postgres-book/pom.xml index 3fd2a83a1..86212c675 100644 --- a/service/service-samples/sample-service-rest-postgres-book/pom.xml +++ b/service/service-samples/sample-service-rest-postgres-book/pom.xml @@ -33,6 +33,36 @@ synapse-service-rest + + + org.springframework.boot + spring-boot-starter-test + test + + + org.postgresql + postgresql + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + diff --git a/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java new file mode 100644 index 000000000..d38293118 --- /dev/null +++ b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookCrudIT.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import io.americanexpress.service.book.rest.BookApplication; +import io.americanexpress.service.book.rest.model.CreateBookRequest; +import io.americanexpress.service.book.rest.model.ReadBookRequest; +import io.americanexpress.service.book.rest.model.ReadBookResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = BookApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("integration-test") +class BookCrudIT { + + @DynamicPropertySource + static void props(DynamicPropertyRegistry registry) { + BookPostgresContainer.registerProperties(registry); + } + + @Autowired + private TestRestTemplate http; + + @Test + void createReadDelete_givenValidBook_endToEndAgainstRealPostgres() { + CreateBookRequest create = new CreateBookRequest(); + create.setTitle("Synapse Crud Lifecycle"); + create.setAuthor("synapse-it"); + + ResponseEntity created = http.postForEntity("/v1/books", create, Void.class); + assertEquals(HttpStatus.CREATED, created.getStatusCode()); + assertNotNull(created.getHeaders().getLocation(), "BaseCreateController must emit Location"); + assertTrue(created.getHeaders().getLocation().toString().endsWith("/v1/books/0"), + "CreateBookResponse has no id, so synapse falls back to /0"); + + ReadBookRequest read = new ReadBookRequest(); + read.setTitle("Synapse Crud Lifecycle"); + read.setAuthor("synapse-it"); + ResponseEntity readResp = http.postForEntity( + "/v1/books/inquiry_results", read, ReadBookResponse.class); + assertEquals(HttpStatus.OK, readResp.getStatusCode()); + assertNotNull(readResp.getBody()); + assertEquals("Synapse Crud Lifecycle", readResp.getBody().getTitle()); + assertEquals("synapse-it", readResp.getBody().getAuthor()); + + // BaseDeleteController exposes DELETE /v1/books/{identifier}; DeleteBookService treats + // the identifier as a title, so we delete by the book's title. + ResponseEntity deleted = http.exchange( + "/v1/books/Synapse Crud Lifecycle", + org.springframework.http.HttpMethod.DELETE, + null, + Void.class); + assertEquals(HttpStatus.NO_CONTENT, deleted.getStatusCode()); + + ResponseEntity readAfter = http.postForEntity( + "/v1/books/inquiry_results", read, ReadBookResponse.class); + assertEquals(HttpStatus.NO_CONTENT, readAfter.getStatusCode(), + "MonoResponseEntityCreator returns 204 when the service produces null"); + } +} diff --git a/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java new file mode 100644 index 000000000..79968154c --- /dev/null +++ b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookExceptionEnvelopeIT.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.americanexpress.service.book.rest.BookApplication; +import io.americanexpress.service.book.rest.model.CreateBookRequest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = BookApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("integration-test") +class BookExceptionEnvelopeIT { + + @DynamicPropertySource + static void props(DynamicPropertyRegistry registry) { + BookPostgresContainer.registerProperties(registry); + } + + @Autowired + private TestRestTemplate http; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void create_givenBlankRequiredFields_returnsSynapseErrorEnvelope() throws Exception { + // BookRequest fields title and author are @NotBlank; sending blanks must trigger + // ControllerExceptionHandler.handleMethodArgumentNotValidException -> ErrorResponse @ 400. + CreateBookRequest invalid = new CreateBookRequest(); + invalid.setTitle(""); + invalid.setAuthor(""); + + ResponseEntity response = http.postForEntity("/v1/books", invalid, String.class); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + + JsonNode body = objectMapper.readTree(response.getBody()); + assertNotNull(body.get("code"), "ErrorResponse.code missing"); + assertNotNull(body.get("message"), "ErrorResponse.message missing"); + assertNotNull(body.get("moreInfo"), "ErrorResponse.moreInfo missing"); + assertNotNull(body.get("developerMessage"), "ErrorResponse.developerMessage missing"); + } + + @Test + void create_givenMalformedJson_returnsSynapseErrorEnvelopeFor4xx() throws Exception { + // Triggers HttpMessageNotReadableException -> handler returns ErrorResponse with the + // GENERIC_4XX_ERROR code (400-series). + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity request = new HttpEntity<>("{not-valid-json", headers); + + ResponseEntity response = http.exchange("/v1/books", HttpMethod.POST, request, String.class); + assertTrue(response.getStatusCode().is4xxClientError(), + "Expected 4xx, got " + response.getStatusCode()); + + JsonNode body = objectMapper.readTree(response.getBody()); + assertNotNull(body.get("code")); + assertNotNull(body.get("message")); + assertNotNull(body.get("moreInfo")); + assertNotNull(body.get("developerMessage")); + } +} diff --git a/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookPostgresContainer.java b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookPostgresContainer.java new file mode 100644 index 000000000..df9454222 --- /dev/null +++ b/service/service-samples/sample-service-rest-postgres-book/src/test/java/io/americanexpress/service/book/rest/integration/BookPostgresContainer.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.americanexpress.service.book.rest.integration; + +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Singleton PostgreSQL container shared across every IT in this module so Spring's + * test-context cache can reuse one application context for all suites. + */ +final class BookPostgresContainer { + + private static final PostgreSQLContainer INSTANCE = + new PostgreSQLContainer<>(DockerImageName.parse("postgres:16-alpine")); + + static { + INSTANCE.start(); + } + + private BookPostgresContainer() {} + + static void registerProperties(DynamicPropertyRegistry registry) { + // The default `test` profile in data-book-application.properties wires up H2 and gates + // the real Postgres config behind @Profile("!test"). ITs activate `integration-test` + // so DataBookConfig loads, then we point the datasource at the container here. + registry.add("spring.datasource.jdbcUrl", INSTANCE::getJdbcUrl); + registry.add("spring.datasource.url", INSTANCE::getJdbcUrl); + registry.add("spring.datasource.username", INSTANCE::getUsername); + registry.add("spring.datasource.password", INSTANCE::getPassword); + registry.add("spring.datasource.driver-class-name", INSTANCE::getDriverClassName); + registry.add("spring.jpa.properties.hibernate.default_schema", () -> "public"); + registry.add("hibernate.dialect", () -> "org.hibernate.dialect.PostgreSQLDialect"); + registry.add("hibernate.hbm2ddl.auto", () -> "create-drop"); + registry.add("hibernate.show_sql", () -> "false"); + registry.add("hibernate.format_sql", () -> "false"); + } +}