9150f466e5
Characterization tests capturing current behavior before refactoring. Covers auth, config, logging, proxy, ratelimit, server, and telemetry packages with race-safe concurrent access tests.
335 lines
9.8 KiB
Go
335 lines
9.8 KiB
Go
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")
|
|
}
|
|
}
|