Files
Alexander 9150f466e5 test: add comprehensive test harness across all packages (156 tests)
Characterization tests capturing current behavior before refactoring.
Covers auth, config, logging, proxy, ratelimit, server, and telemetry
packages with race-safe concurrent access tests.
2026-04-15 10:40:43 +02:00

324 lines
11 KiB
Go

package proxy
import (
"encoding/hex"
"strings"
"testing"
)
func TestFingerprintSaltConstant(t *testing.T) {
if fingerprintSalt != "59cf53e54c78" {
t.Errorf("fingerprintSalt = %q, want %q", fingerprintSalt, "59cf53e54c78")
}
}
func TestComputeFingerprint_Deterministic(t *testing.T) {
a := computeFingerprint("hello world test message", "1.0.0")
b := computeFingerprint("hello world test message", "1.0.0")
if a != b {
t.Errorf("fingerprint not deterministic: %q != %q", a, b)
}
}
func TestComputeFingerprint_Length(t *testing.T) {
fp := computeFingerprint("some message here", "2.0.0")
if len(fp) != 3 {
t.Errorf("fingerprint length = %d, want 3", len(fp))
}
// Must be valid hex
if _, err := hex.DecodeString(fp + "0"); err != nil { // pad to even length for decode
// Check each char is hex individually
for _, c := range fp {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
t.Errorf("fingerprint %q contains non-hex char %c", fp, c)
}
}
}
}
func TestComputeFingerprint_DifferentVersions(t *testing.T) {
a := computeFingerprint("same message", "1.0.0")
b := computeFingerprint("same message", "2.0.0")
if a == b {
t.Errorf("different versions should (almost certainly) produce different fingerprints")
}
}
func TestComputeFingerprint_ShortMessage(t *testing.T) {
// "hi" has only 2 chars, indices [4,7,20] all out of range → chars = "000"
fp := computeFingerprint("hi", "1.0.0")
if len(fp) != 3 {
t.Errorf("short message fingerprint length = %d, want 3", len(fp))
}
}
func TestComputeFingerprint_EmptyMessage(t *testing.T) {
// Empty → all indices out of range → chars = "000"
fp := computeFingerprint("", "1.0.0")
if len(fp) != 3 {
t.Errorf("empty message fingerprint length = %d, want 3", len(fp))
}
// Empty and short message with same version should produce same fingerprint
// since both result in chars = "000"
fpShort := computeFingerprint("hi", "1.0.0")
if fp != fpShort {
t.Errorf("empty and 'hi' should produce same fingerprint (both use '000'), got %q vs %q", fp, fpShort)
}
}
func TestComputeFingerprint_Unicode(t *testing.T) {
// Emoji: 🎉 is U+1F389, encoded as UTF-16 surrogate pair [0xD83C, 0xDF89]
// So "abcd🎉fg" in UTF-16 is [a, b, c, d, 0xD83C, 0xDF89, f, g] = 8 uint16 values
// indices [4,7,20]: runes[4]=0xD83C, runes[7]='g', runes[20]=out of range
fp := computeFingerprint("abcd🎉fg", "1.0.0")
if len(fp) != 3 {
t.Errorf("unicode fingerprint length = %d, want 3", len(fp))
}
}
func TestComputeFingerprint_CharExtraction(t *testing.T) {
// "Hello, World!" UTF-16: [H,e,l,l,o,',', ,W,o,r,l,d,!]
// indices [4,7,20]: runes[4]='o', runes[7]='W', runes[20]=out of range → "0"
// So chars should be "oW0"
// Verify by comparing to a message where we know the expected extracted chars
// Two messages that extract same chars at indices should produce same fingerprint
// "xxxxoxxWxxxxxxxxxxxx" → index 4='o', 7='W', 20=out of range → "oW0" (20 chars, index 20 out of range)
fp1 := computeFingerprint("Hello, World!", "1.0.0")
fp2 := computeFingerprint("xxxxoxxWxxxxxxxxxxxx", "1.0.0")
if fp1 != fp2 {
t.Errorf("messages with same chars at indices [4,7,20] should produce same fingerprint, got %q vs %q", fp1, fp2)
}
}
func TestComputeFingerprint_IndexBoundary(t *testing.T) {
// Message with exactly 21 chars → index 20 is valid
msg21 := "abcdefghijklmnopqrstu" // 21 chars
fp21 := computeFingerprint(msg21, "1.0.0")
// Message with exactly 20 chars → index 20 is out of range → "0"
msg20 := "abcdefghijklmnopqrst" // 20 chars
fp20 := computeFingerprint(msg20, "1.0.0")
// They should differ because index 20 produces different chars
if fp21 == fp20 {
t.Errorf("boundary test: 21-char and 20-char messages should differ at index 20")
}
}
func TestExtractFirstUserMessage(t *testing.T) {
tests := []struct {
name string
body string
expected string
}{
{
name: "simple string content",
body: `{"messages":[{"role":"user","content":"hello world"}]}`,
expected: "hello world",
},
{
name: "array content with text block",
body: `{"messages":[{"role":"user","content":[{"type":"text","text":"from array"}]}]}`,
expected: "from array",
},
{
name: "no user messages",
body: `{"messages":[{"role":"assistant","content":"I am assistant"}]}`,
expected: "",
},
{
name: "assistant only messages",
body: `{"messages":[{"role":"assistant","content":"a1"},{"role":"assistant","content":"a2"}]}`,
expected: "",
},
{
name: "user with non-text block first then text",
body: `{"messages":[{"role":"user","content":[{"type":"image","source":"x"},{"type":"text","text":"the text"}]}]}`,
expected: "the text",
},
{
name: "user with only non-text blocks",
body: `{"messages":[{"role":"user","content":[{"type":"image","source":"x"}]}]}`,
expected: "",
},
{
name: "no messages field",
body: `{"model":"claude-sonnet-4-6"}`,
expected: "",
},
{
name: "messages not array",
body: `{"messages":"not array"}`,
expected: "",
},
{
name: "empty messages array",
body: `{"messages":[]}`,
expected: "",
},
{
name: "first user message used even if multiple exist",
body: `{"messages":[{"role":"user","content":"first"},{"role":"user","content":"second"}]}`,
expected: "first",
},
{
name: "assistant before user",
body: `{"messages":[{"role":"assistant","content":"assistant msg"},{"role":"user","content":"user msg"}]}`,
expected: "user msg",
},
{
name: "user with array content - first text block used",
body: `{"messages":[{"role":"user","content":[{"type":"text","text":"first text"},{"type":"text","text":"second text"}]}]}`,
expected: "first text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractFirstUserMessage([]byte(tt.body))
if result != tt.expected {
t.Errorf("got %q, want %q", result, tt.expected)
}
})
}
}
func TestExtractFirstUserMessage_BreaksAfterFirstUser(t *testing.T) {
// The function should break after finding the first user message,
// even if it didn't extract text (e.g. user with only image blocks)
body := `{"messages":[{"role":"user","content":[{"type":"image","source":"x"}]},{"role":"user","content":"second user"}]}`
result := extractFirstUserMessage([]byte(body))
// First user has no text blocks, function breaks, returns ""
if result != "" {
t.Errorf("should return empty when first user has no text, got %q", result)
}
}
func TestBuildBillingHeader(t *testing.T) {
body := []byte(`{"messages":[{"role":"user","content":"test message"}]}`)
version := "1.2.3"
header := buildBillingHeader(body, version)
// Check format
if !strings.HasPrefix(header, "x-anthropic-billing-header: cc_version=1.2.3.") {
t.Errorf("header should start with 'x-anthropic-billing-header: cc_version=1.2.3.', got %q", header)
}
if !strings.Contains(header, "; cc_entrypoint=cli; cch=00000;") {
t.Errorf("header should contain '; cc_entrypoint=cli; cch=00000;', got %q", header)
}
// Verify the fingerprint part is 3 chars
// Format: "x-anthropic-billing-header: cc_version=1.2.3.XXX; cc_entrypoint=cli; cch=00000;"
parts := strings.Split(header, "cc_version=")
if len(parts) != 2 {
t.Fatalf("unexpected header format: %q", header)
}
versionFP := strings.Split(parts[1], ";")[0]
if !strings.HasPrefix(versionFP, "1.2.3.") {
t.Errorf("version+fingerprint should start with '1.2.3.', got %q", versionFP)
}
fp := strings.TrimPrefix(versionFP, "1.2.3.")
if len(fp) != 3 {
t.Errorf("fingerprint should be 3 chars, got %q (len %d)", fp, len(fp))
}
}
func TestBuildBillingHeader_EmptyMessages(t *testing.T) {
body := []byte(`{"messages":[]}`)
version := "1.0.0"
header := buildBillingHeader(body, version)
if !strings.HasPrefix(header, "x-anthropic-billing-header: cc_version=") {
t.Errorf("header format wrong: %q", header)
}
}
func TestInjectBillingHeader_NoExistingSystem(t *testing.T) {
body := []byte(`{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}]}`)
result := injectBillingHeader(body, "1.0.0")
resultStr := string(result)
// Should have system field now
if !strings.Contains(resultStr, `"system"`) {
t.Errorf("should inject system field, got %s", resultStr)
}
// System should be an array with one billing block
if !strings.Contains(resultStr, "x-anthropic-billing-header") {
t.Errorf("should contain billing header text, got %s", resultStr)
}
if !strings.Contains(resultStr, `"type":"text"`) {
t.Errorf("billing block should have type text, got %s", resultStr)
}
}
func TestInjectBillingHeader_ExistingSystemArray(t *testing.T) {
body := []byte(`{"system":[{"type":"text","text":"existing prompt"}],"messages":[{"role":"user","content":"hi"}]}`)
result := injectBillingHeader(body, "1.0.0")
resultStr := string(result)
// Should contain both billing header and existing prompt
if !strings.Contains(resultStr, "x-anthropic-billing-header") {
t.Errorf("should contain billing header, got %s", resultStr)
}
if !strings.Contains(resultStr, "existing prompt") {
t.Errorf("should preserve existing prompt, got %s", resultStr)
}
// Billing block should be FIRST (prepended)
billingIdx := strings.Index(resultStr, "x-anthropic-billing-header")
existingIdx := strings.Index(resultStr, "existing prompt")
if billingIdx > existingIdx {
t.Errorf("billing block should come before existing prompt")
}
}
func TestInjectBillingHeader_ExistingSystemString(t *testing.T) {
body := []byte(`{"system":"You are a helpful assistant","messages":[{"role":"user","content":"hi"}]}`)
result := injectBillingHeader(body, "1.0.0")
resultStr := string(result)
// Should convert to array with billing block first, then original text
if !strings.Contains(resultStr, "x-anthropic-billing-header") {
t.Errorf("should contain billing header, got %s", resultStr)
}
if !strings.Contains(resultStr, "You are a helpful assistant") {
t.Errorf("should preserve original system string, got %s", resultStr)
}
// Billing should come first
billingIdx := strings.Index(resultStr, "x-anthropic-billing-header")
origIdx := strings.Index(resultStr, "You are a helpful assistant")
if billingIdx > origIdx {
t.Errorf("billing block should come before original system text")
}
}
func TestInjectBillingHeader_PreservesOtherFields(t *testing.T) {
body := []byte(`{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}],"max_tokens":1024}`)
result := injectBillingHeader(body, "1.0.0")
resultStr := string(result)
if !strings.Contains(resultStr, `"model":"claude-sonnet-4-6"`) {
t.Errorf("should preserve model field, got %s", resultStr)
}
if !strings.Contains(resultStr, `"max_tokens":1024`) {
t.Errorf("should preserve max_tokens field, got %s", resultStr)
}
}
func TestInjectBillingHeader_BillingBlockFormat(t *testing.T) {
body := []byte(`{"messages":[{"role":"user","content":"test"}]}`)
result := injectBillingHeader(body, "2.5.0")
resultStr := string(result)
// Verify the billing block contains the correct version
if !strings.Contains(resultStr, "cc_version=2.5.0.") {
t.Errorf("billing block should contain cc_version=2.5.0., got %s", resultStr)
}
if !strings.Contains(resultStr, "cc_entrypoint=cli") {
t.Errorf("billing block should contain cc_entrypoint=cli, got %s", resultStr)
}
if !strings.Contains(resultStr, "cch=00000") {
t.Errorf("billing block should contain cch=00000, got %s", resultStr)
}
}