danhaywood/cfct

★ 0Forks 0JavaGitHub ↗Compare

README

cfct

cfct (Command Footprint Comparison Tool) is a webapp and CLI for comparing selected SQL Server tables between two databases, typically based on a set of Apache Causeway commands having been executed.

User guide for Fixture SQL Server

Use scripts/fixture-sqlserver.sh to manage a local SQL Server container for the fixture example. The script starts SQL Server 2022, creates left_db and right_db, and loads fixture data for the example tables. The script also supports invalid-target and missing-system-tables modes for manual login-validation failure testing.

  • To start the fixture:

    scripts/fixture-sqlserver.sh start
  • To check fixture status:

    scripts/fixture-sqlserver.sh status
  • To stop and remove the fixture:

    scripts/fixture-sqlserver.sh stop

By default the fixture listens on localhost:14333 and uses the container name cfct-fixture-sqlserver. Override the host port if needed:

CFCT_FIXTURE_PORT=14334 scripts/fixture-sqlserver.sh start

If you change the port, also update the env file used by the CLI or the webapp configuration.

To support manual testing of sad cases:

  • To start the fixture in invalid-target mode

    scripts/fixture-sqlserver.sh start --invalid-target-db

    In this mode, one of the specified databases is missing. Use CFCT_INVALID_TARGET_DATABASE to override the invalid target name used by --invalid-target-db.

  • to start the fixture with missing required target system objects (for manual login testing):

    scripts/fixture-sqlserver.sh start --missing-system-tables

    In this mode, the target database exists but required system tables/views are missing.

User guide for Webapp

Walkthrough

Webapp usage is as follows.

  • Login in a startup modal dialog with server, source database, target database, username, and password

    Webapp startup login modal (current walkthrough)

    Defaults are loaded from config props.

    Once logged in, the footer/status bar shows connection details and status.

  • After successful login, the main shell shows the initial table-selection view:

    Webapp table selection with footer status bar

  • Select commands in the navigation drawer, the corresponding business tables (representing the footprint) are selected. These can be fine-tuned as required.

    Note
    Tables that do not meet the _PK suffix requirement on a unique index or unique constraint are still shown, but their checkboxes are disabled and expose the eligibility reason as tooltip text.
  • Click compare (or hit enter); this triggers the comparison.

    During comparison, the footer/status bar also shows live table-by-table progress and a terminal completion or failure message. The Compare action bar shows a live N of M completion counter, and completed business-table rows are highlighted in the navigation grid as each table finishes. These visual cues are cleared automatically when Clear is pressed or when a new selection/filter workflow begins. If successful, the results are shown on the right hand side:

    Webapp table comparison results in tabs

    Tabs for tables with differences are color-highlighted, and a Diffs only checkbox (off by default) can hide unchanged tables. Differing values are shown on separate lines, with Excel-like status coloring.

    Downloads are provided through one unified Download action with a format selector for json, yaml, or excel (the selector defaults to json).

  • The navigation drawer can be collapsed, to provide more real estate.

    Webapp with collapsed navigation

  • Opening the account menu shows the logout action.

    Webapp account menu with logout action

  • After logout, the app returns to the login modal dialog.

    Webapp after logout returns to login modal (current walkthrough)

User Guide for CLI (cfct.sh)

cfct.sh is the root comparison wrapper script. It expects the CLI jar to already exist and then invokes the Java CLI with an env file.

The CFCT_CLI_JAR can be used to override the jar path; it defaults to cfct-cli/target/cfct-cli-0.0.1-SNAPSHOT.jar.

Connection

The target server and databases can be specified either using a .env file or directly. The .env file is usually easiest

.env file

Specify the .env file directly using:

d* -e / --env-file: path to a file containing SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD, CFCT_LEFT_DATABASE, and CFCT_RIGHT_DATABASE.

+ The demo/.env contains values to connect to the SQL Server fixture script:

+

