@@ -159,25 +159,24 @@ jest.unstable_mockModule('../middleware/auth.js', async () => ({ authenticate: m
159159
160160// Imports that pull in the mocked modules must happen after the mocks above.
161161const { orgMembersRouter } = await import ( '../routes/orgMembers.js' )
162- const { requireOrgAccess } = await import ( '../middleware/orgAuth.js' )
163162const { errorHandler } = await import ( '../middleware/errorHandler.js' )
164163
165- // ── Test app: real router + real org-access middleware on a scoped vault route ─
166- let testVaults : Array < { id : string ; orgId : string } > = [ ]
167-
164+ // ── Test app: just the real router (which carries its own auth + org-access) ───
165+ // Scoped access is verified through the router's real `GET /:orgId/members`
166+ // route — itself guarded by `requireOrgAccess` — so the test never defines its
167+ // own authorization endpoint.
168168const app = express ( )
169169app . use ( express . json ( ) )
170170app . use ( '/api/organizations' , orgMembersRouter )
171- app . get (
172- '/api/organizations/:orgId/vaults' ,
173- mockAuthenticate ,
174- requireOrgAccess ( 'owner' , 'admin' , 'member' ) ,
175- ( req , res ) => {
176- res . json ( { data : testVaults . filter ( ( v ) => v . orgId === req . params . orgId ) } )
177- } ,
178- )
179171app . use ( errorHandler )
180172
173+ // Reads an org's member roster as `userId`; this is the org-scoped resource the
174+ // real `requireOrgAccess` middleware gates.
175+ const readRoster = ( orgId : string , userId : string , role = 'member' ) =>
176+ request ( app )
177+ . get ( `/api/organizations/${ orgId } /members` )
178+ . set ( 'Authorization' , bearer ( userId , role ) )
179+
181180const bearer = ( userId : string , role = 'member' ) =>
182181 `Bearer ${ jwt . sign ( { userId, role, sub : userId } , JWT_SECRET , { expiresIn : '1h' } ) } `
183182
@@ -194,18 +193,12 @@ beforeEach(() => {
194193 { orgId : ORG_ALPHA , userId : 'alice' , role : 'owner' } ,
195194 { orgId : ORG_BETA , userId : 'dave' , role : 'owner' } ,
196195 ] )
197- testVaults = [
198- { id : 'va-1' , orgId : ORG_ALPHA } ,
199- { id : 'va-2' , orgId : ORG_ALPHA } ,
200- { id : 'vb-1' , orgId : ORG_BETA } ,
201- ]
202196} )
203197
204198afterEach ( ( ) => {
205199 invitations . length = 0
206200 setOrganizations ( [ ] )
207201 setOrgMembers ( [ ] )
208- testVaults = [ ]
209202 jest . clearAllMocks ( )
210203} )
211204
@@ -222,10 +215,8 @@ async function inviteToAlpha(email = 'erin@example.com'): Promise<string> {
222215
223216describe ( 'Org-member invitation acceptance — end to end (issue #668)' , ( ) => {
224217 it ( 'walks invite → accept → scoped member → org-scoped resource access' , async ( ) => {
225- // Before acceptance, the invitee is not a member and cannot read vaults.
226- const before = await request ( app )
227- . get ( `/api/organizations/${ ORG_ALPHA } /vaults` )
228- . set ( 'Authorization' , bearer ( 'erin' ) )
218+ // Before acceptance, the invitee is not a member and is denied org access.
219+ const before = await readRoster ( ORG_ALPHA , 'erin' )
229220 expect ( before . status ) . toBe ( 403 )
230221
231222 // 1. Admin invites; 2. invitee accepts with the raw token.
@@ -238,21 +229,15 @@ describe('Org-member invitation acceptance — end to end (issue #668)', () => {
238229
239230 // 3. The new member is recorded with the expected role…
240231 expect ( getMemberRole ( ORG_ALPHA , 'erin' ) ) . toBe ( 'member' )
241- const roster = await request ( app )
242- . get ( `/api/organizations/${ ORG_ALPHA } /members` )
243- . set ( 'Authorization' , bearer ( 'erin' ) )
232+
233+ // 4. …and can now read that org's scoped resource (its member roster),
234+ // seeing itself listed with the expected role.
235+ const roster = await readRoster ( ORG_ALPHA , 'erin' )
244236 expect ( roster . status ) . toBe ( 200 )
245237 expect ( roster . body . members . find ( ( m : any ) => m . user_id === 'erin' ) ) . toMatchObject ( {
246238 organization_id : ORG_ALPHA ,
247239 role : 'member' ,
248240 } )
249-
250- // 4. …and can now read that org's vaults.
251- const own = await request ( app )
252- . get ( `/api/organizations/${ ORG_ALPHA } /vaults` )
253- . set ( 'Authorization' , bearer ( 'erin' ) )
254- expect ( own . status ) . toBe ( 200 )
255- expect ( own . body . data . map ( ( v : any ) => v . id ) . sort ( ) ) . toEqual ( [ 'va-1' , 'va-2' ] )
256241 } )
257242
258243 it ( 'confines the newly accepted member to their org (cross-tenant isolation)' , async ( ) => {
@@ -262,9 +247,8 @@ describe('Org-member invitation acceptance — end to end (issue #668)', () => {
262247 . send ( { token, userId : 'erin' , role : 'member' } )
263248 . expect ( 200 )
264249
265- const crossOrg = await request ( app )
266- . get ( `/api/organizations/${ ORG_BETA } /vaults` )
267- . set ( 'Authorization' , bearer ( 'erin' ) )
250+ // Accepted into Alpha only — Beta's roster must stay off-limits.
251+ const crossOrg = await readRoster ( ORG_BETA , 'erin' )
268252 expect ( crossOrg . status ) . toBe ( 403 )
269253 expect ( crossOrg . body . error ) . toMatch ( / n o t a m e m b e r / i)
270254 } )
0 commit comments