Files
MyGoNavi/internal/app/methods_redis_i18n_test.go
Syngnat a9bae6bb3f 🐛 fix(explorer): 补齐 Redis 部分导入与库节点加载态
- 新增 Redis 导入预览与按 key 勾选导入能力

- 后端支持导入文件预读、选中 key 过滤与本地化错误提示

- 打开数据库节点时同步展示加载态并补齐前后端回归测试

Fixes #607

Fixes #609
2026-06-30 21:17:00 +08:00

306 lines
10 KiB
Go

package app
import (
"errors"
"os"
"strings"
"testing"
"GoNavi-Wails/internal/connection"
redislib "GoNavi-Wails/internal/redis"
"GoNavi-Wails/shared/i18n"
)
func redisFunctionSource(t *testing.T, source string, signature string) string {
t.Helper()
start := strings.Index(source, signature)
if start < 0 {
t.Fatalf("methods_redis.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 TestRedisBackendOperationMessagesUseLocalizedText(t *testing.T) {
sourceBytes, err := os.ReadFile("methods_redis.go")
if err != nil {
t.Fatalf("read methods_redis.go: %v", err)
}
source := string(sourceBytes)
checks := map[string]struct {
rawMessages []string
keys []string
}{
"func (a *App) RedisConnect": {
rawMessages: []string{`Message: "连接成功"`},
keys: []string{"redis.backend.message.connect_success"},
},
"func (a *App) RedisTestConnection": {
rawMessages: []string{
`Message: "连接成功"`,
`fmt.Sprintf("连接成功但释放测试连接失败:%v", closeErr)`,
},
keys: []string{
"redis.backend.message.connect_success",
"redis.backend.error.test_connection_close_failed",
},
},
"func (a *App) RedisSetString": {
rawMessages: []string{`Message: "设置成功"`},
keys: []string{"redis.backend.message.set_success"},
},
"func (a *App) RedisSetHashField": {
rawMessages: []string{`Message: "设置成功"`},
keys: []string{"redis.backend.message.set_success"},
},
"func (a *App) RedisSetTTL": {
rawMessages: []string{`Message: "设置成功"`},
keys: []string{"redis.backend.message.set_success"},
},
"func (a *App) RedisExecuteCommand": {
rawMessages: []string{`Message: "命令不能为空"`},
keys: []string{"redis.backend.error.command_required"},
},
"func (a *App) RedisSelectDB": {
rawMessages: []string{`Message: "切换成功"`},
keys: []string{"redis.backend.message.select_db_success"},
},
"func (a *App) RedisRenameKey": {
rawMessages: []string{`Message: "重命名成功"`},
keys: []string{"redis.backend.message.rename_success"},
},
"func (a *App) RedisDeleteHashField": {
rawMessages: []string{`Message: "删除成功"`},
keys: []string{"redis.backend.message.delete_success"},
},
"func (a *App) RedisListPush": {
rawMessages: []string{`Message: "添加成功"`},
keys: []string{"redis.backend.message.add_success"},
},
"func (a *App) RedisListSet": {
rawMessages: []string{`Message: "设置成功"`},
keys: []string{"redis.backend.message.set_success"},
},
"func (a *App) RedisSetAdd": {
rawMessages: []string{`Message: "添加成功"`},
keys: []string{"redis.backend.message.add_success"},
},
"func (a *App) RedisSetRemove": {
rawMessages: []string{`Message: "删除成功"`},
keys: []string{"redis.backend.message.delete_success"},
},
"func (a *App) RedisZSetAdd": {
rawMessages: []string{`Message: "添加成功"`},
keys: []string{"redis.backend.message.add_success"},
},
"func (a *App) RedisZSetRemove": {
rawMessages: []string{`Message: "删除成功"`},
keys: []string{"redis.backend.message.delete_success"},
},
"func (a *App) RedisStreamAdd": {
rawMessages: []string{`Message: "添加成功"`},
keys: []string{"redis.backend.message.add_success"},
},
"func (a *App) RedisStreamDelete": {
rawMessages: []string{`Message: "删除成功"`},
keys: []string{"redis.backend.message.delete_success"},
},
"func (a *App) RedisFlushDB": {
rawMessages: []string{`Message: "清空成功"`},
keys: []string{"redis.backend.message.flush_success"},
},
"func (a *App) RedisExportKeys": {
keys: []string{
"redis.backend.error.export_no_keys",
"redis.backend.message.export_success",
},
},
"func (a *App) RedisImportKeys": {
keys: []string{
"redis.backend.error.import_no_keys_selected",
"redis.backend.error.import_payload_invalid",
"redis.backend.message.import_success",
},
},
}
for signature, check := range checks {
functionSource := redisFunctionSource(t, source, signature)
for _, rawMessage := range check.rawMessages {
if strings.Contains(functionSource, rawMessage) {
t.Fatalf("%s still contains raw Redis operation result %q", signature, rawMessage)
}
}
for _, key := range check.keys {
if !strings.Contains(functionSource, key) {
t.Fatalf("%s does not reference Redis i18n key %q", signature, key)
}
}
}
}
func TestRedisBackendOperationMessageCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"redis.backend.message.connect_success",
"redis.backend.error.test_connection_close_failed",
"redis.backend.message.set_success",
"redis.backend.message.select_db_success",
"redis.backend.message.rename_success",
"redis.backend.message.delete_success",
"redis.backend.message.add_success",
"redis.backend.message.flush_success",
"redis.backend.message.export_success",
"redis.backend.message.import_success",
"redis.backend.error.command_required",
"redis.backend.error.argument_required",
"redis.backend.error.argument_invalid_type",
"redis.backend.error.export_no_keys",
"redis.backend.error.import_no_keys_selected",
"redis.backend.error.import_payload_invalid",
"redis.backend.label.topology_sentinel",
"redis.backend.label.topology_cluster",
"redis.backend.label.topology_multi_node",
"redis.backend.error.address_required",
"redis.backend.error.node_address_required",
"redis.backend.error.invalid_node_address",
"redis.backend.error.invalid_port",
"redis.backend.error.topology_ssh_tunnel_unsupported",
"redis.backend.error.sentinel_master_required",
"redis.backend.error.connect_tls_setup_failed",
"redis.backend.error.connect_attempt_failed",
"redis.backend.error.sentinel_connect_failed",
"redis.backend.error.cluster_connect_failed",
"redis.backend.error.connect_failed",
"redis.backend.error.ssh_tunnel_create_failed",
"redis.backend.error.select_db_index_required",
"redis.backend.error.select_db_index_invalid",
"redis.backend.error.select_db_index_out_of_range",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing Redis backend message key %q", language, key)
}
}
}
}
type redisExecuteErrorClient struct {
capturingRedisClient
executeErr error
}
func (c *redisExecuteErrorClient) ExecuteCommand(args []string) (interface{}, error) {
return nil, c.executeErr
}
func TestRedisDeleteHashFieldArgumentValidationUsesLocalizedText(t *testing.T) {
app := NewAppWithSecretStore(newFakeAppSecretStore())
app.configDir = t.TempDir()
app.SetLanguage(string(i18n.LanguageEnUS))
CloseAllRedisClients()
client := &capturingRedisClient{}
originalNewRedisClientFunc := newRedisClientFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
defer func() {
newRedisClientFunc = originalNewRedisClientFunc
resolveDialConfigWithProxyFunc = originalResolveDialConfigWithProxyFunc
CloseAllRedisClients()
}()
newRedisClientFunc = func() redislib.RedisClient {
return client
}
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
return raw, nil
}
config := connection.ConnectionConfig{Type: "redis", Host: "redis.local", Port: 6379}
requiredResult := app.RedisDeleteHashField(config, "profile", nil)
if requiredResult.Success {
t.Fatalf("RedisDeleteHashField(nil fields) returned success: %+v", requiredResult)
}
if requiredResult.Message != "fields is required" {
t.Fatalf("expected localized required argument message, got %q", requiredResult.Message)
}
invalidTypeResult := app.RedisDeleteHashField(config, "profile", map[string]string{"nickname": "neo"})
if invalidTypeResult.Success {
t.Fatalf("RedisDeleteHashField(invalid fields) returned success: %+v", invalidTypeResult)
}
if invalidTypeResult.Message != "fields has an invalid type" {
t.Fatalf("expected localized invalid argument type message, got %q", invalidTypeResult.Message)
}
}
func TestRedisExecuteCommandPropagatesLocalizedBackendValidation(t *testing.T) {
app := NewAppWithSecretStore(newFakeAppSecretStore())
app.configDir = t.TempDir()
app.SetLanguage(string(i18n.LanguageEnUS))
CloseAllRedisClients()
client := &redisExecuteErrorClient{executeErr: errors.New("SELECT command requires a database index")}
originalNewRedisClientFunc := newRedisClientFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
defer func() {
newRedisClientFunc = originalNewRedisClientFunc
resolveDialConfigWithProxyFunc = originalResolveDialConfigWithProxyFunc
CloseAllRedisClients()
}()
newRedisClientFunc = func() redislib.RedisClient {
return client
}
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
return raw, nil
}
result := app.RedisExecuteCommand(connection.ConnectionConfig{
Type: "redis",
Host: "redis.local",
Port: 6379,
Topology: "cluster",
}, "SELECT")
if result.Success {
t.Fatalf("RedisExecuteCommand returned success for backend validation error: %+v", result)
}
if result.Message != "SELECT command requires a database index" {
t.Fatalf("expected RedisExecuteCommand to surface backend validation text, got %q", result.Message)
}
}
func TestRedisConnectPropagatesLocalizedTopologyValidation(t *testing.T) {
app := NewAppWithSecretStore(newFakeAppSecretStore())
app.configDir = t.TempDir()
app.SetLanguage(string(i18n.LanguageEnUS))
result := app.RedisConnect(connection.ConnectionConfig{
Type: "redis",
Host: "127.0.0.1",
Port: 6379,
Topology: "cluster",
UseSSH: true,
})
if result.Success {
t.Fatalf("expected RedisConnect to fail for cluster+SSH, got %+v", result)
}
const want = "Redis Cluster mode does not support SSH tunnels yet. Disable SSH and try again."
if !strings.Contains(result.Message, want) {
t.Fatalf("expected RedisConnect message to contain %q, got %q", want, result.Message)
}
if strings.Contains(result.Message, "集群模式暂不支持 SSH 隧道") {
t.Fatalf("expected no Chinese topology validation text in RedisConnect result, got %q", result.Message)
}
}