SPRING_DATASOURCE_URL=jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.microsoft.sqlserver.jdbc.SQLServerDriver
SPRING_DATASOURCE_USERNAME=sa
SPRING_DATASOURCE_PASSWORD=Str0ng_password!123
CFCT_LEFT_DATABASE=left_db
CFCT_RIGHT_DATABASE=right_db

+ If not specified, will search for .env as per the CFCT_ENV_FILE env var, or in the current directory if not set.

Use .env.TEMPLATE as the starting point for a user-managed env file. Copy it to .env in the directory from which you run cfct.sh, or store it elsewhere and set CFCT_ENV_FILE. Do not commit real production credentials.

Direct options

Alternatively, the connection can be specified directly:

  • --jdbc-url: SQL Server JDBC URL, for example jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true.

  • --jdbc-driver: JDBC driver class name, for example com.microsoft.sqlserver.jdbc.SQLServerDriver.

  • -U / --username: SQL Server username.

  • -P / --password: SQL Server password.

  • -l / --left-database: left database name.

  • -r / --right-database: right database name.

Comparison Tables

The CLI provides two different ways to specify the tables to compare. The more powerful is to specify the command range:

  • --commands-from: inclusive command timestamp range start (ISO-8601 local date-time, for example 2026-05-01T10:00:00).

  • --commands-to: inclusive command timestamp range end (ISO-8601 local date-time, for example 2026-05-01T11:00:00).

From this the audit table and logical type/table mapping is used to determine the business tables impacted.

Alternatively, the tables can be specified directly:

  • -t / --tables: comma-separated schema.table list.

  • -F / --tables-file: path to a flat file with one schema.table reference per line.

    This file contains one table reference per line: If not specified, then the CFCT_TABLES_FILE env var is used as a fallback.

Do not mix explicit table and command-time-range options in the same invocation.

Output formats

The CLI supports these output options:

  • -f / --output-format: one of text, json, yaml, or excel.

    text is the default when --output-format is omitted.

  • -o / --output-file: optional output file path for successful output.

    Excel output requires -o, for example:

    `--output-format excel -o comparison.xlsx`.

Text, JSON, and YAML are written to stdout as UTF-8 when -o is omitted. When -o is supplied, successful output is written to that file instead.

CLI comparison progress is emitted to stderr as per-table progress lines so stdout or output files remain reserved for comparison artifacts.

Examples

These assume that the jar has been built, and the fixture started.

  • Specify tables directly:

    ./cfct.sh --env-file demo/.env --tables-file demo/tables.txt

    where demo/tables.txt contains one table reference per line:

    dbo.Supplier
    dbo.Product
    dbo.CustomerAddress
    dbo.PurchaseOrder
  • Equivalent usage with environment overrides:

    CFCT_ENV_FILE=demo/.env \
    CFCT_TABLES_FILE=demo/tables.txt \
    ./cfct.sh

    This overrides the JDBC URL value from the env file:

  • Override the JDBC URL:

    ./cfct.sh --env-file demo/.env --tables-file demo/tables.txt --jdbc-url "jdbc:sqlserver://localhost:14334;encrypt=false;trustServerCertificate=true"
  • Writes JSON output instead of the default text output:

./cfct.sh --env-file demo/.env --tables-file demo/tables.txt --output-format json

+ Equivalent short-flag example:

+

