aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorSebastianKrupinski <krupinskis05@gmail.com>2024-04-26 06:59:46 -0400
committerSebastianKrupinski <krupinskis05@gmail.com>2024-08-06 08:39:24 -0400
commit0df30363a4948384f8da43b963254ce69961f320 (patch)
tree0ff462ac6eb5872825ba3810a5865fc69fdd2db9 /apps
parentb6e3009e12485536ca72225f7d6e6a92914c15bf (diff)
downloadnextcloud-server-0df30363a4948384f8da43b963254ce69961f320.tar.gz
nextcloud-server-0df30363a4948384f8da43b963254ce69961f320.zip
fix(caldav): Add sharee to address list when calendar is shared
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/dav/lib/CalDAV/Schedule/Plugin.php39
-rw-r--r--apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php401
2 files changed, 408 insertions, 32 deletions
diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php
index 5496fb23a96..48f4cbf22ef 100644
--- a/apps/dav/lib/CalDAV/Schedule/Plugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php
@@ -158,7 +158,42 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
}
try {
- parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
+
+ if (!$this->scheduleReply($this->server->httpRequest)) {
+ return;
+ }
+
+ /** @var \OCA\DAV\CalDAV\Calendar $calendarNode */
+ $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
+ // extract addresses for owner
+ $addresses = $this->getAddressesForPrincipal($calendarNode->getOwner());
+ // determain if request is from a sharee
+ if ($calendarNode->isShared()) {
+ // extract addresses for sharee and add to address collection
+ $addresses = array_merge(
+ $addresses,
+ $this->getAddressesForPrincipal($calendarNode->getPrincipalURI())
+ );
+ }
+ // determine if we are updating a calendar event
+ if (!$isNew) {
+ // retrieve current calendar event node
+ /** @var \OCA\DAV\CalDAV\CalendarObject $currentNode */
+ $currentNode = $this->server->tree->getNodeForPath($request->getPath());
+ // convert calendar event string data to VCalendar object
+ /** @var \Sabre\VObject\Component\VCalendar $currentObject */
+ $currentObject = Reader::read($currentNode->get());
+ } else {
+ $currentObject = null;
+ }
+ // process request
+ $this->processICalendarChange($currentObject, $vCal, $addresses, [], $modified);
+
+ if ($currentObject) {
+ // Destroy circular references so PHP will GC the object.
+ $currentObject->destroy();
+ }
+
} catch (SameOrganizerForAllComponentsException $e) {
$this->handleSameOrganizerException($e, $vCal, $calendarPath);
}
@@ -526,7 +561,9 @@ EOF;
$calendarTimeZone = new DateTimeZone('UTC');
$homePath = $result[0][200]['{' . self::NS_CALDAV . '}calendar-home-set']->getHref();
+ /** @var \OCA\DAV\CalDAV\Calendar $node */
foreach ($this->server->tree->getNodeForPath($homePath)->getChildren() as $node) {
+
if (!$node instanceof ICalendar) {
continue;
}
diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
index 18788ecc395..853094e4de2 100644
--- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
+++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
@@ -16,74 +16,77 @@ use OCP\IConfig;
use OCP\IL10N;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
+use Sabre\CalDAV\Backend\BackendInterface;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\Tree;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL\IPrincipal;
+use Sabre\HTTP\Request;
+use Sabre\HTTP\Response;
use Sabre\HTTP\ResponseInterface;
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\ITip\Message;
use Sabre\VObject\Parameter;
use Sabre\VObject\Property\ICalendar\CalAddress;
use Sabre\Xml\Service;
use Test\TestCase;
class PluginTest extends TestCase {
+
/** @var Plugin */
private $plugin;
+
/** @var Server|MockObject */
private $server;
/** @var IConfig|MockObject */
private $config;
- /** @var MockObject|LoggerInterface */
+ /** @var LoggerInterface&MockObject */
private $logger;
- /** @property MockObject|DefaultCalendarValidator */
- private $defaultCalendarValidator;
+ /** @var DefaultCalendarValidator */
+ private $calendarValidator;
protected function setUp(): void {
parent::setUp();
- $this->server = $this->createMock(Server::class);
$this->config = $this->createMock(IConfig::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->calendarValidator = new DefaultCalendarValidator();
- $response = $this->getMockBuilder(ResponseInterface::class)
+ $this->server = $this->createMock(Server::class);
+ $this->server->httpResponse = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
-
- $this->server->httpResponse = $response;
$this->server->xml = new Service();
- $this->logger = $this->createMock(LoggerInterface::class);
- $this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class);
-
- $this->plugin = new Plugin($this->config, $this->logger, $this->defaultCalendarValidator);
+ $this->plugin = new Plugin($this->config, $this->logger, $this->calendarValidator);
$this->plugin->initialize($this->server);
}
public function testInitialize(): void {
- $plugin = new Plugin($this->config, $this->logger, $this->defaultCalendarValidator);
$this->server->expects($this->exactly(10))
->method('on')
->withConsecutive(
// Sabre\CalDAV\Schedule\Plugin events
- ['method:POST', [$plugin, 'httpPost']],
- ['propFind', [$plugin, 'propFind']],
- ['propPatch', [$plugin, 'propPatch']],
- ['calendarObjectChange', [$plugin, 'calendarObjectChange']],
- ['beforeUnbind', [$plugin, 'beforeUnbind']],
- ['schedule', [$plugin, 'scheduleLocalDelivery']],
- ['getSupportedPrivilegeSet', [$plugin, 'getSupportedPrivilegeSet']],
+ ['method:POST', [$this->plugin, 'httpPost']],
+ ['propFind', [$this->plugin, 'propFind']],
+ ['propPatch', [$this->plugin, 'propPatch']],
+ ['calendarObjectChange', [$this->plugin, 'calendarObjectChange']],
+ ['beforeUnbind', [$this->plugin, 'beforeUnbind']],
+ ['schedule', [$this->plugin, 'scheduleLocalDelivery']],
+ ['getSupportedPrivilegeSet', [$this->plugin, 'getSupportedPrivilegeSet']],
// OCA\DAV\CalDAV\Schedule\Plugin events
- ['propFind', [$plugin, 'propFindDefaultCalendarUrl'], 90],
- ['afterWriteContent', [$plugin, 'dispatchSchedulingResponses']],
- ['afterCreateFile', [$plugin, 'dispatchSchedulingResponses']]
+ ['propFind', [$this->plugin, 'propFindDefaultCalendarUrl'], 90],
+ ['afterWriteContent', [$this->plugin, 'dispatchSchedulingResponses']],
+ ['afterCreateFile', [$this->plugin, 'dispatchSchedulingResponses']]
);
- $plugin->initialize($this->server);
+ $this->plugin->initialize($this->server);
}
public function testGetAddressesForPrincipal(): void {
@@ -109,7 +112,6 @@ class PluginTest extends TestCase {
$this->assertSame(['lukas@nextcloud.com', 'rullzer@nextcloud.com'], $result);
}
-
public function testGetAddressesForPrincipalEmpty(): void {
$this->server
->expects($this->once())
@@ -381,13 +383,6 @@ class PluginTest extends TestCase {
->with($calendarHome .'/' . $calendarUri, [], 1)
->willReturn($properties);
- $this->defaultCalendarValidator->method('validateScheduleDefaultCalendar')
- ->willReturnCallback(function (Calendar $node) {
- if ($node->isDeleted()) {
- throw new \Sabre\DAV\Exception('Deleted calendar');
- }
- });
-
$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
if (!$propertiesForPath) {
@@ -399,4 +394,348 @@ class PluginTest extends TestCase {
$result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
$this->assertEquals('/remote.php/dav/'. $calendarHome . '/' . $calendarUri, $result->getHref());
}
+
+ /**
+ * Test Calendar Event Creation for Personal Calendar
+ *
+ * Should generate 2 messages for attendees User 2 and User External
+ */
+ public function testCalendarObjectChangePersonalCalendarCreate() {
+
+ // define place holders
+ /** @var Message[] $iTipMessages */
+ $iTipMessages = [];
+ // construct calendar node
+ $calendarNode = new Calendar(
+ $this->createMock(BackendInterface::class),
+ [
+ 'uri' => 'personal',
+ 'principaluri' => 'principals/users/user1',
+ '{DAV:}displayname' => 'Calendar Shared By User1',
+ ],
+ $this->createMock(IL10N::class),
+ $this->config,
+ $this->logger
+ );
+ // construct server request object
+ $request = new Request(
+ 'PUT',
+ '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
+ );
+ $request->setBaseUrl('/remote.php/dav/');
+ // construct server response object
+ $response = new Response();
+ // construct server tree object
+ $tree = $this->createMock(Tree::class);
+ $tree->expects($this->once())
+ ->method('getNodeForPath')
+ ->with('calendars/user1/personal')
+ ->willReturn($calendarNode);
+ // construct server properties and returns
+ $this->server->httpRequest = $request;
+ $this->server->tree = $tree;
+ $this->server->expects($this->exactly(1))->method('getProperties')
+ ->willReturnMap([
+ [
+ 'principals/users/user1',
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
+ ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
+ )]
+ ]
+ ]);
+ $this->server->expects($this->exactly(2))->method('emit')->willReturnCallback(
+ function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
+ $this->assertEquals('schedule', $eventName);
+ $this->assertCount(1, $arguments);
+ $iTipMessages[] = $arguments[0];
+ return true;
+ }
+ );
+ // construct calendar with a 1 hour event and same start/end time zones
+ $vCalendar = new VCalendar();
+ $vEvent = $vCalendar->add('VEVENT', []);
+ $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
+ $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('SUMMARY', 'Test Recurring Event');
+ $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']);
+ $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [
+ 'CN' => 'User Two',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
+ 'CN' => 'User External',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ // define flags
+ $newFlag = true;
+ $modifiedFlag = false;
+ // execute method
+ $this->plugin->calendarObjectChange(
+ $request,
+ $response,
+ $vCalendar,
+ 'calendars/user1/personal',
+ $modifiedFlag,
+ $newFlag
+ );
+ // test for correct iTip message count
+ $this->assertCount(2, $iTipMessages);
+ // test for Sharer Attendee
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender);
+ $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient);
+ $this->assertTrue($iTipMessages[0]->significantChange);
+ // test for External Attendee
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender);
+ $this->assertEquals('mailto:user@external.local', $iTipMessages[1]->recipient);
+ $this->assertTrue($iTipMessages[1]->significantChange);
+
+ }
+
+ /**
+ * Test Calendar Event Creation for Shared Calendar as Sharer/Owner
+ *
+ * Should generate 3 messages for attendees User 2 (Sharee), User 3 (Non-Sharee) and User External
+ */
+ public function testCalendarObjectChangeSharedCalendarSharerCreate() {
+
+ // define place holders
+ /** @var Message[] $iTipMessages */
+ $iTipMessages = [];
+ // construct calendar node
+ $calendarNode = new Calendar(
+ $this->createMock(BackendInterface::class),
+ [
+ 'uri' => 'calendar_shared_by_user1',
+ 'principaluri' => 'principals/users/user1',
+ '{DAV:}displayname' => 'Calendar Shared By User1',
+ '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
+ ],
+ $this->createMock(IL10N::class),
+ $this->config,
+ $this->logger
+ );
+ // construct server request object
+ $request = new Request(
+ 'PUT',
+ '/remote.php/dav/calendars/user1/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
+ );
+ $request->setBaseUrl('/remote.php/dav/');
+ // construct server response object
+ $response = new Response();
+ // construct server tree object
+ $tree = $this->createMock(Tree::class);
+ $tree->expects($this->once())
+ ->method('getNodeForPath')
+ ->with('calendars/user1/calendar_shared_by_user1')
+ ->willReturn($calendarNode);
+ // construct server properties and returns
+ $this->server->httpRequest = $request;
+ $this->server->tree = $tree;
+ $this->server->expects($this->exactly(1))->method('getProperties')
+ ->willReturnMap([
+ [
+ 'principals/users/user1',
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
+ ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
+ )]
+ ]
+ ]);
+ $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
+ function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
+ $this->assertEquals('schedule', $eventName);
+ $this->assertCount(1, $arguments);
+ $iTipMessages[] = $arguments[0];
+ return true;
+ }
+ );
+ // construct calendar with a 1 hour event and same start/end time zones
+ $vCalendar = new VCalendar();
+ $vEvent = $vCalendar->add('VEVENT', []);
+ $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
+ $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('SUMMARY', 'Test Recurring Event');
+ $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']);
+ $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [
+ 'CN' => 'User Two',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [
+ 'CN' => 'User Three',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
+ 'CN' => 'User External',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ // define flags
+ $newFlag = true;
+ $modifiedFlag = false;
+ // execute method
+ $this->plugin->calendarObjectChange(
+ $request,
+ $response,
+ $vCalendar,
+ 'calendars/user1/calendar_shared_by_user1',
+ $modifiedFlag,
+ $newFlag
+ );
+ // test for correct iTip message count
+ $this->assertCount(3, $iTipMessages);
+ // test for Sharer Attendee
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender);
+ $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient);
+ $this->assertTrue($iTipMessages[0]->significantChange);
+ // test for Non Shee Attendee
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender);
+ $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient);
+ $this->assertTrue($iTipMessages[1]->significantChange);
+ // test for External Attendee
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[2]->sender);
+ $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient);
+ $this->assertTrue($iTipMessages[2]->significantChange);
+
+ }
+
+ /**
+ * Test Calendar Event Creation for Shared Calendar as Shree
+ *
+ * Should generate 3 messages for attendees User 1 (Sharer/Owner), User 3 (Non-Sharee) and User External
+ */
+ public function testCalendarObjectChangeSharedCalendarShreeCreate() {
+
+ // define place holders
+ /** @var Message[] $iTipMessages */
+ $iTipMessages = [];
+ // construct calendar node
+ $calendarNode = new Calendar(
+ $this->createMock(BackendInterface::class),
+ [
+ 'uri' => 'calendar_shared_by_user1',
+ 'principaluri' => 'principals/users/user2',
+ '{DAV:}displayname' => 'Calendar Shared By User1',
+ '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
+ ],
+ $this->createMock(IL10N::class),
+ $this->config,
+ $this->logger
+ );
+ // construct server request object
+ $request = new Request(
+ 'PUT',
+ '/remote.php/dav/calendars/user2/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
+ );
+ $request->setBaseUrl('/remote.php/dav/');
+ // construct server response object
+ $response = new Response();
+ // construct server tree object
+ $tree = $this->createMock(Tree::class);
+ $tree->expects($this->once())
+ ->method('getNodeForPath')
+ ->with('calendars/user2/calendar_shared_by_user1')
+ ->willReturn($calendarNode);
+ // construct server properties and returns
+ $this->server->httpRequest = $request;
+ $this->server->tree = $tree;
+ $this->server->expects($this->exactly(2))->method('getProperties')
+ ->willReturnMap([
+ [
+ 'principals/users/user1',
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
+ ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
+ )]
+ ],
+ [
+ 'principals/users/user2',
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
+ ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
+ ['mailto:user2@testing.local','/remote.php/dav/principals/users/user2/']
+ )]
+ ]
+ ]);
+ $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
+ function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
+ $this->assertEquals('schedule', $eventName);
+ $this->assertCount(1, $arguments);
+ $iTipMessages[] = $arguments[0];
+ return true;
+ }
+ );
+ // construct calendar with a 1 hour event and same start/end time zones
+ $vCalendar = new VCalendar();
+ $vEvent = $vCalendar->add('VEVENT', []);
+ $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
+ $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
+ $vEvent->add('SUMMARY', 'Test Recurring Event');
+ $vEvent->add('ORGANIZER', 'mailto:user2@testing.local', ['CN' => 'User Two']);
+ $vEvent->add('ATTENDEE', 'mailto:user1@testing.local', [
+ 'CN' => 'User One',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [
+ 'CN' => 'User Three',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
+ 'CN' => 'User External',
+ 'CUTYPE' => 'INDIVIDUAL',
+ 'PARTSTAT' => 'NEEDS-ACTION',
+ 'ROLE' => 'REQ-PARTICIPANT',
+ 'RSVP' => 'TRUE'
+ ]);
+ // define flags
+ $newFlag = true;
+ $modifiedFlag = false;
+ // execute method
+ $this->plugin->calendarObjectChange(
+ $request,
+ $response,
+ $vCalendar,
+ 'calendars/user2/calendar_shared_by_user1',
+ $modifiedFlag,
+ $newFlag
+ );
+ // test for correct iTip message count
+ $this->assertCount(3, $iTipMessages);
+ // test for Sharer Attendee
+ $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->sender);
+ $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->recipient);
+ $this->assertTrue($iTipMessages[0]->significantChange);
+ // test for Non Shee Attendee
+ $this->assertEquals('mailto:user2@testing.local', $iTipMessages[1]->sender);
+ $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient);
+ $this->assertTrue($iTipMessages[1]->significantChange);
+ // test for External Attendee
+ $this->assertEquals('mailto:user2@testing.local', $iTipMessages[2]->sender);
+ $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient);
+ $this->assertTrue($iTipMessages[2]->significantChange);
+
+ }
+
}