Files
magpie/internal/gateway/claude_code_headers_test.go

90 lines
3.2 KiB
Go

package gateway
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// A relay that serves only Claude Code (#179: packy's "only accessible via
// the official Claude CLI") gets Claude Code's own headers and body as it
// sent them, the provider's key in place of magpie's
func TestClaudeCodeHeadersReachAnthropicRelay(t *testing.T) {
f := &fake{t: t, ctype: "application/json", reply: `{"id":"msg","type":"message","content":[]}`}
up := setup(t, provider.Anthropic, f)
var query string
up.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
query = r.URL.RawQuery
f.ServeHTTP(w, r)
})
claude := http.Header{
"User-Agent": {"claude-cli/2.1.0 (external, cli)"},
"X-App": {"cli"},
"Anthropic-Version": {"2023-06-01"},
"Anthropic-Beta": {"claude-code-20250219,interleaved-thinking-2025-05-14"},
"Anthropic-Dangerous-Direct-Browser-Access": {"true"},
"X-Stainless-Lang": {"js"},
"X-Stainless-Package-Version": {"0.60.0"},
"X-Stainless-Retry-Count": {"0"},
"X-Claude-Code-Session-Id": {"ses-1"},
"Authorization": {"Bearer " + Token},
"X-Api-Key": {Token},
}
body := `{"model":"m1","max_tokens":5,"system":[{"type":"text","text":"You are Claude Code, Anthropic's official CLI for Claude."}],"metadata":{"user_id":"user_abc_account__session_123"},"messages":[{"role":"user","content":"hi"}]}`
send := func(path string, h http.Header) {
t.Helper()
rec := httptest.NewRecorder()
req := httptest.NewRequest("POST", path, strings.NewReader(body))
for k, v := range h {
req.Header[k] = v
}
New().Handler().ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("%d %s", rec.Code, rec.Body.String())
}
}
send("/v1/messages?beta=true", claude)
for k, v := range claude {
if k == "Authorization" || k == "X-Api-Key" {
continue
}
if got := f.head.Get(k); got != v[0] {
t.Errorf("%s: got %q, want %q", k, got, v[0])
}
}
if a, k := f.head.Get("Authorization"), f.head.Get("X-Api-Key"); a != "Bearer k" || k != "k" {
t.Errorf("auth: %q %q", a, k)
}
if query != "beta=true" {
t.Errorf("query %q", query)
}
for _, want := range []string{`"You are Claude Code, Anthropic's official CLI for Claude."`, `"metadata":{"user_id":"user_abc_account__session_123"}`} {
if !bytes.Contains(f.got, []byte(want)) {
t.Errorf("body lost %s: %s", want, f.got)
}
}
// a provider with no key of its own still never sees magpie's
p, _ := provider.Find("fake")
p.Key = ""
provider.Save(*p)
send("/v1/messages", claude)
if a, k := f.head.Get("Authorization"), f.head.Get("X-Api-Key"); a != "" || k != "" {
t.Errorf("magpie's key leaked: %q %q", a, k)
}
if f.head.Get("X-App") != "cli" {
t.Errorf("x-app: %v", f.head)
}
// another agent's request goes as magpie's
send("/v1/messages?beta=true", http.Header{"User-Agent": {"opencode/1.0"}, "X-App": {"other"}, "X-Stainless-Lang": {"js"}})
if ua := f.head.Get("User-Agent"); !strings.HasPrefix(ua, "magpie/") || f.head.Get("X-App") != "" || f.head.Get("X-Stainless-Lang") != "" || query != "" {
t.Errorf("other agent: %q %v %q", ua, f.head, query)
}
}