@@ -452,3 +452,294 @@ func TestCustomMigration(t *testing.T) {
452
452
})
453
453
}
454
454
}
455
+
456
+ // TestSchemaMigrationIdempotency tests that the our schema migrations are
457
+ // idempotent. This means that we can apply the migrations multiple times and
458
+ // the schema version will always be the same.
459
+ func TestSchemaMigrationIdempotency (t * testing.T ) {
460
+ dropMigrationTrackerEntries := func (t * testing.T , db * BaseDB ) {
461
+ _ , err := db .Exec ("DELETE FROM migration_tracker;" )
462
+ require .NoError (t , err )
463
+ }
464
+
465
+ lastMigration := migrationConfig [len (migrationConfig )- 1 ]
466
+
467
+ t .Run ("SQLite" , func (t * testing.T ) {
468
+ // First instantiate the database and run the migrations
469
+ // including the custom migrations.
470
+ t .Logf ("Creating new SQLite DB for testing migrations" )
471
+
472
+ dbFileName := filepath .Join (t .TempDir (), "tmp.db" )
473
+ var (
474
+ db * SqliteStore
475
+ err error
476
+ )
477
+
478
+ // Run the migration 3 times to test that the migrations
479
+ // are idempotent.
480
+ for i := 0 ; i < 3 ; i ++ {
481
+ db , err = NewSqliteStore (& SqliteConfig {
482
+ SkipMigrations : false ,
483
+ }, dbFileName )
484
+ require .NoError (t , err )
485
+
486
+ dbToCleanup := db .DB
487
+ t .Cleanup (func () {
488
+ require .NoError (
489
+ t , dbToCleanup .Close (),
490
+ )
491
+ })
492
+
493
+ ctxb := context .Background ()
494
+ require .NoError (
495
+ t , db .ApplyAllMigrations (ctxb , GetMigrations ()),
496
+ )
497
+
498
+ version , dirty , err := db .GetSchemaVersion ()
499
+ require .NoError (t , err )
500
+
501
+ // Now reset the schema version to 0 and make sure that
502
+ // we can apply the migrations again.
503
+ require .Equal (t , lastMigration .SchemaVersion , version )
504
+ require .False (t , dirty )
505
+
506
+ require .NoError (
507
+ t , db .SetSchemaVersion (
508
+ database .NilVersion , false ,
509
+ ),
510
+ )
511
+ dropMigrationTrackerEntries (t , db .BaseDB )
512
+
513
+ // Make sure that we reset the schema version.
514
+ version , dirty , err = db .GetSchemaVersion ()
515
+ require .NoError (t , err )
516
+ require .Equal (t , - 1 , version )
517
+ require .False (t , dirty )
518
+ }
519
+ })
520
+
521
+ t .Run ("Postgres" , func (t * testing.T ) {
522
+ // First create a temporary Postgres database to run
523
+ // the migrations on.
524
+ fixture := NewTestPgFixture (
525
+ t , DefaultPostgresFixtureLifetime ,
526
+ )
527
+ t .Cleanup (func () {
528
+ fixture .TearDown (t )
529
+ })
530
+
531
+ dbName := randomDBName (t )
532
+
533
+ // Next instantiate the database and run the migrations
534
+ // including the custom migrations.
535
+ t .Logf ("Creating new Postgres DB '%s' for testing " +
536
+ "migrations" , dbName )
537
+
538
+ _ , err := fixture .db .ExecContext (
539
+ context .Background (), "CREATE DATABASE " + dbName ,
540
+ )
541
+ require .NoError (t , err )
542
+
543
+ cfg := fixture .GetConfig (dbName )
544
+ var db * PostgresStore
545
+
546
+ // Run the migration 3 times to test that the migrations
547
+ // are idempotent.
548
+ for i := 0 ; i < 3 ; i ++ {
549
+ cfg .SkipMigrations = false
550
+ db , err = NewPostgresStore (cfg )
551
+ require .NoError (t , err )
552
+
553
+ ctxb := context .Background ()
554
+ require .NoError (
555
+ t , db .ApplyAllMigrations (ctxb , GetMigrations ()),
556
+ )
557
+
558
+ version , dirty , err := db .GetSchemaVersion ()
559
+ require .NoError (t , err )
560
+
561
+ // Now reset the schema version to 0 and make sure that
562
+ // we can apply the migrations again.
563
+ require .Equal (t , lastMigration .SchemaVersion , version )
564
+ require .False (t , dirty )
565
+
566
+ require .NoError (
567
+ t , db .SetSchemaVersion (
568
+ database .NilVersion , false ,
569
+ ),
570
+ )
571
+ dropMigrationTrackerEntries (t , db .BaseDB )
572
+
573
+ // Make sure that we reset the schema version.
574
+ version , dirty , err = db .GetSchemaVersion ()
575
+ require .NoError (t , err )
576
+ require .Equal (t , - 1 , version )
577
+ require .False (t , dirty )
578
+ }
579
+ })
580
+ }
581
+
582
+ // TestMigrationBug19RC1 tests a bug that was present in the migration code
583
+ // at the v0.19.0-rc1 release.
584
+ // The bug was fixed in: https://github.com/lightningnetwork/lnd/pull/9647
585
+ // NOTE: This test may be removed once the final version of 0.19.0 is released.
586
+ func TestMigrationSucceedsAfterDirtyStateMigrationFailure19RC1 (t * testing.T ) {
587
+ // setMigrationTrackerVersion is a helper function that
588
+ // updates the migration tracker table to a specific version that
589
+ // simulates the conditions of the bug.
590
+ setMigrationTrackerVersion := func (t * testing.T , db * BaseDB ) {
591
+ _ , err := db .Exec (`
592
+ DELETE FROM migration_tracker;
593
+ INSERT INTO migration_tracker (version, migration_time)
594
+ VALUES (2, CURRENT_TIMESTAMP);
595
+ ` )
596
+ require .NoError (t , err )
597
+ }
598
+
599
+ const (
600
+ maxSchemaVersionBefore19RC1 = 4
601
+ failingSchemaVersion = 3
602
+ )
603
+
604
+ ctxb := context .Background ()
605
+ migrations := GetMigrations ()
606
+ migrations = migrations [:maxSchemaVersionBefore19RC1 ]
607
+ lastMigration := migrations [len (migrations )- 1 ]
608
+
609
+ // Make sure that the last migration is the one we expect.
610
+ require .Equal (
611
+ t , maxSchemaVersionBefore19RC1 , lastMigration .SchemaVersion ,
612
+ )
613
+
614
+ t .Run ("SQLite" , func (t * testing.T ) {
615
+ // First instantiate the database and run the migrations
616
+ // including the custom migrations.
617
+ t .Logf ("Creating new SQLite DB for testing migrations" )
618
+
619
+ dbFileName := filepath .Join (t .TempDir (), "tmp.db" )
620
+ var (
621
+ db * SqliteStore
622
+ err error
623
+ )
624
+
625
+ db , err = NewSqliteStore (& SqliteConfig {
626
+ SkipMigrations : false ,
627
+ }, dbFileName )
628
+ require .NoError (t , err )
629
+
630
+ dbToCleanup := db .DB
631
+ t .Cleanup (func () {
632
+ require .NoError (t , dbToCleanup .Close ())
633
+ })
634
+
635
+ require .NoError (t , db .ApplyAllMigrations (ctxb , migrations ))
636
+
637
+ version , dirty , err := db .GetSchemaVersion ()
638
+ require .NoError (t , err )
639
+
640
+ // Now reset the schema version to 0 and make sure that
641
+ // we can apply the migrations again.
642
+ require .Equal (t , lastMigration .SchemaVersion , version )
643
+ require .False (t , dirty )
644
+
645
+ // Set the schema version to the failing version and
646
+ // make make the version dirty which essentially tells
647
+ // golang-migrate that the migration failed.
648
+ require .NoError (
649
+ t , db .SetSchemaVersion (failingSchemaVersion , true ),
650
+ )
651
+
652
+ // Set the migration tracker to the failing version.
653
+ setMigrationTrackerVersion (t , db .BaseDB )
654
+
655
+ // Close the DB so we can reopen it and apply the
656
+ // migrations again.
657
+ db .DB .Close ()
658
+
659
+ db , err = NewSqliteStore (& SqliteConfig {
660
+ SkipMigrations : false ,
661
+ }, dbFileName )
662
+ require .NoError (t , err )
663
+
664
+ dbToCleanup2 := db .DB
665
+ t .Cleanup (func () {
666
+ require .NoError (t , dbToCleanup2 .Close ())
667
+ })
668
+
669
+ require .NoError (t , db .ApplyAllMigrations (ctxb , migrations ))
670
+
671
+ version , dirty , err = db .GetSchemaVersion ()
672
+ require .NoError (t , err )
673
+ require .Equal (t , lastMigration .SchemaVersion , version )
674
+ require .False (t , dirty )
675
+ })
676
+
677
+ t .Run ("Postgres" , func (t * testing.T ) {
678
+ // First create a temporary Postgres database to run
679
+ // the migrations on.
680
+ fixture := NewTestPgFixture (
681
+ t , DefaultPostgresFixtureLifetime ,
682
+ )
683
+ t .Cleanup (func () {
684
+ fixture .TearDown (t )
685
+ })
686
+
687
+ dbName := randomDBName (t )
688
+
689
+ // Next instantiate the database and run the migrations
690
+ // including the custom migrations.
691
+ t .Logf ("Creating new Postgres DB '%s' for testing " +
692
+ "migrations" , dbName )
693
+
694
+ _ , err := fixture .db .ExecContext (
695
+ context .Background (), "CREATE DATABASE " + dbName ,
696
+ )
697
+ require .NoError (t , err )
698
+
699
+ cfg := fixture .GetConfig (dbName )
700
+ var db * PostgresStore
701
+
702
+ cfg .SkipMigrations = false
703
+ db , err = NewPostgresStore (cfg )
704
+ require .NoError (t , err )
705
+
706
+ require .NoError (t , db .ApplyAllMigrations (ctxb , migrations ))
707
+
708
+ version , dirty , err := db .GetSchemaVersion ()
709
+ require .NoError (t , err )
710
+
711
+ // Now reset the schema version to 0 and make sure that
712
+ // we can apply the migrations again.
713
+ require .Equal (t , lastMigration .SchemaVersion , version )
714
+ require .False (t , dirty )
715
+
716
+ // Set the schema version to the failing version and
717
+ // make make the version dirty which essentially tells
718
+ // golang-migrate that the migration failed.
719
+ require .NoError (
720
+ t , db .SetSchemaVersion (failingSchemaVersion , true ),
721
+ )
722
+
723
+ // Set the migration tracker to the failing version.
724
+ setMigrationTrackerVersion (t , db .BaseDB )
725
+
726
+ // Close the DB so we can reopen it and apply the
727
+ // migrations again.
728
+ db .DB .Close ()
729
+
730
+ db , err = NewPostgresStore (cfg )
731
+ require .NoError (t , err )
732
+
733
+ dbToCleanup2 := db .DB
734
+ t .Cleanup (func () {
735
+ require .NoError (t , dbToCleanup2 .Close ())
736
+ })
737
+
738
+ require .NoError (t , db .ApplyAllMigrations (ctxb , migrations ))
739
+
740
+ version , dirty , err = db .GetSchemaVersion ()
741
+ require .NoError (t , err )
742
+ require .Equal (t , lastMigration .SchemaVersion , version )
743
+ require .False (t , dirty )
744
+ })
745
+ }
0 commit comments