DatabaseAuditTestConfiguration.java

package io.github.databaseaudits.spring.boot;

import javax.sql.DataSource;

import org.hibernate.cfg.JdbcSettings;
import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import io.github.databaseaudits.audit.catalog.ForeignKeyIndexAudit;
import io.github.databaseaudits.audit.catalog.ForeignKeyNotNullAudit;
import io.github.databaseaudits.audit.catalog.ForeignKeyTypeMatchAudit;
import io.github.databaseaudits.audit.catalog.PrimaryKeyPresenceAudit;
import io.github.databaseaudits.audit.catalog.RedundantIndexAudit;
import io.github.databaseaudits.audit.jpa.SchemaEntityValidationAudit;
import io.github.databaseaudits.audit.runtime.UnconditionalMutationAudit;
import io.github.databaseaudits.audit.runtime.plan.JoinIndexAudit;
import io.github.databaseaudits.audit.runtime.plan.OrderByIndexAudit;
import io.github.databaseaudits.audit.runtime.plan.WhereClauseIndexAudit;
import io.github.databaseaudits.capture.SqlCapturingStatementInspector;
import io.github.databaseaudits.catalog.IndexCatalog;
import io.github.databaseaudits.jdbc.CatalogQueries;
import io.github.databaseaudits.plan.QueryPlanExplainer;
import io.github.databaseaudits.platform.DatabasePlatform;
import io.github.databaseaudits.spring.boot.assertion.DatabaseAuditAssertions;
import io.github.databaseaudits.spring.boot.assertion.ForeignKeyIndexAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.ForeignKeyNotNullAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.ForeignKeyTypeMatchAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.JoinIndexAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.OrderByIndexAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.PrimaryKeyPresenceAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.RedundantIndexAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.SchemaEntityValidationAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.UnconditionalMutationAuditAssertion;
import io.github.databaseaudits.spring.boot.assertion.WhereClauseIndexAuditAssertion;
import jakarta.persistence.EntityManagerFactory;

/**
 * Wires the database-audit suite for a module's integration-test context. The
 * core module is dependency-injection-framework-neutral (its classes carry no
 * Spring annotations), so this configuration registers every audit, the
 * {@link QueryPlanExplainer}, and the {@link SqlCapturingStatementInspector} as
 * explicit beans, and registers the capturer as Hibernate's
 * {@code StatementInspector} so the runtime audits see every statement the
 * repositories run.
 *
 * <p>
 * A single {@code @Import(DatabaseAuditTestConfiguration.class)} on your
 * test-infrastructure base class is enough — no separate component-scan setup
 * required. (Assumes a JPA + Liquibase + DataSource context, which the DB
 * integration tests already boot.)
 *
 * <p>
 * The {@link DatabasePlatform} is auto-detected from the {@link DataSource}
 * metadata, so the audits run the catalog SQL matching the database under test.
 * The plan-based audits ({@link WhereClauseIndexAudit},
 * {@link OrderByIndexAudit}, {@link JoinIndexAudit}) are PostgreSQL-only and
 * fail fast on other platforms. For them the test {@link DataSource} must also
 * connect with {@code preferQueryMode=simple} (a PostgreSQL JDBC connection
 * property, e.g. appended to the JDBC URL): generic-plan EXPLAIN of a statement
 * containing {@code $n} placeholders only works over the simple query protocol
 * — see {@link QueryPlanExplainer}. Without it every parameterized statement is
 * skipped, and the plan audits then fail their vacuous-run guard.
 */
@TestConfiguration(proxyBeanMethods = false)
public class DatabaseAuditTestConfiguration {
    @Bean
    SqlCapturingStatementInspector sqlCapturingStatementInspector() {
        return new SqlCapturingStatementInspector();
    }

