@@ -3,6 +3,7 @@ package tapdb
3
3
import (
4
4
"context"
5
5
"encoding/hex"
6
+ "errors"
6
7
"fmt"
7
8
"os"
8
9
"path/filepath"
@@ -694,3 +695,80 @@ func TestMigration37(t *testing.T) {
694
695
695
696
require .Len (t , burns , 5 )
696
697
}
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