import { main } from '@/commands/db/revert'; import { mockInstance } from '../../../shared/mocking'; import { Logger } from '@/Logger'; import type { IrreversibleMigration, ReversibleMigration } from '@/databases/types'; import type { Migration, MigrationExecutor } from '@n8n/typeorm'; import { type DataSource } from '@n8n/typeorm'; import { mock } from 'jest-mock-extended'; const logger = mockInstance(Logger); afterEach(() => { jest.resetAllMocks(); }); test("don't revert migrations if there is no migration", async () => { // // ARRANGE // const migrations: Migration[] = []; const dataSource = mock({ migrations }); const migrationExecutor = mock(); migrationExecutor.getExecutedMigrations.mockResolvedValue([]); // // ACT // await main(logger, dataSource, migrationExecutor); // // ASSERT // expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( "Cancelled command. The database was never migrated. Are you sure you're connected to the right database?.", ); expect(dataSource.undoLastMigration).not.toHaveBeenCalled(); expect(dataSource.destroy).not.toHaveBeenCalled(); }); test("don't revert the last migration if it had no down migration", async () => { // // ARRANGE // class TestMigration implements IrreversibleMigration { name = undefined; async up() {} down = undefined; } const migrationsInCode = [new TestMigration()]; const migrationsInDb: Migration[] = [{ id: 1, timestamp: Date.now(), name: 'TestMigration' }]; const dataSource = mock({ migrations: migrationsInCode }); const migrationExecutor = mock(); migrationExecutor.getExecutedMigrations.mockResolvedValue(migrationsInDb); // // ACT // await main(logger, dataSource, migrationExecutor); // // ASSERT // expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toBeCalledWith('Cancelled command. The last migration was irreversible.'); expect(dataSource.undoLastMigration).not.toHaveBeenCalled(); expect(dataSource.destroy).not.toHaveBeenCalled(); }); test('print migration name instead of class name in error message if the migration has a name', async () => { // // ARRANGE // class TestMigration implements IrreversibleMigration { name = 'Migration Name'; async up() {} down = undefined; } const migrationsInCode = [new TestMigration()]; const migrationsInDb: Migration[] = [{ id: 1, timestamp: Date.now(), name: 'Migration Name' }]; const dataSource = mock({ migrations: migrationsInCode }); const migrationExecutor = mock(); migrationExecutor.getExecutedMigrations.mockResolvedValue(migrationsInDb); // // ACT // await main(logger, dataSource, migrationExecutor); // // ASSERT // expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( 'Cancelled command. The last migration "Migration Name" was irreversible.', ); expect(dataSource.undoLastMigration).not.toHaveBeenCalled(); expect(dataSource.destroy).not.toHaveBeenCalled(); }); test("don't revert the last migration if we cannot find the migration in the code", async () => { // // ARRANGE // const migrationsInDb: Migration[] = [{ id: 1, timestamp: Date.now(), name: 'TestMigration' }]; const dataSource = mock({ migrations: [] }); const migrationExecutor = mock(); migrationExecutor.getExecutedMigrations.mockResolvedValue(migrationsInDb); // // ACT // await main(logger, dataSource, migrationExecutor); // // ASSERT // expect(logger.error).toHaveBeenCalledTimes(2); expect(logger.error).toHaveBeenNthCalledWith( 1, 'The last migration that was executed is "TestMigration", but I could not find that migration\'s code in the currently installed version of n8n.', ); expect(logger.error).toHaveBeenNthCalledWith( 2, 'This usually means that you downgraded n8n before running `n8n db:revert`. Please upgrade n8n again and run `n8n db:revert` and then downgrade again.', ); expect(dataSource.undoLastMigration).not.toHaveBeenCalled(); expect(dataSource.destroy).not.toHaveBeenCalled(); }); test('revert the last migration if it has a down migration', async () => { // // ARRANGE // class TestMigration implements ReversibleMigration { name = 'ReversibleMigration'; async up() {} async down() {} } const migrationsInDb: Migration[] = [ { id: 1, timestamp: Date.now(), name: 'ReversibleMigration' }, ]; const dataSource = mock({ migrations: [new TestMigration()] }); const migrationExecutor = mock(); migrationExecutor.getExecutedMigrations.mockResolvedValue(migrationsInDb); // // ACT // await main(logger, dataSource, migrationExecutor); // // ASSERT // expect(logger.error).not.toHaveBeenCalled(); expect(dataSource.undoLastMigration).toHaveBeenCalled(); expect(dataSource.destroy).toHaveBeenCalled(); });