package providers import ( "encoding/json" "testing" "gophergate/internal/models" ) func TestSanitizeFunctionName(t *testing.T) { tests := []struct { input string expected string }{ {"google-search", "google-search"}, {"google.search", "google_search"}, {"google search", "google_search"}, {"web_search(query)", "web_search_query_"}, {"", "function"}, {"123_abc-XYZ", "123_abc-XYZ"}, {"invalid.name.with.dots", "invalid_name_with_dots"}, } for _, tc := range tests { actual := sanitizeFunctionName(tc.input) if actual != tc.expected { t.Errorf("sanitizeFunctionName(%q) = %q; expected %q", tc.input, actual, tc.expected) } } } func TestMessagesToOpenAIJSON_SanitizeToolCalls(t *testing.T) { messages := []models.UnifiedMessage{ { Role: "assistant", Content: []models.UnifiedContentPart{ {Type: "text", Text: "I will use search."}, }, ToolCalls: []models.ToolCall{ { ID: "call_1", Type: "function", Function: models.FunctionCall{ Name: "google.search", Arguments: `{"query": "hello"}`, }, }, }, }, { Role: "tool", Content: []models.UnifiedContentPart{ {Type: "text", Text: `{"result": "success"}`}, }, ToolCallID: stringPtr("call_1"), Name: stringPtr("google.search"), }, } res, err := MessagesToOpenAIJSON(messages) if err != nil { t.Fatalf("MessagesToOpenAIJSON failed: %v", err) } if len(res) != 2 { t.Fatalf("expected 2 messages, got %d", len(res)) } // Verify assistant message msg1 := res[0].(map[string]interface{}) if msg1["role"] != "assistant" { t.Errorf("expected role assistant, got %v", msg1["role"]) } calls := msg1["tool_calls"].([]models.ToolCall) if len(calls) != 1 { t.Fatalf("expected 1 tool call, got %d", len(calls)) } if calls[0].Function.Name != "google_search" { t.Errorf("expected function name google_search, got %q", calls[0].Function.Name) } // Verify tool response message msg2 := res[1].(map[string]interface{}) if msg2["role"] != "tool" { t.Errorf("expected role tool, got %v", msg2["role"]) } if msg2["name"] != "google_search" { t.Errorf("expected tool name google_search, got %v", msg2["name"]) } } func TestBuildOpenAIBody_SanitizeToolsAndChoice(t *testing.T) { req := &models.UnifiedRequest{ Model: "gpt-4o", Tools: []models.Tool{ { Type: "function", Function: models.FunctionDef{ Name: "google.search", }, }, }, ToolChoice: json.RawMessage(`{"type": "function", "function": {"name": "google.search"}}`), } body := BuildOpenAIBody(req, nil, false) // Verify tools tools := body["tools"].([]models.Tool) if len(tools) != 1 { t.Fatalf("expected 1 tool, got %d", len(tools)) } if tools[0].Function.Name != "google_search" { t.Errorf("expected tool function name google_search, got %q", tools[0].Function.Name) } // Verify tool_choice toolChoice := body["tool_choice"].(map[string]interface{}) funcObj := toolChoice["function"].(map[string]interface{}) if funcObj["name"] != "google_search" { t.Errorf("expected tool_choice function name google_search, got %q", funcObj["name"]) } } func stringPtr(s string) *string { return &s }