PluginTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OCA\DAV\Tests\unit\CalDAV\Schedule;
  7. use OCA\DAV\CalDAV\CalDavBackend;
  8. use OCA\DAV\CalDAV\Calendar;
  9. use OCA\DAV\CalDAV\CalendarHome;
  10. use OCA\DAV\CalDAV\DefaultCalendarValidator;
  11. use OCA\DAV\CalDAV\Plugin as CalDAVPlugin;
  12. use OCA\DAV\CalDAV\Schedule\Plugin;
  13. use OCA\DAV\CalDAV\Trashbin\Plugin as TrashbinPlugin;
  14. use OCP\IConfig;
  15. use OCP\IL10N;
  16. use PHPUnit\Framework\MockObject\MockObject;
  17. use Psr\Log\LoggerInterface;
  18. use Sabre\CalDAV\Backend\BackendInterface;
  19. use Sabre\DAV\PropFind;
  20. use Sabre\DAV\Server;
  21. use Sabre\DAV\Tree;
  22. use Sabre\DAV\Xml\Property\Href;
  23. use Sabre\DAV\Xml\Property\LocalHref;
  24. use Sabre\DAVACL\IPrincipal;
  25. use Sabre\HTTP\Request;
  26. use Sabre\HTTP\Response;
  27. use Sabre\HTTP\ResponseInterface;
  28. use Sabre\VObject\Component\VCalendar;
  29. use Sabre\VObject\ITip\Message;
  30. use Sabre\VObject\Parameter;
  31. use Sabre\VObject\Property\ICalendar\CalAddress;
  32. use Sabre\Xml\Service;
  33. use Test\TestCase;
  34. class PluginTest extends TestCase {
  35. /** @var Plugin */
  36. private $plugin;
  37. /** @var Server|MockObject */
  38. private $server;
  39. /** @var IConfig|MockObject */
  40. private $config;
  41. /** @var LoggerInterface&MockObject */
  42. private $logger;
  43. /** @var DefaultCalendarValidator */
  44. private $calendarValidator;
  45. protected function setUp(): void {
  46. parent::setUp();
  47. $this->config = $this->createMock(IConfig::class);
  48. $this->logger = $this->createMock(LoggerInterface::class);
  49. $this->calendarValidator = new DefaultCalendarValidator();
  50. $this->server = $this->createMock(Server::class);
  51. $this->server->httpResponse = $this->getMockBuilder(ResponseInterface::class)
  52. ->disableOriginalConstructor()
  53. ->getMock();
  54. $this->server->xml = new Service();
  55. $this->plugin = new Plugin($this->config, $this->logger, $this->calendarValidator);
  56. $this->plugin->initialize($this->server);
  57. }
  58. public function testInitialize(): void {
  59. $this->server->expects($this->exactly(10))
  60. ->method('on')
  61. ->withConsecutive(
  62. // Sabre\CalDAV\Schedule\Plugin events
  63. ['method:POST', [$this->plugin, 'httpPost']],
  64. ['propFind', [$this->plugin, 'propFind']],
  65. ['propPatch', [$this->plugin, 'propPatch']],
  66. ['calendarObjectChange', [$this->plugin, 'calendarObjectChange']],
  67. ['beforeUnbind', [$this->plugin, 'beforeUnbind']],
  68. ['schedule', [$this->plugin, 'scheduleLocalDelivery']],
  69. ['getSupportedPrivilegeSet', [$this->plugin, 'getSupportedPrivilegeSet']],
  70. // OCA\DAV\CalDAV\Schedule\Plugin events
  71. ['propFind', [$this->plugin, 'propFindDefaultCalendarUrl'], 90],
  72. ['afterWriteContent', [$this->plugin, 'dispatchSchedulingResponses']],
  73. ['afterCreateFile', [$this->plugin, 'dispatchSchedulingResponses']]
  74. );
  75. $this->plugin->initialize($this->server);
  76. }
  77. public function testGetAddressesForPrincipal(): void {
  78. $href = $this->createMock(Href::class);
  79. $href
  80. ->expects($this->once())
  81. ->method('getHrefs')
  82. ->willReturn(['lukas@nextcloud.com', 'rullzer@nextcloud.com']);
  83. $this->server
  84. ->expects($this->once())
  85. ->method('getProperties')
  86. ->with(
  87. 'MyPrincipal',
  88. [
  89. '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
  90. ]
  91. )
  92. ->willReturn([
  93. '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => $href
  94. ]);
  95. $result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
  96. $this->assertSame(['lukas@nextcloud.com', 'rullzer@nextcloud.com'], $result);
  97. }
  98. public function testGetAddressesForPrincipalEmpty(): void {
  99. $this->server
  100. ->expects($this->once())
  101. ->method('getProperties')
  102. ->with(
  103. 'MyPrincipal',
  104. [
  105. '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
  106. ]
  107. )
  108. ->willReturn(null);
  109. $result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
  110. $this->assertSame([], $result);
  111. }
  112. public function testStripOffMailTo(): void {
  113. $this->assertEquals('test@example.com', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['test@example.com']));
  114. $this->assertEquals('test@example.com', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['mailto:test@example.com']));
  115. }
  116. public function testGetAttendeeRSVP(): void {
  117. $property1 = $this->createMock(CalAddress::class);
  118. $parameter1 = $this->createMock(Parameter::class);
  119. $property1->expects($this->once())
  120. ->method('offsetGet')
  121. ->with('RSVP')
  122. ->willReturn($parameter1);
  123. $parameter1->expects($this->once())
  124. ->method('getValue')
  125. ->with()
  126. ->willReturn('TRUE');
  127. $property2 = $this->createMock(CalAddress::class);
  128. $parameter2 = $this->createMock(Parameter::class);
  129. $property2->expects($this->once())
  130. ->method('offsetGet')
  131. ->with('RSVP')
  132. ->willReturn($parameter2);
  133. $parameter2->expects($this->once())
  134. ->method('getValue')
  135. ->with()
  136. ->willReturn('FALSE');
  137. $property3 = $this->createMock(CalAddress::class);
  138. $property3->expects($this->once())
  139. ->method('offsetGet')
  140. ->with('RSVP')
  141. ->willReturn(null);
  142. $this->assertTrue($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property1]));
  143. $this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property2]));
  144. $this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property3]));
  145. }
  146. public function propFindDefaultCalendarUrlProvider(): array {
  147. return [
  148. [
  149. 'principals/users/myuser',
  150. 'calendars/myuser',
  151. false,
  152. CalDavBackend::PERSONAL_CALENDAR_URI,
  153. CalDavBackend::PERSONAL_CALENDAR_NAME,
  154. true
  155. ],
  156. [
  157. 'principals/users/myuser',
  158. 'calendars/myuser',
  159. false,
  160. CalDavBackend::PERSONAL_CALENDAR_URI,
  161. CalDavBackend::PERSONAL_CALENDAR_NAME,
  162. true,
  163. true
  164. ],
  165. [
  166. 'principals/users/myuser',
  167. 'calendars/myuser',
  168. false,
  169. CalDavBackend::PERSONAL_CALENDAR_URI,
  170. CalDavBackend::PERSONAL_CALENDAR_NAME,
  171. false,
  172. false,
  173. true
  174. ],
  175. [
  176. 'principals/users/myuser',
  177. 'calendars/myuser',
  178. false,
  179. CalDavBackend::PERSONAL_CALENDAR_URI,
  180. CalDavBackend::PERSONAL_CALENDAR_NAME,
  181. false
  182. ],
  183. [
  184. 'principals/users/myuser',
  185. null,
  186. false,
  187. CalDavBackend::PERSONAL_CALENDAR_URI,
  188. CalDavBackend::PERSONAL_CALENDAR_NAME,
  189. true
  190. ],
  191. [
  192. 'principals/users/myuser',
  193. 'calendars/myuser',
  194. false,
  195. CalDavBackend::PERSONAL_CALENDAR_URI,
  196. CalDavBackend::PERSONAL_CALENDAR_NAME,
  197. true,
  198. false,
  199. false,
  200. false,
  201. ],
  202. [
  203. 'principals/users/myuser',
  204. 'calendars/myuser',
  205. false,
  206. 'my_other_calendar',
  207. 'My Other Calendar',
  208. true
  209. ],
  210. [
  211. 'principals/calendar-resources',
  212. 'system-calendars/calendar-resources/myuser',
  213. true,
  214. CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
  215. CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
  216. true
  217. ],
  218. [
  219. 'principals/calendar-resources',
  220. 'system-calendars/calendar-resources/myuser',
  221. true,
  222. CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
  223. CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
  224. false
  225. ],
  226. [
  227. 'principals/something-else',
  228. 'calendars/whatever',
  229. false,
  230. CalDavBackend::PERSONAL_CALENDAR_URI,
  231. CalDavBackend::PERSONAL_CALENDAR_NAME,
  232. true
  233. ],
  234. ];
  235. }
  236. /**
  237. * @dataProvider propFindDefaultCalendarUrlProvider
  238. */
  239. public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void {
  240. $propFind = new PropFind(
  241. $principalUri,
  242. [
  243. Plugin::SCHEDULE_DEFAULT_CALENDAR_URL
  244. ],
  245. 0
  246. );
  247. /** @var IPrincipal|MockObject $node */
  248. $node = $this->getMockBuilder(IPrincipal::class)
  249. ->disableOriginalConstructor()
  250. ->getMock();
  251. $node->expects($this->once())
  252. ->method('getPrincipalUrl')
  253. ->with()
  254. ->willReturn($principalUri);
  255. $calDAVPlugin = $this->getMockBuilder(CalDAVPlugin::class)
  256. ->disableOriginalConstructor()
  257. ->getMock();
  258. $calDAVPlugin->expects($this->once())
  259. ->method('getCalendarHomeForPrincipal')
  260. ->willReturn($calendarHome);
  261. $this->server->expects($this->once())
  262. ->method('getPlugin')
  263. ->with('caldav')
  264. ->willReturn($calDAVPlugin);
  265. if (!$calendarHome) {
  266. $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
  267. $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
  268. return;
  269. }
  270. if ($principalUri === 'principals/something-else') {
  271. $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
  272. $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
  273. return;
  274. }
  275. if (!$isResource) {
  276. $this->config->expects($this->once())
  277. ->method('getUserValue')
  278. ->with('myuser', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI)
  279. ->willReturn($calendarUri);
  280. }
  281. $calendarHomeObject = $this->createMock(CalendarHome::class);
  282. $calendarHomeObject->expects($this->once())
  283. ->method('childExists')
  284. ->with($calendarUri)
  285. ->willReturn($exists);
  286. if ($exists) {
  287. $calendar = $this->createMock(Calendar::class);
  288. $calendar->expects($this->once())->method('isDeleted')->willReturn($deleted);
  289. $calendarHomeObject->expects($deleted && !$hasExistingCalendars ? $this->exactly(2) : $this->once())->method('getChild')->with($calendarUri)->willReturn($calendar);
  290. }
  291. $calendarBackend = $this->createMock(CalDavBackend::class);
  292. $calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri;
  293. $displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName;
  294. $existingCalendars = $hasExistingCalendars ? [
  295. new Calendar(
  296. $calendarBackend,
  297. ['uri' => 'deleted', '{DAV:}displayname' => 'A deleted calendar', TrashbinPlugin::PROPERTY_DELETED_AT => 42],
  298. $this->createMock(IL10N::class),
  299. $this->config,
  300. $this->createMock(LoggerInterface::class)
  301. ),
  302. new Calendar(
  303. $calendarBackend,
  304. ['uri' => $calendarUri, '{DAV:}displayname' => $displayName],
  305. $this->createMock(IL10N::class),
  306. $this->config,
  307. $this->createMock(LoggerInterface::class)
  308. )
  309. ] : [];
  310. if (!$exists || $deleted) {
  311. if (!$hasExistingCalendars) {
  312. $calendarBackend->expects($this->once())
  313. ->method('createCalendar')
  314. ->with($principalUri, $calendarUri, [
  315. '{DAV:}displayname' => $displayName,
  316. ]);
  317. $calendarHomeObject->expects($this->once())
  318. ->method('getCalDAVBackend')
  319. ->with()
  320. ->willReturn($calendarBackend);
  321. }
  322. if (!$isResource) {
  323. $calendarHomeObject->expects($this->once())
  324. ->method('getChildren')
  325. ->with()
  326. ->willReturn($existingCalendars);
  327. }
  328. }
  329. /** @var Tree|MockObject $tree */
  330. $tree = $this->createMock(Tree::class);
  331. $tree->expects($this->once())
  332. ->method('getNodeForPath')
  333. ->with($calendarHome)
  334. ->willReturn($calendarHomeObject);
  335. $this->server->tree = $tree;
  336. $properties = $propertiesForPath ? [
  337. ['href' => '/remote.php/dav/' . $calendarHome . '/' . $calendarUri]
  338. ] : [];
  339. $this->server->expects($this->once())
  340. ->method('getPropertiesForPath')
  341. ->with($calendarHome . '/' . $calendarUri, [], 1)
  342. ->willReturn($properties);
  343. $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
  344. if (!$propertiesForPath) {
  345. $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
  346. return;
  347. }
  348. /** @var LocalHref $result */
  349. $result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
  350. $this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref());
  351. }
  352. /**
  353. * Test Calendar Event Creation for Personal Calendar
  354. *
  355. * Should generate 2 messages for attendees User 2 and User External
  356. */
  357. public function testCalendarObjectChangePersonalCalendarCreate(): void {
  358. // define place holders
  359. /** @var Message[] $iTipMessages */
  360. $iTipMessages = [];
  361. // construct calendar node
  362. $calendarNode = new Calendar(
  363. $this->createMock(BackendInterface::class),
  364. [
  365. 'uri' => 'personal',
  366. 'principaluri' => 'principals/users/user1',
  367. '{DAV:}displayname' => 'Calendar Shared By User1',
  368. ],
  369. $this->createMock(IL10N::class),
  370. $this->config,
  371. $this->logger
  372. );
  373. // construct server request object
  374. $request = new Request(
  375. 'PUT',
  376. '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
  377. );
  378. $request->setBaseUrl('/remote.php/dav/');
  379. // construct server response object
  380. $response = new Response();
  381. // construct server tree object
  382. $tree = $this->createMock(Tree::class);
  383. $tree->expects($this->once())
  384. ->method('getNodeForPath')
  385. ->with('calendars/user1/personal')
  386. ->willReturn($calendarNode);
  387. // construct server properties and returns
  388. $this->server->httpRequest = $request;
  389. $this->server->tree = $tree;
  390. $this->server->expects($this->exactly(1))->method('getProperties')
  391. ->willReturnMap([
  392. [
  393. 'principals/users/user1',
  394. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
  395. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
  396. ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
  397. )]
  398. ]
  399. ]);
  400. $this->server->expects($this->exactly(2))->method('emit')->willReturnCallback(
  401. function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
  402. $this->assertEquals('schedule', $eventName);
  403. $this->assertCount(1, $arguments);
  404. $iTipMessages[] = $arguments[0];
  405. return true;
  406. }
  407. );
  408. // construct calendar with a 1 hour event and same start/end time zones
  409. $vCalendar = new VCalendar();
  410. $vEvent = $vCalendar->add('VEVENT', []);
  411. $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
  412. $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
  413. $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
  414. $vEvent->add('SUMMARY', 'Test Recurring Event');
  415. $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']);
  416. $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [
  417. 'CN' => 'User Two',
  418. 'CUTYPE' => 'INDIVIDUAL',
  419. 'PARTSTAT' => 'NEEDS-ACTION',
  420. 'ROLE' => 'REQ-PARTICIPANT',
  421. 'RSVP' => 'TRUE'
  422. ]);
  423. $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
  424. 'CN' => 'User External',
  425. 'CUTYPE' => 'INDIVIDUAL',
  426. 'PARTSTAT' => 'NEEDS-ACTION',
  427. 'ROLE' => 'REQ-PARTICIPANT',
  428. 'RSVP' => 'TRUE'
  429. ]);
  430. // define flags
  431. $newFlag = true;
  432. $modifiedFlag = false;
  433. // execute method
  434. $this->plugin->calendarObjectChange(
  435. $request,
  436. $response,
  437. $vCalendar,
  438. 'calendars/user1/personal',
  439. $modifiedFlag,
  440. $newFlag
  441. );
  442. // test for correct iTip message count
  443. $this->assertCount(2, $iTipMessages);
  444. // test for Sharer Attendee
  445. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender);
  446. $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient);
  447. $this->assertTrue($iTipMessages[0]->significantChange);
  448. // test for External Attendee
  449. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender);
  450. $this->assertEquals('mailto:user@external.local', $iTipMessages[1]->recipient);
  451. $this->assertTrue($iTipMessages[1]->significantChange);
  452. }
  453. /**
  454. * Test Calendar Event Creation for Shared Calendar as Sharer/Owner
  455. *
  456. * Should generate 3 messages for attendees User 2 (Sharee), User 3 (Non-Sharee) and User External
  457. */
  458. public function testCalendarObjectChangeSharedCalendarSharerCreate(): void {
  459. // define place holders
  460. /** @var Message[] $iTipMessages */
  461. $iTipMessages = [];
  462. // construct calendar node
  463. $calendarNode = new Calendar(
  464. $this->createMock(BackendInterface::class),
  465. [
  466. 'uri' => 'calendar_shared_by_user1',
  467. 'principaluri' => 'principals/users/user1',
  468. '{DAV:}displayname' => 'Calendar Shared By User1',
  469. '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
  470. ],
  471. $this->createMock(IL10N::class),
  472. $this->config,
  473. $this->logger
  474. );
  475. // construct server request object
  476. $request = new Request(
  477. 'PUT',
  478. '/remote.php/dav/calendars/user1/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
  479. );
  480. $request->setBaseUrl('/remote.php/dav/');
  481. // construct server response object
  482. $response = new Response();
  483. // construct server tree object
  484. $tree = $this->createMock(Tree::class);
  485. $tree->expects($this->once())
  486. ->method('getNodeForPath')
  487. ->with('calendars/user1/calendar_shared_by_user1')
  488. ->willReturn($calendarNode);
  489. // construct server properties and returns
  490. $this->server->httpRequest = $request;
  491. $this->server->tree = $tree;
  492. $this->server->expects($this->exactly(1))->method('getProperties')
  493. ->willReturnMap([
  494. [
  495. 'principals/users/user1',
  496. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
  497. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
  498. ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
  499. )]
  500. ]
  501. ]);
  502. $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
  503. function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
  504. $this->assertEquals('schedule', $eventName);
  505. $this->assertCount(1, $arguments);
  506. $iTipMessages[] = $arguments[0];
  507. return true;
  508. }
  509. );
  510. // construct calendar with a 1 hour event and same start/end time zones
  511. $vCalendar = new VCalendar();
  512. $vEvent = $vCalendar->add('VEVENT', []);
  513. $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
  514. $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
  515. $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
  516. $vEvent->add('SUMMARY', 'Test Recurring Event');
  517. $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']);
  518. $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [
  519. 'CN' => 'User Two',
  520. 'CUTYPE' => 'INDIVIDUAL',
  521. 'PARTSTAT' => 'NEEDS-ACTION',
  522. 'ROLE' => 'REQ-PARTICIPANT',
  523. 'RSVP' => 'TRUE'
  524. ]);
  525. $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [
  526. 'CN' => 'User Three',
  527. 'CUTYPE' => 'INDIVIDUAL',
  528. 'PARTSTAT' => 'NEEDS-ACTION',
  529. 'ROLE' => 'REQ-PARTICIPANT',
  530. 'RSVP' => 'TRUE'
  531. ]);
  532. $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
  533. 'CN' => 'User External',
  534. 'CUTYPE' => 'INDIVIDUAL',
  535. 'PARTSTAT' => 'NEEDS-ACTION',
  536. 'ROLE' => 'REQ-PARTICIPANT',
  537. 'RSVP' => 'TRUE'
  538. ]);
  539. // define flags
  540. $newFlag = true;
  541. $modifiedFlag = false;
  542. // execute method
  543. $this->plugin->calendarObjectChange(
  544. $request,
  545. $response,
  546. $vCalendar,
  547. 'calendars/user1/calendar_shared_by_user1',
  548. $modifiedFlag,
  549. $newFlag
  550. );
  551. // test for correct iTip message count
  552. $this->assertCount(3, $iTipMessages);
  553. // test for Sharer Attendee
  554. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender);
  555. $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient);
  556. $this->assertTrue($iTipMessages[0]->significantChange);
  557. // test for Non Shee Attendee
  558. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender);
  559. $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient);
  560. $this->assertTrue($iTipMessages[1]->significantChange);
  561. // test for External Attendee
  562. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[2]->sender);
  563. $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient);
  564. $this->assertTrue($iTipMessages[2]->significantChange);
  565. }
  566. /**
  567. * Test Calendar Event Creation for Shared Calendar as Shree
  568. *
  569. * Should generate 3 messages for attendees User 1 (Sharer/Owner), User 3 (Non-Sharee) and User External
  570. */
  571. public function testCalendarObjectChangeSharedCalendarShreeCreate(): void {
  572. // define place holders
  573. /** @var Message[] $iTipMessages */
  574. $iTipMessages = [];
  575. // construct calendar node
  576. $calendarNode = new Calendar(
  577. $this->createMock(BackendInterface::class),
  578. [
  579. 'uri' => 'calendar_shared_by_user1',
  580. 'principaluri' => 'principals/users/user2',
  581. '{DAV:}displayname' => 'Calendar Shared By User1',
  582. '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
  583. ],
  584. $this->createMock(IL10N::class),
  585. $this->config,
  586. $this->logger
  587. );
  588. // construct server request object
  589. $request = new Request(
  590. 'PUT',
  591. '/remote.php/dav/calendars/user2/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
  592. );
  593. $request->setBaseUrl('/remote.php/dav/');
  594. // construct server response object
  595. $response = new Response();
  596. // construct server tree object
  597. $tree = $this->createMock(Tree::class);
  598. $tree->expects($this->once())
  599. ->method('getNodeForPath')
  600. ->with('calendars/user2/calendar_shared_by_user1')
  601. ->willReturn($calendarNode);
  602. // construct server properties and returns
  603. $this->server->httpRequest = $request;
  604. $this->server->tree = $tree;
  605. $this->server->expects($this->exactly(2))->method('getProperties')
  606. ->willReturnMap([
  607. [
  608. 'principals/users/user1',
  609. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
  610. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
  611. ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/']
  612. )]
  613. ],
  614. [
  615. 'principals/users/user2',
  616. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
  617. ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
  618. ['mailto:user2@testing.local','/remote.php/dav/principals/users/user2/']
  619. )]
  620. ]
  621. ]);
  622. $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
  623. function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
  624. $this->assertEquals('schedule', $eventName);
  625. $this->assertCount(1, $arguments);
  626. $iTipMessages[] = $arguments[0];
  627. return true;
  628. }
  629. );
  630. // construct calendar with a 1 hour event and same start/end time zones
  631. $vCalendar = new VCalendar();
  632. $vEvent = $vCalendar->add('VEVENT', []);
  633. $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
  634. $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
  635. $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
  636. $vEvent->add('SUMMARY', 'Test Recurring Event');
  637. $vEvent->add('ORGANIZER', 'mailto:user2@testing.local', ['CN' => 'User Two']);
  638. $vEvent->add('ATTENDEE', 'mailto:user1@testing.local', [
  639. 'CN' => 'User One',
  640. 'CUTYPE' => 'INDIVIDUAL',
  641. 'PARTSTAT' => 'NEEDS-ACTION',
  642. 'ROLE' => 'REQ-PARTICIPANT',
  643. 'RSVP' => 'TRUE'
  644. ]);
  645. $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [
  646. 'CN' => 'User Three',
  647. 'CUTYPE' => 'INDIVIDUAL',
  648. 'PARTSTAT' => 'NEEDS-ACTION',
  649. 'ROLE' => 'REQ-PARTICIPANT',
  650. 'RSVP' => 'TRUE'
  651. ]);
  652. $vEvent->add('ATTENDEE', 'mailto:user@external.local', [
  653. 'CN' => 'User External',
  654. 'CUTYPE' => 'INDIVIDUAL',
  655. 'PARTSTAT' => 'NEEDS-ACTION',
  656. 'ROLE' => 'REQ-PARTICIPANT',
  657. 'RSVP' => 'TRUE'
  658. ]);
  659. // define flags
  660. $newFlag = true;
  661. $modifiedFlag = false;
  662. // execute method
  663. $this->plugin->calendarObjectChange(
  664. $request,
  665. $response,
  666. $vCalendar,
  667. 'calendars/user2/calendar_shared_by_user1',
  668. $modifiedFlag,
  669. $newFlag
  670. );
  671. // test for correct iTip message count
  672. $this->assertCount(3, $iTipMessages);
  673. // test for Sharer Attendee
  674. $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->sender);
  675. $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->recipient);
  676. $this->assertTrue($iTipMessages[0]->significantChange);
  677. // test for Non Shee Attendee
  678. $this->assertEquals('mailto:user2@testing.local', $iTipMessages[1]->sender);
  679. $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient);
  680. $this->assertTrue($iTipMessages[1]->significantChange);
  681. // test for External Attendee
  682. $this->assertEquals('mailto:user2@testing.local', $iTipMessages[2]->sender);
  683. $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient);
  684. $this->assertTrue($iTipMessages[2]->significantChange);
  685. }
  686. }