Bug 1847658 - Account for organizer as attendee of sent invite in iTIP. r=mkmelin
Differential Revision: https://phabricator.services.mozilla.com/D189023 --HG-- extra : amend_source : 0b68acfae8702e1e669b68faf4845b5ac30b1474
This commit is contained in:
Родитель
62fdc7790c
Коммит
38b9c6416a
|
@ -835,8 +835,21 @@ export var itip = {
|
||||||
* to ask the user whether to send)
|
* to ask the user whether to send)
|
||||||
*/
|
*/
|
||||||
checkAndSend(aOpType, aItem, aOriginalItem, aExtResponse = null) {
|
checkAndSend(aOpType, aItem, aOriginalItem, aExtResponse = null) {
|
||||||
let sender = new lazy.CalItipMessageSender(aOriginalItem, itip.getInvitedAttendee(aItem));
|
// `CalItipMessageSender` uses the presence of an "invited attendee"
|
||||||
if (sender.detectChanges(aOpType, aItem, aExtResponse)) {
|
// (representation of the current user) as an indication that this is an
|
||||||
|
// incoming invitation, so we need to avoid passing it if the current user
|
||||||
|
// is the event organizer.
|
||||||
|
let currentUserAsAttendee = null;
|
||||||
|
const itemCalendar = aItem.calendar;
|
||||||
|
if (
|
||||||
|
itemCalendar?.supportsScheduling &&
|
||||||
|
itemCalendar.getSchedulingSupport().isInvitation(aItem)
|
||||||
|
) {
|
||||||
|
currentUserAsAttendee = this.getInvitedAttendee(aItem, itemCalendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sender = new lazy.CalItipMessageSender(aOriginalItem, currentUserAsAttendee);
|
||||||
|
if (sender.buildOutgoingMessages(aOpType, aItem, aExtResponse)) {
|
||||||
sender.send(itip.getImipTransport(aItem));
|
sender.send(itip.getImipTransport(aItem));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,8 @@ class CalItipMessageSender {
|
||||||
* it is modified.
|
* it is modified.
|
||||||
*
|
*
|
||||||
* @param {?calIAttendee} invitedAttendee - For incoming invitations, this is
|
* @param {?calIAttendee} invitedAttendee - For incoming invitations, this is
|
||||||
* the attendee that was invited (corresponding to an installed identity).
|
* the attendee that was invited (corresponding to an installed identity).
|
||||||
|
* For outgoing invitations, this should be `null`.
|
||||||
*/
|
*/
|
||||||
constructor(originalItem, invitedAttendee) {
|
constructor(originalItem, invitedAttendee) {
|
||||||
this.originalItem = originalItem;
|
this.originalItem = originalItem;
|
||||||
|
@ -39,9 +40,9 @@ class CalItipMessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects whether the passed invitation item has been modified from the
|
* Builds a list of iTIP messages to be sent as a result of operations on a
|
||||||
* original (attendees added/removed, item deleted etc.) thus requiring iTIP
|
* calendar item, based on the current user's role and any modifications to
|
||||||
* messages to be sent.
|
* the item.
|
||||||
*
|
*
|
||||||
* This method should be called before send().
|
* This method should be called before send().
|
||||||
*
|
*
|
||||||
|
@ -58,7 +59,7 @@ class CalItipMessageSender {
|
||||||
*
|
*
|
||||||
* @returns {number} - The number of messages to be sent.
|
* @returns {number} - The number of messages to be sent.
|
||||||
*/
|
*/
|
||||||
detectChanges(opType, item, extResponse = null) {
|
buildOutgoingMessages(opType, item, extResponse = null) {
|
||||||
let { originalItem, invitedAttendee } = this;
|
let { originalItem, invitedAttendee } = this;
|
||||||
|
|
||||||
// balance out parts of the modification vs delete confusion, deletion of occurrences
|
// balance out parts of the modification vs delete confusion, deletion of occurrences
|
||||||
|
@ -146,8 +147,10 @@ class CalItipMessageSender {
|
||||||
return this.pendingMessageCount;
|
return this.pendingMessageCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an "invited attendee" (i.e., the current user) is present, we assume
|
||||||
|
// that this is an incoming invite and that we should send only a REPLY if
|
||||||
|
// needed.
|
||||||
if (invitedAttendee) {
|
if (invitedAttendee) {
|
||||||
// actually is an invitation copy, fix attendee list to send REPLY
|
|
||||||
/* We check if the attendee id matches one of of the
|
/* We check if the attendee id matches one of of the
|
||||||
* userAddresses. If they aren't equal, it means that
|
* userAddresses. If they aren't equal, it means that
|
||||||
* someone is accepting invitations on behalf of an other user. */
|
* someone is accepting invitations on behalf of an other user. */
|
||||||
|
@ -158,9 +161,10 @@ class CalItipMessageSender {
|
||||||
!cal.email.attendeeMatchesAddresses(invitedAttendee, userAddresses)
|
!cal.email.attendeeMatchesAddresses(invitedAttendee, userAddresses)
|
||||||
) {
|
) {
|
||||||
invitedAttendee = invitedAttendee.clone();
|
invitedAttendee = invitedAttendee.clone();
|
||||||
invitedAttendee.setProperty("SENT-BY", "mailto:" + userAddresses[0]);
|
invitedAttendee.setProperty("SENT-BY", cal.email.prependMailTo(userAddresses[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.organizer) {
|
if (item.organizer) {
|
||||||
let origInvitedAttendee = originalItem && originalItem.getAttendeeById(invitedAttendee.id);
|
let origInvitedAttendee = originalItem && originalItem.getAttendeeById(invitedAttendee.id);
|
||||||
|
|
||||||
|
@ -312,6 +316,7 @@ class CalItipMessageSender {
|
||||||
attendee.rsvp = "TRUE";
|
attendee.rsvp = "TRUE";
|
||||||
requestItem.addAttendee(attendee);
|
requestItem.addAttendee(attendee);
|
||||||
}
|
}
|
||||||
|
|
||||||
recipients.push(attendee);
|
recipients.push(attendee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +333,14 @@ class CalItipMessageSender {
|
||||||
recipients = addedAttendees;
|
recipients = addedAttendees;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since this is a REQUEST, it is being sent from the event creator to
|
||||||
|
// attendees. We do not need to send a message to the creator, even
|
||||||
|
// though they may also be an attendee.
|
||||||
|
const calendarEmail = cal.provider.getEmailIdentityOfCalendar(item.calendar)?.email;
|
||||||
|
recipients = recipients.filter(
|
||||||
|
attendee => cal.email.removeMailTo(attendee.id) != calendarEmail
|
||||||
|
);
|
||||||
|
|
||||||
if (recipients.length > 0) {
|
if (recipients.length > 0) {
|
||||||
this.pendingMessages.push(
|
this.pendingMessages.push(
|
||||||
new CalItipOutgoingMessage("REQUEST", recipients, requestItem, null, autoResponse)
|
new CalItipOutgoingMessage("REQUEST", recipients, requestItem, null, autoResponse)
|
||||||
|
@ -352,7 +365,7 @@ class CalItipMessageSender {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the iTIP message using the item's calendar transport. This method
|
* Sends the iTIP message using the item's calendar transport. This method
|
||||||
* should be called after detectChanges().
|
* should be called after buildOutgoingMessages().
|
||||||
*
|
*
|
||||||
* @param {calIItipTransport} [transport] - An optional transport to use
|
* @param {calIItipTransport} [transport] - An optional transport to use
|
||||||
* instead of the one provided by the item's calendar.
|
* instead of the one provided by the item's calendar.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs");
|
var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs");
|
||||||
|
const { CalAttendee } = ChromeUtils.import("resource:///modules/CalAttendee.jsm");
|
||||||
var { CalEvent } = ChromeUtils.import("resource:///modules/CalEvent.jsm");
|
var { CalEvent } = ChromeUtils.import("resource:///modules/CalEvent.jsm");
|
||||||
var { CalItipMessageSender } = ChromeUtils.import("resource:///modules/CalItipMessageSender.jsm");
|
var { CalItipMessageSender } = ChromeUtils.import("resource:///modules/CalItipMessageSender.jsm");
|
||||||
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
|
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
|
||||||
|
@ -12,10 +13,22 @@ var { CalendarTestUtils } = ChromeUtils.import(
|
||||||
);
|
);
|
||||||
|
|
||||||
const identityEmail = "user@example.com";
|
const identityEmail = "user@example.com";
|
||||||
const calendarOrganizerId = "mailto:user@example.com";
|
|
||||||
const eventOrganizerEmail = "eventorganizer@example.com";
|
const eventOrganizerEmail = "eventorganizer@example.com";
|
||||||
const eventOrganizerId = `mailto:${eventOrganizerEmail}`;
|
|
||||||
const icalString = CalendarTestUtils.dedent`
|
/**
|
||||||
|
* Creates a calendar event mimicking an event to which we have received an
|
||||||
|
* invitation.
|
||||||
|
*
|
||||||
|
* @param {string} organizerEmail - The email address of the event organizer.
|
||||||
|
* @param {string} attendeeEmail - The email address of an attendee who has
|
||||||
|
* accepted the invitation.
|
||||||
|
* @returns {calIItemBase} - The new calendar event.
|
||||||
|
*/
|
||||||
|
function createIncomingEvent(organizerEmail, attendeeEmail) {
|
||||||
|
const organizerId = cal.email.prependMailTo(organizerEmail);
|
||||||
|
const attendeeId = cal.email.prependMailTo(attendeeEmail);
|
||||||
|
|
||||||
|
const icalString = CalendarTestUtils.dedent`
|
||||||
BEGIN:VEVENT
|
BEGIN:VEVENT
|
||||||
CREATED:20210105T000000Z
|
CREATED:20210105T000000Z
|
||||||
DTSTAMP:20210501T000000Z
|
DTSTAMP:20210501T000000Z
|
||||||
|
@ -25,19 +38,21 @@ const icalString = CalendarTestUtils.dedent`
|
||||||
DTEND:20210105T100000Z
|
DTEND:20210105T100000Z
|
||||||
STATUS:CONFIRMED
|
STATUS:CONFIRMED
|
||||||
SUMMARY:Test Event
|
SUMMARY:Test Event
|
||||||
ORGANIZER;CN=${eventOrganizerEmail}:${eventOrganizerId}
|
ORGANIZER;CN=${organizerEmail}:${organizerId}
|
||||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
||||||
RSVP=TRUE;CN=other@example.com;:mailto:other@example.com
|
RSVP=TRUE;CN=other@example.com;:mailto:other@example.com
|
||||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
||||||
RSVP=TRUE;CN=${identityEmail};:${calendarOrganizerId}
|
RSVP=TRUE;CN=${attendeeEmail};:${attendeeId}
|
||||||
X-MOZ-RECEIVED-SEQUENCE:0
|
X-MOZ-RECEIVED-SEQUENCE:0
|
||||||
X-MOZ-RECEIVED-DTSTAMP:20210501T000000Z
|
X-MOZ-RECEIVED-DTSTAMP:20210501T000000Z
|
||||||
X-MOZ-GENERATION:0
|
X-MOZ-GENERATION:0
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
return new CalEvent(icalString);
|
||||||
|
}
|
||||||
|
|
||||||
let calendar;
|
let calendar;
|
||||||
let identity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the calendar manager is available, initialize the calendar and
|
* Ensure the calendar manager is available, initialize the calendar and
|
||||||
|
@ -46,113 +61,273 @@ let identity;
|
||||||
add_setup(async function () {
|
add_setup(async function () {
|
||||||
await new Promise(resolve => do_load_calmgr(resolve));
|
await new Promise(resolve => do_load_calmgr(resolve));
|
||||||
calendar = CalendarTestUtils.createCalendar("Test", "memory");
|
calendar = CalendarTestUtils.createCalendar("Test", "memory");
|
||||||
identity = MailServices.accounts.createIdentity();
|
|
||||||
|
const identity = MailServices.accounts.createIdentity();
|
||||||
identity.email = identityEmail;
|
identity.email = identityEmail;
|
||||||
calendar.setProperty("imip.identity.key", identity.key);
|
|
||||||
calendar.setProperty("organizerId", calendarOrganizerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
const account = MailServices.accounts.createAccount();
|
||||||
* Test receiving a new invitation queues a "REPLY" message.
|
account.incomingServer = MailServices.accounts.createIncomingServer(
|
||||||
*/
|
`${account.key}user`,
|
||||||
add_task(async function testInvitationReceived() {
|
"localhost",
|
||||||
const item = new CalEvent(icalString);
|
"none"
|
||||||
const savedItem = await calendar.addItem(item);
|
|
||||||
const invitedAttendee = savedItem.getAttendeeById(calendarOrganizerId);
|
|
||||||
const sender = new CalItipMessageSender(null, invitedAttendee);
|
|
||||||
const result = sender.detectChanges(Ci.calIOperationListener.ADD, savedItem);
|
|
||||||
Assert.equal(result, 1, "result indicates 1 pending message queued");
|
|
||||||
Assert.equal(sender.pendingMessageCount, 1, "pendingMessageCount is 1");
|
|
||||||
|
|
||||||
const [msg] = sender.pendingMessages;
|
|
||||||
Assert.equal(msg.method, "REPLY", "message method is 'REPLY'");
|
|
||||||
Assert.equal(msg.recipients.length, 1, "message has 1 recipient");
|
|
||||||
|
|
||||||
const [recipient] = msg.recipients;
|
|
||||||
Assert.equal(recipient.id, eventOrganizerId, "recipient is the event organizer");
|
|
||||||
|
|
||||||
const attendeeList = msg.item.getAttendees();
|
|
||||||
Assert.equal(attendeeList.length, 1, "reply attendees list has 1 attendee");
|
|
||||||
|
|
||||||
const [attendee] = attendeeList;
|
|
||||||
Assert.equal(attendee.id, calendarOrganizerId, "invited attendee is on the reply attendees list");
|
|
||||||
Assert.equal(
|
|
||||||
attendee.participationStatus,
|
|
||||||
"ACCEPTED",
|
|
||||||
"invited attendee participation status is 'ACCEPTED'"
|
|
||||||
);
|
);
|
||||||
|
account.addIdentity(identity);
|
||||||
|
|
||||||
await calendar.deleteItem(savedItem);
|
registerCleanupFunction(() => {
|
||||||
|
MailServices.accounts.removeIncomingServer(account.incomingServer, false);
|
||||||
|
MailServices.accounts.removeAccount(account);
|
||||||
|
});
|
||||||
|
|
||||||
|
calendar.setProperty("imip.identity.key", identity.key);
|
||||||
|
calendar.setProperty("organizerId", cal.email.prependMailTo(identityEmail));
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
add_task(async function testAddAttendeesToOwnEvent() {
|
||||||
* Test updating the invited attendee's participation status queues a "REPLY"
|
const icalString = CalendarTestUtils.dedent`
|
||||||
* message.
|
BEGIN:VEVENT
|
||||||
*/
|
CREATED:20210105T000000Z
|
||||||
add_task(async function testParticipationStatusUpdated() {
|
DTSTAMP:20210501T000000Z
|
||||||
|
UID:c1a6cfe7-7fbb-4bfb-a00d-861e07c649a5
|
||||||
|
SUMMARY:Test Invitation
|
||||||
|
DTSTART:20210105T000000Z
|
||||||
|
DTEND:20210105T100000Z
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:Test Event
|
||||||
|
X-MOZ-SEND-INVITATIONS:TRUE
|
||||||
|
END:VEVENT
|
||||||
|
`;
|
||||||
|
|
||||||
const item = new CalEvent(icalString);
|
const item = new CalEvent(icalString);
|
||||||
const savedItem = await calendar.addItem(item);
|
const savedItem = await calendar.addItem(item);
|
||||||
|
|
||||||
|
// Modify the event to include an attendee not in the original, as well as the
|
||||||
|
// organizer. As of the writing of this test, this is the expected behavior
|
||||||
|
// for adding an attendee to an event which previously had none.
|
||||||
|
const newAttendeeEmail = "foo@example.com";
|
||||||
|
const newAttendee = new CalAttendee();
|
||||||
|
newAttendee.id = newAttendeeEmail;
|
||||||
|
|
||||||
|
const organizer = new CalAttendee();
|
||||||
|
organizer.isOrganizer = true;
|
||||||
|
organizer.id = identityEmail;
|
||||||
|
|
||||||
|
const organizerAsAttendee = new CalAttendee();
|
||||||
|
organizerAsAttendee.id = identityEmail;
|
||||||
|
|
||||||
const targetItem = savedItem.clone();
|
const targetItem = savedItem.clone();
|
||||||
const invitedAttendee = targetItem.getAttendeeById(calendarOrganizerId);
|
targetItem.addAttendee(newAttendee);
|
||||||
invitedAttendee.participationStatus = "TENTATIVE";
|
targetItem.addAttendee(organizer);
|
||||||
|
targetItem.addAttendee(organizerAsAttendee);
|
||||||
const modifiedItem = await calendar.modifyItem(targetItem, savedItem);
|
const modifiedItem = await calendar.modifyItem(targetItem, savedItem);
|
||||||
const sender = new CalItipMessageSender(savedItem, invitedAttendee);
|
|
||||||
const result = sender.detectChanges(Ci.calIOperationListener.MODIFY, modifiedItem);
|
// Test that a sender with an original item and for which the current user is
|
||||||
Assert.equal(result, 1, "result indicates 1 pending message queued");
|
// both an attendee and the organizer will generate a REQUEST, but not send a
|
||||||
Assert.equal(sender.pendingMessageCount, 1, "pendingMessageCount is 1");
|
// message to the organizer.
|
||||||
|
const sender = new CalItipMessageSender(savedItem, null);
|
||||||
|
|
||||||
|
const result = sender.buildOutgoingMessages(Ci.calIOperationListener.MODIFY, modifiedItem);
|
||||||
|
Assert.equal(result, 1, "return value should indicate there are pending messages");
|
||||||
|
Assert.equal(sender.pendingMessageCount, 1, "there should be one pending message");
|
||||||
|
|
||||||
const [msg] = sender.pendingMessages;
|
const [msg] = sender.pendingMessages;
|
||||||
Assert.equal(msg.method, "REPLY", "message method is 'REPLY'");
|
Assert.equal(msg.method, "REQUEST", "message method should be 'REQUEST'");
|
||||||
Assert.equal(msg.recipients.length, 1, "message has 1 recipient");
|
Assert.equal(msg.recipients.length, 1, "message should have one recipient");
|
||||||
|
|
||||||
const [recipient] = msg.recipients;
|
const [recipient] = msg.recipients;
|
||||||
Assert.equal(recipient.id, eventOrganizerId, "recipient is the event organizer");
|
|
||||||
|
|
||||||
const attendeeList = msg.item.getAttendees();
|
|
||||||
Assert.equal(attendeeList.length, 1, "reply attendees list has 1 attendee");
|
|
||||||
|
|
||||||
const [attendee] = attendeeList;
|
|
||||||
Assert.equal(attendee.id, calendarOrganizerId, "invited attendee is on the reply attendees list");
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
attendee.participationStatus,
|
recipient.id,
|
||||||
"TENTATIVE",
|
cal.email.prependMailTo(newAttendeeEmail),
|
||||||
"invited attendee participation status is 'TENTATIVE'"
|
"recipient should be the non-organizer attendee"
|
||||||
);
|
);
|
||||||
|
|
||||||
await calendar.deleteItem(modifiedItem);
|
await calendar.deleteItem(modifiedItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
add_task(async function testAddAdditionalAttendee() {
|
||||||
* Test deleting an event queues a "CANCEL" message.
|
const icalString = CalendarTestUtils.dedent`
|
||||||
*/
|
BEGIN:VEVENT
|
||||||
add_task(async function testEventDeleted() {
|
CREATED:20210105T000000Z
|
||||||
|
DTSTAMP:20210501T000000Z
|
||||||
|
UID:c1a6cfe7-7fbb-4bfb-a00d-861e07c649a5
|
||||||
|
SUMMARY:Test Invitation
|
||||||
|
DTSTART:20210105T000000Z
|
||||||
|
DTEND:20210105T100000Z
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:Test Event
|
||||||
|
ORGANIZER;CN=${identityEmail}:${cal.email.prependMailTo(identityEmail)}
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
||||||
|
RSVP=TRUE;CN=other@example.com;:mailto:other@example.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
||||||
|
RSVP=TRUE;CN=${identityEmail};:${cal.email.prependMailTo(identityEmail)}
|
||||||
|
X-MOZ-SEND-INVITATIONS:TRUE
|
||||||
|
END:VEVENT
|
||||||
|
`;
|
||||||
|
|
||||||
const item = new CalEvent(icalString);
|
const item = new CalEvent(icalString);
|
||||||
const savedItem = await calendar.addItem(item);
|
const savedItem = await calendar.addItem(item);
|
||||||
|
|
||||||
await calendar.deleteItem(savedItem);
|
// Modify the event to include an attendee not in the original.
|
||||||
const invitedAttendee = savedItem.getAttendeeById(calendarOrganizerId);
|
const newAttendeeEmail = "bar@example.com";
|
||||||
const sender = new CalItipMessageSender(null, invitedAttendee);
|
const newAttendee = new CalAttendee();
|
||||||
const result = sender.detectChanges(Ci.calIOperationListener.DELETE, savedItem);
|
newAttendee.id = newAttendeeEmail;
|
||||||
Assert.equal(result, 1, "result indicates 1 pending message queued");
|
|
||||||
Assert.equal(sender.pendingMessageCount, 1, "pendingMessageCount is 1");
|
const organizer = new CalAttendee();
|
||||||
|
organizer.isOrganizer = true;
|
||||||
|
organizer.id = identityEmail;
|
||||||
|
|
||||||
|
const organizerAsAttendee = new CalAttendee();
|
||||||
|
organizerAsAttendee.id = identityEmail;
|
||||||
|
|
||||||
|
const targetItem = savedItem.clone();
|
||||||
|
targetItem.addAttendee(newAttendee);
|
||||||
|
const modifiedItem = await calendar.modifyItem(targetItem, savedItem);
|
||||||
|
|
||||||
|
// Test that adding an attendee won't cause messages to be sent to the
|
||||||
|
// existing attendees.
|
||||||
|
const sender = new CalItipMessageSender(savedItem, null);
|
||||||
|
|
||||||
|
const result = sender.buildOutgoingMessages(Ci.calIOperationListener.MODIFY, modifiedItem);
|
||||||
|
Assert.equal(result, 1, "return value should indicate there are pending messages");
|
||||||
|
Assert.equal(sender.pendingMessageCount, 1, "there should be one pending message");
|
||||||
|
|
||||||
const [msg] = sender.pendingMessages;
|
const [msg] = sender.pendingMessages;
|
||||||
Assert.equal(msg.method, "REPLY", "message method is 'REPLY'");
|
Assert.equal(msg.method, "REQUEST", "message method should be 'REQUEST'");
|
||||||
Assert.equal(msg.recipients.length, 1, "message has 1 recipient");
|
Assert.equal(msg.recipients.length, 1, "message should have one recipient");
|
||||||
|
|
||||||
const [recipient] = msg.recipients;
|
const [recipient] = msg.recipients;
|
||||||
Assert.equal(recipient.id, eventOrganizerId, "recipient is the event organizer");
|
Assert.equal(
|
||||||
|
recipient.id,
|
||||||
|
cal.email.prependMailTo(newAttendeeEmail),
|
||||||
|
"recipient should be the new attendee"
|
||||||
|
);
|
||||||
|
|
||||||
|
await calendar.deleteItem(modifiedItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testInvitationReceived() {
|
||||||
|
const item = createIncomingEvent(eventOrganizerEmail, identityEmail);
|
||||||
|
const savedItem = await calendar.addItem(item);
|
||||||
|
|
||||||
|
const attendeeId = cal.email.prependMailTo(identityEmail);
|
||||||
|
|
||||||
|
// Test that a sender with no original item and for which the current user is
|
||||||
|
// an attendee but not the organizer (representing a new incoming invitation)
|
||||||
|
// generates a single pending REPLY message on ADD.
|
||||||
|
const currentUserAsAttendee = savedItem.getAttendeeById(attendeeId);
|
||||||
|
const sender = new CalItipMessageSender(null, currentUserAsAttendee);
|
||||||
|
|
||||||
|
const result = sender.buildOutgoingMessages(Ci.calIOperationListener.ADD, savedItem);
|
||||||
|
Assert.equal(result, 1, "return value should indicate there are pending messages");
|
||||||
|
Assert.equal(sender.pendingMessageCount, 1, "there should be one pending message");
|
||||||
|
|
||||||
|
const [msg] = sender.pendingMessages;
|
||||||
|
Assert.equal(msg.method, "REPLY", "message method should be 'REPLY'");
|
||||||
|
Assert.equal(msg.recipients.length, 1, "message should have one recipient");
|
||||||
|
|
||||||
|
const [recipient] = msg.recipients;
|
||||||
|
Assert.equal(
|
||||||
|
recipient.id,
|
||||||
|
cal.email.prependMailTo(eventOrganizerEmail),
|
||||||
|
"recipient should be the event organizer"
|
||||||
|
);
|
||||||
|
|
||||||
const attendeeList = msg.item.getAttendees();
|
const attendeeList = msg.item.getAttendees();
|
||||||
Assert.equal(attendeeList.length, 1, "reply attendees list has 1 attendee");
|
Assert.equal(attendeeList.length, 1, "there should be one attendee listed in the message");
|
||||||
|
|
||||||
const [attendee] = attendeeList;
|
const [attendee] = attendeeList;
|
||||||
Assert.equal(attendee.id, calendarOrganizerId, "invited attendee is on the reply attendees list");
|
Assert.equal(attendee.id, attendeeId, "listed attendee should be the current user");
|
||||||
|
Assert.equal(
|
||||||
|
attendee.participationStatus,
|
||||||
|
"ACCEPTED",
|
||||||
|
"current user's participation status should be 'ACCEPTED'"
|
||||||
|
);
|
||||||
|
|
||||||
|
await calendar.deleteItem(savedItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testParticipationStatusUpdated() {
|
||||||
|
const item = createIncomingEvent(eventOrganizerEmail, identityEmail);
|
||||||
|
const savedItem = await calendar.addItem(item);
|
||||||
|
|
||||||
|
const attendeeId = cal.email.prependMailTo(identityEmail);
|
||||||
|
|
||||||
|
// Modify the event to update the user's participation status.
|
||||||
|
const targetItem = savedItem.clone();
|
||||||
|
const currentUserAsAttendee = targetItem.getAttendeeById(attendeeId);
|
||||||
|
currentUserAsAttendee.participationStatus = "TENTATIVE";
|
||||||
|
const modifiedItem = await calendar.modifyItem(targetItem, savedItem);
|
||||||
|
|
||||||
|
// Test that a sender for which the current user is an attendee but not the
|
||||||
|
// organizer will generate a pending REPLY message on MODIFY.
|
||||||
|
const sender = new CalItipMessageSender(savedItem, currentUserAsAttendee);
|
||||||
|
const result = sender.buildOutgoingMessages(Ci.calIOperationListener.MODIFY, modifiedItem);
|
||||||
|
|
||||||
|
Assert.equal(result, 1, "return value should indicate there are pending messages");
|
||||||
|
Assert.equal(sender.pendingMessageCount, 1, "there should be one pending message");
|
||||||
|
|
||||||
|
const [msg] = sender.pendingMessages;
|
||||||
|
Assert.equal(msg.method, "REPLY", "message method should be 'REPLY'");
|
||||||
|
Assert.equal(msg.recipients.length, 1, "message should have one recipient");
|
||||||
|
|
||||||
|
const [recipient] = msg.recipients;
|
||||||
|
Assert.equal(
|
||||||
|
recipient.id,
|
||||||
|
cal.email.prependMailTo(eventOrganizerEmail),
|
||||||
|
"recipient should be the event organizer"
|
||||||
|
);
|
||||||
|
|
||||||
|
const attendeeList = msg.item.getAttendees();
|
||||||
|
Assert.equal(attendeeList.length, 1, "there should be one attendee listed in the message");
|
||||||
|
|
||||||
|
const [attendee] = attendeeList;
|
||||||
|
Assert.equal(attendee.id, attendeeId, "listed attendee should be the current user");
|
||||||
|
Assert.equal(
|
||||||
|
attendee.participationStatus,
|
||||||
|
"TENTATIVE",
|
||||||
|
"current user's participation status should be 'TENTATIVE'"
|
||||||
|
);
|
||||||
|
|
||||||
|
await calendar.deleteItem(modifiedItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testEventDeleted() {
|
||||||
|
const item = createIncomingEvent(eventOrganizerEmail, identityEmail);
|
||||||
|
const savedItem = await calendar.addItem(item);
|
||||||
|
|
||||||
|
const attendeeId = cal.email.prependMailTo(identityEmail);
|
||||||
|
|
||||||
|
await calendar.deleteItem(savedItem);
|
||||||
|
const currentUserAsAttendee = savedItem.getAttendeeById(attendeeId);
|
||||||
|
|
||||||
|
// Test that a sender with no original item and for which the current user is
|
||||||
|
// an attendee but not the organizer (representing the user deleting an event
|
||||||
|
// from their calendar) generates a single REPLY message to the organizer on
|
||||||
|
// DELETE.
|
||||||
|
const sender = new CalItipMessageSender(null, currentUserAsAttendee);
|
||||||
|
const result = sender.buildOutgoingMessages(Ci.calIOperationListener.DELETE, savedItem);
|
||||||
|
|
||||||
|
Assert.equal(result, 1, "return value should indicate there are pending messages");
|
||||||
|
Assert.equal(sender.pendingMessageCount, 1, "there should be one pending message");
|
||||||
|
|
||||||
|
const [msg] = sender.pendingMessages;
|
||||||
|
Assert.equal(msg.method, "REPLY", "message method should be 'REPLY'");
|
||||||
|
Assert.equal(msg.recipients.length, 1, "message should have one recipient");
|
||||||
|
|
||||||
|
const [recipient] = msg.recipients;
|
||||||
|
Assert.equal(
|
||||||
|
recipient.id,
|
||||||
|
cal.email.prependMailTo(eventOrganizerEmail),
|
||||||
|
"recipient should be the event organizer"
|
||||||
|
);
|
||||||
|
|
||||||
|
const attendeeList = msg.item.getAttendees();
|
||||||
|
Assert.equal(attendeeList.length, 1, "there should be one attendee listed in the message");
|
||||||
|
|
||||||
|
const [attendee] = attendeeList;
|
||||||
|
Assert.equal(attendee.id, attendeeId, "listed attendee should be the current user");
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
attendee.participationStatus,
|
attendee.participationStatus,
|
||||||
"DECLINED",
|
"DECLINED",
|
||||||
"invited attendee status changed to 'DECLINED'"
|
"current user's participation status should be 'DECLINED'"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ head = head.js
|
||||||
prefs =
|
prefs =
|
||||||
calendar.timezone.local=UTC
|
calendar.timezone.local=UTC
|
||||||
calendar.timezone.useSystemTimezone=false
|
calendar.timezone.useSystemTimezone=false
|
||||||
|
calendar.itip.updateInvitationForNewAttendeesOnly=true
|
||||||
support-files = data/**
|
support-files = data/**
|
||||||
tags = calendar
|
tags = calendar
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче