diff --git a/spring-boot-project/spring-boot-docker-compose/build.gradle b/spring-boot-project/spring-boot-docker-compose/build.gradle index be484f8b8d..c1e95e62f8 100644 --- a/spring-boot-project/spring-boot-docker-compose/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose/build.gradle @@ -20,6 +20,8 @@ dependencies { dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql") + dockerTestRuntimeOnly("org.postgresql:postgresql") + dockerTestRuntimeOnly("org.postgresql:r2dbc-postgresql") implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java index 398bffbc22..ff1919d292 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,15 @@ package org.springframework.boot.docker.compose.service.connection.postgres; +import java.sql.Driver; + import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -33,13 +39,16 @@ import static org.assertj.core.api.Assertions.assertThat; class PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { @DockerComposeTest(composeFile = "postgres-compose.yaml", image = TestImage.POSTGRESQL) - void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); } @DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL) - void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) + throws ClassNotFoundException { assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); } private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { @@ -48,4 +57,21 @@ class PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); } + private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + assertThat(executeQuery(connectionDetails, DatabaseDriver.POSTGRESQL.getValidationQuery(), Integer.class)) + .isEqualTo(1); + } + + @SuppressWarnings("unchecked") + private T executeQuery(JdbcConnectionDetails connectionDetails, String sql, Class result) + throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setUrl(connectionDetails.getJdbcUrl()); + dataSource.setUsername(connectionDetails.getUsername()); + dataSource.setPassword(connectionDetails.getPassword()); + dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), + getClass().getClassLoader())); + return new JdbcTemplate(dataSource).queryForObject(sql, result); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java index 2c01091b09..80f4f88c0d 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,16 @@ package org.springframework.boot.docker.compose.service.connection.postgres; +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactoryOptions; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.r2dbc.core.DatabaseClient; import static org.assertj.core.api.Assertions.assertThat; @@ -37,11 +42,13 @@ class PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { @DockerComposeTest(composeFile = "postgres-compose.yaml", image = TestImage.POSTGRESQL) void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); } @DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL) void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); } private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { @@ -51,4 +58,18 @@ class PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); } + private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) { + assertThat(executeQuery(connectionDetails, DatabaseDriver.POSTGRESQL.getValidationQuery(), Integer.class)) + .isEqualTo(1); + } + + private T executeQuery(R2dbcConnectionDetails connectionDetails, String sql, Class result) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + return DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions)) + .sql(sql) + .mapValue(result) + .first() + .block(Duration.ofSeconds(30)); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml index cb34245ec1..97eca6b3c8 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml @@ -4,6 +4,6 @@ services: ports: - '5432' environment: - - 'POSTGRESQL_USER=myuser' - - 'POSTGRESQL_DB=mydatabase' + - 'POSTGRESQL_USERNAME=myuser' + - 'POSTGRESQL_DATABASE=mydatabase' - 'POSTGRESQL_PASSWORD=secret' diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java index 4a63bbc0b6..3c69e87dce 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,18 @@ import org.springframework.util.StringUtils; * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick + * @author He Zean */ class PostgresEnvironment { + private static final String[] USERNAME_KEYS = new String[] { "POSTGRES_USER", "POSTGRESQL_USER", + "POSTGRESQL_USERNAME" }; + + private static final String DEFAULT_USERNAME = "postgres"; + + private static final String[] DATABASE_KEYS = new String[] { "POSTGRES_DB", "POSTGRESQL_DB", + "POSTGRESQL_DATABASE" }; + private final String username; private final String password; @@ -38,9 +47,18 @@ class PostgresEnvironment { private final String database; PostgresEnvironment(Map env) { - this.username = env.getOrDefault("POSTGRES_USER", env.getOrDefault("POSTGRESQL_USER", "postgres")); + this.username = extract(env, USERNAME_KEYS, DEFAULT_USERNAME); this.password = extractPassword(env); - this.database = env.getOrDefault("POSTGRES_DB", env.getOrDefault("POSTGRESQL_DB", this.username)); + this.database = extract(env, DATABASE_KEYS, this.username); + } + + private String extract(Map env, String[] keys, String defaultValue) { + for (String key : keys) { + if (env.containsKey(key)) { + return env.get(key); + } + } + return defaultValue; } private String extractPassword(Map env) { diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java index 9058c1f11d..e79f89625f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick + * @author He Zean */ class PostgresEnvironmentTests { @@ -66,6 +67,13 @@ class PostgresEnvironmentTests { assertThat(environment.getUsername()).isEqualTo("me"); } + @Test + void getUsernameWhenHasPostgresqlUsername() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_USERNAME", "me", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getUsername()).isEqualTo("me"); + } + @Test void getPasswordWhenHasPostgresPassword() { PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRES_PASSWORD", "secret")); @@ -104,6 +112,13 @@ class PostgresEnvironmentTests { assertThat(environment.getDatabase()).isEqualTo("me"); } + @Test + void getDatabaseWhenNoPostgresqlDatabaseAndPostgresqlUsername() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_USERNAME", "me", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("me"); + } + @Test void getDatabaseWhenHasPostgresDb() { PostgresEnvironment environment = new PostgresEnvironment( @@ -118,4 +133,11 @@ class PostgresEnvironmentTests { assertThat(environment.getDatabase()).isEqualTo("db"); } + @Test + void getDatabaseWhenHasPostgresqlDatabase() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_DATABASE", "db", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("db"); + } + }