Files
gpt-load/internal/gateway/request_log_test.go
T
2026-08-12 08:37:53 +08:00

2939 lines
97 KiB
Go

package gateway
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"sync"
"testing"
"time"
"unicode/utf8"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"gpt-load/internal/channel"
"gpt-load/internal/dialect"
"gpt-load/internal/execution"
"gpt-load/internal/health"
"gpt-load/internal/httplifecycle"
"gpt-load/internal/platform/encryption"
"gpt-load/internal/platform/redact"
"gpt-load/internal/pricing"
"gpt-load/internal/protocol"
"gpt-load/internal/ratelimit"
"gpt-load/internal/reasoning"
"gpt-load/internal/scheduler"
"gpt-load/internal/state"
"gpt-load/internal/telemetry"
"gpt-load/internal/usage"
)
func requestLogSelection(groupID, credentialID uint, name string) scheduler.Selection {
return scheduler.Selection{
GroupID: groupID, ChannelID: channel.OpenAI, CredentialID: credentialID,
RouteMode: channel.RouteNative,
Group: state.GroupView{ID: groupID, Name: name, ChannelID: channel.OpenAI},
}
}
func TestRequestRecorderCapturesFirstResponseAfterDownstreamCommit(t *testing.T) {
startedAt := time.Date(2026, time.August, 5, 8, 0, 0, 0, time.UTC)
sink := &recordingRequestLogSink{}
times := []time.Time{startedAt.Add(340 * time.Millisecond), startedAt.Add(2 * time.Second)}
now := func() time.Time {
value := times[0]
times = times[1:]
return value
}
recorder := newRequestRecorder(
sink,
"00000000-0000-4000-8000-000000000001",
startedAt,
1,
protocol.OpenAICompletions,
now,
)
recorder.setStream(true)
recorder.recordFirstResponse()
recorder.recordFirstResponse()
recorder.emit()
events := sink.snapshot()
if len(events) != 1 || !events[0].Stream || events[0].FirstResponseMs == nil ||
*events[0].FirstResponseMs != 340 || events[0].DurationMs != 2_000 {
t.Fatalf("captured timing = %#v", events)
}
}
func withoutPricingReceipt(value telemetry.PricingObservation) telemetry.PricingObservation {
value.ReceiptJSON = ""
return value
}
const fixedRequestID = "11111111-2222-4333-8444-555555555555"
type rpmLimiterCall struct {
accessKeyID uint
limit int64
}
type recordingAccessKeyRPMLimiter struct {
mu sync.Mutex
calls []rpmLimiterCall
decisions []ratelimit.LimitDecision
onAllow func(rpmLimiterCall)
}
func (limiter *recordingAccessKeyRPMLimiter) Allow(
accessKeyID uint,
limit int64,
) ratelimit.LimitDecision {
limiter.mu.Lock()
call := rpmLimiterCall{accessKeyID: accessKeyID, limit: limit}
limiter.calls = append(limiter.calls, call)
index := len(limiter.calls) - 1
decision := ratelimit.LimitDecision{Allowed: true}
if len(limiter.decisions) > 0 {
decision = limiter.decisions[min(index, len(limiter.decisions)-1)]
}
onAllow := limiter.onAllow
limiter.mu.Unlock()
if onAllow != nil {
onAllow(call)
}
return decision
}
func (limiter *recordingAccessKeyRPMLimiter) snapshot() []rpmLimiterCall {
limiter.mu.Lock()
defer limiter.mu.Unlock()
return append([]rpmLimiterCall(nil), limiter.calls...)
}
type recordingRequestLogSink struct {
mu sync.Mutex
events []telemetry.RequestEvent
}
type mutableGatewayPriceTableProvider struct {
mu sync.Mutex
table *pricing.Table
loads int
}
func (provider *mutableGatewayPriceTableProvider) Load() *pricing.Table {
provider.mu.Lock()
defer provider.mu.Unlock()
provider.loads++
return provider.table
}
func (provider *mutableGatewayPriceTableProvider) Publish(table *pricing.Table) {
provider.mu.Lock()
defer provider.mu.Unlock()
provider.table = table
}
func (provider *mutableGatewayPriceTableProvider) LoadCount() int {
provider.mu.Lock()
defer provider.mu.Unlock()
return provider.loads
}
type usageObservingStreamRetryForwarder struct {
observed []usage.Result
}
func (*usageObservingStreamRetryForwarder) Forward(context.Context, ForwardInput) UpstreamResult {
return UpstreamResult{Err: errors.New("unexpected non-streaming forward")}
}
func (forwarder *usageObservingStreamRetryForwarder) ForwardStream(
_ context.Context,
input ForwardInput,
_ http.ResponseWriter,
) UpstreamResult {
payloads := [][]byte{
[]byte(`{"choices":[],"usage":{"prompt_tokens":100,"completion_tokens":9,"prompt_tokens_details":{"cached_tokens":20}}}`),
[]byte(`{"choices":[],"usage":{"prompt_tokens":100,"completion_tokens":30,"prompt_tokens_details":{"cached_tokens":20}}}`),
}
index := len(forwarder.observed)
if index >= len(payloads) {
return UpstreamResult{Err: errors.New("stream script exhausted")}
}
capture := newUsageCaptureBoundary().newStreamForRequest(input.Dialect, true)
capture.observeEvent(dialect.StreamEvent{Payload: payloads[index]})
result := capture.finalize()
forwarder.observed = append(forwarder.observed, result)
if index == 0 {
return UpstreamResult{
StatusCode: http.StatusOK,
Header: http.Header{"Retry-After": {"1"}},
ClassificationBody: []byte(`{"error":{"type":"rate_limit_error"}}`),
ErrorSummary: fixedErrorSummary("upstream_sse_error"),
RequestWritten: true,
ProviderErrorBeforeCommit: true,
Usage: result,
}
}
if input.OnStreamReady != nil {
input.OnStreamReady()
}
return UpstreamResult{
StatusCode: http.StatusOK, RequestWritten: true, Committed: true, Usage: result,
}
}
func TestNewRequestRecorderInitializesUsageNotApplicable(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"req-usage-initial",
time.Unix(100, 0),
9,
protocol.OpenAICompletions,
func() time.Time { return time.Unix(101, 0) },
)
recorder.emit()
if len(sink.events) != 1 {
t.Fatalf("events = %d, want 1", len(sink.events))
}
event := sink.events[0]
if event.Usage.Result.State != usage.StateNotApplicable ||
event.Usage.Result.Tokens != (usage.Tokens{}) ||
event.Usage.GroupID != 0 ||
event.Usage.CredentialID != 0 ||
event.Usage.AttemptSequence != 0 {
t.Fatalf("initial Usage = %#v", event.Usage)
}
}
func TestHandlerRecordsProtocolOnlyResponsesResourceWithoutFabricatingModels(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: []byte(`{"id":"resp_123","object":"response"}`),
RequestWritten: true,
}}}
sink := &recordingRequestLogSink{}
engine, handler, manager, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-first",
)
if _, err := manager.Publish(state.CompileInput{
ChannelRegistry: channel.NewRegistry(),
Groups: []state.GroupConfig{{
ID: 1, Name: "responses", ChannelID: channel.OpenAI,
Params: json.RawMessage(`{}`), Enabled: true,
}},
Credentials: []state.CredentialConfig{testCredentialConfig(1, 1)},
AccessKeys: []state.AccessKeyConfig{{
ID: 1,
Name: "client",
KeyHash: handler.encryption.Hash("gl-client"),
Status: state.AccessKeyStatusActive,
}},
}); err != nil {
t.Fatalf("Publish() error = %v", err)
}
handler.dialects = dialect.NewSet(dialect.NewOpenAIResponses())
request := httptest.NewRequest(http.MethodGet, "/v1/responses/resp_123", nil)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if response.Code != http.StatusOK || len(events) != 1 {
t.Fatalf("response/events = %d/%#v", response.Code, events)
}
event := events[0]
if event.Protocol != protocol.OpenAIResponses ||
event.ClientModel != "" ||
event.UpstreamModel != "" ||
event.UpstreamReportedModel != "" ||
event.ModelConsistency != telemetry.ModelConsistencyNotApplicable ||
len(event.Attempts) != 1 ||
event.Attempts[0].UpstreamModel != "" ||
event.Usage.Result.State != usage.StateNotApplicable ||
withoutPricingReceipt(event.Usage.Pricing) != (telemetry.PricingObservation{
CostState: "not_applicable", PricingCompleteness: "not_applicable",
}) {
t.Fatalf("event = %#v", event)
}
}
func TestHandlerRecordsModelConsistencyOnlyForSuccessfulModeledRequests(t *testing.T) {
tests := []struct {
name string
result UpstreamResult
wantConsistency telemetry.ModelConsistency
wantReportedModel string
wantDownstreamStatus int
}{
{
name: "match",
result: UpstreamResult{
StatusCode: http.StatusOK, Header: make(http.Header), RequestWritten: true,
ResponseModelObserved: true, UpstreamReportedModel: "gpt-4o",
},
wantConsistency: telemetry.ModelConsistencyMatch,
wantReportedModel: "gpt-4o",
wantDownstreamStatus: http.StatusOK,
},
{
name: "unknown",
result: UpstreamResult{
StatusCode: http.StatusOK, Header: make(http.Header), RequestWritten: true,
},
wantConsistency: telemetry.ModelConsistencyUnknown,
wantDownstreamStatus: http.StatusOK,
},
{
name: "mismatch",
result: UpstreamResult{
StatusCode: http.StatusOK, Header: make(http.Header), RequestWritten: true,
ResponseModelObserved: true, ResponseModelMismatch: true,
UpstreamReportedModel: "different-model",
},
wantConsistency: telemetry.ModelConsistencyMismatch,
wantReportedModel: "different-model",
wantDownstreamStatus: http.StatusOK,
},
{
name: "unsuccessful response is ignored",
result: UpstreamResult{
StatusCode: http.StatusBadRequest, Header: make(http.Header), RequestWritten: true,
ClassificationBody: []byte(`{"error":{"message":"invalid request"}}`),
ResponseModelObserved: true, ResponseModelMismatch: true,
UpstreamReportedModel: "different-model",
},
wantConsistency: telemetry.ModelConsistencyNotApplicable,
wantDownstreamStatus: http.StatusBadRequest,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sink := &recordingRequestLogSink{}
engine, _, _, _ := newRequestLogHandlerTestRuntime(
t,
&scriptedForwarder{results: []UpstreamResult{test.result}},
&recordingAccessKeyRPMLimiter{},
sink,
"sk-first",
)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if response.Code != test.wantDownstreamStatus || len(events) != 1 {
t.Fatalf("response/events = %d/%#v", response.Code, events)
}
event := events[0]
if event.UpstreamModel != "gpt-4o" ||
event.UpstreamReportedModel != test.wantReportedModel ||
event.ModelConsistency != test.wantConsistency {
t.Fatalf("model consistency event = %#v", event)
}
})
}
}
func TestHandlerBillableResponsesUsageWithoutPriceIsUnpriced(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: []byte(`{"id":"resp_123"}`),
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{UncachedInput: 10},
},
RequestWritten: true,
}}}
sink := &recordingRequestLogSink{}
engine, handler, manager, _ := newRequestLogHandlerTestRuntime(
t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first",
)
if _, err := manager.Publish(state.CompileInput{
ChannelRegistry: channel.NewRegistry(),
Groups: []state.GroupConfig{{
ID: 1, Name: "responses", ChannelID: channel.OpenAI,
Params: json.RawMessage(`{}`),
Models: []state.ModelConfig{{ID: "unpriced-model"}}, Enabled: true,
}},
Credentials: []state.CredentialConfig{testCredentialConfig(1, 1)},
AccessKeys: []state.AccessKeyConfig{{
ID: 1, Name: "client", KeyHash: handler.encryption.Hash("gl-client"),
Status: state.AccessKeyStatusActive,
}},
}); err != nil {
t.Fatal(err)
}
handler.dialects = dialect.NewSet(dialect.NewOpenAIResponses())
handler.priceTables = &mutableGatewayPriceTableProvider{table: mustGatewayPriceTable(t, 1, true)}
request := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{"model":"unpriced-model","input":"ping"}`))
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || withoutPricingReceipt(events[0].Usage.Pricing) != (telemetry.PricingObservation{
UpstreamModel: "unpriced-model", CostState: "unpriced", PricingCompleteness: "unavailable",
}) {
t.Fatalf("events = %#v", events)
}
}
func TestRequestRecorderBindsUsageToRecordedAttempt(t *testing.T) {
tests := []struct {
name string
result usage.Result
}{
{name: "complete", result: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 30}}},
{name: "partial", result: usage.Result{State: usage.StatePartial, Tokens: usage.Tokens{Output: 30}}},
{name: "missing", result: usage.Result{State: usage.StateMissing}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(sink, "req-usage-bind", time.Unix(100, 0), 9, protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) })
recorder.appendAttempt(requestLogSelection(11, 21, "first"), UpstreamResult{}, telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "", time.Unix(100, 0), time.Unix(100, 0))
second := recorder.appendAttempt(requestLogSelection(12, 22, "second"), UpstreamResult{StatusCode: http.StatusOK}, telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "", time.Unix(100, 0), time.Unix(100, 0))
recorder.completeResponse(UpstreamResult{StatusCode: http.StatusOK, Usage: test.result}, health.Result{}, "provider", second)
recorder.emit()
event := sink.events[0]
if event.Usage.Result != test.result || event.Usage.GroupID != 12 || event.Usage.ChannelID != channel.OpenAI || event.Usage.CredentialID != 22 || event.Usage.AttemptSequence != 2 {
t.Fatalf("Usage = %#v", event.Usage)
}
if event.Attempts[1].GroupID != event.Usage.GroupID || event.Attempts[1].ChannelID != event.Usage.ChannelID || event.Attempts[1].CredentialID != event.Usage.CredentialID || event.Attempts[1].Sequence != event.Usage.AttemptSequence {
t.Fatalf("attempt/Usage identity = %#v/%#v", event.Attempts[1], event.Usage)
}
})
}
}
func TestRequestRecorderNon2xxKeepsAttributionButNotApplicable(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(sink, "req-usage-429", time.Unix(100, 0), 9, protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) })
index := recorder.appendAttempt(requestLogSelection(12, 22, "second"), UpstreamResult{StatusCode: http.StatusTooManyRequests}, telemetry.FailureCategoryRateLimited, telemetry.ActionRetry, "", "", time.Unix(100, 0), time.Unix(100, 0))
recorder.completeResponse(UpstreamResult{StatusCode: http.StatusTooManyRequests, Usage: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{Output: 30}}}, health.Result{Category: health.FailureCategoryRateLimited}, "provider", index)
recorder.emit()
event := sink.events[0]
if event.Usage.Result != (usage.Result{State: usage.StateNotApplicable}) || event.Usage.GroupID != 12 || event.Usage.ChannelID != channel.OpenAI || event.Usage.CredentialID != 22 || event.Usage.AttemptSequence != 1 {
t.Fatalf("Usage = %#v", event.Usage)
}
}
func TestRequestRecorderInvalidAttemptIndexDoesNotForgeUsageAttribution(t *testing.T) {
for _, index := range []int{-1, 1} {
t.Run(fmt.Sprintf("index_%d", index), func(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(sink, "req-usage-invalid", time.Unix(100, 0), 9, protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) })
recorder.appendAttempt(requestLogSelection(12, 22, "second"), UpstreamResult{}, telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "", time.Unix(100, 0), time.Unix(100, 0))
recorder.bindUsage(index, usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{Output: 30}}, true)
recorder.emit()
if got := sink.events[0].Usage; got != (telemetry.UsageObservation{
Result: usage.Result{State: usage.StateNotApplicable},
Pricing: telemetry.PricingObservation{
CostState: "not_applicable",
PricingCompleteness: "not_applicable",
},
}) {
t.Fatalf("Usage = %#v", got)
}
})
}
}
func TestRequestRecorderDownstreamFailureKeepsBoundUsage(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(sink, "req-usage-write", time.Unix(100, 0), 9, protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) })
index := recorder.appendAttempt(requestLogSelection(12, 22, "second"), UpstreamResult{StatusCode: http.StatusOK}, telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "", time.Unix(100, 0), time.Unix(100, 0))
result := usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 30}}
recorder.completeResponse(UpstreamResult{StatusCode: http.StatusOK, Usage: result}, health.Result{}, "provider", index)
recorder.completeDownstreamWrite(http.StatusOK)
recorder.emit()
event := sink.events[0]
if event.Status != telemetry.RequestStatusIncomplete || event.Usage.Result != result || event.Usage.GroupID != 12 || event.Usage.ChannelID != channel.OpenAI || event.Usage.CredentialID != 22 || event.Usage.AttemptSequence != 1 {
t.Fatalf("event = %#v", event)
}
}
func TestHandlerPublishesUsageForActualNonStreamingResponseAttempt(t *testing.T) {
sink := &recordingRequestLogSink{}
forwarder := &scriptedForwarder{results: []UpstreamResult{
{StatusCode: http.StatusTooManyRequests, Header: make(http.Header), ClassificationBody: []byte(`{"error":{"type":"rate_limit_error"}}`), Usage: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{Output: 99}}, RequestWritten: true},
{StatusCode: http.StatusOK, Header: make(http.Header), Body: []byte(`{"ok":true}`), Usage: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 30}}, RequestWritten: true},
}}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first", "sk-second")
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"gpt-4o"}`))
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || events[0].Usage.Result != (usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 30}}) || events[0].Usage.GroupID != 1 || events[0].Usage.CredentialID != 2 || events[0].Usage.AttemptSequence != 2 {
t.Fatalf("events = %#v", events)
}
}
func TestHandlerFreezesAttemptPriceBeforeForward(t *testing.T) {
first := mustGatewayPriceTable(t, 2_000_000_000, true)
second := mustGatewayPriceTable(t, 9_000_000_000, true)
provider := &mutableGatewayPriceTableProvider{table: first}
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: []byte(`{"ok":true}`),
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{UncachedInput: 1_000_000},
},
RequestWritten: true,
}}}
forwarder.onCall = func(int) { provider.Publish(second) }
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first",
)
handler.priceTables = provider
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v", events)
}
want := telemetry.PricingObservation{
UpstreamModel: "gpt-4o",
CostState: string(pricing.CostStatePriced),
PricingCompleteness: string(pricing.CompletenessComplete),
EstimatedCostNanoUSD: 2_000_000_000,
}
if withoutPricingReceipt(events[0].Usage.Pricing) != want {
t.Fatalf("frozen pricing = %#v, want %#v", events[0].Usage.Pricing, want)
}
if provider.LoadCount() != 1 {
t.Fatalf("PriceTableProvider.Load calls = %d, want 1", provider.LoadCount())
}
}
func TestRequestRecorderPreservesKnownCostWhenUsedOutputPriceIsUnavailable(t *testing.T) {
table := mustGatewayPriceTable(t, 2_000_000_000, false)
recorder := newRequestRecorder(
&recordingRequestLogSink{}, "partial-price", time.Unix(100, 0), 1,
protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) },
)
model := "gpt-4o"
recorder.freezeNextAttemptPricing(frozenAttemptPricing{
channelID: string(channel.OpenAI), groupID: 1,
upstreamModel: model, table: table, applicable: true,
})
selection := requestLogSelection(1, 2, "group")
selection.UpstreamModelID = &model
index := recorder.appendAttempt(
selection,
UpstreamResult{StatusCode: http.StatusOK},
telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "",
time.Unix(100, 0), time.Unix(101, 0),
)
recorder.completeResponse(UpstreamResult{
StatusCode: http.StatusOK,
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{UncachedInput: 1_000_000, Output: 1_000_000},
},
}, health.Result{}, model, index)
if got := withoutPricingReceipt(recorder.usage.Pricing); got != (telemetry.PricingObservation{
UpstreamModel: model,
CostState: "priced", PricingCompleteness: "partial",
EstimatedCostNanoUSD: 2_000_000_000,
}) {
t.Fatalf("partial pricing = %#v", got)
}
}
func TestRequestRecorderMergesRequestPricingDiagnosticsIntoKnownCost(t *testing.T) {
table := mustGatewayPriceTable(t, 2_000_000_000, true)
recorder := newRequestRecorder(
&recordingRequestLogSink{}, "unsupported-mode", time.Unix(100, 0), 1,
protocol.OpenAICompletions, func() time.Time { return time.Unix(101, 0) },
)
diagnostics := usage.Diagnostics{}
diagnostics.Add(usage.DiagnosticUnsupportedBillableDetail)
recorder.setUsageDiagnostics(diagnostics)
model := "gpt-4o"
recorder.freezeNextAttemptPricing(frozenAttemptPricing{
channelID: string(channel.OpenAI), groupID: 1,
upstreamModel: model, table: table, applicable: true,
})
selection := requestLogSelection(1, 2, "group")
selection.UpstreamModelID = &model
index := recorder.appendAttempt(
selection,
UpstreamResult{StatusCode: http.StatusOK},
telemetry.FailureCategoryOK, telemetry.ActionTerminate, "", "",
time.Unix(100, 0), time.Unix(101, 0),
)
recorder.completeResponse(UpstreamResult{
StatusCode: http.StatusOK,
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{UncachedInput: 1_000_000, Output: 1_000_000},
},
}, health.Result{}, model, index)
if !recorder.usage.Result.Diagnostics.Has(usage.DiagnosticUnsupportedBillableDetail) {
t.Fatalf("usage diagnostics = %#v, want unsupported billable detail", recorder.usage.Result.Diagnostics)
}
if got := withoutPricingReceipt(recorder.usage.Pricing); got != (telemetry.PricingObservation{
UpstreamModel: model,
CostState: "priced", PricingCompleteness: "complete",
EstimatedCostNanoUSD: 4_000_000_000,
}) {
t.Fatalf("unsupported mode pricing = %#v", got)
}
}
func TestHandlerUsesSelectedProviderModelForPricingInsteadOfAliasOrBodyModel(t *testing.T) {
model := "provider-model"
table, err := pricing.NewTable([]pricing.Rule{{
Identity: pricing.Identity{ChannelID: string(channel.OpenAI), ModelID: model},
Prices: pricing.Prices{Input: pricing.Price{
NanoUSDPerMillion: 3_000_000_000, Set: true,
}},
}})
if err != nil {
t.Fatal(err)
}
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK, Header: make(http.Header),
Body: []byte(`{"model":"body-model"}`), RequestWritten: true,
Usage: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 1_000_000}},
}}}
sink := &recordingRequestLogSink{}
engine, handler, manager, _ := newRequestLogHandlerTestRuntime(
t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first",
)
if _, err := manager.Publish(state.CompileInput{
ChannelRegistry: channel.NewRegistry(),
Groups: []state.GroupConfig{{
ID: 1, Name: "provider", ChannelID: channel.OpenAI,
Params: json.RawMessage(`{}`),
Models: []state.ModelConfig{{ID: model, Alias: "client-alias"}}, Enabled: true,
}},
Credentials: []state.CredentialConfig{testCredentialConfig(1, 1)},
AccessKeys: []state.AccessKeyConfig{{
ID: 1, Name: "client", KeyHash: handler.encryption.Hash("gl-client"),
Status: state.AccessKeyStatusActive,
}},
}); err != nil {
t.Fatal(err)
}
handler.priceTables = &mutableGatewayPriceTableProvider{table: table}
request := httptest.NewRequest(
http.MethodPost, "/v1/chat/completions",
strings.NewReader(`{"model":"client-alias"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || events[0].ClientModel != "client-alias" ||
events[0].UpstreamModel != model || withoutPricingReceipt(events[0].Usage.Pricing) != (telemetry.PricingObservation{
UpstreamModel: model,
CostState: "priced", PricingCompleteness: "complete",
EstimatedCostNanoUSD: 3_000_000_000,
}) {
t.Fatalf("events = %#v", events)
}
}
func TestHandlerKeepsKnownCostCompleteForUnsupportedPricingMode(t *testing.T) {
table := mustGatewayPriceTable(t, 2_000_000_000, true)
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK, Header: make(http.Header), RequestWritten: true,
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{
UncachedInput: 1_000_000,
Output: 1_000_000,
},
},
}}}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first",
)
handler.priceTables = &mutableGatewayPriceTableProvider{table: table}
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","service_tier":"priority"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want one", events)
}
if !events[0].Usage.Result.Diagnostics.Has(usage.DiagnosticUnsupportedBillableDetail) ||
withoutPricingReceipt(events[0].Usage.Pricing) != (telemetry.PricingObservation{
UpstreamModel: "gpt-4o",
CostState: "priced", PricingCompleteness: "complete",
EstimatedCostNanoUSD: 4_000_000_000,
}) {
t.Fatalf("unsupported mode event = %#v", events[0])
}
}
func mustGatewayPriceTable(t *testing.T, input int64, outputSet bool) *pricing.Table {
t.Helper()
prices := pricing.Prices{
Input: pricing.Price{NanoUSDPerMillion: pricing.NanoUSD(input), Set: true},
}
if outputSet {
prices.Output = pricing.Price{NanoUSDPerMillion: pricing.NanoUSD(input), Set: true}
}
table, err := pricing.NewTable([]pricing.Rule{{
Identity: pricing.Identity{ChannelID: string(channel.OpenAI), ModelID: "gpt-4o"},
Prices: prices,
}})
if err != nil {
t.Fatalf("NewTable() error = %v", err)
}
return table
}
func TestHandlerDiscardsPreCommitStreamUsageOnRetry(t *testing.T) {
sink := &recordingRequestLogSink{}
forwarder := &usageObservingStreamRetryForwarder{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first", "sk-second",
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
first := usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 9}}
second := usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{UncachedInput: 80, CacheRead: 20, Output: 30}}
if len(forwarder.observed) != 2 || forwarder.observed[0] != first || forwarder.observed[1] != second {
t.Fatalf("observed Usage = %#v, want %#v then %#v", forwarder.observed, first, second)
}
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want exactly one final event", events)
}
event := events[0]
if event.Usage.Result != second || event.Usage.Result == first ||
event.Usage.GroupID != 1 || event.Usage.CredentialID != 2 || event.Usage.AttemptSequence != 2 {
t.Fatalf("event Usage = %#v, want second attempt Usage", event.Usage)
}
if len(event.Attempts) != 2 || event.Attempts[1].GroupID != event.Usage.GroupID ||
event.Attempts[1].CredentialID != event.Usage.CredentialID || event.Attempts[1].Sequence != event.Usage.AttemptSequence {
t.Fatalf("attempts/Usage = %#v/%#v", event.Attempts, event.Usage)
}
}
func TestHandlerFirstProviderErrorRequestLogContract(t *testing.T) {
sink := &recordingRequestLogSink{}
const (
apiKey = "sk-obviously-fake-log-contract"
providerRaw = "provider-body-must-not-be-logged"
)
forwarder := &scriptedForwarder{streamResults: []UpstreamResult{
withProviderErrorBeforeCommit(UpstreamResult{
StatusCode: http.StatusOK,
Header: http.Header{"Retry-After": {"1"}},
ClassificationBody: []byte(
`{"error":{"type":"rate_limit_error","message":"` + providerRaw + ` ` + apiKey + `"}}`,
),
ErrorSummary: fixedErrorSummary("upstream_sse_error"),
RequestWritten: true,
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{
UncachedInput: 25,
Output: 2,
},
},
}),
}}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
apiKey,
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v", events)
}
event := events[0]
if response.Code != http.StatusBadGateway ||
event.Status != telemetry.RequestStatusError ||
event.StatusCode != http.StatusBadGateway ||
event.ErrorCode != reasonUpstreamProtocol.Code ||
event.ErrorSummary != fixedErrorSummary("upstream_sse_error") ||
event.Usage.Result != (usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{
UncachedInput: 25,
Output: 2,
},
}) ||
event.Usage.GroupID != 1 ||
event.Usage.CredentialID != 1 ||
event.Usage.AttemptSequence != 1 ||
len(event.Attempts) != 1 {
t.Fatalf("response/event = %d/%#v", response.Code, event)
}
attempt := event.Attempts[0]
if attempt.StatusCode != http.StatusOK ||
attempt.FailureCategory != telemetry.FailureCategoryRateLimited ||
attempt.Action != telemetry.ActionCooldownCredential ||
attempt.Committed ||
attempt.ErrorSummary != fixedErrorSummary("upstream_sse_error") {
t.Fatalf("attempt = %#v", attempt)
}
serialized := fmt.Sprintf("%#v", event)
for _, forbidden := range []string{apiKey, providerRaw, "rate_limit_error"} {
if strings.Contains(serialized, forbidden) {
t.Fatalf("request log leaked %q: %s", forbidden, serialized)
}
}
}
func TestHandlerRetryExhaustionUsesProviderErrorAttemptAndItsFrozenPrice(t *testing.T) {
firstTable := mustGatewayPriceTable(t, 2_000_000_000, true)
secondTable := mustGatewayPriceTable(t, 9_000_000_000, true)
thirdTable := mustGatewayPriceTable(t, 15_000_000_000, true)
provider := &mutableGatewayPriceTableProvider{table: firstTable}
forwarder := &scriptedForwarder{streamResults: []UpstreamResult{
withProviderErrorBeforeCommit(UpstreamResult{
StatusCode: http.StatusOK,
Header: http.Header{"Retry-After": {"1"}},
ClassificationBody: []byte(`{"error":{"type":"rate_limit_error"}}`),
ErrorSummary: fixedErrorSummary("upstream_sse_error"),
RequestWritten: true,
Usage: usage.Result{
State: usage.StateComplete,
Tokens: usage.Tokens{UncachedInput: 1_000_000},
},
}),
{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: []byte(`{"error":"invalid key"}`),
ClassificationBody: []byte(`{"error":"invalid key"}`),
RequestWritten: true,
},
{Err: errors.New("dial failed")},
}}
forwarder.onStreamCall = func(index int, _ http.ResponseWriter) {
switch index {
case 0:
provider.Publish(secondTable)
case 1:
provider.Publish(thirdTable)
}
}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-first",
"sk-second",
"sk-third",
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
handler.priceTables = provider
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if response.Code != http.StatusBadGateway || len(events) != 1 ||
len(events[0].Attempts) != 3 {
t.Fatalf("response/events = %d/%#v", response.Code, events)
}
event := events[0]
if event.Usage.GroupID != 1 || event.Usage.CredentialID != 1 ||
event.Usage.AttemptSequence != 1 || withoutPricingReceipt(event.Usage.Pricing) != (telemetry.PricingObservation{
UpstreamModel: "gpt-4o",
CostState: string(pricing.CostStatePriced),
PricingCompleteness: string(pricing.CompletenessComplete),
EstimatedCostNanoUSD: 2_000_000_000,
}) {
t.Fatalf("selected usage/pricing = %#v, want first provider-error attempt", event.Usage)
}
if !event.Attempts[0].WillRetry || !event.Attempts[1].WillRetry ||
event.Attempts[2].WillRetry || provider.LoadCount() != 3 {
t.Fatalf("attempt retry flags/provider loads = %#v/%d", event.Attempts, provider.LoadCount())
}
}
func TestHandlerRememberedLastResponseKeepsOriginalUsageAttempt(t *testing.T) {
sink := &recordingRequestLogSink{}
forwarder := &scriptedForwarder{results: []UpstreamResult{
{StatusCode: http.StatusTooManyRequests, Header: make(http.Header), ClassificationBody: []byte(`{"error":{"type":"rate_limit_error"}}`), RequestWritten: true},
{Err: errors.New("transport two")},
{Err: errors.New("transport three")},
}}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first", "sk-second", "sk-third")
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"gpt-4o"}`))
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || events[0].Usage.Result != (usage.Result{State: usage.StateNotApplicable}) || events[0].Usage.GroupID != 1 || events[0].Usage.CredentialID != 1 || events[0].Usage.AttemptSequence != 1 {
t.Fatalf("events = %#v", events)
}
}
func TestHandlerFinalNon2xxPublishesNotApplicableWithResponseAttribution(t *testing.T) {
sink := &recordingRequestLogSink{}
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusTooManyRequests, Header: make(http.Header), ClassificationBody: []byte(`{"error":{"type":"rate_limit_error"}}`), Usage: usage.Result{State: usage.StateComplete, Tokens: usage.Tokens{Output: 99}}, RequestWritten: true,
}}}
engine, _, _, _ := newRequestLogHandlerTestRuntime(t, forwarder, &recordingAccessKeyRPMLimiter{}, sink, "sk-first")
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"gpt-4o"}`))
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || events[0].Usage.Result != (usage.Result{State: usage.StateNotApplicable}) || events[0].Usage.GroupID != 1 || events[0].Usage.CredentialID != 1 || events[0].Usage.AttemptSequence != 1 {
t.Fatalf("events = %#v", events)
}
}
func TestHandlerTerminalAttemptUsageKeepsRouteAttribution(t *testing.T) {
for _, test := range []struct {
name string
forwarder *scriptedForwarder
upstreamKeys []string
wantStatus telemetry.RequestStatus
wantGroupID uint
wantCredentialID uint
wantAttemptSequence int
wantUpstreamModel string
}{
{
name: "transport",
forwarder: &scriptedForwarder{results: []UpstreamResult{{Err: errors.New("transport"), RequestWritten: true}}},
upstreamKeys: []string{"sk-first"},
wantStatus: telemetry.RequestStatusError,
wantGroupID: 1,
wantCredentialID: 1,
wantAttemptSequence: 1,
wantUpstreamModel: "gpt-4o",
},
{
name: "canceled transport",
forwarder: &scriptedForwarder{results: []UpstreamResult{{Err: context.Canceled, RequestWritten: true}}},
upstreamKeys: []string{"sk-first"},
wantStatus: telemetry.RequestStatusCanceled,
wantGroupID: 1,
wantCredentialID: 1,
wantAttemptSequence: 1,
wantUpstreamModel: "gpt-4o",
},
{
name: "transport failure keeps the skipped group attempt",
forwarder: &scriptedForwarder{results: []UpstreamResult{
{Err: errors.New("transport one")},
{Err: errors.New("transport two")},
{Err: errors.New("transport three"), RequestWritten: true},
}},
upstreamKeys: []string{"sk-first", "sk-second", "sk-third"},
wantStatus: telemetry.RequestStatusError,
wantGroupID: 1,
wantCredentialID: 1,
wantAttemptSequence: 1,
wantUpstreamModel: "gpt-4o",
},
{
name: "no candidate",
forwarder: &scriptedForwarder{},
wantStatus: telemetry.RequestStatusError,
},
} {
t.Run(test.name, func(t *testing.T) {
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(t, test.forwarder, &recordingAccessKeyRPMLimiter{}, sink, test.upstreamKeys...)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"gpt-4o"}`))
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
var wantChannelID channel.ID
if test.wantGroupID != 0 {
wantChannelID = channel.OpenAI
}
wantPricing := telemetry.PricingObservation{
CostState: "not_applicable",
PricingCompleteness: "not_applicable",
}
if test.wantGroupID != 0 {
wantPricing.UpstreamModel = "gpt-4o"
}
if len(events) != 1 || events[0].Status != test.wantStatus ||
events[0].UpstreamModel != test.wantUpstreamModel ||
events[0].Usage != (telemetry.UsageObservation{
Result: usage.Result{State: usage.StateNotApplicable},
GroupID: test.wantGroupID,
ChannelID: wantChannelID,
CredentialID: test.wantCredentialID,
AttemptSequence: test.wantAttemptSequence,
Pricing: wantPricing,
}) {
t.Fatalf("events = %#v", events)
}
})
}
}
func (sink *recordingRequestLogSink) Emit(event telemetry.RequestEvent) {
sink.mu.Lock()
defer sink.mu.Unlock()
event.Attempts = append([]telemetry.Attempt(nil), event.Attempts...)
sink.events = append(sink.events, event)
}
func (sink *recordingRequestLogSink) snapshot() []telemetry.RequestEvent {
sink.mu.Lock()
defer sink.mu.Unlock()
events := append([]telemetry.RequestEvent(nil), sink.events...)
for index := range events {
events[index].Attempts = append([]telemetry.Attempt(nil), events[index].Attempts...)
}
return events
}
func TestHandlerPanicFallbackEmitsFailClosedRequestLogBeforeAndAfterHeaders(t *testing.T) {
const panicCanary = "forwarder-panic-canary-must-not-leak"
tests := []struct {
name string
body string
configure func(*scriptedForwarder)
wantStatus telemetry.RequestStatus
wantCode int
}{
{
name: "before downstream write",
body: `{"model":"gpt-4o"}`,
configure: func(forwarder *scriptedForwarder) {
forwarder.onCall = func(int) { panic(panicCanary) }
},
wantStatus: telemetry.RequestStatusError,
wantCode: http.StatusInternalServerError,
},
{
name: "after accepted stream headers",
body: `{"model":"gpt-4o","stream":true}`,
configure: func(forwarder *scriptedForwarder) {
forwarder.onStreamCall = func(_ int, writer http.ResponseWriter) {
writer.WriteHeader(http.StatusAccepted)
_, _ = writer.Write(nil)
panic(panicCanary)
}
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: http.StatusAccepted,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
forwarder := &scriptedForwarder{}
test.configure(forwarder)
sink := &recordingRequestLogSink{}
engine, _, _, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-panic-fallback",
)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(test.body),
)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
var logs bytes.Buffer
logger := logrus.StandardLogger()
previousOutput := logger.Out
logger.SetOutput(&logs)
defer logger.SetOutput(previousOutput)
var recovered any
func() {
defer func() { recovered = recover() }()
engine.ServeHTTP(response, request)
}()
if recovered != panicCanary {
t.Fatalf("outer recovery received %#v, want original panic %q", recovered, panicCanary)
}
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want exactly one event", events)
}
event := events[0]
if event.Status != test.wantStatus ||
event.StatusCode != test.wantCode ||
event.ErrorCode != "internal_error" ||
event.ErrorSummary != "The request failed due to an internal error." {
t.Fatalf("event = %#v", event)
}
for _, surface := range []string{
fmt.Sprintf("%#v", event),
response.Body.String(),
fmt.Sprintf("%v", response.Header()),
logs.String(),
} {
if strings.Contains(surface, panicCanary) {
t.Fatalf("panic canary leaked to %q", surface)
}
}
})
}
}
func TestRequestRecorderBoundsModelsAtUTF8Boundary(t *testing.T) {
const requestID = "00000000-0000-4000-8000-000000000206"
clientModel := strings.Repeat("界", 85)
upstreamModel := strings.Repeat("upstream-", 32)
attemptModel := strings.Repeat("模", 86)
sink := &recordingRequestLogSink{}
startedAt := time.Date(2026, time.July, 27, 10, 0, 0, 0, time.UTC)
recorder := newRequestRecorder(
sink,
requestID,
startedAt,
1,
protocol.OpenAICompletions,
func() time.Time { return startedAt.Add(time.Second) },
)
recorder.setClientModel(clientModel)
recorder.attempts = []telemetry.Attempt{{
Sequence: 1,
UpstreamModel: attemptModel,
}}
recorder.outcome = requestOutcome{
status: telemetry.RequestStatusSuccess,
statusCode: http.StatusOK,
upstreamModel: upstreamModel,
}
recorder.emit()
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %d, want 1", len(events))
}
event := events[0]
if len(clientModel) != 255 || event.ClientModel != clientModel {
t.Fatalf(
"client model bytes/value = %d/%q, want 255-byte model unchanged",
len(event.ClientModel),
event.ClientModel,
)
}
if len(upstreamModel) <= 255 || event.UpstreamModel != upstreamModel {
t.Fatalf("overall upstream model was changed before pricing: %q", event.UpstreamModel)
}
if len(event.Attempts) != 1 || len(attemptModel) <= 255 ||
event.Attempts[0].UpstreamModel != attemptModel {
t.Fatalf(
"attempt upstream model was changed before SQLite projection: %#v",
event.Attempts,
)
}
}
func TestRequestRecorderEmitsClientReasoningConfiguration(t *testing.T) {
budget := int64(8192)
want := reasoning.Config{Mode: "adaptive", Effort: "high", BudgetTokens: &budget}
startedAt := time.Date(2026, time.August, 6, 10, 0, 0, 0, time.UTC)
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"00000000-0000-4000-8000-000000000207",
startedAt,
1,
protocol.Anthropic,
func() time.Time { return startedAt.Add(time.Second) },
)
recorder.reasoning = want
recorder.outcome = requestOutcome{
status: telemetry.RequestStatusSuccess,
statusCode: http.StatusOK,
}
recorder.emit()
events := sink.snapshot()
if len(events) != 1 || !reflect.DeepEqual(events[0].Reasoning, want) {
t.Fatalf("events = %#v, want reasoning %#v", events, want)
}
}
func TestRequestRecorderEmitsOperationAndAppliedAttemptObservation(t *testing.T) {
budget := int64(4096)
startedAt := time.Date(2026, time.August, 10, 10, 0, 0, 0, time.UTC)
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"00000000-0000-4000-8000-000000000208",
startedAt,
1,
protocol.OpenAIResponses,
func() time.Time { return startedAt.Add(time.Second) },
)
recorder.setOperation(execution.OperationResponsesCreate)
selection := requestLogSelection(7, 8, "converted")
selection.RouteMode = channel.RouteConverted
recorder.appendAttempt(
selection,
UpstreamResult{
UpstreamAPI: execution.UpstreamAPIAnthropicMessages,
AppliedReasoning: reasoning.Config{
Mode: "enabled", BudgetTokens: &budget,
},
},
telemetry.FailureCategoryOK,
telemetry.ActionTerminate,
"",
"",
startedAt,
startedAt.Add(500*time.Millisecond),
)
recorder.outcome = requestOutcome{
status: telemetry.RequestStatusSuccess, statusCode: http.StatusOK,
}
budget = 1
recorder.emit()
events := sink.snapshot()
if len(events) != 1 || events[0].Operation != execution.OperationResponsesCreate ||
len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one responses_create attempt", events)
}
attempt := events[0].Attempts[0]
if attempt.UpstreamAPI != execution.UpstreamAPIAnthropicMessages ||
attempt.Reasoning.Mode != "enabled" || attempt.Reasoning.BudgetTokens == nil ||
*attempt.Reasoning.BudgetTokens != 4096 {
t.Fatalf("attempt observation = %#v", attempt)
}
}
type observingReadCloser struct {
reader io.Reader
read bool
}
func (reader *observingReadCloser) Read(destination []byte) (int, error) {
reader.read = true
return reader.reader.Read(destination)
}
func (*observingReadCloser) Close() error { return nil }
type failingReadCloser struct{}
func (failingReadCloser) Read([]byte) (int, error) { return 0, errors.New("body read failed") }
func (failingReadCloser) Close() error { return nil }
type cancelingErrorReadCloser struct {
cancel context.CancelFunc
err error
}
func (reader cancelingErrorReadCloser) Read([]byte) (int, error) {
reader.cancel()
return 0, reader.err
}
func (cancelingErrorReadCloser) Close() error { return nil }
type cancelingExtractDialect struct {
dialect.Dialect
cancel context.CancelFunc
err error
extractCalls int
}
func (value *cancelingExtractDialect) InspectRequest(
request *dialect.ParsedRequest,
) (dialect.RequestMetadata, error) {
value.extractCalls++
if value.cancel != nil {
value.cancel()
return dialect.RequestMetadata{}, value.err
}
return value.Dialect.InspectRequest(request)
}
type cancelingHeaderDeadlineWriter struct {
gin.ResponseWriter
cancel context.CancelFunc
err error
}
func (writer *cancelingHeaderDeadlineWriter) SetWriteDeadline(deadline time.Time) error {
if !deadline.IsZero() {
writer.cancel()
return writer.err
}
return nil
}
func newSteppingRequestClock() func() time.Time {
var mu sync.Mutex
next := time.Date(2026, time.July, 24, 12, 0, 0, 0, time.UTC)
return func() time.Time {
mu.Lock()
defer mu.Unlock()
current := next
next = next.Add(10 * time.Millisecond)
return current
}
}
func newRequestLogHandlerTestRuntime(
t *testing.T,
forwarder AttemptForwarder,
limiter AccessKeyRPMLimiter,
sink telemetry.RequestLogSink,
upstreamKeys ...string,
) (*gin.Engine, *Handler, *state.Manager, *state.CredentialRegistry) {
t.Helper()
handler, manager, registry := newHandlerForTest(t, forwarder, upstreamKeys...)
handler.limiter = limiter
handler.requestLogSink = sink
handler.newRequestID = func() (string, error) { return fixedRequestID, nil }
handler.requestNow = newSteppingRequestClock()
engine := gin.New()
bindGatewayRoutesForTest(t, engine, handler)
return engine, handler, manager, registry
}
func TestHandlerRPMAdmissionOrderingAndSingleCharge(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{
{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: []byte(`{"error":{"message":"invalid key"}}`),
ClassificationBody: []byte(
`{"error":{"message":"invalid key"}}`,
),
ErrorSummary: "invalid key",
RequestWritten: true,
},
{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: []byte(`{"ok":true}`),
RequestWritten: true,
},
}}
limiter := &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink, "sk-first", "sk-second",
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
body := &observingReadCloser{
reader: strings.NewReader(`{"model":"gpt-4o"}`),
}
var recorder *httptest.ResponseRecorder
limiter.onAllow = func(call rpmLimiterCall) {
if body.read {
t.Fatal("RPM admission ran after inference body read")
}
if got := recorder.Header().Get(requestIDHeader); got != fixedRequestID {
t.Fatalf("request ID at admission = %q, want %q", got, fixedRequestID)
}
}
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
request.Body = body
request.Header.Set("Authorization", "Bearer gl-client")
recorder = httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
calls := limiter.snapshot()
if len(calls) != 1 || calls[0].accessKeyID != 1 {
t.Fatalf("limiter calls = %#v, want one charge for AccessKey 1", calls)
}
if len(forwarder.inputs) != 2 {
t.Fatalf("forward calls = %d, want two attempts after one admission", len(forwarder.inputs))
}
}
func TestHandlerUsesFrozenRPMLimitAcrossSnapshotPublish(t *testing.T) {
keyService, err := encryption.NewService("frozen-rpm-test-master-key")
if err != nil {
t.Fatalf("NewService() error = %v", err)
}
manager := state.NewManager()
publish := func(limit int64) {
t.Helper()
if _, err := manager.Publish(state.CompileInput{
ChannelRegistry: channel.NewRegistry(),
Groups: []state.GroupConfig{{
ID: 1, Name: "openai", ChannelID: channel.OpenAI, Params: json.RawMessage(`{}`),
Models: []state.ModelConfig{{ID: "gpt-4o"}}, Enabled: true,
}},
AccessKeys: []state.AccessKeyConfig{{
ID: 1, Name: "client", KeyHash: keyService.Hash("gl-client"),
Status: state.AccessKeyStatusActive, RPMLimit: limit,
}},
}); err != nil {
t.Fatalf("Publish(limit=%d) error = %v", limit, err)
}
}
publish(3)
limiter := &recordingAccessKeyRPMLimiter{}
limiter.onAllow = func(rpmLimiterCall) {
if len(limiter.snapshot()) == 1 {
publish(9)
}
}
handler := NewHandler(
manager,
state.NewCredentialRegistry(),
keyService,
&scriptedForwarder{},
dialect.NewSet(),
health.NewStatsStore(),
health.NewMutationCoordinator(),
limiter,
telemetry.NoopRequestLogSink{},
nil,
)
handler.newRequestID = func() (string, error) { return fixedRequestID, nil }
engine := gin.New()
bindGatewayRoutesForTest(t, engine, handler)
for range 2 {
request := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
}
calls := limiter.snapshot()
if len(calls) != 2 || calls[0].limit != 3 || calls[1].limit != 9 {
t.Fatalf("limiter calls = %#v, want frozen limits [3,9]", calls)
}
}
func TestHandlerRPMLimitRejectsWithStableResponse(t *testing.T) {
tests := []struct {
name string
retryAfter time.Duration
want string
}{
{name: "ceil seconds", retryAfter: 1500 * time.Millisecond, want: "2"},
{name: "clamp minimum", retryAfter: 0, want: "1"},
{name: "clamp maximum", retryAfter: 2 * time.Minute, want: "60"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
forwarder := &scriptedForwarder{}
limiter := &recordingAccessKeyRPMLimiter{
decisions: []ratelimit.LimitDecision{{
Allowed: false, RetryAfter: test.retryAfter,
}},
}
sink := &recordingRequestLogSink{}
engine, _, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink, "sk-one",
)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
recorder := httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
if recorder.Code != http.StatusTooManyRequests ||
recorder.Header().Get("Retry-After") != test.want ||
recorder.Header().Get(requestIDHeader) != fixedRequestID {
t.Fatalf(
"response status/retry/request-id = %d/%q/%q",
recorder.Code,
recorder.Header().Get("Retry-After"),
recorder.Header().Get(requestIDHeader),
)
}
assertJSONEqual(
t,
recorder.Body.String(),
`{"code":"access_key_rate_limited","message":"Access key rate limit exceeded."}`,
)
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 0 ||
events[0].Status != telemetry.RequestStatusError ||
events[0].StatusCode != http.StatusTooManyRequests ||
events[0].ErrorCode != "access_key_rate_limited" {
t.Fatalf("events = %#v, want one zero-attempt RPM rejection", events)
}
if len(forwarder.inputs)+len(forwarder.streamInputs) != 0 {
t.Fatal("RPM-rejected request reached upstream")
}
})
}
}
func TestHandlerRequestLogScopeAndExactlyOnce(t *testing.T) {
tests := []struct {
name string
method string
target string
accessKey string
body string
decision ratelimit.LimitDecision
upstreamKeys []string
results []UpstreamResult
wantLimiter int
wantRequestID bool
wantEvents int
wantAttempts int
}{
{
name: "invalid auth", method: http.MethodPost, target: "/v1/chat/completions",
accessKey: "wrong", body: `{"model":"gpt-4o"}`,
},
{
name: "unknown endpoint", method: http.MethodPost, target: "/unknown",
accessKey: "gl-client", body: `{}`,
},
{
name: "models endpoint", method: http.MethodGet, target: "/v1/models",
accessKey: "gl-client", decision: ratelimit.LimitDecision{Allowed: true},
wantLimiter: 1, wantRequestID: true,
},
{
name: "inference RPM 429", method: http.MethodPost, target: "/v1/chat/completions",
accessKey: "gl-client", body: `{"model":"gpt-4o"}`,
decision: ratelimit.LimitDecision{Allowed: false, RetryAfter: time.Second},
wantLimiter: 1, wantRequestID: true, wantEvents: 1,
},
{
name: "body model error", method: http.MethodPost, target: "/v1/chat/completions",
accessKey: "gl-client", body: `{}`,
decision: ratelimit.LimitDecision{Allowed: true},
wantLimiter: 1, wantRequestID: true, wantEvents: 1,
},
{
name: "one request with retry", method: http.MethodPost, target: "/v1/chat/completions",
accessKey: "gl-client", body: `{"model":"gpt-4o"}`,
decision: ratelimit.LimitDecision{Allowed: true},
upstreamKeys: []string{"sk-first", "sk-second"},
results: []UpstreamResult{
{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: []byte(`{"error":{"message":"invalid key"}}`),
ClassificationBody: []byte(
`{"error":{"message":"invalid key"}}`,
),
ErrorSummary: "invalid key",
RequestWritten: true,
},
{
StatusCode: http.StatusOK, Header: make(http.Header),
Body: []byte(`{"ok":true}`), RequestWritten: true,
},
},
wantLimiter: 1, wantRequestID: true, wantEvents: 1, wantAttempts: 2,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
forwarder := &scriptedForwarder{results: test.results}
limiter := &recordingAccessKeyRPMLimiter{
decisions: []ratelimit.LimitDecision{test.decision},
}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink, test.upstreamKeys...,
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(
test.method, test.target, strings.NewReader(test.body),
)
request.Header.Set("Authorization", "Bearer "+test.accessKey)
recorder := httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
if got := len(limiter.snapshot()); got != test.wantLimiter {
t.Fatalf("limiter calls = %d, want %d", got, test.wantLimiter)
}
hasRequestID := recorder.Header().Get(requestIDHeader) != ""
if hasRequestID != test.wantRequestID {
t.Fatalf("has request ID = %t, want %t", hasRequestID, test.wantRequestID)
}
events := sink.snapshot()
if len(events) != test.wantEvents {
t.Fatalf("event count = %d, want %d: %#v", len(events), test.wantEvents, events)
}
if len(events) == 1 && len(events[0].Attempts) != test.wantAttempts {
t.Fatalf(
"attempt count = %d, want %d: %#v",
len(events[0].Attempts), test.wantAttempts, events[0],
)
}
})
}
}
func TestHandlerRecordsLocalInferenceFailuresWithoutAttempts(t *testing.T) {
tests := []struct {
name string
body io.ReadCloser
wantStatus int
wantCode string
}{
{
name: "body read error", body: failingReadCloser{},
wantStatus: http.StatusBadRequest, wantCode: "invalid_protocol_request",
},
{
name: "model extraction error", body: io.NopCloser(strings.NewReader(`{}`)),
wantStatus: http.StatusBadRequest, wantCode: "invalid_protocol_request",
},
{
name: "no candidate", body: io.NopCloser(strings.NewReader(`{"model":"gpt-4o"}`)),
wantStatus: http.StatusServiceUnavailable, wantCode: "no_available_candidate",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
forwarder := &scriptedForwarder{}
limiter := &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
engine, _, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink,
)
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
request.Body = test.body
request.Header.Set("Authorization", "Bearer gl-client")
recorder := httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
events := sink.snapshot()
if recorder.Code != test.wantStatus || len(events) != 1 ||
events[0].Status != telemetry.RequestStatusError ||
events[0].StatusCode != test.wantStatus ||
events[0].ErrorCode != test.wantCode ||
len(events[0].Attempts) != 0 {
t.Fatalf("response/event = %d/%#v", recorder.Code, events)
}
if events[0].ErrorSummary == "" {
t.Fatal("local failure summary is empty")
}
})
}
}
func TestHandlerPrioritizesClientCancellationOverLocalInferenceErrors(t *testing.T) {
sentinel := errors.New("local inference failed after cancellation")
tests := []struct {
name string
body func(context.CancelFunc) io.ReadCloser
cancelOnExtract bool
wantExtract int
}{
{
name: "body read error after cancellation",
body: func(cancel context.CancelFunc) io.ReadCloser {
return cancelingErrorReadCloser{cancel: cancel, err: sentinel}
},
},
{
name: "model extraction error after cancellation",
body: func(context.CancelFunc) io.ReadCloser {
return io.NopCloser(strings.NewReader(`{"model":"gpt-4o"}`))
},
cancelOnExtract: true,
wantExtract: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
requestContext, cancel := context.WithCancel(context.Background())
defer cancel()
forwarder := &scriptedForwarder{}
limiter := &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
engine, handler, _, registry := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink,
)
stats := health.NewStatsStore()
handler.stats = stats
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
extractDialect := &cancelingExtractDialect{
Dialect: handler.dialects[protocol.OpenAICompletions],
err: sentinel,
}
if test.cancelOnExtract {
extractDialect.cancel = cancel
}
handler.dialects[protocol.OpenAICompletions] = extractDialect
handler.now = func() time.Time {
t.Fatal("canceled local failure entered health timing")
return time.Time{}
}
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
nil,
).WithContext(requestContext)
request.Body = test.body(cancel)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
if calls := limiter.snapshot(); len(calls) != 1 {
t.Fatalf("limiter calls = %#v, want one admitted request", calls)
}
if response.Code == http.StatusBadRequest ||
strings.Contains(response.Body.String(), "invalid_protocol_request") {
t.Fatalf(
"response = %d %q, canceled local failure must not write 400",
response.Code,
response.Body.String(),
)
}
events := sink.snapshot()
if len(events) != 1 ||
events[0].Status != telemetry.RequestStatusCanceled ||
events[0].StatusCode != 0 ||
events[0].ErrorCode != "client_canceled" ||
len(events[0].Attempts) != 0 {
t.Fatalf(
"events = %#v, want one zero-attempt client cancellation",
events,
)
}
if extractDialect.extractCalls != test.wantExtract {
t.Fatalf(
"ExtractModel calls = %d, want %d",
extractDialect.extractCalls,
test.wantExtract,
)
}
if len(forwarder.inputs)+len(forwarder.streamInputs) != 0 {
t.Fatal("canceled local failure reached upstream")
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 ||
runtimeRegistry.clearCalls != 0 {
t.Fatalf("Registry side effects = %#v", runtimeRegistry)
}
if got := stats.Snapshot(1, time.Now()); got != (health.CredentialStats{}) {
t.Fatalf("StatsStore side effects = %#v, want zero", got)
}
})
}
}
func TestHandlerRecordsNonStreamingRetryChain(t *testing.T) {
t.Run("actual retry marks only the previous attempt", func(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{
{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: []byte(`{"error":{"message":" first \n invalid\tkey "}}`),
ClassificationBody: []byte(
`{"error":{"message":" first \n invalid\tkey "}}`,
),
ErrorSummary: "first invalid key",
RequestWritten: true,
},
{
StatusCode: http.StatusOK, Header: make(http.Header),
Body: []byte(`{"ok":true}`), RequestWritten: true,
},
}}
limiter := &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink, "sk-first", "sk-second",
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
recorder := httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
events := sink.snapshot()
if recorder.Code != http.StatusOK || len(events) != 1 {
t.Fatalf("response/event count = %d/%d", recorder.Code, len(events))
}
event := events[0]
if event.Status != telemetry.RequestStatusSuccess || event.StatusCode != http.StatusOK ||
event.ErrorCode != "" || event.ClientModel != "gpt-4o" ||
event.UpstreamModel != "gpt-4o" || len(event.Attempts) != 2 {
t.Fatalf("event = %#v", event)
}
first, second := event.Attempts[0], event.Attempts[1]
if first.Sequence != 1 || !first.WillRetry ||
first.FailureCategory != telemetry.FailureCategoryInvalidKey ||
first.Action != telemetry.ActionFailCredential ||
first.ErrorCode != "upstream_invalid_key" ||
first.ErrorSummary != "first invalid key" ||
first.DurationMs <= 0 {
t.Fatalf("first attempt = %#v", first)
}
if second.Sequence != 2 || second.WillRetry ||
second.FailureCategory != telemetry.FailureCategoryOK ||
second.Action != telemetry.ActionTerminate ||
second.ErrorCode != "" ||
second.DurationMs <= 0 {
t.Fatalf("second attempt = %#v", second)
}
})
t.Run("candidate exhaustion does not claim a retry", func(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: []byte(`{"error":{"message":"invalid key"}}`),
ClassificationBody: []byte(
`{"error":{"message":"invalid key"}}`,
),
ErrorSummary: "invalid key",
RequestWritten: true,
}}}
sink := &recordingRequestLogSink{}
engine, handler, _, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-only",
)
handler.newRandom = func() *rand.Rand { return rand.New(zeroSource{}) }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 1 ||
events[0].Attempts[0].WillRetry {
t.Fatalf("events = %#v, exhausted candidate must keep WillRetry=false", events)
}
})
}
func TestHandlerRecordsCommittedStreamTerminalMatrix(t *testing.T) {
const downstreamBody = "data: terminal\n\n"
type terminalCase struct {
name string
observation StreamObservation
err error
cancelRequest bool
wantStatus telemetry.RequestStatus
wantCode string
wantSummary string
wantCategory telemetry.FailureCategory
wantHTTPStatus int
}
tests := []terminalCase{
{
name: "clean terminal survives later request cancellation",
observation: StreamObservation{
EndReason: StreamEndCleanEOF,
},
err: &streamFailure{
kind: streamFailureClientCanceled,
err: context.Canceled,
},
cancelRequest: true,
wantStatus: telemetry.RequestStatusSuccess,
wantCategory: telemetry.FailureCategoryOK,
wantHTTPStatus: http.StatusOK,
},
{
name: "SSE error then clean EOF",
observation: StreamObservation{
EndReason: StreamEndSSEError,
ErrorSummary: "safe first SSE error",
},
wantStatus: telemetry.RequestStatusError,
wantCode: "upstream_sse_error",
wantSummary: "safe first SSE error",
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "abrupt upstream read failure",
observation: StreamObservation{
EndReason: StreamEndUpstreamTerminated,
},
err: &streamFailure{
kind: streamFailureUpstreamRead,
err: errors.New("private upstream read detail"),
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "upstream_stream_terminated",
wantSummary: fixedErrorSummary("upstream_stream_terminated"),
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "committed protocol rewrite failure",
observation: StreamObservation{
EndReason: StreamEndUpstreamProtocolError,
},
err: &streamFailure{
kind: streamFailureProtocol,
err: fmt.Errorf("%w: private rewrite detail", ErrUpstreamProtocol),
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "upstream_protocol_error",
wantSummary: fixedErrorSummary("upstream_protocol_error"),
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "idle timeout",
observation: StreamObservation{
EndReason: StreamEndIdleTimeout,
},
err: &streamFailure{
kind: streamFailureIdle,
err: errStreamIdleTimeout,
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "upstream_stream_idle_timeout",
wantSummary: fixedErrorSummary("upstream_stream_idle_timeout"),
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "downstream write failure",
observation: StreamObservation{
EndReason: StreamEndDownstreamWriteFailure,
},
err: &streamFailure{
kind: streamFailureDownstreamWrite,
err: errors.New("private downstream write detail"),
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "downstream_write_failed",
wantSummary: fixedErrorSummary("downstream_write_failed"),
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "downstream flush failure",
observation: StreamObservation{
EndReason: StreamEndDownstreamWriteFailure,
},
err: &streamFailure{
kind: streamFailureDownstreamWrite,
err: errors.New("private downstream flush detail"),
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "downstream_write_failed",
wantSummary: fixedErrorSummary("downstream_write_failed"),
wantCategory: telemetry.FailureCategoryAmbiguous,
wantHTTPStatus: http.StatusOK,
},
{
name: "client cancellation",
observation: StreamObservation{
EndReason: StreamEndClientCanceled,
},
err: &streamFailure{
kind: streamFailureClientCanceled,
err: context.Canceled,
},
cancelRequest: true,
wantStatus: telemetry.RequestStatusCanceled,
wantCode: "client_canceled",
wantSummary: fixedErrorSummary("client_canceled"),
wantCategory: telemetry.FailureCategoryDownstreamCancel,
wantHTTPStatus: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
requestContext, cancel := context.WithCancel(context.Background())
defer cancel()
forwarder := &scriptedForwarder{
invokeStreamReady: true,
streamResults: []UpstreamResult{{
StatusCode: http.StatusOK,
RequestWritten: true,
Committed: true,
Err: test.err,
Stream: test.observation,
}},
}
forwarder.onStreamCall = func(_ int, writer http.ResponseWriter) {
writer.WriteHeader(http.StatusOK)
_, _ = io.WriteString(writer, downstreamBody)
if test.cancelRequest {
cancel()
}
}
sink := &recordingRequestLogSink{}
engine, handler, _, registry := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-one",
)
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
now := time.Date(2026, time.July, 24, 13, 0, 0, 0, time.UTC)
handler.now = func() time.Time { return now }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
).WithContext(requestContext)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one event with one attempt", events)
}
event := events[0]
attempt := event.Attempts[0]
if event.Status != test.wantStatus ||
event.StatusCode != test.wantHTTPStatus ||
event.ErrorCode != test.wantCode ||
event.ErrorSummary != test.wantSummary ||
event.UpstreamModel != "gpt-4o" {
t.Fatalf("event = %#v", event)
}
if attempt.FailureCategory != test.wantCategory ||
attempt.Action != telemetry.ActionTerminate ||
attempt.WillRetry ||
attempt.ErrorCode != test.wantCode ||
attempt.Committed != true {
t.Fatalf("attempt = %#v", attempt)
}
if test.wantCode != "" && attempt.ErrorSummary == "" {
t.Fatalf("attempt summary is empty: %#v", attempt)
}
if len(forwarder.streamInputs) != 1 {
t.Fatalf("stream attempts = %d, want 1", len(forwarder.streamInputs))
}
if response.Code != http.StatusOK || response.Body.String() != downstreamBody {
t.Fatalf(
"downstream status/body = %d/%q, want unchanged %d/%q",
response.Code,
response.Body.String(),
http.StatusOK,
downstreamBody,
)
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 ||
runtimeRegistry.clearCalls != 1 {
t.Fatalf("Registry side effects = %#v", runtimeRegistry)
}
if got := handler.stats.Snapshot(1, now); got != (health.CredentialStats{Success: 1}) {
t.Fatalf("StatsStore side effects = %#v, want existing ready success only", got)
}
})
}
}
func TestHandlerStreamTelemetryDoesNotChangeHealthOrRetrySideEffects(t *testing.T) {
tests := []struct {
name string
observation StreamObservation
err error
}{
{
name: "SSE error observation",
observation: StreamObservation{
EndReason: StreamEndSSEError,
ErrorSummary: "safe SSE error",
},
},
{
name: "upstream termination observation",
observation: StreamObservation{
EndReason: StreamEndUpstreamTerminated,
},
err: &streamFailure{
kind: streamFailureUpstreamRead,
err: errors.New("private upstream detail"),
},
},
{
name: "downstream failure observation",
observation: StreamObservation{
EndReason: StreamEndDownstreamWriteFailure,
},
err: &streamFailure{
kind: streamFailureDownstreamWrite,
err: errors.New("private downstream detail"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
forwarder := &scriptedForwarder{streamResults: []UpstreamResult{
{
StatusCode: http.StatusOK,
RequestWritten: true,
Committed: true,
Err: test.err,
Stream: test.observation,
},
{
StatusCode: http.StatusOK,
RequestWritten: true,
Committed: true,
Stream: StreamObservation{
EndReason: StreamEndCleanEOF,
},
},
}}
sink := &recordingRequestLogSink{}
engine, handler, _, registry := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-one",
"sk-two",
)
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
now := time.Date(2026, time.July, 24, 14, 0, 0, 0, time.UTC)
handler.now = func() time.Time { return now }
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
if len(forwarder.streamInputs) != 1 {
t.Fatalf("stream attempts = %d, want observation-only terminal return", len(forwarder.streamInputs))
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 ||
runtimeRegistry.clearCalls != 0 {
t.Fatalf("Registry side effects = %#v", runtimeRegistry)
}
if got := handler.stats.Snapshot(1, now); got != (health.CredentialStats{}) {
t.Fatalf("StatsStore side effects = %#v, want zero", got)
}
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one observed terminal event", events)
}
})
}
}
func TestHandlerRecordsDownstreamWriteFailureWithoutChangingResponse(t *testing.T) {
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": {"application/json"}},
Body: []byte(`{"ok":true}`),
}}}
limiter := &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
_, handler, _, _ := newRequestLogHandlerTestRuntime(
t, forwarder, limiter, sink, "sk-one",
)
base := httptest.NewRecorder()
ginContext, _ := gin.CreateTestContext(base)
writeFailure := errors.New("downstream disconnected")
writes := 0
ginContext.Writer = &deadlineGinWriter{
ResponseWriter: ginContext.Writer,
write: func([]byte) (int, error) {
writes++
return 0, writeFailure
},
}
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
ginContext.Request = request
prepareAndAuthenticateGatewayContextForTest(
t,
handler,
ginContext,
"data.openai.completions",
)
handler.Handle(ginContext)
events := sink.snapshot()
if base.Code != http.StatusOK || writes != 1 || base.Body.Len() != 0 {
t.Fatalf(
"downstream status/writes/body = %d/%d/%q, want 200/1/empty",
base.Code, writes, base.Body.String(),
)
}
if len(events) != 1 || events[0].Status != telemetry.RequestStatusIncomplete ||
events[0].StatusCode != http.StatusOK ||
events[0].ErrorCode != "downstream_write_failed" ||
len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one incomplete write-failure event", events)
}
}
func TestHandlerRecordsCanceledSuccessfulResponseWithoutHealthSideEffects(t *testing.T) {
requestContext, cancel := context.WithCancel(context.Background())
defer cancel()
forwarder := &scriptedForwarder{
results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": {"application/json"}},
Body: []byte(`{"ok":true}`),
}},
onCall: func(int) { cancel() },
}
stats := health.NewStatsStore()
handler, _, registry := newHandlerForTestWithStats(
t, forwarder, stats, "sk-one",
)
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
handler.limiter = &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
handler.requestLogSink = sink
handler.newRequestID = func() (string, error) { return fixedRequestID, nil }
handler.requestNow = newSteppingRequestClock()
engine := gin.New()
bindGatewayRoutesForTest(t, engine, handler)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
).WithContext(requestContext)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want one canceled event", events)
}
event := events[0]
if event.Status != telemetry.RequestStatusCanceled ||
event.StatusCode != 0 ||
event.ErrorCode != "client_canceled" ||
len(event.Attempts) != 1 {
t.Fatalf("event = %#v, want uncommitted client cancellation", event)
}
if event.Usage.Result != (usage.Result{State: usage.StateNotApplicable}) ||
event.Usage.GroupID != 1 || event.Usage.CredentialID != 1 ||
event.Usage.AttemptSequence != 1 {
t.Fatalf("event Usage = %#v, want canceled attempt route attribution", event.Usage)
}
attempt := event.Attempts[0]
if attempt.FailureCategory != telemetry.FailureCategoryDownstreamCancel ||
attempt.Action != telemetry.ActionTerminate ||
attempt.WillRetry {
t.Fatalf("attempt = %#v, want read-only downstream_cancel/terminate", attempt)
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 ||
runtimeRegistry.clearCalls != 0 {
t.Fatalf("Registry side effects = %#v", runtimeRegistry)
}
if got := stats.Snapshot(1, time.Now()); got != (health.CredentialStats{}) {
t.Fatalf("StatsStore side effects = %#v", got)
}
}
func TestHandlerRecordsServerShutdownInsteadOfClientCancellation(t *testing.T) {
coordinator := httplifecycle.NewCoordinator()
forwarder := &scriptedForwarder{
results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": {"application/json"}},
Body: []byte(`{"ok":true}`),
}},
}
forwarder.onCall = func(int) { coordinator.BeginShutdown() }
stats := health.NewStatsStore()
handler, _, registry := newHandlerForTestWithStats(
t, forwarder, stats, "sk-one",
)
handler.lifecycle = coordinator
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
handler.limiter = &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
handler.requestLogSink = sink
handler.newRequestID = func() (string, error) { return fixedRequestID, nil }
handler.requestNow = newSteppingRequestClock()
engine := gin.New()
bindGatewayRoutesForTest(t, engine, handler)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
engine.ServeHTTP(httptest.NewRecorder(), request)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want one canceled event", events)
}
event := events[0]
if event.Status != telemetry.RequestStatusCanceled ||
event.StatusCode != 0 ||
event.ErrorCode != "server_shutdown" ||
event.ErrorSummary != fixedErrorSummary("server_shutdown") ||
len(event.Attempts) != 1 {
t.Fatalf("event = %#v, want server-shutdown cancellation", event)
}
attempt := event.Attempts[0]
if attempt.FailureCategory != telemetry.FailureCategoryDownstreamCancel ||
attempt.Action != telemetry.ActionTerminate ||
attempt.WillRetry {
t.Fatalf("attempt = %#v, want read-only downstream_cancel/terminate", attempt)
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 ||
runtimeRegistry.clearCalls != 0 {
t.Fatalf("Registry side effects = %#v", runtimeRegistry)
}
if got := stats.Snapshot(1, time.Now()); got != (health.CredentialStats{}) {
t.Fatalf("StatsStore side effects = %#v, want zero", got)
}
}
func TestHandlerRecordsStreamServerShutdownInsteadOfClientCancellation(t *testing.T) {
coordinator := httplifecycle.NewCoordinator()
forwarder := &scriptedForwarder{
streamResults: []UpstreamResult{{
StatusCode: http.StatusOK,
RequestWritten: true,
Committed: true,
Err: context.Canceled,
}},
}
forwarder.onStreamCall = func(_ int, writer http.ResponseWriter) {
writer.WriteHeader(http.StatusOK)
_, _ = io.WriteString(writer, `data: {"type":"response.output_text.delta"}`+"\n\n")
coordinator.BeginShutdown()
}
stats := health.NewStatsStore()
handler, _, registry := newHandlerForTestWithStats(
t, forwarder, stats, "sk-one",
)
handler.lifecycle = coordinator
runtimeRegistry := &recordingRuntimeRegistry{CredentialRegistry: registry}
handler.registry = runtimeRegistry
handler.limiter = &recordingAccessKeyRPMLimiter{}
sink := &recordingRequestLogSink{}
handler.requestLogSink = sink
handler.newRequestID = func() (string, error) { return fixedRequestID, nil }
handler.requestNow = newSteppingRequestClock()
engine := gin.New()
bindGatewayRoutesForTest(t, engine, handler)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o","stream":true}`),
)
request.Header.Set("Authorization", "Bearer gl-client")
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one stream cancellation with one attempt", events)
}
event := events[0]
attempt := event.Attempts[0]
if event.Status != telemetry.RequestStatusCanceled ||
event.StatusCode != http.StatusOK ||
event.ErrorCode != "server_shutdown" ||
event.ErrorSummary != fixedErrorSummary("server_shutdown") ||
attempt.ErrorCode != "server_shutdown" ||
attempt.FailureCategory != telemetry.FailureCategoryDownstreamCancel ||
attempt.Action != telemetry.ActionTerminate ||
attempt.WillRetry {
t.Fatalf("stream event/attempt = %#v/%#v", event, attempt)
}
if runtimeRegistry.cooldownCalls != 0 ||
runtimeRegistry.incrFailureCalls != 0 ||
runtimeRegistry.blacklistCalls != 0 {
t.Fatalf("Registry failure side effects = %#v", runtimeRegistry)
}
if got := stats.Snapshot(1, time.Now()); got != (health.CredentialStats{}) {
t.Fatalf("StatsStore side effects = %#v, want zero", got)
}
}
func TestHandlerPrioritizesClientCancellationOverDownstreamWriteFailure(t *testing.T) {
tests := []struct {
name string
writer func(*gin.Context, context.CancelFunc, error) gin.ResponseWriter
wantStatus telemetry.RequestStatus
wantCode string
wantHTTP int
}{
{
name: "committed body write cancellation",
writer: func(
ginContext *gin.Context,
cancel context.CancelFunc,
writeErr error,
) gin.ResponseWriter {
return &deadlineGinWriter{
ResponseWriter: ginContext.Writer,
write: func([]byte) (int, error) {
cancel()
return 0, writeErr
},
}
},
wantStatus: telemetry.RequestStatusCanceled,
wantCode: "client_canceled",
wantHTTP: http.StatusOK,
},
{
name: "uncommitted header write cancellation",
writer: func(
ginContext *gin.Context,
cancel context.CancelFunc,
writeErr error,
) gin.ResponseWriter {
return &cancelingHeaderDeadlineWriter{
ResponseWriter: ginContext.Writer,
cancel: cancel,
err: writeErr,
}
},
wantStatus: telemetry.RequestStatusCanceled,
wantCode: "client_canceled",
wantHTTP: 0,
},
{
name: "ordinary write failure",
writer: func(
ginContext *gin.Context,
_ context.CancelFunc,
writeErr error,
) gin.ResponseWriter {
return &deadlineGinWriter{
ResponseWriter: ginContext.Writer,
write: func([]byte) (int, error) {
return 0, writeErr
},
}
},
wantStatus: telemetry.RequestStatusIncomplete,
wantCode: "downstream_write_failed",
wantHTTP: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
requestContext, cancel := context.WithCancel(context.Background())
defer cancel()
forwarder := &scriptedForwarder{results: []UpstreamResult{{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": {"application/json"}},
Body: []byte(`{"ok":true}`),
}}}
sink := &recordingRequestLogSink{}
_, handler, _, _ := newRequestLogHandlerTestRuntime(
t,
forwarder,
&recordingAccessKeyRPMLimiter{},
sink,
"sk-one",
)
base := httptest.NewRecorder()
ginContext, _ := gin.CreateTestContext(base)
writeErr := errors.New("downstream write failed")
ginContext.Writer = test.writer(ginContext, cancel, writeErr)
request := httptest.NewRequest(
http.MethodPost,
"/v1/chat/completions",
strings.NewReader(`{"model":"gpt-4o"}`),
).WithContext(requestContext)
request.Header.Set("Authorization", "Bearer gl-client")
ginContext.Request = request
prepareAndAuthenticateGatewayContextForTest(
t,
handler,
ginContext,
"data.openai.completions",
)
handler.Handle(ginContext)
events := sink.snapshot()
if len(events) != 1 {
t.Fatalf("events = %#v, want one write-terminal event", events)
}
event := events[0]
if event.Status != test.wantStatus ||
event.ErrorCode != test.wantCode ||
event.StatusCode != test.wantHTTP ||
event.UpstreamModel != "gpt-4o" ||
event.Usage.GroupID != 1 ||
event.Usage.CredentialID != 1 ||
event.Usage.AttemptSequence != 1 {
t.Fatalf(
"event status/code/http = %q/%q/%d, want %q/%q/%d with route attribution: %#v",
event.Status,
event.ErrorCode,
event.StatusCode,
test.wantStatus,
test.wantCode,
test.wantHTTP,
event,
)
}
})
}
}
func TestRequestRecorderRedactsHeaderRuleLiteralsFromNonStreamingAndSSESummaries(t *testing.T) {
const (
apiKey = "fake-recorder-provider-key"
customRule = "opaque literal\tvalue"
ordinaryEncoding = "gzip"
)
selection := scheduler.Selection{
GroupID: 11, ChannelID: channel.OpenAI, CredentialID: 21,
RouteMode: channel.RouteNative,
Group: state.GroupView{
ID: 11, ChannelID: channel.OpenAI,
HeaderRules: state.HeaderRules{Set: map[string]string{
"X-Custom": customRule,
"Accept-Encoding": ordinaryEncoding,
}},
},
}
for _, test := range []struct {
name string
record func(*requestRecorder, UpstreamResult) int
finish func(*requestRecorder, UpstreamResult, int)
result UpstreamResult
}{
{
name: "non-streaming",
result: UpstreamResult{
StatusCode: http.StatusBadRequest,
ErrorSummary: "echo opaque literal value " + ordinaryEncoding,
},
record: func(recorder *requestRecorder, result UpstreamResult) int {
return recorder.recordAttempt(
selection,
[]string{apiKey},
result,
health.Result{
Category: health.FailureCategoryClientError,
Action: health.ActionTerminate,
},
time.Unix(100, 0),
time.Unix(101, 0),
)
},
finish: func(recorder *requestRecorder, result UpstreamResult, attempt int) {
recorder.completeResponse(
result,
health.Result{Category: health.FailureCategoryClientError},
"provider-model",
attempt,
)
},
},
{
name: "SSE",
result: UpstreamResult{
StatusCode: http.StatusOK,
Committed: true,
Stream: StreamObservation{
EndReason: StreamEndSSEError,
ErrorSummary: "echo opaque literal value " + ordinaryEncoding,
},
},
record: func(recorder *requestRecorder, result UpstreamResult) int {
return recorder.recordStreamAttempt(
selection,
[]string{apiKey},
result,
time.Unix(100, 0),
time.Unix(101, 0),
)
},
finish: func(recorder *requestRecorder, result UpstreamResult, attempt int) {
recorder.completeStream(result, "provider-model", attempt)
},
},
} {
t.Run(test.name, func(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"req-header-rule-summary-"+test.name,
time.Unix(100, 0),
9,
protocol.OpenAICompletions,
func() time.Time { return time.Unix(102, 0) },
)
attempt := test.record(recorder, test.result)
test.finish(recorder, test.result, attempt)
recorder.emit()
if len(sink.events) != 1 || len(sink.events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one event with one attempt", sink.events)
}
event := sink.events[0]
for _, surface := range []string{
event.ErrorSummary,
event.Attempts[0].ErrorSummary,
} {
for _, forbidden := range []string{
apiKey,
customRule,
"opaque literal value",
ordinaryEncoding,
} {
if strings.Contains(surface, forbidden) {
t.Fatalf("%s summary leaked %q: %q", test.name, forbidden, surface)
}
}
if !strings.Contains(surface, redact.Placeholder) {
t.Fatalf("%s summary = %q, want redaction placeholder", test.name, surface)
}
}
})
}
}
func TestRequestRecorderPreservesFixedTerminalSummariesWithShortHeaderRuleLiteral(t *testing.T) {
const shortLiteral = "a"
selection := scheduler.Selection{
GroupID: 11, ChannelID: channel.OpenAI, CredentialID: 21,
RouteMode: channel.RouteNative,
Group: state.GroupView{
ID: 11, ChannelID: channel.OpenAI,
HeaderRules: state.HeaderRules{Set: map[string]string{
"X-Short": shortLiteral,
}},
},
}
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"req-fixed-terminal-summary",
time.Unix(100, 0),
9,
protocol.OpenAICompletions,
func() time.Time { return time.Unix(102, 0) },
)
result := UpstreamResult{
StatusCode: http.StatusOK,
Committed: true,
Stream: streamTerminalObservation(
StreamEndUpstreamTerminated,
),
}
attempt := recorder.recordStreamAttempt(
selection,
[]string{"fake-fixed-summary-provider-key"},
result,
time.Unix(100, 0),
time.Unix(101, 0),
)
recorder.completeStream(result, "provider-model", attempt)
recorder.emit()
if len(sink.events) != 1 || len(sink.events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one event with one attempt", sink.events)
}
want := fixedErrorSummary("upstream_stream_terminated")
event := sink.events[0]
if event.ErrorSummary != want || event.Attempts[0].ErrorSummary != want {
t.Fatalf(
"fixed summaries = outcome %q / attempt %q, want %q",
event.ErrorSummary,
event.Attempts[0].ErrorSummary,
want,
)
}
}
func TestRequestRecorderPreservesProviderErrorFixedSummaryWithOrdinaryHeaderRules(t *testing.T) {
const marker = "rate_limit_error"
selection := scheduler.Selection{
GroupID: 11, ChannelID: channel.OpenAI, CredentialID: 21,
RouteMode: channel.RouteNative,
Group: state.GroupView{
ID: 11, ChannelID: channel.OpenAI,
HeaderRules: state.HeaderRules{Set: map[string]string{
"X-Ordinary-Marker": marker,
"X-Short-Literal": "a",
}},
},
}
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"req-provider-fixed-summary",
time.Unix(100, 0),
9,
protocol.OpenAICompletions,
func() time.Time { return time.Unix(102, 0) },
)
result := UpstreamResult{
StatusCode: http.StatusOK,
ClassificationBody: []byte(`{"error":{"type":"` + marker + `"}}`),
ErrorSummary: fixedErrorSummary("upstream_sse_error"),
RequestWritten: true,
ProviderErrorBeforeCommit: true,
Usage: usage.Result{State: usage.StateMissing},
}
decision := health.Result{
Category: health.FailureCategoryRateLimited,
Action: health.ActionCooldownCredential,
}
recorder.recordAttempt(
selection,
[]string{"fake-provider-fixed-summary-key"},
result,
decision,
time.Unix(100, 0),
time.Unix(101, 0),
)
recorder.emit()
events := sink.snapshot()
if len(events) != 1 || len(events[0].Attempts) != 1 {
t.Fatalf("events = %#v, want one event with one attempt", events)
}
want := fixedErrorSummary("upstream_sse_error")
if got := events[0].Attempts[0].ErrorSummary; got != want {
t.Fatalf("provider fixed attempt summary = %q, want %q", got, want)
}
if serialized := fmt.Sprintf("%#v", events[0]); strings.Contains(serialized, marker) {
t.Fatalf("request log leaked ordinary Provider marker %q: %s", marker, serialized)
}
}
func TestRequestLogSummaryRetainsSafeErrorMessagesAndUTF8Limit(t *testing.T) {
redactor := redact.New()
const fallback = "Upstream request failed."
tests := []struct {
name string
body string
want string
}{
{
name: "error message first",
body: `{"error":{"message":" first \n value ","detail":"second"},"message":"third"}`,
want: "first value",
},
{
name: "error detail",
body: `{"error":{"detail":"nested detail"},"message":"top message"}`,
want: "nested detail",
},
{name: "top message", body: `{"message":"top message"}`, want: "top message"},
{name: "top detail", body: `{"detail":"top detail"}`, want: "top detail"},
{name: "string error", body: `{"error":"string error"}`, want: "string error"},
{
name: "disallowed nested path",
body: `{"debug":{"message":"must not persist"}}`,
want: fallback,
},
{name: "non-string allowed path", body: `{"error":{"message":{"secret":"x"}}}`, want: fallback},
{name: "plain text", body: `auth_unavailable: no auth available`, want: "auth_unavailable: no auth available"},
{name: "HTML", body: `<html><body>upstream failure</body></html>`, want: fallback},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := summarizeErrorBody(redactor, []byte(test.body), fallback); got != test.want {
t.Fatalf("summarizeErrorBody() = %q, want %q", got, test.want)
}
})
}
long := strings.Repeat("界", 2_000)
summary := summarizeErrorBody(
redactor,
[]byte(`{"message":"`+long+`"}`),
fallback,
)
if len(summary) <= 1024 || len(summary) > maxRequestLogSummaryBytes || !utf8.ValidString(summary) ||
!strings.HasSuffix(summary, "...[truncated]") {
t.Fatalf(
"summary bytes/UTF-8/suffix = %d/%t/%q",
len(summary), utf8.ValidString(summary), summary,
)
}
}
func TestRequestRecorderTerminalErrorsUseSanitizedAttemptSummary(t *testing.T) {
selection := scheduler.Selection{
GroupID: 1, ChannelID: channel.OpenAI, CredentialID: 1,
RouteMode: channel.RouteNative,
Group: state.GroupView{ID: 1, Name: "OpenAI", ChannelID: channel.OpenAI},
}
const rawSummary = "auth_unavailable: no auth available (token=upstream-secret)"
const wantSummary = "auth_unavailable: no auth available (token=[REDACTED])"
for _, test := range []struct {
name string
complete func(*requestRecorder, UpstreamResult, string, int)
}{
{
name: "provider error",
complete: func(recorder *requestRecorder, result UpstreamResult, model string, attempt int) {
recorder.completeProviderError(result, model, attempt)
},
},
{
name: "transport error",
complete: func(recorder *requestRecorder, result UpstreamResult, model string, attempt int) {
recorder.completeTransport(reasonUpstreamConnect, model, attempt)
},
},
} {
t.Run(test.name, func(t *testing.T) {
sink := &recordingRequestLogSink{}
recorder := newRequestRecorder(
sink,
"req-terminal-attempt-summary",
time.Unix(100, 0),
1,
protocol.OpenAICompletions,
func() time.Time { return time.Unix(102, 0) },
)
result := UpstreamResult{StatusCode: http.StatusServiceUnavailable, ErrorSummary: rawSummary}
attempt := recorder.recordAttempt(
selection,
nil,
result,
health.Result{Category: health.FailureCategoryUpstreamHostError, Action: health.ActionSkipGroup},
time.Unix(100, 0),
time.Unix(101, 0),
)
test.complete(recorder, result, "gpt-5.6-sol", attempt)
recorder.emit()
events := sink.snapshot()
if len(events) != 1 || events[0].ErrorSummary != wantSummary ||
len(events[0].Attempts) != 1 || events[0].Attempts[0].ErrorSummary != wantSummary {
t.Fatalf("event = %#v, want sanitized attempt summary %q", events, wantSummary)
}
})
}
}