./cfct.sh --env-file demo/.env --tables-file demo/tables.txt -f json
  • Writes JSON output to a file:

    ./cfct.sh --env-file demo/.env --tables-file demo/tables.txt --output-format json -o comparison.json

    The JSON report uses hasDifferences as the top-level summary flag. Detailed table result objects appear under differingTables for compared tables that have missing rows or differing rows. The comparedTables array lists every table identity that was compared, including clean tables that are omitted from differingTables. An empty successful comparison is represented as hasDifferences: false, differingTables: [], and comparedTables: [].

  • Writes YAML output instead of the default text output:

    ./cfct.sh --env-file demo/.env --tables-file demo/tables.txt --output-format yaml
  • Writes Excel output to a workbook file:

    ./cfct.sh --env-file demo/.env --tables-file demo/tables.txt --output-format excel -o comparison.xlsx
  • Example command-time-range invocation:

    ./cfct.sh \
      --env-file demo/.env \
      --commands-from 2026-05-01T10:00:00 \
      --commands-to 2026-05-01T11:00:00 \
      --output-format json
  • Instead of using the cfct.sh script, you can run the CLI jar directly:

java -jar cfct-cli/target/cfct-cli-0.0.1-SNAPSHOT.jar \
  --jdbc-url "jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true" \
  --jdbc-driver com.microsoft.sqlserver.jdbc.SQLServerDriver \
  -U sa \
  -P 'Str0ng_password!123' \
  -l left_db \
  -r right_db \
  -t dbo.Supplier,dbo.Product,dbo.PurchaseOrder \
  --output-format text

Deployment Guide

Before first real-world use, ensure the target database provides these required objects as either tables or views:

  • causewayExtCommandLog.CommandLogEntry

  • causewayExtAuditTrail.AuditTrailEntry

  • util.LogicalTypeTableMapping

Expected structure for these objects is:

Table 1. causewayExtCommandLog.CommandLogEntry
Column SQL Server type

interactionId

UNIQUEIDENTIFIER (PK)

executeIn

VARCHAR(10)

logicalMemberIdentifier

VARCHAR(255)

timestamp

DATETIME2

target

VARCHAR(1500)

replayState

VARCHAR(20)

Table 2. causewayExtAuditTrail.AuditTrailEntry
Column SQL Server type

interactionId

UNIQUEIDENTIFIER
(FK to CommandLogEntry.interactionId)

sequence

INT

target

VARCHAR(1500)

propertyId

VARCHAR(100)

CFCT treats propertyId as the canonical audited member identifier when comparing audit structure. Installations backed by legacy audit tables can expose their equivalent memberIdentifier column as propertyId through the compatibility view. The comparison deliberately ignores generated audit identity, timestamp, transaction sequence, and value columns.

Table 3. util.LogicalTypeTableMapping
Column SQL Server type

logicalTypeName

NVARCHAR(255)

qualifiedName

NVARCHAR(255)

For business tables you compare, CFCT requires a unique index or unique constraint whose name ends with _PK. CFCT uses this _PK-suffixed object to resolve business row identity during comparison.

Configuration reference (application.yml)

The webapp reads defaults from cfct.webapp.* properties. Use this as a deployer reference for supported keys:

application.yml
spring:
  datasource:
    url: jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    username: sa
    password: change-me
cfct:
  webapp:
    comparison:
      left-database: left_db
      right-database: right_db
      env-file: .env
      output:
        format: text
        file:
      validation:
        enabled: true
        fail-fast: true

Configuration property reference:

Property Default Description

spring.datasource.url

jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true

SQL Server JDBC URL shown as the default login endpoint.

spring.datasource.username

sa

Default login username shown in the webapp login form.

spring.datasource.password

change-me

Default login password shown in the webapp login form.

cfct.webapp.connection.left-database

left_db

Default source database name used for the left-hand comparison side.

cfct.webapp.connection.right-database

right_db

Default target database name used for the right-hand comparison side.

cfct.webapp.validation.enabled

true

Enables login-time connectivity and required-object validation.

cfct.webapp.validation.fail-fast

true

Stops login flow immediately when validation fails.

cfct.sql.trace.enabled

false

Enables datasource-proxy SQL statement logging for CLI and webapp datasource usage.

