mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-12 08:29:06 +02:00
The per-target middleware chain (slots, dispatcher, mutation gate, metadata merger), body capture, access-log terminal sink, and the proxy wiring that builds + runs chains for synthesized agent-network services.
87 lines
3.2 KiB
Go
87 lines
3.2 KiB
Go
package bodytap
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// makeBigAnthropicBody builds a request body shaped like Claude Code's:
|
|
// a multi-MB "messages" array with the routing fields (model, stream)
|
|
// placed AFTER it, which is the ordering that defeats a prefix-only
|
|
// capture.
|
|
func makeBigAnthropicBody(t *testing.T, model string, stream bool, messagesBytes int) string {
|
|
t.Helper()
|
|
filler := strings.Repeat("x", messagesBytes)
|
|
return fmt.Sprintf(
|
|
`{"max_tokens":64000,"messages":[{"role":"user","content":%q}],"model":%q,"stream":%t}`,
|
|
filler, model, stream,
|
|
)
|
|
}
|
|
|
|
func TestScanRoutingFields_ModelAfterLargeMessages(t *testing.T) {
|
|
body := makeBigAnthropicBody(t, "claude-opus-4-8", true, 3<<20) // 3 MiB messages
|
|
req := httptest.NewRequest("POST", "https://x/v1/messages", strings.NewReader(body))
|
|
|
|
model, stream, ok := ScanRoutingFields(req, MaxRoutingScanBytes)
|
|
require.True(t, ok, "model must be recovered even when it follows a multi-MB messages array")
|
|
assert.Equal(t, "claude-opus-4-8", model, "model field must be extracted")
|
|
assert.True(t, stream, "stream field must be extracted")
|
|
|
|
// Body must be fully restored for the upstream.
|
|
got, err := io.ReadAll(req.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, body, string(got), "the full request body must be replayed to upstream after scanning")
|
|
}
|
|
|
|
func TestScanRoutingFields_SmallBody(t *testing.T) {
|
|
body := `{"model":"claude-opus-4-8","stream":false,"messages":[]}`
|
|
req := httptest.NewRequest("POST", "https://x/v1/messages", strings.NewReader(body))
|
|
|
|
model, stream, ok := ScanRoutingFields(req, MaxRoutingScanBytes)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "claude-opus-4-8", model)
|
|
assert.False(t, stream)
|
|
|
|
got, _ := io.ReadAll(req.Body)
|
|
assert.Equal(t, body, string(got), "small bodies must also be restored intact")
|
|
}
|
|
|
|
func TestScanRoutingFields_NoModel(t *testing.T) {
|
|
body := `{"stream":true,"messages":[]}`
|
|
req := httptest.NewRequest("POST", "https://x/v1/messages", strings.NewReader(body))
|
|
|
|
_, _, ok := ScanRoutingFields(req, MaxRoutingScanBytes)
|
|
assert.False(t, ok, "ok must be false when no model field is present")
|
|
|
|
got, _ := io.ReadAll(req.Body)
|
|
assert.Equal(t, body, string(got), "body must be restored even when model is absent")
|
|
}
|
|
|
|
func TestScanRoutingFields_NotJSON(t *testing.T) {
|
|
body := "this is not json at all"
|
|
req := httptest.NewRequest("POST", "https://x/v1/messages", strings.NewReader(body))
|
|
|
|
_, _, ok := ScanRoutingFields(req, MaxRoutingScanBytes)
|
|
assert.False(t, ok, "ok must be false for a non-JSON body")
|
|
}
|
|
|
|
func TestScanRoutingFields_ModelBeyondScanCeiling(t *testing.T) {
|
|
// model sits after 4 MiB of messages but the scan ceiling is 1 MiB:
|
|
// model can't be recovered, yet the full body must still replay.
|
|
body := makeBigAnthropicBody(t, "claude-opus-4-8", true, 4<<20)
|
|
req := httptest.NewRequest("POST", "https://x/v1/messages", strings.NewReader(body))
|
|
|
|
_, _, ok := ScanRoutingFields(req, 1<<20)
|
|
assert.False(t, ok, "model beyond the scan ceiling is not recoverable")
|
|
|
|
got, err := io.ReadAll(req.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, body, string(got), "the full body must still replay to upstream even when the scan gives up")
|
|
}
|