| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- //go:build unit
- package handler
- import (
- "strings"
- "testing"
- "github.com/stretchr/testify/require"
- "go.uber.org/zap"
- "go.uber.org/zap/zaptest/observer"
- )
- func newObservedLogger(t *testing.T) (*zap.Logger, *observer.ObservedLogs) {
- t.Helper()
- core, logs := observer.New(zap.WarnLevel)
- return zap.New(core), logs
- }
- func loggedFields(t *testing.T, logs *observer.ObservedLogs) map[string]any {
- t.Helper()
- entries := logs.All()
- require.Len(t, entries, 1)
- fields := map[string]any{}
- for _, f := range entries[0].Context {
- switch f.Key {
- case "body_len":
- fields[f.Key] = int(f.Integer)
- case "error":
- fields[f.Key] = f.Interface.(error).Error()
- default:
- fields[f.Key] = f.String
- }
- }
- return fields
- }
- func TestLogRequestBodyParseFailure_DerivesErrorWhenNil(t *testing.T) {
- log, logs := newObservedLogger(t)
- body := []byte(`{"model": bad}`)
- logRequestBodyParseFailure(log, body, nil)
- fields := loggedFields(t, logs)
- require.Equal(t, len(body), fields["body_len"])
- require.Contains(t, fields["error"], "invalid json")
- require.Contains(t, fields["error"], "offset=11")
- }
- func TestLogRequestBodyParseFailure_ShortBodyHasNoTail(t *testing.T) {
- log, logs := newObservedLogger(t)
- body := []byte(`{"broken":`)
- logRequestBodyParseFailure(log, body, nil)
- fields := loggedFields(t, logs)
- require.Contains(t, fields, "body_head")
- require.NotContains(t, fields, "body_tail")
- require.Contains(t, fields["body_head"].(string), `{\"broken\":`)
- }
- func TestLogRequestBodyParseFailure_LargeBodyBoundedSnippets(t *testing.T) {
- log, logs := newObservedLogger(t)
- // ~1MB body: head must show the structural prefix, tail the trailing bytes,
- // and neither snippet may exceed the configured bound (plus quoting overhead).
- body := []byte(`{"model":"claude-sonnet-4-6","big":"` + strings.Repeat("A", 1<<20) + `"`)
- logRequestBodyParseFailure(log, body, nil)
- fields := loggedFields(t, logs)
- require.Equal(t, len(body), fields["body_len"])
- head := fields["body_head"].(string)
- tail := fields["body_tail"].(string)
- require.Contains(t, head, "claude-sonnet-4-6")
- require.Contains(t, tail, "AAA")
- require.NotContains(t, tail, "claude-sonnet-4-6")
- // strconv.Quote adds surrounding quotes and escapes; 4x is a generous cap.
- require.LessOrEqual(t, len(head), parseFailureSnippetLen*4)
- require.LessOrEqual(t, len(tail), parseFailureSnippetLen*4)
- }
- func TestLogRequestBodyParseFailure_EscapesControlCharacters(t *testing.T) {
- log, logs := newObservedLogger(t)
- body := []byte("{\"model\":\x01\n\"x\"}")
- logRequestBodyParseFailure(log, body, nil)
- fields := loggedFields(t, logs)
- head := fields["body_head"].(string)
- require.NotContains(t, head, "\n")
- require.NotContains(t, head, "\x01")
- require.Contains(t, head, `\n`)
- require.Contains(t, head, `\x01`)
- }
- func TestLogRequestBodyParseFailure_NilLoggerNoPanic(t *testing.T) {
- require.NotPanics(t, func() {
- logRequestBodyParseFailure(nil, []byte(`{`), nil)
- })
- }
|