341 lines
9.4 KiB
Go
341 lines
9.4 KiB
Go
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")
|
|
}
|
|
}
|