diff --git a/spec/controllers/events_spec.cr b/spec/controllers/events_spec.cr index 578d5884..60533aba 100644 --- a/spec/controllers/events_spec.cr +++ b/spec/controllers/events_spec.cr @@ -1728,4 +1728,62 @@ describe Events, tags: ["event"] do last["previous_event_start"].as_i64.should eq thu end end + + # Metadata is stored per room, so an event that changes room used to start a + # fresh record and re-announce every visitor as newly invited (PPT-2375). + describe "room moves", tags: "PPT-2375" do + it "keeps the meeting's metadata and does not re-invite anyone" do + WebMock.reset + attending_bodies = [] of String + WebMock.stub(:post, "#{ENV["PLACE_URI"]}/api/engine/v2/signal?channel=staff/guest/attending") + .to_return do |request| + attending_bodies << (request.body.try(&.gets_to_end) || "") + HTTP::Client::Response.new(200, body: "") + end + changed_bodies = [] of String + WebMock.stub(:post, "#{ENV["PLACE_URI"]}/api/engine/v2/signal?channel=staff/event/changed") + .to_return do |request| + changed_bodies << (request.body.try(&.gets_to_end) || "") + HTTP::Client::Response.new(200, body: "") + end + EventsHelper.stub_event_tokens + EventsHelper.stub_update_endpoints + + system_id = "sys-rJQQlR4Cn7" + EventsHelper.stub_permissions_check(system_id) + created = JSON.parse(client.post(EVENTS_BASE, headers: headers, body: EventsHelper.create_event_input).body).as_h + event_id = created["id"].to_s + EventsHelper.stub_room_event_query(event_id) + + # the room the event moves to + systems = Array(JSON::Any).from_json(File.read("./spec/fixtures/placeos/systems.json")).map &.to_json + moved_system_id = "sys_id" + WebMock.stub(:get, ENV["PLACE_URI"].to_s + "/api/engine/v2/systems/#{moved_system_id}") + .to_return(body: systems[1]) + EventsHelper.stub_permissions_check(moved_system_id) + + before = EventMetadata.find_by(event_id: event_id) + before.system_id.should eq system_id + attending_bodies.clear + changed_bodies.clear + + # the same guest list as the meeting was created with, moved to another room + body = EventsHelper.create_event_input.gsub(%("system_id": "#{system_id}"), %("system_id": "#{moved_system_id}")) + client.patch("#{EVENTS_BASE}/#{event_id}?system_id=#{system_id}", headers: headers, body: body).status_code.should eq(200) + sleep 100.milliseconds + + # the same record, now against the new room, so the visitors and their + # check-in state come with it + after = EventMetadata.find!(before.id.not_nil!) + after.system_id.should eq moved_system_id + after.resource_calendar.should eq "room2@example.com" + after.attendees.to_a.size.should eq before.attendees.to_a.size + + # the move is a change, not an invitation + payload = JSON.parse(changed_bodies.last) + payload["previous_system_id"].as_s.should eq system_id + payload["system_id"].as_s.should eq moved_system_id + attending_bodies.map { |signal| JSON.parse(signal)["attendee_email"].as_s }.should_not contain "jon@example.com" + end + end end diff --git a/spec/controllers/helpers/event_helper.cr b/spec/controllers/helpers/event_helper.cr index 664bf3c2..d8825a11 100644 --- a/spec/controllers/helpers/event_helper.cr +++ b/spec/controllers/helpers/event_helper.cr @@ -34,6 +34,25 @@ module EventsHelper .to_return(body: File.read("./spec/fixtures/calendars/o365/show.json")) end + # Everything Graph needs stubbed to create an event and then PATCH it. + def stub_update_endpoints + stub_create_endpoints + + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/room1%40example.com/calendar/events/AAMkADE3YmQxMGQ2LTRmZDgtNDljYy1hNDg1LWM0NzFmMGI0ZTQ3YgBGAAAAAADFYQb3DJ_xSJHh14kbXHWhBwB08dwEuoS_QYSBDzuv558sAAAAAAENAAB08dwEuoS_QYSBDzuv558sAACGVOwUAAA%3D") + .to_return(body: File.read("./spec/fixtures/events/o365/create.json")) + WebMock.stub(:patch, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/events/AAMkADE3YmQxMGQ2LTRmZDgtNDljYy1hNDg1LWM0NzFmMGI0ZTQ3YgBGAAAAAADFYQb3DJ_xSJHh14kbXHWhBwB08dwEuoS_QYSBDzuv558sAAAAAAENAAB08dwEuoS_QYSBDzuv558sAACGVOwUAAA%3D") + .to_return(body: File.read("./spec/fixtures/events/o365/update.json")) + # the host's copy of the event, looked up by ical uid + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/calendarView?startDateTime=2020-08-26T14%3A00%3A00-00%3A00&endDateTime=2020-08-27T13%3A59%3A59-00%3A00&%24filter=iCalUId+eq+%27040000008200E00074C5B7101A82E008000000006DE2E3761F8AD6010000000000000000100000009CCCDBB1F09DE74D8B157797D97F6A10%27&%24top=10000") + .to_return(body: File.read("./spec/fixtures/events/o365/events_query.json")) + end + + # The room's copy of the event, looked up by ical uid once the id is known. + def stub_room_event_query(event_id) + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/room1%40example.com/calendar/calendarView?startDateTime=2020-08-26T14:00:00-00:00&endDateTime=2020-08-27T13:59:59-00:00&%24filter=iCalUId+eq+%27040000008200E00074C5B7101A82E008000000006DE2E3761F8AD6010000000000000000100000009CCCDBB1F09DE74D8B157797D97F6A10%27&$top=10000") + .to_return(event_query_response(event_id)) + end + def mock_event_id(id, ical = nil) event = Office365::Event.new(**{ id: id, diff --git a/src/controllers/events.cr b/src/controllers/events.cr index 7273c0b8..9f67fd56 100644 --- a/src/controllers/events.cr +++ b/src/controllers/events.cr @@ -702,7 +702,14 @@ class Events < Application if system raise Error::BadRequest.new("system_id must be present") if system_id.nil? - meta = get_migrated_metadata(updated_event, system_id) || EventMetadata.new + # An event that moves takes its metadata with it: the attendee records, + # their check-in state and the extension data belong to the meeting, not + # to the room. Metadata is looked up per room, so a move would otherwise + # start a fresh record, orphaning all of that and making every visitor + # look newly invited (PPT-2375). + moved_meta = previous_meta_for_signal if changing_room + meta = moved_meta || get_migrated_metadata(updated_event, system_id) || EventMetadata.new + if extension_data = changes.extension_data meta_ext_data = meta.ext_data data = if (val = meta_ext_data) && val.as_h? @@ -802,7 +809,12 @@ class Events < Application next unless attend.visit_expected - if !previously_visiting || changing_room + # Only a visitor who wasn't already attending is being invited. + # Moving the event to another room used to re-announce everyone, + # which reads downstream as a fresh invitation for each of them -- + # the room change is reported by staff/event/changed instead + # (PPT-2375). + if !previously_visiting spawn do sys = system raise Error::BadUpstreamResponse.new("event_start must be present on updated event #{updated_event.id}") unless updated_event_start = updated_event.event_start @@ -824,30 +836,6 @@ class Events < Application end end end - elsif changing_room - existing.each do |attend| - next unless attend.visit_expected - spawn do - sys = system - raise Error::NotFound.new("guest not found for attendee #{attend.id}") unless guest = attend.guest - raise Error::BadUpstreamResponse.new("event_start must be present on updated event #{updated_event.id}") unless updated_event_start = updated_event.event_start - - placeos_client.root.signal("staff/guest/attending", { - action: :meeting_update, - system_id: sys.id, - event_id: event_id, - event_ical_uid: updated_event.ical_uid, - host: host, - resource: sys.email, - event_title: updated_event.title, - event_summary: updated_event.title, - event_starting: updated_event_start.to_unix, - attendee_name: guest.name, - attendee_email: guest.email, - zones: sys.zones, - }) - end - end end end