mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
Amp-Thread-ID: https://ampcode.com/threads/T-019c0f77-82b6-711c-9172-092bd2a2059d Co-authored-by: Amp <amp@ampcode.com>
149 lines
4.5 KiB
Go
149 lines
4.5 KiB
Go
package amp
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/http/httputil"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Characterization tests for fallback_handlers.go
|
|
// These tests capture existing behavior before refactoring to routing layer
|
|
|
|
func TestFallbackHandler_WrapHandler_LocalProvider_NoMapping(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Setup: model that has local providers (gemini-2.5-pro is registered)
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
body := `{"model": "gemini-2.5-pro", "messages": [{"role": "user", "content": "hello"}]}`
|
|
req := httptest.NewRequest(http.MethodPost, "/api/provider/anthropic/v1/messages", bytes.NewReader([]byte(body)))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
c.Request = req
|
|
|
|
// Handler that should be called (not proxy)
|
|
handlerCalled := false
|
|
handler := func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
}
|
|
|
|
// Create fallback handler
|
|
fh := NewFallbackHandler(func() *httputil.ReverseProxy {
|
|
return nil // no proxy
|
|
})
|
|
|
|
// Execute
|
|
wrapped := fh.WrapHandler(handler)
|
|
wrapped(c)
|
|
|
|
// Assert: handler should be called directly (no mapping needed)
|
|
assert.True(t, handlerCalled, "handler should be called for local provider")
|
|
assert.Equal(t, 200, w.Code)
|
|
}
|
|
|
|
func TestFallbackHandler_WrapHandler_MappingApplied(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Register a mock provider for the target model
|
|
reg := registry.GetGlobalRegistry()
|
|
reg.RegisterClient("test-client", "anthropic", []*registry.ModelInfo{
|
|
{ID: "claude-opus-4-5-thinking"},
|
|
})
|
|
|
|
// Setup: model that needs mapping
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
body := `{"model": "claude-opus-4-5-20251101", "messages": [{"role": "user", "content": "hello"}]}`
|
|
req := httptest.NewRequest(http.MethodPost, "/api/provider/anthropic/v1/messages", bytes.NewReader([]byte(body)))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
c.Request = req
|
|
|
|
// Handler to capture rewritten body
|
|
var capturedBody []byte
|
|
handler := func(c *gin.Context) {
|
|
capturedBody, _ = io.ReadAll(c.Request.Body)
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
}
|
|
|
|
// Create fallback handler with mapper
|
|
mapper := NewModelMapper([]config.AmpModelMapping{
|
|
{From: "claude-opus-4-5-20251101", To: "claude-opus-4-5-thinking"},
|
|
})
|
|
|
|
fh := NewFallbackHandlerWithMapper(
|
|
func() *httputil.ReverseProxy { return nil },
|
|
mapper,
|
|
func() bool { return false },
|
|
)
|
|
|
|
// Execute
|
|
wrapped := fh.WrapHandler(handler)
|
|
wrapped(c)
|
|
|
|
// Assert: body should be rewritten
|
|
assert.Contains(t, string(capturedBody), "claude-opus-4-5-thinking")
|
|
|
|
// Assert: context should have mapped model
|
|
mappedModel, exists := c.Get(MappedModelContextKey)
|
|
assert.True(t, exists, "MappedModelContextKey should be set")
|
|
assert.NotEmpty(t, mappedModel)
|
|
}
|
|
|
|
func TestFallbackHandler_WrapHandler_ThinkingSuffixPreserved(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Register a mock provider for the target model
|
|
reg := registry.GetGlobalRegistry()
|
|
reg.RegisterClient("test-client-2", "anthropic", []*registry.ModelInfo{
|
|
{ID: "claude-opus-4-5-thinking"},
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
// Model with thinking suffix
|
|
body := `{"model": "claude-opus-4-5-20251101(xhigh)", "messages": []}`
|
|
req := httptest.NewRequest(http.MethodPost, "/api/provider/anthropic/v1/messages", bytes.NewReader([]byte(body)))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
c.Request = req
|
|
|
|
var capturedBody []byte
|
|
handler := func(c *gin.Context) {
|
|
capturedBody, _ = io.ReadAll(c.Request.Body)
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
}
|
|
|
|
mapper := NewModelMapper([]config.AmpModelMapping{
|
|
{From: "claude-opus-4-5-20251101", To: "claude-opus-4-5-thinking"},
|
|
})
|
|
|
|
fh := NewFallbackHandlerWithMapper(
|
|
func() *httputil.ReverseProxy { return nil },
|
|
mapper,
|
|
func() bool { return false },
|
|
)
|
|
|
|
wrapped := fh.WrapHandler(handler)
|
|
wrapped(c)
|
|
|
|
// Assert: thinking suffix should be preserved
|
|
assert.Contains(t, string(capturedBody), "(xhigh)")
|
|
}
|
|
|
|
func TestFallbackHandler_WrapHandler_NoProvider_NoMapping_ProxyEnabled(t *testing.T) {
|
|
// Skip: httptest.ResponseRecorder doesn't implement http.CloseNotifier
|
|
// which is required by httputil.ReverseProxy. This test requires a real
|
|
// HTTP server and client to properly test proxy behavior.
|
|
t.Skip("requires real HTTP server for proxy testing")
|
|
}
|