DatabaseAuditAssertions.java

package io.github.databaseaudits.spring.boot.assertion;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * Convenience facade that runs whole audit families in one call. Each family
 * method runs every audit in the family and, if any find violations, throws a
 * single {@link AssertionError} aggregating all of their messages (rather than
 * failing fast on the first). Genuine cannot-run conditions — the core audits'
 * {@link IllegalStateException} (vacuous capture, unsupported platform) —
 * propagate immediately. For per-audit granularity, inject the individual
 * {@code *AuditAssertion} beans instead.
 *
 * <p>
 * The facade drives the audits through the family-agnostic
 * {@link AuditAssertion} contract, so it iterates the suite's roster rather than
 * enumerating each audit — adding an audit needs no change here.
 */
public class DatabaseAuditAssertions {
    private final List<AuditAssertion> assertions;

    /**
     * Constructs the facade from the suite's audit assertions.
     *
     * @param assertions
     *                       every audit assertion to run, in the order the suite
     *                       wires them.
     */
    public DatabaseAuditAssertions(final List<AuditAssertion> assertions) {
        this.assertions = List.copyOf(assertions);
    }

    /**
     * Asserts that the catalog audits are clean for the schema, with no
     * exclusions.
     *
     * @param schema
     *                   the schema to audit.
     */
    public void assertCatalogClean(final String schema) {
        assertCatalogClean(schema, DatabaseAuditExcludes.none());
    }

    /**
     * Asserts that the catalog audits are clean for the schema, ignoring the
     * given exclusions (the Liquibase bookkeeping tables are always excluded
     * from the primary-key audit).
     *
     * @param schema
     *                     the schema to audit.
     * @param excludes
     *                     the exclusions to apply.
     */
    public void assertCatalogClean(final String schema,
            final DatabaseAuditExcludes excludes) {
        assertFamily(AuditFamily.CATALOG, new AuditScope(schema, excludes));
    }

    /**
     * Asserts that the JPA mapping audit is clean.
     */
    public void assertJpaClean() {
        assertFamily(AuditFamily.JPA,
                new AuditScope(null, DatabaseAuditExcludes.none()));
    }

    /**
     * Asserts that the runtime SQL audits are clean for the SQL captured so
     * far, with no exclusions.
     */
    public void assertRuntimeClean() {
        assertRuntimeClean(DatabaseAuditExcludes.none());
    }

    /**
     * Asserts that the runtime SQL audits are clean for the SQL captured so
     * far, ignoring the given exclusions.
     *
     * @param excludes
     *                     the exclusions to apply.
     */
    public void assertRuntimeClean(final DatabaseAuditExcludes excludes) {
        assertFamily(AuditFamily.RUNTIME, new AuditScope(null, excludes));
    }

    /**
     * Asserts that every audit family is clean, with no exclusions.
     *
     * @param schema
     *                   the schema to audit.
     */
    public void assertAllClean(final String schema) {
        assertAllClean(schema, DatabaseAuditExcludes.none());
    }

    /**
     * Asserts that every audit family is clean, ignoring the given exclusions;
     * failures across all families are aggregated into one error.
     *
     * @param schema
     *                     the schema to audit.
     * @param excludes
     *                     the exclusions to apply.
     */
    public void assertAllClean(final String schema,
            final DatabaseAuditExcludes excludes) {
        assertMatching(assertion -> true, new AuditScope(schema, excludes));
    }

    private void assertFamily(final AuditFamily family,
            final AuditScope scope) {
        assertMatching(assertion -> assertion.family() == family, scope);
    }

    private void assertMatching(final Predicate<AuditAssertion> selector,
            final AuditScope scope) {
        final List<String> failures = new ArrayList<>();
        for (final AuditAssertion assertion : assertions) {
            if (!selector.test(assertion)) {
                continue;
            }
            try {
                assertion.assertClean(scope);
            } catch (final AssertionError failure) {
                failures.add(failure.getMessage());
            }
        }
        if (!failures.isEmpty()) {
            final String separator =
                    System.lineSeparator() + System.lineSeparator();
            throw new AssertionError(String.join(separator, failures));
        }
    }
}