258 lines
8.8 KiB
Go
258 lines
8.8 KiB
Go
package translator
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
func TestRegistryTranslateRequestAppliesSummaryIntent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
from Format
|
|
to Format
|
|
input string
|
|
translated string
|
|
path string
|
|
want string
|
|
wantExists bool
|
|
}{
|
|
{
|
|
name: "chat effort enables Claude summary",
|
|
from: FormatOpenAI,
|
|
to: FormatClaude,
|
|
input: `{"reasoning_effort":"high"}`,
|
|
translated: `{"thinking":{"type":"adaptive"}}`,
|
|
path: "thinking.display",
|
|
want: "summarized",
|
|
wantExists: true,
|
|
},
|
|
{
|
|
name: "responses effort alone leaves Claude display absent",
|
|
from: FormatOpenAIResponse,
|
|
to: FormatClaude,
|
|
input: `{"reasoning":{"effort":"high"}}`,
|
|
translated: `{"thinking":{"type":"adaptive"}}`,
|
|
path: "thinking.display",
|
|
},
|
|
{
|
|
name: "responses summary enables Claude summary",
|
|
from: FormatOpenAIResponse,
|
|
to: FormatClaude,
|
|
input: `{"reasoning":{"effort":"high","summary":"auto"}}`,
|
|
translated: `{"thinking":{"type":"adaptive"}}`,
|
|
path: "thinking.display",
|
|
want: "summarized",
|
|
wantExists: true,
|
|
},
|
|
{
|
|
name: "responses null summary disables Gemini summaries",
|
|
from: FormatOpenAIResponse,
|
|
to: FormatGemini,
|
|
input: `{"reasoning":{"effort":"high","summary":null}}`,
|
|
translated: `{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}`,
|
|
path: "generationConfig.thinkingConfig.includeThoughts",
|
|
want: "false",
|
|
wantExists: true,
|
|
},
|
|
{
|
|
name: "Google Chat extension overrides effort",
|
|
from: FormatOpenAI,
|
|
to: FormatGemini,
|
|
input: `{"reasoning_effort":"high","extra_body":{"google":{"thinking_config":{"include_thoughts":false}}}}`,
|
|
translated: `{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high","includeThoughts":true}}}`,
|
|
path: "generationConfig.thinkingConfig.includeThoughts",
|
|
want: "false",
|
|
wantExists: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
registry := NewRegistry()
|
|
registry.Register(test.from, test.to, func(_ string, _ []byte, _ bool) []byte {
|
|
return []byte(test.translated)
|
|
}, ResponseTransform{})
|
|
out := registry.TranslateRequest(test.from, test.to, "model", []byte(test.input), false)
|
|
result := gjson.GetBytes(out, test.path)
|
|
if result.Exists() != test.wantExists {
|
|
t.Fatalf("%s exists = %v, want %v; body=%s", test.path, result.Exists(), test.wantExists, out)
|
|
}
|
|
if test.wantExists && result.String() != test.want {
|
|
t.Fatalf("%s = %q, want %q; body=%s", test.path, result.String(), test.want, out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestActivatesClaudeForEnabledSummary(t *testing.T) {
|
|
registry := NewRegistry()
|
|
registry.Register(FormatOpenAIResponse, FormatClaude, func(_ string, _ []byte, _ bool) []byte {
|
|
return []byte(`{"model":"claude-opus-5","max_tokens":32000}`)
|
|
}, ResponseTransform{})
|
|
out := registry.TranslateRequest(
|
|
FormatOpenAIResponse,
|
|
FormatClaude,
|
|
"claude-opus-5",
|
|
[]byte(`{"reasoning":{"summary":"auto"},"input":"hi"}`),
|
|
false,
|
|
)
|
|
if got := gjson.GetBytes(out, "thinking.type").String(); got != "adaptive" {
|
|
t.Fatalf("thinking.type = %q, want adaptive; body=%s", got, out)
|
|
}
|
|
if got := gjson.GetBytes(out, "thinking.display").String(); got != "summarized" {
|
|
t.Fatalf("thinking.display = %q, want summarized; body=%s", got, out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestDoesNotActivateClaudeForDisabledSummary(t *testing.T) {
|
|
registry := NewRegistry()
|
|
registry.Register(FormatOpenAIResponse, FormatClaude, func(_ string, _ []byte, _ bool) []byte {
|
|
return []byte(`{"model":"claude-opus-5","max_tokens":32000}`)
|
|
}, ResponseTransform{})
|
|
out := registry.TranslateRequest(
|
|
FormatOpenAIResponse,
|
|
FormatClaude,
|
|
"claude-opus-5",
|
|
[]byte(`{"reasoning":{"summary":null},"input":"hi"}`),
|
|
false,
|
|
)
|
|
if gjson.GetBytes(out, "thinking").Exists() {
|
|
t.Fatalf("disabled summary activated Claude thinking: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestPreservesNativeClaudeMissingDisplay(t *testing.T) {
|
|
registry := NewRegistry()
|
|
body := []byte(`{"model":"claude-opus-5","thinking":{"type":"adaptive"}}`)
|
|
out := registry.TranslateRequest(FormatClaude, FormatClaude, "claude-opus-5", body, true)
|
|
if gjson.GetBytes(out, "thinking.display").Exists() {
|
|
t.Fatalf("native Claude request without display gained one: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestDoesNotMixSummaryIntoFallback(t *testing.T) {
|
|
registry := NewRegistry()
|
|
body := []byte(`{"model":"gemini-3.6-flash","reasoning":{"summary":"auto"},"input":"hi"}`)
|
|
out := registry.TranslateRequest(FormatOpenAIResponse, FormatGemini, "gemini-3.6-flash", body, false)
|
|
if !bytes.Equal(out, body) {
|
|
t.Fatalf("missing translator changed fallback body: got %s, want %s", out, body)
|
|
}
|
|
if gjson.GetBytes(out, "generationConfig").Exists() {
|
|
t.Fatalf("missing translator mixed Gemini fields into Responses body: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestPluginMissDoesNotMixSummary(t *testing.T) {
|
|
registry := NewRegistry()
|
|
hooks := &fakePluginHooks{requestTranslateOK: false}
|
|
registry.SetPluginHooks(hooks)
|
|
body := []byte(`{"model":"gemini-3.6-flash","reasoning":{"summary":"auto"},"input":"hi"}`)
|
|
out := registry.TranslateRequest(FormatOpenAIResponse, FormatGemini, "gemini-3.6-flash", body, false)
|
|
if !bytes.Equal(out, body) {
|
|
t.Fatalf("plugin translation miss changed fallback body: got %s, want %s", out, body)
|
|
}
|
|
if gjson.GetBytes(out, "generationConfig").Exists() {
|
|
t.Fatalf("plugin translation miss mixed Gemini fields into Responses body: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestAppliesSummaryAfterPluginTranslation(t *testing.T) {
|
|
registry := NewRegistry()
|
|
hooks := &fakePluginHooks{
|
|
requestTranslateBody: []byte(`{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}`),
|
|
requestTranslateOK: true,
|
|
}
|
|
registry.SetPluginHooks(hooks)
|
|
out := registry.TranslateRequest(
|
|
FormatOpenAIResponse,
|
|
FormatGemini,
|
|
"gemini-3.6-flash",
|
|
[]byte(`{"reasoning":{"summary":"auto"},"input":"hi"}`),
|
|
false,
|
|
)
|
|
if !gjson.GetBytes(out, "generationConfig.thinkingConfig.includeThoughts").Bool() {
|
|
t.Fatalf("plugin-translated request lost canonical summary: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestPluginNormalizerOwnsSourceSummaryIntent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
normalize func([]byte) []byte
|
|
wantExists bool
|
|
want bool
|
|
}{
|
|
{
|
|
name: "removed summary remains absent",
|
|
normalize: func(body []byte) []byte {
|
|
out, _ := sjson.DeleteBytes(body, "reasoning.summary")
|
|
return out
|
|
},
|
|
},
|
|
{
|
|
name: "disabled summary replaces enabled intent",
|
|
normalize: func(body []byte) []byte {
|
|
out, _ := sjson.SetBytes(body, "reasoning.summary", nil)
|
|
return out
|
|
},
|
|
wantExists: true,
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
registry := NewRegistry()
|
|
hooks := &fakePluginHooks{
|
|
normalizeRequest: test.normalize,
|
|
requestTranslateBody: []byte(`{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}`),
|
|
requestTranslateOK: true,
|
|
}
|
|
registry.SetPluginHooks(hooks)
|
|
|
|
out := registry.TranslateRequest(
|
|
FormatOpenAIResponse,
|
|
FormatGemini,
|
|
"gemini-3.6-flash",
|
|
[]byte(`{"reasoning":{"summary":"auto"},"input":"hi"}`),
|
|
false,
|
|
)
|
|
result := gjson.GetBytes(out, "generationConfig.thinkingConfig.includeThoughts")
|
|
if result.Exists() != test.wantExists {
|
|
t.Fatalf("includeThoughts exists = %v, want %v; body=%s", result.Exists(), test.wantExists, out)
|
|
}
|
|
if test.wantExists && result.Bool() != test.want {
|
|
t.Fatalf("includeThoughts = %v, want %v; body=%s", result.Bool(), test.want, out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryTranslateRequestNormalizerOwnsFinalSummaryField(t *testing.T) {
|
|
registry := NewRegistry()
|
|
registry.Register(FormatOpenAIResponse, FormatGemini, func(_ string, _ []byte, _ bool) []byte {
|
|
return []byte(`{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}`)
|
|
}, ResponseTransform{})
|
|
hooks := &fakePluginHooks{normalizeRequest: func(body []byte) []byte {
|
|
if !gjson.GetBytes(body, "generationConfig.thinkingConfig.includeThoughts").Bool() {
|
|
t.Fatalf("normalizer did not receive canonical enabled summary: %s", body)
|
|
}
|
|
out, _ := sjson.DeleteBytes(body, "generationConfig.thinkingConfig.includeThoughts")
|
|
return out
|
|
}}
|
|
registry.SetPluginHooks(hooks)
|
|
|
|
out := registry.TranslateRequest(
|
|
FormatOpenAIResponse,
|
|
FormatGemini,
|
|
"gemini-3.6-flash",
|
|
[]byte(`{"reasoning":{"effort":"high","summary":"auto"},"input":"hi"}`),
|
|
false,
|
|
)
|
|
if gjson.GetBytes(out, "generationConfig.thinkingConfig.includeThoughts").Exists() {
|
|
t.Fatalf("summary post-processing overrode request normalizer: %s", out)
|
|
}
|
|
}
|