package proxy import ( "net/http" "net/http/httptest" "strings" "testing" ) func newRequest(t *testing.T, headers map[string][]string) *http.Request { t.Helper() r := httptest.NewRequest("POST", "/v1/messages", nil) r.Header = http.Header{} for k, vals := range headers { for _, v := range vals { r.Header.Add(k, v) } } return r } func TestExtractProfile_BasicHeaders(t *testing.T) { r := newRequest(t, map[string][]string{ "Content-Type": {"application/json"}, "X-Custom-Header": {"custom-value"}, "User-Agent": {"Claude/1.2.3 linux"}, }) body := []byte(`{"model":"claude-sonnet-4-6"}`) p := extractProfile(r, body) // Check version parsed if p.Version != "1.2.3" { t.Errorf("version = %q, want %q", p.Version, "1.2.3") } // Check body preserved if string(p.Body) != string(body) { t.Errorf("body not preserved") } // Check headers captured found := map[string]bool{} for _, h := range p.Headers { found[strings.ToLower(h[0])] = true } if !found["content-type"] { t.Error("Content-Type header should be captured") } if !found["x-custom-header"] { t.Error("X-Custom-Header should be captured") } } func TestExtractProfile_SkipHeaders(t *testing.T) { r := newRequest(t, map[string][]string{ "Host": {"example.com"}, "Content-Length": {"42"}, "Authorization": {"Bearer token123"}, "X-Api-Key": {"key123"}, "Connection": {"keep-alive"}, "Content-Type": {"application/json"}, "X-Custom": {"keep-me"}, }) p := extractProfile(r, []byte(`{}`)) for _, h := range p.Headers { lower := strings.ToLower(h[0]) if skipHeaders[lower] { t.Errorf("header %q should have been skipped", h[0]) } } // Verify non-skipped headers are present found := map[string]bool{} for _, h := range p.Headers { found[strings.ToLower(h[0])] = true } if !found["content-type"] { t.Error("Content-Type should be kept") } if !found["x-custom"] { t.Error("X-Custom should be kept") } } func TestExtractProfile_HeaderDeduplication(t *testing.T) { r := newRequest(t, map[string][]string{ "Content-Type": {"application/json"}, }) // Add duplicate with different casing - Go's http.Header normalizes to canonical form // so we need to add the same canonical header with multiple values to test dedup r.Header.Add("Content-Type", "text/plain") p := extractProfile(r, []byte(`{}`)) // After deduplication by lowercase key, only one entry per key seen := map[string]int{} for _, h := range p.Headers { seen[strings.ToLower(h[0])]++ } for key, count := range seen { if count > 1 { t.Errorf("header %q appears %d times after dedup, want 1", key, count) } } } func TestExtractProfile_AnthropicBetaContextStripping(t *testing.T) { r := newRequest(t, map[string][]string{ "Anthropic-Beta": {"prompt-caching-2024-07-31,context-1m-2024-09-01,some-other-beta"}, }) p := extractProfile(r, []byte(`{}`)) var betaValue string for _, h := range p.Headers { if strings.ToLower(h[0]) == "anthropic-beta" { betaValue = h[1] break } } if strings.Contains(betaValue, "context-1m") { t.Errorf("context-1m should be stripped from anthropic-beta, got %q", betaValue) } if !strings.Contains(betaValue, "prompt-caching-2024-07-31") { t.Errorf("prompt-caching should be preserved, got %q", betaValue) } if !strings.Contains(betaValue, "some-other-beta") { t.Errorf("some-other-beta should be preserved, got %q", betaValue) } } func TestExtractProfile_AnthropicBetaAllContextRemoved(t *testing.T) { r := newRequest(t, map[string][]string{ "Anthropic-Beta": {"context-1m-2024-09-01"}, }) p := extractProfile(r, []byte(`{}`)) for _, h := range p.Headers { if strings.ToLower(h[0]) == "anthropic-beta" { // All betas were context-1m, so after filtering the value should be empty if h[1] != "" { t.Errorf("all context-1m betas stripped should leave empty, got %q", h[1]) } return } } // It's also acceptable if the header is still present but empty } func TestExtractProfile_VersionParsing(t *testing.T) { tests := []struct { name string userAgent string expected string }{ { name: "standard Claude UA", userAgent: "Claude/1.2.3 linux x86_64", expected: "1.2.3", }, { name: "version with no space after", userAgent: "Claude/4.5.6", expected: "4.5.6", }, { name: "no slash in UA", userAgent: "Mozilla 5.0", expected: "", }, { name: "empty UA", userAgent: "", expected: "", }, { name: "slash at start", userAgent: "/1.0.0 rest", expected: "", }, { name: "multiple slashes", userAgent: "App/1.0.0 (sub/2.0)", expected: "1.0.0", }, { name: "version only after slash no space", userAgent: "Tool/9.8.7", expected: "9.8.7", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := newRequest(t, map[string][]string{ "User-Agent": {tt.userAgent}, }) p := extractProfile(r, []byte(`{}`)) if p.Version != tt.expected { t.Errorf("version = %q, want %q", p.Version, tt.expected) } }) } } func TestExtractProfile_EmptyHeaders(t *testing.T) { r := httptest.NewRequest("POST", "/v1/messages", nil) r.Header = http.Header{} p := extractProfile(r, []byte(`{"test":true}`)) if len(p.Headers) != 0 { t.Errorf("expected no headers, got %d", len(p.Headers)) } if p.Version != "" { t.Errorf("expected empty version with no UA, got %q", p.Version) } if string(p.Body) != `{"test":true}` { t.Errorf("body not preserved") } } func TestExtractProfile_BodyPreserved(t *testing.T) { r := newRequest(t, map[string][]string{ "User-Agent": {"Claude/1.0.0 test"}, }) body := []byte(`{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hello"}],"stream":true}`) p := extractProfile(r, body) if string(p.Body) != string(body) { t.Errorf("body not preserved.\ngot: %s\nwant: %s", p.Body, body) } } func TestSkipHeaders_Entries(t *testing.T) { expected := map[string]bool{ "host": true, "content-length": true, "authorization": true, "x-api-key": true, "connection": true, } if len(skipHeaders) != len(expected) { t.Errorf("skipHeaders has %d entries, want %d", len(skipHeaders), len(expected)) } for k, v := range expected { if skipHeaders[k] != v { t.Errorf("skipHeaders[%q] = %v, want %v", k, skipHeaders[k], v) } } } func TestSniffedProfile_Fields(t *testing.T) { // Verify the struct can hold all expected data p := &SniffedProfile{ Headers: [][2]string{{"Content-Type", "application/json"}}, Body: []byte(`{}`), Version: "1.0.0", } if len(p.Headers) != 1 { t.Error("Headers should have 1 entry") } if p.Headers[0][0] != "Content-Type" || p.Headers[0][1] != "application/json" { t.Error("Header not stored correctly") } if string(p.Body) != `{}` { t.Error("Body not stored correctly") } if p.Version != "1.0.0" { t.Error("Version not stored correctly") } }