| 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" |
| dsclient "ds2api/internal/deepseek/client" |
| ) |
|
|
| 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) UploadFile(_ context.Context, _ *auth.RequestAuth, _ dsclient.UploadFileRequest, _ int) (*dsclient.UploadFileResult, error) { |
| return &dsclient.UploadFileResult{ID: "file-id", Filename: "file.txt", Bytes: 1, Status: "uploaded"}, nil |
| } |
|
|
| func (m streamStatusDSStub) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ map[string]any, _ string, _ int) (*http.Response, error) { |
| return m.resp, nil |
| } |
|
|
| func (m streamStatusDSStub) DeleteSessionForToken(_ context.Context, _ string, _ string) (*dsclient.DeleteSessionResult, error) { |
| return &dsclient.DeleteSessionResult{Success: true}, nil |
| } |
|
|
| func (m streamStatusDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error { |
| return nil |
| } |
|
|
| type streamStatusDSSeqStub struct { |
| resps []*http.Response |
| payloads []map[string]any |
| } |
|
|
| func (m *streamStatusDSSeqStub) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) { |
| return "session-id", nil |
| } |
|
|
| func (m *streamStatusDSSeqStub) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) { |
| return "pow", nil |
| } |
|
|
| func (m *streamStatusDSSeqStub) UploadFile(_ context.Context, _ *auth.RequestAuth, _ dsclient.UploadFileRequest, _ int) (*dsclient.UploadFileResult, error) { |
| return &dsclient.UploadFileResult{ID: "file-id", Filename: "file.txt", Bytes: 1, Status: "uploaded"}, nil |
| } |
|
|
| func (m *streamStatusDSSeqStub) CallCompletion(_ context.Context, _ *auth.RequestAuth, payload map[string]any, _ string, _ int) (*http.Response, error) { |
| clone := make(map[string]any, len(payload)) |
| for k, v := range payload { |
| clone[k] = v |
| } |
| m.payloads = append(m.payloads, clone) |
| idx := len(m.payloads) - 1 |
| if idx >= len(m.resps) { |
| idx = len(m.resps) - 1 |
| } |
| return m.resps[idx], nil |
| } |
|
|
| func (m *streamStatusDSSeqStub) DeleteSessionForToken(_ context.Context, _ string, _ string) (*dsclient.DeleteSessionResult, error) { |
| return &dsclient.DeleteSessionResult{Success: true}, nil |
| } |
|
|
| func (m *streamStatusDSSeqStub) 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 newOpenAITestRouter(h *openAITestSurface) http.Handler { |
| r := chi.NewRouter() |
| registerOpenAITestRoutes(r, h) |
| return r |
| } |
|
|
| 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 := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"hello"}`, "data: [DONE]")}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","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 := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"hello"}`, "data: [DONE]")}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","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 TestChatCompletionsStreamContentFilterStopsNormallyWithoutLeak(t *testing.T) { |
| statuses := make([]int, 0, 1) |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse( |
| `data: {"p":"response/content","v":"合法前缀"}`, |
| `data: {"p":"response/status","v":"CONTENT_FILTER","accumulated_token_usage":77}`, |
| `data: {"p":"response/content","v":"CONTENT_FILTER你好,这个问题我暂时无法回答,让我们换个话题再聊聊吧。"}`, |
| )}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","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 || statuses[0] != http.StatusOK { |
| t.Fatalf("expected captured status 200, got %#v", statuses) |
| } |
| if strings.Contains(rec.Body.String(), "这个问题我暂时无法回答") { |
| t.Fatalf("expected leaked content-filter suffix to be hidden, body=%s", rec.Body.String()) |
| } |
|
|
| frames, done := parseSSEDataFrames(t, rec.Body.String()) |
| if !done { |
| t.Fatalf("expected [DONE], body=%s", rec.Body.String()) |
| } |
| if len(frames) == 0 { |
| t.Fatalf("expected at least one json frame, body=%s", rec.Body.String()) |
| } |
| last := frames[len(frames)-1] |
| choices, _ := last["choices"].([]any) |
| if len(choices) != 1 { |
| t.Fatalf("expected one choice in final frame, got %#v", last) |
| } |
| choice, _ := choices[0].(map[string]any) |
| if choice["finish_reason"] != "stop" { |
| t.Fatalf("expected finish_reason=stop for content-filter upstream stop, got %#v", choice["finish_reason"]) |
| } |
| } |
|
|
| func TestChatCompletionsStreamEmitsFailureFrameWhenUpstreamOutputEmpty(t *testing.T) { |
| statuses := make([]int, 0, 1) |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse("data: [DONE]")}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","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 || statuses[0] != http.StatusOK { |
| t.Fatalf("expected captured status 200, got %#v", statuses) |
| } |
|
|
| frames, done := parseSSEDataFrames(t, rec.Body.String()) |
| if !done { |
| t.Fatalf("expected [DONE], body=%s", rec.Body.String()) |
| } |
| if len(frames) != 1 { |
| t.Fatalf("expected one failure frame, got %#v body=%s", frames, rec.Body.String()) |
| } |
| last := frames[0] |
| statusCode, ok := last["status_code"].(float64) |
| if !ok || int(statusCode) != http.StatusTooManyRequests { |
| t.Fatalf("expected status_code=429, got %#v body=%s", last["status_code"], rec.Body.String()) |
| } |
| errObj, _ := last["error"].(map[string]any) |
| if asString(errObj["code"]) != "upstream_empty_output" { |
| t.Fatalf("expected code=upstream_empty_output, got %#v", last) |
| } |
| } |
|
|
| func TestChatCompletionsStreamRetriesEmptyOutputOnSameSession(t *testing.T) { |
| ds := &streamStatusDSSeqStub{resps: []*http.Response{ |
| makeOpenAISSEHTTPResponse(`data: {"response_message_id":42,"p":"response/thinking_content","v":"plan"}`, "data: [DONE]"), |
| makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"visible"}`, "data: [DONE]"), |
| }} |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: ds, |
| } |
| reqBody := `{"model":"deepseek-v4-pro","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() |
| newOpenAITestRouter(h).ServeHTTP(rec, req) |
|
|
| if rec.Code != http.StatusOK { |
| t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) |
| } |
| if len(ds.payloads) != 2 { |
| t.Fatalf("expected one synthetic retry call, got %d", len(ds.payloads)) |
| } |
| if ds.payloads[0]["chat_session_id"] != ds.payloads[1]["chat_session_id"] { |
| t.Fatalf("expected retry to reuse session, payloads=%#v", ds.payloads) |
| } |
| retryPrompt := asString(ds.payloads[1]["prompt"]) |
| if !strings.Contains(retryPrompt, "Previous reply had no visible output. Please regenerate the visible final answer or tool call now.") { |
| t.Fatalf("expected retry suffix in prompt, got %q", retryPrompt) |
| } |
| |
| if parentID, ok := ds.payloads[1]["parent_message_id"].(int); !ok || parentID != 42 { |
| t.Fatalf("expected retry parent_message_id=42, got %#v", ds.payloads[1]["parent_message_id"]) |
| } |
|
|
| frames, done := parseSSEDataFrames(t, rec.Body.String()) |
| if !done { |
| t.Fatalf("expected [DONE], body=%s", rec.Body.String()) |
| } |
| doneCount := strings.Count(rec.Body.String(), "data: [DONE]") |
| if doneCount != 1 { |
| t.Fatalf("expected one [DONE], got %d body=%s", doneCount, rec.Body.String()) |
| } |
| if len(frames) != 3 { |
| t.Fatalf("expected reasoning, content, finish frames, got %#v body=%s", frames, rec.Body.String()) |
| } |
| id := asString(frames[0]["id"]) |
| for _, frame := range frames[1:] { |
| if asString(frame["id"]) != id { |
| t.Fatalf("expected same completion id across retry stream, frames=%#v", frames) |
| } |
| } |
| choices, _ := frames[1]["choices"].([]any) |
| choice, _ := choices[0].(map[string]any) |
| delta, _ := choice["delta"].(map[string]any) |
| if asString(delta["content"]) != "visible" { |
| t.Fatalf("expected retry content delta, got %#v body=%s", delta, rec.Body.String()) |
| } |
| } |
|
|
| func TestChatCompletionsNonStreamRetriesThinkingOnlyOutput(t *testing.T) { |
| ds := &streamStatusDSSeqStub{resps: []*http.Response{ |
| makeOpenAISSEHTTPResponse(`data: {"response_message_id":99}`, "data: [DONE]"), |
| makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"visible"}`, "data: [DONE]"), |
| }} |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: ds, |
| } |
| reqBody := `{"model":"deepseek-v4-pro","messages":[{"role":"user","content":"hi"}],"stream":false}` |
| 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() |
| newOpenAITestRouter(h).ServeHTTP(rec, req) |
|
|
| if rec.Code != http.StatusOK { |
| t.Fatalf("expected 200 after retry, got %d body=%s", rec.Code, rec.Body.String()) |
| } |
| if len(ds.payloads) != 2 { |
| t.Fatalf("expected one synthetic retry call, got %d", len(ds.payloads)) |
| } |
| |
| if parentID, ok := ds.payloads[1]["parent_message_id"].(int); !ok || parentID != 99 { |
| t.Fatalf("expected retry parent_message_id=99, got %#v", ds.payloads[1]["parent_message_id"]) |
| } |
| 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()) |
| } |
| choices, _ := out["choices"].([]any) |
| choice, _ := choices[0].(map[string]any) |
| message, _ := choice["message"].(map[string]any) |
| if asString(message["content"]) != "visible" { |
| t.Fatalf("expected retry visible content, got %#v", message) |
| } |
| } |
|
|
| func TestChatCompletionsContentFilterDoesNotRetry(t *testing.T) { |
| ds := &streamStatusDSSeqStub{resps: []*http.Response{ |
| makeOpenAISSEHTTPResponse(`data: {"code":"content_filter"}`), |
| makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"visible"}`, "data: [DONE]"), |
| }} |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: ds, |
| } |
| reqBody := `{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"stream":false}` |
| 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() |
| newOpenAITestRouter(h).ServeHTTP(rec, req) |
|
|
| if rec.Code != http.StatusBadRequest { |
| t.Fatalf("expected content_filter 400, got %d body=%s", rec.Code, rec.Body.String()) |
| } |
| if len(ds.payloads) != 1 { |
| t.Fatalf("expected no retry on content_filter, got %d calls", len(ds.payloads)) |
| } |
| } |
|
|
| func TestResponsesStreamUsageIgnoresBatchAccumulatedTokenUsage(t *testing.T) { |
| statuses := make([]int, 0, 1) |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse( |
| `data: {"p":"response/content","v":"hello"}`, |
| `data: {"p":"response","o":"BATCH","v":[{"p":"accumulated_token_usage","v":190},{"p":"quasi_status","v":"FINISHED"}]}`, |
| )}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","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 || statuses[0] != http.StatusOK { |
| t.Fatalf("expected captured status 200, got %#v", statuses) |
| } |
| frames, done := parseSSEDataFrames(t, rec.Body.String()) |
| if !done { |
| t.Fatalf("expected [DONE], body=%s", rec.Body.String()) |
| } |
| if len(frames) == 0 { |
| t.Fatalf("expected at least one json frame, body=%s", rec.Body.String()) |
| } |
| last := frames[len(frames)-1] |
| resp, _ := last["response"].(map[string]any) |
| if resp == nil { |
| t.Fatalf("expected response payload in final frame, got %#v", last) |
| } |
| usage, _ := resp["usage"].(map[string]any) |
| if usage == nil { |
| t.Fatalf("expected usage in response payload, got %#v", resp) |
| } |
| if got, _ := usage["output_tokens"].(float64); int(got) == 190 { |
| t.Fatalf("expected upstream accumulated token usage to be ignored, got %#v", usage["output_tokens"]) |
| } |
| } |
|
|
| func TestResponsesStreamRetriesThinkingOnlyOutput(t *testing.T) { |
| ds := &streamStatusDSSeqStub{resps: []*http.Response{ |
| makeOpenAISSEHTTPResponse(`data: {"response_message_id":77,"p":"response/thinking_content","v":"plan"}`, "data: [DONE]"), |
| makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"visible"}`, "data: [DONE]"), |
| }} |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: ds, |
| } |
| reqBody := `{"model":"deepseek-v4-pro","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() |
| newOpenAITestRouter(h).ServeHTTP(rec, req) |
|
|
| if rec.Code != http.StatusOK { |
| t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) |
| } |
| if len(ds.payloads) != 2 { |
| t.Fatalf("expected one synthetic retry call, got %d", len(ds.payloads)) |
| } |
| |
| if parentID, ok := ds.payloads[1]["parent_message_id"].(int); !ok || parentID != 77 { |
| t.Fatalf("expected retry parent_message_id=77, got %#v", ds.payloads[1]["parent_message_id"]) |
| } |
| body := rec.Body.String() |
| if strings.Contains(body, "response.failed") { |
| t.Fatalf("did not expect premature response.failed, body=%s", body) |
| } |
| if !strings.Contains(body, "response.reasoning.delta") || !strings.Contains(body, "response.output_text.delta") || !strings.Contains(body, "response.completed") { |
| t.Fatalf("expected reasoning, text delta, and completed events, body=%s", body) |
| } |
| if strings.Count(body, "data: [DONE]") != 1 { |
| t.Fatalf("expected one [DONE], body=%s", body) |
| } |
| } |
|
|
| func TestResponsesNonStreamRetriesThinkingOnlyOutput(t *testing.T) { |
| ds := &streamStatusDSSeqStub{resps: []*http.Response{ |
| makeOpenAISSEHTTPResponse(`data: {"response_message_id":88}`, "data: [DONE]"), |
| makeOpenAISSEHTTPResponse(`data: {"p":"response/content","v":"visible"}`, "data: [DONE]"), |
| }} |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: ds, |
| } |
| reqBody := `{"model":"deepseek-v4-pro","input":"hi","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() |
| newOpenAITestRouter(h).ServeHTTP(rec, req) |
|
|
| if rec.Code != http.StatusOK { |
| t.Fatalf("expected 200 after retry, got %d body=%s", rec.Code, rec.Body.String()) |
| } |
| if len(ds.payloads) != 2 { |
| t.Fatalf("expected one synthetic retry call, got %d", len(ds.payloads)) |
| } |
| |
| if parentID, ok := ds.payloads[1]["parent_message_id"].(int); !ok || parentID != 88 { |
| t.Fatalf("expected retry parent_message_id=88, got %#v", ds.payloads[1]["parent_message_id"]) |
| } |
| 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()) |
| } |
| if asString(out["output_text"]) != "visible" { |
| t.Fatalf("expected retry visible output_text, got %#v", out["output_text"]) |
| } |
| output, _ := out["output"].([]any) |
| if len(output) == 0 { |
| t.Fatalf("expected output items, got %#v", out) |
| } |
| item, _ := output[0].(map[string]any) |
| content, _ := item["content"].([]any) |
| if len(content) == 0 { |
| t.Fatalf("expected content entries, got %#v", item) |
| } |
| textEntry, _ := content[0].(map[string]any) |
| if asString(textEntry["type"]) != "output_text" || asString(textEntry["text"]) != "visible" { |
| t.Fatalf("expected visible text entry, got %#v", content) |
| } |
| } |
|
|
| func TestResponsesNonStreamUsageIgnoresPromptAndOutputTokenUsage(t *testing.T) { |
| statuses := make([]int, 0, 1) |
| h := &openAITestSurface{ |
| Store: mockOpenAIConfig{}, |
| Auth: streamStatusAuthStub{}, |
| DS: streamStatusDSStub{resp: makeOpenAISSEHTTPResponse( |
| `data: {"p":"response/content","v":"ok"}`, |
| `data: {"p":"response","o":"BATCH","v":[{"p":"token_usage","v":{"prompt_tokens":11,"completion_tokens":29}},{"p":"quasi_status","v":"FINISHED"}]}`, |
| )}, |
| } |
| r := chi.NewRouter() |
| r.Use(captureStatusMiddleware(&statuses)) |
| registerOpenAITestRoutes(r, h) |
|
|
| reqBody := `{"model":"deepseek-v4-flash","input":"hi","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()) |
| } |
| usage, _ := out["usage"].(map[string]any) |
| if usage == nil { |
| t.Fatalf("expected usage object, got %#v", out) |
| } |
| input, _ := usage["input_tokens"].(float64) |
| output, _ := usage["output_tokens"].(float64) |
| total, _ := usage["total_tokens"].(float64) |
| if int(output) == 29 { |
| t.Fatalf("expected upstream completion token usage to be ignored, got %#v", usage["output_tokens"]) |
| } |
| if int(total) != int(input)+int(output) { |
| t.Fatalf("expected total_tokens=input_tokens+output_tokens, usage=%#v", usage) |
| } |
| } |
|
|