Migration note for older configs:

  • cfct.webapp.comparison.connection.left-database → cfct.webapp.connection.left-database

  • cfct.webapp.comparison.connection.right-database → cfct.webapp.connection.right-database

  • cfct.webapp.comparison.validation.enabled → cfct.webapp.validation.enabled

  • cfct.webapp.comparison.validation.fail-fast → cfct.webapp.validation.fail-fast

  • cfct.webapp.comparison.env-file removed (CLI concern)

  • cfct.webapp.comparison.output.format removed (CLI concern)

  • cfct.webapp.comparison.output.file removed (CLI concern)

Happy-path connectivity check with the demo fixture SQL Server:

  1. Start the fixture SQL Server container and load demo data.

    scripts/fixture-sqlserver.sh start
  2. Start the webapp with fixture credentials and fixture databases.

    mvn -pl cfct-webapp -am spring-boot:run \
      -Dspring-boot.run.arguments="--spring.datasource.url=jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true --spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver --spring.datasource.username=sa --spring.datasource.password=Str0ng_password!123 --cfct.webapp.connection.left-database=left_db --cfct.webapp.connection.right-database=right_db"

    Or use the one-liner helper script that starts the fixture and runs the webapp with the same happy-path settings:

    scripts/check-webapp-happy-path.sh
  3. Open http://localhost:8080 and click Login (or edit defaults first, then login).

  4. Confirm successful startup by checking that the app reports successful startup.

  5. Optional manual negative-path check for invalid target database.

    scripts/fixture-sqlserver.sh restart --invalid-target-db

    Login should fail with a clear validation message.

  6. Optional manual negative-path check for missing required target system objects.

    scripts/fixture-sqlserver.sh restart --missing-system-tables

    In this mode, the target database exists but required system objects are removed. Login should fail with a clear missing-system-objects validation message.

  7. Stop the fixture when done.

    scripts/fixture-sqlserver.sh stop

The webapp reads login defaults and validation defaults from cfct-webapp/src/main/resources/application.yml using cfct.webapp.connection. and cfct.webapp.validation. keys. The webapp binds both spring.datasource. and cfct.webapp. keys through typed @ConfigurationProperties models.

These keys map to CLI concepts as follows:

  • spring.datasource.url ↔ --jdbc-url

  • spring.datasource.driver-class-name ↔ --jdbc-driver

  • spring.datasource.username ↔ -U / --username

  • spring.datasource.password ↔ -P / --password

  • cfct.webapp.connection.left-database ↔ -l / --left-database

  • cfct.webapp.connection.right-database ↔ -r / --right-database

  • cfct.webapp.validation.enabled ↔ login-time validation switch

  • cfct.webapp.validation.fail-fast ↔ login failure behavior

Ignore-column advisor toggles are also typesafe configuration properties. All default to true.

  • cfct.comparison.ignore-column-advisors.identity-enabled

  • cfct.comparison.ignore-column-advisors.uuid-enabled

  • cfct.comparison.ignore-column-advisors.timestamps-enabled

  • cfct.comparison.ignore-column-advisors.extended-properties-enabled

For extended-property based ignores, set a column-level SQL Server extended property named cfct.ignored. Truthy values are interpreted case-insensitively and include: true, 1, yes, y, on. The local customer-address fixture demonstrates this by marking dbo.CustomerAddress.postcode as cfct.ignored via sp_addextendedproperty.

For extended-property based value scrubbing, set a column-level SQL Server extended property named cfct.normalizeMask. The value is a date/time-like mask such as yyyy-MM-ddThh:MM.ss.SSS. Matching fragments are replaced with the mask literal before client-side row-difference emission. This preserves non-masked text while suppressing timestamp-only noise.

Developer guide

The tool consistss of a reusable comparison API, an implementation module, a Spring Boot CLI, a Vaadin webapp scaffold, a root comparison wrapper script, and a Docker-backed integration-test fixture.

