From 68fc32fda01b21068080d5ff5cb3156e59e515cd Mon Sep 17 00:00:00 2001 From: TriplEight Date: Sun, 22 Mar 2026 16:43:37 +0100 Subject: [PATCH] ci: add integration test workflow with live PostgreSQL and MySQL Two jobs - one per db - each spinning up a service container: 1. diesel migration run --migration-dir migrations/ Verifies every migration applies cleanly to a fresh instance. This is the primary gap: a broken migration would otherwise only surface on production deployment. 2. cargo test --features with DATABASE_URL set Builds and runs the test suite against the live engine. Existing tests are unit-level (no DB access), but DATABASE_URL is wired in so any future integration tests work without further infrastructure changes. Service containers: postgres:16, mysql:8.4 (utf8mb4). diesel CLI binary is cached keyed on Cargo.lock hash to avoid recompiling it on every run. Triggers on the same path set as build.yml (src/**, migrations/**, Cargo.*, rust-toolchain.toml). --- .github/workflows/integration-test.yml | 167 ++++++++++++++++++ .../up.sql | 2 +- .../down.sql | 7 + .../up.sql | 10 ++ 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/integration-test.yml create mode 100644 migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/down.sql create mode 100644 migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/up.sql diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000..a0ce17fd --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,167 @@ +name: Integration Tests +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + push: + paths: + - ".github/workflows/integration-test.yml" + - "src/**" + - "migrations/**" + - "Cargo.*" + - "build.rs" + - "rust-toolchain.toml" + + pull_request: + paths: + - ".github/workflows/integration-test.yml" + - "src/**" + - "migrations/**" + - "Cargo.*" + - "build.rs" + - "rust-toolchain.toml" + +defaults: + run: + shell: bash + +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + # ============================================================ + # PostgreSQL integration test + # ============================================================ + postgresql: + name: Integration test (PostgreSQL) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: vaultwarden + POSTGRES_PASSWORD: vaultwarden + POSTGRES_DB: vaultwarden + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U vaultwarden" + --health-interval 5s + --health-timeout 5s + --health-retries 15 + + env: + DATABASE_URL: "postgresql://vaultwarden:vaultwarden@localhost:5432/vaultwarden" + + steps: + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libpq-dev pkg-config + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Rust cache + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + prefix-key: "v2025.09-rust-integration-pg" + + # Cache the diesel CLI binary to avoid recompiling it on every run. + # Key includes the Cargo.lock hash so it is invalidated when diesel is upgraded. + - name: Cache diesel CLI (postgresql) + id: cache-diesel-pg + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.2 + with: + path: ~/.cargo/bin/diesel + key: diesel-cli-postgres-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} + + - name: Install diesel CLI (postgresql) + if: steps.cache-diesel-pg.outputs.cache-hit != 'true' + run: cargo install diesel_cli --no-default-features --features postgres + + # Verify that all PostgreSQL migrations apply cleanly to a fresh database. + # This catches broken SQL before it reaches a production deployment. + # DIESEL_CONFIG_FILE=/dev/null disables the [print_schema] setting from + # diesel.toml so that schema.rs is not overwritten during the check. + - name: Run PostgreSQL migrations + env: + DIESEL_CONFIG_FILE: /dev/null + run: diesel migration run --migration-dir migrations/postgresql + + # Run the test suite with a live PostgreSQL instance available. + # Existing tests are unit tests (no DB access), but DATABASE_URL is set + # so any future integration tests can connect without further setup. + - name: Run tests (postgresql) + run: cargo test --profile ci --features postgresql + + # ============================================================ + # MySQL integration test + # ============================================================ + mysql: + name: Integration test (MySQL) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + services: + mysql: + image: mysql:8.4 + env: + MYSQL_USER: vaultwarden + MYSQL_PASSWORD: vaultwarden + MYSQL_DATABASE: vaultwarden + MYSQL_ROOT_PASSWORD: root + MYSQL_CHARACTER_SET_SERVER: utf8mb4 + MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci + ports: + - 3306:3306 + options: >- + --health-cmd "mysqladmin ping -h localhost -uvaultwarden -pvaultwarden --silent" + --health-interval 5s + --health-timeout 5s + --health-retries 15 + + env: + DATABASE_URL: "mysql://vaultwarden:vaultwarden@127.0.0.1:3306/vaultwarden" + + steps: + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libmariadb-dev-compat pkg-config + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Rust cache + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + prefix-key: "v2025.09-rust-integration-mysql" + + # Cache the diesel CLI binary to avoid recompiling it on every run. + - name: Cache diesel CLI (mysql) + id: cache-diesel-mysql + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.2 + with: + path: ~/.cargo/bin/diesel + key: diesel-cli-mysql-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} + + - name: Install diesel CLI (mysql) + if: steps.cache-diesel-mysql.outputs.cache-hit != 'true' + run: cargo install diesel_cli --no-default-features --features mysql + + # Verify that all MySQL migrations apply cleanly to a fresh database. + # DIESEL_CONFIG_FILE=/dev/null for the same reason as the PostgreSQL job. + - name: Run MySQL migrations + env: + DIESEL_CONFIG_FILE: /dev/null + run: diesel migration run --migration-dir migrations/mysql + + # Run the test suite with a live MySQL instance available. + - name: Run tests (mysql) + run: cargo test --profile ci --features mysql diff --git a/migrations/mysql/2022-07-27-110000_add_group_support/up.sql b/migrations/mysql/2022-07-27-110000_add_group_support/up.sql index 6d40638a..3dde2e09 100644 --- a/migrations/mysql/2022-07-27-110000_add_group_support/up.sql +++ b/migrations/mysql/2022-07-27-110000_add_group_support/up.sql @@ -20,4 +20,4 @@ CREATE TABLE collections_groups ( read_only BOOLEAN NOT NULL, hide_passwords BOOLEAN NOT NULL, UNIQUE (collections_uuid, groups_uuid) -); \ No newline at end of file +); diff --git a/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/down.sql b/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/down.sql new file mode 100644 index 00000000..f7f7e54a --- /dev/null +++ b/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/down.sql @@ -0,0 +1,7 @@ +ALTER TABLE groups_users + DROP PRIMARY KEY, + ADD UNIQUE (groups_uuid, users_organizations_uuid); + +ALTER TABLE collections_groups + DROP PRIMARY KEY, + ADD UNIQUE (collections_uuid, groups_uuid); diff --git a/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/up.sql b/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/up.sql new file mode 100644 index 00000000..c40f9d3d --- /dev/null +++ b/migrations/mysql/2026-03-22-100000_add_primary_keys_to_group_tables/up.sql @@ -0,0 +1,10 @@ +-- groups_users and collections_groups were created with UNIQUE instead of +-- PRIMARY KEY. Diesel requires primary keys on all tables for schema +-- introspection. Drop the auto-named unique index and add the primary key. +ALTER TABLE groups_users + DROP INDEX groups_uuid, + ADD PRIMARY KEY (groups_uuid, users_organizations_uuid); + +ALTER TABLE collections_groups + DROP INDEX collections_uuid, + ADD PRIMARY KEY (collections_uuid, groups_uuid);