340 lines · 14.4 KB · Go
RawDownload
package helps import (	"context"	"net/http"	"strings"	"testing" 	internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging") func TestParseCodexQuotaEventHeadersPreservesActiveLimit(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"plan_type":"pro",		"metered_limit_name":"codex_bengalfox",		"rate_limits":{			"primary":{"used_percent":2,"window_minutes":10080,"reset_at":1782951970},			"secondary":null		}	}`))	if headers.Get("X-Codex-Active-Limit") != "codex_bengalfox" ||		headers.Get("X-Codex-Primary-Used-Percent") != "2" ||		headers.Get("X-Codex-Primary-Window-Minutes") != "10080" ||		headers.Get("X-Codex-Primary-Reset-At") != "1782951970" ||		headers.Get("X-Codex-Plan-Type") != "pro" {		t.Fatalf("unexpected quota headers: %#v", headers)	}} func TestParseCodexQuotaEventHeadersPreservesAdditionalLimitsAndCredits(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"plan_type":"pro",		"rate_limits":{			"allowed":true,			"limit_reached":false,			"primary":{"used_percent":48,"window_minutes":10080,"reset_after_seconds":523210,"reset_at":1786677299},			"secondary":null		},		"additional_rate_limits":{			"GPT-5.3-Codex-Spark":{				"allowed":true,				"limit_reached":false,				"primary":{"used_percent":3,"window_minutes":300,"reset_after_seconds":10148,"reset_at":1787231961},				"secondary":{"used_percent":63,"window_minutes":10080,"reset_after_seconds":75420,"reset_at":1787290791}			}		},		"credits":{"has_credits":false,"unlimited":false,"balance":"0"}	}`))	for key, want := range map[string]string{		"X-Codex-Primary-Used-Percent":                                  "48",		"X-Codex-Primary-Window-Minutes":                                "10080",		"X-Codex-Primary-Reset-After-Seconds":                           "523210",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Limit-Name":             "GPT-5.3-Codex-Spark",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Primary-Used-Percent":   "3",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Primary-Window-Minutes": "300",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Secondary-Used-Percent": "63",		"X-Codex-Credits-Has-Credits":                                   "false",		"X-Codex-Credits-Unlimited":                                     "false",		"X-Codex-Credits-Balance":                                       "0",	} {		if got := headers.Get(key); got != want {			t.Fatalf("header %s = %q, want %q; all headers = %#v", key, got, want, headers)		}	}} func TestParseCodexQuotaEventHeadersReadsQuotaHeadersFromErrorFrames(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"error",		"status_code":429,		"headers":{			"X-Codex-Primary-Used-Percent":"100",			"X-Codex-Primary-Window-Minutes":"10080",			"X-Codex-Primary-Reset-After-Seconds":"437380",			"X-Codex-Credits-Balance":"0",			"X-Codex-Turn-State":"must-not-be-observed",			"Set-Cookie":"must-not-be-observed"		}	}`))	if headers.Get("X-Codex-Primary-Used-Percent") != "100" ||		headers.Get("X-Codex-Primary-Reset-After-Seconds") != "437380" ||		headers.Get("X-Codex-Credits-Balance") != "0" {		t.Fatalf("unexpected error quota headers: %#v", headers)	}	if headers.Get("Set-Cookie") != "" || headers.Get("X-Codex-Turn-State") != "" {		t.Fatalf("non-quota error header leaked into quota observation: %#v", headers)	}} func TestParseCodexQuotaEventHeadersDoesNotInventActiveLimitAndRejectsIncompleteWindows(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"rate_limits":{"primary":{"used_percent":42,"window_minutes":300,"reset_after_seconds":60}}	}`))	if headers.Get("X-Codex-Active-Limit") != "" || headers.Get("X-Codex-Primary-Reset-After-Seconds") != "60" {		t.Fatalf("unexpected quota headers: %#v", headers)	}	if got := ParseCodexQuotaEventHeaders([]byte(`{"type":"codex.rate_limits","rate_limits":{"primary":{"used_percent":42}}}`)); got != nil {		t.Fatalf("incomplete quota event produced headers: %#v", got)	} 	headers = ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"rate_limits":{			"primary":{"used_percent":42,"window_minutes":300},			"secondary":{"used_percent":17,"window_minutes":10080,"reset_after_seconds":60}		}	}`))	if headers.Get("X-Codex-Primary-Used-Percent") != "" ||		headers.Get("X-Codex-Secondary-Used-Percent") != "17" {		t.Fatalf("partially valid quota event produced incomplete headers: %#v", headers)	}} func TestParseCodexQuotaEventHeadersSupportsAdditionalArrayAndCamelCase(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"planType":"pro",		"meteredLimitName":"premium",		"rateLimit":{			"allowed":true,			"limitReached":false,			"primary":{"usedPercent":71,"windowMinutes":10080,"resetAfterSeconds":60}		},		"additionalRateLimits":[{			"limitName":"GPT-5.3-Codex-Spark",			"rateLimit":{				"primary":{"usedPercent":13,"windowMinutes":300,"resetAt":1787399817},				"secondary":{"usedPercent":51,"windowMinutes":10080,"resetAfterSeconds":120}			}		}]	}`))	for key, want := range map[string]string{		"X-Codex-Active-Limit":                                                 "premium",		"X-Codex-Plan-Type":                                                    "pro",		"X-Codex-Primary-Used-Percent":                                         "71",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Limit-Name":                    "GPT-5.3-Codex-Spark",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Primary-Used-Percent":          "13",		"X-Codex-Additional-GPT-5.3-Codex-Spark-Secondary-Reset-After-Seconds": "120",	} {		if got := headers.Get(key); got != want {			t.Fatalf("header %s = %q, want %q; all headers = %#v", key, got, want, headers)		}	}} func TestParseCodexQuotaEventHeadersKeepsOnlySafeErrorHeaders(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"error",		"headers":{			"X-Ratelimit-Remaining-Requests":"7",			"Retry-After":"60",			"X-Codex-Primary-Over-Secondary-Limit-Percent":"0",			"X-Codex-Turn-State":"secret",			"Authorization":"Bearer secret"		}	}`))	for key, want := range map[string]string{		"X-Ratelimit-Remaining-Requests":               "7",		"Retry-After":                                  "60",		"X-Codex-Primary-Over-Secondary-Limit-Percent": "0",	} {		if got := headers.Get(key); got != want {			t.Fatalf("header %s = %q, want %q", key, got, want)		}	}	if headers.Get("X-Codex-Turn-State") != "" || headers.Get("Authorization") != "" {		t.Fatalf("unsafe error headers leaked: %#v", headers)	}} func TestParseCodexQuotaEventHeadersNonQuotaEventDoesNotAllocate(t *testing.T) {	payload := []byte(`{"type":"response.output_text.delta","delta":"hello"}`)	allocs := testing.AllocsPerRun(1000, func() {		if headers := ParseCodexQuotaEventHeaders(payload); headers != nil {			t.Fatalf("non-quota event produced headers: %#v", headers)		}	})	if allocs != 0 {		t.Fatalf("non-quota event allocations = %v, want 0", allocs)	}} // Large non-quota frames must stay allocation-free: only the bounded frame// prefix is scanned for the type discriminator.func TestParseCodexQuotaEventHeadersLargeNonQuotaFrameDoesNotAllocate(t *testing.T) {	big := strings.Repeat("x", 4096)	payload := []byte(`{"sequence_number":7,"type":"response.output_text.delta","delta":"` + big + `"}`)	allocs := testing.AllocsPerRun(100, func() {		if headers := ParseCodexQuotaEventHeaders(payload); headers != nil {			t.Fatalf("non-quota event produced headers: %#v", headers)		}	})	if allocs != 0 {		t.Fatalf("large non-quota frame allocations = %v, want 0", allocs)	}} // Quota markers live in the frame prefix, so large quota events must still be parsed.func TestParseCodexQuotaEventHeadersLargeRateLimitsFrameIsParsed(t *testing.T) {	padding := strings.Repeat("p", 4096)	payload := []byte(`{"type":"codex.rate_limits","rate_limits":{"primary":{"used_percent":42,"limit_percent":100,"window_minutes":10080,"reset_after_seconds":3600,"used_minutes":120},"secondary":{"used_percent":1,"limit_percent":100,"window_minutes":300,"reset_after_seconds":60,"used_minutes":2},"active_limit":"primary","credits":{"total_credits":"10","available_credits":"5","pct_remaining":"50"}},"padding":"` + padding + `"}`)	headers := ParseCodexQuotaEventHeaders(payload)	if headers == nil {		t.Fatal("large rate_limits frame produced no headers")	}	if got := headers.Get("X-Codex-Primary-Used-Percent"); got != "42" {		t.Fatalf("X-Codex-Primary-Used-Percent = %q, want 42", got)	}} func TestParseCodexQuotaEventHeadersRejectsInvalidAndEmptyEvents(t *testing.T) {	for _, payload := range []string{		``,		`{"type":"response.completed"}`,		`{"type":"codex.rate_limits","rate_limits":{"primary":{"used_percent":101,"window_minutes":300,"reset_after_seconds":60}}}`,	} {		if headers := ParseCodexQuotaEventHeaders([]byte(payload)); headers != nil {			t.Fatalf("payload %q produced unexpected headers: %#v", payload, headers)		}	}} // A malformed active-limit name must not discard the window watermarks that were// parsed successfully from the same event.func TestParseCodexQuotaEventHeadersKeepsWindowsWhenActiveLimitIsInvalid(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"metered_limit_name":"bad limit",		"rate_limits":{"primary":{"used_percent":1,"window_minutes":300,"reset_after_seconds":60}}	}`))	if headers == nil {		t.Fatal("valid window watermarks were discarded")	}	if got := headers.Get("X-Codex-Primary-Used-Percent"); got != "1" {		t.Fatalf("X-Codex-Primary-Used-Percent = %q, want \"1\"", got)	}	if got := headers.Get("X-Codex-Active-Limit"); got != "" {		t.Fatalf("malformed active limit was retained: %q", got)	}} // Real websocket events namespace additional limits by limit name and carry a// code_review_rate_limits sibling; both must survive parsing.func TestParseCodexQuotaEventHeadersObservedProductionEvent(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"plan_type":"pro",		"rate_limits":{"allowed":true,"limit_reached":false,"primary":{"used_percent":81,"window_minutes":10080,"reset_after_seconds":137160,"reset_at":1787588999},"secondary":null},		"code_review_rate_limits":{"primary":{"used_percent":4,"window_minutes":300,"reset_after_seconds":900}},		"additional_rate_limits":{"GPT-5.3-Codex-Spark":{"allowed":true,"limit_reached":false,"primary":{"used_percent":0,"window_minutes":300,"reset_after_seconds":18000}}},		"credits":{"has_credits":false,"unlimited":false,"balance":"0"},		"promo":null	}`))	if headers == nil {		t.Fatal("observed production event produced no headers")	}	for key, want := range map[string]string{		"X-Codex-Plan-Type":                                           "pro",		"X-Codex-Primary-Used-Percent":                                "81",		"X-Codex-Primary-Reset-At":                                    "1787588999",		"X-Codex-Code-Review-Primary-Used-Percent":                    "4",		"X-Codex-Additional-Gpt-5.3-Codex-Spark-Limit-Name":           "GPT-5.3-Codex-Spark",		"X-Codex-Additional-Gpt-5.3-Codex-Spark-Primary-Used-Percent": "0",		"X-Codex-Credits-Balance":                                     "0",		"X-Codex-Credits-Has-Credits":                                 "false",	} {		if got := headers.Get(key); got != want {			t.Fatalf("header %s = %q, want %q", key, got, want)		}	}	// secondary is null upstream and must not be invented.	if got := headers.Get("X-Codex-Secondary-Used-Percent"); got != "" {		t.Fatalf("null secondary window was materialized: %q", got)	}} // An upstream-controlled limit name containing control characters must not reach// the plain-text request log.func TestParseCodexQuotaEventHeadersRejectsControlCharactersInLimitName(t *testing.T) {	headers := ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"additional_rate_limits":[{"limit_name":"evil\r\nX-Injected: 1","primary":{"used_percent":3,"window_minutes":300,"reset_after_seconds":60}}]	}`))	if headers == nil {		t.Fatal("expected window watermarks to survive")	}	for key, values := range headers {		for _, value := range values {			if strings.ContainsAny(value, "\r\n") {				t.Fatalf("header %s carried control characters: %q", key, value)			}		}	}} // Codex quota parsing must stay on the codex-specific websocket entry point.// The generic entry point is shared with other providers (xAI), whose frames// must never be reinterpreted as codex quota data: an xAI error frame really// does carry x-ratelimit-* headers, and merging them here would forge codex// quota headers into an unrelated provider's request log.func TestGenericWebsocketEntryPointDoesNotObserveCodexQuota(t *testing.T) {	frame := []byte(`{"type":"error","headers":{"x-ratelimit-remaining-requests":"0","retry-after":"60"}}`) 	genericCtx := internallogging.WithResponseHeadersHolder(context.Background())	internallogging.SetResponseHeaders(genericCtx, http.Header{"X-Request-Id": []string{"xai-1"}})	AppendAPIWebsocketResponse(genericCtx, nil, frame)	generic := internallogging.GetResponseHeaders(genericCtx)	if got := generic.Get("X-Ratelimit-Remaining-Requests"); got != "" {		t.Fatalf("generic websocket entry point observed codex quota: %q", got)	}	if got := generic.Get("Retry-After"); got != "" {		t.Fatalf("generic websocket entry point observed retry-after: %q", got)	}	if got := generic.Get("X-Request-Id"); got != "xai-1" {		t.Fatalf("generic websocket entry point disturbed response headers: %q", got)	} 	codexCtx := internallogging.WithResponseHeadersHolder(context.Background())	AppendCodexAPIWebsocketResponse(codexCtx, nil, frame)	if got := internallogging.GetResponseHeaders(codexCtx).Get("X-Ratelimit-Remaining-Requests"); got != "0" {		t.Fatalf("codex websocket entry point lost quota observation: %q", got)	}} func TestMergeWebsocketQuotaHeaders(t *testing.T) {	ctx := internallogging.WithResponseHeadersHolder(context.Background())	internallogging.SetResponseHeaders(ctx, http.Header{"X-Request-Id": []string{"req-1"}})	internallogging.MergeResponseHeaders(ctx, ParseCodexQuotaEventHeaders([]byte(`{		"type":"codex.rate_limits",		"rate_limits":{"primary":{"used_percent":2,"window_minutes":10080,"reset_at":1782951970}},		"metered_limit_name":"codex_bengalfox"	}`)))	headers := internallogging.GetResponseHeaders(ctx)	if headers.Get("X-Request-Id") != "req-1" || headers.Get("X-Codex-Active-Limit") != "codex_bengalfox" {		t.Fatalf("merged response headers = %#v", headers)	}}