@@ -3,6 +3,7 @@ package tapdb
33import (
44 "context"
55 "encoding/hex"
6+ "errors"
67 "fmt"
78 "os"
89 "path/filepath"
@@ -694,3 +695,80 @@ func TestMigration37(t *testing.T) {
694695
695696 require .Len (t , burns , 5 )
696697}
698+
699+ // TestDirtySqliteVersion tests that if a migration fails and leaves an Sqlite
700+ // database backend in a dirty state, any attempts of re-executing migrations on
701+ // the db (i.e. restart tapd), will fail with an error indicating that the
702+ // database is in a dirty state. This is regardless of whether the failing
703+ // migration is the latest migration or an intermediate migration.
704+ func TestDirtySqliteVersion (t * testing.T ) {
705+ var (
706+ err error
707+ testError = errors .New ("test error" )
708+
709+ // testPostMigrationChecks1 is a map that will trigger a
710+ // migration callback for migration 2 which always returns an
711+ // error. This is used to simulate an intermediate migration
712+ // that fails and leaves the db in a dirty state.
713+ testPostMigrationChecks1 = map [uint ]postMigrationCheck {
714+ 2 : func (ctx context.Context , q sqlc.Querier ) error {
715+ return testError
716+ },
717+ }
718+
719+ // testPostMigrationChecks2 is a map that will trigger a
720+ // migration callback for the latest migration which always
721+ // returns an error. This is used to simulate that the latest
722+ // migration fails and leaves the db in a dirty state.
723+ testPostMigrationChecks2 = map [uint ]postMigrationCheck {
724+ LatestMigrationVersion : func (ctx context.Context ,
725+ q sqlc.Querier ) error {
726+
727+ return testError
728+ },
729+ }
730+ )
731+
732+ // First, we'll test that intermediate migration that fails and leaves
733+ // the db in a dirty state is detected on subsequent migration attempts.
734+ // Note that this test only targets Sqlite database backends, and
735+ // therefore we create a new Sqlite test db for this.
736+ db1 := NewTestSqliteDBWithVersion (t , 1 )
737+
738+ // As intend that the failing migration version should be an
739+ // intermediate migration, we use the testPostMigrationChecks1 when
740+ // executing the migration. Note that we use the `backupAndMigrate` func
741+ // as the MigrationTarget, to simulate what is used in production for
742+ // Sqlite database backends.
743+ err = db1 .ExecuteMigrations (db1 .backupAndMigrate , WithPostStepCallbacks (
744+ makePostStepCallbacks (db1 , testPostMigrationChecks1 ),
745+ ))
746+ require .ErrorIs (t , err , testError )
747+
748+ // If we now attempt to execute migrations again, it should fail with an
749+ // error indicating that the db is in a dirty state.
750+ err = db1 .ExecuteMigrations (db1 .backupAndMigrate , WithPostStepCallbacks (
751+ makePostStepCallbacks (db1 , testPostMigrationChecks1 ),
752+ ))
753+ require .ErrorContains (t , err , "database is in a dirty state" )
754+
755+ // Next, we'll test that if the **latest** migration fails and leaves
756+ // the db in a dirty state, that this is also detected on subsequent
757+ // migration attempts.
758+ db2 := NewTestSqliteDBWithVersion (t , 1 )
759+
760+ // As we intend that the failing migration version should be the latest
761+ // migration, we now use the testPostMigrationChecks2 when executing
762+ // the migration.
763+ err = db2 .ExecuteMigrations (db2 .backupAndMigrate , WithPostStepCallbacks (
764+ makePostStepCallbacks (db2 , testPostMigrationChecks2 ),
765+ ))
766+ require .ErrorIs (t , err , testError )
767+
768+ // If we now attempt to execute migrations again, it should fail with an
769+ // error indicating that the db is in a dirty state.
770+ err = db2 .ExecuteMigrations (db2 .backupAndMigrate , WithPostStepCallbacks (
771+ makePostStepCallbacks (db2 , testPostMigrationChecks2 ),
772+ ))
773+ require .ErrorContains (t , err , "database is in a dirty state" )
774+ }
0 commit comments