package openai import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-chi/chi/v5" chimw "github.com/go-chi/chi/v5/middleware" "ds2api/internal/auth" ) type streamStatusAuthStub struct{} func (streamStatusAuthStub) Determine(_ *http.Request) (*auth.RequestAuth, error) { return &auth.RequestAuth{ UseConfigToken: false, DeepSeekToken: "direct-token", CallerID: "caller:test", TriedAccounts: map[string]bool{}, }, nil } func (streamStatusAuthStub) DetermineCaller(_ *http.Request) (*auth.RequestAuth, error) { return &auth.RequestAuth{ UseConfigToken: false, DeepSeekToken: "direct-token", CallerID: "caller:test", TriedAccounts: map[string]bool{}, }, nil } func (streamStatusAuthStub) Release(_ *auth.RequestAuth) {} type streamStatusDSStub struct { resp *http.Response } func (m streamStatusDSStub) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) { return "session-id", nil } func (m streamStatusDSStub) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) { return "pow", nil } func (m streamStatusDSStub) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ map[string]any, _ string, _ int) (*http.Response, error) { return m.resp, nil } func (m streamStatusDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error { return nil } func makeOpenAISSEHTTPResponse(lines ...string) *http.Response { body := strings.Join(lines, "\n") if !strings.HasSuffix(body, "\n") { body += "\n" } return &http.Response{ StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body)), } } func captureStatusMiddleware(statuses *[]int) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ww := chimw.NewWrapResponseWriter(w, r.ProtoMajor) next.ServeHTTP(ww, r) *statuses = append(*statuses, ww.Status()) }) } } func TestChatCompletionsStreamStatusCapturedAs200(t *testing.T) { statuses := make([]int, 0, 1) h := &Handler{ Store: mockOpenAIConfig{wideInput: true}, Auth: streamStatusAuthStub{}, DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"hello"}`, "data: [DONE]")}, } r := chi.NewRouter() r.Use(captureStatusMiddleware(&statuses)) RegisterRoutes(r, h) reqBody := `{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}],"stream":true}` req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody)) req.Header.Set("Authorization", "Bearer direct-token") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) } if len(statuses) != 1 { t.Fatalf("expected one captured status, got %d", len(statuses)) } if statuses[0] != http.StatusOK { t.Fatalf("expected captured status 200 (not 000), got %d", statuses[0]) } } func TestResponsesStreamStatusCapturedAs200(t *testing.T) { statuses := make([]int, 0, 1) h := &Handler{ Store: mockOpenAIConfig{wideInput: true}, Auth: streamStatusAuthStub{}, DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"hello"}`, "data: [DONE]")}, } r := chi.NewRouter() r.Use(captureStatusMiddleware(&statuses)) RegisterRoutes(r, h) reqBody := `{"model":"deepseek-chat","input":"hi","stream":true}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(reqBody)) req.Header.Set("Authorization", "Bearer direct-token") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) } if len(statuses) != 1 { t.Fatalf("expected one captured status, got %d", len(statuses)) } if statuses[0] != http.StatusOK { t.Fatalf("expected captured status 200 (not 000), got %d", statuses[0]) } } func TestResponsesNonStreamMixedProseToolPayloadHandlerPath(t *testing.T) { statuses := make([]int, 0, 1) content, _ := json.Marshal(map[string]any{ "p": "response/content", "v": "我来调用工具\n{\"tool_calls\":[{\"name\":\"read_file\",\"input\":{\"path\":\"README.MD\"}}]}", }) h := &Handler{ Store: mockOpenAIConfig{wideInput: true}, Auth: streamStatusAuthStub{}, DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse("data: "+string(content), "data: [DONE]")}, } r := chi.NewRouter() r.Use(captureStatusMiddleware(&statuses)) RegisterRoutes(r, h) reqBody := `{"model":"deepseek-chat","input":"请调用工具","tools":[{"type":"function","function":{"name":"read_file","description":"read","parameters":{"type":"object","properties":{"path":{"type":"string"}}}}}],"stream":false}` req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(reqBody)) req.Header.Set("Authorization", "Bearer direct-token") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) } if len(statuses) != 1 || statuses[0] != http.StatusOK { t.Fatalf("expected captured status 200, got %#v", statuses) } var out map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatalf("decode response failed: %v body=%s", err, rec.Body.String()) } outputText, _ := out["output_text"].(string) if outputText != "" { t.Fatalf("expected output_text hidden for mixed prose tool payload, got %q", outputText) } output, _ := out["output"].([]any) if len(output) != 1 { t.Fatalf("expected one output item, got %#v", output) } first, _ := output[0].(map[string]any) if first["type"] != "function_call" { t.Fatalf("expected function_call output item, got %#v", output) } }