    @Bean
    HibernatePropertiesCustomizer sqlCaptureHibernatePropertiesCustomizer(
            final SqlCapturingStatementInspector sqlCapturer) {
        // Register the Spring bean itself, so the inspector and the injected
        // audits share one capture instance.
        return properties -> properties.put(JdbcSettings.STATEMENT_INSPECTOR,
                sqlCapturer);
    }

    @Bean
    DatabasePlatform databasePlatform(final DataSource dataSource) {
        return DatabasePlatform.fromDataSource(dataSource);
    }

    @Bean
    CatalogQueries jdbcSupport(final DataSource dataSource) {
        return new CatalogQueries(dataSource);
    }

    @Bean
    IndexCatalog indexCatalog(final CatalogQueries jdbcSupport,
            final DatabasePlatform platform) {
        return new IndexCatalog(jdbcSupport, platform);
    }

    /**
     * The explainer behind the plan-based audits. On PostgreSQL the injected
     * {@link DataSource} must connect with {@code preferQueryMode=simple} — see
     * the {@link QueryPlanExplainer} class contract.
     */
    @Bean
    QueryPlanExplainer queryPlanExplainer(final DataSource dataSource,
            final DatabasePlatform platform) {
        return new QueryPlanExplainer(dataSource, platform);
    }

    @Bean
    WhereClauseIndexAudit whereClauseIndexAudit(
            final QueryPlanExplainer queryPlanExplainer,
            final SqlCapturingStatementInspector sqlCapturer) {
        return new WhereClauseIndexAudit(queryPlanExplainer, sqlCapturer);
    }

    @Bean
    OrderByIndexAudit orderByIndexAudit(
            final QueryPlanExplainer queryPlanExplainer,
            final SqlCapturingStatementInspector sqlCapturer) {
        return new OrderByIndexAudit(queryPlanExplainer, sqlCapturer);
    }

    @Bean
    JoinIndexAudit joinIndexAudit(final QueryPlanExplainer queryPlanExplainer,
            final SqlCapturingStatementInspector sqlCapturer) {
        return new JoinIndexAudit(queryPlanExplainer, sqlCapturer);
    }

    @Bean
    UnconditionalMutationAudit unconditionalMutationAudit(
            final SqlCapturingStatementInspector sqlCapturer) {
        return new UnconditionalMutationAudit(sqlCapturer);
    }

    @Bean
    PrimaryKeyPresenceAudit primaryKeyPresenceAudit(
            final CatalogQueries jdbcSupport, final DatabasePlatform platform) {
        return new PrimaryKeyPresenceAudit(jdbcSupport, platform);
    }

    @Bean
    ForeignKeyIndexAudit foreignKeyIndexAudit(final CatalogQueries jdbcSupport,
            final IndexCatalog indexCatalog, final DatabasePlatform platform) {
        return new ForeignKeyIndexAudit(jdbcSupport, indexCatalog, platform);
    }

    @Bean
    ForeignKeyNotNullAudit foreignKeyNotNullAudit(
            final CatalogQueries jdbcSupport, final DatabasePlatform platform) {
        return new ForeignKeyNotNullAudit(jdbcSupport, platform);
    }

    @Bean
    ForeignKeyTypeMatchAudit foreignKeyTypeMatchAudit(
            final CatalogQueries jdbcSupport, final DatabasePlatform platform) {
        return new ForeignKeyTypeMatchAudit(jdbcSupport, platform);
    }

    @Bean
    RedundantIndexAudit redundantIndexAudit(final IndexCatalog indexCatalog) {
        return new RedundantIndexAudit(indexCatalog);
    }

    @Bean
    SchemaEntityValidationAudit schemaEntityValidationAudit(
            final EntityManagerFactory entityManagerFactory) {
        return new SchemaEntityValidationAudit(entityManagerFactory);
    }

    @Bean
    ForeignKeyIndexAuditAssertion foreignKeyIndexAuditAssertion(
            final ForeignKeyIndexAudit foreignKeyIndexAudit) {
        return new ForeignKeyIndexAuditAssertion(foreignKeyIndexAudit);
    }

