Smail/backend/services/dbmigration/portable_character_test.go
smail 4d9c938221
Some checks are pending
Security Checks / security (push) Waiting to run
修复跨引擎定长字符校验并补齐双SQL实机验收
2026-09-21 07:35:41 +08:00

104 lines
4.3 KiB
Go

package dbmigration
import (
"context"
"database/sql"
"reflect"
"testing"
"git.pcux.cn/xuans/Smail/config"
)
// 用真实行扫描接口重放云端驱动类型;这些用例不能代替双 SQL 实机验收。
func TestPortableFixedCharacterDriverValues(t *testing.T) {
for _, item := range []struct {
name string
kind config.DatabaseType
columnType string
input, want any
}{
{"postgres_empty", config.DatabaseTypePostgreSQL, "BPCHAR", []byte(" "), []byte("")},
{"postgres_token", config.DatabaseTypePostgreSQL, "BPCHAR", []byte("abc "), []byte("abc")},
{"postgres_string", config.DatabaseTypePostgreSQL, "BPCHAR", "abc ", "abc"},
{"postgres_null", config.DatabaseTypePostgreSQL, "BPCHAR", nil, nil},
{"mysql_padding", config.DatabaseTypeMySQL, "CHAR", []byte("abc "), []byte("abc")},
{"postgres_leading_space", config.DatabaseTypePostgreSQL, "BPCHAR", []byte(" abc "), []byte(" abc")},
{"postgres_other_whitespace", config.DatabaseTypePostgreSQL, "BPCHAR", []byte("abc\t\n\u00a0 "), []byte("abc\t\n\u00a0")},
{"postgres_varchar", config.DatabaseTypePostgreSQL, "VARCHAR", "abc ", "abc "},
{"mysql_varchar", config.DatabaseTypeMySQL, "VARCHAR", []byte("abc "), []byte("abc ")},
{"postgres_text", config.DatabaseTypePostgreSQL, "TEXT", "abc ", "abc "},
{"postgres_binary", config.DatabaseTypePostgreSQL, "BYTEA", []byte{0, 255, 32, 32}, []byte{0, 255, 32, 32}},
{"mysql_binary", config.DatabaseTypeMySQL, "BLOB", []byte{0, 255, 32}, []byte{0, 255, 32}},
{"sqlite_char", config.DatabaseTypeSQLite, "CHAR", "abc ", "abc "},
{"sqlite_bpchar", config.DatabaseTypeSQLite, "BPCHAR", []byte("abc "), []byte("abc ")},
} {
t.Run(item.name, func(t *testing.T) {
database := portableCharacterFixture(t, item.columnType, item.input)
count := 0
err := visitPortableRows(context.Background(), database, item.kind, "sample", []string{"value"}, func(values []any) error {
count++
if !reflect.DeepEqual(values[0], item.want) {
t.Fatal("定长字符填充或非定长数据处理错误")
}
return nil
})
if err != nil || count != 1 {
t.Fatal("驱动类型重放未完成")
}
})
}
}
func TestPortableFixedCharacterDigest(t *testing.T) {
for _, item := range []struct {
name string
sourceType, targetType string
source, target any
equal bool
}{
{"empty_padding", "CHAR", "BPCHAR", []byte(""), []byte(" "), true},
{"token_padding", "CHAR", "BPCHAR", []byte("abc"), []byte("abc "), true},
{"changed_token", "CHAR", "BPCHAR", []byte("abc"), []byte("abd "), false},
{"leading_space", "CHAR", "BPCHAR", []byte("abc"), []byte(" abc "), false},
{"null_not_empty", "CHAR", "BPCHAR", nil, []byte(" "), false},
{"varchar_space", "VARCHAR", "VARCHAR", []byte("abc"), "abc ", false},
{"text_space", "TEXT", "TEXT", []byte("abc"), "abc ", false},
{"binary_space", "BLOB", "BYTEA", []byte{0, 255}, []byte{0, 255, 32}, false},
} {
t.Run(item.name, func(t *testing.T) {
source := portableCharacterFixture(t, item.sourceType, item.source)
target := portableCharacterFixture(t, item.targetType, item.target)
types := map[string]string{"value": item.targetType}
left, err := digestPortableTable(context.Background(), source, config.DatabaseTypeMySQL, "sample", []string{"value"}, types, config.DatabaseTypePostgreSQL)
if err != nil {
t.Fatal("源摘要计算失败")
}
right, err := digestPortableTable(context.Background(), target, config.DatabaseTypePostgreSQL, "sample", []string{"value"}, types, config.DatabaseTypePostgreSQL)
if err != nil {
t.Fatal("目标摘要计算失败")
}
if (left == right) != item.equal {
t.Fatal("摘要未区分填充与真实数据差异")
}
})
}
}
func portableCharacterFixture(t *testing.T, columnType string, value any) *sql.DB {
t.Helper()
database, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatal("创建重放数据库失败")
}
database.SetMaxOpenConns(1)
t.Cleanup(func() { _ = database.Close() })
// 类型来自本文件固定用例,不接受外部输入。
if _, err := database.Exec("CREATE TABLE sample (value " + columnType + ")"); err != nil {
t.Fatal("创建重放表失败")
}
if _, err := database.Exec("INSERT INTO sample(value) VALUES (?)", value); err != nil {
t.Fatal("写入重放值失败")
}
return database
}