diff --git a/.agents/testing.md b/.agents/testing.md index 12c425e3f..43fb8fa70 100644 --- a/.agents/testing.md +++ b/.agents/testing.md @@ -231,3 +231,47 @@ Existing fakes to reuse: - `FakeAnalysisMetadataRecalculator` / `FakeAnalysisNodeMetadataRecalculator` / `FakeAnalysisFileMetadataRecalculator` — record `recalc()` and `recalcDebounced()` calls When a dependency has no fake yet, create one in the entity's test directory (`test/unit/core/entities//`) implementing the port interface. + +## Migration Tests + +The `test` CI job runs the integration suite against MySQL, Postgres, and SQLite, but the schema is built via `dataSource.synchronize()` (see `apps//test/app/database.ts`) — migration files in `apps//src/adapters/database/migrations/{mysql,postgres}/` are **not** exercised by that suite. + +A separate `tests-migrations` CI job runs the migration CLI end-to-end for `server-core`, `server-storage`, and `server-telemetry` against a fresh MySQL and Postgres container: + +1. `migration run` — applies all migrations forward +2. `migration revert` × N — undoes every migration in reverse order (verifies every `down()` works) +3. `migration run` — re-applies the full chain (verifies idempotency) + +This catches SQL syntax errors, cross-DB type mismatches, and `down()` regressions across every migration. It does **not** catch data-correctness bugs in `UPDATE`/`INSERT` migrations against pre-existing rows — those still require manual smoke-testing against a populated database. + +The job pre-flights with a sanity check that the compiled migrations exist under `apps//dist/adapters/database/migrations/{mysql,postgres}/` — without this guard, running the CLI from the wrong working directory results in typeorm silently reporting "No migrations are pending" with exit code 0, masking the failure. + +Locally, run the same `run → revert × N → run` flow against a running compose stack: + +```bash +# MySQL +docker compose up -d mysql +cd apps/server-core # or: server-storage / server-telemetry + +export DB_TYPE=mysql DB_HOST=127.0.0.1 DB_PORT=3306 \ + DB_USERNAME=root DB_PASSWORD=start123 DB_DATABASE=app + +node dist/cli/index.mjs migration run +node dist/cli/index.mjs migration revert # repeat once per migration +node dist/cli/index.mjs migration run # idempotency replay +``` + +```bash +# Postgres +docker compose up -d postgres +cd apps/server-core + +export DB_TYPE=postgres DB_HOST=127.0.0.1 DB_PORT=5432 \ + DB_USERNAME=postgres DB_PASSWORD=start123 DB_DATABASE=app + +node dist/cli/index.mjs migration run +node dist/cli/index.mjs migration revert +node dist/cli/index.mjs migration run +``` + +The CLI auto-creates the target database if it does not exist. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb07f1548..fc5789fd9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -136,6 +136,98 @@ jobs: run: | npm run test + tests-migrations: + name: Test Migrations + needs: [build] + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + service: [ server-core, server-storage, server-telemetry ] + db: [ mysql, postgres ] + include: + - db: mysql + port: 3306 + user: root + - db: postgres + port: 5432 + user: postgres + + env: + DB_TYPE: ${{ matrix.db }} + DB_HOST: 127.0.0.1 + DB_PORT: ${{ matrix.port }} + DB_USERNAME: ${{ matrix.user }} + DB_PASSWORD: start123 + DB_DATABASE: app + + services: + mysql: + image: mysql:9 + env: + MYSQL_ROOT_PASSWORD: start123 + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping -h localhost -pstart123" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + postgres: + image: postgres:18 + env: + POSTGRES_PASSWORD: start123 + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U postgres" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install + uses: ./.github/actions/install + with: + node-version: ${{ env.NODE_VERSION }} + node-registry: ${{ env.NODE_REGISTRY }} + + - name: Build + uses: ./.github/actions/build + + - name: Verify compiled migrations exist + working-directory: apps/${{ matrix.service }} + run: | + shopt -s nullglob + files=(dist/adapters/database/migrations/${{ matrix.db }}/*.mjs) + count=${#files[@]} + if [ "$count" = "0" ]; then + echo "ERROR: no compiled migrations in apps/${{ matrix.service }}/dist/adapters/database/migrations/${{ matrix.db }}/" + exit 1 + fi + echo "Found $count migration(s)" + echo "MIGRATION_COUNT=$count" >> "$GITHUB_ENV" + + - name: Run migrations forward + working-directory: apps/${{ matrix.service }} + run: node dist/cli/index.mjs migration run + + - name: Revert all migrations + working-directory: apps/${{ matrix.service }} + run: | + for ((i = 1; i <= MIGRATION_COUNT; i++)); do + node dist/cli/index.mjs migration revert + done + + - name: Re-apply all migrations (idempotency) + working-directory: apps/${{ matrix.service }} + run: node dist/cli/index.mjs migration run + lint: needs: [build] runs-on: ubuntu-latest diff --git a/docker-compose.yml b/docker-compose.yml index d5116d7c2..d2e1aff69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.9' services: mysql: - image: mysql + image: mysql:9 restart: always healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] @@ -14,7 +14,7 @@ services: ports: - '3306:3306' postgres: - image: postgres + image: postgres:18 restart: always healthcheck: test: [ "CMD", "pg_isready" ] diff --git a/package-lock.json b/package-lock.json index 25e6f9ffb..66d44997f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26343,13 +26343,13 @@ "version": "0.8.21", "license": "Apache-2.0", "devDependencies": { - "@privateaim/kit": "^0.8.21", - "@privateaim/server-kit": "^0.8.21", + "@privateaim/kit": "^0.9.0", + "@privateaim/server-kit": "^0.9.0", "@privateaim/storage-kit": "^0.8.21" }, "peerDependencies": { - "@privateaim/kit": "^0.8.21", - "@privateaim/server-kit": "^0.8.21", + "@privateaim/kit": "^0.9.0", + "@privateaim/server-kit": "^0.9.0", "@privateaim/storage-kit": "^0.8.21" } }, @@ -26423,13 +26423,13 @@ "@authup/access": "^1.0.0-beta.36", "@authup/core-kit": "^1.0.0-beta.36", "@privateaim/errors": "^0.8.42", - "@privateaim/server-kit": "^0.8.37" + "@privateaim/server-kit": "^0.9.0" }, "peerDependencies": { "@authup/access": "^1.0.0-beta.36", "@authup/core-kit": "^1.0.0-beta.36", "@privateaim/errors": "^0.8.42", - "@privateaim/server-kit": "^0.8.37" + "@privateaim/server-kit": "^0.9.0" } }, "packages/storage-kit": { diff --git a/packages/server-storage-kit/package.json b/packages/server-storage-kit/package.json index 2f2347413..4491d4edd 100644 --- a/packages/server-storage-kit/package.json +++ b/packages/server-storage-kit/package.json @@ -19,13 +19,13 @@ "license": "Apache-2.0", "description": "This package contains server side db helpers & utilities.", "devDependencies": { - "@privateaim/kit": "^0.8.21", - "@privateaim/server-kit": "^0.8.21", + "@privateaim/kit": "^0.9.0", + "@privateaim/server-kit": "^0.9.0", "@privateaim/storage-kit": "^0.8.21" }, "peerDependencies": { - "@privateaim/kit": "^0.8.21", - "@privateaim/server-kit": "^0.8.21", + "@privateaim/kit": "^0.9.0", + "@privateaim/server-kit": "^0.9.0", "@privateaim/storage-kit": "^0.8.21" }, "scripts": { diff --git a/packages/server-test-kit/package.json b/packages/server-test-kit/package.json index b1a1440eb..c2f3e01dd 100644 --- a/packages/server-test-kit/package.json +++ b/packages/server-test-kit/package.json @@ -22,13 +22,13 @@ "@authup/access": "^1.0.0-beta.36", "@authup/core-kit": "^1.0.0-beta.36", "@privateaim/errors": "^0.8.42", - "@privateaim/server-kit": "^0.8.37" + "@privateaim/server-kit": "^0.9.0" }, "peerDependencies": { "@authup/access": "^1.0.0-beta.36", "@authup/core-kit": "^1.0.0-beta.36", "@privateaim/errors": "^0.8.42", - "@privateaim/server-kit": "^0.8.37" + "@privateaim/server-kit": "^0.9.0" }, "scripts": { "build": "rimraf ./dist && tsdown"