package service import ( "encoding/json" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) func TestExtractOpenAIRequestMetaFromBody(t *testing.T) { tests := []struct { name string body []byte wantModel string wantStream bool wantPromptKey string }{ { name: "完整字段", body: []byte(`{"model":"gpt-5","stream":true,"prompt_cache_key":" ses-1 "}`), wantModel: "gpt-5", wantStream: true, wantPromptKey: "ses-1", }, { name: "缺失可选字段", body: []byte(`{"model":"gpt-4"}`), wantModel: "gpt-4", wantStream: false, wantPromptKey: "", }, { name: "空请求体", body: nil, wantModel: "", wantStream: false, wantPromptKey: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { model, stream, promptKey := extractOpenAIRequestMetaFromBody(tt.body) require.Equal(t, tt.wantModel, model) require.Equal(t, tt.wantStream, stream) require.Equal(t, tt.wantPromptKey, promptKey) }) } } func TestExtractOpenAIReasoningEffortFromBody(t *testing.T) { tests := []struct { name string body []byte model string wantNil bool wantValue string }{ { name: "优先读取 reasoning.effort", body: []byte(`{"reasoning":{"effort":"medium"}}`), model: "gpt-5-high", wantNil: false, wantValue: "medium", }, { name: "兼容 reasoning_effort", body: []byte(`{"reasoning_effort":"x-high"}`), model: "", wantNil: false, wantValue: "xhigh", }, { name: "minimal 归一化为空", body: []byte(`{"reasoning":{"effort":"minimal"}}`), model: "gpt-5-high", wantNil: true, }, { name: "缺失字段时从模型后缀推导", body: []byte(`{"input":"hi"}`), model: "gpt-5-high", wantNil: false, wantValue: "high", }, { name: "未知后缀不返回", body: []byte(`{"input":"hi"}`), model: "gpt-5-unknown", wantNil: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := extractOpenAIReasoningEffortFromBody(tt.body, tt.model) if tt.wantNil { require.Nil(t, got) return } require.NotNil(t, got) require.Equal(t, tt.wantValue, *got) }) } } func TestGetOpenAIRequestBodyMap_UsesContextCache(t *testing.T) { gin.SetMode(gin.TestMode) rec := httptest.NewRecorder() c, _ := gin.CreateTestContext(rec) cached := map[string]any{"model": "cached-model", "stream": true} c.Set(OpenAIParsedRequestBodyKey, cached) got, err := getOpenAIRequestBodyMap(c, []byte(`{invalid-json`)) require.NoError(t, err) require.Equal(t, cached, got) } func TestGetOpenAIRequestBodyMap_ParseErrorWithoutCache(t *testing.T) { _, err := getOpenAIRequestBodyMap(nil, []byte(`{invalid-json`)) require.Error(t, err) require.Contains(t, err.Error(), "parse request") } func TestGetOpenAIRequestBodyMap_WriteBackContextCache(t *testing.T) { gin.SetMode(gin.TestMode) rec := httptest.NewRecorder() c, _ := gin.CreateTestContext(rec) got, err := getOpenAIRequestBodyMap(c, []byte(`{"model":"gpt-5","stream":true}`)) require.NoError(t, err) require.Equal(t, "gpt-5", got["model"]) cached, ok := c.Get(OpenAIParsedRequestBodyKey) require.True(t, ok) cachedMap, ok := cached.(map[string]any) require.True(t, ok) require.Equal(t, got, cachedMap) } func TestSanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap(t *testing.T) { var reqBody map[string]any require.NoError(t, json.Unmarshal([]byte(`{ "model":"gpt-5.4", "input":[ {"role":"user","content":[ {"type":"input_text","text":"Describe this"}, {"type":"input_image","image_url":"data:image/png;base64, "}, {"type":"input_image","image_url":"data:image/png;base64,abc123"} ]}, {"role":"user","content":[ {"type":"input_image","image_url":"data:image/png;base64,"} ]}, {"type":"input_image","image_url":"data:image/png;base64,"}, {"type":"input_image","image_url":"data:image/png;base64,top-level-valid"} ] }`), &reqBody)) require.True(t, sanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap(reqBody)) normalized, err := json.Marshal(reqBody) require.NoError(t, err) require.JSONEq(t, `{ "model":"gpt-5.4", "input":[ {"role":"user","content":[ {"type":"input_text","text":"Describe this"}, {"type":"input_image","image_url":"data:image/png;base64,abc123"} ]}, {"type":"input_image","image_url":"data:image/png;base64,top-level-valid"} ] }`, string(normalized)) } func TestSanitizeEmptyBase64InputImagesInOpenAIBody(t *testing.T) { body, changed, err := sanitizeEmptyBase64InputImagesInOpenAIBody([]byte(`{ "model":"gpt-5.4", "stream":true, "input":[ {"role":"user","content":[ {"type":"input_text","text":"Describe this"}, {"type":"input_image","image_url":"data:image/png;base64,"} ]} ] }`)) require.NoError(t, err) require.True(t, changed) require.JSONEq(t, `{ "model":"gpt-5.4", "stream":true, "input":[ {"role":"user","content":[ {"type":"input_text","text":"Describe this"} ]} ] }`, string(body)) }