From 289e4592833f0986c9d8a589e7c41edb4d6bb4f7 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 5 Jul 2023 09:36:23 +0200 Subject: [PATCH] Added tests for migrations refs https://github.com/TryGhost/DevOps/issues/39 - up until now, we've had a CI job which does a really basic test for migrations, but it barely functions and misses bugs all the time - this commit removes that and switches to an actual test suite for our migrations, so we can ensure they function as expected - also removes the env var hack I came up with for those migrations tests - this should lead to safer migrations and faster tests --- .github/workflows/ci.yml | 72 ------------------- ghost/core/core/boot.js | 8 --- .../integration/migrations/migration.test.js | 60 +++++++++++++++- 3 files changed, 57 insertions(+), 83 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4082255c6..d7a97affe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,9 +78,6 @@ jobs: sodo-search: - *shared - 'apps/sodo-search/**' - migrations: - - *shared - - 'ghost/core/core/server/data/migrations/**' any-code: - '!**/*.md' outputs: @@ -92,7 +89,6 @@ jobs: changed_portal: ${{ steps.changed.outputs.portal }} changed_signup_form: ${{ steps.changed.outputs.signup-form }} changed_sodo_search: ${{ steps.changed.outputs.sodo-search }} - changed_migrations: ${{ steps.changed.outputs.migrations }} changed_any_code: ${{ steps.changed.outputs.any-code }} commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}' is_git_sync: ${{ github.head_ref == 'main' || github.ref == 'refs/heads/main' }} @@ -209,72 +205,6 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - job_migrations: - runs-on: ubuntu-latest - needs: [job_get_metadata, job_install_deps] - if: needs.job_get_metadata.outputs.changed_migrations == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) - strategy: - matrix: - env: - - DB: sqlite3 - DB_CLIENT: sqlite3 - - DB: mysql8 - DB_CLIENT: mysql - env: - database__client: ${{ matrix.env.DB_CLIENT }} - name: Migrations checks (${{ matrix.env.DB }}) - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: true - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: '18.12.1' - - - name: Shutdown MySQL - run: sudo service mysql stop - if: matrix.env.DB == 'mysql8' - - - uses: daniellockyer/mysql-action@main - if: matrix.env.DB == 'mysql8' - with: - authentication plugin: 'caching_sha2_password' - mysql version: '8.0' - mysql database: 'ghost_testing' - mysql root password: 'root' - - - name: Set env vars (SQLite) - if: contains(matrix.env.DB, 'sqlite') - run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV - - - name: Set env vars (MySQL) - if: contains(matrix.env.DB, 'mysql') - run: | - echo "database__connection__host=127.0.0.1" >> $GITHUB_ENV - echo "database__connection__user=root" >> $GITHUB_ENV - echo "database__connection__password=root" >> $GITHUB_ENV - echo "database__connection__database=ghost_testing" >> $GITHUB_ENV - - - name: Restore caches - uses: ./.github/actions/restore-cache - env: - DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} - - - name: Run Ghost and then kill it - run: yarn workspace ghost run start - env: - GHOST_TESTS_KILL_SERVER_AFTER_BOOT: true - - - run: sqlite3 ${{ env.database__connection__filename }} "DELETE FROM migrations WHERE version LIKE '5.%';" - if: matrix.env.DB == 'sqlite3' - - run: mysql -h127.0.0.1 -uroot -proot ghost_testing -e "DELETE FROM migrations WHERE version LIKE '5.%';" - if: matrix.env.DB == 'mysql8' - - - run: yarn knex-migrator migrate --force - job_unit-tests: runs-on: ubuntu-latest needs: [job_get_metadata, job_install_deps] @@ -768,7 +698,6 @@ jobs: job_lint, job_ghost-cli, job_admin-tests, - job_migrations, job_unit-tests, job_database-tests, job_regression-tests, @@ -790,7 +719,6 @@ jobs: job_lint, job_ghost-cli, job_admin-tests, - job_migrations, job_unit-tests, job_database-tests, job_regression-tests diff --git a/ghost/core/core/boot.js b/ghost/core/core/boot.js index 233c3e2b49..e1c6f31609 100644 --- a/ghost/core/core/boot.js +++ b/ghost/core/core/boot.js @@ -536,14 +536,6 @@ async function bootGhost({backend = true, frontend = true, server = true} = {}) // Step 7 - Init our background services, we don't wait for this to finish initBackgroundServices({config}); - // Step 8 - Kill the process - what?? - // During the migration tests, we want to boot ghost, run migrations, then shut down - // This is the easiest way to get Ghost to boot and then kill itself - if (process.env.GHOST_TESTS_KILL_SERVER_AFTER_BOOT === 'true') { - debug('Killing Ghost Server after boot'); - process.exit(0); - } - // We return the server purely for testing purposes if (server) { debug('End Boot: Returning Ghost Server'); diff --git a/ghost/core/test/integration/migrations/migration.test.js b/ghost/core/test/integration/migrations/migration.test.js index 3ecabfdcd0..31db1804f8 100644 --- a/ghost/core/test/integration/migrations/migration.test.js +++ b/ghost/core/test/integration/migrations/migration.test.js @@ -4,13 +4,67 @@ const testUtils = require('../../utils'); const _ = require('lodash'); const Models = require('../../../core/server/models'); -describe('Database Migration (special functions)', function () { - before(testUtils.teardownDb); - afterEach(testUtils.teardownDb); +const KnexMigrator = require('knex-migrator'); +const path = require('path'); +const semver = require('semver'); + +const knexMigrator = new KnexMigrator({ + knexMigratorFilePath: path.join(__dirname, '../../../') +}); + +const db = require('../../../core/server/data/db'); +const dbUtils = require('../../utils/db-utils'); + +const currentVersion = require('@tryghost/version'); +const currentMajor = semver.major(currentVersion.original); +const previousMinor = semver.minor(currentVersion.original) - 1; +const previousVersion = `${currentMajor}.${previousMinor}`; + +describe('Migrations', function () { + beforeEach(async function () { + await dbUtils.teardown(); + }); + afterEach(function () { sinon.restore(); }); + describe('Database initialization + rollback', function () { + beforeEach(async function () { + await knexMigrator.init(); + }); + + it('can rollback to the previous minor version', async function () { + await knexMigrator.rollback({ + version: previousVersion, + force: true + }); + }); + + it('can rollback to the previous minor version and then forwards again', async function () { + await knexMigrator.rollback({ + version: previousVersion, + force: true + }); + await knexMigrator.migrate({ + force: true + }); + }); + + it('should have idempotent migrations', async function () { + // Delete all knowledge that we've run migrations so we can run them again + if (dbUtils.isMySQL()) { + await db.knex('migrations').whereILike('version', `${currentMajor}.%`).del(); + } else { + await db.knex('migrations').whereLike('version', `${currentMajor}.%`).del(); + } + + await knexMigrator.migrate({ + force: true + }); + }); + }); + describe('Fixtures', function () { // Custom assertion for detection that a permissions is assigned to the correct roles should.Assertion.add('AssignedToRoles', function (roles) {