@@ -862,6 +862,60 @@ describe("runtime rotation proxy", () => {
862862 } ) ;
863863 } ) ;
864864
865+ it ( "records a concurrent deactivation failure once for the account" , async ( ) => {
866+ const now = Date . now ( ) ;
867+ const accountManager = new AccountManager ( undefined , createStorage ( now , 1 ) ) ;
868+ const recordFailureSpy = vi . spyOn ( accountManager , "recordFailure" ) ;
869+ let disabledCalls = 0 ;
870+ let releaseDisabledCalls : ( ( ) => void ) | null = null ;
871+ const allDisabledCallsArrived = new Promise < void > ( ( resolve ) => {
872+ releaseDisabledCalls = resolve ;
873+ } ) ;
874+ const { calls, fetchImpl } = createRecordingFetch ( async ( call ) => {
875+ if ( call . headers . get ( OPENAI_HEADERS . ACCOUNT_ID ) === "acc_1" ) {
876+ disabledCalls += 1 ;
877+ if ( disabledCalls === 2 ) releaseDisabledCalls ?.( ) ;
878+ await allDisabledCallsArrived ;
879+ return new Response (
880+ JSON . stringify ( { error : { code : "deactivated_workspace" } } ) ,
881+ {
882+ status : 402 ,
883+ headers : { "content-type" : "application/json" } ,
884+ } ,
885+ ) ;
886+ }
887+ return textEventStream ( "data: recovered\n\n" ) ;
888+ } ) ;
889+ const proxy = await startProxy ( { accountManager, fetchImpl } ) ;
890+ const body = {
891+ model : "gpt-5-codex" ,
892+ stream : true ,
893+ metadata : { session_id : "thread-concurrent-deactivated" } ,
894+ } ;
895+
896+ const responses = await Promise . all ( [ postResponses ( proxy , body ) , postResponses ( proxy , body ) ] ) ;
897+ const payloads = ( await Promise . all ( responses . map ( ( response ) => response . json ( ) ) ) ) as Array < {
898+ error : { reason : string } ;
899+ } > ;
900+
901+ expect ( responses . map ( ( response ) => response . status ) ) . toEqual ( [
902+ HTTP_STATUS . SERVICE_UNAVAILABLE ,
903+ HTTP_STATUS . SERVICE_UNAVAILABLE ,
904+ ] ) ;
905+ expect ( payloads . map ( ( payload ) => payload . error . reason ) ) . toEqual ( [
906+ "deactivated" ,
907+ "deactivated" ,
908+ ] ) ;
909+ expect ( calls . map ( ( call ) => call . headers . get ( OPENAI_HEADERS . ACCOUNT_ID ) ) ) . toEqual ( [
910+ "acc_1" ,
911+ "acc_1" ,
912+ ] ) ;
913+ expect (
914+ recordFailureSpy . mock . calls . filter ( ( [ account ] ) => account . index === 0 ) ,
915+ ) . toHaveLength ( 1 ) ;
916+ expect ( accountManager . getAccountByIndex ( 0 ) ?. enabled ) . toBe ( false ) ;
917+ } ) ;
918+
865919 it ( "returns pool exhaustion after all accounts are deactivated" , async ( ) => {
866920 const now = Date . now ( ) ;
867921 const accountManager = new AccountManager ( undefined , createStorage ( now , 6 ) ) ;
0 commit comments