goose/internal/provider/provider_test.go

154 lines
4.5 KiB
Go

package provider_test
import (
"context"
"database/sql"
"errors"
"io/fs"
"path/filepath"
"testing"
"testing/fstest"
"github.com/pressly/goose/v3/database"
"github.com/pressly/goose/v3/internal/check"
"github.com/pressly/goose/v3/internal/provider"
_ "modernc.org/sqlite"
)
func TestProvider(t *testing.T) {
dir := t.TempDir()
db, err := sql.Open("sqlite", filepath.Join(dir, "sql_embed.db"))
check.NoError(t, err)
t.Run("empty", func(t *testing.T) {
_, err := provider.NewProvider(database.DialectSQLite3, db, fstest.MapFS{})
check.HasError(t, err)
check.Bool(t, errors.Is(err, provider.ErrNoMigrations), true)
})
mapFS := fstest.MapFS{
"migrations/001_foo.sql": {Data: []byte(`-- +goose Up`)},
"migrations/002_bar.sql": {Data: []byte(`-- +goose Up`)},
}
fsys, err := fs.Sub(mapFS, "migrations")
check.NoError(t, err)
p, err := provider.NewProvider(database.DialectSQLite3, db, fsys)
check.NoError(t, err)
sources := p.ListSources()
check.Equal(t, len(sources), 2)
check.Equal(t, sources[0], newSource(provider.TypeSQL, "001_foo.sql", 1))
check.Equal(t, sources[1], newSource(provider.TypeSQL, "002_bar.sql", 2))
t.Run("duplicate_go", func(t *testing.T) {
// Not parallel because it modifies global state.
register := []*provider.MigrationCopy{
{
Version: 1, Source: "00001_users_table.go", Registered: true,
UpFnContext: nil,
DownFnContext: nil,
},
}
err := provider.SetGlobalGoMigrations(register)
check.NoError(t, err)
t.Cleanup(provider.ResetGlobalGoMigrations)
db := newDB(t)
_, err = provider.NewProvider(database.DialectSQLite3, db, nil,
provider.WithGoMigration(1, nil, nil),
)
check.HasError(t, err)
check.Equal(t, err.Error(), "go migration with version 1 already registered")
})
t.Run("empty_go", func(t *testing.T) {
db := newDB(t)
// explicit
_, err := provider.NewProvider(database.DialectSQLite3, db, nil,
provider.WithGoMigration(1, &provider.GoMigrationFunc{Run: nil}, &provider.GoMigrationFunc{Run: nil}),
)
check.HasError(t, err)
check.Contains(t, err.Error(), "go migration with version 1 must have an up function")
})
t.Run("duplicate_up", func(t *testing.T) {
err := provider.SetGlobalGoMigrations([]*provider.MigrationCopy{
{
Version: 1, Source: "00001_users_table.go", Registered: true,
UpFnContext: func(context.Context, *sql.Tx) error { return nil },
UpFnNoTxContext: func(ctx context.Context, db *sql.DB) error { return nil },
},
})
t.Cleanup(provider.ResetGlobalGoMigrations)
check.HasError(t, err)
check.Contains(t, err.Error(), "must specify exactly one of UpFnContext or UpFnNoTxContext")
})
t.Run("duplicate_down", func(t *testing.T) {
err := provider.SetGlobalGoMigrations([]*provider.MigrationCopy{
{
Version: 1, Source: "00001_users_table.go", Registered: true,
DownFnContext: func(context.Context, *sql.Tx) error { return nil },
DownFnNoTxContext: func(ctx context.Context, db *sql.DB) error { return nil },
},
})
t.Cleanup(provider.ResetGlobalGoMigrations)
check.HasError(t, err)
check.Contains(t, err.Error(), "must specify exactly one of DownFnContext or DownFnNoTxContext")
})
t.Run("not_registered", func(t *testing.T) {
err := provider.SetGlobalGoMigrations([]*provider.MigrationCopy{
{
Version: 1, Source: "00001_users_table.go",
},
})
t.Cleanup(provider.ResetGlobalGoMigrations)
check.HasError(t, err)
check.Contains(t, err.Error(), "migration must be registered")
})
t.Run("zero_not_allowed", func(t *testing.T) {
err := provider.SetGlobalGoMigrations([]*provider.MigrationCopy{
{
Version: 0,
},
})
t.Cleanup(provider.ResetGlobalGoMigrations)
check.HasError(t, err)
check.Contains(t, err.Error(), "migration versions must be greater than zero")
})
}
var (
migration1 = `
-- +goose Up
CREATE TABLE foo (id INTEGER PRIMARY KEY);
-- +goose Down
DROP TABLE foo;
`
migration2 = `
-- +goose Up
ALTER TABLE foo ADD COLUMN name TEXT;
-- +goose Down
ALTER TABLE foo DROP COLUMN name;
`
migration3 = `
-- +goose Up
CREATE TABLE bar (
id INTEGER PRIMARY KEY,
description TEXT
);
-- +goose Down
DROP TABLE bar;
`
migration4 = `
-- +goose Up
-- Rename the 'foo' table to 'my_foo'
ALTER TABLE foo RENAME TO my_foo;
-- Add a new column 'timestamp' to 'my_foo'
ALTER TABLE my_foo ADD COLUMN timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
-- +goose Down
-- Remove the 'timestamp' column from 'my_foo'
ALTER TABLE my_foo DROP COLUMN timestamp;
-- Rename the 'my_foo' table back to 'foo'
ALTER TABLE my_foo RENAME TO foo;
`
)