Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
164 lines
5.5 KiB
Go
164 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestGeminiResponseToChatCompletionsPreservesInlineData(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
parts []any
|
|
want string
|
|
}{
|
|
{
|
|
name: "image only",
|
|
parts: []any{
|
|
map[string]any{"inlineData": map[string]any{"mimeType": "image/png", "data": "aW1hZ2U="}},
|
|
},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "text and image",
|
|
parts: []any{
|
|
map[string]any{"text": "rendered image:\n"},
|
|
map[string]any{"inlineData": map[string]any{"mimeType": "image/webp", "data": "d2VicA=="}},
|
|
},
|
|
want: "rendered image:\n",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
geminiResp := map[string]any{
|
|
"candidates": []any{map[string]any{
|
|
"content": map[string]any{"parts": tt.parts},
|
|
"finishReason": "STOP",
|
|
}},
|
|
}
|
|
rawData, err := json.Marshal(geminiResp)
|
|
require.NoError(t, err)
|
|
|
|
got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.Choices, 1)
|
|
|
|
var content string
|
|
require.NoError(t, json.Unmarshal(got.Choices[0].Message.Content, &content))
|
|
require.Equal(t, tt.want, content)
|
|
require.Equal(t, "stop", got.Choices[0].FinishReason)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGeminiResponseToChatCompletionsOmitsInvalidInlineData(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inlineData map[string]any
|
|
}{
|
|
{
|
|
name: "unsupported MIME type",
|
|
inlineData: map[string]any{"mimeType": "image/svg+xml", "data": "PHN2Zz48L3N2Zz4="},
|
|
},
|
|
{
|
|
name: "malformed MIME type",
|
|
inlineData: map[string]any{"mimeType": "image/png; charset=utf-8", "data": "aW1hZ2U="},
|
|
},
|
|
{
|
|
name: "malformed base64",
|
|
inlineData: map[string]any{"mimeType": "image/png", "data": "not-valid-base64!!!"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
geminiResp := map[string]any{
|
|
"candidates": []any{map[string]any{
|
|
"content": map[string]any{"parts": []any{map[string]any{"text": "before"}, map[string]any{"inlineData": tt.inlineData}, map[string]any{"text": "after"}}},
|
|
"finishReason": "STOP",
|
|
}},
|
|
}
|
|
rawData, err := json.Marshal(geminiResp)
|
|
require.NoError(t, err)
|
|
|
|
got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
|
|
require.NoError(t, err)
|
|
|
|
var content string
|
|
require.NoError(t, json.Unmarshal(got.Choices[0].Message.Content, &content))
|
|
require.Equal(t, "beforeafter", content)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConvertGeminiToClaudeMessageOmitsInlineDataForAnthropicMessages(t *testing.T) {
|
|
geminiResp := map[string]any{
|
|
"candidates": []any{map[string]any{
|
|
"content": map[string]any{"parts": []any{
|
|
map[string]any{"text": "before"},
|
|
map[string]any{"inlineData": map[string]any{"mimeType": "image/png", "data": "aW1hZ2U="}},
|
|
map[string]any{"functionCall": map[string]any{"name": "get_weather", "args": map[string]any{"city": "Paris"}}},
|
|
map[string]any{"text": "after"},
|
|
}},
|
|
"finishReason": "STOP",
|
|
}},
|
|
}
|
|
rawData, err := json.Marshal(geminiResp)
|
|
require.NoError(t, err)
|
|
|
|
withInlineData, _ := convertGeminiToClaudeMessage(geminiResp, "gemini-test", rawData, true)
|
|
contentWithInlineData, ok := withInlineData["content"].([]any)
|
|
require.True(t, ok)
|
|
require.Len(t, contentWithInlineData, 4)
|
|
require.Equal(t, map[string]any{"type": "text", "text": "before"}, contentWithInlineData[0])
|
|
require.Equal(t, map[string]any{"type": "text", "text": ""}, contentWithInlineData[1])
|
|
toolUse, ok := contentWithInlineData[2].(map[string]any)
|
|
require.True(t, ok)
|
|
require.Equal(t, "tool_use", toolUse["type"])
|
|
require.Equal(t, "get_weather", toolUse["name"])
|
|
require.Equal(t, map[string]any{"type": "text", "text": "after"}, contentWithInlineData[3])
|
|
|
|
withoutInlineData, _ := convertGeminiToClaudeMessage(geminiResp, "gemini-test", rawData, false)
|
|
contentWithoutInlineData, ok := withoutInlineData["content"].([]any)
|
|
require.True(t, ok)
|
|
require.Len(t, contentWithoutInlineData, 3)
|
|
require.Equal(t, map[string]any{"type": "text", "text": "before"}, contentWithoutInlineData[0])
|
|
toolUseWithoutInlineData, ok := contentWithoutInlineData[1].(map[string]any)
|
|
require.True(t, ok)
|
|
require.Equal(t, "tool_use", toolUseWithoutInlineData["type"])
|
|
require.Equal(t, "get_weather", toolUseWithoutInlineData["name"])
|
|
require.Equal(t, map[string]any{"type": "text", "text": "after"}, contentWithoutInlineData[2])
|
|
}
|
|
|
|
func TestGeminiResponseToChatCompletionsRetainsTextAndToolBehavior(t *testing.T) {
|
|
geminiResp := map[string]any{
|
|
"candidates": []any{map[string]any{
|
|
"content": map[string]any{"parts": []any{
|
|
map[string]any{"text": "checking"},
|
|
map[string]any{"functionCall": map[string]any{
|
|
"name": "get_weather",
|
|
"args": map[string]any{"city": "Paris"},
|
|
}},
|
|
}},
|
|
"finishReason": "STOP",
|
|
}},
|
|
}
|
|
rawData, err := json.Marshal(geminiResp)
|
|
require.NoError(t, err)
|
|
|
|
got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, got.Choices, 1)
|
|
|
|
choice := got.Choices[0]
|
|
var content string
|
|
require.NoError(t, json.Unmarshal(choice.Message.Content, &content))
|
|
require.Equal(t, "checking", content)
|
|
require.Equal(t, "tool_calls", choice.FinishReason)
|
|
require.Len(t, choice.Message.ToolCalls, 1)
|
|
require.Equal(t, "get_weather", choice.Message.ToolCalls[0].Function.Name)
|
|
require.JSONEq(t, `{"city":"Paris"}`, choice.Message.ToolCalls[0].Function.Arguments)
|
|
}
|