package proxy import ( "net/http" "strings" "testing" ) // --- NewUpstreamClient --- func TestNewUpstreamClient_NilProfile(t *testing.T) { uc := NewUpstreamClient(nil) if uc == nil { t.Fatal("NewUpstreamClient returned nil") } if uc.sessionID == "" { t.Error("expected non-empty sessionID") } if uc.profile != nil { t.Error("expected nil profile") } } func TestNewUpstreamClient_WithProfile(t *testing.T) { profile := &SniffedProfile{ Version: "1.2.3", Headers: [][2]string{{"User-Agent", "test/1.0"}}, } uc := NewUpstreamClient(profile) if uc.profile != profile { t.Error("expected profile to be stored") } if uc.sessionID == "" { t.Error("expected non-empty sessionID") } } func TestNewUpstreamClient_UniqueSessionIDs(t *testing.T) { uc1 := NewUpstreamClient(nil) uc2 := NewUpstreamClient(nil) if uc1.sessionID == uc2.sessionID { t.Errorf("expected different session IDs, both got %q", uc1.sessionID) } } // --- version() --- func TestVersion_WithProfileVersion(t *testing.T) { uc := &UpstreamClient{ profile: &SniffedProfile{Version: "3.5.7"}, } if got := uc.version(); got != "3.5.7" { t.Errorf("version() = %q, want %q", got, "3.5.7") } } func TestVersion_NilProfile_Fallback(t *testing.T) { uc := &UpstreamClient{profile: nil} if got := uc.version(); got != "2.1.92" { t.Errorf("version() = %q, want %q", got, "2.1.92") } } func TestVersion_EmptyProfileVersion_Fallback(t *testing.T) { uc := &UpstreamClient{ profile: &SniffedProfile{Version: ""}, } if got := uc.version(); got != "2.1.92" { t.Errorf("version() = %q, want %q", got, "2.1.92") } } // --- applyHeaders --- func TestApplyHeaders_NilProfile_NonOAuth_NonStream(t *testing.T) { uc := &UpstreamClient{ sessionID: "test-session-id", profile: nil, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-api123", false) // x-api-key for non-OAuth token if got := req.Header.Get("x-api-key"); got != "sk-ant-api123" { t.Errorf("x-api-key = %q, want %q", got, "sk-ant-api123") } // Should NOT have Authorization if got := req.Header.Get("Authorization"); got != "" { t.Errorf("Authorization = %q, want empty", got) } // Session ID if got := req.Header.Get("X-Claude-Code-Session-Id"); got != "test-session-id" { t.Errorf("X-Claude-Code-Session-Id = %q, want %q", got, "test-session-id") } // Request ID should be a UUID if got := req.Header.Get("x-client-request-id"); got == "" { t.Error("expected non-empty x-client-request-id") } // Non-stream: application/json if got := req.Header.Get("Accept"); got != "application/json" { t.Errorf("Accept = %q, want %q", got, "application/json") } // Accept-Encoding always identity if got := req.Header.Get("Accept-Encoding"); got != "identity" { t.Errorf("Accept-Encoding = %q, want %q", got, "identity") } } func TestApplyHeaders_NilProfile_NonOAuth_Stream(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: nil, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-api123", true) if got := req.Header.Get("Accept"); got != "text/event-stream" { t.Errorf("Accept = %q, want %q", got, "text/event-stream") } } func TestApplyHeaders_OAuthToken_SetsBearerAndBetaFlag(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: nil, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-oat-mytoken", false) // OAuth: Authorization Bearer if got := req.Header.Get("Authorization"); got != "Bearer sk-ant-oat-mytoken" { t.Errorf("Authorization = %q, want %q", got, "Bearer sk-ant-oat-mytoken") } // Should NOT have x-api-key if got := req.Header.Get("x-api-key"); got != "" { t.Errorf("x-api-key = %q, want empty for OAuth", got) } // anthropic-beta should include oauth-2025-04-20 if got := req.Header.Get("anthropic-beta"); got != "oauth-2025-04-20" { t.Errorf("anthropic-beta = %q, want %q", got, "oauth-2025-04-20") } } func TestApplyHeaders_OAuthToken_AppendsToExistingBeta(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: &SniffedProfile{ Headers: [][2]string{ {"anthropic-beta", "max-tokens-3-5-sonnet-2024-07-15"}, }, }, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-oat-tok", false) beta := req.Header.Get("anthropic-beta") if !strings.Contains(beta, "max-tokens-3-5-sonnet-2024-07-15") { t.Errorf("anthropic-beta %q should contain existing beta", beta) } if !strings.Contains(beta, "oauth-2025-04-20") { t.Errorf("anthropic-beta %q should contain oauth flag", beta) } // Should be appended with comma if beta != "max-tokens-3-5-sonnet-2024-07-15,oauth-2025-04-20" { t.Errorf("anthropic-beta = %q, want %q", beta, "max-tokens-3-5-sonnet-2024-07-15,oauth-2025-04-20") } } func TestApplyHeaders_OAuthToken_ExistingBetaAlreadyHasOAuth(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: &SniffedProfile{ Headers: [][2]string{ {"anthropic-beta", "oauth-2025-04-20,something-else"}, }, }, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-oat-tok", false) beta := req.Header.Get("anthropic-beta") // Should NOT duplicate oauth flag count := strings.Count(beta, "oauth-2025-04-20") if count != 1 { t.Errorf("oauth flag appeared %d times in %q, want 1", count, beta) } } func TestApplyHeaders_WithProfile_ReplaysHeaders(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: &SniffedProfile{ Headers: [][2]string{ {"User-Agent", "Claude/1.0"}, {"anthropic-version", "2023-06-01"}, {"Custom-Header", "custom-value"}, }, }, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-api123", false) if got := req.Header.Get("User-Agent"); got != "Claude/1.0" { t.Errorf("User-Agent = %q, want %q", got, "Claude/1.0") } if got := req.Header.Get("anthropic-version"); got != "2023-06-01" { t.Errorf("anthropic-version = %q, want %q", got, "2023-06-01") } if got := req.Header.Get("Custom-Header"); got != "custom-value" { t.Errorf("Custom-Header = %q, want %q", got, "custom-value") } } func TestApplyHeaders_ProfileAuthHeadersRemoved(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: &SniffedProfile{ Headers: [][2]string{ {"Authorization", "Bearer old-token"}, {"x-api-key", "old-api-key"}, {"User-Agent", "test"}, }, }, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-api-new", false) // Old auth headers from profile should be removed if got := req.Header.Get("Authorization"); got != "" { t.Errorf("Authorization should be empty for non-OAuth, got %q", got) } // New auth should be set via x-api-key if got := req.Header.Get("x-api-key"); got != "sk-ant-api-new" { t.Errorf("x-api-key = %q, want %q", got, "sk-ant-api-new") } // User-Agent from profile should remain if got := req.Header.Get("User-Agent"); got != "test" { t.Errorf("User-Agent = %q, want %q", got, "test") } } func TestApplyHeaders_ProfileAuthHeadersRemovedForOAuth(t *testing.T) { uc := &UpstreamClient{ sessionID: "sess", profile: &SniffedProfile{ Headers: [][2]string{ {"Authorization", "Bearer old-token"}, {"x-api-key", "old-api-key"}, }, }, } req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-oat-new", false) // Old x-api-key removed if got := req.Header.Get("x-api-key"); got != "" { t.Errorf("x-api-key should be empty for OAuth, got %q", got) } // New auth set via Authorization if got := req.Header.Get("Authorization"); got != "Bearer sk-ant-oat-new" { t.Errorf("Authorization = %q, want %q", got, "Bearer sk-ant-oat-new") } } func TestApplyHeaders_AcceptEncoding_AlwaysIdentity(t *testing.T) { tests := []struct { name string streaming bool }{ {"non-stream", false}, {"stream", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { uc := &UpstreamClient{sessionID: "s", profile: nil} req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "token", tt.streaming) if got := req.Header.Get("Accept-Encoding"); got != "identity" { t.Errorf("Accept-Encoding = %q, want %q", got, "identity") } }) } } func TestApplyHeaders_UniqueRequestIDs(t *testing.T) { uc := &UpstreamClient{sessionID: "s", profile: nil} req1, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req1, "tok", false) req2, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req2, "tok", false) id1 := req1.Header.Get("x-client-request-id") id2 := req2.Header.Get("x-client-request-id") if id1 == "" || id2 == "" { t.Fatal("expected non-empty request IDs") } if id1 == id2 { t.Errorf("expected unique request IDs, both got %q", id1) } } func TestApplyHeaders_NonOAuth_NoAnthroPicBetaSet(t *testing.T) { // Non-OAuth tokens should NOT set anthropic-beta oauth flag uc := &UpstreamClient{sessionID: "s", profile: nil} req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-api123", false) beta := req.Header.Get("anthropic-beta") if strings.Contains(beta, "oauth-2025-04-20") { t.Errorf("non-OAuth token should not have oauth beta flag, got %q", beta) } } func TestApplyHeaders_OAuthToken_FreshBeta(t *testing.T) { // No profile, no existing beta — should set fresh uc := &UpstreamClient{sessionID: "s", profile: nil} req, _ := http.NewRequest(http.MethodPost, "https://example.com", nil) uc.applyHeaders(req, "sk-ant-oat-tok", false) if got := req.Header.Get("anthropic-beta"); got != "oauth-2025-04-20" { t.Errorf("anthropic-beta = %q, want %q", got, "oauth-2025-04-20") } }