package providers import ( "strings" "testing" ) // ── Profile Schema Tests ──────────────────── func TestGetProfileSchema_BuiltIn(t *testing.T) { for _, id := range []string{"openai", "anthropic", "venice", "openrouter"} { s := GetProfileSchema(id) if len(s.Fields) == 0 { t.Errorf("GetProfileSchema(%q) returned empty fields", id) } // Every schema should have system_prompt_prefix found := false for _, f := range s.Fields { if f.Key == "system_prompt_prefix" { found = true } } if !found { t.Errorf("GetProfileSchema(%q) missing system_prompt_prefix field", id) } } } func TestGetProfileSchema_UnknownFallsToOpenAI(t *testing.T) { s := GetProfileSchema("unknown-provider") oai := GetProfileSchema("openai") if len(s.Fields) != len(oai.Fields) { t.Errorf("Unknown provider should fall back to openai schema, got %d fields, want %d", len(s.Fields), len(oai.Fields)) } } // ── Hook PreRequest Tests ─────────────────── func TestOpenAIHooks_SystemPromptPrefix(t *testing.T) { h := &OpenAIHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "system_prompt_prefix": "You are a helpful pirate.", }, } req := &CompletionRequest{ Messages: []Message{ {Role: "system", Content: "Be concise."}, {Role: "user", Content: "Hello"}, }, } h.PreRequest(cfg, req) if req.Messages[0].Content != "You are a helpful pirate.\n\nBe concise." { t.Errorf("system prompt prefix not prepended, got: %s", req.Messages[0].Content) } } func TestOpenAIHooks_SystemPromptPrefix_NoExisting(t *testing.T) { h := &OpenAIHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "system_prompt_prefix": "Be a pirate.", }, } req := &CompletionRequest{ Messages: []Message{ {Role: "user", Content: "Hello"}, }, } h.PreRequest(cfg, req) if len(req.Messages) != 2 { t.Fatalf("expected 2 messages, got %d", len(req.Messages)) } if req.Messages[0].Role != "system" || req.Messages[0].Content != "Be a pirate." { t.Errorf("expected injected system message, got role=%s content=%s", req.Messages[0].Role, req.Messages[0].Content) } } func TestOpenAIHooks_FrequencyPenalty(t *testing.T) { h := &OpenAIHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "frequency_penalty": 0.5, }, } req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hello"}}} h.PreRequest(cfg, req) if req.ExtraBody == nil { t.Fatal("ExtraBody should be set") } if v, ok := req.ExtraBody["frequency_penalty"].(float64); !ok || v != 0.5 { t.Errorf("frequency_penalty: got %v, want 0.5", req.ExtraBody["frequency_penalty"]) } } func TestOpenAIHooks_NilSettings(t *testing.T) { h := &OpenAIHooks{} cfg := ProviderConfig{Settings: nil} req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hello"}}} h.PreRequest(cfg, req) // should not panic if req.ExtraBody != nil { t.Errorf("ExtraBody should be nil with no settings") } } func TestAnthropicHooks_ExtendedThinking(t *testing.T) { h := &AnthropicHooks{} temp := 0.7 cfg := ProviderConfig{ Settings: map[string]interface{}{ "extended_thinking": true, "thinking_budget": float64(20000), // JSON numbers are float64 }, } req := &CompletionRequest{ Messages: []Message{{Role: "user", Content: "Think hard"}}, Temperature: &temp, } h.PreRequest(cfg, req) if req.Temperature != nil { t.Error("Temperature should be cleared for extended thinking") } if req.ExtraBody == nil { t.Fatal("ExtraBody should be set") } thinking, ok := req.ExtraBody["thinking"].(map[string]interface{}) if !ok { t.Fatal("ExtraBody[thinking] should be map") } if thinking["type"] != "enabled" { t.Errorf("thinking.type = %v, want enabled", thinking["type"]) } if thinking["budget_tokens"] != 20000 { t.Errorf("thinking.budget_tokens = %v, want 20000", thinking["budget_tokens"]) } } func TestAnthropicHooks_ThinkingDefaultBudget(t *testing.T) { h := &AnthropicHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "extended_thinking": true, // no thinking_budget → should use default 10000 }, } req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Think"}}} h.PreRequest(cfg, req) thinking := req.ExtraBody["thinking"].(map[string]interface{}) if thinking["budget_tokens"] != 10000 { t.Errorf("default budget = %v, want 10000", thinking["budget_tokens"]) } } func TestAnthropicHooks_NoThinking(t *testing.T) { h := &AnthropicHooks{} temp := 0.7 cfg := ProviderConfig{ Settings: map[string]interface{}{ "extended_thinking": false, }, } req := &CompletionRequest{ Messages: []Message{{Role: "user", Content: "Hi"}}, Temperature: &temp, } h.PreRequest(cfg, req) if req.ExtraBody != nil { t.Error("ExtraBody should be nil when thinking is disabled") } if req.Temperature == nil { t.Error("Temperature should be preserved when thinking is off") } } func TestVeniceHooks_WebSearch(t *testing.T) { h := &VeniceHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "enable_web_search": true, }, } req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Search for news"}}} h.PreRequest(cfg, req) vp, ok := req.ExtraBody["venice_parameters"].(map[string]interface{}) if !ok { t.Fatal("venice_parameters missing from ExtraBody") } if vp["enable_web_search"] != "always" { t.Errorf("enable_web_search = %v, want 'always'", vp["enable_web_search"]) } } func TestVeniceHooks_ThinkingDisablesSystemPrompt(t *testing.T) { h := &VeniceHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "enable_thinking": true, }, } req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Think"}}} h.PreRequest(cfg, req) vp := req.ExtraBody["venice_parameters"].(map[string]interface{}) if vp["include_venice_system_prompt"] != false { t.Error("Thinking mode should set include_venice_system_prompt=false") } } func TestVeniceHooks_NoSettings(t *testing.T) { h := &VeniceHooks{} cfg := ProviderConfig{Settings: map[string]interface{}{}} req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hi"}}} h.PreRequest(cfg, req) if req.ExtraBody != nil { t.Error("ExtraBody should be nil with empty settings") } } func TestOpenRouterHooks_Route(t *testing.T) { h := &OpenRouterHooks{} cfg := ProviderConfig{ Settings: map[string]interface{}{ "route": "fallback", "require_parameters": true, }, CustomHeaders: map[string]string{}, } req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hi"}}} h.PreRequest(cfg, req) // Note: OpenRouter hooks set headers on the cfg copy, which is local. // The real effect is tested via the serialized header value. // Since cfg is passed by value in hooks, this test verifies the logic runs without panic. } // ── GetHooks Tests ────────────────────────── func TestGetHooks_BuiltIn(t *testing.T) { for _, id := range []string{"openai", "anthropic", "venice", "openrouter"} { h := GetHooks(id) if h == nil { t.Errorf("GetHooks(%q) returned nil", id) } } } func TestGetHooks_Unknown(t *testing.T) { h := GetHooks("nonexistent") if h != nil { t.Error("GetHooks for unknown type should return nil") } } // ── MergePersonaSettings Tests ────────────── func TestMergePersonaSettings_Basic(t *testing.T) { provider := map[string]interface{}{ "system_prompt_prefix": "Provider prompt", "frequency_penalty": 0.3, } persona := map[string]interface{}{ "system_prompt_prefix": "Persona prompt", } merged := MergePersonaSettings("openai", provider, persona) if merged["system_prompt_prefix"] != "Persona prompt" { t.Errorf("persona should override provider, got %v", merged["system_prompt_prefix"]) } if merged["frequency_penalty"] != 0.3 { t.Errorf("provider value should be preserved, got %v", merged["frequency_penalty"]) } } func TestMergePersonaSettings_ProviderOnlyBlocked(t *testing.T) { provider := map[string]interface{}{ "route": "auto", } persona := map[string]interface{}{ "route": "fallback", // route is ProviderOnly for openrouter } merged := MergePersonaSettings("openrouter", provider, persona) if merged["route"] != "auto" { t.Errorf("ProviderOnly field should not be overridden by persona, got %v", merged["route"]) } } func TestMergePersonaSettings_EmptyOverrides(t *testing.T) { provider := map[string]interface{}{"key": "value"} merged := MergePersonaSettings("openai", provider, nil) if merged["key"] != "value" { t.Error("empty overrides should return provider settings unchanged") } } // ── mergeExtraBody Tests ──────────────────── func TestMergeExtraBody(t *testing.T) { body := []byte(`{"model":"gpt-4","stream":true}`) extra := map[string]interface{}{ "venice_parameters": map[string]interface{}{ "enable_web_search": "always", }, } merged, err := mergeExtraBody(body, extra) if err != nil { t.Fatalf("mergeExtraBody error: %v", err) } // Should contain both original and extra fields s := string(merged) if !strings.Contains(s, `"model"`) || !strings.Contains(s, `"stream"`) { t.Error("original fields missing from merged body") } if !strings.Contains(s, `"venice_parameters"`) || !strings.Contains(s, `"enable_web_search"`) { t.Error("extra fields missing from merged body") } }