Project layout

  • cfct.sh: root comparison wrapper script for running the CLI with env-file and table-selection inputs.

  • .env.TEMPLATE: template for user-managed connection configuration.

  • cfct-api: public comparison contracts, result models, exceptions, and service interfaces.

  • cfct-impl: comparison services, SQL Server readers, JSON request loading, and report renderers.

  • cfct-cli: Spring Boot CLI application and executable jar packaging.

  • cfct-webapp: Spring Boot + Vaadin Flow web application scaffold with typed configuration properties.

  • cfct-integration-tests: SQL Server Testcontainers harness, fixture SQL, approval files, and integration tests.

  • demo/: committed fixture example files for local comparison runs.

  • scripts/: local helper scripts for the fixture SQL Server.

Module responsibilities

cfct-cli and cfct-webapp consume comparison orchestration through API interfaces from cfct-api. These entry-point modules only reference cfct-impl for explicit Spring wiring import (ComparisonImplementationConfiguration). Direct references to non-configuration classes in cfct-impl are intentionally disallowed and covered by architecture tests.

The webapp uses Spring-managed DataSource beans for SQL Server access at its service boundaries. Connectivity validation, table discovery, and webapp comparison execution acquire short-lived JDBC connections from these DataSources and close them within the service method.

Naming conventions

For classes that implement an interface, implementation names follow interface-first convention: <Interface><Qualifier>. Examples include CliComparisonExecutorSqlServer, TableMetadataReaderSqlServer, and TableRowReaderSqlServer.

Comparison

Default comparison behavior discovers business-key objects (unique indexes or unique constraints) using the _PK suffix. By default, technical identifier columns are ignored for value comparison, including identity-backed columns, columns named guid or uuid, and SQL Server UNIQUEIDENTIFIER columns. For SQL Server-backed runs on the same server instance, CFCT executes one database-side diff query per table and returns only left-only, right-only, and value-different rows.

Generated SQL is intentionally compatibility-level-100-safe and avoids post-2008 syntax such as OFFSET/FETCH, IIF, TRY_CONVERT, CONCAT, and STRING_AGG. Set cfct.sql.trace.enabled=true in Spring configuration to emit SQL statements before execution for verification and troubleshooting.

Query plan and indexing expectations

Add or verify a unique index or unique constraint ending in _PK on every compared business table. Ensure _PK key columns are selective and indexed so key joins and EXCEPT branches can seek instead of scanning. For large tables, keep compared columns narrow where possible because wider compared projections increase sort and memory pressure in SQL Server execution plans. If a table is frequently compared, monitor execution plans for hash/sort spills and add supporting nonclustered indexes that cover the _PK keys plus high-churn compared columns.

Ignored columns

The IgnoreColumnAdvisor SPI allows columns are ignored by default; there are four default implementations:

  • identity

  • UUID

  • timestamps column

  • based on the extended property cfct.ignored being set to a truthy value:

These can each of which can be enabled/disabled if required (enabled by default):

cfct.comparison.ignore-column-advisors.identityEnabled=true|false
cfct.comparison.ignore-column-advisors.uuidEnabled=true|false
cfct.comparison.ignore-column-advisors.timestampsEnabled=true|false
cfct.comparison.ignore-column-advisors.extendedPropertiesEnabled=true|false

Testing conventions

Use AssertJ for fluent assertions in harness tests. Use JUnit 5 parameterized tests with @EnumSource when the same behavior must be checked across the left and right logical databases or similar modes. Use Approvals for stable textual, JSON, Excel, or tabular outputs when characterization-style verification is clearer than many small assertions.

Scope guardrail

The fixture scripts and integration harness are intentionally narrow. They own local/example SQL Server lifecycle, logical database creation, fixture initialization, and smoke-test setup. They do not implement comparison logic, reporting logic, or a broader database support matrix.

Prerequisites

  • Java 21 or later.

  • Maven 3.9 or later.

  • Docker, for the fixture SQL Server and integration tests.

  • Enough local resources and startup time for the mcr.microsoft.com/mssql/server:2022-latest container.

