98 lines
3.4 KiB
Go
98 lines
3.4 KiB
Go
package migrations
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
func TestEmbeddedBundleMatchesCanonicalSQL(t *testing.T) {
|
|
all, err := discover(sqlFiles)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
canonical, err := filepath.Glob("../../../database/migrations/*.sql")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(canonical) != len(all) {
|
|
t.Fatalf("canonical count %d differs from embedded count %d", len(canonical), len(all))
|
|
}
|
|
for i, path := range canonical {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
version := strings.TrimSuffix(filepath.Base(path), ".sql")
|
|
if all[i].version != version || !bytes.Equal(data, []byte(all[i].sql)) {
|
|
t.Fatalf("embedded migration %s differs from canonical SQL", version)
|
|
}
|
|
checksum := sha256.Sum256(data)
|
|
if all[i].checksum != hex.EncodeToString(checksum[:]) {
|
|
t.Fatalf("checksum differs for %s", version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiscoverMigrations(t *testing.T) {
|
|
fsys := fstest.MapFS{
|
|
"sql/0002.sql": {Data: []byte("select 2;\n")},
|
|
"sql/0001.sql": {Data: []byte("select 1;\n")},
|
|
}
|
|
all, err := discover(fsys)
|
|
if err != nil || len(all) != 2 || all[0].version != "0001" || all[1].version != "0002" {
|
|
t.Fatalf("discover: %+v, %v", all, err)
|
|
}
|
|
}
|
|
|
|
func TestOuterTransactionRemoval(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in, want string
|
|
}{
|
|
{"-- title\n\nBEGIN;\nselect 1;\nCOMMIT;\n", "-- title\n\nselect 1;\n"},
|
|
{"do $$ begin raise notice 'x'; end $$;", "do $$ begin raise notice 'x'; end $$;"},
|
|
} {
|
|
got, err := withoutOuterTransaction(tc.in)
|
|
if err != nil || got != tc.want {
|
|
t.Fatalf("got %q, %v; want %q", got, err, tc.want)
|
|
}
|
|
}
|
|
for _, sql := range []string{"begin;\nselect 1;", "select 1;\ncommit;"} {
|
|
if _, err := withoutOuterTransaction(sql); err == nil {
|
|
t.Fatalf("accepted unmatched transaction: %q", sql)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunDisabledAndConfiguration(t *testing.T) {
|
|
getter := func(mapper map[string]string) func(string) string {
|
|
return func(key string) string { return mapper[key] }
|
|
}
|
|
if err := Run(context.Background(), getter(nil)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
env map[string]string
|
|
want string
|
|
}{
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "yes"}, "must be true or false"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "local"}, "requires ZHINIAN_DATA_BACKEND=postgres"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "postgres", "DATABASE_MIGRATION_TIMEOUT_SECONDS": "0"}, "must be an integer"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "postgres", "NODE_ENV": "production"}, "DATABASE_APP_ROLE is required"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "postgres"}, "DATABASE_MIGRATION_URL or DATABASE_URL is required"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "postgres", "DATABASE_APP_ROLE": "bad;role"}, "DATABASE_APP_ROLE must match"},
|
|
{map[string]string{"DATABASE_AUTO_MIGRATE": "true", "ZHINIAN_DATA_BACKEND": "postgres", "DATABASE_MIGRATION_URL": "postgres://admin:secret@localhost/db1", "DATABASE_URL": "postgres://app:secret@localhost/db2"}, "must target the same"},
|
|
} {
|
|
err := Run(context.Background(), getter(tc.env))
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("env %+v: got %v, want %q", tc.env, err, tc.want)
|
|
}
|
|
}
|
|
}
|