gci is configured with the sections standard, default and localmodule, and goimports with the module path as its local prefix, so both formatters agree.
169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
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")
|
|
}
|