package translatorcliproxy import ( "net/http/httptest" "strings" "testing" sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" ) func TestOpenAIStreamTranslatorWriterClaude(t *testing.T) { original := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":true}`) translated := []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"stream":true}`) rec := httptest.NewRecorder() w := NewOpenAIStreamTranslatorWriter(rec, sdktranslator.FormatClaude, "claude-sonnet-4-5", original, translated) w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(200) _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl_1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4-5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"finish_reason\":null}]}\n\n")) _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl_1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4-5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hi\"},\"finish_reason\":null}]}\n\n")) _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl_1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4-5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":29,\"total_tokens\":40}}\n\n")) _, _ = w.Write([]byte("data: [DONE]\n\n")) body := rec.Body.String() if !strings.Contains(body, "event: message_start") { t.Fatalf("expected claude message_start event, got: %s", body) } if !strings.Contains(body, `"output_tokens":29`) { t.Fatalf("expected claude stream usage to preserve output tokens, got: %s", body) } } func TestOpenAIStreamTranslatorWriterGemini(t *testing.T) { original := []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`) translated := []byte(`{"model":"gemini-2.5-pro","messages":[{"role":"user","content":"hi"}],"stream":true}`) rec := httptest.NewRecorder() w := NewOpenAIStreamTranslatorWriter(rec, sdktranslator.FormatGemini, "gemini-2.5-pro", original, translated) w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(200) _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl_1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gemini-2.5-pro\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hi\"},\"finish_reason\":null}]}\n\n")) _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl_1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gemini-2.5-pro\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":29,\"total_tokens\":40}}\n\n")) _, _ = w.Write([]byte("data: [DONE]\n\n")) body := rec.Body.String() if !strings.Contains(body, "candidates") { t.Fatalf("expected gemini stream payload, got: %s", body) } if !strings.Contains(body, `"promptTokenCount":11`) || !strings.Contains(body, `"candidatesTokenCount":29`) { t.Fatalf("expected gemini stream usageMetadata to preserve usage, got: %s", body) } } func TestOpenAIStreamTranslatorWriterPreservesKeepAliveComment(t *testing.T) { rec := httptest.NewRecorder() w := NewOpenAIStreamTranslatorWriter(rec, sdktranslator.FormatGemini, "gemini-2.5-pro", []byte(`{}`), []byte(`{}`)) w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(200) _, _ = w.Write([]byte(": keep-alive\n\n")) body := rec.Body.String() if !strings.Contains(body, ": keep-alive\n\n") { t.Fatalf("expected keep-alive comment passthrough, got %q", body) } } func TestInjectStreamUsageMetadataPreservesSSEFrameTerminator(t *testing.T) { chunk := []byte("data: {\"candidates\":[{\"index\":0}],\"model\":\"gemini-2.5-pro\"}\n\n") usage := openAIUsage{PromptTokens: 11, CompletionTokens: 29, TotalTokens: 40} got := injectStreamUsageMetadata(chunk, sdktranslator.FormatGemini, usage) if !strings.HasSuffix(string(got), "\n\n") { t.Fatalf("expected injected chunk to preserve \\n\\n frame terminator, got %q", string(got)) } if !strings.Contains(string(got), `"usageMetadata"`) { t.Fatalf("expected usageMetadata injected, got %q", string(got)) } } func TestExtractOpenAIUsageSupportsResponsesUsageFields(t *testing.T) { line := []byte(`data: {"usage":{"input_tokens":"11","output_tokens":"29","total_tokens":"40"}}`) got, ok := extractOpenAIUsage(line) if !ok { t.Fatal("expected usage extracted from input/output usage fields") } if got.PromptTokens != 11 || got.CompletionTokens != 29 || got.TotalTokens != 40 { t.Fatalf("unexpected usage extracted: %#v", got) } }