Files
ds2api/internal/translatorcliproxy/bridge_test.go
2026-04-26 04:44:55 +08:00

117 lines
6.8 KiB
Go

package translatorcliproxy
import (
"strings"
"testing"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
)
func TestToOpenAIClaude(t *testing.T) {
raw := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
got := ToOpenAI(sdktranslator.FormatClaude, "claude-sonnet-4-5", raw, false)
s := string(got)
if !strings.Contains(s, `"messages"`) || !strings.Contains(s, `"model"`) {
t.Fatalf("unexpected translated request: %s", s)
}
}
func TestToOpenAIGeminiThinkingBudgetZeroDisablesReasoning(t *testing.T) {
raw := []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":0}}}`)
got := string(ToOpenAI(sdktranslator.FormatGemini, "gemini-2.5-flash", raw, false))
if !strings.Contains(got, `"reasoning_effort":"none"`) {
t.Fatalf("expected Gemini thinkingBudget=0 to translate to reasoning_effort none, got: %s", got)
}
}
func TestFromOpenAINonStreamClaude(t *testing.T) {
original := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
translatedReq := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
openaibody := []byte(`{"id":"chatcmpl_1","object":"chat.completion","created":1,"model":"claude-sonnet-4-5","choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`)
got := FromOpenAINonStream(sdktranslator.FormatClaude, "claude-sonnet-4-5", original, translatedReq, openaibody)
if !strings.Contains(string(got), `"type":"message"`) {
t.Fatalf("expected claude response format, got: %s", string(got))
}
}
func TestFromOpenAINonStreamClaudePreservesUsageFromOpenAI(t *testing.T) {
original := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
translatedReq := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
openaibody := []byte(`{"id":"chatcmpl_1","object":"chat.completion","created":1,"model":"claude-sonnet-4-5","choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}],"usage":{"prompt_tokens":11,"completion_tokens":29,"total_tokens":40}}`)
got := string(FromOpenAINonStream(sdktranslator.FormatClaude, "claude-sonnet-4-5", original, translatedReq, openaibody))
if !strings.Contains(got, `"input_tokens":11`) || !strings.Contains(got, `"output_tokens":29`) {
t.Fatalf("expected claude usage to preserve prompt/completion tokens, got: %s", got)
}
}
func TestFromOpenAINonStreamGeminiPreservesUsageFromOpenAI(t *testing.T) {
original := []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`)
translatedReq := []byte(`{"model":"gemini-2.5-pro","messages":[{"role":"user","content":"hi"}],"stream":false}`)
openaibody := []byte(`{"id":"chatcmpl_1","object":"chat.completion","created":1,"model":"gemini-2.5-pro","choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}],"usage":{"prompt_tokens":11,"completion_tokens":29,"total_tokens":40}}`)
got := string(FromOpenAINonStream(sdktranslator.FormatGemini, "gemini-2.5-pro", original, translatedReq, openaibody))
if !strings.Contains(got, `"promptTokenCount":11`) || !strings.Contains(got, `"candidatesTokenCount":29`) || !strings.Contains(got, `"totalTokenCount":40`) {
t.Fatalf("expected gemini usageMetadata to preserve prompt/completion tokens, got: %s", got)
}
}
func TestFromOpenAINonStreamPreservesResponsesUsageShape(t *testing.T) {
original := []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`)
translatedReq := []byte(`{"model":"gemini-2.5-pro","messages":[{"role":"user","content":"hi"}],"stream":false}`)
openaibody := []byte(`{"id":"resp_1","object":"response","model":"gemini-2.5-pro","usage":{"input_tokens":"11","output_tokens":"29","total_tokens":"40"}}`)
gotGemini := string(FromOpenAINonStream(sdktranslator.FormatGemini, "gemini-2.5-pro", original, translatedReq, openaibody))
if !strings.Contains(gotGemini, `"promptTokenCount":11`) || !strings.Contains(gotGemini, `"candidatesTokenCount":29`) || !strings.Contains(gotGemini, `"totalTokenCount":40`) {
t.Fatalf("expected gemini usageMetadata from input/output usage fields, got: %s", gotGemini)
}
origClaude := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":false}`)
gotClaude := string(FromOpenAINonStream(sdktranslator.FormatClaude, "claude-sonnet-4-5", origClaude, origClaude, openaibody))
if !strings.Contains(gotClaude, `"input_tokens":11`) || !strings.Contains(gotClaude, `"output_tokens":29`) {
t.Fatalf("expected claude usage from input/output usage fields, got: %s", gotClaude)
}
}
func TestParseFormatAliases(t *testing.T) {
cases := map[string]sdktranslator.Format{
"responses": sdktranslator.FormatOpenAIResponse,
"anthropic": sdktranslator.FormatClaude,
"geminicli": sdktranslator.FormatGeminiCLI,
"openai-codex": sdktranslator.FormatCodex,
"antigravity": sdktranslator.FormatAntigravity,
"chat-completions": sdktranslator.FormatOpenAI,
}
for in, want := range cases {
if got := ParseFormat(in); got != want {
t.Fatalf("ParseFormat(%q)=%q want %q", in, got, want)
}
}
}
func TestToOpenAIByNameAllSupportedFormats(t *testing.T) {
tests := []struct {
name string
format string
model string
body string
}{
{name: "openai", format: "openai", model: "gpt-4.1", body: `{"model":"gpt-4.1","messages":[{"role":"user","content":"hi"}],"stream":false}`},
{name: "responses", format: "responses", model: "gpt-4.1", body: `{"model":"gpt-4.1","input":"hello","stream":false}`},
{name: "claude", format: "claude", model: "claude-sonnet-4-5", body: `{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hello"}],"stream":false}`},
{name: "gemini", format: "gemini", model: "gemini-2.5-pro", body: `{"contents":[{"role":"user","parts":[{"text":"hello"}]}]}`},
{name: "gemini-cli", format: "gemini-cli", model: "gemini-2.5-pro", body: `{"model":"gemini-2.5-pro","messages":[{"role":"user","content":"hello"}],"stream":false}`},
{name: "codex", format: "codex", model: "gpt-5-codex", body: `{"model":"gpt-5-codex","messages":[{"role":"user","content":"hello"}],"stream":false}`},
{name: "antigravity", format: "antigravity", model: "gpt-4.1", body: `{"model":"gpt-4.1","messages":[{"role":"user","content":"hello"}],"stream":false}`},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := ToOpenAIByName(tc.format, tc.model, []byte(tc.body), false)
if len(got) == 0 {
t.Fatalf("expected non-empty conversion result for format=%s", tc.format)
}
if !strings.Contains(string(got), `"model"`) {
t.Fatalf("expected model field in converted payload, got=%s", string(got))
}
})
}
}