first commit

This commit is contained in:
Beyhan Ogur
2026-05-11 15:08:50 +03:00
commit a408821410
47 changed files with 4670 additions and 0 deletions
+650
View File
@@ -0,0 +1,650 @@
package tests
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gofiber/fiber/v3"
"optoant/config"
"optoant/handlers"
"optoant/internal/transform"
)
// ──────────────────────────────────────────────
// Transform Unit Tests — AnthropicToBifrost
// ──────────────────────────────────────────────
func TestAnthropicToBifrost_Basic(t *testing.T) {
antBody := `{"model":"claude-3-5-sonnet","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}`
out, err := transform.AnthropicToBifrost([]byte(antBody))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var bfReq struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"messages"`
}
if err := json.Unmarshal(out, &bfReq); err != nil {
t.Fatalf("invalid bifrost json: %v", err)
}
if bfReq.Model != "Anthropic/claude-3-5-sonnet" {
t.Errorf("expected Anthropic/claude-3-5-sonnet, got %s", bfReq.Model)
}
if bfReq.MaxTokens != 1024 {
t.Errorf("expected 1024, got %d", bfReq.MaxTokens)
}
if len(bfReq.Messages) != 1 || bfReq.Messages[0].Role != "user" {
t.Errorf("unexpected messages: %+v", bfReq.Messages)
}
}
func TestAnthropicToBifrost_WithSystem(t *testing.T) {
antBody := `{"model":"claude-3","system":"You are helpful.","messages":[{"role":"user","content":"Hi"}]}`
out, err := transform.AnthropicToBifrost([]byte(antBody))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var bfReq struct {
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"messages"`
}
json.Unmarshal(out, &bfReq)
if len(bfReq.Messages) != 2 {
t.Fatalf("expected 2 messages (system+user), got %d", len(bfReq.Messages))
}
if bfReq.Messages[0].Role != "system" || bfReq.Messages[0].Content != "You are helpful." {
t.Errorf("system message missing or wrong: %+v", bfReq.Messages[0])
}
if bfReq.Messages[1].Role != "user" || bfReq.Messages[1].Content != "Hi" {
t.Errorf("user message wrong: %+v", bfReq.Messages[1])
}
}
func TestAnthropicToBifrost_ModelPrefix(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"claude-3-5-sonnet", "Anthropic/claude-3-5-sonnet"},
{"deepseek-v4-flash", "DeepSeek/deepseek-v4-flash"},
{"gpt-4", "OpenAI/gpt-4"},
{"gemini-pro", "Google/gemini-pro"},
{"unknown-model", "DeepSeek/unknown-model"},
{"Anthropic/claude-3", "Anthropic/claude-3"},
{"DeepSeek/deepseek-v4", "DeepSeek/deepseek-v4"},
}
for _, tc := range tests {
body := `{"model":"` + tc.input + `","messages":[{"role":"user","content":"Hi"}]}`
out, err := transform.AnthropicToBifrost([]byte(body))
if err != nil {
t.Fatalf("error for model %s: %v", tc.input, err)
}
var bfReq struct {
Model string `json:"model"`
}
json.Unmarshal(out, &bfReq)
if bfReq.Model != tc.expected {
t.Errorf("model %s: expected %s, got %s", tc.input, tc.expected, bfReq.Model)
}
}
}
func TestAnthropicToBifrost_StreamPassthrough(t *testing.T) {
body := `{"model":"claude-3","stream":true,"messages":[{"role":"user","content":"Hi"}]}`
out, err := transform.AnthropicToBifrost([]byte(body))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var bfReq struct {
Stream bool `json:"stream"`
}
json.Unmarshal(out, &bfReq)
if !bfReq.Stream {
t.Error("expected stream=true to be preserved")
}
}
func TestAnthropicToBifrost_InvalidJSON(t *testing.T) {
_, err := transform.AnthropicToBifrost([]byte(`not json`))
if err == nil {
t.Fatal("expected error for invalid json")
}
}
// ──────────────────────────────────────────────
// Transform Unit Tests — BifrostToAnthropic
// ──────────────────────────────────────────────
func TestBifrostToAnthropic_Basic(t *testing.T) {
bfBody := `{"id":"chatcmpl-abc","object":"chat.completion","created":1700000000,"model":"Anthropic/claude-3","choices":[{"message":{"role":"assistant","content":"Hello!"},"finish_reason":"stop","index":0}]}`
out, err := transform.BifrostToAnthropic([]byte(bfBody))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var antResp struct {
ID string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"content"`
Model string `json:"model"`
StopReason string `json:"stop_reason"`
}
json.Unmarshal(out, &antResp)
if antResp.Type != "message" {
t.Errorf("expected type=message, got %s", antResp.Type)
}
if antResp.Role != "assistant" {
t.Errorf("expected role=assistant, got %s", antResp.Role)
}
if len(antResp.Content) != 1 || antResp.Content[0].Text != "Hello!" {
t.Errorf("unexpected content: %+v", antResp.Content)
}
if antResp.StopReason != "stop" {
t.Errorf("expected stop_reason=stop, got %s", antResp.StopReason)
}
if antResp.ID != "chatcmpl-abc" {
t.Errorf("expected id to pass through, got %s", antResp.ID)
}
}
func TestBifrostToAnthropic_EmptyFinishReason(t *testing.T) {
bfBody := `{"id":"x","object":"chat.completion","created":1,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"Done"},"finish_reason":"","index":0}]}`
out, err := transform.BifrostToAnthropic([]byte(bfBody))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var antResp struct {
StopReason string `json:"stop_reason"`
}
json.Unmarshal(out, &antResp)
if antResp.StopReason != "end_turn" {
t.Errorf("expected end_turn for empty finish_reason, got %s", antResp.StopReason)
}
}
func TestBifrostToAnthropic_MultipleChoices(t *testing.T) {
bfBody := `{"id":"x","object":"chat.completion","created":1,"model":"DeepSeek/deepseek-v4","choices":[
{"message":{"role":"assistant","content":"First"},"finish_reason":"stop","index":0},
{"message":{"role":"assistant","content":"Second"},"finish_reason":"stop","index":1}
]}`
out, err := transform.BifrostToAnthropic([]byte(bfBody))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var antResp struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
}
json.Unmarshal(out, &antResp)
if len(antResp.Content) != 2 {
t.Fatalf("expected 2 content blocks, got %d", len(antResp.Content))
}
if antResp.Content[0].Text != "First" || antResp.Content[1].Text != "Second" {
t.Errorf("unexpected content order: %+v", antResp.Content)
}
}
func TestBifrostToAnthropic_InvalidJSON(t *testing.T) {
_, err := transform.BifrostToAnthropic([]byte(`not json`))
if err == nil {
t.Fatal("expected error for invalid json")
}
}
// ──────────────────────────────────────────────
// AnthropicHandler Integration Tests
// ──────────────────────────────────────────────
func newAnthropicTestApp(cfg *config.Config) *fiber.App {
app := fiber.New()
app.All("/anthropic", handlers.AnthropicHandler(cfg, nil))
app.All("/anthropic/*", handlers.AnthropicHandler(cfg, nil))
return app
}
func TestAnthropicProxy_Success(t *testing.T) {
upstream := mockUpstream(t, http.StatusOK, `{"id":"chatcmpl-abc","object":"chat.completion","created":1700000000,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"Hello from Bifrost!"},"finish_reason":"stop","index":0}]}`)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
payload := `{"model":"claude-3-5-sonnet","max_tokens":1024,"messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
t.Fatalf("decode response: %v", err)
}
if result["type"] != "message" {
t.Errorf("expected type=message, got %v", result["type"])
}
if result["role"] != "assistant" {
t.Errorf("expected role=assistant, got %v", result["role"])
}
content, ok := result["content"].([]interface{})
if !ok || len(content) == 0 {
t.Fatalf("expected content array, got: %v", result["content"])
}
firstBlock := content[0].(map[string]interface{})
if firstBlock["type"] != "text" {
t.Errorf("expected text content type, got %v", firstBlock["type"])
}
if firstBlock["text"] != "Hello from Bifrost!" {
t.Errorf("expected Hello from Bifrost!, got %v", firstBlock["text"])
}
}
func TestAnthropicProxy_InvalidFormatPassthrough(t *testing.T) {
upstream := mockUpstream(t, http.StatusOK, `{"raw":"response"}`)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
payload := `this is not valid json`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200 (passthrough), got %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), "raw") {
t.Errorf("expected raw response body, got: %s", string(body))
}
}
func TestAnthropicProxy_UpstreamError(t *testing.T) {
cfg := &config.Config{
OpenAIBackend: "http://127.0.0.1:19999",
RequestTimeoutSeconds: 2,
}
app := newAnthropicTestApp(cfg)
payload := `{"model":"claude-3","messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadGateway {
t.Errorf("expected 502, got %d", resp.StatusCode)
}
}
func TestAnthropicProxy_ModelsList(t *testing.T) {
cfg := &config.Config{OpenAIBackend: "http://127.0.0.1:19999", RequestTimeoutSeconds: 1}
app := newAnthropicTestApp(cfg)
req := httptest.NewRequest(http.MethodGet, "/anthropic/v1/models", nil)
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
data, ok := result["data"].([]interface{})
if !ok || len(data) == 0 {
t.Fatalf("expected data array, got: %v", result["data"])
}
first := data[0].(map[string]interface{})
if first["id"] != "deepseek-v4-flash" {
t.Errorf("expected deepseek-v4-flash as first model, got %v", first["id"])
}
}
func TestAnthropicProxy_EmptyBody(t *testing.T) {
cfg := &config.Config{OpenAIBackend: "http://127.0.0.1:19999", RequestTimeoutSeconds: 1}
app := newAnthropicTestApp(cfg)
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", nil)
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if result["type"] != "message" {
t.Errorf("expected type=message, got %v", result["type"])
}
}
func TestAnthropicProxy_HEAD(t *testing.T) {
cfg := &config.Config{OpenAIBackend: "http://127.0.0.1:19999", RequestTimeoutSeconds: 1}
app := newAnthropicTestApp(cfg)
req := httptest.NewRequest(http.MethodHead, "/anthropic/v1/messages", nil)
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNotFound {
t.Errorf("expected 404 for HEAD, got %d", resp.StatusCode)
}
}
func TestAnthropicProxy_xApiKeyConversion(t *testing.T) {
upstream := mockUpstream(t, http.StatusOK, `{"id":"chatcmpl-test","object":"chat.completion","created":1,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"Auth ok"},"finish_reason":"stop","index":0}]}`)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
payload := `{"model":"claude-3","messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", "my-secret-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
content := result["content"].([]interface{})
first := content[0].(map[string]interface{})
if first["text"] != "Auth ok" {
t.Errorf("expected response with text, got %v", first["text"])
}
}
// ──────────────────────────────────────────────
// Claude Code Simulation Tests
// ──────────────────────────────────────────────
// TestClaudeCode_ExactRequestFormat simulates what Claude Code CLI sends:
// - POST /v1/messages (via /anthropic/v1/messages)
// - x-api-key header (NOT Authorization)
// - anthropic-version header
// - Exact Anthropic Messages API format with model: "claude-sonnet-4-20250514"
func TestClaudeCode_ExactRequestFormat(t *testing.T) {
upstream := mockUpstream(t, http.StatusOK, `{"id":"chatcmpl-cc","object":"chat.completion","created":1700000001,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"Hello from Bifrost! I am connected through the LLM Gateway."},"finish_reason":"stop","index":0}]}`)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
// Exact format Claude Code sends:
// https://docs.anthropic.com/en/api/messages
payload := `{"model":"claude-sonnet-4-20250514","max_tokens":8192,"stream":false,"messages":[{"role":"user","content":"Hello, can you help me with a coding task?"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", "sk-ant-my-claude-code-key")
req.Header.Set("Anthropic-Version", "2023-06-01")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
t.Fatalf("decode response: %v", err)
}
// Verify Anthropic Messages API response shape
if result["type"] != "message" {
t.Errorf("Claude Code expects type=message, got %v", result["type"])
}
if result["role"] != "assistant" {
t.Errorf("Claude Code expects role=assistant, got %v", result["role"])
}
content, ok := result["content"].([]interface{})
if !ok || len(content) == 0 {
t.Fatalf("Claude Code expects content array, got: %v", result["content"])
}
firstBlock := content[0].(map[string]interface{})
if firstBlock["type"] != "text" {
t.Errorf("expected text block, got %v", firstBlock["type"])
}
if !strings.Contains(firstBlock["text"].(string), "Hello from Bifrost!") {
t.Errorf("expected Bifrost greeting, got: %v", firstBlock["text"])
}
// Verify model was auto-prefixed correctly
upstreamReqBody := upstreamResponseBody(t, req, app)
if strings.Contains(upstreamReqBody, `"model":"Anthropic/claude-sonnet-4-20250514"`) {
t.Logf("✓ Model auto-prefixed to Anthropic/claude-sonnet-4-20250514")
}
}
// upstreamResponseBody captures what the mock upstream received.
// Only used in TestClaudeCode_ExactRequestFormat for verifying the transformed request.
func upstreamResponseBody(t *testing.T, originalReq *http.Request, app *fiber.App) string {
t.Helper()
resp, err := app.Test(originalReq, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body)
}
func TestClaudeCode_MaxTokensDefault(t *testing.T) {
// Claude Code doesn't always send max_tokens (SDK has defaults)
upstream := mockUpstream(t, http.StatusOK, `{"id":"chatcmpl-cc2","object":"chat.completion","created":1700000002,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"Response without max_tokens"},"finish_reason":"end_turn","index":0}]}`)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
// No max_tokens - Claude Code doesn't always send it
payload := `{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", "sk-ant-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
}
// ──────────────────────────────────────────────
// Default Model Injection Tests
// ──────────────────────────────────────────────
func TestAnthropicProxy_DefaultModelInjection(t *testing.T) {
var capturedBody string
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
capturedBody = string(body)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, `{"id":"x","object":"chat.completion","created":1,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"ok"},"finish_reason":"stop","index":0}]}`)
}))
t.Cleanup(upstream.Close)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
OpenAIModel: "deepseek/deepseek-v4-pro",
}
app := newAnthropicTestApp(cfg)
// No model in payload — should be injected
payload := `{"max_tokens":256,"messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
// Verify the injected model was passed through as-is (already has "/")
var bfReq struct {
Model string `json:"model"`
}
json.Unmarshal([]byte(capturedBody), &bfReq)
expected := "deepseek/deepseek-v4-pro"
if bfReq.Model != expected {
t.Errorf("expected model=%q (injected as-is, no prefix needed), got %q", expected, bfReq.Model)
}
}
func TestAnthropicProxy_DefaultModelInjection_NopWhenModelExists(t *testing.T) {
var capturedBody string
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
capturedBody = string(body)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, `{"id":"x","object":"chat.completion","created":1,"model":"DeepSeek/deepseek-v4","choices":[{"message":{"role":"assistant","content":"ok"},"finish_reason":"stop","index":0}]}`)
}))
t.Cleanup(upstream.Close)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
OpenAIModel: "deepseek/deepseek-v4-pro",
}
app := newAnthropicTestApp(cfg)
// Model already set — should NOT be overridden
payload := `{"model":"claude-3","max_tokens":256,"messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var bfReq struct {
Model string `json:"model"`
}
json.Unmarshal([]byte(capturedBody), &bfReq)
expected := "Anthropic/claude-3"
if bfReq.Model != expected {
t.Errorf("expected model=%q (preserved + prefixed), got %q", expected, bfReq.Model)
}
}
func TestAnthropicProxy_TransformErrorFallsbackToPassthrough(t *testing.T) {
bfBody := `this is not a valid JSON response either`
upstream := mockUpstream(t, http.StatusOK, bfBody)
cfg := &config.Config{
OpenAIBackend: upstream.URL,
RequestTimeoutSeconds: 5,
}
app := newAnthropicTestApp(cfg)
payload := `{"model":"claude-3","messages":[{"role":"user","content":"Hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-key")
resp, err := app.Test(req, fiber.TestConfig{Timeout: -1})
if err != nil {
t.Fatalf("app.Test error: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), "not a valid JSON") {
t.Errorf("expected raw fallback body, got: %s", string(body))
}
}