Scert/backend/internal/cli/mysql_view_restore_test.go
xuans 777c7169b6 完善MySQL视图备份恢复及完整清单权限校验
保留原始视图定义与编码,按依赖排序导出,并严格识别和重排官方最终视图块;增加运行上下文和权限检查。补充视图、清单、恢复失败保护测试,并修正旧事件、触发器和例程测试的权限前提。

隔离MySQL 5.7和8.0完整专项分别通过176和202个叶子用例,无失败或跳过;后端常规全量、静态检查、模块校验及Linux测试包交叉编译通过。测试产物和凭据未提交。本提交不代表生产部署验收完成。
2026-09-28 00:35:26 +08:00

88 lines
3.3 KiB
Go

package cli
import (
"fmt"
"slices"
"testing"
)
func mysqlViewRestoreFixture(name, query string) []string {
plain := []string{
"DROP VIEW IF EXISTS " + quoteMySQLIdentifier(name),
"SET @saved_cs_client = @@character_set_client",
"SET @saved_cs_results = @@character_set_results",
"SET @saved_col_connection = @@collation_connection",
"SET character_set_client = utf8mb4",
"SET character_set_results = utf8mb4",
"SET collation_connection = utf8mb4_bin",
"CREATE ALGORITHM=MERGE SQL SECURITY INVOKER VIEW " + quoteMySQLIdentifier(name) + " AS " + query,
"SET character_set_client = @saved_cs_client",
"SET character_set_results = @saved_cs_results",
"SET collation_connection = @saved_col_connection",
}
for i := range plain {
plain[i] = "/*!50001 " + plain[i] + " */"
}
return plain
}
func TestMySQLViewRestoreBlock(t *testing.T) {
statements := mysqlViewRestoreFixture("a", "select * from z")
block, ok := readMySQLViewRestoreBlock(statements)
if !ok || block.name != "a" || block.charset != "utf8mb4" || !slices.Equal(block.statements, statements) {
t.Fatal("完整官方视图块未被原样识别")
}
for _, test := range []struct {
name string
at int
sql string
}{
{"missing_restore", 10, "SELECT 1"},
{"business_statement", 5, "/*!50001 DELETE FROM user_data */"},
{"global_setting", 4, "/*!50001 SET GLOBAL character_set_client = utf8mb4 */"},
{"setting_expression", 4, "/*!50001 SET character_set_client = CONCAT('utf8','mb4') */"},
{"unknown_restore", 8, "/*!50001 SET character_set_client = @another */"},
{"extra_assignment", 8, "/*!50001 SET character_set_client = @saved_cs_client, sql_mode = '' */"},
{"split_identifier", 8, "/*!50001 SET character_set_ client = @saved_cs_client */"},
{"plain_create", 7, "CREATE VIEW `a` AS SELECT 1"},
{"future_version", 7, "/*!99999 CREATE ALGORITHM=MERGE VIEW `a` AS SELECT 1 */"},
{"qualified_drop", 0, "/*!50001 DROP VIEW IF EXISTS `db`.`a` */"},
{"multiple_drops", 0, "/*!50001 DROP VIEW IF EXISTS `a`, `b` */"},
} {
t.Run(test.name, func(t *testing.T) {
changed := slices.Clone(statements)
changed[test.at] = test.sql
if _, ok := readMySQLViewRestoreBlock(changed); ok {
t.Fatal("非闭合官方视图块不能被重排")
}
})
}
for length := 0; length < 11; length++ {
t.Run(fmt.Sprintf("truncated_%d", length), func(t *testing.T) {
if _, ok := readMySQLViewRestoreBlock(statements[:length]); ok {
t.Fatal("截断视图块不能被重排")
}
})
}
}
func TestMySQLViewDumpWrapper(t *testing.T) {
for _, source := range []string{
"/*!50001 CREATE ALGORITHM=MERGE */\n/*!50013 DEFINER=`a`@`b` SQL SECURITY INVOKER */\n/*!50001 VIEW `a` AS SELECT 1 */",
"/*!50001 CREATE ALGORITHM=MERGE VIEW `a` AS SELECT 1 */ /*!50002 WITH LOCAL CHECK OPTION */",
} {
plain, ok := unwrapMySQLViewDumpStatement(source)
if !ok {
t.Fatal("官方版本化定义无法展开")
}
if name, err := mysqlViewCreateName(plain); err != nil || name != "a" {
t.Fatalf("视图名称识别失败:%v", err)
}
}
for _, source := range []string{"", "CREATE VIEW `a` AS SELECT 1", "/*!50001 unfinished", "/*!99999 VIEW a */", "/*!50001SELECT 1 */", "/*!50001 VIEW a */; DELETE FROM t", "/* plain */"} {
if _, ok := unwrapMySQLViewDumpStatement(source); ok {
t.Fatal("未知封装被识别成官方视图定义")
}
}
}