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

269 lines
7.9 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 app
import (
"context"
"errors"
"os"
"strings"
"testing"
"time"
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/internal/db"
"GoNavi-Wails/internal/logger"
syncbackend "GoNavi-Wails/internal/sync"
"GoNavi-Wails/shared/i18n"
)
func appFunctionSource(t *testing.T, source string, signature string) string {
t.Helper()
start := strings.Index(source, signature)
if start < 0 {
t.Fatalf("app.go missing function signature %q", signature)
}
rest := source[start+len(signature):]
end := strings.Index(rest, "\nfunc ")
if end < 0 {
return source[start:]
}
return source[start : start+len(signature)+end]
}
func expectedLocalizedAppMessage(base string) string {
path := strings.TrimSpace(logger.Path())
if path == "" {
return base
}
info, err := os.Stat(path)
if err != nil || info.IsDir() || info.Size() <= 0 {
return base
}
return base + defaultAppText("driver_manager.backend.message.log_hint", map[string]any{"path": path})
}
func TestAppUserVisibleConnectionMessagesUseLocalizedText(t *testing.T) {
sourceBytes, err := os.ReadFile("app.go")
if err != nil {
t.Fatalf("read app.go: %v", err)
}
source := string(sourceBytes)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func wrapConnectError": {
rawMessages: []string{
`fmt.Errorf("数据库连接超时:%s %s:%d/%s%w", config.Type, config.Host, config.Port, dbName, err)`,
},
keys: []string{
"db.backend.message.connect_timeout_detail",
},
},
"func (a *App) getDatabaseWithPing": {
rawMessages: []string{
`fmt.Sprintf("连接最近失败,正在冷却中,请 %s 后重试;上次错误:%s",`,
},
keys: []string{
"db.backend.message.connect_failure_cooldown",
},
},
"func (a *App) CancelQuery": {
rawMessages: []string{
`Message: "查询已取消"`,
`Message: "查询不存在或已完成"`,
},
keys: []string{
"query_editor.message.cancel_success",
"query_editor.message.cancel_no_running",
},
},
"func (a *App) ResetWebViewZoom() (result connection.QueryResult)": {
rawMessages: []string{
`Message: fmt.Sprintf("重置 WebView2 zoom 失败:%v", recovered)`,
},
keys: []string{
"app.backend.error.reset_webview_zoom_failed",
},
},
}
for signature, check := range checks {
body := appFunctionSource(t, source, signature)
for _, raw := range check.rawMessages {
if strings.Contains(body, raw) {
t.Fatalf("%s still contains raw user-visible message %q", signature, raw)
}
}
for _, key := range check.keys {
if !strings.Contains(body, key) {
t.Fatalf("%s should reference localized key %q", signature, key)
}
}
}
}
func TestAppBackendCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"app.backend.error.reset_webview_zoom_failed",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing app backend key %q", language, key)
}
}
}
}
func TestWrapConnectError_UsesCurrentLanguageForTimeoutWrapper(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageEnUS))
})
config := connection.ConnectionConfig{
Type: "postgres",
Host: "127.0.0.1",
Port: 5432,
Database: "crm",
}
wrapped := wrapConnectError(config, context.DeadlineExceeded)
text := wrapped.Error()
expected := defaultAppText("db.backend.message.connect_timeout_detail", map[string]any{
"dbType": config.Type,
"host": config.Host,
"port": config.Port,
"database": config.Database,
"detail": context.DeadlineExceeded.Error(),
})
expected = expectedLocalizedAppMessage(expected)
if text != expected {
t.Fatalf("expected localized timeout wrapper %q, got %q", expected, text)
}
if strings.Contains(text, "数据库连接超时") {
t.Fatalf("expected no raw Chinese timeout wrapper, got %q", text)
}
}
func TestCancelQuery_UsesCurrentLanguageForMessages(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageEnUS))
})
missing := app.CancelQuery("missing-query")
if expected := app.appText("query_editor.message.cancel_no_running", nil); missing.Message != expected {
t.Fatalf("expected missing query message %q, got %q", expected, missing.Message)
}
queryID := app.GenerateQueryID()
_, cancel := context.WithCancel(context.Background())
app.queryMu.Lock()
app.runningQueries[queryID] = queryContext{
cancel: cancel,
started: time.Now(),
}
app.queryMu.Unlock()
cancelled := app.CancelQuery(queryID)
if expected := app.appText("query_editor.message.cancel_success", nil); cancelled.Message != expected {
t.Fatalf("expected cancel success message %q, got %q", expected, cancelled.Message)
}
}
func TestGetDatabaseWithPing_UsesCurrentLanguageForFailureCooldown(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
defer func() {
newDatabaseFunc = originalNewDatabaseFunc
resolveDialConfigWithProxyFunc = originalResolveDialConfigWithProxyFunc
}()
newDatabaseFunc = func(dbType string) (db.Database, error) {
return &fakeStartupRetryDB{
connect: func(config connection.ConnectionConfig) error {
return errors.New("dial tcp 10.1.131.86:5432: connect: connection refused")
},
}, nil
}
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
return raw, nil
}
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageEnUS))
})
app.startedAt = time.Now().Add(-startupConnectRetryWindow - time.Second)
config := connection.ConnectionConfig{Type: "postgres", Host: "10.1.131.86", Port: 5432, User: "postgres"}
_, firstErr := app.getDatabaseWithPing(config, false)
if firstErr == nil {
t.Fatal("expected first connection attempt to fail")
}
key := getCacheKey(config)
failure, remaining, ok := app.getCachedConnectFailureByKey(key)
if !ok {
t.Fatal("expected cached failure after first connect attempt")
}
_, secondErr := app.getDatabaseWithPing(config, false)
if secondErr == nil {
t.Fatal("expected second connection attempt to hit cooldown")
}
expected := app.appText("db.backend.message.connect_failure_cooldown", map[string]any{
"remaining": formatConnectFailureCooldown(remaining),
"detail": normalizeErrorMessage(unwrapLogHintError(failure.err)),
})
expected = expectedLocalizedAppMessage(expected)
if secondErr.Error() != expected {
t.Fatalf("expected localized cooldown message %q, got %q", expected, secondErr.Error())
}
if strings.Contains(secondErr.Error(), "连接最近失败,正在冷却中") {
t.Fatalf("expected no raw Chinese cooldown wrapper, got %q", secondErr.Error())
}
}
func TestAppSetLanguageUpdatesDataSyncBackendLanguage(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
result := syncbackend.NewSyncEngine(syncbackend.Reporter{}).Analyze(syncbackend.SyncConfig{
SourceQuery: "SELECT id FROM active_users",
Content: "schema",
Tables: []string{"users"},
})
if result.Success {
t.Fatalf("expected source query analyze validation failure, got %+v", result)
}
localizer, err := i18n.NewLocalizer(i18n.LanguageEnUS)
if err != nil {
t.Fatalf("NewLocalizer(en-US) error = %v", err)
}
want := localizer.T("data_sync.backend.validation.query_mode_data_only", nil)
if result.Message != want {
t.Fatalf("expected App.SetLanguage to propagate English DataSync backend message %q, got %q", want, result.Message)
}
if strings.Contains(result.Message, "SQL 结果集同步当前仅支持") {
t.Fatalf("expected no raw Chinese DataSync validation message, got %q", result.Message)
}
}