gcli2api / internal /upstream /cache_key_test.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
5.54 kB
package upstream
import (
"encoding/json"
"strings"
"testing"
)
// extractCacheKey 从改写后的 body 里取出 prompt_cache_key 字段值。
func extractCacheKey(t *testing.T, body []byte) string {
t.Helper()
var obj map[string]any
if err := json.Unmarshal(body, &obj); err != nil {
t.Fatalf("unmarshal body: %v", err)
}
v, _ := obj["prompt_cache_key"].(string)
return v
}
// TestInjectPromptCacheKey_PreservesExisting 覆盖任务用例 1:
// 入站 body 已带 prompt_cache_key → 原值保留,不被网关覆盖。
func TestInjectPromptCacheKey_PreservesExisting(t *testing.T) {
// Arrange
uid := "user-abc-123"
conv := "conv-xyz"
in := `{"model":"glm-5.2","messages":[],"prompt_cache_key":"client-set-key"}`
// Act
out := InjectPromptCacheKey([]byte(in), uid, conv)
// Assert
if got := extractCacheKey(t, out); got != "client-set-key" {
t.Fatalf("existing prompt_cache_key overwritten: got=%q want client-set-key", got)
}
}
// TestInjectPromptCacheKey_UsesConversationID 覆盖任务用例 2:
// body 无 prompt_cache_key 但有 conversation_id → 用它做会话哈希源。
// 格式 wb2a-<uid8>-<conversationHash>:conversation_id 不逐字出现,而是驱动哈希段
// (同 id 同 key、不同 id 不同 key)。同时仍带 uid8 隔离前缀。
func TestInjectPromptCacheKey_UsesConversationID(t *testing.T) {
uid := "user-abc-123"
in := `{"model":"glm-5.2","messages":[],"conversation_id":"conv-42"}`
out := InjectPromptCacheKey([]byte(in), uid, "")
got := extractCacheKey(t, out)
if !strings.HasPrefix(got, "wb2a-") {
t.Fatalf("expected wb2a- prefix, got=%q", got)
}
if !strings.Contains(got, "user-abc") {
t.Fatalf("expected uid8 isolation prefix, got=%q", got)
}
// 同 conversation_id 两次出站 → 同 key(稳定)。
out2 := InjectPromptCacheKey([]byte(in), uid, "")
if got2 := extractCacheKey(t, out2); got2 != got {
t.Fatalf("non-deterministic key for same conversation_id: %q vs %q", got, got2)
}
// 不同 conversation_id → 不同 key(哈希段区分)。
inB := `{"model":"glm-5.2","messages":[],"conversation_id":"conv-99"}`
gotB := extractCacheKey(t, InjectPromptCacheKey([]byte(inB), uid, ""))
if gotB == got {
t.Fatalf("different conversation_id produced same key: %q", got)
}
}
// TestInjectPromptCacheKey_GeneratesStableKey 覆盖任务用例 3:
// body 两者都无 → 网关生成稳定键,两次同输入同账号得到同 key。
func TestInjectPromptCacheKey_GeneratesStableKey(t *testing.T) {
uid := "user-abc-123"
conv := "conv-stable"
in := `{"model":"glm-5.2","messages":[]}`
a := InjectPromptCacheKey([]byte(in), uid, conv)
b := InjectPromptCacheKey([]byte(in), uid, conv)
ka, kb := extractCacheKey(t, a), extractCacheKey(t, b)
if ka != kb {
t.Fatalf("non-deterministic key for same input+uid: a=%q b=%q", ka, kb)
}
}
// TestInjectPromptCacheKey_AccountIsolation 覆盖任务用例 4:
// 不同账号 → key 不同(隔离验证)。
func TestInjectPromptCacheKey_AccountIsolation(t *testing.T) {
conv := "conv-shared"
in := `{"model":"glm-5.2","messages":[]}`
ka := extractCacheKey(t, InjectPromptCacheKey([]byte(in), "user-aaa-111", conv))
kb := extractCacheKey(t, InjectPromptCacheKey([]byte(in), "user-bbb-222", conv))
if ka == kb {
t.Fatalf("cross-account key collision: both=%q", ka)
}
if !strings.HasPrefix(ka, "wb2a-") || !strings.HasPrefix(kb, "wb2a-") {
t.Fatalf("keys must carry wb2a- prefix: a=%q b=%q", ka, kb)
}
}
// TestInjectPromptCacheKey_Format 覆盖任务用例 5:
// cache key 格式校验(含 uid8 前缀)。
func TestInjectPromptCacheKey_Format(t *testing.T) {
uid := "1234567890abcdef"
in := `{"model":"glm-5.2","messages":[]}`
out := InjectPromptCacheKey([]byte(in), uid, "conv-fmt")
got := extractCacheKey(t, out)
if !strings.HasPrefix(got, "wb2a-") {
t.Fatalf("expected wb2a- prefix, got=%q", got)
}
if !strings.Contains(got, "12345678") {
t.Fatalf("expected uid8 (12345678) in key, got=%q", got)
}
}
// TestInjectPromptCacheKey_EmptyConversation 覆盖任务用例 3 边界:
// 无 conversation_id 且无会话标识 → 生成键含 uid8 但 conversationHash 段为定值(不复用前缀)。
// 不应报错、不应空串(key 非空)。
func TestInjectPromptCacheKey_EmptyConversation(t *testing.T) {
uid := "user-abc-123"
in := `{"model":"glm-5.2","messages":[]}`
out := InjectPromptCacheKey([]byte(in), uid, "")
got := extractCacheKey(t, out)
if got == "" {
t.Fatalf("expected non-empty key when no conversation, got empty")
}
if !strings.HasPrefix(got, "wb2a-") {
t.Fatalf("expected wb2a- prefix, got=%q", got)
}
if !strings.Contains(got, "user-abc") {
t.Fatalf("expected uid8 in key, got=%q", got)
}
}
// TestPrepareBodyOptNoCacheKeyInjection 覆盖任务用例 6:
// PrepareBodyOpt(sanitize=false 的旧入口)行为不变——不注入 cache key。
// 向后兼容:仅传 body 不给 cacheKey 上下文时,不得引入新字段。
func TestPrepareBodyOptNoCacheKeyInjection(t *testing.T) {
in := `{"model":"glm-5.2","messages":[]}`
out := PrepareBodyOpt([]byte(in), false)
var obj map[string]any
if err := json.Unmarshal(out, &obj); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if _, present := obj["prompt_cache_key"]; present {
t.Fatalf("PrepareBodyOpt must NOT inject prompt_cache_key, but got: %v", obj["prompt_cache_key"])
}
}