SQL Server container startup can be slow, especially on Apple Silicon where emulation may be involved. The committed fixture credentials are examples for the local fixture only and are not production-safe. Do not reuse them for real systems.

Build and test

Run the non-integration test suite from the repository root:

mvn test

Run the full build, including SQL Server integration tests, from a Docker-enabled environment:

mvn verify

Build the CLI jar and its required reactor modules before using cfct.sh:

mvn -pl cfct-cli -am package

cfct.sh does not build or rebuild jars automatically. If the CLI jar is missing, it prints the build command and exits.

Run the webapp scaffold locally from the repository root:

mvn -pl cfct-webapp -am spring-boot:run

Build and run the layered webapp Docker image from the repository root:

mvn -pl cfct-webapp -am package -DskipTests
jar tf cfct-webapp/target/cfct-webapp-0.0.1-SNAPSHOT.jar | grep 'META-INF/VAADIN/webapp/index.html'
docker build -f cfct-webapp/Dockerfile -t cfct-webapp:local .
docker run --rm -p 8080:8080 cfct-webapp:local

This container build requires Docker and uses Spring Boot layertools layer metadata from the packaged webapp jar. The Maven package step runs the Vaadin production frontend build and packages index.html into the webapp jar. The jar tf check confirms the production index.html is present before image assembly. The webapp is reachable at http://localhost:8080 when the container is running.

If browser access shows a whitelabel error and logs include Unable to find index.html, rebuild the jar with Maven package and recheck the classpath entry above before rebuilding the image.

Automation REST API

The webapp can expose a Basic-authenticated automation API for non-interactive JSON refresh-and-download workflows. Enable the API and configure credentials and comparison inputs with Spring properties:

cfct.webapp.automation.enabled=true
cfct.webapp.automation.username=robot
cfct.webapp.automation.password=secret
cfct.webapp.automation.left-database=left_db
cfct.webapp.automation.right-database=right_db

The automation endpoints use the same JDBC URL, JDBC driver, username, and password as the webapp datasource configuration. If automation-specific left or right database values are omitted, the endpoints fall back to cfct.webapp.connection.left-database and cfct.webapp.connection.right-database. The automation download dynamically selects the newest successful foreground command present on both database sides. It recursively discovers that foreground command’s background descendants through parentInteractionId independently on each side, so generated child interaction IDs do not need to match. It resolves touched eligible business tables for the foreground command and every completed descendant on each side using the same command-driven table-selection logic as the Vaadin UI. It compares the union of the two resolved table sets and returns the refreshed JSON in the same response. The union contains the independently resolved foreground and completed-background footprints from both sides; CFCT does not intersect the two audit-derived footprints. It also compares audit structure independently for the shared foreground interaction and for each side’s completed descendants. Audit records are grouped by (target, propertyId) and compared by occurrence count, so differing background child identifiers and transaction sequences do not affect equality. This first audit comparison phase does not compare postValue; value masking, timestamp tolerance, and large-value policies are deferred. The response additionally includes an independent top-level executionTiming object with app-a and app-b observations for the shared foreground command and each side’s terminal background descendants. Each observation contains side, scope, side-local interaction identity, parent identity where available, logical member identifier, replay status, startedAt, completedAt, and nullable durationMillis. durationMillis is calculated only from that command’s own start and completion timestamps, so nominal replay-clock jumps between commands do not inflate execution duration. Missing timestamps or a completion before start produce a null duration rather than zero. Timing observations deliberately omit target bookmarks and target object identifiers. Background observations remain side-local and are deterministically ordered; their interaction identifiers support deduplication but are not cross-side correlation keys. Timing facts do not change business hasDifferences, audit hasDifferences, background status, or the HTTP outcome. A background command is pending when its replay state is PENDING or it has no completedAt value, failed when its replay state is FAILED, and otherwise completed when completedAt is populated. If the selected foreground group does not resolve any eligible touched business tables, the endpoint still succeeds and returns an empty comparison JSON document with hasDifferences: false, differingTables: [], and comparedTables: []. If no completed successful foreground command is common to both sides, the endpoint returns 200 OK with status: no_completed_foreground.

