package service import ( "net/http" "testing" "github.com/google/uuid" ) const ( testValidUUID = "01970000-0000-7000-8000-000000000001" testValidUUIDAlt = "01970000-0000-7000-8000-000000000002" ) func newReq(t *testing.T) *http.Request { t.Helper() req, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", nil) if err != nil { t.Fatalf("NewRequest: %v", err) } return req } func TestEnsureClaudeCodeSessionID_NilRequest(t *testing.T) { // Should not panic. ensureClaudeCodeSessionID(nil, nil, "oauth", true) } func TestEnsureClaudeCodeSessionID_FromMetadataJSON(t *testing.T) { req := newReq(t) body := []byte(`{"metadata":{"user_id":"{\"device_id\":\"abc\",\"account_uuid\":\"\",\"session_id\":\"` + testValidUUID + `\"}"}}`) ensureClaudeCodeSessionID(req, body, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != testValidUUID { t.Fatalf("session_id = %q, want %q", got, testValidUUID) } } func TestEnsureClaudeCodeSessionID_FromMetadataLegacy(t *testing.T) { req := newReq(t) // legacy format: user_{64hex}_account_{uuid}_session_{uuid} dev := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" acc := "11111111-2222-3333-4444-555555555555" uid := "user_" + dev + "_account_" + acc + "_session_" + testValidUUID body := []byte(`{"metadata":{"user_id":"` + uid + `"}}`) ensureClaudeCodeSessionID(req, body, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != testValidUUID { t.Fatalf("session_id = %q, want %q", got, testValidUUID) } } // 安全测试:metadata.user_id 中的 session_id 不是合法 UUID 时, // 必须 fallback 到 OAuth UUID 兜底,而不是写入恶意值。 func TestEnsureClaudeCodeSessionID_RejectsMalformedMetadataUUID(t *testing.T) { req := newReq(t) // session_id 字段是 36 字符但非 UUID 格式(凑数 hex+dash) bogus := "abcdefab-0000-0000-0000-not-a-real-uuid" body := []byte(`{"metadata":{"user_id":"{\"device_id\":\"abc\",\"account_uuid\":\"\",\"session_id\":\"` + bogus + `\"}"}}`) ensureClaudeCodeSessionID(req, body, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got == bogus { t.Fatalf("malformed metadata session_id was written verbatim: %q", got) } if got == "" { t.Fatalf("expected OAuth mimic to fallback to UUID, got empty") } if _, err := uuid.Parse(got); err != nil { t.Fatalf("fallback session_id is not a valid UUID: %q (err=%v)", got, err) } } func TestEnsureClaudeCodeSessionID_PreservesExistingValidHeader(t *testing.T) { req := newReq(t) setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", testValidUUID) ensureClaudeCodeSessionID(req, nil, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != testValidUUID { t.Fatalf("session_id = %q, want preserved %q", got, testValidUUID) } } func TestEnsureClaudeCodeSessionID_OverwritesInvalidHeader(t *testing.T) { req := newReq(t) setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", "not-a-uuid") ensureClaudeCodeSessionID(req, nil, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got == "not-a-uuid" { t.Fatalf("invalid header was not overwritten: %q", got) } if _, err := uuid.Parse(got); err != nil { t.Fatalf("fallback session_id is not a valid UUID: %q (err=%v)", got, err) } } func TestEnsureClaudeCodeSessionID_OAuthMimicFallback(t *testing.T) { req := newReq(t) ensureClaudeCodeSessionID(req, nil, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got == "" { t.Fatalf("expected OAuth mimic fallback to set X-Claude-Code-Session-Id") } if _, err := uuid.Parse(got); err != nil { t.Fatalf("fallback session_id is not a valid UUID: %q (err=%v)", got, err) } } // API key passthrough 路径必须不被污染:缺失 session-id 时不强制生成。 func TestEnsureClaudeCodeSessionID_DoesNotPolluteAPIKey(t *testing.T) { req := newReq(t) ensureClaudeCodeSessionID(req, nil, "api_key", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != "" { t.Fatalf("API key path should NOT auto-generate session-id, got %q", got) } } // API key 路径即使 body 中有合法 metadata.user_id,也不应该派生 session-id 头。 // 这是 fresh code review 发现的 C1 修复:保护客户端原始语义。 func TestEnsureClaudeCodeSessionID_APIKeyIgnoresMetadata(t *testing.T) { req := newReq(t) // 客户端传入合法的 metadata.user_id,但 tokenType=api_key body := []byte(`{"metadata":{"user_id":"{\"device_id\":\"abc\",\"account_uuid\":\"\",\"session_id\":\"` + testValidUUID + `\"}"}}`) ensureClaudeCodeSessionID(req, body, "api_key", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != "" { t.Fatalf("API key path must NOT derive session-id from metadata, got %q", got) } } // OAuth 路径即使 mimic=false 也应该从 metadata 派生 header: // OAuth 凭证本身就是 Claude Code 类型账号,metadata.user_id 可信任。 // 这与 API key 路径不同(API key 是任意第三方调用方)。 func TestEnsureClaudeCodeSessionID_OAuthNonMimicDerivesFromMetadata(t *testing.T) { req := newReq(t) body := []byte(`{"metadata":{"user_id":"{\"device_id\":\"abc\",\"account_uuid\":\"\",\"session_id\":\"` + testValidUUID + `\"}"}}`) ensureClaudeCodeSessionID(req, body, "oauth", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != testValidUUID { t.Fatalf("OAuth must derive session-id from metadata regardless of mimic flag, got %q want %q", got, testValidUUID) } } // API key 路径若客户端传入了非法 UUID header,必须删除避免向上游透传。 // 这是 C2 修复:UUID 校验承诺要对所有路径生效。 func TestEnsureClaudeCodeSessionID_APIKeyDeletesInvalidHeader(t *testing.T) { req := newReq(t) setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", "not-a-uuid") ensureClaudeCodeSessionID(req, nil, "api_key", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != "" { t.Fatalf("API key path must delete invalid header, got %q", got) } } // API key 路径若客户端传入了合法 UUID header,规范化保留(不删除)。 func TestEnsureClaudeCodeSessionID_APIKeyPreservesValidHeader(t *testing.T) { req := newReq(t) setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", testValidUUID) ensureClaudeCodeSessionID(req, nil, "api_key", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != testValidUUID { t.Fatalf("API key path must preserve valid client header, got %q want %q", got, testValidUUID) } } // OAuth 但非 mimic 场景:保留客户端原始语义,不自动生成。 func TestEnsureClaudeCodeSessionID_OAuthNonMimicDoesNotForce(t *testing.T) { req := newReq(t) ensureClaudeCodeSessionID(req, nil, "oauth", false) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") if got != "" { t.Fatalf("non-mimic OAuth should not auto-generate, got %q", got) } } // 验证不同 UUID 输入会被规范化为 canonical 小写形式。 func TestEnsureClaudeCodeSessionID_CanonicalForm(t *testing.T) { req := newReq(t) // 大写 UUID 输入 upper := "01970000-0000-7000-8000-ABCDEF000003" setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", upper) ensureClaudeCodeSessionID(req, nil, "oauth", true) got := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id") want := "01970000-0000-7000-8000-abcdef000003" if got != want { t.Fatalf("session_id = %q, want canonical %q", got, want) } }