package migrate import ( "fmt" "path/filepath" "testing" "github.com/spf13/cobra" "git.avsdev.uk/Go/job-search/internal/cli" "git.avsdev.uk/Go/job-search/internal/cli/cmdtest" "git.avsdev.uk/Go/job-search/internal/store" ) // Run with: go test -tags sqlite_fts5 ./... // // These tests run the migrate commands as a user would, through a root // command like job-search's (see cmdtest). // dbConfig is a config.yaml with a database in the test's directory. const dbConfig = "logging:\n level: info\ndatabase:\n file: ./test.db\n" func newRoot() *cobra.Command { opts := &cli.Options{} return cmdtest.NewRoot(opts, NewCommand(opts)) } func run(t *testing.T, stdin string, args ...string) (string, error) { t.Helper() return cmdtest.Run(t, newRoot, stdin, args...) } func mustRun(t *testing.T, args ...string) string { t.Helper() return cmdtest.MustRun(t, newRoot, args...) } // latestVersion is the schema version of this build's newest migration. func latestVersion(t *testing.T) int64 { t.Helper() db, err := store.OpenDB(filepath.Join(t.TempDir(), "version.db")) if err != nil { t.Fatal(err) } defer db.Close() m, err := store.NewMigrator(db) if err != nil { t.Fatal(err) } return store.LatestVersion(m) } func TestMigrateUpCreateThenStatus(t *testing.T) { cmdtest.Setup(t, dbConfig) latest := latestVersion(t) out := mustRun(t, "migrate", "up", "--create") cmdtest.Contains(t, out, "applied 00001_create_opportunities_table.sql", fmt.Sprintf("Database migrated to version %d", latest), ) if !cmdtest.Exists("test.db") { t.Fatal("test.db was not created") } out = mustRun(t, "migrate", "status") cmdtest.Contains(t, out, fmt.Sprintf("Database version %d of %d (up-to-date)", latest, latest), "00001_create_opportunities_table.sql", ) out = mustRun(t, "migrate", "up") cmdtest.Contains(t, out, "No changes made to database (up-to-date)") } // Plain "migrate" is "migrate up" with every pending migration. func TestMigrateWithoutSubcommand(t *testing.T) { cmdtest.Setup(t, dbConfig) cmdtest.WriteFile(t, "test.db", "") // exists, so no question is asked out := mustRun(t, "migrate") cmdtest.Contains(t, out, fmt.Sprintf("Database migrated to version %d", latestVersion(t))) } func TestMigrateUpAsksBeforeCreating(t *testing.T) { cmdtest.Setup(t, dbConfig) out, err := run(t, "n\n", "migrate", "up") cmdtest.ErrContains(t, err, "no database file") cmdtest.Contains(t, out, "Database file './test.db' does not exist. Would you like to create it?") if cmdtest.Exists("test.db") { t.Error("test.db was created after answering no") } out, err = run(t, "y\n", "migrate", "up") if err != nil { t.Fatalf("answering yes: %v\n%s", err, out) } if !cmdtest.Exists("test.db") { t.Error("test.db was not created after answering yes") } } func TestMigrateUpBySteps(t *testing.T) { cmdtest.Setup(t, dbConfig) latest := latestVersion(t) out := mustRun(t, "migrate", "up", "--create", "--steps", "2") cmdtest.Contains(t, out, "Database migrated to version 2") out = mustRun(t, "migrate", "status") cmdtest.Contains(t, out, fmt.Sprintf("Database version 2 of %d (%d pending)", latest, latest-2)) } func TestMigrateDown(t *testing.T) { cmdtest.Setup(t, dbConfig) latest := latestVersion(t) mustRun(t, "migrate", "up", "--create") out := mustRun(t, "migrate", "down", "--steps", "1") cmdtest.Contains(t, out, "reverted", fmt.Sprintf("Database migrated to version %d", latest-1)) // Rolling back everything deletes all data, so it must be confirmed. for _, args := range [][]string{ {"migrate", "down", "--steps", "0"}, {"migrate", "down", "--steps", "99"}, } { _, err := run(t, "", args...) cmdtest.ErrContains(t, err, "pass the --yes flag") } cmdtest.Contains(t, mustRun(t, "migrate", "status"), fmt.Sprintf("Database version %d of %d", latest-1, latest)) out = mustRun(t, "migrate", "down", "--steps", "0", "--yes") cmdtest.Contains(t, out, "Database cleared") out = mustRun(t, "migrate", "down", "--steps", "1") cmdtest.Contains(t, out, "No changes made to database (empty)") } func TestMigrateDownRequiresSteps(t *testing.T) { cmdtest.Setup(t, dbConfig) mustRun(t, "migrate", "up", "--create") _, err := run(t, "", "migrate", "down") cmdtest.ErrContains(t, err, "steps") } func TestMigrateMissingDatabase(t *testing.T) { cmdtest.Setup(t, dbConfig) for _, args := range [][]string{ {"migrate", "status"}, {"migrate", "down", "--steps", "1"}, } { _, err := run(t, "", args...) cmdtest.ErrContains(t, err, "does not exist") } if cmdtest.Exists("test.db") { t.Error("status or down created the database") } } func TestMigrateWithoutDatabaseConfigured(t *testing.T) { cmdtest.Setup(t, "logging:\n level: info\n") _, err := run(t, "", "migrate", "status") cmdtest.ErrContains(t, err, "no database file specified") }