Skip to content

Commit

Permalink
Fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mfvanek committed Dec 4, 2024
1 parent bfdd30d commit 383522b
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import io.github.mfvanek.pg.connection.PgConnection;
import io.github.mfvanek.pg.connection.PgConnectionImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Expand All @@ -23,6 +22,7 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;

import javax.annotation.Nonnull;
import javax.sql.DataSource;
Expand All @@ -41,19 +41,42 @@
public class DatabaseStructureHealthAutoConfiguration {

/**
* {@link PgConnection} bean.
* Configuration properties for the database structure health check.
*/
private final DatabaseStructureHealthProperties properties;

/**
* Constructs a new instance of {@code DatabaseStructureHealthAutoConfiguration}.
*
* @param properties the {@link DatabaseStructureHealthProperties} containing
* the configuration for this auto-configuration (must not be null)
*/
public DatabaseStructureHealthAutoConfiguration(@Nonnull final DatabaseStructureHealthProperties properties) {
this.properties = properties;
}

/**
* Creates and configures a {@link PgConnection} bean.
* <p>
* This bean is created only if:
* <ul>
* <li>A {@link DataSource} bean is available in the application context.</li>
* <li>No other {@link PgConnection} bean is already defined.</li>
* </ul>
* The {@link DataSource} bean and database URL property are resolved dynamically
* based on the configured {@link DatabaseStructureHealthProperties}.
*
* @param beanFactory {@link BeanFactory} instance
* @param databaseUrl connection string to database
* @return {@link PgConnection} instance
* @param beanFactory the {@link BeanFactory} instance used to retrieve the {@link DataSource} bean
* @param environment the {@link Environment} instance used to resolve the database URL property
* @return a configured {@link PgConnection} instance
*/
@Bean
@ConditionalOnBean(DataSource.class)
@ConditionalOnMissingBean
public PgConnection pgConnection(@Nonnull final BeanFactory beanFactory,
@Value("${spring.datasource.url:#{null}}") final String databaseUrl) {
// TODO
final DataSource dataSource = beanFactory.getBean("dataSource", DataSource.class);
@Nonnull final Environment environment) {
final DataSource dataSource = beanFactory.getBean(properties.getDatasourceBeanName(), DataSource.class);
final String databaseUrl = environment.getProperty(properties.getDatasourceUrlPropertyName());
return PgConnectionImpl.ofUrl(dataSource, databaseUrl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,43 @@
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* Custom {@link SpringBootCondition} to disable starter when configured a data source to another database (not PostgreSQL).
*/
public class DatabaseStructureHealthCondition extends SpringBootCondition {

// TODO use value from properties
private static final String PROPERTY_NAME = "spring.datasource.url";

/**
* {@inheritDoc}
*/
@Override
public ConditionOutcome getMatchOutcome(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final ConditionMessage.Builder message = ConditionMessage.forCondition("pg.index.health.test PostgreSQL condition");
final String jdbcUrl = getJdbcUrl(context);
final String datasourceUrlPropertyName = getDatasourceUrlPropertyName(context);
final String jdbcUrl = getJdbcUrl(context, datasourceUrlPropertyName);
if (jdbcUrl != null && !jdbcUrl.isBlank()) {
if (jdbcUrl.startsWith(PgUrlParser.URL_HEADER) || jdbcUrl.startsWith(PgUrlParser.TESTCONTAINERS_PG_URL_PREFIX)) {
return ConditionOutcome.match(message.foundExactly("found PostgreSQL connection " + jdbcUrl));
}
return ConditionOutcome.noMatch(message.notAvailable("not PostgreSQL connection"));
}
return ConditionOutcome.match(message.didNotFind(PROPERTY_NAME).items());
return ConditionOutcome.match(message.didNotFind(datasourceUrlPropertyName).items());
}

@Nullable
private static String getJdbcUrl(final ConditionContext context) {
private static String getJdbcUrl(@Nonnull final ConditionContext context,
@Nonnull final String datasourceUrlPropertyName) {
return Binder.get(context.getEnvironment())
.bind(PROPERTY_NAME, String.class)
.bind(datasourceUrlPropertyName, String.class)
.orElse(null);
}

@Nonnull
private static String getDatasourceUrlPropertyName(@Nonnull final ConditionContext context) {
return Binder.get(context.getEnvironment())
.bind("pg.index.health.test.datasource-url-property-name", String.class)
.orElse(DatabaseStructureHealthProperties.STANDARD_DATASOURCE_URL_PROPERTY_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@
@Immutable
public class DatabaseStructureHealthProperties {

/**
* The standard name of the primary {@link javax.sql.DataSource} bean.
* <p>
* This constant is used as the default name when no custom datasource bean name
* is specified in the configuration.
* </p>
*/
public static final String STANDARD_DATASOURCE_BEAN_NAME = "dataSource";

/**
* The standard property name for the datasource URL in Spring configuration.
* <p>
* This constant is used as the default key for retrieving the datasource URL
* from the {@link org.springframework.core.env.Environment}.
* </p>
*/
public static final String STANDARD_DATASOURCE_URL_PROPERTY_NAME = "spring.datasource.url";

/**
* Indicates whether the starter is enabled, even if it is present on the classpath.
* This allows for manual control over autoconfiguration.
Expand Down Expand Up @@ -62,8 +80,8 @@ public class DatabaseStructureHealthProperties {
* @throws IllegalArgumentException if {@code datasourceBeanName} or {@code datasourceUrlPropertyName} is blank
*/
public DatabaseStructureHealthProperties(@DefaultValue("true") final boolean enabled,
@DefaultValue("dataSource") final String datasourceBeanName,
@DefaultValue("spring.datasource.url") final String datasourceUrlPropertyName) {
@DefaultValue(STANDARD_DATASOURCE_BEAN_NAME) final String datasourceBeanName,
@DefaultValue(STANDARD_DATASOURCE_URL_PROPERTY_NAME) final String datasourceUrlPropertyName) {
this.enabled = enabled;
this.datasourceBeanName = Validators.notBlank(datasourceBeanName, "datasourceBeanName");
this.datasourceUrlPropertyName = Validators.notBlank(datasourceUrlPropertyName, "datasourceUrlPropertyName");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javax.annotation.Nonnull;
import javax.sql.DataSource;

import static io.github.mfvanek.pg.spring.DatabaseStructureHealthProperties.STANDARD_DATASOURCE_BEAN_NAME;
import static org.assertj.core.api.Assertions.assertThat;

abstract class AutoConfigurationTestBase {
Expand Down Expand Up @@ -62,8 +63,13 @@ abstract class AutoConfigurationTestBase {
protected static final Class<?>[] EXPECTED_TYPES = {PgConnection.class, DatabaseCheckOnHost.class, StatisticsMaintenanceOnHost.class, ConfigurationMaintenanceOnHost.class};
protected static final DataSource DATA_SOURCE_MOCK = Mockito.mock(DataSource.class);

protected final Predicate<String> beanNamesFilter = b -> !b.startsWith("org.springframework") && !b.startsWith("pg.index.health.test") &&
!b.endsWith("AutoConfiguration") && !"dataSource".equals(b);
private static final String CUSTOM_DATASOURCE_BEAN_NAME = "customDataSource";

protected final Predicate<String> beanNamesFilter = b -> !b.startsWith("org.springframework") &&
!b.startsWith("pg.index.health.test") &&
!b.endsWith("AutoConfiguration") &&
!STANDARD_DATASOURCE_BEAN_NAME.equals(b) &&
!CUSTOM_DATASOURCE_BEAN_NAME.equals(b);
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();

@Nonnull
Expand All @@ -73,7 +79,12 @@ protected ApplicationContextRunner assertWithTestConfig() {

protected static <C extends ConfigurableApplicationContext> void initialize(@Nonnull final C applicationContext) {
final GenericApplicationContext context = (GenericApplicationContext) applicationContext;
context.registerBean("dataSource", DataSource.class, () -> DATA_SOURCE_MOCK);
context.registerBean(STANDARD_DATASOURCE_BEAN_NAME, DataSource.class, () -> DATA_SOURCE_MOCK);
}

protected static <C extends ConfigurableApplicationContext> void initializeCustom(@Nonnull final C applicationContext) {
final GenericApplicationContext context = (GenericApplicationContext) applicationContext;
context.registerBean(CUSTOM_DATASOURCE_BEAN_NAME, DataSource.class, () -> DATA_SOURCE_MOCK);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ void withDataSource() {
});
}

@Test
void withCustomDataSource() {
assertWithTestConfig()
.withPropertyValues("custom.datasource.url=jdbc:postgresql://localhost:5432",
"pg.index.health.test.datasource-bean-name=customDataSource",
"pg.index.health.test.datasource-url-property-name=custom.datasource.url")
.withInitializer(AutoConfigurationTestBase::initializeCustom)
.run(context -> {
assertThatBeansPresent(context);
assertThatBeansAreNotNullBean(context);
});
}

@Test
void withDataSourceButWithoutConnectionString() throws SQLException {
try (Connection connectionMock = Mockito.mock(Connection.class)) {
Expand All @@ -70,6 +83,23 @@ void withDataSourceButWithoutConnectionString() throws SQLException {
}
}

@Test
void withCustomDataSourceButWithoutConnectionString() throws SQLException {
try (Connection connectionMock = Mockito.mock(Connection.class)) {
setMocks(connectionMock);

assertWithTestConfig()
.withInitializer(AutoConfigurationTestBase::initializeCustom)
.withPropertyValues("pg.index.health.test.datasource-bean-name=customDataSource",
"pg.index.health.test.datasource-url-property-name=custom.datasource.url")
.run(context -> {
assertThatBeansPresent(context);
assertThatBeansAreNotNullBean(context);
assertThatPgConnectionIsValid(context);
});
}
}

@Test
void withDataSourceAndEmptyConnectionString() throws SQLException {
try (Connection connectionMock = Mockito.mock(Connection.class)) {
Expand All @@ -86,6 +116,24 @@ void withDataSourceAndEmptyConnectionString() throws SQLException {
}
}

@Test
void withCustomDataSourceAndEmptyConnectionString() throws SQLException {
try (Connection connectionMock = Mockito.mock(Connection.class)) {
setMocks(connectionMock);

assertWithTestConfig()
.withPropertyValues("custom.datasource.url=",
"pg.index.health.test.datasource-bean-name=customDataSource",
"pg.index.health.test.datasource-url-property-name=custom.datasource.url")
.withInitializer(AutoConfigurationTestBase::initializeCustom)
.run(context -> {
assertThatBeansPresent(context);
assertThatBeansAreNotNullBean(context);
assertThatPgConnectionIsValid(context);
});
}
}

@Test
void withDataSourceAndWrongConnectionString() {
assertWithTestConfig()
Expand All @@ -101,6 +149,23 @@ void withDataSourceAndWrongConnectionString() {
});
}

@Test
void withCustomDataSourceAndWrongConnectionString() {
assertWithTestConfig()
.withPropertyValues("custom.datasource.url=jdbc:mysql://localhost/test",
"pg.index.health.test.datasource-bean-name=customDataSource",
"pg.index.health.test.datasource-url-property-name=custom.datasource.url")
.withInitializer(AutoConfigurationTestBase::initializeCustom)
.run(context -> {
assertThat(context.getBeansOfType(DatabaseStructureHealthProperties.class))
.isEmpty();
assertThat(context.getBeanDefinitionNames())
.isNotEmpty()
.filteredOn(beanNamesFilter)
.isEmpty();
});
}

@Test
void withDataSourceAndTestcontainersConnectionString() throws SQLException {
try (Connection connectionMock = Mockito.mock(Connection.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,65 @@

import org.junit.jupiter.api.Test;

import static io.github.mfvanek.pg.spring.DatabaseStructureHealthProperties.STANDARD_DATASOURCE_BEAN_NAME;
import static io.github.mfvanek.pg.spring.DatabaseStructureHealthProperties.STANDARD_DATASOURCE_URL_PROPERTY_NAME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class DatabaseStructureHealthPropertiesTest {

@Test
void getterShouldWork() {
final DatabaseStructureHealthProperties propertiesEnabled = new DatabaseStructureHealthProperties(true);
void getterShouldWorkWhenEnabled() {
final DatabaseStructureHealthProperties propertiesEnabled =
new DatabaseStructureHealthProperties(true, STANDARD_DATASOURCE_BEAN_NAME, STANDARD_DATASOURCE_URL_PROPERTY_NAME);
assertThat(propertiesEnabled.isEnabled())
.isTrue();
assertThat(propertiesEnabled.getDatasourceBeanName())
.isEqualTo(STANDARD_DATASOURCE_BEAN_NAME);
assertThat(propertiesEnabled.getDatasourceUrlPropertyName())
.isEqualTo(STANDARD_DATASOURCE_URL_PROPERTY_NAME);
assertThat(propertiesEnabled)
.hasToString("DatabaseStructureHealthProperties{enabled=true}");
.hasToString("DatabaseStructureHealthProperties{enabled=true, datasourceBeanName='dataSource', datasourceUrlPropertyName='spring.datasource.url'}");
}

final DatabaseStructureHealthProperties propertiesDisabled = new DatabaseStructureHealthProperties(false);
@Test
void getterShouldWorkWhenDisabled() {
final DatabaseStructureHealthProperties propertiesDisabled =
new DatabaseStructureHealthProperties(false, "customDataSource", "custom.datasource.url");
assertThat(propertiesDisabled.isEnabled())
.isFalse();
assertThat(propertiesDisabled.getDatasourceBeanName())
.isEqualTo("customDataSource");
assertThat(propertiesDisabled.getDatasourceUrlPropertyName())
.isEqualTo("custom.datasource.url");
assertThat(propertiesDisabled)
.hasToString("DatabaseStructureHealthProperties{enabled=false}");
.hasToString("DatabaseStructureHealthProperties{enabled=false, datasourceBeanName='customDataSource', datasourceUrlPropertyName='custom.datasource.url'}");
}

@Test
void shouldThrowExceptionWhenInvalidArgumentsArePassed() {
assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, null, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("datasourceBeanName cannot be null");

assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, "", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("datasourceBeanName cannot be blank");

assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, " ", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("datasourceBeanName cannot be blank");

assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, "beanName", null))
.isInstanceOf(NullPointerException.class)
.hasMessage("datasourceUrlPropertyName cannot be null");

assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, "beanName", ""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("datasourceUrlPropertyName cannot be blank");

assertThatThrownBy(() -> new DatabaseStructureHealthProperties(true, "beanName", " "))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("datasourceUrlPropertyName cannot be blank");
}
}

0 comments on commit 383522b

Please sign in to comment.