Files
gocqlx/migrate/migrate_test.go
Michał Matczuk fe1498fe13 migrate: add support for new Go io/fs.FS
This patch adds FromFS function and lets you use the new go:embed directive to add the migration files to the binary.
Also, migration tests use an in memory FS impl instead of working with tmp directories.
2021-04-30 11:19:14 +02:00

268 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (C) 2017 ScyllaDB
// Use of this source code is governed by a ALv2-style
// license that can be found in the LICENSE file.
// +build all integration
package migrate_test
import (
"context"
"fmt"
"io/fs"
"strings"
"testing"
"github.com/psanford/memfs"
"github.com/scylladb/gocqlx/v2"
. "github.com/scylladb/gocqlx/v2/gocqlxtest"
"github.com/scylladb/gocqlx/v2/migrate"
)
var migrateSchema = `
CREATE TABLE IF NOT EXISTS gocqlx_test.migrate_table (
testint int,
testuuid timeuuid,
PRIMARY KEY(testint, testuuid)
)
`
var insertMigrate = `INSERT INTO gocqlx_test.migrate_table (testint, testuuid) VALUES (%d, now())`
func recreateTables(tb testing.TB, session gocqlx.Session) {
tb.Helper()
if err := session.ExecStmt("DROP TABLE IF EXISTS gocqlx_test.gocqlx_migrate"); err != nil {
tb.Fatal(err)
}
if err := session.ExecStmt(migrateSchema); err != nil {
tb.Fatal(err)
}
if err := session.ExecStmt("TRUNCATE gocqlx_test.migrate_table"); err != nil {
tb.Fatal(err)
}
}
func TestMigration(t *testing.T) {
session := CreateSession(t)
defer session.Close()
recreateTables(t, session)
ctx := context.Background()
t.Run("init", func(t *testing.T) {
if err := migrate.FromFS(ctx, session, makeTestFS(2)); err != nil {
t.Fatal(err)
}
if c := countMigrations(t, session); c != 2 {
t.Fatal("expected 2 migration got", c)
}
})
t.Run("update", func(t *testing.T) {
if err := migrate.FromFS(ctx, session, makeTestFS(4)); err != nil {
t.Fatal(err)
}
if c := countMigrations(t, session); c != 4 {
t.Fatal("expected 4 migration got", c)
}
})
t.Run("ahead", func(t *testing.T) {
err := migrate.FromFS(ctx, session, makeTestFS(2))
if err == nil || !strings.Contains(err.Error(), "ahead") {
t.Fatal("expected error")
} else {
t.Log(err)
}
})
t.Run("tempered with file", func(t *testing.T) {
f := makeTestFS(4)
writeFile(f, 3, "SELECT * FROM bla;")
if err := migrate.FromFS(ctx, session, f); err == nil || !strings.Contains(err.Error(), "tempered") {
t.Fatal("expected error")
} else {
t.Log(err)
}
})
}
func TestMigrationNoSemicolon(t *testing.T) {
session := CreateSession(t)
defer session.Close()
recreateTables(t, session)
if err := session.ExecStmt(migrateSchema); err != nil {
t.Fatal(err)
}
f := makeTestFS(0)
f.WriteFile("0.cql", []byte(fmt.Sprintf(insertMigrate, 0)+";"+fmt.Sprintf(insertMigrate, 1)), fs.ModePerm)
ctx := context.Background()
if err := migrate.FromFS(ctx, session, f); err != nil {
t.Fatal(err)
}
if c := countMigrations(t, session); c != 2 {
t.Fatal("expected 2 migration got", c)
}
}
func TestIsCallback(t *testing.T) {
table := []struct {
Name string
Stmt string
Cb string
}{
{
Name: "CQL statement",
Stmt: "SELECT * from X;",
},
{
Name: "CQL comment",
Stmt: "-- Item",
},
{
Name: "CALL without space",
Stmt: "--CALL Foo;",
Cb: "Foo",
},
{
Name: "CALL with space",
Stmt: "-- CALL Foo;",
Cb: "Foo",
},
{
Name: "CALL with many spaces",
Stmt: "-- CALL Foo;",
Cb: "Foo",
},
{
Name: "CALL with many spaces 2",
Stmt: "-- CALL Foo;",
Cb: "Foo",
},
{
Name: "CALL with unicode",
Stmt: "-- CALL α;",
Cb: "α",
},
}
for i := range table {
test := table[i]
t.Run(test.Name, func(t *testing.T) {
if migrate.IsCallback(test.Stmt) != test.Cb {
t.Errorf("IsCallback(%s)=%s, expected %s", test.Stmt, migrate.IsCallback(test.Stmt), test.Cb)
}
})
}
}
func TestMigrationCallback(t *testing.T) {
var (
beforeCalled int
afterCalled int
inCalled int
)
migrate.Callback = func(ctx context.Context, session gocqlx.Session, ev migrate.CallbackEvent, name string) error {
switch ev {
case migrate.BeforeMigration:
beforeCalled += 1
case migrate.AfterMigration:
afterCalled += 1
case migrate.CallComment:
inCalled += 1
}
return nil
}
defer func() {
migrate.Callback = nil
}()
reset := func() {
beforeCalled = 0
afterCalled = 0
inCalled = 0
}
assertCallbacks := func(t *testing.T, before, afer, in int) {
if beforeCalled != before {
t.Fatalf("expected %d before calls got %d", before, beforeCalled)
}
if afterCalled != afer {
t.Fatalf("expected %d after calls got %d", afer, afterCalled)
}
if inCalled != in {
t.Fatalf("expected %d in calls got %d", in, inCalled)
}
}
session := CreateSession(t)
defer session.Close()
recreateTables(t, session)
if err := session.ExecStmt(migrateSchema); err != nil {
t.Fatal(err)
}
ctx := context.Background()
t.Run("init", func(t *testing.T) {
f := makeTestFS(2)
reset()
if err := migrate.FromFS(ctx, session, f); err != nil {
t.Fatal(err)
}
assertCallbacks(t, 2, 2, 0)
})
t.Run("no duplicate calls", func(t *testing.T) {
f := makeTestFS(4)
reset()
if err := migrate.FromFS(ctx, session, f); err != nil {
t.Fatal(err)
}
assertCallbacks(t, 2, 2, 0)
})
t.Run("in calls", func(t *testing.T) {
f := makeTestFS(4)
writeFile(f, 4, "\n-- CALL Foo;\n")
writeFile(f, 5, "\n-- CALL Bar;\n")
reset()
if err := migrate.FromFS(ctx, session, f); err != nil {
t.Fatal(err)
}
assertCallbacks(t, 2, 2, 2)
})
}
func countMigrations(tb testing.TB, session gocqlx.Session) int {
tb.Helper()
var v int
if err := session.Query("SELECT COUNT(*) FROM gocqlx_test.migrate_table", nil).Get(&v); err != nil {
tb.Fatal(err)
}
return v
}
func makeTestFS(n int) *memfs.FS {
f := memfs.New()
for i := 0; i < n; i++ {
writeFile(f, i, fmt.Sprintf(insertMigrate, i)+";")
}
return f
}
func writeFile(f *memfs.FS, i int, text string) {
f.WriteFile(fmt.Sprint(i, ".cql"), []byte(text), fs.ModePerm)
}