package openai import ( "net/http" "net/http/httptest" "strings" "testing" "iop/packages/go/config" iop "iop/proto/gen/iop" ) func workspaceBoundCfg() config.EdgeOpenAIConf { return config.EdgeOpenAIConf{ Adapter: "ollama", Target: "llama3", ModelRoutes: []config.OpenAIRouteEntry{ {Model: "codex", Adapter: "cli", Target: "codex", WorkspaceRequired: true}, {Model: "llama3", Adapter: "ollama", Target: "llama3"}, }, } } func TestResponsesGenericMetadataContract(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "input":"test", "metadata":{ "request_id":"req-001", "workspace":"/config/workspace/iop", "task_id":"task-123", "custom":"value" } }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Target != "client-model" { t.Fatalf("target: got %q, want client-model", fake.req.Target) } if fake.req.Workspace != "/config/workspace/iop" { t.Fatalf("workspace: got %q", fake.req.Workspace) } if fake.req.Metadata["request_id"] != "req-001" { t.Fatalf("request_id: got %q", fake.req.Metadata["request_id"]) } if _, ok := fake.req.Metadata["workspace"]; ok { t.Fatal("workspace should not be copied into run metadata") } if fake.req.Metadata["task_id"] != "task-123" { t.Fatalf("task_id: got %q", fake.req.Metadata["task_id"]) } if fake.req.Metadata["custom"] != "value" { t.Fatalf("custom: got %q", fake.req.Metadata["custom"]) } if fake.req.Metadata["openai_model"] != "client-model" { t.Fatalf("openai_model: got %q", fake.req.Metadata["openai_model"]) } if fake.req.ModelGroupKey != "client-model" { t.Fatalf("model group key: got %q, want client-model", fake.req.ModelGroupKey) } if fake.req.Metadata["openai_stream"] != "false" { t.Fatalf("openai_stream: got %q", fake.req.Metadata["openai_stream"]) } if fake.req.Metadata["strict_output"] != "false" { t.Fatalf("strict_output: got %q", fake.req.Metadata["strict_output"]) } } func TestChatCompletionsMetadataContractAndWorkspace(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"client-model", "messages":[{"role":"user","content":"hi"}], "metadata":{ "request_id":"req-chat-001", "workspace":"/config/workspace/iop", "task_id":"task-123" } }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Target != "client-model" { t.Fatalf("target: got %q, want client-model", fake.req.Target) } if fake.req.Workspace != "/config/workspace/iop" { t.Fatalf("workspace: got %q", fake.req.Workspace) } if fake.req.Metadata["request_id"] != "req-chat-001" { t.Fatalf("request_id: got %q", fake.req.Metadata["request_id"]) } if fake.req.Metadata["task_id"] != "task-123" { t.Fatalf("task_id: got %q", fake.req.Metadata["task_id"]) } if fake.req.ModelGroupKey != "client-model" { t.Fatalf("model group key: got %q, want client-model", fake.req.ModelGroupKey) } if _, ok := fake.req.Metadata["workspace"]; ok { t.Fatal("workspace should not be copied into run metadata") } } func TestChatCompletionsRejectsObjectMetadata(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"m", "messages":[{"role":"user","content":"hi"}], "metadata":{"cli":{"flag":"x"}} }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } } func TestChatCompletionsRejectsSourceMetadata(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"m", "messages":[{"role":"user","content":"hi"}], "metadata":{"source":"manual"} }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "metadata.source is not supported") { t.Fatalf("expected source unsupported error, got %s", w.Body.String()) } } func TestChatCompletionsRejectsNonStringMetadataValue(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"m", "messages":[{"role":"user","content":"hi"}], "metadata":{"attempt":2} }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "metadata.attempt must be a string") { t.Fatalf("expected string metadata error, got %s", w.Body.String()) } } func TestChatCompletionsRejectsObjectWorkspaceMetadata(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"m", "messages":[{"role":"user","content":"hi"}], "metadata":{ "workspace": { "path": "/home/user/workspace", "source_branch": "develop" } } }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "metadata.workspace must be a string") { t.Fatalf("expected workspace string error, got %s", w.Body.String()) } } func TestResponsesPreservesGenericTaskMetadata(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "input":"test", "metadata":{ "request_id":"req-flat-001", "task_id":"task-flat" } }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Metadata["task_id"] != "task-flat" { t.Fatalf("task_id: got %q", fake.req.Metadata["task_id"]) } } func TestResponsesRejectsNonStringMetadata(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) cases := []struct { name string body string }{ {"cli only", `{"model":"m","input":"hi","metadata":{"cli":{"flag":"x"}}}`}, {"inference target", `{"model":"m","input":"hi","metadata":{"inference":{"target":"t"}}}`}, {"nomadcode metadata", `{"model":"m","input":"hi","metadata":{"nomadcode":{"task_id":"t"}}}`}, {"source metadata", `{"model":"m","input":"hi","metadata":{"source":"manual"}}`}, {"number metadata", `{"model":"m","input":"hi","metadata":{"attempt":2}}`}, {"boolean metadata", `{"model":"m","input":"hi","metadata":{"urgent":true}}`}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(tc.body)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } }) } } func TestResponsesRejectsObjectWorkspaceMetadata(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"m", "input":"hi", "metadata":{ "workspace": { "path": "/home/user/workspace", "source_branch": "develop" } } }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "metadata.workspace must be a string") { t.Fatalf("expected workspace string error, got %s", w.Body.String()) } } func TestResponsesWorkspaceRequiredRouteMissingWorkspace400(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent)} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","input":"hello"}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("missing workspace: want 400, got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "workspace is required") { t.Fatalf("expected workspace error, got %s", w.Body.String()) } } func TestResponsesWorkspaceRequiredRouteRelativeWorkspace400(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent)} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","input":"hello","metadata":{"workspace":"relative/path"}}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("relative workspace: want 400, got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "absolute path") { t.Fatalf("expected absolute path error, got %s", w.Body.String()) } } func TestResponsesWorkspaceRequiredRouteAbsoluteWorkspaceOK(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","input":"hello","metadata":{"workspace":"/abs/path"}}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("absolute workspace: want 200, got %d body=%s", w.Code, w.Body.String()) } if fake.req.Workspace != "/abs/path" { t.Fatalf("workspace not preserved: got %q", fake.req.Workspace) } } func TestResponsesNonRequiredRouteNoWorkspaceOK(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"llama3","input":"hello"}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("non-required route no workspace: want 200, got %d body=%s", w.Code, w.Body.String()) } } func TestResponsesSurfacesDistinctRunFailures(t *testing.T) { cases := []struct { name string runError string want []string forbidAny []string }{ { name: "missing workspace path from node", runError: "cli adapter: workspace not found: /abs/missing", want: []string{`"type":"run_error"`, "cli adapter: workspace not found: /abs/missing"}, }, { name: "inaccessible workspace path from node", runError: "cli adapter: workspace inaccessible: /abs/private: permission denied", want: []string{`"type":"run_error"`, "cli adapter: workspace inaccessible: /abs/private"}, }, { name: "agent process exit failure", runError: "command failed: exit status 7", want: []string{`"type":"run_error"`, "command failed: exit status 7"}, forbidAny: []string{"workspace not found", "workspace inaccessible", "workspace is not a directory"}, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 1)} fake.events <- &iop.RunEvent{Type: "error", Error: tc.runError} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","input":"hello","metadata":{"workspace":"/abs/workspace"}}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("run failure: want 502, got %d body=%s", w.Code, w.Body.String()) } for _, want := range tc.want { if !strings.Contains(w.Body.String(), want) { t.Fatalf("expected body to contain %q, got %s", want, w.Body.String()) } } for _, forbidden := range tc.forbidAny { if strings.Contains(w.Body.String(), forbidden) { t.Fatalf("body should not contain %q, got %s", forbidden, w.Body.String()) } } }) } } func TestChatCompletionsWorkspaceRequiredRouteMissingWorkspace400(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent)} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","messages":[{"role":"user","content":"hi"}]}` req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("missing workspace: want 400, got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "workspace is required") { t.Fatalf("expected workspace error, got %s", w.Body.String()) } } func TestChatCompletionsWorkspaceRequiredRouteRelativeWorkspace400(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent)} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","messages":[{"role":"user","content":"hi"}],"metadata":{"workspace":"some/relative"}}` req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("relative workspace: want 400, got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "absolute path") { t.Fatalf("expected absolute path error, got %s", w.Body.String()) } } func TestChatCompletionsWorkspaceRequiredRouteAbsoluteWorkspaceOK(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"codex","messages":[{"role":"user","content":"hi"}],"metadata":{"workspace":"/abs/workspace"}}` req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("absolute workspace: want 200, got %d body=%s", w.Code, w.Body.String()) } if fake.req.Workspace != "/abs/workspace" { t.Fatalf("workspace not preserved: got %q", fake.req.Workspace) } } func TestChatCompletionsNonRequiredRouteNoWorkspaceOK(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(workspaceBoundCfg(), fake, nil) body := `{"model":"llama3","messages":[{"role":"user","content":"hi"}]}` req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("non-required route no workspace: want 200, got %d body=%s", w.Code, w.Body.String()) } }