shinnmen / internal /runtime /executor /codex_provider_test.go
shinmentakezo07
feat(codex): add planner-reviewer agent mode
bb8f6a6
Raw
History Blame Contribute Delete
7.53 kB
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
}