| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | package core_test | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/pocketbase/dbx" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/core" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/tests" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestMigrationsRunnerUpAndDown(t *testing.T) { | 
					
						
							|  |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	callsOrder := []string{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	l := core.MigrationsList{} | 
					
						
							|  |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "up2") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "down2") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "2_test") | 
					
						
							|  |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "up3") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "down3") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "3_test") | 
					
						
							|  |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "up1") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "down1") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "1_test") | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "up4") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		callsOrder = append(callsOrder, "down4") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "4_test") | 
					
						
							|  |  |  | 	l.Add(&core.Migration{ | 
					
						
							|  |  |  | 		Up: func(app core.App) error { | 
					
						
							|  |  |  | 			callsOrder = append(callsOrder, "up5") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Down: func(app core.App) error { | 
					
						
							|  |  |  | 			callsOrder = append(callsOrder, "down5") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		File: "5_test", | 
					
						
							|  |  |  | 		ReapplyCondition: func(txApp core.App, runner *core.MigrationsRunner, fileName string) (bool, error) { | 
					
						
							|  |  |  | 			return true, nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	runner := core.NewMigrationsRunner(app, l) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 	// simulate partially out-of-order applied migration
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	_, err := app.DB().Insert(core.DefaultMigrationsTable, dbx.Params{ | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 		"file":    "4_test", | 
					
						
							|  |  |  | 		"applied": time.Now().UnixMicro() - 2, | 
					
						
							|  |  |  | 	}).Execute() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to insert 5_test migration: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = app.DB().Insert(core.DefaultMigrationsTable, dbx.Params{ | 
					
						
							|  |  |  | 		"file":    "5_test", | 
					
						
							|  |  |  | 		"applied": time.Now().UnixMicro() - 1, | 
					
						
							|  |  |  | 	}).Execute() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to insert 5_test migration: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = app.DB().Insert(core.DefaultMigrationsTable, dbx.Params{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		"file":    "2_test", | 
					
						
							|  |  |  | 		"applied": time.Now().UnixMicro(), | 
					
						
							|  |  |  | 	}).Execute() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to insert 2_test migration: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 	// Up()
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err := runner.Up(); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 	expectedUpCallsOrder := `["up1","up3","up5"]` // skip up2 and up4 since they were applied already (up5 has extra reapply condition)
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	upCallsOrder, err := json.Marshal(callsOrder) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if v := string(upCallsOrder); v != expectedUpCallsOrder { | 
					
						
							|  |  |  | 		t.Fatalf("Expected Up() calls order %s, got %s", expectedUpCallsOrder, upCallsOrder) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// reset callsOrder
 | 
					
						
							|  |  |  | 	callsOrder = []string{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// simulate unrun migration
 | 
					
						
							|  |  |  | 	l.Register(nil, func(app core.App) error { | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 		callsOrder = append(callsOrder, "down6") | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 	}, "6_test") | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// simulate applied migrations from different migrations list
 | 
					
						
							|  |  |  | 	_, err = app.DB().Insert(core.DefaultMigrationsTable, dbx.Params{ | 
					
						
							|  |  |  | 		"file":    "from_different_list", | 
					
						
							|  |  |  | 		"applied": time.Now().UnixMicro(), | 
					
						
							|  |  |  | 	}).Execute() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to insert from_different_list migration: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 	// Down()
 | 
					
						
							|  |  |  | 	// ---------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err := runner.Down(2); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-08 21:23:58 +08:00
										 |  |  | 	expectedDownCallsOrder := `["down5","down3"]` // revert in the applied order
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	downCallsOrder, err := json.Marshal(callsOrder) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if v := string(downCallsOrder); v != expectedDownCallsOrder { | 
					
						
							|  |  |  | 		t.Fatalf("Expected Down() calls order %s, got %s", expectedDownCallsOrder, downCallsOrder) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestMigrationsRunnerRemoveMissingAppliedMigrations(t *testing.T) { | 
					
						
							|  |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// mock migrations history
 | 
					
						
							|  |  |  | 	for i := 1; i <= 3; i++ { | 
					
						
							|  |  |  | 		_, err := app.DB().Insert(core.DefaultMigrationsTable, dbx.Params{ | 
					
						
							|  |  |  | 			"file":    fmt.Sprintf("%d_test", i), | 
					
						
							|  |  |  | 			"applied": time.Now().UnixMicro(), | 
					
						
							|  |  |  | 		}).Execute() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !isMigrationApplied(app, "2_test") { | 
					
						
							|  |  |  | 		t.Fatalf("Expected 2_test migration to be applied") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create a runner without 2_test to mock deleted migration
 | 
					
						
							|  |  |  | 	l := core.MigrationsList{} | 
					
						
							|  |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "1_test") | 
					
						
							|  |  |  | 	l.Register(func(app core.App) error { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, func(app core.App) error { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}, "3_test") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r := core.NewMigrationsRunner(app, l) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := r.RemoveMissingAppliedMigrations(); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to remove missing applied migrations: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if isMigrationApplied(app, "2_test") { | 
					
						
							|  |  |  | 		t.Fatalf("Expected 2_test migration to NOT be applied") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func isMigrationApplied(app core.App, file string) bool { | 
					
						
							|  |  |  | 	var exists bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := app.DB().Select("count(*)"). | 
					
						
							|  |  |  | 		From(core.DefaultMigrationsTable). | 
					
						
							|  |  |  | 		Where(dbx.HashExp{"file": file}). | 
					
						
							|  |  |  | 		Limit(1). | 
					
						
							|  |  |  | 		Row(&exists) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err == nil && exists | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // // -------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // type testDB struct {
 | 
					
						
							|  |  |  | // 	*dbx.DB
 | 
					
						
							|  |  |  | // 	CalledQueries []string
 | 
					
						
							|  |  |  | // }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // // NB! Don't forget to call `db.Close()` at the end of the test.
 | 
					
						
							|  |  |  | // func createTestDB() (*testDB, error) {
 | 
					
						
							|  |  |  | // 	sqlDB, err := sql.Open("sqlite", ":memory:")
 | 
					
						
							|  |  |  | // 	if err != nil {
 | 
					
						
							|  |  |  | // 		return nil, err
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 	db := testDB{DB: dbx.NewFromDB(sqlDB, "sqlite")}
 | 
					
						
							|  |  |  | // 	db.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
 | 
					
						
							|  |  |  | // 		db.CalledQueries = append(db.CalledQueries, sql)
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | // 	db.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
 | 
					
						
							|  |  |  | // 		db.CalledQueries = append(db.CalledQueries, sql)
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 	return &db, nil
 | 
					
						
							|  |  |  | // }
 |