mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-14 00:42:17 +08:00
- 新增 Redis 导入预览与按 key 勾选导入能力 - 后端支持导入文件预读、选中 key 过滤与本地化错误提示 - 打开数据库节点时同步展示加载态并补齐前后端回归测试 Fixes #607 Fixes #609
306 lines
10 KiB
Go
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)
|
|
}
|
|
}
|