Files
MyGoNavi/internal/app/methods_driver_i18n_test.go
2026-06-23 14:04:25 +08:00

1698 lines
60 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 (
"errors"
"fmt"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"GoNavi-Wails/internal/db"
"GoNavi-Wails/internal/logger"
"GoNavi-Wails/shared/i18n"
)
func methodsDriverFunctionSource(t *testing.T, source string, signature string) string {
t.Helper()
start := strings.Index(source, signature)
if start < 0 {
t.Fatalf("methods driver source 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]
}
type timeoutDriverNetworkError struct{}
func (timeoutDriverNetworkError) Error() string { return "dial timeout" }
func (timeoutDriverNetworkError) Timeout() bool { return true }
func (timeoutDriverNetworkError) Temporary() bool { return true }
var _ net.Error = timeoutDriverNetworkError{}
func TestMethodsDriverNetworkBackendMessagesUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func probeDriverNetworkEndpoint": {
rawMessages: []string{`probed.Error = "检测地址为空"`},
keys: []string{"driver_manager.backend.network.error.probe_url_empty"},
},
"func resolveDriverProbeDialAddress": {
rawMessages: []string{
`fmt.Errorf("检测地址为空")`,
`fmt.Errorf("检测地址缺少主机")`,
},
keys: []string{
"driver_manager.backend.network.error.probe_url_empty",
"driver_manager.backend.network.error.probe_host_missing",
},
},
"func normalizeDriverNetworkError": {
rawMessages: []string{`return "网络连接超时"`},
keys: []string{"driver_manager.backend.network.error.timeout"},
},
"func driverLogHint": {
rawMessages: []string{`fmt.Sprintf("(详细日志:%s", path)`},
keys: []string{"driver_manager.backend.message.log_hint"},
},
"func logDriverOperationError": {
rawMessages: []string{`message = "未知错误"`},
keys: []string{"driver_manager.backend.error.unknown"},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw driver network text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference driver network i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverNetworkBackendCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.network.error.probe_url_empty",
"driver_manager.backend.network.error.probe_host_missing",
"driver_manager.backend.network.error.timeout",
"driver_manager.backend.message.log_hint",
"driver_manager.backend.error.unknown",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing driver network key %q", language, key)
}
}
}
}
func TestMethodsDriverReleaseHelpersUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func loadReleaseAssetSizesCached": {
rawMessages: []string{`fmt.Errorf("缓存 key 为空")`},
keys: []string{"driver_manager.backend.error.cache_key_empty"},
},
"func fetchDriverReleaseList": {
rawMessages: []string{
`fmt.Errorf("拉取驱动版本列表失败HTTP %d", resp.StatusCode)`,
`fmt.Errorf("解析驱动版本列表失败:%w", err)`,
},
keys: []string{
"driver_manager.backend.error.driver_version_list_fetch_failed",
"driver_manager.backend.error.driver_version_list_parse_failed",
},
},
"func fetchDriverBundleAssetSizeIndex": {
rawMessages: []string{
`fmt.Errorf("release 为空")`,
`fmt.Errorf("未找到驱动总包索引资产")`,
`fmt.Errorf("拉取驱动总包索引失败HTTP %d")`,
`fmt.Errorf("解析驱动总包索引失败:%w")`,
`fmt.Errorf("驱动总包索引为空")`,
},
keys: []string{
"driver_manager.backend.error.release_empty",
"driver_manager.backend.error.bundle_index_asset_missing",
"driver_manager.backend.error.bundle_index_fetch_failed",
"driver_manager.backend.error.bundle_index_parse_failed",
"driver_manager.backend.error.bundle_index_empty",
},
},
"func fetchReleaseByTag": {
rawMessages: []string{`fmt.Errorf("Tag 为空")`},
keys: []string{"driver_manager.backend.error.tag_empty"},
},
"func fetchDriverReleaseByURL": {
rawMessages: []string{
`fmt.Errorf("API 地址为空")`,
`fmt.Errorf("拉取 Release 信息失败HTTP %d")`,
},
keys: []string{
"driver_manager.backend.error.api_url_empty",
"driver_manager.backend.error.release_info_fetch_failed",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw release helper text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference release helper i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverInstallActionDetailsUseEnglishInternalWrappers(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
internalDetails []string
}{
"func (a *App) InstallLocalDriverPackage": {
rawMessages: []string{
`"导入本地驱动包失败driver=%s file=%s"`,
`"写入本地驱动元数据失败driver=%s"`,
},
internalDetails: []string{
`"failed to import local driver package, driver=%s file=%s"`,
`"failed to write local driver metadata, driver=%s"`,
},
},
"func (a *App) DownloadDriverPackage": {
rawMessages: []string{
`"驱动下载安装失败driver=%s version=%s url=%s"`,
`"写入驱动元数据失败driver=%s version=%s"`,
},
internalDetails: []string{
`"failed to download and install driver, driver=%s version=%s url=%s"`,
`"failed to write driver metadata, driver=%s version=%s"`,
},
},
"func (a *App) RemoveDriverPackage": {
rawMessages: []string{
`"移除驱动包失败driver=%s path=%s"`,
},
internalDetails: []string{
`"failed to remove driver package, driver=%s path=%s"`,
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw install action detail wrapper %q", signature, rawMessage)
}
}
for _, internalDetail := range check.internalDetails {
if !strings.Contains(functionSource, internalDetail) {
t.Fatalf("%s does not contain English internal detail wrapper %q", signature, internalDetail)
}
}
}
}
func TestMethodsDriverReleaseHelperCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.cache_key_empty",
"driver_manager.backend.error.driver_version_list_fetch_failed",
"driver_manager.backend.error.driver_version_list_parse_failed",
"driver_manager.backend.error.release_empty",
"driver_manager.backend.error.bundle_index_asset_missing",
"driver_manager.backend.error.bundle_index_fetch_failed",
"driver_manager.backend.error.bundle_index_parse_failed",
"driver_manager.backend.error.bundle_index_empty",
"driver_manager.backend.error.tag_empty",
"driver_manager.backend.error.api_url_empty",
"driver_manager.backend.error.release_info_fetch_failed",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing release helper key %q", language, key)
}
}
}
}
func TestResolveDriverDownloadDirectoryUsesCurrentLanguageForCreateDirectoryFailure(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
blocker := filepath.Join(t.TempDir(), "driver-root-blocker")
if err := os.WriteFile(blocker, []byte("blocker"), 0o644); err != nil {
t.Fatalf("write blocker file: %v", err)
}
result := app.ResolveDriverDownloadDirectory(filepath.Join(blocker, "nested"))
if result.Success {
t.Fatalf("expected resolve driver directory failure, got %+v", result)
}
if !strings.Contains(result.Message, "Failed to create driver directory:") {
t.Fatalf("expected English create-directory wrapper, got %q", result.Message)
}
if strings.Contains(result.Message, "\u521b\u5efa\u9a71\u52a8\u76ee\u5f55\u5931\u8d25") {
t.Fatalf("expected no Chinese create-directory wrapper in en-US mode, got %q", result.Message)
}
}
func TestProbeDriverNetworkEndpointUsesCurrentLanguageForEmptyURL(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
probed := probeDriverNetworkEndpoint(nil, driverNetworkProbeItem{URL: " "})
if probed.Error != "Probe URL is empty" {
t.Fatalf("expected English probe URL message, got %q", probed.Error)
}
if strings.Contains(probed.Error, "检测地址为空") {
t.Fatalf("expected no Chinese probe URL message in en-US mode, got %q", probed.Error)
}
}
func TestResolveDriverProbeDialAddressUsesCurrentLanguageForMissingHost(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
_, err := resolveDriverProbeDialAddress("https:///driver")
if err == nil {
t.Fatal("expected probe host validation error")
}
if err.Error() != "Probe URL is missing a host" {
t.Fatalf("expected English probe host message, got %q", err.Error())
}
if strings.Contains(err.Error(), "检测地址缺少主机") {
t.Fatalf("expected no Chinese probe host message in en-US mode, got %q", err.Error())
}
}
func TestNormalizeDriverNetworkErrorUsesCurrentLanguageForTimeout(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
text := normalizeDriverNetworkError(timeoutDriverNetworkError{})
if text != "Network connection timed out" {
t.Fatalf("expected English network timeout message, got %q", text)
}
if strings.Contains(text, "网络连接超时") {
t.Fatalf("expected no Chinese timeout message in en-US mode, got %q", text)
}
}
func TestDriverOperationLegacyHelpersUseCurrentLanguageForUnknownAndLogHint(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
if strings.TrimSpace(logger.Path()) == "" {
t.Skip("logger path unavailable")
}
hint := driverLogHint()
if !strings.Contains(hint, "detail log:") {
t.Fatalf("expected English driver log hint, got %q", hint)
}
if strings.Contains(hint, "详细日志") {
t.Fatalf("expected no Chinese driver log hint in en-US mode, got %q", hint)
}
text := logDriverOperationError(errors.New(""), "test driver error")
if !strings.Contains(text, "Unknown error") {
t.Fatalf("expected English unknown driver error fallback, got %q", text)
}
if !strings.Contains(text, "detail log:") {
t.Fatalf("expected English detail log hint in legacy driver error text, got %q", text)
}
if strings.Contains(text, "未知错误") || strings.Contains(text, "详细日志") {
t.Fatalf("expected no Chinese legacy driver error text in en-US mode, got %q", text)
}
}
func TestLoadReleaseAssetSizesCachedUsesCurrentLanguageForEmptyCacheKey(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
_, _, err := loadReleaseAssetSizesCached(" ", func() (*githubRelease, error) {
t.Fatal("fetch should not be called for empty cache key")
return nil, nil
})
if err == nil {
t.Fatal("expected empty cache key error")
}
if err.Error() != "Cache key is empty" {
t.Fatalf("expected English cache key message, got %q", err.Error())
}
if strings.Contains(err.Error(), "缓存 key 为空") {
t.Fatalf("expected no Chinese cache key message in en-US mode, got %q", err.Error())
}
}
func TestFetchDriverBundleAssetSizeIndexUsesCurrentLanguageForStructuredErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
disableGlobalProxyForTest(t)
makeRelease := func(downloadURL string) *githubRelease {
return &githubRelease{
Assets: []githubAsset{{
Name: optionalDriverBundleIndexAssetName,
BrowserDownloadURL: downloadURL,
}},
}
}
server500 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "nope", http.StatusInternalServerError)
}))
defer server500.Close()
serverInvalidJSON := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("{invalid"))
}))
defer serverInvalidJSON.Close()
serverEmptyIndex := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`{"assets":{}}`))
}))
defer serverEmptyIndex.Close()
cases := []struct {
name string
release *githubRelease
want string
avoid string
}{
{
name: "nil release",
release: nil,
want: "Release is empty",
avoid: "release 为空",
},
{
name: "missing bundle index asset",
release: &githubRelease{},
want: "Driver bundle index asset was not found",
avoid: "未找到驱动总包索引资产",
},
{
name: "http status failure",
release: makeRelease(server500.URL),
want: "Failed to fetch driver bundle index: HTTP 500",
avoid: "拉取驱动总包索引失败",
},
{
name: "parse failure",
release: makeRelease(serverInvalidJSON.URL),
want: "Failed to parse driver bundle index:",
avoid: "解析驱动总包索引失败",
},
{
name: "empty index",
release: makeRelease(serverEmptyIndex.URL),
want: "Driver bundle index is empty",
avoid: "驱动总包索引为空",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := fetchDriverBundleAssetSizeIndex(tc.release)
if err == nil {
t.Fatal("expected structured release index error")
}
if !strings.Contains(err.Error(), tc.want) {
t.Fatalf("expected %q in English release index error, got %q", tc.want, err.Error())
}
if strings.Contains(err.Error(), tc.avoid) {
t.Fatalf("expected no Chinese release index error in en-US mode, got %q", err.Error())
}
})
}
}
func TestFetchReleaseByTagUsesCurrentLanguageForEmptyTag(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
_, err := fetchReleaseByTag(" ")
if err == nil {
t.Fatal("expected empty tag error")
}
if err.Error() != "Tag is empty" {
t.Fatalf("expected English empty tag message, got %q", err.Error())
}
if strings.Contains(err.Error(), "Tag 为空") {
t.Fatalf("expected no Chinese empty tag message in en-US mode, got %q", err.Error())
}
}
func TestFetchDriverReleaseByURLUsesCurrentLanguageForStructuredErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
disableGlobalProxyForTest(t)
_, err := fetchDriverReleaseByURL(" ")
if err == nil {
t.Fatal("expected empty API URL error")
}
if err.Error() != "API URL is empty" {
t.Fatalf("expected English API URL message, got %q", err.Error())
}
if strings.Contains(err.Error(), "API 地址为空") {
t.Fatalf("expected no Chinese API URL message in en-US mode, got %q", err.Error())
}
server500 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "nope", http.StatusInternalServerError)
}))
defer server500.Close()
_, err = fetchDriverReleaseByURL(server500.URL)
if err == nil {
t.Fatal("expected Release information fetch failure")
}
want := fmt.Sprintf("Failed to fetch Release information: HTTP %d", http.StatusInternalServerError)
if err.Error() != want {
t.Fatalf("expected English Release information fetch message %q, got %q", want, err.Error())
}
if strings.Contains(err.Error(), "拉取 Release 信息失败") {
t.Fatalf("expected no Chinese Release information fetch message in en-US mode, got %q", err.Error())
}
}
func TestMethodsDriverVersionOptionErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
functionSource := methodsDriverFunctionSource(t, source, "func resolveDriverVersionOptions")
for _, rawMessage := range []string{
`fmt.Errorf("驱动类型为空")`,
`fmt.Errorf("未找到可用驱动版本")`,
} {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("resolveDriverVersionOptions still contains raw version option text %q", rawMessage)
}
}
for _, key := range []string{
"driver_manager.backend.error.driver_type_empty",
"driver_manager.backend.error.no_driver_versions",
} {
if !strings.Contains(functionSource, key) {
t.Fatalf("resolveDriverVersionOptions does not reference version option i18n key %q", key)
}
}
}
func TestMethodsDriverVersionOptionErrorCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.driver_type_empty",
"driver_manager.backend.error.no_driver_versions",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing version option error key %q", language, key)
}
}
}
}
func TestResolveDriverVersionOptionsUsesCurrentLanguageForStructuredErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := resolveDriverVersionOptions(driverDefinition{}, "", app.appText)
if err == nil {
t.Fatal("expected empty driver type error")
}
if err.Error() != "Driver type is empty" {
t.Fatalf("expected English driver type message, got %q", err.Error())
}
if strings.Contains(err.Error(), "驱动类型为空") {
t.Fatalf("expected no Chinese driver type message in en-US mode, got %q", err.Error())
}
originalModulePath, hadModulePath := driverGoModulePathMap["mongodb"]
originalAliasPaths, hadAliasPaths := driverGoModuleAliasPathMap["mongodb"]
originalFallbackVersions, hadFallbackVersions := fallbackRecentDriverVersionsMap["mongodb"]
originalLatestVersion, hadLatestVersion := latestDriverVersionMap["mongodb"]
delete(driverGoModulePathMap, "mongodb")
delete(driverGoModuleAliasPathMap, "mongodb")
delete(fallbackRecentDriverVersionsMap, "mongodb")
delete(latestDriverVersionMap, "mongodb")
t.Cleanup(func() {
if hadModulePath {
driverGoModulePathMap["mongodb"] = originalModulePath
} else {
delete(driverGoModulePathMap, "mongodb")
}
if hadAliasPaths {
driverGoModuleAliasPathMap["mongodb"] = originalAliasPaths
} else {
delete(driverGoModuleAliasPathMap, "mongodb")
}
if hadFallbackVersions {
fallbackRecentDriverVersionsMap["mongodb"] = originalFallbackVersions
} else {
delete(fallbackRecentDriverVersionsMap, "mongodb")
}
if hadLatestVersion {
latestDriverVersionMap["mongodb"] = originalLatestVersion
} else {
delete(latestDriverVersionMap, "mongodb")
}
})
_, err = resolveDriverVersionOptions(
driverDefinition{Type: "mongodb", Name: "MongoDB"},
"unsupported://manifest",
app.appText,
)
if err == nil {
t.Fatal("expected missing driver version options error")
}
if err.Error() != "No available driver versions were found" {
t.Fatalf("expected English version options message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未找到可用驱动版本") {
t.Fatalf("expected no Chinese version options message in en-US mode, got %q", err.Error())
}
}
func TestMethodsDriverModuleVersionFetchErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
functionSource := methodsDriverFunctionSource(t, source, "func fetchGoModuleVersionMetas(modulePath string)")
for _, rawMessage := range []string{
`fmt.Errorf("模块路径为空")`,
`fmt.Errorf("拉取模块版本列表失败HTTP %d", resp.StatusCode)`,
`fmt.Errorf("读取模块版本列表失败:%w", err)`,
`fmt.Errorf("模块版本列表为空")`,
} {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("fetchGoModuleVersionMetas still contains raw module-version text %q", rawMessage)
}
}
for _, key := range []string{
"driver_manager.backend.error.module_path_empty",
"driver_manager.backend.error.module_version_list_fetch_failed",
"driver_manager.backend.error.module_version_list_read_failed",
"driver_manager.backend.error.module_version_list_empty",
} {
if !strings.Contains(functionSource, key) {
t.Fatalf("fetchGoModuleVersionMetas does not reference module-version i18n key %q", key)
}
}
}
func TestMethodsDriverModuleVersionFetchErrorCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.module_path_empty",
"driver_manager.backend.error.module_version_list_fetch_failed",
"driver_manager.backend.error.module_version_list_read_failed",
"driver_manager.backend.error.module_version_list_empty",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing module-version fetch key %q", language, key)
}
}
}
}
func TestFetchGoModuleVersionMetasUsesCurrentLanguageForEmptyModulePath(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := fetchGoModuleVersionMetas(" ")
if err == nil {
t.Fatal("expected empty module path error")
}
if err.Error() != "Module path is empty" {
t.Fatalf("expected English module path message, got %q", err.Error())
}
if strings.Contains(err.Error(), "模块路径为空") {
t.Fatalf("expected no Chinese module path message in en-US mode, got %q", err.Error())
}
}
func TestMethodsDriverBundleAcquireErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
functionSource := methodsDriverFunctionSource(t, source, "func acquireOptionalDriverBundlePath(bundleURL string, onProgress func(downloaded, total int64), onWaiting func()) (string, error)")
if strings.Contains(functionSource, `fmt.Errorf("驱动总包下载地址为空")`) {
t.Fatalf("acquireOptionalDriverBundlePath still contains raw bundle URL text")
}
if !strings.Contains(functionSource, "driver_manager.backend.error.bundle_url_empty") {
t.Fatalf("acquireOptionalDriverBundlePath does not reference bundle URL i18n key")
}
}
func TestAcquireOptionalDriverBundlePathUsesCurrentLanguageForEmptyURL(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := acquireOptionalDriverBundlePath(" ", nil, nil)
if err == nil {
t.Fatal("expected empty bundle URL error")
}
if err.Error() != "Driver bundle download URL is empty" {
t.Fatalf("expected English bundle URL message, got %q", err.Error())
}
if strings.Contains(err.Error(), "驱动总包下载地址为空") {
t.Fatalf("expected no Chinese bundle URL message in en-US mode, got %q", err.Error())
}
}
func TestMethodsDriverManifestErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func resolveDriverRepositoryURL": {
rawMessages: []string{
`fmt.Errorf("无效的文件清单地址")`,
`fmt.Errorf("不支持的内置清单地址:%s", parsed.String())`,
`fmt.Errorf("不支持的清单地址协议:%s", parsed.Scheme)`,
},
keys: []string{
"driver_manager.backend.error.file_manifest_url_invalid",
"driver_manager.backend.message.unsupported_builtin_manifest_url",
"driver_manager.backend.error.manifest_scheme_unsupported",
},
},
"func loadManifestPackageAndVersions": {
rawMessages: []string{`fmt.Errorf("解析驱动清单失败:%w", err)`},
keys: []string{"driver_manager.backend.error.manifest_parse_failed"},
},
"func loadManifestContent": {
rawMessages: []string{
`fmt.Errorf("驱动清单地址为空")`,
`fmt.Errorf("拉取驱动清单失败HTTP %d", resp.StatusCode)`,
`fmt.Errorf("驱动清单超过大小限制")`,
`fmt.Errorf("无效的本地驱动清单地址")`,
`fmt.Errorf("不支持的内置清单地址:%s", parsed.String())`,
},
keys: []string{
"driver_manager.backend.error.manifest_url_empty",
"driver_manager.backend.error.manifest_fetch_failed",
"driver_manager.backend.error.manifest_too_large",
"driver_manager.backend.error.local_manifest_url_invalid",
"driver_manager.backend.message.unsupported_builtin_manifest_url",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw manifest text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference manifest i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverManifestErrorCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.file_manifest_url_invalid",
"driver_manager.backend.message.unsupported_builtin_manifest_url",
"driver_manager.backend.error.manifest_scheme_unsupported",
"driver_manager.backend.error.manifest_parse_failed",
"driver_manager.backend.error.manifest_url_empty",
"driver_manager.backend.error.manifest_fetch_failed",
"driver_manager.backend.error.manifest_too_large",
"driver_manager.backend.error.local_manifest_url_invalid",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing manifest key %q", language, key)
}
}
}
}
func TestResolveDriverRepositoryURLUsesCurrentLanguageForStructuredErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
invalidFile := app.ResolveDriverRepositoryURL("file://")
if invalidFile.Success {
t.Fatal("expected invalid file manifest URL error")
}
if invalidFile.Message != "Invalid file driver manifest URL" {
t.Fatalf("expected English invalid file manifest URL message, got %q", invalidFile.Message)
}
if strings.Contains(invalidFile.Message, "无效的文件清单地址") {
t.Fatalf("expected no Chinese invalid file manifest URL text in en-US mode, got %q", invalidFile.Message)
}
unsupportedScheme := app.ResolveDriverRepositoryURL("unsupported://manifest")
if unsupportedScheme.Success {
t.Fatal("expected unsupported manifest scheme error")
}
if unsupportedScheme.Message != "Unsupported driver manifest URL scheme: unsupported" {
t.Fatalf("expected English unsupported scheme message, got %q", unsupportedScheme.Message)
}
if strings.Contains(unsupportedScheme.Message, "不支持的清单地址协议") {
t.Fatalf("expected no Chinese unsupported scheme text in en-US mode, got %q", unsupportedScheme.Message)
}
}
func TestGetDriverStatusListUsesCurrentLanguageForManifestErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
disableGlobalProxyForTest(t)
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
fetchServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte("busy"))
}))
defer fetchServer.Close()
driverManifestCacheMu.Lock()
delete(driverManifestCache, fetchServer.URL)
driverManifestCacheMu.Unlock()
t.Cleanup(func() {
driverManifestCacheMu.Lock()
delete(driverManifestCache, fetchServer.URL)
driverManifestCacheMu.Unlock()
})
fetchRes := app.GetDriverStatusList(t.TempDir(), fetchServer.URL)
if !fetchRes.Success {
t.Fatalf("expected status list success with manifest warning, got %+v", fetchRes)
}
fetchData, ok := fetchRes.Data.(map[string]interface{})
if !ok {
t.Fatalf("expected status list data map, got %T", fetchRes.Data)
}
fetchManifestError := strings.TrimSpace(fmt.Sprint(fetchData["manifestError"]))
if fetchManifestError != "Failed to fetch driver manifest: HTTP 503" {
t.Fatalf("expected English manifest fetch warning, got %q", fetchManifestError)
}
if strings.Contains(fetchManifestError, "拉取驱动清单失败") {
t.Fatalf("expected no Chinese manifest fetch warning in en-US mode, got %q", fetchManifestError)
}
fetchResCached := app.GetDriverStatusList(t.TempDir(), fetchServer.URL)
if !fetchResCached.Success {
t.Fatalf("expected cached status list success with manifest warning, got %+v", fetchResCached)
}
fetchDataCached, ok := fetchResCached.Data.(map[string]interface{})
if !ok {
t.Fatalf("expected cached status list data map, got %T", fetchResCached.Data)
}
fetchManifestErrorCached := strings.TrimSpace(fmt.Sprint(fetchDataCached["manifestError"]))
if fetchManifestErrorCached != "Failed to fetch driver manifest: HTTP 503" {
t.Fatalf("expected cached English manifest fetch warning, got %q", fetchManifestErrorCached)
}
if strings.Contains(fetchManifestErrorCached, "拉取驱动清单失败") {
t.Fatalf("expected no Chinese cached manifest fetch warning in en-US mode, got %q", fetchManifestErrorCached)
}
parseServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte("{not-json"))
}))
defer parseServer.Close()
driverManifestCacheMu.Lock()
delete(driverManifestCache, parseServer.URL)
driverManifestCacheMu.Unlock()
t.Cleanup(func() {
driverManifestCacheMu.Lock()
delete(driverManifestCache, parseServer.URL)
driverManifestCacheMu.Unlock()
})
parseRes := app.GetDriverStatusList(t.TempDir(), parseServer.URL)
if !parseRes.Success {
t.Fatalf("expected status list success with manifest parse warning, got %+v", parseRes)
}
parseData, ok := parseRes.Data.(map[string]interface{})
if !ok {
t.Fatalf("expected status list data map, got %T", parseRes.Data)
}
parseManifestError := strings.TrimSpace(fmt.Sprint(parseData["manifestError"]))
if !strings.HasPrefix(parseManifestError, "Failed to parse driver manifest:") {
t.Fatalf("expected English manifest parse warning, got %q", parseManifestError)
}
if strings.Contains(parseManifestError, "解析驱动清单失败") {
t.Fatalf("expected no Chinese manifest parse warning in en-US mode, got %q", parseManifestError)
}
parseResCached := app.GetDriverStatusList(t.TempDir(), parseServer.URL)
if !parseResCached.Success {
t.Fatalf("expected cached status list success with manifest parse warning, got %+v", parseResCached)
}
parseDataCached, ok := parseResCached.Data.(map[string]interface{})
if !ok {
t.Fatalf("expected cached status list data map, got %T", parseResCached.Data)
}
parseManifestErrorCached := strings.TrimSpace(fmt.Sprint(parseDataCached["manifestError"]))
if !strings.HasPrefix(parseManifestErrorCached, "Failed to parse driver manifest:") {
t.Fatalf("expected cached English manifest parse warning, got %q", parseManifestErrorCached)
}
if strings.Contains(parseManifestErrorCached, "解析驱动清单失败") {
t.Fatalf("expected no Chinese cached manifest parse warning in en-US mode, got %q", parseManifestErrorCached)
}
}
func TestMethodsDriverUnsupportedVersionErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
functionSource := methodsDriverFunctionSource(t, source, "func (a *App) localizeDriverSelectionError")
for _, key := range []string{
"driver_manager.backend.error.mongo_version_unsupported",
"driver_manager.backend.error.driver_version_unsupported",
} {
if !strings.Contains(functionSource, key) {
t.Fatalf("localizeDriverSelectionError does not reference unsupported-version i18n key %q", key)
}
}
}
func TestMethodsDriverUnsupportedVersionErrorTypesUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func (e *driverBuildUnavailableError) Error() string": {
rawMessages: []string{
`fmt.Sprintf("%s 当前发行包为精简构建,未内置该驱动;如需使用请安装 Full 版", strings.TrimSpace(e.Name))`,
},
keys: []string{
"driver_manager.backend.status.slim_build_required",
},
},
"func (e *driverVersionValidationError) Error() string": {
rawMessages: []string{
`fmt.Sprintf("MongoDB 版本 %s 当前不受支持;仅支持 1.17.x 和 2.x", versionText)`,
`fmt.Sprintf("%s 版本 %s 当前不受支持", driverType, versionText)`,
},
keys: []string{
"driver_manager.backend.error.mongo_version_unsupported",
"driver_manager.backend.error.driver_version_unsupported",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw unsupported-version error text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference unsupported-version error i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverUnsupportedVersionErrorCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.mongo_version_unsupported",
"driver_manager.backend.error.driver_version_unsupported",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing unsupported-version key %q", language, key)
}
}
}
}
func TestDriverSelectionErrorTypesUseCurrentLanguageDirectly(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
cases := []struct {
name string
err error
want string
avoid string
}{
{
name: "slim build required",
err: &driverBuildUnavailableError{Name: "ClickHouse"},
want: "ClickHouse is not included in the current slim build. Install the Full edition to use this driver.",
avoid: "当前发行包为精简构建",
},
{
name: "mongodb version unsupported",
err: &driverVersionValidationError{
DriverType: "mongodb",
Version: "1.16.9",
},
want: "MongoDB version 1.16.9 is not supported; only 1.17.x and 2.x are supported",
avoid: "当前不受支持;仅支持 1.17.x 和 2.x",
},
{
name: "generic driver version unsupported",
err: &driverVersionValidationError{
DriverType: "clickhouse",
Version: " 24.3.1 ",
},
want: "ClickHouse version 24.3.1 is not supported",
avoid: "当前不受支持",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := tc.err.Error()
if got != tc.want {
t.Fatalf("expected direct English driver error %q, got %q", tc.want, got)
}
if strings.Contains(got, tc.avoid) {
t.Fatalf("expected no Chinese direct driver error in en-US mode, got %q", got)
}
})
}
}
func TestLocalizeDriverSelectionErrorUsesCurrentLanguageForUnsupportedVersions(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
cases := []struct {
name string
definition driverDefinition
err error
want string
avoid string
}{
{
name: "mongodb compatibility pin",
definition: driverDefinition{Type: "mongodb", Name: "MongoDB"},
err: &driverVersionValidationError{
DriverType: "mongodb",
Version: "1.16.9",
},
want: "MongoDB version 1.16.9 is not supported; only 1.17.x and 2.x are supported",
avoid: "当前不受支持;仅支持 1.17.x 和 2.x",
},
{
name: "generic optional driver version",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
err: &driverVersionValidationError{
DriverType: "clickhouse",
Version: " 24.3.1 ",
},
want: "ClickHouse version 24.3.1 is not supported",
avoid: "当前不受支持",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := app.localizeDriverSelectionError(tc.definition, tc.err)
if err == nil {
t.Fatal("expected unsupported version error")
}
if err.Error() != tc.want {
t.Fatalf("expected English unsupported-version message %q, got %q", tc.want, err.Error())
}
if strings.Contains(err.Error(), tc.avoid) {
t.Fatalf("expected no Chinese unsupported-version message in en-US mode, got %q", err.Error())
}
})
}
}
func TestMethodsDriverRuntimeReasonCompatibilityUsesLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func parseDriverAgentArchIncompatibleDetail": {
rawMessages: []string{
`"可执行文件架构不兼容(文件="`,
`",当前进程="`,
},
},
"func parseDriverAgentUnavailableDetail": {
rawMessages: []string{
`" 驱动代理不可用:"`,
`";请在驱动管理中重新安装启用"`,
},
keys: []string{
"driver_manager.backend.status.agent_unavailable_reinstall",
},
},
"func (a *App) localizeDriverRuntimeReason": {
rawMessages: []string{
`"未识别的数据源类型"`,
`fmt.Sprintf("%s 当前发行包为精简构建,未内置该驱动;如需使用请安装 Full 版", name)`,
`fmt.Sprintf("%s 驱动代理路径解析失败,请在驱动管理中重新安装启用", name)`,
`fmt.Sprintf("%s 驱动代理路径为空;请在驱动管理中重新安装启用", name)`,
`fmt.Sprintf("%s 驱动代理缺失,请在驱动管理中重新安装启用", name)`,
`fmt.Sprintf("%s 纯 Go 驱动未启用,请先在驱动管理中点击“安装启用”", name)`,
},
keys: []string{
"driver_manager.backend.status.unrecognized_driver_type",
"driver_manager.backend.status.slim_build_required",
"driver_manager.backend.status.agent_path_failed",
"driver_manager.backend.status.agent_missing",
"driver_manager.backend.status.optional_disabled",
"driver_manager.backend.status.agent_unavailable_reinstall",
"driver_manager.backend.status.agent_arch_incompatible_detail",
},
},
"func resolveDriverDisplayName": {
rawMessages: []string{
`return "未知"`,
},
keys: []string{
"driver_manager.backend.driver_fallback_name",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw runtime-reason text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference runtime-reason i18n key %q", signature, key)
}
}
}
}
func TestLocalizeDriverRuntimeReasonUsesCurrentLanguageForLegacyZhCNReasons(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
zhLocalizer, err := i18n.NewLocalizer(i18n.LanguageZhCN)
if err != nil {
t.Fatalf("NewLocalizer(zh-CN): %v", err)
}
buildLegacyAgentUnavailableReason := func(name string, detail string) string {
current := zhLocalizer.T("driver_manager.backend.status.agent_unavailable_reinstall", map[string]any{
"name": name,
"detail": detail,
})
return strings.Replace(current, detail+"。", detail+"", 1)
}
cases := []struct {
name string
definition driverDefinition
reason string
want string
avoid string
}{
{
name: "unrecognized driver type",
definition: driverDefinition{},
reason: zhLocalizer.T("driver_manager.backend.status.unrecognized_driver_type", nil),
want: "Unrecognized data source type",
avoid: "未识别的数据源类型",
},
{
name: "slim build required",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: zhLocalizer.T("driver_manager.backend.status.slim_build_required", map[string]any{
"name": "ClickHouse",
}),
want: "ClickHouse is not included in the current slim build. Install the Full edition to use this driver.",
avoid: "当前发行包为精简构建",
},
{
name: "agent path failed",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: zhLocalizer.T("driver_manager.backend.status.agent_path_failed", map[string]any{
"name": "ClickHouse",
}),
want: "ClickHouse driver agent path could not be resolved; reinstall and enable it in Driver Manager.",
avoid: "驱动代理路径解析失败",
},
{
name: "agent missing",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: zhLocalizer.T("driver_manager.backend.status.agent_missing", map[string]any{
"name": "ClickHouse",
}),
want: "ClickHouse driver agent is missing; reinstall and enable it in Driver Manager.",
avoid: "驱动代理缺失",
},
{
name: "optional driver disabled",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: zhLocalizer.T("driver_manager.backend.status.optional_disabled", map[string]any{
"name": "ClickHouse",
}),
want: "ClickHouse Go driver is not enabled; install and enable it in Driver Manager.",
avoid: "纯 Go 驱动未启用",
},
{
name: "agent arch incompatible current zh-CN template",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: zhLocalizer.T("driver_manager.backend.status.agent_arch_incompatible_detail", map[string]any{
"name": "ClickHouse",
"file": "arm64",
"process": "amd64",
}),
want: "ClickHouse driver agent architecture is incompatible: file=arm64, current process=amd64; reinstall and enable it in Driver Manager.",
avoid: "驱动代理架构不兼容",
},
{
name: "legacy unavailable wrapper with english arch detail",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: buildLegacyAgentUnavailableReason(
"ClickHouse",
"driver agent architecture is incompatible (file=arm64, current process=amd64)",
),
want: "ClickHouse driver agent architecture is incompatible: file=arm64, current process=amd64; reinstall and enable it in Driver Manager.",
avoid: "驱动代理不可用",
},
{
name: "legacy unavailable wrapper keeps raw detail",
definition: driverDefinition{Type: "clickhouse", Name: "ClickHouse"},
reason: buildLegacyAgentUnavailableReason("ClickHouse", "permission denied"),
want: "ClickHouse driver agent is unavailable: permission denied; reinstall and enable it in Driver Manager.",
avoid: "驱动代理不可用",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := app.localizeDriverRuntimeReason(tc.definition, tc.reason)
if got != tc.want {
t.Fatalf("expected localized runtime reason %q, got %q", tc.want, got)
}
if strings.Contains(got, tc.avoid) {
t.Fatalf("expected no Chinese runtime reason fragment %q in %q", tc.avoid, got)
}
})
}
}
func TestResolveDriverDisplayNameUsesCurrentLanguageFallbackName(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
got := resolveDriverDisplayName(driverDefinition{})
if got != "driver" {
t.Fatalf("expected English fallback driver display name %q, got %q", "driver", got)
}
if strings.Contains(got, "未知") {
t.Fatalf("expected no Chinese fallback driver display name, got %q", got)
}
}
func TestMethodsDriverUpdateStatusUsesLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func optionalDriverAgentRevisionStatus": {
rawMessages: []string{
`fmt.Sprintf("当前 GoNavi 版本要求更新后的 %s driver-agentrevision: %s", displayName, expected)`,
`impact := "driver-agent 是独立二进制,不会随主程序自动更新;如果不重装,会继续使用旧 agent 逻辑,驱动侧已修复或优化的行为不会生效,可能继续出现旧版本问题。强烈建议重装对应驱动代理"`,
`fmt.Sprintf("原因:%s。影响%s", updateReason, impact)`,
`fmt.Sprintf("原因:%s。影响%s已安装标记%s当前需要%s", updateReason, impact, actual, expected)`,
},
keys: []string{
"driver_manager.backend.status.agent_revision_update_detail",
"driver_manager.backend.status.agent_revision_update_detail_with_actual",
},
},
"func optionalDriverPackageUpdateStatus": {
rawMessages: []string{
`fmt.Sprintf("原因:当前推荐 MongoDB 兼容驱动版本为 %s已安装版本为 %s。影响MongoDB 2.x driver-agent 使用官方 v2 驱动,要求服务端 MongoDB 4.2+;连接 MongoDB 4.0 会出现 wire version 7 不兼容。强烈建议重装对应驱动代理", pinned, installed)`,
},
keys: []string{
"driver_manager.backend.status.mongodb_compatibility_update_detail",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw update status text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference update status i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverUpdateStatusCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.status.agent_revision_update_detail",
"driver_manager.backend.status.agent_revision_update_detail_with_actual",
"driver_manager.backend.status.mongodb_compatibility_update_detail",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing update status key %q", language, key)
}
}
}
}
func TestOptionalDriverAgentRevisionStatusUsesCurrentLanguageForUpdateReason(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
needsUpdate, reason, expected := optionalDriverAgentRevisionStatus("clickhouse", installedDriverPackage{}, true)
if !needsUpdate {
t.Fatal("expected ClickHouse revision mismatch to require update")
}
if expected == "" {
t.Fatal("expected ClickHouse to define an expected revision")
}
if !strings.Contains(reason, "Reason:") || !strings.Contains(reason, "Impact:") {
t.Fatalf("expected English reason/impact wrapper, got %q", reason)
}
if !strings.Contains(reason, "ClickHouse driver-agent") || !strings.Contains(reason, expected) {
t.Fatalf("expected English ClickHouse revision detail with expected revision, got %q", reason)
}
if strings.Contains(reason, "原因:") || strings.Contains(reason, "影响:") || strings.Contains(reason, "强烈建议重装") {
t.Fatalf("expected no Chinese revision update reason in en-US mode, got %q", reason)
}
}
func TestOptionalDriverPackageUpdateStatusUsesCurrentLanguageForMongoCompatibility(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
definition, ok := resolveDriverDefinition("mongodb")
if !ok {
t.Fatal("expected mongodb driver definition")
}
meta := installedDriverPackage{
Version: "2.5.0",
AgentRevision: db.OptionalDriverAgentRevision("mongodb"),
}
needsUpdate, reason, _ := optionalDriverPackageUpdateStatus(definition, meta, true)
if !needsUpdate {
t.Fatal("expected MongoDB legacy compatibility prompt")
}
if !strings.Contains(reason, "Reason:") || !strings.Contains(reason, "Impact:") {
t.Fatalf("expected English reason/impact wrapper, got %q", reason)
}
if !strings.Contains(reason, "recommended MongoDB compatibility driver version") || !strings.Contains(reason, "wire version 7") {
t.Fatalf("expected English MongoDB compatibility detail, got %q", reason)
}
if strings.Contains(reason, "原因:") || strings.Contains(reason, "影响:") || strings.Contains(reason, "当前推荐 MongoDB 兼容驱动版本") {
t.Fatalf("expected no Chinese MongoDB compatibility reason in en-US mode, got %q", reason)
}
}
func TestMethodsDriverSourceBuildHelperErrorsUseLocalizedText(t *testing.T) {
source := methodsDriverSource(t)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func optionalDriverBuildTag": {
rawMessages: []string{
`fmt.Errorf("未配置驱动构建标签:%s", driverType)`,
},
keys: []string{
"driver_manager.backend.error.source_build_tag_unconfigured",
},
},
"func locateProjectRootForAgentBuild": {
rawMessages: []string{
`fmt.Errorf("获取当前目录失败:%w", err)`,
`fmt.Errorf("未找到通用驱动代理源码,无法自动构建;请使用已发布版本")`,
},
keys: []string{
"driver_manager.backend.error.source_build_workdir_unavailable",
"driver_manager.backend.error.source_build_project_root_missing",
},
},
"func buildVersionedDriverModOverride": {
rawMessages: []string{
`fmt.Errorf("读取 go.mod 失败:%w", err)`,
`fmt.Errorf("未在 go.mod 中找到驱动依赖:%s", modulePath)`,
`fmt.Errorf("创建驱动构建临时目录失败:%w", err)`,
`fmt.Errorf("写入临时 go.mod 失败:%w", err)`,
`fmt.Errorf("写入临时 go.sum 失败:%w", writeErr)`,
},
keys: []string{
"driver_manager.backend.error.source_build_go_mod_read_failed",
"driver_manager.backend.error.source_build_module_dependency_missing",
"driver_manager.backend.error.source_build_temp_directory_create_failed",
"driver_manager.backend.error.source_build_temp_go_mod_write_failed",
"driver_manager.backend.error.source_build_temp_go_sum_write_failed",
},
},
"func rewriteRequiredModuleVersion": {
rawMessages: []string{
`fmt.Errorf("驱动模块或版本为空")`,
},
keys: []string{
"driver_manager.backend.error.source_build_module_or_version_empty",
},
},
"func buildOptionalDriverAgentFromSource": {
rawMessages: []string{
`fmt.Errorf("准备 DuckDB Windows CGO 编译器失败:%w", toolchainErr)`,
`fmt.Errorf("准备 DuckDB Windows 动态库失败:%w", prepErr)`,
},
keys: []string{
"driver_manager.backend.error.source_build_duckdb_windows_cgo_toolchain_prepare_failed",
"driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_prepare_failed",
},
},
"func resolveDuckDBWindowsCGOToolchainBinFromCandidates": {
rawMessages: []string{
`请先安装 MSYS2 UCRT64 工具链winget install --id MSYS2.MSYS2 -e然后执行 C:\msys64\usr\bin\bash.exe -lc "pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils"`,
`fmt.Errorf("未找到可用的 gcc.exe/g++.exe%s", installHint)`,
`fmt.Errorf("未找到可用的 gcc.exe/g++.exe已检查%s%s", strings.Join(checked, ", "), installHint)`,
},
keys: []string{
"driver_manager.backend.error.source_build_duckdb_windows_toolchain_install_hint",
"driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found",
"driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found_with_checked",
},
},
"func prepareDuckDBWindowsDynamicLibraryForBuild": {
rawMessages: []string{
`fmt.Errorf("DuckDB 官方动态库包缺少文件:%s", strings.Join(missing, ", "))`,
`fmt.Errorf("定位 DuckDB Windows dlltool 失败:%w", err)`,
},
keys: []string{
"driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_missing_files",
"driver_manager.backend.error.source_build_duckdb_windows_dlltool_resolve_failed",
},
},
}
for signature, check := range checks {
functionSource := methodsDriverFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw source-build text %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference source-build i18n key %q", signature, key)
}
}
}
}
func TestMethodsDriverSourceBuildHelperCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"driver_manager.backend.error.source_build_tag_unconfigured",
"driver_manager.backend.error.source_build_workdir_unavailable",
"driver_manager.backend.error.source_build_project_root_missing",
"driver_manager.backend.error.source_build_go_mod_read_failed",
"driver_manager.backend.error.source_build_module_dependency_missing",
"driver_manager.backend.error.source_build_temp_directory_create_failed",
"driver_manager.backend.error.source_build_temp_go_mod_write_failed",
"driver_manager.backend.error.source_build_temp_go_sum_write_failed",
"driver_manager.backend.error.source_build_module_or_version_empty",
"driver_manager.backend.error.source_build_duckdb_windows_cgo_toolchain_prepare_failed",
"driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_prepare_failed",
"driver_manager.backend.error.source_build_duckdb_windows_toolchain_install_hint",
"driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found",
"driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found_with_checked",
"driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_missing_files",
"driver_manager.backend.error.source_build_duckdb_windows_dlltool_resolve_failed",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing source-build key %q", language, key)
}
}
}
}
func TestOptionalDriverBuildTagUsesCurrentLanguageForUnconfiguredDriver(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := optionalDriverBuildTag("unsupported", "")
if err == nil {
t.Fatal("expected missing build-tag error")
}
if err.Error() != "No build tags are configured for driver type: unsupported" {
t.Fatalf("expected English source-build tag message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未配置驱动构建标签") {
t.Fatalf("expected no Chinese source-build tag message in en-US mode, got %q", err.Error())
}
}
func TestLocateProjectRootForAgentBuildUsesCurrentLanguageWhenSourceMissing(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
originalWD, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd() error = %v", err)
}
tempDir := t.TempDir()
if err := os.Chdir(tempDir); err != nil {
t.Fatalf("Chdir(%q) error = %v", tempDir, err)
}
t.Cleanup(func() {
_ = os.Chdir(originalWD)
})
_, err = locateProjectRootForAgentBuild()
if err == nil {
t.Fatal("expected missing project-root error")
}
if err.Error() != "Optional driver agent source was not found in the project; please use a published build" {
t.Fatalf("expected English source-build project-root message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未找到通用驱动代理源码") {
t.Fatalf("expected no Chinese source-build project-root message in en-US mode, got %q", err.Error())
}
}
func TestBuildVersionedDriverModOverrideUsesCurrentLanguageForStructuredErrors(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
missingGoModDir := t.TempDir()
_, err := buildVersionedDriverModOverride(missingGoModDir, "github.com/example/driver", "1.2.3")
if err == nil {
t.Fatal("expected go.mod read failure")
}
if !strings.HasPrefix(err.Error(), "Failed to read go.mod:") {
t.Fatalf("expected English go.mod read prefix, got %q", err.Error())
}
if strings.Contains(err.Error(), "读取 go.mod 失败") {
t.Fatalf("expected no Chinese go.mod read message in en-US mode, got %q", err.Error())
}
projectDir := t.TempDir()
goMod := "module example.com/test\n\ngo 1.24.0\n\nrequire github.com/example/other v1.0.0\n"
if writeErr := os.WriteFile(filepath.Join(projectDir, "go.mod"), []byte(goMod), 0o644); writeErr != nil {
t.Fatalf("WriteFile(go.mod) error = %v", writeErr)
}
_, err = buildVersionedDriverModOverride(projectDir, "github.com/example/missing", "1.2.3")
if err == nil {
t.Fatal("expected missing driver dependency error")
}
if err.Error() != "Driver dependency was not found in go.mod: github.com/example/missing" {
t.Fatalf("expected English missing dependency message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未在 go.mod 中找到驱动依赖") {
t.Fatalf("expected no Chinese missing dependency message in en-US mode, got %q", err.Error())
}
}
func TestRewriteRequiredModuleVersionUsesCurrentLanguageForEmptyInput(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, _, err := rewriteRequiredModuleVersion(nil, " ", " ")
if err == nil {
t.Fatal("expected empty module/version error")
}
if err.Error() != "Driver module path or version is empty" {
t.Fatalf("expected English empty module/version message, got %q", err.Error())
}
if strings.Contains(err.Error(), "驱动模块或版本为空") {
t.Fatalf("expected no Chinese empty module/version message in en-US mode, got %q", err.Error())
}
}
func TestResolveDuckDBWindowsCGOToolchainBinFromCandidatesUsesCurrentLanguageWhenNoneFound(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := resolveDuckDBWindowsCGOToolchainBinFromCandidates(nil)
if err == nil {
t.Fatal("expected missing DuckDB Windows toolchain error")
}
expected := `No usable gcc.exe/g++.exe was found; Please install the MSYS2 UCRT64 toolchain first: winget install --id MSYS2.MSYS2 -e; then run C:\msys64\usr\bin\bash.exe -lc "pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils"`
if err.Error() != expected {
t.Fatalf("expected English DuckDB Windows toolchain message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未找到可用的 gcc.exe/g++.exe") || strings.Contains(err.Error(), "请先安装 MSYS2 UCRT64 工具链") {
t.Fatalf("expected no Chinese DuckDB Windows toolchain message in en-US mode, got %q", err.Error())
}
}
func TestResolveDuckDBWindowsCGOToolchainBinFromCandidatesUsesCurrentLanguageWhenCheckedPathsMissing(t *testing.T) {
app := NewApp()
app.SetLanguage(string(i18n.LanguageEnUS))
t.Cleanup(func() {
app.SetLanguage(string(i18n.LanguageZhCN))
})
_, err := resolveDuckDBWindowsCGOToolchainBinFromCandidates([]string{`C:\missing1`, `C:\missing2`})
if err == nil {
t.Fatal("expected missing DuckDB Windows toolchain error after checking candidates")
}
expected := `No usable gcc.exe/g++.exe was found. Checked: C:\missing1, C:\missing2; Please install the MSYS2 UCRT64 toolchain first: winget install --id MSYS2.MSYS2 -e; then run C:\msys64\usr\bin\bash.exe -lc "pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils"`
if err.Error() != expected {
t.Fatalf("expected English DuckDB Windows checked-path message, got %q", err.Error())
}
if strings.Contains(err.Error(), "未找到可用的 gcc.exe/g++.exe") || strings.Contains(err.Error(), "请先安装 MSYS2 UCRT64 工具链") {
t.Fatalf("expected no Chinese DuckDB Windows checked-path message in en-US mode, got %q", err.Error())
}
}