Scert/backend/internal/database/migration_catalog_test.go

99 lines
3.5 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.

package database
import (
"bytes"
"fmt"
"io/fs"
"os"
"path"
"testing"
"testing/fstest"
)
func checkMigrationCatalog(source, embedded fs.FS) error {
for _, driver := range []string{"sqlite", "mysql", "postgres"} {
directory := "migrations/" + driver
for _, catalog := range []fs.FS{source, embedded} {
entries, err := fs.ReadDir(catalog, directory)
if err != nil {
return err
}
if len(entries) != latestMigrationVersion {
return fmt.Errorf("%s 的文件数 %d 与登记版本 %d 不一致", directory, len(entries), latestMigrationVersion)
}
for version, entry := range entries {
expected := fmt.Sprintf("%04d_%s.sql", version+1, migrationName(version+1))
if entry.IsDir() || entry.Name() != expected {
return fmt.Errorf("%s 的迁移文件应为 %s,实际为 %s", directory, expected, entry.Name())
}
}
}
for version := 1; version <= latestMigrationVersion; version++ {
name := path.Join(directory, fmt.Sprintf("%04d_%s.sql", version, migrationName(version)))
content, err := fs.ReadFile(source, name)
if err != nil {
return err
}
packed, err := fs.ReadFile(embedded, name)
if err != nil {
return err
}
if len(bytes.TrimSpace(content)) == 0 || !bytes.Equal(content, packed) {
return fmt.Errorf("%s 为空或与嵌入内容不一致", name)
}
}
}
return nil
}
func TestMigrationCatalogMatchesSourceAndVersionRegistry(t *testing.T) {
if err := checkMigrationCatalog(os.DirFS("."), migrationFiles); err != nil {
t.Fatal(err)
}
}
func TestMigrationCatalogDetectsIncompleteChanges(t *testing.T) {
cases := []struct {
name string
mutate func(fstest.MapFS, fstest.MapFS)
}{
{"unregistered_source", func(source, embedded fstest.MapFS) {
source[fmt.Sprintf("migrations/mysql/%04d_extra.sql", latestMigrationVersion+1)] = &fstest.MapFile{Data: []byte("SELECT 1;")}
}},
{"missing_embed", func(source, embedded fstest.MapFS) { delete(embedded, "migrations/postgres/0001_initial.sql") }},
{"missing_source", func(source, embedded fstest.MapFS) { delete(source, "migrations/sqlite/0001_initial.sql") }},
{"changed_embed", func(source, embedded fstest.MapFS) {
embedded["migrations/mysql/0001_initial.sql"] = &fstest.MapFile{Data: []byte("SELECT 2;")}
}},
{"empty_file", func(source, embedded fstest.MapFS) {
source["migrations/sqlite/0001_initial.sql"] = &fstest.MapFile{Data: []byte(" \n")}
}},
{"wrong_name", func(source, embedded fstest.MapFS) {
source["migrations/mysql/0001_wrong.sql"] = source["migrations/mysql/0001_initial.sql"]
delete(source, "migrations/mysql/0001_initial.sql")
}},
{"unregistered_embed", func(source, embedded fstest.MapFS) {
embedded[fmt.Sprintf("migrations/postgres/%04d_extra.sql", latestMigrationVersion+1)] = &fstest.MapFile{Data: []byte("SELECT 1;")}
}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
source, embedded := fstest.MapFS{}, fstest.MapFS{}
for _, driver := range []string{"sqlite", "mysql", "postgres"} {
for version := 1; version <= latestMigrationVersion; version++ {
name := fmt.Sprintf("migrations/%s/%04d_%s.sql", driver, version, migrationName(version))
source[name] = &fstest.MapFile{Data: []byte("SELECT 1;")}
embedded[name] = &fstest.MapFile{Data: []byte("SELECT 1;")}
}
}
if err := checkMigrationCatalog(source, embedded); err != nil {
t.Fatalf("测试基线无效:%v", err)
}
tc.mutate(source, embedded)
if err := checkMigrationCatalog(source, embedded); err == nil {
t.Fatal("未检出不完整的迁移变更")
}
})
}
}