Refresh and download the automation JSON result with Basic Auth:

curl -u robot:secret -OJ http://localhost:8080/api/automation/comparison.json

A failed refresh/download reports an error response instead of returning a cached previous result.

Manual regression test with fixture SQL Server

Start or restart the fixture SQL Server from the repository root:

scripts/fixture-sqlserver.sh restart

The fixture defaults are localhost:14333, username sa, password Str0ng_password!123, source database left_db, and target database right_db. Check fixture status if startup is slow:

scripts/fixture-sqlserver.sh status

Start the webapp with Java 21 and automation settings:

export JAVA_HOME="$HOME/.sdkman/candidates/java/21.0.10-tem"

mvn -pl cfct-webapp -am spring-boot:run \
  -Dspring-boot.run.arguments="\
--spring.datasource.url=jdbc:sqlserver://localhost:14333;encrypt=false;trustServerCertificate=true \
--spring.datasource.username=sa \
--spring.datasource.password=Str0ng_password!123 \
--cfct.webapp.connection.left-database=left_db \
--cfct.webapp.connection.right-database=right_db \
--cfct.webapp.automation.enabled=true \
--cfct.webapp.automation.username=robot \
--cfct.webapp.automation.password=secret \
--cfct.webapp.automation.left-database=left_db \
--cfct.webapp.automation.right-database=right_db"

Verify unauthenticated download is rejected:

curl -i http://localhost:8080/api/automation/comparison.json

The expected response is 401 Unauthorized with a WWW-Authenticate: Basic realm="CFCT Automation" header. Verify invalid Basic Auth is rejected:

curl -i -u robot:wrong http://localhost:8080/api/automation/comparison.json

The expected response is 401 Unauthorized. Refresh and download the latest JSON comparison:

curl -i -u robot:secret http://localhost:8080/api/automation/comparison.json

The expected response is 200 OK, Content-Type: application/json, and a Content-Disposition filename beginning with comparison-. Save and inspect the artifact:

curl -u robot:secret -OJ http://localhost:8080/api/automation/comparison.json
jq . comparison-*.json | head -80

The saved artifact should be valid JSON in the same deterministic comparison format used by existing JSON downloads. The JSON preserves hasDifferences, differingTables, and comparedTables, keeps the selected foreground command in the top-level command object, and adds backgroundCommands and auditTrailComparison. Top-level hasDifferences continues to describe business-table divergence only. The independent auditTrailComparison.hasDifferences field describes audit-structure divergence and identifies its mode as semantic-key-counts. Its foreground and background objects contain appACount, appBCount, and deterministic differences entries with target, memberIdentifier, appACount, and appBCount. Audit records whose member identifier is exactly objectVersion are excluded from semantic-key comparison and from both scope totals because persistence implementations do not update that technical member consistently. The backgroundCommands object contains aggregate pending, completed, and failed counts plus separate appA and appB command lists with interaction ID, parent ID, member, replay state, execution mode, timestamps, and classified status. Its tableFootprint object lists eligible tables attributable to completed background descendants under appA and appB, together with their deduplicated union. Each table identity contains schema and name, and the same union tables participate in the normal comparison request. Run a second refresh-and-download request to confirm the filename timestamp updates. For failure regression, stop the fixture while the webapp remains running:

scripts/fixture-sqlserver.sh stop
curl -i -u robot:secret http://localhost:8080/api/automation/comparison.json

The expected response is 500 Internal Server Error with a JSON body whose status is failed. Stop the webapp with Ctrl+C and stop the fixture when done:

scripts/fixture-sqlserver.sh stop

