@@ -569,6 +569,69 @@ describe('calendar feed and legacy URLs', () => {
569569 expect ( res . body ) . not . toContain ( `/events/${ ancient . slug } ` ) ;
570570 } ) ;
571571
572+ it ( "narrows the feed to one member's RSVPs with ?attendee=" , async ( ) => {
573+ const going = await createEvent ( hackNight ( 7 , { title : 'Going' } ) ) ;
574+ const notGoing = await createEvent ( hackNight ( 14 , { title : 'Not Going' } ) ) ;
575+ const cancelled = await createEvent ( hackNight ( 21 , { title : 'Called Off' } ) ) ;
576+ const jane = await janeCookie ( ) ;
577+ for ( const slug of [ going . slug , cancelled . slug ] ) {
578+ const rsvp = await app ! . inject ( { method : 'PUT' , url : `/api/events/${ slug } /rsvp` , headers : { cookie : jane } } ) ;
579+ expect ( rsvp . statusCode , rsvp . body ) . toBe ( 201 ) ;
580+ }
581+ const cancel = await app ! . inject ( {
582+ method : 'POST' ,
583+ url : `/api/events/${ cancelled . slug } /cancel` ,
584+ headers : { cookie : await staffCookie ( ) , 'content-type' : 'application/json' } ,
585+ payload : { } ,
586+ } ) ;
587+ expect ( cancel . statusCode , cancel . body ) . toBe ( 200 ) ;
588+
589+ const url = `/api/events/feed.ics?attendee=${ fixtures . personSlug } ` ;
590+ // No cookie: the member feed is public, like the attendee list.
591+ let res = await app ! . inject ( { method : 'GET' , url } ) ;
592+ expect ( res . statusCode , res . body ) . toBe ( 200 ) ;
593+ expect ( res . headers [ 'content-type' ] ) . toContain ( 'text/calendar' ) ;
594+ expect ( res . headers [ 'cache-control' ] ) . toBe ( 'public, max-age=300' ) ;
595+ expect ( res . headers [ 'content-disposition' ] ) . toBe ( `inline; filename="code-for-philly-${ fixtures . personSlug } .ics"` ) ;
596+ expect ( res . body ) . toMatch ( / X - W R - C A L N A M E : C o d e f o r P h i l l y : .+ ' s e v e n t s / ) ;
597+ expect ( res . body ) . toContain ( `/events/${ going . slug } ` ) ;
598+ expect ( res . body ) . toContain ( `/events/${ cancelled . slug } ` ) ;
599+ expect ( res . body ) . toContain ( 'STATUS:CANCELLED' ) ;
600+ expect ( res . body ) . not . toContain ( `/events/${ notGoing . slug } ` ) ;
601+
602+ // Withdrawing drops the event at the next poll.
603+ await app ! . inject ( { method : 'DELETE' , url : `/api/events/${ going . slug } /rsvp` , headers : { cookie : jane } } ) ;
604+ res = await app ! . inject ( { method : 'GET' , url } ) ;
605+ expect ( res . body ) . not . toContain ( `/events/${ going . slug } ` ) ;
606+
607+ // A member with no RSVPs gets a valid empty calendar, not an error.
608+ const empty = await app ! . inject ( { method : 'GET' , url : `/api/events/feed.ics?attendee=${ STAFF_SLUG } ` } ) ;
609+ expect ( empty . statusCode ) . toBe ( 200 ) ;
610+ expect ( empty . body ) . toContain ( 'BEGIN:VCALENDAR' ) ;
611+ expect ( empty . body ) . not . toContain ( 'BEGIN:VEVENT' ) ;
612+ } ) ;
613+
614+ it ( 'answers 404 for an unknown or deactivated attendee and 422 for an empty one' , async ( ) => {
615+ const unknown = await app ! . inject ( { method : 'GET' , url : '/api/events/feed.ics?attendee=nobody-here' } ) ;
616+ expect ( unknown . statusCode ) . toBe ( 404 ) ;
617+ expect ( unknown . json ( ) ) . toMatchObject ( { success : false , error : { code : 'not_found' } } ) ;
618+
619+ const blank = await app ! . inject ( { method : 'GET' , url : '/api/events/feed.ics?attendee=' } ) ;
620+ expect ( blank . statusCode ) . toBe ( 422 ) ;
621+
622+ const { slug } = await createEvent ( hackNight ( 7 ) ) ;
623+ const jane = await janeCookie ( ) ;
624+ await app ! . inject ( { method : 'PUT' , url : `/api/events/${ slug } /rsvp` , headers : { cookie : jane } } ) ;
625+ const deactivate = await app ! . inject ( {
626+ method : 'POST' ,
627+ url : `/api/people/${ fixtures . personSlug } /deactivate` ,
628+ headers : { cookie : jane } ,
629+ } ) ;
630+ expect ( deactivate . statusCode , deactivate . body ) . toBeLessThan ( 300 ) ;
631+ const gone = await app ! . inject ( { method : 'GET' , url : `/api/events/feed.ics?attendee=${ fixtures . personSlug } ` } ) ;
632+ expect ( gone . statusCode ) . toBe ( 404 ) ;
633+ } ) ;
634+
572635 it ( '301s /calendar and the laddr list views and segment URLs' , async ( ) => {
573636 const cases : Array < [ string , string ] > = [
574637 [ '/calendar' , '/events' ] ,
0 commit comments