Files
MyGoNavi/internal/sync/sync_engine_i18n_test.go
2026-06-22 15:12:42 +08:00

263 lines
8.5 KiB
Go

package sync
import (
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/shared/i18n"
"errors"
"os"
"strings"
"testing"
)
func baseRunSyncI18nConfig() SyncConfig {
return SyncConfig{
SourceConfig: connection.ConnectionConfig{Type: "mysql", Database: "app", Host: "source-host"},
TargetConfig: connection.ConnectionConfig{Type: "mysql", Database: "app", Host: "target-host"},
Tables: []string{"users"},
Content: "schema",
Mode: "insert_update",
JobID: "sync-engine-i18n-job",
}
}
func TestSyncEngineUsesLocalizedProgressAndFailureSourceGuard(t *testing.T) {
sourceBytes, err := os.ReadFile("sync_engine.go")
if err != nil {
t.Fatalf("read sync_engine.go: %v", err)
}
source := string(sourceBytes)
legacyMessages := []string{
`"开始同步"`,
`"连接源数据库"`,
`"连接目标数据库"`,
`fmt.Sprintf("同步表(%d/%d)", i+1, totalTables)`,
`"表处理完成"`,
`"同步完成"`,
`"同步失败"`,
`"初始化源数据库驱动失败: "+err.Error()`,
`"初始化目标数据库驱动失败: "+err.Error()`,
`"源数据库连接失败: "+err.Error()`,
`"目标数据库连接失败: "+err.Error()`,
}
for _, legacy := range legacyMessages {
if strings.Contains(source, legacy) {
t.Fatalf("sync_engine.go still contains legacy raw user-visible message %q", legacy)
}
}
requiredKeys := []string{
"data_sync.progress.stage.sync_started",
"data_sync.progress.stage.connecting_source",
"data_sync.progress.stage.connecting_target",
"data_sync.progress.stage.syncing_table",
"data_sync.progress.stage.table_completed",
"data_sync.progress.stage.completed",
"data_sync.progress.stage.failed",
"data_sync.backend.error.init_source_driver_failed",
"data_sync.backend.error.init_target_driver_failed",
"data_sync.backend.error.connect_source_failed",
"data_sync.backend.error.connect_target_failed",
}
for _, key := range requiredKeys {
if !strings.Contains(source, key) {
t.Fatalf("sync_engine.go should reference localized key %q", key)
}
}
}
func TestSyncEngineProgressAndFailureCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"data_sync.progress.stage.sync_started",
"data_sync.progress.stage.connecting_source",
"data_sync.progress.stage.connecting_target",
"data_sync.progress.stage.syncing_table",
"data_sync.progress.stage.table_completed",
"data_sync.progress.stage.completed",
"data_sync.progress.stage.failed",
"data_sync.backend.error.init_source_driver_failed",
"data_sync.backend.error.init_target_driver_failed",
"data_sync.backend.error.connect_source_failed",
"data_sync.backend.error.connect_target_failed",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing sync engine key %q", language, key)
}
}
}
}
func TestRunSyncUsesCurrentLanguageForInitAndConnectFailures(t *testing.T) {
SetBackendLanguage(i18n.LanguageEnUS)
t.Cleanup(func() {
SetBackendLanguage(i18n.LanguageZhCN)
})
wantStart := localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.sync_started", nil)
wantConnectingSource := localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.connecting_source", nil)
wantConnectingTarget := localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.connecting_target", nil)
wantFailed := localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.failed", nil)
cases := []struct {
name string
steps []syncDatabaseFactoryStep
wantKey string
wantParams map[string]any
wantStages []string
}{
{
name: "init source driver failed",
steps: []syncDatabaseFactoryStep{
{err: errors.New("source init boom")},
},
wantKey: "data_sync.backend.error.init_source_driver_failed",
wantParams: map[string]any{
"detail": "source init boom",
},
wantStages: []string{wantStart, wantFailed},
},
{
name: "init target driver failed",
steps: []syncDatabaseFactoryStep{
{db: &fakeMigrationDB{}},
{err: errors.New("target init boom")},
},
wantKey: "data_sync.backend.error.init_target_driver_failed",
wantParams: map[string]any{
"detail": "target init boom",
},
wantStages: []string{wantStart, wantFailed},
},
{
name: "connect source failed",
steps: []syncDatabaseFactoryStep{
{db: &connectErrorMigrationDB{connectErr: errors.New("source connect boom")}},
{db: &fakeMigrationDB{}},
},
wantKey: "data_sync.backend.error.connect_source_failed",
wantParams: map[string]any{
"detail": "source connect boom",
},
wantStages: []string{wantStart, wantConnectingSource, wantFailed},
},
{
name: "connect target failed",
steps: []syncDatabaseFactoryStep{
{db: &fakeMigrationDB{}},
{db: &connectErrorMigrationDB{connectErr: errors.New("target connect boom")}},
},
wantKey: "data_sync.backend.error.connect_target_failed",
wantParams: map[string]any{
"detail": "target connect boom",
},
wantStages: []string{wantStart, wantConnectingSource, wantConnectingTarget, wantFailed},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
useSyncDatabaseFactorySequence(t, tc.steps...)
var stages []string
engine := NewSyncEngine(Reporter{
OnProgress: func(event SyncProgressEvent) {
stages = append(stages, event.Stage)
},
})
result := engine.RunSync(baseRunSyncI18nConfig())
if result.Success {
t.Fatalf("expected RunSync failure for %s, got %+v", tc.name, result)
}
want := localizedSyncTestText(t, i18n.LanguageEnUS, tc.wantKey, tc.wantParams)
if result.Message != want {
t.Fatalf("expected localized RunSync failure message %q, got %q", want, result.Message)
}
if !strings.EqualFold(strings.TrimSpace(stages[len(stages)-1]), strings.TrimSpace(wantFailed)) {
t.Fatalf("expected final progress stage %q, got %v", wantFailed, stages)
}
if len(stages) != len(tc.wantStages) {
t.Fatalf("unexpected progress stage count: got=%v want=%v", stages, tc.wantStages)
}
for idx, wantStage := range tc.wantStages {
if stages[idx] != wantStage {
t.Fatalf("stage[%d] = %q, want %q; all stages=%v", idx, stages[idx], wantStage, stages)
}
}
for _, stage := range stages {
assertNoLegacySourceQueryChinese(t, stage)
}
})
}
}
func TestRunSyncUsesCurrentLanguageForOverallProgressStages(t *testing.T) {
SetBackendLanguage(i18n.LanguageEnUS)
t.Cleanup(func() {
SetBackendLanguage(i18n.LanguageZhCN)
})
sourceDB := &fakeMigrationDB{
columns: map[string][]connection.ColumnDefinition{
"app.users": {
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
{Name: "name", Type: "varchar(255)", Nullable: "YES"},
},
},
}
targetDB := &fakeMigrationDB{
columns: map[string][]connection.ColumnDefinition{
"app.users": {
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
{Name: "name", Type: "varchar(255)", Nullable: "YES"},
},
},
}
useSyncDatabaseFactorySequence(t,
syncDatabaseFactoryStep{db: sourceDB},
syncDatabaseFactoryStep{db: targetDB},
)
var stages []string
engine := NewSyncEngine(Reporter{
OnProgress: func(event SyncProgressEvent) {
stages = append(stages, event.Stage)
},
})
result := engine.RunSync(baseRunSyncI18nConfig())
if !result.Success {
t.Fatalf("expected RunSync success, got %+v", result)
}
if result.TablesSynced != 1 {
t.Fatalf("expected TablesSynced=1, got %+v", result)
}
wantStages := []string{
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.sync_started", nil),
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.connecting_source", nil),
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.connecting_target", nil),
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.syncing_table", map[string]any{"current": 1, "total": 1}),
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.table_completed", nil),
localizedSyncTestText(t, i18n.LanguageEnUS, "data_sync.progress.stage.completed", nil),
}
if len(stages) != len(wantStages) {
t.Fatalf("unexpected progress stage count: got=%v want=%v", stages, wantStages)
}
for idx, wantStage := range wantStages {
if stages[idx] != wantStage {
t.Fatalf("stage[%d] = %q, want %q; all stages=%v", idx, stages[idx], wantStage, stages)
}
assertNoLegacySourceQueryChinese(t, stages[idx])
}
}