package openai import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" iop "iop/proto/gen/iop" ) type fakeRunService struct { req edgeservice.SubmitRunRequest ollamaReq edgeservice.OllamaAPIRequest ollamaResp edgeservice.OllamaAPIView events chan *iop.RunEvent } func (s *fakeRunService) SubmitRun(_ context.Context, req edgeservice.SubmitRunRequest) (edgeservice.RunResult, error) { s.req = req return &edgeservice.RunHandle{ RunDispatch: edgeservice.RunDispatch{ RunID: "run-test", Target: req.Target, TimeoutSec: 5, }, RunStream: edgeservice.RunStream{ Events: s.events, NodeEvents: make(chan *iop.EdgeNodeEvent), }, }, nil } func (s *fakeRunService) OllamaAPI(_ context.Context, req edgeservice.OllamaAPIRequest) (edgeservice.OllamaAPIView, error) { s.ollamaReq = req if s.ollamaResp.StatusCode == 0 { s.ollamaResp.StatusCode = http.StatusOK } return s.ollamaResp, nil } func TestChatCompletionsDispatchesConfiguredOllamaTarget(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "hello"} fake.events <- &iop.RunEvent{Type: "delta", Delta: " world"} fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 2, OutputTokens: 2}} srv := NewServer(config.EdgeOpenAIConf{ Adapter: "ollama", Target: "llama-fixed", SessionID: "cline", TimeoutSec: 15, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"client-model", "messages":[{"role":"system","content":"brief"},{"role":"user","content":"say hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Adapter != "ollama" || fake.req.Target != "llama-fixed" { t.Fatalf("dispatch target mismatch: %+v", fake.req) } if fake.req.SessionID != "cline" || fake.req.TimeoutSec != 15 { t.Fatalf("execution config mismatch: %+v", fake.req) } if !strings.Contains(fake.req.Prompt, "system: brief") || !strings.Contains(fake.req.Prompt, "user: say hello") { t.Fatalf("prompt did not include messages: %q", fake.req.Prompt) } var resp chatCompletionResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v", err) } if resp.Choices[0].Message.Content != "hello world" { t.Fatalf("content: got %q", resp.Choices[0].Message.Content) } if resp.Usage == nil || resp.Usage.TotalTokens != 4 { t.Fatalf("usage: %+v", resp.Usage) } } func TestChatCompletionsUsesRequestModelWhenNoConfiguredTarget(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":"from-request", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Target != "from-request" { t.Fatalf("target: got %q", fake.req.Target) } } func TestChatCompletionsPassesOllamaOptions(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":"from-request", "messages":[{"role":"user","content":"hi"}], "options":{"temperature":0.2,"top_p":0.9,"num_predict":32,"stop":["END"]}, "keep_alive":"10m", "think":false, "format":"json", "tools":[{"type":"function","function":{"name":"lookup"}}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } options, ok := fake.req.Input["options"].(map[string]any) if !ok { t.Fatalf("options not passed: %+v", fake.req.Input) } if options["temperature"].(float64) != 0.2 || options["top_p"].(float64) != 0.9 || options["num_predict"].(float64) != 32 { t.Fatalf("unexpected options: %+v", options) } if fake.req.Input["keep_alive"] != "10m" || fake.req.Input["think"] != false || fake.req.Input["format"] != "json" { t.Fatalf("top-level ollama fields not passed: %+v", fake.req.Input) } if tools, ok := fake.req.Input["tools"].([]any); !ok || len(tools) != 1 { t.Fatalf("tools not passed: %+v", fake.req.Input["tools"]) } } func TestChatCompletionsStreamsSSE(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "hi"} 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":"stream-model", "stream":true, "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if !strings.Contains(body, `"content":"hi"`) || !strings.Contains(body, "data: [DONE]") { t.Fatalf("unexpected SSE body:\n%s", body) } } func TestChatCompletionsStreamingReportsClosedRunStream(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent)} close(fake.events) srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"stream-model", "stream":true, "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if !strings.Contains(body, `"message":"run stream closed"`) || !strings.Contains(body, "data: [DONE]") { t.Fatalf("unexpected SSE error body:\n%s", body) } } func TestChatCompletionsReturnsReasoningContentSeparately(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "thinking "} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "more"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "answer"} fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 2, OutputTokens: 2}} srv := NewServer(config.EdgeOpenAIConf{ Adapter: "ollama", Target: "llama-fixed", }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"client-model", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } var resp chatCompletionResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v", err) } if resp.Choices[0].Message.Content != "answer" { t.Fatalf("content: got %q, expected \"answer\"", resp.Choices[0].Message.Content) } if resp.Choices[0].Message.ReasoningContent != "thinking more" { t.Fatalf("reasoning_content: got %q, expected \"thinking more\"", resp.Choices[0].Message.ReasoningContent) } } func TestChatCompletionsStreamsReasoningSSE(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "hi"} 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":"stream-model", "stream":true, "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if !strings.Contains(body, `"reasoning_content":"think"`) || !strings.Contains(body, `"content":"hi"`) || !strings.Contains(body, "data: [DONE]") { t.Fatalf("unexpected SSE body:\n%s", body) } } func TestChatCompletionsStrictOutputNormalizesXMLStyleAgentResponse(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "---\nhidden\n\n\nok\n\n```\ntrailing noise"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"agent-model", "messages":[{"role":"user","content":"finish"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Input["think"] != false { t.Fatalf("strict output should disable thinking by default: %+v", fake.req.Input) } var resp chatCompletionResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v", err) } want := "\nok\n" if resp.Choices[0].Message.Content != want { t.Fatalf("content:\ngot %q\nwant %q", resp.Choices[0].Message.Content, want) } if resp.Choices[0].Message.ReasoningContent != "" { t.Fatalf("strict output should drop reasoning content: %q", resp.Choices[0].Message.ReasoningContent) } } func TestChatCompletionsStrictOutputWrapsPlainTextWhenPromptDeclaresCompletionTool(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "plain answer"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"agent-model", "messages":[ {"role":"system","content":"Once you've completed the user's task, you must use the attempt_completion tool to present the result.\n\n\ndone\n"}, {"role":"user","content":"finish"} ] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(fake.req.Prompt, "output exactly one block") || !strings.Contains(fake.req.Prompt, "actual user-facing response") { t.Fatalf("strict contract instruction not injected:\n%s", fake.req.Prompt) } var resp chatCompletionResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v", err) } want := "\nplain answer\n" if resp.Choices[0].Message.Content != want { t.Fatalf("content:\ngot %q\nwant %q", resp.Choices[0].Message.Content, want) } } func TestChatCompletionsStrictOutputBuffersStreamingResponse(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "---\nnoisea.go"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true, StrictStreamBuffer: true}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"agent-model", "stream":true, "messages":[{"role":"user","content":"read"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if strings.Contains(body, "reasoning_content") || strings.Contains(body, "") || strings.Contains(body, "") { t.Fatalf("strict stream leaked unstable content:\n%s", body) } if !strings.Contains(body, "read_file") || !strings.Contains(body, "a.go") || !strings.Contains(body, "data: [DONE]") { t.Fatalf("unexpected strict SSE body:\n%s", body) } } func TestChatCompletionsStrictOutputStreamsLiveContent(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "\n"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok\n"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"agent-model", "stream":true, "messages":[{"role":"user","content":"finish"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if strings.Contains(body, "reasoning_content") || strings.Contains(body, "hidden") { t.Fatalf("strict stream leaked reasoning content:\n%s", body) } if strings.Count(body, `"content":`) != 2 || !strings.Contains(body, "attempt_completion") || !strings.Contains(body, "data: [DONE]") { t.Fatalf("strict stream should pass live content deltas:\n%s", body) } } func TestModelsUsesConfiguredModelsOrTarget(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Target: "fallback-model"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) w := httptest.NewRecorder() srv.handleModels(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d", w.Code) } if !strings.Contains(w.Body.String(), "fallback-model") { t.Fatalf("expected target model, got %s", w.Body.String()) } srv = NewServer(config.EdgeOpenAIConf{Models: []string{"a", "b"}}, &fakeRunService{}, nil) w = httptest.NewRecorder() srv.handleModels(w, req) if !strings.Contains(w.Body.String(), `"id":"a"`) || !strings.Contains(w.Body.String(), `"id":"b"`) { t.Fatalf("expected configured models, got %s", w.Body.String()) } } func TestOllamaAPIPassthrough(t *testing.T) { fake := &fakeRunService{ ollamaResp: edgeservice.OllamaAPIView{ StatusCode: http.StatusAccepted, ContentType: "application/json", Body: `{"ok":true}`, }, } srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", TimeoutSec: 15}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/api/show", strings.NewReader(`{"model":"gemma4:26b"}`)) w := httptest.NewRecorder() srv.handleOllamaAPI(w, req) if w.Code != http.StatusAccepted { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.ollamaReq.Adapter != "ollama" || fake.ollamaReq.Method != http.MethodPost || fake.ollamaReq.Path != "/api/show" { t.Fatalf("passthrough req mismatch: %+v", fake.ollamaReq) } if fake.ollamaReq.Body != `{"model":"gemma4:26b"}` || fake.ollamaReq.TimeoutSec != 15 { t.Fatalf("passthrough body/timeout mismatch: %+v", fake.ollamaReq) } if w.Body.String() != `{"ok":true}` { t.Fatalf("body: got %s", w.Body.String()) } } func TestOllamaAPIPassthroughPreservesConfiguredTarget(t *testing.T) { fake := &fakeRunService{ ollamaResp: edgeservice.OllamaAPIView{ StatusCode: http.StatusOK, ContentType: "application/json", Body: `{"models":[]}`, }, } srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "gemma4:26b", TimeoutSec: 15}, fake, nil) req := httptest.NewRequest(http.MethodGet, "/api/tags", nil) w := httptest.NewRecorder() srv.handleOllamaAPI(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.ollamaReq.Target != "gemma4:26b" { t.Fatalf("passthrough target: got %q, want gemma4:26b", fake.ollamaReq.Target) } } func TestResponsesDispatchesNonStreamingRequest(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "hello"} fake.events <- &iop.RunEvent{Type: "delta", Delta: " world"} fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 1, OutputTokens: 1}} srv := NewServer(config.EdgeOpenAIConf{ Adapter: "ollama", Target: "llama-fixed", }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "input":"say hello" }`)) 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 != "llama-fixed" { t.Fatalf("target: got %q, want llama-fixed", fake.req.Target) } if fake.req.Prompt != "say hello" { t.Fatalf("prompt: got %q", fake.req.Prompt) } var resp responsesResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v", err) } if resp.OutputText != "hello world" { t.Fatalf("output_text: got %q", resp.OutputText) } if len(resp.Output) == 0 || len(resp.Output[0].Content) == 0 || resp.Output[0].Content[0].Text != "hello world" { t.Fatalf("output[0].content[0].text wrong: %+v", resp.Output) } } func TestResponsesRejectsUnsupportedRequests(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil) cases := []struct { name string method string body string want int }{ {"wrong method", http.MethodGet, "", http.StatusMethodNotAllowed}, {"stream=true", http.MethodPost, `{"model":"m","input":"hi","stream":true}`, http.StatusBadRequest}, {"empty input", http.MethodPost, `{"model":"m","input":""}`, http.StatusBadRequest}, {"non-string input", http.MethodPost, `{"model":"m","input":["a","b"]}`, http.StatusBadRequest}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.method, "/v1/responses", strings.NewReader(tc.body)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != tc.want { t.Fatalf("got %d want %d body=%s", w.Code, tc.want, w.Body.String()) } }) } } func TestResponsesMetadataContractAndTargetOverride(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", "inference":{"target":"metadata-target"}, "nomadcode":{"task_id":"task-123","source":"nomadcode"} } }`)) 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 != "metadata-target" { t.Fatalf("target: got %q, want metadata-target", 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["inference.target"] != "metadata-target" { t.Fatalf("inference.target: got %q", fake.req.Metadata["inference.target"]) } if fake.req.Metadata["nomadcode.task_id"] != "task-123" { t.Fatalf("nomadcode.task_id: got %q", fake.req.Metadata["nomadcode.task_id"]) } if fake.req.Metadata["nomadcode.source"] != "nomadcode" { t.Fatalf("nomadcode.source: got %q", fake.req.Metadata["nomadcode.source"]) } if fake.req.Metadata["source"] != "openai-responses" { t.Fatalf("source: got %q", fake.req.Metadata["source"]) } if fake.req.Metadata["openai_model"] != "client-model" { t.Fatalf("openai_model: got %q", fake.req.Metadata["openai_model"]) } 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", "inference":{"target":"metadata-target"}, "nomadcode":{"task_id":"task-123","source":"nomadcode"} } }`)) 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 != "metadata-target" { t.Fatalf("target: got %q, want metadata-target", 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["inference.target"] != "metadata-target" { t.Fatalf("inference.target: got %q", fake.req.Metadata["inference.target"]) } if fake.req.Metadata["nomadcode.task_id"] != "task-123" { t.Fatalf("nomadcode.task_id: got %q", fake.req.Metadata["nomadcode.task_id"]) } if fake.req.Metadata["nomadcode.source"] != "nomadcode" { t.Fatalf("nomadcode.source: got %q", fake.req.Metadata["nomadcode.source"]) } if fake.req.Metadata["source"] != "openai" { t.Fatalf("source: got %q", fake.req.Metadata["source"]) } if _, ok := fake.req.Metadata["workspace"]; ok { t.Fatal("workspace should not be copied into run metadata") } } func TestChatCompletionsRejectsCLIMetadata(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 TestResponsesAcceptsFlatNomadCodeMetadataAliases(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", "source":"plane", "inference":{"target":"metadata-target"} } }`)) 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["nomadcode.task_id"] != "task-flat" { t.Fatalf("nomadcode.task_id: got %q", fake.req.Metadata["nomadcode.task_id"]) } if fake.req.Metadata["nomadcode.source"] != "plane" { t.Fatalf("nomadcode.source: got %q", fake.req.Metadata["nomadcode.source"]) } if fake.req.Metadata["source"] != "openai-responses" { t.Fatalf("source should remain IOP-owned: got %q", fake.req.Metadata["source"]) } } func TestResponsesStructuredNomadCodeMetadataWinsOverFlatAliases(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":{ "task_id":"task-flat", "source":"plane", "nomadcode":{"task_id":"task-structured","source":"manual"} } }`)) 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["nomadcode.task_id"] != "task-structured" { t.Fatalf("nomadcode.task_id: got %q", fake.req.Metadata["nomadcode.task_id"]) } if fake.req.Metadata["nomadcode.source"] != "manual" { t.Fatalf("nomadcode.source: got %q", fake.req.Metadata["nomadcode.source"]) } if fake.req.Metadata["source"] != "openai-responses" { t.Fatalf("source should remain IOP-owned: got %q", fake.req.Metadata["source"]) } } func TestResponsesConfiguredTargetWinsOverMetadataTarget(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", Target: "config-target"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "input":"test", "metadata":{"inference":{"target":"metadata-target"}} }`)) 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 != "config-target" { t.Fatalf("target: got %q, want config-target", fake.req.Target) } var resp responsesResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if resp.Model != "client-model" { t.Fatalf("model: got %q, want client-model", resp.Model) } } func TestResponsesRejectsCLIMetadata(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 and cli", `{"model":"m","input":"hi","metadata":{"inference":{"target":"t"},"cli":{"flag":"x"}}}`}, } 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 TestResponsesReturnsNomadCodeCompatibleShape(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "answer"} fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 1, OutputTokens: 1}} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "input":"say hello" }`)) 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()) } var resp responsesResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if !strings.HasPrefix(resp.ID, "resp-") { t.Fatalf("id: got %q, want resp- prefix", resp.ID) } if resp.Model != "client-model" { t.Fatalf("model: got %q", resp.Model) } if resp.OutputText != "answer" { t.Fatalf("output_text: got %q", resp.OutputText) } if len(resp.Output) == 0 || len(resp.Output[0].Content) == 0 || resp.Output[0].Content[0].Text != "answer" { t.Fatalf("output[0].content[0].text wrong: %+v", resp.Output) } if resp.Usage.TotalTokens != 2 { t.Fatalf("usage.total_tokens: got %d", resp.Usage.TotalTokens) } } func TestResponsesUsageDefaultsToZeroObject(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", Target: "llama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"m", "input":"hi" }`)) 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()) } var body map[string]json.RawMessage if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("decode: %v", err) } if _, ok := body["usage"]; !ok { t.Fatal("usage field missing from response") } var usage openAIUsage if err := json.Unmarshal(body["usage"], &usage); err != nil { t.Fatalf("usage is not an object: %s", body["usage"]) } } func TestResponsesStrictOutputNormalizesAgentResponse(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)} fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden"} fake.events <- &iop.RunEvent{Type: "delta", Delta: "plain answer"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama", StrictOutput: true}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"client-model", "instructions":"Once you've completed the user's task, you must use the attempt_completion tool to present the result.\n\n\ndone\n", "input":"finish" }`)) 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["strict_output"] != "true" { t.Fatalf("strict_output metadata: got %q", fake.req.Metadata["strict_output"]) } if fake.req.Input["think"] != false { t.Fatalf("strict output should disable thinking by default: %+v", fake.req.Input) } if !strings.Contains(fake.req.Prompt, "output exactly one block") { t.Fatalf("strict contract instruction not injected:\n%s", fake.req.Prompt) } var resp responsesResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } want := "\nplain answer\n" if resp.OutputText != want { t.Fatalf("output_text:\ngot %q\nwant %q", resp.OutputText, want) } if len(resp.Output) == 0 || len(resp.Output[0].Content) == 0 || resp.Output[0].Content[0].Text != want { t.Fatalf("output[0].content[0].text wrong: %+v", resp.Output) } } func TestCollectRunResultTimesOut(t *testing.T) { handle := &edgeservice.RunHandle{ RunDispatch: edgeservice.RunDispatch{TimeoutSec: 1}, RunStream: edgeservice.RunStream{ Events: make(chan *iop.RunEvent), NodeEvents: make(chan *iop.EdgeNodeEvent), }, } ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond) defer cancel() _, _, _, err := collectRunResult(ctx, handle.Stream(), handle.WaitTimeout()) if err == nil { t.Fatal("expected timeout error") } } func TestModelsExposesCatalogRouteModels(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "ollama", Target: "llama3"}, {Model: "model-b", Adapter: "vllm", Target: "qwen"}, }, }, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) w := httptest.NewRecorder() srv.handleModels(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d", w.Code) } body := w.Body.String() if !strings.Contains(body, `"id":"model-a"`) || !strings.Contains(body, `"id":"model-b"`) { t.Fatalf("expected catalog model IDs, got %s", body) } } func TestModelsSkipsRoutesWithEmptyTarget(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-with-target", Adapter: "ollama", Target: "llama3"}, {Model: "model-no-target", Adapter: "ollama", Target: ""}, }, }, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) w := httptest.NewRecorder() srv.handleModels(w, req) body := w.Body.String() if !strings.Contains(body, `"id":"model-with-target"`) { t.Fatalf("expected model-with-target in response, got %s", body) } if strings.Contains(body, "model-no-target") { t.Fatalf("model-no-target should be skipped (no target), got %s", body) } } func TestModelsRouteCatalogWinsOverModelsField(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ Models: []string{"legacy-model"}, ModelRoutes: []config.OpenAIRouteEntry{ {Model: "catalog-model", Adapter: "ollama", Target: "llama3"}, }, }, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) w := httptest.NewRecorder() srv.handleModels(w, req) body := w.Body.String() if !strings.Contains(body, `"id":"catalog-model"`) { t.Fatalf("expected catalog model in response, got %s", body) } if strings.Contains(body, "legacy-model") { t.Fatalf("legacy model should be hidden when catalog is set, got %s", body) } } func TestChatCompletionsRouteCatalogDispatches(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{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "ollama", Target: "llama3"}, {Model: "model-b", Adapter: "vllm", Target: "qwen"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"model-a", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Adapter != "ollama" || fake.req.Target != "llama3" { t.Fatalf("dispatch mismatch: adapter=%q target=%q", fake.req.Adapter, fake.req.Target) } } func TestChatCompletionsRouteCatalogDispatchesModelB(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{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "ollama", Target: "llama3"}, {Model: "model-b", Adapter: "vllm", Target: "qwen"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"model-b", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Adapter != "vllm" || fake.req.Target != "qwen" { t.Fatalf("dispatch mismatch: adapter=%q target=%q", fake.req.Adapter, fake.req.Target) } } func TestChatCompletionsCatalogMissFallsToLegacyTarget(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", Target: "legacy-target", ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "ollama", Target: "llama3"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"unknown-model", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Target != "legacy-target" { t.Fatalf("expected legacy-target fallback, got %q", fake.req.Target) } } func TestChatCompletionsEmptyModelReturns400(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400 for empty model, got %d body=%s", w.Code, w.Body.String()) } } func TestResponsesRouteCatalogDispatchesRoute(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{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "vllm", Target: "qwen"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"model-a", "input":"say hello" }`)) 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.Adapter != "vllm" || fake.req.Target != "qwen" { t.Fatalf("dispatch mismatch: adapter=%q target=%q", fake.req.Adapter, fake.req.Target) } } func TestResponsesRouteCatalogTakesPriorityOverMetadataTarget(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{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "model-a", Adapter: "ollama", Target: "route-target"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"model-a", "input":"test", "metadata":{"inference":{"target":"metadata-target"}} }`)) 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 != "route-target" { t.Fatalf("expected route-target, got %q", fake.req.Target) } } func TestChatCompletionsEdgePassesNumCtxForAdapterEnforcement(t *testing.T) { // Edge passes request options.num_ctx to the service unchanged. // The Ollama adapter enforces Edge-owned context_size over this value. 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", Target: "llama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"from-request", "messages":[{"role":"user","content":"hi"}], "options":{"num_ctx":8192} }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } options, ok := fake.req.Input["options"].(map[string]any) if !ok { t.Fatalf("options not passed to service: %+v", fake.req.Input) } if options["num_ctx"].(float64) != 8192 { t.Fatalf("edge should pass num_ctx unchanged for adapter enforcement: got %v", options["num_ctx"]) } } func TestResolveRouteDispatchPreservesWorkspaceRequired(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "codex", Adapter: "cli", Target: "codex", WorkspaceRequired: true}, {Model: "llama3", Adapter: "ollama", Target: "llama3:8b"}, }, }, &fakeRunService{}, nil) dispatch, ok := srv.resolveRouteDispatch("codex", "") if !ok { t.Fatal("expected dispatch to succeed for codex route") } if !dispatch.WorkspaceRequired { t.Fatalf("expected workspace_required=true for codex route, got false") } dispatch, ok = srv.resolveRouteDispatch("llama3", "") if !ok { t.Fatal("expected dispatch to succeed for llama3 route") } if dispatch.WorkspaceRequired { t.Fatalf("expected workspace_required=false for llama3 route, got true") } } func TestResolveRouteDispatchFallbackWorkspaceRequiredFalse(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ Adapter: "ollama", Target: "llama3", ModelRoutes: []config.OpenAIRouteEntry{ {Model: "codex", Adapter: "cli", Target: "codex", WorkspaceRequired: true}, }, }, &fakeRunService{}, nil) dispatch, ok := srv.resolveRouteDispatch("unknown-model", "") if !ok { t.Fatal("expected fallback dispatch to succeed") } if dispatch.WorkspaceRequired { t.Fatalf("expected workspace_required=false for fallback route, got true") } } func TestCollectRunResultFailsWhenEventStreamCloses(t *testing.T) { events := make(chan *iop.RunEvent) close(events) handle := &edgeservice.RunHandle{ RunDispatch: edgeservice.RunDispatch{TimeoutSec: 60}, RunStream: edgeservice.RunStream{ Events: events, NodeEvents: make(chan *iop.EdgeNodeEvent), }, } _, _, _, err := collectRunResult(context.Background(), handle.Stream(), handle.WaitTimeout()) if err == nil { t.Fatal("expected closed stream error") } if !strings.Contains(err.Error(), "run stream closed") { t.Fatalf("expected run stream closed error, got %v", err) } } // workspaceBoundCfg returns a server config with a workspace-required codex route and a // non-required llama3 route for workspace validation tests. 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 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 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()) } }