Files
NianAIGC/backend/internal/migrations/migrations_test.go
T
2026-10-12 13:57:23 +08:00

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)
}
}
}