    @Bean
    ForeignKeyNotNullAuditAssertion foreignKeyNotNullAuditAssertion(
            final ForeignKeyNotNullAudit foreignKeyNotNullAudit) {
        return new ForeignKeyNotNullAuditAssertion(foreignKeyNotNullAudit);
    }

    @Bean
    ForeignKeyTypeMatchAuditAssertion foreignKeyTypeMatchAuditAssertion(
            final ForeignKeyTypeMatchAudit foreignKeyTypeMatchAudit) {
        return new ForeignKeyTypeMatchAuditAssertion(foreignKeyTypeMatchAudit);
    }

    @Bean
    PrimaryKeyPresenceAuditAssertion primaryKeyPresenceAuditAssertion(
            final PrimaryKeyPresenceAudit primaryKeyPresenceAudit) {
        return new PrimaryKeyPresenceAuditAssertion(primaryKeyPresenceAudit);
    }

    @Bean
    RedundantIndexAuditAssertion redundantIndexAuditAssertion(
            final RedundantIndexAudit redundantIndexAudit) {
        return new RedundantIndexAuditAssertion(redundantIndexAudit);
    }

    @Bean
    SchemaEntityValidationAuditAssertion schemaEntityValidationAuditAssertion(
            final SchemaEntityValidationAudit schemaEntityValidationAudit) {
        return new SchemaEntityValidationAuditAssertion(
                schemaEntityValidationAudit);
    }

    @Bean
    JoinIndexAuditAssertion joinIndexAuditAssertion(
            final JoinIndexAudit joinIndexAudit) {
        return new JoinIndexAuditAssertion(joinIndexAudit);
    }

    @Bean
    OrderByIndexAuditAssertion orderByIndexAuditAssertion(
            final OrderByIndexAudit orderByIndexAudit) {
        return new OrderByIndexAuditAssertion(orderByIndexAudit);
    }

    @Bean
    WhereClauseIndexAuditAssertion whereClauseIndexAuditAssertion(
            final WhereClauseIndexAudit whereClauseIndexAudit) {
        return new WhereClauseIndexAuditAssertion(whereClauseIndexAudit);
    }

    @Bean
    UnconditionalMutationAuditAssertion unconditionalMutationAuditAssertion(
            final UnconditionalMutationAudit unconditionalMutationAudit) {
        return new UnconditionalMutationAuditAssertion(
                unconditionalMutationAudit);
    }

    @Bean
    DatabaseAuditAssertions databaseAuditAssertions(
            final ForeignKeyIndexAuditAssertion foreignKeyIndexAuditAssertion,
            final ForeignKeyNotNullAuditAssertion foreignKeyNotNullAuditAssertion,
            final ForeignKeyTypeMatchAuditAssertion foreignKeyTypeMatchAuditAssertion,
            final PrimaryKeyPresenceAuditAssertion primaryKeyPresenceAuditAssertion,
            final RedundantIndexAuditAssertion redundantIndexAuditAssertion,
            final SchemaEntityValidationAuditAssertion schemaEntityValidationAuditAssertion,
            final JoinIndexAuditAssertion joinIndexAuditAssertion,
            final OrderByIndexAuditAssertion orderByIndexAuditAssertion,
            final WhereClauseIndexAuditAssertion whereClauseIndexAuditAssertion,
            final UnconditionalMutationAuditAssertion unconditionalMutationAuditAssertion) {
        return new DatabaseAuditAssertions(foreignKeyIndexAuditAssertion,
                foreignKeyNotNullAuditAssertion,
                foreignKeyTypeMatchAuditAssertion,
                primaryKeyPresenceAuditAssertion, redundantIndexAuditAssertion,
                schemaEntityValidationAuditAssertion, joinIndexAuditAssertion,
                orderByIndexAuditAssertion, whereClauseIndexAuditAssertion,
                unconditionalMutationAuditAssertion);
    }
}