mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-13 00:13:33 +08:00
269 lines
7.9 KiB
Go
269 lines
7.9 KiB
Go
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)
|
||
}
|
||
}
|