From 65e035929f6eeea6bf79dc58a49053c71070a628 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 30 Sep 2026 16:27:10 +1000 Subject: [PATCH] fix(events): a meeting moved to a new host is sent with the attendees as given (PPT-2640) When a host change moves a meeting to the new host's calendar, the previous host was added back to the attendees before the meeting was re-sent, so they stayed in it even when the edit had removed them. The previous host is now only invited when the request still lists them. The frontend keeps them in the list by default, so an explicit removal is what changes. PPT-2640 --- spec/controllers/events_spec.cr | 28 ++++++++++++++++++++++++++-- src/controllers/events.cr | 14 ++++++++------ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/spec/controllers/events_spec.cr b/spec/controllers/events_spec.cr index 22750940..8d84aec0 100644 --- a/spec/controllers/events_spec.cr +++ b/spec/controllers/events_spec.cr @@ -1846,10 +1846,11 @@ describe Events, tags: ["event"] do cancelled.size.should eq 1 resent.size.should eq 1 - # the room is invited by the new meeting, and the old host stays involved + # the room is invited by the new meeting, and the previous host is not, + # as the attendees sent no longer list them invited = JSON.parse(resent.first)["attendees"].as_a.map { |attendee| attendee["emailAddress"]["address"].as_s.downcase } invited.should contain "room1@example.com" - invited.should contain organiser + invited.should_not contain organiser # the meeting keeps its identity in PlaceOS: same metadata record, so # visitors, check-in state and extension data survive the move @@ -1865,6 +1866,29 @@ describe Events, tags: ["event"] do payload["previous_host_email"].as_s.should eq organiser end + it "keeps the previous host in the moved meeting when the attendees sent still list them" do + event_id, _, _, _ = create_event.call(true) + new_host = "another-host@example.com" + EventsHelper.stub_calendar_write_access(new_host) + WebMock.stub(:delete, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/events/#{URI.encode_path_segment(event_id)}") + .to_return(status: 204, body: "") + resent = [] of String + WebMock.stub(:post, "https://graph.microsoft.com/v1.0/users/#{URI.encode_path_segment(new_host)}/calendar/events") + .to_return do |request| + resent << (request.body.try(&.gets_to_end) || "") + HTTP::Client::Response.new(201, body: EventsHelper.mock_event_id("evt-moved-with-old-host", "ical-moved-002", recurring: false, organizer: new_host).to_json) + end + + resp = client.patch("#{EVENTS_BASE}/#{event_id}?system_id=#{system_id}", headers: headers, + body: EventsHelper.reassign_host_input(host: new_host, extra_attendee: organiser)) + resp.status_code.should eq(200) + + resent.size.should eq 1 + invited = JSON.parse(resent.first)["attendees"].as_a.map { |attendee| attendee["emailAddress"]["address"].as_s.downcase } + invited.should contain organiser + invited.should contain new_host + end + it "supersedes a reassignment when the meeting moves to the new host" do event_id, changed_bodies, _, _ = create_event.call(true) diff --git a/src/controllers/events.cr b/src/controllers/events.cr index 84fe608c..49364300 100644 --- a/src/controllers/events.cr +++ b/src/controllers/events.cr @@ -635,18 +635,20 @@ class Events < Application update_attendees = !changes.attendees.nil? attendees = changes.attendees.try(&.map { |a| a.email.downcase }) || existing_attendees - # Ensure the host is configured to be attending the meeting and has accepted the meeting - unless host.in?(attendees) + # Handing the meeting to someone else means moving it to their calendar, + # which Office365 only allows by cancelling and re-sending it (PPT-2375). + new_organiser = requested_organiser(changes, organiser: host, requester: user_email) + + # Ensure the host is configured to be attending the meeting and has accepted the meeting. + # A meeting moving to a new host is sent with the attendees as given, so the + # previous host is only invited when the request still lists them. + unless new_organiser || host.in?(attendees) host_attendee = PlaceCalendar::Event::Attendee.new(name: host, email: host, response_status: "accepted") host_attendee.visit_expected = true changes.attendees << host_attendee attendees << host end - # Handing the meeting to someone else means moving it to their calendar, - # which Office365 only allows by cancelling and re-sending it (PPT-2375). - new_organiser = requested_organiser(changes, organiser: host, requester: user_email) - # Naming a host without moving the meeting is recorded against the event # metadata instead, for deployments where the organiser is a room or a # service account rather than a person. A transfer supersedes it: the new