66 fetchSessionMessages ,
77 deleteSession ,
88 shareSession ,
9+ invalidateOrganizationSessionAccess ,
910} from './session-ingest-client' ;
1011
1112// ---------------------------------------------------------------------------
@@ -18,6 +19,7 @@ jest.mock('@sentry/nextjs', () => ({
1819
1920jest . mock ( '@/lib/config.server' , ( ) => ( {
2021 SESSION_INGEST_WORKER_URL : 'https://ingest.test.example.com' ,
22+ INTERNAL_API_SECRET : 'internal-secret' ,
2123} ) ) ;
2224
2325jest . mock ( '@/lib/tokens' , ( ) => ( {
@@ -410,6 +412,82 @@ describe('shareSession', () => {
410412// fetchSessionMessages (thin wrapper)
411413// ---------------------------------------------------------------------------
412414
415+ describe ( 'invalidateOrganizationSessionAccess' , ( ) => {
416+ beforeEach ( ( ) => {
417+ mockFetch . mockReset ( ) ;
418+ mockCaptureException . mockReset ( ) ;
419+ } ) ;
420+
421+ it ( 'reports and throws invalidation failures' , async ( ) => {
422+ mockFetch . mockResolvedValue ( {
423+ ok : false ,
424+ status : 503 ,
425+ statusText : 'Service Unavailable' ,
426+ text : ( ) => Promise . resolve ( 'cache unavailable' ) ,
427+ } ) ;
428+
429+ await expect (
430+ invalidateOrganizationSessionAccess ( 'usr_removed' , '11111111-1111-4111-8111-111111111111' )
431+ ) . rejects . toThrow (
432+ 'Session access invalidation failed: 503 Service Unavailable - cache unavailable'
433+ ) ;
434+ expect ( mockCaptureException ) . toHaveBeenCalledWith (
435+ expect . any ( Error ) ,
436+ expect . objectContaining ( {
437+ tags : { source : 'session-ingest-client' , endpoint : 'invalidate-session-access' } ,
438+ extra : {
439+ kiloUserId : 'usr_removed' ,
440+ organizationId : '11111111-1111-4111-8111-111111111111' ,
441+ status : 503 ,
442+ } ,
443+ } )
444+ ) ;
445+ } ) ;
446+
447+ it ( 'calls the secret-protected organization invalidation endpoint' , async ( ) => {
448+ mockFetch . mockResolvedValue ( { ok : true , status : 204 } ) ;
449+
450+ await invalidateOrganizationSessionAccess (
451+ 'usr_removed' ,
452+ '11111111-1111-4111-8111-111111111111'
453+ ) ;
454+
455+ expect ( mockFetch ) . toHaveBeenCalledWith (
456+ 'https://ingest.test.example.com/internal/session-access/invalidate' ,
457+ {
458+ method : 'POST' ,
459+ headers : {
460+ 'content-type' : 'application/json' ,
461+ 'X-Internal-Secret' : 'internal-secret' ,
462+ } ,
463+ body : JSON . stringify ( {
464+ kiloUserId : 'usr_removed' ,
465+ organizationId : '11111111-1111-4111-8111-111111111111' ,
466+ } ) ,
467+ signal : expect . any ( AbortSignal ) ,
468+ }
469+ ) ;
470+ } ) ;
471+
472+ it ( 'sets a 30-second invalidation deadline' , async ( ) => {
473+ const signal = new AbortController ( ) . signal ;
474+ const timeout = jest . spyOn ( AbortSignal , 'timeout' ) . mockReturnValue ( signal ) ;
475+ mockFetch . mockResolvedValue ( { ok : true , status : 204 } ) ;
476+
477+ await invalidateOrganizationSessionAccess (
478+ 'usr_removed' ,
479+ '11111111-1111-4111-8111-111111111111'
480+ ) ;
481+
482+ expect ( timeout ) . toHaveBeenCalledWith ( 30_000 ) ;
483+ expect ( mockFetch ) . toHaveBeenCalledWith (
484+ 'https://ingest.test.example.com/internal/session-access/invalidate' ,
485+ expect . objectContaining ( { signal } )
486+ ) ;
487+ timeout . mockRestore ( ) ;
488+ } ) ;
489+ } ) ;
490+
413491describe ( 'fetchSessionMessages' , ( ) => {
414492 beforeEach ( ( ) => {
415493 mockFetch . mockReset ( ) ;
0 commit comments