@@ -16,9 +16,10 @@ import (
1616
1717func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess (t * testing.T ) {
1818 withEnabledQueue (t , func () {
19- ginCtx := newTestGinContext (t , http .MethodPost , "/v1/chat/completions" , http .StatusOK )
20- internallogging .SetGinRequestID (ginCtx , "gin-request-id-ignored" )
21- ctx := context .WithValue (internallogging .WithRequestID (context .Background (), "ctx-request-id" ), "gin" , ginCtx )
19+ ctx := internallogging .WithRequestID (context .Background (), "ctx-request-id" )
20+ ctx = internallogging .WithEndpoint (ctx , "POST /v1/chat/completions" )
21+ ctx = internallogging .WithResponseStatusHolder (ctx )
22+ internallogging .SetResponseStatus (ctx , http .StatusOK )
2223
2324 plugin := & usageQueuePlugin {}
2425 plugin .HandleUsage (ctx , coreusage.Record {
@@ -49,9 +50,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess(t *testing.T) {
4950
5051func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID (t * testing.T ) {
5152 withEnabledQueue (t , func () {
52- ginCtx := newTestGinContext (t , http .MethodGet , "/v1/responses" , http .StatusInternalServerError )
53- internallogging .SetGinRequestID (ginCtx , "gin-request-id" )
54- ctx := context .WithValue (context .Background (), "gin" , ginCtx )
53+ ctx := internallogging .WithRequestID (context .Background (), "gin-request-id" )
54+ ctx = internallogging .WithEndpoint (ctx , "GET /v1/responses" )
55+ ctx = internallogging .WithResponseStatusHolder (ctx )
56+ internallogging .SetResponseStatus (ctx , http .StatusInternalServerError )
5557
5658 plugin := & usageQueuePlugin {}
5759 plugin .HandleUsage (ctx , coreusage.Record {
@@ -80,6 +82,47 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t
8082 })
8183}
8284
85+ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext (t * testing.T ) {
86+ withEnabledQueue (t , func () {
87+ ginCtx := newTestGinContext (t , http .MethodPost , "/v1/chat/completions" , http .StatusOK )
88+ ctx := context .WithValue (context .Background (), "gin" , ginCtx )
89+ ctx = internallogging .WithRequestID (ctx , "ctx-request-id" )
90+ ctx = internallogging .WithEndpoint (ctx , "POST /v1/chat/completions" )
91+ ctx = internallogging .WithResponseStatusHolder (ctx )
92+ internallogging .SetResponseStatus (ctx , http .StatusInternalServerError )
93+
94+ mgr := coreusage .NewManager (16 )
95+ defer mgr .Stop ()
96+
97+ mgr .Register (pluginFunc (func (_ context.Context , _ coreusage.Record ) {
98+ ginCtx .Request = httptest .NewRequest (http .MethodGet , "http://example.com/v1/responses" , nil )
99+ ginCtx .Status (http .StatusOK )
100+ }))
101+ mgr .Register (& usageQueuePlugin {})
102+
103+ mgr .Publish (ctx , coreusage.Record {
104+ Provider : "openai" ,
105+ Model : "gpt-5.4" ,
106+ APIKey : "test-key" ,
107+ AuthIndex : "0" ,
108+ AuthType : "apikey" ,
109+ Source : "user@example.com" ,
110+ RequestedAt : time .Date (2026 , 4 , 25 , 0 , 0 , 0 , 0 , time .UTC ),
111+ Latency : 1500 * time .Millisecond ,
112+ Detail : coreusage.Detail {
113+ InputTokens : 10 ,
114+ OutputTokens : 20 ,
115+ TotalTokens : 30 ,
116+ },
117+ })
118+
119+ payload := waitForSinglePayload (t , 2 * time .Second )
120+ requireStringField (t , payload , "endpoint" , "POST /v1/chat/completions" )
121+ requireStringField (t , payload , "request_id" , "ctx-request-id" )
122+ requireBoolField (t , payload , "failed" , true )
123+ })
124+ }
125+
83126func withEnabledQueue (t * testing.T , fn func ()) {
84127 t .Helper ()
85128
@@ -127,6 +170,29 @@ func popSinglePayload(t *testing.T) map[string]json.RawMessage {
127170 return payload
128171}
129172
173+ func waitForSinglePayload (t * testing.T , timeout time.Duration ) map [string ]json.RawMessage {
174+ t .Helper ()
175+
176+ deadline := time .Now ().Add (timeout )
177+ for time .Now ().Before (deadline ) {
178+ items := PopOldest (10 )
179+ if len (items ) == 0 {
180+ time .Sleep (10 * time .Millisecond )
181+ continue
182+ }
183+ if len (items ) != 1 {
184+ t .Fatalf ("PopOldest() items = %d, want 1" , len (items ))
185+ }
186+ var payload map [string ]json.RawMessage
187+ if err := json .Unmarshal (items [0 ], & payload ); err != nil {
188+ t .Fatalf ("unmarshal payload: %v" , err )
189+ }
190+ return payload
191+ }
192+ t .Fatalf ("timeout waiting for queued payload" )
193+ return nil
194+ }
195+
130196func requireStringField (t * testing.T , payload map [string ]json.RawMessage , key , want string ) {
131197 t .Helper ()
132198
@@ -143,6 +209,12 @@ func requireStringField(t *testing.T, payload map[string]json.RawMessage, key, w
143209 }
144210}
145211
212+ type pluginFunc func (context.Context , coreusage.Record )
213+
214+ func (fn pluginFunc ) HandleUsage (ctx context.Context , record coreusage.Record ) {
215+ fn (ctx , record )
216+ }
217+
146218func requireBoolField (t * testing.T , payload map [string ]json.RawMessage , key string , want bool ) {
147219 t .Helper ()
148220
0 commit comments