Smoke Tests

  • Run webapp connectivity-validation tests (Docker required):

    scripts/test-webapp-connectivity-validation.sh
  • Run headless Playwright browser tests for login + connectivity status flows (Docker required):

    mvn -pl cfct-webapp -am \
      -Dplaywright=true \
      -Dtest=HomePageConnectionStatusPlaywrightSuccessTest,HomePageConnectionStatusPlaywrightFailureTest \
      -Dsurefire.failIfNoSpecifiedTests=false \
      test

    Playwright tests are headless and use Testcontainers-backed SQL Server settings to keep runs reproducible in local and CI environments. Reusable Playwright page objects now live in the dedicated cfct-webapp-playwright-page-objects Maven module and are consumed by the webapp browser tests.

  • Run layered webapp Docker image smoke test (Docker required):

    scripts/test-webapp-layered-image.sh

    This smoke test builds the webapp jar, validates layertools metadata, builds the layered Docker image, and checks that the container becomes reachable.

Docker Hub publishing with GitHub Actions

The repository includes .github/workflows/dockerhub-publish.yml to build and push cfct-webapp images to Docker Hub. The workflow runs on pushes to main, pushes of tags matching v*, and manual workflow_dispatch runs. Image tags are generated from branch or tag refs and include an immutable sha-<commit> tag for traceability.

Configure the following required GitHub repository secrets before enabling publish runs.

  • DOCKERHUB_USERNAME: Docker Hub account or organization robot username with permission to push to the target repository.

  • DOCKERHUB_TOKEN: Docker Hub access token with read and write scope for the target repository.

Optional workflow_dispatch inputs allow maintainers to tune publication behavior.

  • image_repository: Docker Hub repository in namespace/name form. If omitted, the workflow defaults to ${github.repository_owner}/cfct-webapp.

  • extra_tags: newline-separated extra docker/metadata-action tag directives. If omitted, only the default branch, release-tag, latest (default branch), and sha-* tags are pushed.

Maintainer setup checklist:

  1. Create or choose a Docker Hub repository for the image.

  2. Add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN in GitHub repository settings under Secrets and variables → Actions.

  3. Run the workflow manually once from the Actions tab and optionally provide image_repository.

  4. Confirm the expected tags are present in Docker Hub after the run completes.

Running the published Docker Hub image

After an image is published, pull and run it from Docker Hub. Replace <namespace> and <tag> with your repository namespace and desired tag (latest, v*, or sha-*).

docker pull <namespace>/cfct-webapp:<tag>
docker run --rm -p 8080:8080 <namespace>/cfct-webapp:<tag>

The webapp is reachable at http://localhost:8080.

Overriding defaults with a mounted application.yml

The container can load an external Spring Boot config file mounted at /config/application.yml. Spring Boot automatically checks /config in the container config search locations.

docker run --rm -p 8080:8080 \
  -v "$(pwd)/application.yml:/config/application.yml:ro" \
  <namespace>/cfct-webapp:<tag>

You can also make the config location explicit with SPRING_CONFIG_ADDITIONAL_LOCATION.

docker run --rm -p 8080:8080 \
  -e SPRING_CONFIG_ADDITIONAL_LOCATION=file:/config/ \
  -v "$(pwd)/application.yml:/config/application.yml:ro" \
  <namespace>/cfct-webapp:<tag>

This is useful for overriding default values:

application.yml
spring:
  datasource:
    url: jdbc:sqlserver://xxx;encrypt=true;trustServerCertificate=false
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    username: xxx
    password: xxx
cfct:
  webapp:
    comparison:
      left-database: xxx
      right-database: xxx

The webapp uses a login-first flow. SQL connectivity, database existence, and required target-system object presence are validated when the user submits the login form. Required target-system objects may be either tables or views. The login form is pre-populated from spring.datasource.* plus cfct.webapp.connection.left-database and cfct.webapp.connection.right-database configuration values, and every field remains editable.

Contributors

danhaywood

Issues