package processing import ( "strings" "testing" ) func TestFormatOpenAIHTTPError_preservesUsefulHints(t *testing.T) { t.Parallel() cases := []struct { status int err *openAIErrorBody want string }{ { status: 400, err: &openAIErrorBody{ Message: "Unsupported parameter: 'temperature' does not support 0.2 with this model. Only the default (1) value is supported.", Type: "invalid_request_error", Code: "unsupported_value", Param: "temperature", }, want: "model rejects custom temperature", }, { status: 400, err: &openAIErrorBody{ Message: "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.", Param: "max_tokens", }, want: "model requires completion-token budget", }, { status: 404, err: &openAIErrorBody{ Message: "The model `gpt-5.6-luna` does not exist or you do not have access to it.", Code: "model_not_found", }, want: "model not found or API key lacks access", }, { status: 400, err: &openAIErrorBody{ Message: "Unsupported parameter: 'max_tokens' is not supported with this model.", Param: "max_tokens", Code: "unsupported_parameter", }, want: "model rejects legacy token cap", }, { status: 429, err: &openAIErrorBody{ Message: "You exceeded your current quota, please check your plan and billing details.", Type: "insufficient_quota", Code: "insufficient_quota", }, want: "API billing/quota empty", }, { status: 429, err: &openAIErrorBody{ Message: "Rate limit reached for requests", Code: "rate_limit_exceeded", }, want: "AI provider rate limited", }, { status: 401, err: &openAIErrorBody{ Message: "Incorrect API key provided: sk-proj-REDACTED", Code: "invalid_api_key", }, want: "AI provider rejected the API key", }, { status: 400, err: &openAIErrorBody{ Message: "something obscure mentioning gpt-5.6-luna", Type: "invalid_request_error", Code: "invalid_request_error", Param: "messages", }, want: "provider http 400 (invalid_request_error param=messages)", }, } for _, tc := range cases { got := formatOpenAIHTTPError(tc.status, tc.err) if !strings.Contains(strings.ToLower(got), strings.ToLower(tc.want)) { t.Fatalf("status=%d got=%q want contains %q", tc.status, got, tc.want) } if strings.Contains(got, "sk-") || strings.Contains(got, "gpt-5.6-luna") { t.Fatalf("leaked secret or model id: %q", got) } safe := TruncateError(errString(got)) if safe == "processing_failed" { t.Fatalf("TruncateError wiped useful hint: in=%q", got) } } } func TestOpenAIErrorIsRetryableRateLimit(t *testing.T) { t.Parallel() if openAIErrorIsRetryableRateLimit(&openAIErrorBody{Code: "insufficient_quota", Type: "insufficient_quota"}) { t.Fatal("quota must not retry") } if !openAIErrorIsRetryableRateLimit(&openAIErrorBody{Code: "rate_limit_exceeded"}) { t.Fatal("true rate limit should retry") } if openAIErrorIsRetryableRateLimit(nil) { t.Fatal("nil 429 body must not auto-retry") } } func TestProbeCompleteOptions(t *testing.T) { t.Parallel() opts := ProbeCompleteOptions() if opts.MaxTokens != 64 || opts.ReasoningEffort != "none" { t.Fatalf("%+v", opts) } }