This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/providers/hooks_test.go
2026-03-04 10:44:42 +00:00

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")
}
}