package migrate import ( "bytes" "errors" "strings" "testing" "time" "github.com/pressly/goose/v3" ) // errWrite is the error failingWriter fails every write with. var errWrite = errors.New("write failed") // The commands are tested through the real command tree in cmd/jobsearch. // These tests cover reporting a failed migration, which a working set of // migrations can't trigger. func result(path, direction string) *goose.MigrationResult { return &goose.MigrationResult{ Source: &goose.Source{ Path: "migrations/" + path, }, Direction: direction, Duration: 1500 * time.Microsecond, } } func TestFailurePartial(t *testing.T) { cause := errors.New("no such table: missing") var out bytes.Buffer err := failure(&out, &goose.PartialError{ Applied: []*goose.MigrationResult{result("00001_first.sql", "up")}, Failed: result("00002_second.sql", "up"), Err: cause, }) if want := " applied 00001_first.sql (2ms)\n"; out.String() != want { t.Errorf("output = %q, want %q", out.String(), want) } if !errors.Is(err, cause) { t.Errorf("err = %v, want it to wrap the migration error", err) } for _, want := range []string{ "00002_second.sql", "rolled back", "last version that succeeded", } { if err == nil || !strings.Contains(err.Error(), want) { t.Errorf("err = %v, want it to mention %q", err, want) } } } func TestFailureWithoutFailedSource(t *testing.T) { err := failure(&bytes.Buffer{}, &goose.PartialError{ Err: errors.New("boom"), }) if err == nil || !strings.Contains(err.Error(), "unknown migration") { t.Errorf("err = %v, want unknown migration", err) } } func TestFailureOtherError(t *testing.T) { cause := errors.New("database is locked") var out bytes.Buffer if err := failure( &out, cause, ); err != cause { //nolint:errorlint // checks the error is returned unchanged t.Errorf("err = %v, want the error unchanged", err) } if out.Len() != 0 { t.Errorf("printed %q for a non-migration error", out.String()) } } func TestPrintResults(t *testing.T) { var out bytes.Buffer err := printResults(&out, []*goose.MigrationResult{ result("00001_first.sql", "up"), result("00002_second.sql", "down"), }) if err != nil { t.Fatal(err) } want := " applied 00001_first.sql (2ms)\n" + " reverted 00002_second.sql (2ms)\n" if out.String() != want { t.Errorf("output = %q, want %q", out.String(), want) } } // failingWriter fails every write. type failingWriter struct{} func (failingWriter) Write([]byte) (int, error) { return 0, errWrite } // A write error is returned, and the failed migration is still reported. func TestWriteErrors(t *testing.T) { results := []*goose.MigrationResult{result("00001_first.sql", "up")} if err := printResults(failingWriter{}, results); !errors.Is(err, errWrite) { t.Errorf("printResults: err = %v, want the write error", err) } cause := errors.New("boom") err := failure(failingWriter{}, &goose.PartialError{ Applied: results, Failed: result("00002_second.sql", "up"), Err: cause, }) if !errors.Is(err, cause) || !errors.Is(err, errWrite) { t.Errorf("failure: err = %v, want both the migration and write errors", err) } }