Changeset 0.22.1 (#95)
This commit is contained in:
340
server/providers/hooks_test.go
Normal file
340
server/providers/hooks_test.go
Normal file
@@ -0,0 +1,340 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// ── MergePresetSettings Tests ───────────────
|
||||
|
||||
func TestMergePresetSettings_Basic(t *testing.T) {
|
||||
provider := map[string]interface{}{
|
||||
"system_prompt_prefix": "Provider prompt",
|
||||
"frequency_penalty": 0.3,
|
||||
}
|
||||
preset := map[string]interface{}{
|
||||
"system_prompt_prefix": "Preset prompt",
|
||||
}
|
||||
|
||||
merged := MergePresetSettings("openai", provider, preset)
|
||||
|
||||
if merged["system_prompt_prefix"] != "Preset prompt" {
|
||||
t.Errorf("preset 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 TestMergePresetSettings_ProviderOnlyBlocked(t *testing.T) {
|
||||
provider := map[string]interface{}{
|
||||
"route": "auto",
|
||||
}
|
||||
preset := map[string]interface{}{
|
||||
"route": "fallback", // route is ProviderOnly for openrouter
|
||||
}
|
||||
|
||||
merged := MergePresetSettings("openrouter", provider, preset)
|
||||
|
||||
if merged["route"] != "auto" {
|
||||
t.Errorf("ProviderOnly field should not be overridden by preset, got %v", merged["route"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePresetSettings_EmptyOverrides(t *testing.T) {
|
||||
provider := map[string]interface{}{"key": "value"}
|
||||
merged := MergePresetSettings("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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user