2024-04-11 00:20:48 -07:00
import { main } from '@/commands/db/revert' ;
import { mockInstance } from '../../../shared/mocking' ;
import { Logger } from '@/Logger' ;
import type { IrreversibleMigration , ReversibleMigration } from '@/databases/types' ;
2024-05-23 09:16:26 -07:00
import type { Migration , MigrationExecutor } from '@n8n/typeorm' ;
import { type DataSource } from '@n8n/typeorm' ;
2024-04-11 00:20:48 -07:00
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
//
2024-05-23 09:16:26 -07:00
const migrations : Migration [ ] = [ ] ;
const dataSource = mock < DataSource > ( { migrations } ) ;
const migrationExecutor = mock < MigrationExecutor > ( ) ;
migrationExecutor . getExecutedMigrations . mockResolvedValue ( [ ] ) ;
2024-04-11 00:20:48 -07:00
//
// ACT
//
2024-05-23 09:16:26 -07:00
await main ( logger , dataSource , migrationExecutor ) ;
2024-04-11 00:20:48 -07:00
//
// ASSERT
//
expect ( logger . error ) . toHaveBeenCalledTimes ( 1 ) ;
2024-05-23 09:16:26 -07:00
expect ( logger . error ) . toHaveBeenCalledWith (
"Cancelled command. The database was never migrated. Are you sure you're connected to the right database?." ,
) ;
2024-04-11 00:20:48 -07:00
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 {
2024-05-23 09:16:26 -07:00
name = undefined ;
2024-04-11 00:20:48 -07:00
async up() { }
2024-05-23 09:16:26 -07:00
down = undefined ;
2024-04-11 00:20:48 -07:00
}
2024-05-23 09:16:26 -07:00
const migrationsInCode = [ new TestMigration ( ) ] ;
const migrationsInDb : Migration [ ] = [ { id : 1 , timestamp : Date.now ( ) , name : 'TestMigration' } ] ;
const dataSource = mock < DataSource > ( { migrations : migrationsInCode } ) ;
const migrationExecutor = mock < MigrationExecutor > ( ) ;
migrationExecutor . getExecutedMigrations . mockResolvedValue ( migrationsInDb ) ;
2024-04-11 00:20:48 -07:00
//
// ACT
//
2024-05-23 09:16:26 -07:00
await main ( logger , dataSource , migrationExecutor ) ;
2024-04-11 00:20:48 -07:00
//
// ASSERT
//
expect ( logger . error ) . toHaveBeenCalledTimes ( 1 ) ;
2024-05-23 09:16:26 -07:00
expect ( logger . error ) . toBeCalledWith ( 'Cancelled command. The last migration was irreversible.' ) ;
2024-04-11 00:20:48 -07:00
expect ( dataSource . undoLastMigration ) . not . toHaveBeenCalled ( ) ;
expect ( dataSource . destroy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2024-05-23 09:16:26 -07:00
test ( 'print migration name instead of class name in error message if the migration has a name' , async ( ) = > {
2024-04-11 00:20:48 -07:00
//
// ARRANGE
//
2024-05-23 09:16:26 -07:00
class TestMigration implements IrreversibleMigration {
name = 'Migration Name' ;
2024-04-11 00:20:48 -07:00
async up() { }
2024-05-23 09:16:26 -07:00
down = undefined ;
2024-04-11 00:20:48 -07:00
}
2024-05-23 09:16:26 -07:00
const migrationsInCode = [ new TestMigration ( ) ] ;
const migrationsInDb : Migration [ ] = [ { id : 1 , timestamp : Date.now ( ) , name : 'Migration Name' } ] ;
const dataSource = mock < DataSource > ( { migrations : migrationsInCode } ) ;
const migrationExecutor = mock < MigrationExecutor > ( ) ;
migrationExecutor . getExecutedMigrations . mockResolvedValue ( migrationsInDb ) ;
2024-04-11 00:20:48 -07:00
//
// ACT
//
2024-05-23 09:16:26 -07:00
await main ( logger , dataSource , migrationExecutor ) ;
2024-04-11 00:20:48 -07:00
//
// ASSERT
//
2024-05-23 09:16:26 -07:00
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 ( ) ;
2024-04-11 00:20:48 -07:00
} ) ;
2024-05-23 09:16:26 -07:00
test ( "don't revert the last migration if we cannot find the migration in the code" , async ( ) = > {
2024-04-11 00:20:48 -07:00
//
// ARRANGE
//
2024-05-23 09:16:26 -07:00
const migrationsInDb : Migration [ ] = [ { id : 1 , timestamp : Date.now ( ) , name : 'TestMigration' } ] ;
const dataSource = mock < DataSource > ( { migrations : [ ] } ) ;
const migrationExecutor = mock < MigrationExecutor > ( ) ;
migrationExecutor . getExecutedMigrations . mockResolvedValue ( migrationsInDb ) ;
2024-04-11 00:20:48 -07:00
//
// ACT
//
2024-05-23 09:16:26 -07:00
await main ( logger , dataSource , migrationExecutor ) ;
2024-04-11 00:20:48 -07:00
//
// ASSERT
//
2024-05-23 09:16:26 -07:00
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.' ,
) ;
2024-04-11 00:20:48 -07:00
expect ( dataSource . undoLastMigration ) . not . toHaveBeenCalled ( ) ;
expect ( dataSource . destroy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2024-05-23 09:16:26 -07:00
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 < DataSource > ( { migrations : [ new TestMigration ( ) ] } ) ;
const migrationExecutor = mock < MigrationExecutor > ( ) ;
migrationExecutor . getExecutedMigrations . mockResolvedValue ( migrationsInDb ) ;
//
// ACT
//
await main ( logger , dataSource , migrationExecutor ) ;
//
// ASSERT
//
expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
expect ( dataSource . undoLastMigration ) . toHaveBeenCalled ( ) ;
expect ( dataSource . destroy ) . toHaveBeenCalled ( ) ;
} ) ;