Files
MyGoNavi/internal/sync/direct_import_integrity_test.go
Syngnat bdd84db52c 🐛 fix(data-sync): 修复跨库同步静默失败与数据安全问题
- 聚合表级失败并保留已确认提交计数,避免任务误报成功

- 绑定分析上下文与实际同步模式,阻止陈旧或无效配置执行

- 加固全量覆盖及同端点分页写入,补充跨库与失败回归测试

Refs #846
2026-08-05 23:55:44 +08:00

124 lines
3.9 KiB
Go

package sync
import (
"errors"
"strings"
"testing"
"GoNavi-Wails/internal/connection"
)
type directImportIntegrityTargetDB struct {
fakeQuerySyncTargetDB
getColumnsErr error
execErr error
execs []string
}
func (d *directImportIntegrityTargetDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
if d.getColumnsErr != nil {
return nil, d.getColumnsErr
}
return d.fakeQuerySyncTargetDB.GetColumns(dbName, tableName)
}
func (d *directImportIntegrityTargetDB) Exec(query string) (int64, error) {
d.execs = append(d.execs, query)
if d.execErr != nil {
return 0, d.execErr
}
return 0, nil
}
func (d *directImportIntegrityTargetDB) clearedTarget() bool {
for _, query := range d.execs {
upper := strings.ToUpper(query)
if strings.Contains(upper, "TRUNCATE TABLE") || strings.Contains(upper, "DELETE FROM") {
return true
}
}
return false
}
func TestDirectImportFullOverwriteValidatesTargetColumnsBeforeClearing(t *testing.T) {
engine := &SyncEngine{}
source := &fakeMigrationDB{}
target := &directImportIntegrityTargetDB{getColumnsErr: errors.New("metadata unavailable")}
config := SyncConfig{
JobID: "direct-import-columns",
Mode: "full_overwrite",
SourceConfig: connection.ConnectionConfig{Type: "mysql", Host: "source", Database: "src"},
TargetConfig: connection.ConnectionConfig{Type: "mysql", Host: "target", Database: "dst"},
}
plan := SchemaMigrationPlan{
SourceQueryTable: "src.users",
TargetQueryTable: "dst.users",
TargetSchema: "dst",
TargetTable: "users",
TargetTableExists: true,
}
sourceCols := []connection.ColumnDefinition{{Name: "id", Type: "bigint", Key: "PRI"}}
handled, inserted, err := engine.tryApplyDirectImportInPages(
config, &SyncResult{}, 0, 1, "users", source, target, plan,
sourceCols, nil, TableOptions{Insert: true}, "mysql", "mysql", "users",
)
if !handled {
t.Fatal("direct import should handle the request")
}
if err == nil || !strings.Contains(err.Error(), "metadata unavailable") {
t.Fatalf("expected target column metadata error, got %v", err)
}
if inserted != 0 {
t.Fatalf("expected no inserted rows, got %d", inserted)
}
if target.clearedTarget() {
t.Fatalf("target table was cleared before column validation: %v", target.execs)
}
}
func TestDirectImportFullOverwriteStopsBeforeClearingWhenAutoAddColumnFails(t *testing.T) {
engine := &SyncEngine{}
source := &fakeMigrationDB{}
target := &directImportIntegrityTargetDB{execErr: errors.New("add column rejected")}
config := SyncConfig{
JobID: "direct-import-auto-add",
Mode: "full_overwrite",
AutoAddColumns: true,
SourceConfig: connection.ConnectionConfig{Type: "mysql", Host: "source", Database: "src"},
TargetConfig: connection.ConnectionConfig{Type: "mysql", Host: "target", Database: "dst"},
}
plan := SchemaMigrationPlan{
SourceQueryTable: "src.users",
TargetQueryTable: "dst.users",
TargetSchema: "dst",
TargetTable: "users",
TargetTableExists: true,
}
sourceCols := []connection.ColumnDefinition{
{Name: "id", Type: "bigint", Key: "PRI"},
{Name: "name", Type: "varchar(128)"},
}
targetCols := []connection.ColumnDefinition{{Name: "id", Type: "bigint", Key: "PRI"}}
handled, inserted, err := engine.tryApplyDirectImportInPages(
config, &SyncResult{}, 0, 1, "users", source, target, plan,
sourceCols, targetCols, TableOptions{Insert: true}, "mysql", "mysql", "users",
)
if !handled {
t.Fatal("direct import should handle the request")
}
if err == nil || !strings.Contains(err.Error(), "add column rejected") {
t.Fatalf("expected auto-add column error, got %v", err)
}
if inserted != 0 {
t.Fatalf("expected no inserted rows, got %d", inserted)
}
if len(target.appliedChanges.Inserts) != 0 {
t.Fatalf("rows were applied after auto-add failure: %+v", target.appliedChanges.Inserts)
}
if target.clearedTarget() {
t.Fatalf("target table was cleared after auto-add failure: %v", target.execs)
}
}