@@ -109,25 +109,50 @@ function json(response: Awaited<ReturnType<typeof request>>, expected = 200) {
109109 assert . equal ( response . status , expected , 'Unexpected HTTP status; inspect private app logs' )
110110 return toRecord ( JSON . parse ( response . text ) )
111111}
112- function waitEvent ( socket : Socket , event : string , rejectEvent ?: string , timeout = 15_000 ) {
113- return new Promise < Record < string , unknown > > ( ( resolve , reject ) => {
114- const timer = setTimeout ( ( ) => finish ( new Error ( `Timed out waiting for ${ event } ` ) ) , timeout )
112+ async function waitEvent (
113+ socket : Socket ,
114+ event : string ,
115+ rejectEvent ?: string ,
116+ timeout = 15_000 ,
117+ action ?: ( ) => Promise < void >
118+ ) {
119+ let timer : ReturnType < typeof setTimeout > | undefined
120+ let settled = false
121+ let startDeadline : ( ) => void = ( ) => undefined
122+ let accept : ( value : unknown ) => void
123+ let deny : ( value : unknown ) => void
124+ const cleanup = ( ) => {
125+ clearTimeout ( timer )
126+ socket . off ( event , accept )
127+ if ( rejectEvent ) socket . off ( rejectEvent , deny )
128+ }
129+ const signal = new Promise < Record < string , unknown > > ( ( resolve , reject ) => {
115130 function finish ( error ?: Error , value ?: unknown ) {
116- clearTimeout ( timer )
117- socket . off ( event , accept )
118- if ( rejectEvent ) socket . off ( rejectEvent , deny )
131+ settled = true
132+ cleanup ( )
119133 if ( error ) reject ( error )
120134 else resolve ( toRecord ( value ) )
121135 }
122- function accept ( value : unknown ) {
123- finish ( undefined , value )
124- }
125- function deny ( value : unknown ) {
136+ accept = ( value : unknown ) => finish ( undefined , value )
137+ deny = ( value : unknown ) =>
126138 finish ( new Error ( `${ event } rejected: ${ String ( toRecord ( value ) . code ?? 'connection error' ) } ` ) )
127- }
128139 socket . once ( event , accept )
129140 if ( rejectEvent ) socket . once ( rejectEvent , deny )
141+ /** The HTTP action has its own deadline; compilation time must not consume delivery time. */
142+ startDeadline = ( ) => {
143+ if ( ! settled )
144+ timer = setTimeout ( ( ) => finish ( new Error ( `Timed out waiting for ${ event } ` ) ) , timeout )
145+ }
130146 } )
147+ /** Observe early rejection while the bounded HTTP action is still running. */
148+ void signal . catch ( ( ) => undefined )
149+ try {
150+ if ( action ) await action ( )
151+ startDeadline ( )
152+ return await signal
153+ } finally {
154+ cleanup ( )
155+ }
131156}
132157async function connect ( cookie : string ) {
133158 const token = required (
@@ -153,9 +178,7 @@ async function joinRoom(socket: Socket, type: 'project-files' | 'workspace-files
153178}
154179async function changed ( action : ( ) => Promise < void > ) {
155180 const count = projectEvents . length
156- const signal = waitEvent ( reader , 'project-files-changed' )
157- await action ( )
158- const event = await signal
181+ const event = await waitEvent ( reader , 'project-files-changed' , undefined , 15_000 , action )
159182 assert . deepEqual ( Object . keys ( event ) . sort ( ) , [ 'projectId' , 'timestamp' ] )
160183 assert . equal ( event . projectId , fixture . projectId )
161184 assert . equal ( typeof event . timestamp , 'number' )
@@ -294,28 +317,34 @@ try {
294317 await check (
295318 'create broadcasts only after the canonical file is readable and never to the workspace room' ,
296319 async ( ) => {
297- const retainedMembership = waitEvent ( owner , 'project-files-changed' )
298- await changed ( async ( ) => {
299- const file = toRecord (
300- json (
301- await request ( prefix ( ) , 'POST' , {
302- name : `live-${ generateId ( ) } .md` ,
303- content : 'first version' ,
304- contentType : 'text/markdown' ,
305- encoding : 'utf-8' ,
306- } ) ,
307- 201
308- ) . file
309- )
310- fileId = required ( file . id )
311- revision = required ( json ( await request ( `${ prefix ( ) } /${ fileId } /versions` ) ) . revision )
312- } )
320+ const retainedMembership = await waitEvent (
321+ owner ,
322+ 'project-files-changed' ,
323+ undefined ,
324+ 15_000 ,
325+ ( ) =>
326+ changed ( async ( ) => {
327+ const file = toRecord (
328+ json (
329+ await request ( prefix ( ) , 'POST' , {
330+ name : `live-${ generateId ( ) } .md` ,
331+ content : 'first version' ,
332+ contentType : 'text/markdown' ,
333+ encoding : 'utf-8' ,
334+ } ) ,
335+ 201
336+ ) . file
337+ )
338+ fileId = required ( file . id )
339+ revision = required ( json ( await request ( `${ prefix ( ) } /${ fileId } /versions` ) ) . revision )
340+ } )
341+ )
313342 assert . equal (
314343 toRecord ( json ( await request ( `${ prefix ( ) } /${ fileId } ` , 'GET' , undefined , readerCookie ) ) . file )
315344 . id ,
316345 fileId
317346 )
318- assert . equal ( ( await retainedMembership ) . projectId , fixture . projectId )
347+ assert . equal ( retainedMembership . projectId , fixture . projectId )
319348 assert . equal ( workspaceEvents . length , 0 )
320349 }
321350 )
0 commit comments