@@ -24,6 +24,42 @@ function jsonResponse(value: unknown, init?: ResponseInit): Response {
2424 } ) ;
2525}
2626
27+ function boundedTextErrorResponse ( body : string , status = 502 ) : {
28+ response : Response ;
29+ cancel : ReturnType < typeof vi . fn > ;
30+ releaseLock : ReturnType < typeof vi . fn > ;
31+ text : ReturnType < typeof vi . fn > ;
32+ } {
33+ const encoded = new TextEncoder ( ) . encode ( body ) ;
34+ let read = false ;
35+ const cancel = vi . fn ( async ( ) => undefined ) ;
36+ const releaseLock = vi . fn ( ) ;
37+ const text = vi . fn ( async ( ) => {
38+ throw new Error ( "response.text() should not be called" ) ;
39+ } ) ;
40+ const response = {
41+ ok : false ,
42+ status,
43+ headers : new Headers ( ) ,
44+ body : {
45+ getReader : ( ) => ( {
46+ read : async ( ) => {
47+ if ( read ) {
48+ return { done : true , value : undefined } ;
49+ }
50+ read = true ;
51+ return { done : false , value : encoded } ;
52+ } ,
53+ cancel,
54+ releaseLock,
55+ } ) ,
56+ } ,
57+ text,
58+ } as unknown as Response ;
59+
60+ return { response, cancel, releaseLock, text } ;
61+ }
62+
2763function requestUrl ( input : RequestInfo | URL ) : string {
2864 if ( typeof input === "string" ) {
2965 return input ;
@@ -180,6 +216,33 @@ describe("OpenRouter OAuth", () => {
180216 ) . rejects . toThrow ( "OpenRouter OAuth key exchange failed (400): Invalid code" ) ;
181217 } ) ;
182218
219+ it ( "bounds OpenRouter OAuth exchange error bodies without requiring response.text()" , async ( ) => {
220+ const errorResponse = boundedTextErrorResponse (
221+ `${ "openrouter denied " . repeat ( 1024 ) } tail-marker` ,
222+ 502 ,
223+ ) ;
224+ const fetchImpl = vi . fn < typeof fetch > ( async ( ) => errorResponse . response ) ;
225+
226+ let error : unknown ;
227+ try {
228+ await exchangeOpenRouterOAuthCode ( {
229+ code : "bad-code" ,
230+ codeVerifier : "bad-verifier" ,
231+ fetchImpl,
232+ } ) ;
233+ } catch ( caught ) {
234+ error = caught ;
235+ }
236+
237+ expect ( error ) . toBeInstanceOf ( Error ) ;
238+ const message = ( error as Error ) . message ;
239+ expect ( message ) . toContain ( "OpenRouter OAuth key exchange failed (502): openrouter denied" ) ;
240+ expect ( message ) . not . toContain ( "tail-marker" ) ;
241+ expect ( errorResponse . text ) . not . toHaveBeenCalled ( ) ;
242+ expect ( errorResponse . cancel ) . toHaveBeenCalledTimes ( 1 ) ;
243+ expect ( errorResponse . releaseLock ) . toHaveBeenCalledTimes ( 1 ) ;
244+ } ) ;
245+
183246 it ( "stores a browser OAuth result as the default OpenRouter API-key profile" , async ( ) => {
184247 const fetchImpl = vi . fn < typeof fetch > ( async ( ) =>
185248 jsonResponse ( { key : "sk-or-v1-test" , user_id : "user-1" } ) ,
0 commit comments