Short and single-letter names in application code become full words (tests keep theirs). Wrapped calls, signatures and composite literals put one item per line, and one-line function bodies are expanded. Exported names and groups of constants get doc comments, and comments refer to the renamed identifiers.
182 lines
4.8 KiB
Go
182 lines
4.8 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")
|
|
}
|