@@ -38,6 +38,7 @@ vi.mock('@/handlers/file-doc-app', () => ({
3838import {
3939 applyMarkdownToLiveFileDoc ,
4040 cleanupFileDocForSocket ,
41+ fileDocAdmissionRoom ,
4142 flushAllFileDocRooms ,
4243 invalidateLiveFileDocument ,
4344 setupWorkspaceFileDocHandlers ,
@@ -1006,6 +1007,76 @@ describe('setupWorkspaceFileDocHandlers', () => {
10061007 }
10071008 )
10081009
1010+ it . each ( [ 'write' , 'read' , null ] as const ) (
1011+ 'withholds document and presence broadcasts until final authorization resolves to %s' ,
1012+ async ( permission ) => {
1013+ mockFetchFileDocSeed . mockResolvedValue ( seedResult ( '# Private' , 'doc-private' ) )
1014+ const memberships = new Set < string > ( )
1015+ let pending : ReturnType < typeof setup >
1016+ const { io } = createIo ( ( { target, event, payload } ) => {
1017+ if ( memberships . has ( target ) ) pending . socket . emit ( event , payload )
1018+ } )
1019+ pending = setup ( 'socket-pending-authorization' , io , {
1020+ join : vi . fn ( ( name : string ) => {
1021+ memberships . add ( name )
1022+ } ) ,
1023+ leave : vi . fn ( ( name : string ) => {
1024+ memberships . delete ( name )
1025+ } ) ,
1026+ } )
1027+ let resolvePermission ! : ( value : 'write' | 'read' | null ) => void
1028+ const authorization = new Promise < 'write' | 'read' | null > ( ( resolve ) => {
1029+ resolvePermission = resolve
1030+ } )
1031+ const guard = vi
1032+ . spyOn ( permissions , 'resolveCurrentRoomPermission' )
1033+ . mockResolvedValueOnce ( 'write' )
1034+ . mockImplementationOnce ( ( ) => authorization )
1035+ const joining = pending . handlers [ FILE_DOC_EVENTS . JOIN ] ( { fileId : 'file-1' , clientId : 1 } )
1036+ try {
1037+ await vi . waitFor ( ( ) => expect ( guard ) . toHaveBeenCalledTimes ( 2 ) )
1038+ const content = frame ( FILE_DOC_MESSAGE_TYPE . SYNC , ( encoder ) =>
1039+ syncProtocol . writeUpdate ( encoder , seedResult ( '# Private update' ) . update )
1040+ )
1041+ io . local . to ( ROOM_NAME ) . emit ( FILE_DOC_EVENTS . MESSAGE , content )
1042+ io . to ( ROOM_NAME ) . emit (
1043+ FILE_DOC_EVENTS . MESSAGE ,
1044+ new Uint8Array ( [ FILE_DOC_MESSAGE_TYPE . AWARENESS ] )
1045+ )
1046+ io . to ( ROOM_NAME ) . emit ( FILE_DOC_EVENTS . PRESENCE , [ { userId : 'private-peer' } ] )
1047+ expect ( pending . socket . emit ) . not . toHaveBeenCalledWith (
1048+ FILE_DOC_EVENTS . MESSAGE ,
1049+ expect . anything ( )
1050+ )
1051+ expect ( pending . socket . emit ) . not . toHaveBeenCalledWith (
1052+ FILE_DOC_EVENTS . PRESENCE ,
1053+ expect . anything ( )
1054+ )
1055+ expect ( memberships . has ( ROOM_NAME ) ) . toBe ( false )
1056+ resolvePermission ( permission )
1057+ await joining
1058+ expect ( memberships . has ( ROOM_NAME ) ) . toBe ( permission === 'write' )
1059+ if ( permission === 'write' ) {
1060+ expect ( joinSuccessFileId ( pending . socket ) ) . toBe ( 'file-1' )
1061+ expect ( pending . socket . emit ) . toHaveBeenCalledWith (
1062+ FILE_DOC_EVENTS . MESSAGE ,
1063+ expect . any ( Uint8Array )
1064+ )
1065+ } else {
1066+ expect ( joinSuccessFileId ( pending . socket ) ) . toBeUndefined ( )
1067+ expect ( pending . socket . emit ) . toHaveBeenCalledWith (
1068+ FILE_DOC_EVENTS . JOIN_ERROR ,
1069+ expect . objectContaining ( { code : 'ACCESS_DENIED' , retryable : false } )
1070+ )
1071+ }
1072+ } finally {
1073+ resolvePermission ( permission )
1074+ await joining
1075+ guard . mockRestore ( )
1076+ }
1077+ }
1078+ )
1079+
10091080 it ( 'receives invalidation while the subscribed generation check is pending' , async ( ) => {
10101081 mockFetchFileDocSeed . mockResolvedValue ( seedResult ( '# Old' , 'doc-old' ) )
10111082 const memberships = new Set < string > ( )
@@ -1031,9 +1102,10 @@ describe('setupWorkspaceFileDocHandlers', () => {
10311102 try {
10321103 const joining = pending . handlers [ FILE_DOC_EVENTS . JOIN ] ( { fileId : 'file-1' , clientId : 1 } )
10331104 await vi . waitFor ( ( ) => expect ( guard ) . toHaveBeenCalledOnce ( ) )
1034- expect ( memberships . has ( ROOM_NAME ) ) . toBe ( true )
1105+ expect ( memberships . has ( ROOM_NAME ) ) . toBe ( false )
1106+ expect ( memberships . has ( fileDocAdmissionRoom ( 'file-1' ) ) ) . toBe ( true )
10351107 await getFileDocStore ( ) . invalidateDocument ( ROOM_NAME , 2 )
1036- io . to ( ROOM_NAME ) . emit ( FILE_DOC_EVENTS . INVALIDATED , { fileId : 'file-1' } )
1108+ io . to ( fileDocAdmissionRoom ( 'file-1' ) ) . emit ( FILE_DOC_EVENTS . INVALIDATED , { fileId : 'file-1' } )
10371109 expect ( pending . socket . emit ) . toHaveBeenCalledWith ( FILE_DOC_EVENTS . INVALIDATED , {
10381110 fileId : 'file-1' ,
10391111 } )
@@ -1043,6 +1115,7 @@ describe('setupWorkspaceFileDocHandlers', () => {
10431115
10441116 expect ( joinSuccessFileId ( pending . socket ) ) . toBeUndefined ( )
10451117 expect ( memberships . has ( ROOM_NAME ) ) . toBe ( false )
1118+ expect ( memberships . has ( fileDocAdmissionRoom ( 'file-1' ) ) ) . toBe ( false )
10461119 } finally {
10471120 resolveGeneration ( true )
10481121 guard . mockRestore ( )
@@ -1104,7 +1177,8 @@ describe('setupWorkspaceFileDocHandlers', () => {
11041177 FILE_DOC_EVENTS . JOIN_SUCCESS ,
11051178 expect . objectContaining ( { clientId : 2 , docId : 'doc-shared' } )
11061179 )
1107- expect ( pending . socket . leave ) . not . toHaveBeenCalled ( )
1180+ expect ( pending . socket . leave ) . not . toHaveBeenCalledWith ( ROOM_NAME )
1181+ expect ( pending . socket . leave ) . toHaveBeenCalledWith ( fileDocAdmissionRoom ( 'file-1' ) )
11081182 } finally {
11091183 for ( const resolve of checks ) resolve ( true )
11101184 guard . mockRestore ( )
@@ -1121,7 +1195,7 @@ describe('setupWorkspaceFileDocHandlers', () => {
11211195 . mockRejectedValueOnce ( new Error ( 'Temporary generation read failure' ) )
11221196 try {
11231197 await current . handlers [ FILE_DOC_EVENTS . JOIN ] ( { fileId : 'file-1' , clientId : 2 } )
1124- expect ( current . socket . leave ) . not . toHaveBeenCalled ( )
1198+ expect ( current . socket . leave ) . not . toHaveBeenCalledWith ( ROOM_NAME )
11251199 current . socket . emit . mockClear ( )
11261200 current . handlers [ FILE_DOC_EVENTS . MESSAGE ] (
11271201 frame ( FILE_DOC_MESSAGE_TYPE . SYNC , ( encoder ) =>
0 commit comments