| package executor |
|
|
| import ( |
| "context" |
| "net/http" |
| "net/http/httptest" |
| "strings" |
| "testing" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/tidwall/gjson" |
| ) |
|
|
| func TestNormalizeCodexRequestBody(t *testing.T) { |
| input := []byte(`{"previous_response_id":"a","prompt_cache_retention":"24h","safety_identifier":"id"}`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
|
|
| if model := gjson.GetBytes(got, "model").String(); model != "gpt-5" { |
| t.Fatalf("expected model gpt-5, got %q", model) |
| } |
| if stream := gjson.GetBytes(got, "stream"); !stream.Exists() || !stream.Bool() { |
| t.Fatalf("expected stream=true, got %s", stream.Raw) |
| } |
| if gjson.GetBytes(got, "instructions").String() != "" { |
| t.Fatalf("expected instructions to default to empty string") |
| } |
| if gjson.GetBytes(got, "previous_response_id").Exists() { |
| t.Fatalf("expected previous_response_id to be removed") |
| } |
| if gjson.GetBytes(got, "prompt_cache_retention").Exists() { |
| t.Fatalf("expected prompt_cache_retention to be removed") |
| } |
| if gjson.GetBytes(got, "safety_identifier").Exists() { |
| t.Fatalf("expected safety_identifier to be removed") |
| } |
| } |
|
|
| func TestNormalizeCodexRequestBody_MapsReasoningEffortAlias(t *testing.T) { |
| input := []byte(`{"reasoning_effort":"high"}`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
|
|
| if gotEffort := gjson.GetBytes(got, "reasoning.effort").String(); gotEffort != "high" { |
| t.Fatalf("reasoning.effort = %q, want %q", gotEffort, "high") |
| } |
| if gjson.GetBytes(got, "reasoning_effort").Exists() { |
| t.Fatalf("expected reasoning_effort alias to be removed") |
| } |
| } |
|
|
| func TestNormalizeCodexRequestBody_AutoInjectsReasoningProfile(t *testing.T) { |
| input := []byte(`{ |
| "reasoning_effort":"high", |
| "instructions":"Base instructions", |
| "_cliproxy":{"reasoning_profile":"deep_engineering"} |
| }`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
|
|
| instructions := gjson.GetBytes(got, "instructions").String() |
| if instructions == "" { |
| t.Fatal("instructions should not be empty") |
| } |
| if instructions == "Base instructions" { |
| t.Fatalf("expected reasoning profile to append instructions") |
| } |
| if !containsAll(instructions, "Base instructions", "Architectural/Design Rationale", "Edge Cases and Failure Prevention", "Production-Ready Implementation") { |
| t.Fatalf("instructions missing expected scaffold: %q", instructions) |
| } |
| if !containsAll(instructions, "Override Brevity", "Library & Framework Discipline", "Response format (normal mode)") { |
| t.Fatalf("instructions missing deep engineering standards block: %q", instructions) |
| } |
| if gjson.GetBytes(got, "_cliproxy").Exists() { |
| t.Fatalf("expected local _cliproxy controls to be stripped") |
| } |
| } |
|
|
| func TestNormalizeCodexRequestBody_DoesNotInjectReasoningProfileWhenThinkingDisabled(t *testing.T) { |
| input := []byte(`{ |
| "reasoning_effort":"none", |
| "instructions":"Base instructions", |
| "_cliproxy":{"reasoning_profile":"deep_engineering"} |
| }`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
|
|
| if instructions := gjson.GetBytes(got, "instructions").String(); instructions != "Base instructions" { |
| t.Fatalf("instructions = %q, want %q", instructions, "Base instructions") |
| } |
| if gjson.GetBytes(got, "_cliproxy").Exists() { |
| t.Fatalf("expected local _cliproxy controls to be stripped") |
| } |
| } |
|
|
| func TestNormalizeCodexRequestBody_IncludesCustomReasoningPrompt(t *testing.T) { |
| input := []byte(`{ |
| "reasoning":{"effort":"medium"}, |
| "_cliproxy":{"reasoning_prompt":"Use strict WCAG AAA and include an edge-case checklist."} |
| }`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
|
|
| instructions := gjson.GetBytes(got, "instructions").String() |
| if !containsAll(instructions, "strict WCAG AAA", "edge-case checklist") { |
| t.Fatalf("custom reasoning prompt not injected: %q", instructions) |
| } |
| } |
|
|
| func TestNormalizeCodexRequestBody_StripsAgentModeControls(t *testing.T) { |
| input := []byte(`{ |
| "agent_mode":"planner-reviewer", |
| "_cliproxy":{"agent_mode":"planner-reviewer"}, |
| "instructions":"hello" |
| }`) |
|
|
| got, err := normalizeCodexRequestBody(input, "gpt-5", true) |
| if err != nil { |
| t.Fatalf("normalizeCodexRequestBody returned error: %v", err) |
| } |
| if gjson.GetBytes(got, "agent_mode").Exists() { |
| t.Fatalf("expected agent_mode to be stripped") |
| } |
| if gjson.GetBytes(got, "_cliproxy").Exists() { |
| t.Fatalf("expected _cliproxy to be stripped") |
| } |
| } |
|
|
| func TestCodexProviderApplyHeaders_ForwardsCodexHintHeaders(t *testing.T) { |
| gin.SetMode(gin.TestMode) |
| rec := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(rec) |
| c.Request = httptest.NewRequest(http.MethodPost, "/", nil) |
| c.Request.Header.Set("x-codex-turn-state", "turn-state") |
| c.Request.Header.Set("x-codex-turn-metadata", "turn-meta") |
| c.Request.Header.Set("x-responsesapi-include-timing-metrics", "1") |
|
|
| req, err := http.NewRequestWithContext(contextWithGinForTest(c), http.MethodPost, "https://example.com", nil) |
| if err != nil { |
| t.Fatalf("new request: %v", err) |
| } |
|
|
| p := NewCodexProvider(nil) |
| p.ApplyHeaders(req, nil, "token", true) |
|
|
| if got := req.Header.Get("x-codex-turn-state"); got != "turn-state" { |
| t.Fatalf("x-codex-turn-state = %q, want %q", got, "turn-state") |
| } |
| if got := req.Header.Get("x-codex-turn-metadata"); got != "turn-meta" { |
| t.Fatalf("x-codex-turn-metadata = %q, want %q", got, "turn-meta") |
| } |
| if got := req.Header.Get("x-responsesapi-include-timing-metrics"); got != "1" { |
| t.Fatalf("x-responsesapi-include-timing-metrics = %q, want %q", got, "1") |
| } |
| if got := req.Header.Get("Accept"); got != "text/event-stream" { |
| t.Fatalf("Accept = %q, want %q", got, "text/event-stream") |
| } |
| } |
|
|
| func TestCodexCompletedEventPayload(t *testing.T) { |
| tests := []struct { |
| name string |
| data []byte |
| want bool |
| }{ |
| { |
| name: "valid completed event", |
| data: []byte("data: {\"type\":\"response.completed\",\"response\":{}}"), |
| want: true, |
| }, |
| { |
| name: "non-completed event", |
| data: []byte("data: {\"type\":\"response.in_progress\"}"), |
| want: false, |
| }, |
| { |
| name: "non-sse payload", |
| data: []byte(`{"type":"response.completed"}`), |
| want: false, |
| }, |
| } |
|
|
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| payload, ok := codexCompletedEventPayload(tt.data) |
| if ok != tt.want { |
| t.Fatalf("expected ok=%v, got %v", tt.want, ok) |
| } |
| if ok && gjson.GetBytes(payload, "type").String() != "response.completed" { |
| t.Fatalf("expected response.completed payload, got %s", string(payload)) |
| } |
| }) |
| } |
| } |
|
|
| func TestBuildCodexReasoningProfilePrompt_DeepEngineeringIncludesLanguageStandards(t *testing.T) { |
| got := buildCodexReasoningProfilePrompt("deep_engineering", "") |
| if !containsAll(got, "Language-specific awareness", "Shell/Bash", "SQL: Always parameterize queries") { |
| t.Fatalf("deep engineering prompt missing language-specific standards: %q", got) |
| } |
| } |
|
|
| func contextWithGinForTest(c *gin.Context) context.Context { |
| if c == nil { |
| return context.Background() |
| } |
| return context.WithValue(context.Background(), "gin", c) |
| } |
|
|
| func containsAll(s string, subs ...string) bool { |
| for _, sub := range subs { |
| if !strings.Contains(s, sub) { |
| return false |
| } |
| } |
| return true |
| } |
|
|