mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-01 11:49:37 +08:00
- 精确搜索识别无通配符的 Redis literal pattern - 同时查询完整 Key 与同名命名空间前缀 - 修复输入 Agent 无法显示 Agent 文件夹的问题 - 避免误匹配 AgentCapacity、AgentState 等相似前缀 - 补充 glob literal 与命名空间搜索回归测试 - 更新 Redis 精确搜索输入提示文案
242 lines
6.7 KiB
Go
242 lines
6.7 KiB
Go
package redis
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestSanitizeRedisPassword(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "empty password",
|
|
input: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "plain password without special chars",
|
|
input: "mypassword123",
|
|
expected: "mypassword123",
|
|
},
|
|
{
|
|
name: "password with @ not encoded",
|
|
input: "p@ssword",
|
|
expected: "p@ssword",
|
|
},
|
|
{
|
|
name: "password with @ URL-encoded as %40",
|
|
input: "p%40ssword",
|
|
expected: "p@ssword",
|
|
},
|
|
{
|
|
name: "password with multiple encoded chars",
|
|
input: "p%40ss%23word",
|
|
expected: "p@ss#word",
|
|
},
|
|
{
|
|
name: "password with + encoded as %2B",
|
|
input: "p%2Bss",
|
|
expected: "p+ss",
|
|
},
|
|
{
|
|
name: "password that is purely encoded",
|
|
input: "%40%23%24",
|
|
expected: "@#$",
|
|
},
|
|
{
|
|
name: "password with invalid percent encoding",
|
|
input: "p%ZZssword",
|
|
expected: "p%ZZssword",
|
|
},
|
|
{
|
|
name: "password with trailing percent",
|
|
input: "password%",
|
|
expected: "password%",
|
|
},
|
|
{
|
|
name: "password with literal percent not encoding anything",
|
|
input: "100%safe",
|
|
expected: "100%safe",
|
|
},
|
|
{
|
|
name: "password with space encoded as %20",
|
|
input: "my%20pass",
|
|
expected: "my pass",
|
|
},
|
|
{
|
|
name: "complex password with mixed content",
|
|
input: "P%40ss%23w0rd!",
|
|
expected: "P@ss#w0rd!",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := sanitizeRedisPassword(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("sanitizeRedisPassword(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsRedisKeyGone(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
keyType string
|
|
ttl int64
|
|
want bool
|
|
}{
|
|
{name: "type none", keyType: "none", ttl: -2, want: true},
|
|
{name: "type none without ttl", keyType: "none", ttl: -1, want: true},
|
|
{name: "missing by ttl", keyType: "string", ttl: -2, want: true},
|
|
{name: "normal string", keyType: "string", ttl: 30, want: false},
|
|
{name: "permanent hash", keyType: "hash", ttl: -1, want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isRedisKeyGone(tt.keyType, tt.ttl); got != tt.want {
|
|
t.Fatalf("isRedisKeyGone(%q, %d)=%v, want %v", tt.keyType, tt.ttl, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeRedisGetValueError(t *testing.T) {
|
|
err := normalizeRedisGetValueError("none", -2)
|
|
if !errors.Is(err, ErrRedisKeyGone) {
|
|
t.Fatalf("expected ErrRedisKeyGone, got %v", err)
|
|
}
|
|
if err == nil || err.Error() != "Redis Key 不存在或已过期" {
|
|
t.Fatalf("unexpected error text: %v", err)
|
|
}
|
|
|
|
if normalizeRedisGetValueError("hash", -1) != nil {
|
|
t.Fatal("expected nil for supported existing key")
|
|
}
|
|
}
|
|
|
|
func TestRedisGlobPatternLiteralKey(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pattern string
|
|
wantKey string
|
|
wantExact bool
|
|
}{
|
|
{name: "plain exact key", pattern: "Agent", wantKey: "Agent", wantExact: true},
|
|
{name: "escaped glob characters stay literal", pattern: `user:\*:\[id\]\?\\raw`, wantKey: `user:*:[id]?\raw`, wantExact: true},
|
|
{name: "fuzzy wildcard is not exact", pattern: "*[aA][gG][eE][nN][tT]*", wantExact: false},
|
|
{name: "unescaped suffix wildcard is not exact", pattern: "Agent*", wantExact: false},
|
|
{name: "unescaped single character wildcard is not exact", pattern: "Agent?", wantExact: false},
|
|
{name: "unescaped character class is not exact", pattern: "Agent[0-9]", wantExact: false},
|
|
{name: "empty pattern is not exact", pattern: "", wantExact: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotKey, gotExact := redisGlobPatternLiteralKey(tt.pattern)
|
|
if gotExact != tt.wantExact {
|
|
t.Fatalf("redisGlobPatternLiteralKey(%q) exact=%v, want %v", tt.pattern, gotExact, tt.wantExact)
|
|
}
|
|
if gotKey != tt.wantKey {
|
|
t.Fatalf("redisGlobPatternLiteralKey(%q) key=%q, want %q", tt.pattern, gotKey, tt.wantKey)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRedisExactSearchPattern(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
literalKey string
|
|
wantExactKey string
|
|
wantNamespace string
|
|
}{
|
|
{
|
|
name: "plain namespace folder",
|
|
literalKey: "Agent",
|
|
wantExactKey: "Agent",
|
|
wantNamespace: "Agent:*",
|
|
},
|
|
{
|
|
name: "escaped namespace keeps glob chars literal",
|
|
literalKey: `user:*:[id]?\raw`,
|
|
wantExactKey: `user:*:[id]?\raw`,
|
|
wantNamespace: `user:\*:\[id\]\?\\raw:*`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotExactKey, gotNamespace := redisExactSearchPattern(tt.literalKey)
|
|
if gotExactKey != tt.wantExactKey {
|
|
t.Fatalf("redisExactSearchPattern(%q) exactKey=%q, want %q", tt.literalKey, gotExactKey, tt.wantExactKey)
|
|
}
|
|
if gotNamespace != tt.wantNamespace {
|
|
t.Fatalf("redisExactSearchPattern(%q) namespace=%q, want %q", tt.literalKey, gotNamespace, tt.wantNamespace)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReadRedisHashEntriesWithFallbackUsesHScanWhenHGetAllForbidden(t *testing.T) {
|
|
scanCalls := 0
|
|
values, length, err := readRedisHashEntriesWithFallback(
|
|
func() (map[string]string, error) {
|
|
return nil, errors.New("ERR command 'HGETALL' not support for normal user")
|
|
},
|
|
func() (int64, error) {
|
|
return 2, nil
|
|
},
|
|
func(cursor uint64, count int64) ([]string, uint64, error) {
|
|
scanCalls++
|
|
if cursor != 0 {
|
|
t.Fatalf("expected first scan cursor to be 0, got %d", cursor)
|
|
}
|
|
if count <= 0 {
|
|
t.Fatalf("expected positive scan count, got %d", count)
|
|
}
|
|
return []string{"field-a", "value-a", "field-b", "value-b"}, 0, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("readRedisHashEntriesWithFallback() unexpected error: %v", err)
|
|
}
|
|
if scanCalls != 1 {
|
|
t.Fatalf("expected exactly one HSCAN fallback, got %d", scanCalls)
|
|
}
|
|
if length != 2 {
|
|
t.Fatalf("expected hash length 2, got %d", length)
|
|
}
|
|
if got := values["field-a"]; got != "value-a" {
|
|
t.Fatalf("expected field-a=value-a, got %q", got)
|
|
}
|
|
if got := values["field-b"]; got != "value-b" {
|
|
t.Fatalf("expected field-b=value-b, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestReadRedisHashEntriesWithFallbackReturnsOriginalErrorForNonPermissionFailure(t *testing.T) {
|
|
expectedErr := errors.New("ERR wrong type")
|
|
_, _, err := readRedisHashEntriesWithFallback(
|
|
func() (map[string]string, error) {
|
|
return nil, expectedErr
|
|
},
|
|
func() (int64, error) {
|
|
t.Fatal("expected HLEN not to run for non-permission failure")
|
|
return 0, nil
|
|
},
|
|
func(cursor uint64, count int64) ([]string, uint64, error) {
|
|
t.Fatal("expected HSCAN not to run for non-permission failure")
|
|
return nil, 0, nil
|
|
},
|
|
)
|
|
if !errors.Is(err, expectedErr) {
|
|
t.Fatalf("expected original error %v, got %v", expectedErr, err)
|
|
}
|
|
}
|