fix(migrate): validate migration directory before provider creation
This commit is contained in:
1 parent
d912b475b5
commit
3d903403c8
2 files changed
+49
No files matched your search
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
// New returns a provider for SQL files in dir and optional provider-local Go
|
// New returns a provider for SQL files in dir and optional provider-local Go
|
||||||
// migrations. Pass "." for files at the embedded root and a nonnil logger.
|
// migrations. Pass "." for files at the embedded root and a nonnil logger.
|
||||||
|
// The selected directory must exist, including when using only Go migrations.
|
||||||
// Construction does not connect to db; the caller owns and closes db.
|
// Construction does not connect to db; the caller owns and closes db.
|
||||||
//
|
//
|
||||||
// PostgreSQL uses Goose's session advisory locker with its default lock ID and
|
// PostgreSQL uses Goose's session advisory locker with its default lock ID and
|
||||||
@@ -36,6 +37,13 @@ func New(db *sql.DB, dialect goose.Dialect, files embed.FS, dir string, logger *
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("migrate: directory %q: %w", dir, err)
|
return nil, fmt.Errorf("migrate: directory %q: %w", dir, err)
|
||||||
}
|
}
|
||||||
|
info, err := fs.Stat(root, ".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("migrate: directory %q: %w", dir, err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil, fmt.Errorf("migrate: directory %q: not a directory", dir)
|
||||||
|
}
|
||||||
opts := []goose.ProviderOption{
|
opts := []goose.ProviderOption{
|
||||||
goose.WithDisableGlobalRegistry(true),
|
goose.WithDisableGlobalRegistry(true),
|
||||||
goose.WithGoMigrations(migrations...),
|
goose.WithGoMigrations(migrations...),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -414,3 +415,43 @@ func TestConstructor(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConstructorDirectory(t *testing.T) {
|
||||||
|
db, err := sql.Open("sqlite", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Directory validation and Go-only construction must remain offline.
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
|
for _, test := range []struct {
|
||||||
|
dir string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{dir: "missing", wantErr: "directory"},
|
||||||
|
{dir: "testdata/migrations/00007_events.sql", wantErr: "not a directory"},
|
||||||
|
{dir: "."},
|
||||||
|
{dir: "testdata"},
|
||||||
|
} {
|
||||||
|
t.Run(test.dir, func(t *testing.T) {
|
||||||
|
p, err := migrate.New(db, goose.DialectSQLite3, fixtures, test.dir, logger, goose.NewGoMigration(10, nil, nil))
|
||||||
|
if test.wantErr != "" {
|
||||||
|
if err == nil || p != nil || !strings.Contains(err.Error(), test.wantErr) || !strings.Contains(err.Error(), test.dir) {
|
||||||
|
t.Fatalf("New = %v, %v; want directory error containing %q and %q", p, err, test.wantErr, test.dir)
|
||||||
|
}
|
||||||
|
if test.dir == "missing" && !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
t.Fatalf("New error = %v; want fs.ErrNotExist", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sources := p.ListSources(); len(sources) != 1 || sources[0].Version != 10 {
|
||||||
|
t.Fatalf("ListSources = %v; want Go migration 10 only", sources)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user