CalDavBackendTest.php 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. * @copyright Copyright (c) 2017 Georg Ehrke
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author dartcafe <github@dartcafe.de>
  8. * @author Georg Ehrke <oc.list@georgehrke.com>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author leith abdulla <online-nextcloud@eleith.com>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Robin Appelman <robin@icewind.nl>
  13. * @author Roeland Jago Douma <roeland@famdouma.nl>
  14. * @author Thomas Citharel <nextcloud@tcit.fr>
  15. * @author Thomas Müller <thomas.mueller@tmit.eu>
  16. *
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OCA\DAV\Tests\unit\CalDAV;
  33. use DateInterval;
  34. use DateTime;
  35. use DateTimeImmutable;
  36. use DateTimeZone;
  37. use OCA\DAV\CalDAV\CalDavBackend;
  38. use OCA\DAV\CalDAV\Calendar;
  39. use OCA\DAV\DAV\Sharing\Plugin as SharingPlugin;
  40. use OCA\DAV\Events\CalendarDeletedEvent;
  41. use OCP\IConfig;
  42. use OCP\IL10N;
  43. use Sabre\DAV\Exception\NotFound;
  44. use Sabre\DAV\PropPatch;
  45. use Sabre\DAV\Xml\Property\Href;
  46. use Sabre\DAVACL\IACL;
  47. /**
  48. * Class CalDavBackendTest
  49. *
  50. * @group DB
  51. *
  52. * @package OCA\DAV\Tests\unit\CalDAV
  53. */
  54. class CalDavBackendTest extends AbstractCalDavBackend {
  55. public function testCalendarOperations(): void {
  56. $calendarId = $this->createTestCalendar();
  57. // update its display name
  58. $patch = new PropPatch([
  59. '{DAV:}displayname' => 'Unit test',
  60. '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing'
  61. ]);
  62. $this->dispatcher->expects(self::atLeastOnce())
  63. ->method('dispatchTyped');
  64. $this->backend->updateCalendar($calendarId, $patch);
  65. $patch->commit();
  66. $this->assertEquals(1, $this->backend->getCalendarsForUserCount(self::UNIT_TEST_USER));
  67. $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
  68. $this->assertCount(1, $calendars);
  69. $this->assertEquals('Unit test', $calendars[0]['{DAV:}displayname']);
  70. $this->assertEquals('Calendar used for unit testing', $calendars[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']);
  71. $this->assertEquals('User\'s displayname', $calendars[0]['{http://nextcloud.com/ns}owner-displayname']);
  72. // delete the address book
  73. $this->dispatcher->expects(self::atLeastOnce())
  74. ->method('dispatchTyped');
  75. $this->backend->deleteCalendar($calendars[0]['id'], true);
  76. $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
  77. self::assertEmpty($calendars);
  78. }
  79. public function providesSharingData() {
  80. return [
  81. [true, true, true, false, [
  82. [
  83. 'href' => 'principal:' . self::UNIT_TEST_USER1,
  84. 'readOnly' => false
  85. ],
  86. [
  87. 'href' => 'principal:' . self::UNIT_TEST_GROUP,
  88. 'readOnly' => true
  89. ]
  90. ]],
  91. [true, true, true, false, [
  92. [
  93. 'href' => 'principal:' . self::UNIT_TEST_GROUP,
  94. 'readOnly' => true,
  95. ],
  96. [
  97. 'href' => 'principal:' . self::UNIT_TEST_GROUP2,
  98. 'readOnly' => false,
  99. ],
  100. ]],
  101. [true, true, true, true, [
  102. [
  103. 'href' => 'principal:' . self::UNIT_TEST_GROUP,
  104. 'readOnly' => false,
  105. ],
  106. [
  107. 'href' => 'principal:' . self::UNIT_TEST_GROUP2,
  108. 'readOnly' => true,
  109. ],
  110. ]],
  111. [true, false, false, false, [
  112. [
  113. 'href' => 'principal:' . self::UNIT_TEST_USER1,
  114. 'readOnly' => true
  115. ],
  116. ]],
  117. ];
  118. }
  119. /**
  120. * @dataProvider providesSharingData
  121. */
  122. public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add): void {
  123. /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
  124. $l10n = $this->createMock(IL10N::class);
  125. $l10n
  126. ->expects($this->any())
  127. ->method('t')
  128. ->willReturnCallback(function ($text, $parameters = []) {
  129. return vsprintf($text, $parameters);
  130. });
  131. $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
  132. $config = $this->createMock(IConfig::class);
  133. $this->userManager->expects($this->any())
  134. ->method('userExists')
  135. ->willReturn(true);
  136. $this->groupManager->expects($this->any())
  137. ->method('groupExists')
  138. ->willReturn(true);
  139. $calendarId = $this->createTestCalendar();
  140. $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
  141. $this->assertCount(1, $calendars);
  142. $calendar = new Calendar($this->backend, $calendars[0], $l10n, $config, $logger);
  143. $this->backend->updateShares($calendar, $add, []);
  144. $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1);
  145. $this->assertCount(1, $calendars);
  146. $calendar = new Calendar($this->backend, $calendars[0], $l10n, $config, $logger);
  147. $acl = $calendar->getACL();
  148. $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
  149. $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
  150. $this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
  151. $this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
  152. $this->assertEquals(self::UNIT_TEST_USER, $calendar->getOwner());
  153. // test acls on the child
  154. $uri = static::getUniqueID('calobj');
  155. $calData = <<<'EOD'
  156. BEGIN:VCALENDAR
  157. VERSION:2.0
  158. PRODID:ownCloud Calendar
  159. BEGIN:VEVENT
  160. CREATED;VALUE=DATE-TIME:20130910T125139Z
  161. UID:47d15e3ec8
  162. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  163. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  164. SUMMARY:Test Event
  165. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  166. DTEND;VALUE=DATE-TIME:20130912T140000Z
  167. CLASS:PUBLIC
  168. END:VEVENT
  169. END:VCALENDAR
  170. EOD;
  171. $this->dispatcher->expects(self::atLeastOnce())
  172. ->method('dispatchTyped');
  173. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  174. /** @var IACL $child */
  175. $child = $calendar->getChild($uri);
  176. $acl = $child->getACL();
  177. $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
  178. $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
  179. $this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
  180. $this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
  181. // delete the calendar
  182. $this->dispatcher->expects(self::once())
  183. ->method('dispatchTyped')
  184. ->with(self::callback(function ($event) {
  185. return $event instanceof CalendarDeletedEvent;
  186. }));
  187. $this->backend->deleteCalendar($calendars[0]['id'], true);
  188. $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
  189. self::assertEmpty($calendars);
  190. }
  191. public function testCalendarObjectsOperations(): void {
  192. $calendarId = $this->createTestCalendar();
  193. // create a card
  194. $uri = static::getUniqueID('calobj');
  195. $calData = <<<'EOD'
  196. BEGIN:VCALENDAR
  197. VERSION:2.0
  198. PRODID:ownCloud Calendar
  199. BEGIN:VEVENT
  200. CREATED;VALUE=DATE-TIME:20130910T125139Z
  201. UID:47d15e3ec8
  202. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  203. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  204. SUMMARY:Test Event
  205. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  206. DTEND;VALUE=DATE-TIME:20130912T140000Z
  207. CLASS:PUBLIC
  208. END:VEVENT
  209. END:VCALENDAR
  210. EOD;
  211. $this->dispatcher->expects(self::atLeastOnce())
  212. ->method('dispatchTyped');
  213. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  214. // get all the calendar objects
  215. $calendarObjects = $this->backend->getCalendarObjects($calendarId);
  216. $this->assertCount(1, $calendarObjects);
  217. $this->assertEquals($calendarId, $calendarObjects[0]['calendarid']);
  218. $this->assertArrayHasKey('classification', $calendarObjects[0]);
  219. // get the calendar objects
  220. $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
  221. $this->assertNotNull($calendarObject);
  222. $this->assertArrayHasKey('id', $calendarObject);
  223. $this->assertArrayHasKey('uri', $calendarObject);
  224. $this->assertArrayHasKey('lastmodified', $calendarObject);
  225. $this->assertArrayHasKey('etag', $calendarObject);
  226. $this->assertArrayHasKey('size', $calendarObject);
  227. $this->assertArrayHasKey('classification', $calendarObject);
  228. $this->assertArrayHasKey('{' . SharingPlugin::NS_NEXTCLOUD . '}deleted-at', $calendarObject);
  229. $this->assertEquals($calData, $calendarObject['calendardata']);
  230. // update the card
  231. $calData = <<<'EOD'
  232. BEGIN:VCALENDAR
  233. VERSION:2.0
  234. PRODID:ownCloud Calendar
  235. BEGIN:VEVENT
  236. CREATED;VALUE=DATE-TIME:20130910T125139Z
  237. UID:47d15e3ec8
  238. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  239. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  240. SUMMARY:Test Event
  241. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  242. DTEND;VALUE=DATE-TIME:20130912T140000Z
  243. END:VEVENT
  244. END:VCALENDAR
  245. EOD;
  246. $this->dispatcher->expects(self::atLeastOnce())
  247. ->method('dispatchTyped');
  248. $this->backend->updateCalendarObject($calendarId, $uri, $calData);
  249. $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
  250. $this->assertEquals($calData, $calendarObject['calendardata']);
  251. // delete the card
  252. $this->dispatcher->expects(self::atLeastOnce())
  253. ->method('dispatchTyped');
  254. $this->backend->deleteCalendarObject($calendarId, $uri);
  255. $calendarObjects = $this->backend->getCalendarObjects($calendarId);
  256. $this->assertCount(0, $calendarObjects);
  257. }
  258. public function testMultipleCalendarObjectsWithSameUID(): void {
  259. $this->expectException(\Sabre\DAV\Exception\BadRequest::class);
  260. $this->expectExceptionMessage('Calendar object with uid already exists in this calendar collection.');
  261. $calendarId = $this->createTestCalendar();
  262. $calData = <<<'EOD'
  263. BEGIN:VCALENDAR
  264. VERSION:2.0
  265. PRODID:ownCloud Calendar
  266. BEGIN:VEVENT
  267. CREATED;VALUE=DATE-TIME:20130910T125139Z
  268. UID:47d15e3ec8-1
  269. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  270. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  271. SUMMARY:Test Event
  272. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  273. DTEND;VALUE=DATE-TIME:20130912T140000Z
  274. CLASS:PUBLIC
  275. END:VEVENT
  276. END:VCALENDAR
  277. EOD;
  278. $uri0 = static::getUniqueID('event');
  279. $uri1 = static::getUniqueID('event');
  280. $this->backend->createCalendarObject($calendarId, $uri0, $calData);
  281. $this->backend->createCalendarObject($calendarId, $uri1, $calData);
  282. }
  283. public function testMultiCalendarObjects(): void {
  284. $calendarId = $this->createTestCalendar();
  285. // create an event
  286. $calData = [];
  287. $calData[] = <<<'EOD'
  288. BEGIN:VCALENDAR
  289. VERSION:2.0
  290. PRODID:ownCloud Calendar
  291. BEGIN:VEVENT
  292. CREATED;VALUE=DATE-TIME:20130910T125139Z
  293. UID:47d15e3ec8-1
  294. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  295. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  296. SUMMARY:Test Event
  297. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  298. DTEND;VALUE=DATE-TIME:20130912T140000Z
  299. CLASS:PUBLIC
  300. END:VEVENT
  301. END:VCALENDAR
  302. EOD;
  303. $calData[] = <<<'EOD'
  304. BEGIN:VCALENDAR
  305. VERSION:2.0
  306. PRODID:ownCloud Calendar
  307. BEGIN:VEVENT
  308. CREATED;VALUE=DATE-TIME:20130910T125139Z
  309. UID:47d15e3ec8-2
  310. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  311. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  312. SUMMARY:Test Event
  313. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  314. DTEND;VALUE=DATE-TIME:20130912T140000Z
  315. CLASS:PUBLIC
  316. END:VEVENT
  317. END:VCALENDAR
  318. EOD;
  319. $calData[] = <<<'EOD'
  320. BEGIN:VCALENDAR
  321. VERSION:2.0
  322. PRODID:ownCloud Calendar
  323. BEGIN:VEVENT
  324. CREATED;VALUE=DATE-TIME:20130910T125139Z
  325. UID:47d15e3ec8-3
  326. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  327. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  328. SUMMARY:Test Event
  329. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  330. DTEND;VALUE=DATE-TIME:20130912T140000Z
  331. CLASS:PUBLIC
  332. END:VEVENT
  333. END:VCALENDAR
  334. EOD;
  335. $uri0 = static::getUniqueID('card');
  336. $this->dispatcher->expects(self::atLeastOnce())
  337. ->method('dispatchTyped');
  338. $this->backend->createCalendarObject($calendarId, $uri0, $calData[0]);
  339. $uri1 = static::getUniqueID('card');
  340. $this->dispatcher->expects(self::atLeastOnce())
  341. ->method('dispatchTyped');
  342. $this->backend->createCalendarObject($calendarId, $uri1, $calData[1]);
  343. $uri2 = static::getUniqueID('card');
  344. $this->dispatcher->expects(self::atLeastOnce())
  345. ->method('dispatchTyped');
  346. $this->backend->createCalendarObject($calendarId, $uri2, $calData[2]);
  347. // get all the cards
  348. $calendarObjects = $this->backend->getCalendarObjects($calendarId);
  349. $this->assertCount(3, $calendarObjects);
  350. // get the cards
  351. $calendarObjects = $this->backend->getMultipleCalendarObjects($calendarId, [$uri1, $uri2]);
  352. $this->assertCount(2, $calendarObjects);
  353. foreach ($calendarObjects as $card) {
  354. $this->assertArrayHasKey('id', $card);
  355. $this->assertArrayHasKey('uri', $card);
  356. $this->assertArrayHasKey('lastmodified', $card);
  357. $this->assertArrayHasKey('etag', $card);
  358. $this->assertArrayHasKey('size', $card);
  359. $this->assertArrayHasKey('classification', $card);
  360. }
  361. usort($calendarObjects, function ($a, $b) {
  362. return $a['id'] - $b['id'];
  363. });
  364. $this->assertEquals($calData[1], $calendarObjects[0]['calendardata']);
  365. $this->assertEquals($calData[2], $calendarObjects[1]['calendardata']);
  366. // delete the card
  367. $this->dispatcher->expects(self::atLeastOnce())
  368. ->method('dispatchTyped');
  369. $this->backend->deleteCalendarObject($calendarId, $uri0);
  370. $this->dispatcher->expects(self::atLeastOnce())
  371. ->method('dispatchTyped');
  372. $this->backend->deleteCalendarObject($calendarId, $uri1);
  373. $this->dispatcher->expects(self::atLeastOnce())
  374. ->method('dispatchTyped');
  375. $this->backend->deleteCalendarObject($calendarId, $uri2);
  376. $calendarObjects = $this->backend->getCalendarObjects($calendarId);
  377. $this->assertCount(0, $calendarObjects);
  378. }
  379. /**
  380. * @dataProvider providesCalendarQueryParameters
  381. */
  382. public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter): void {
  383. $calendarId = $this->createTestCalendar();
  384. $events = [];
  385. $events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
  386. $events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z');
  387. $events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z');
  388. if (PHP_INT_SIZE > 8) {
  389. $events[3] = $this->createEvent($calendarId, '21130912T130000Z', '22130912T130000Z');
  390. } else {
  391. /* On 32bit we do not support events after 2038 */
  392. $events[3] = $this->createEvent($calendarId, '20370912T130000Z', '20370912T130000Z');
  393. }
  394. $result = $this->backend->calendarQuery($calendarId, [
  395. 'name' => '',
  396. 'prop-filters' => $propFilters,
  397. 'comp-filters' => $compFilter
  398. ]);
  399. $expectedEventsInResult = array_map(function ($index) use ($events) {
  400. return $events[$index];
  401. }, $expectedEventsInResult);
  402. $this->assertEqualsCanonicalizing($expectedEventsInResult, $result);
  403. }
  404. public function testGetCalendarObjectByUID(): void {
  405. $calendarId = $this->createTestCalendar();
  406. $uri = static::getUniqueID('calobj');
  407. $calData = <<<'EOD'
  408. BEGIN:VCALENDAR
  409. VERSION:2.0
  410. PRODID:ownCloud Calendar
  411. BEGIN:VEVENT
  412. CREATED;VALUE=DATE-TIME:20130910T125139Z
  413. UID:47d15e3ec8
  414. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  415. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  416. SUMMARY:Test Event
  417. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  418. DTEND;VALUE=DATE-TIME:20130912T140000Z
  419. CLASS:PUBLIC
  420. END:VEVENT
  421. END:VCALENDAR
  422. EOD;
  423. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  424. $co = $this->backend->getCalendarObjectByUID(self::UNIT_TEST_USER, '47d15e3ec8');
  425. $this->assertNotNull($co);
  426. }
  427. public function providesCalendarQueryParameters() {
  428. return [
  429. 'all' => [[0, 1, 2, 3], [], []],
  430. 'only-todos' => [[], ['name' => 'VTODO'], []],
  431. 'only-events' => [[0, 1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],],
  432. 'start' => [[1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],],
  433. 'end' => [[0], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC'))], 'prop-filters' => []]],],
  434. 'future' => [[3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2036-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],],
  435. ];
  436. }
  437. public function testSyncSupport(): void {
  438. $calendarId = $this->createTestCalendar();
  439. // fist call without synctoken
  440. $changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
  441. $syncToken = $changes['syncToken'];
  442. // add a change
  443. $event = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
  444. // look for changes
  445. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
  446. $this->assertEquals($event, $changes['added'][0]);
  447. }
  448. public function testPublications(): void {
  449. $this->dispatcher->expects(self::atLeastOnce())
  450. ->method('dispatchTyped');
  451. $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
  452. $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
  453. /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
  454. $l10n = $this->createMock(IL10N::class);
  455. $config = $this->createMock(IConfig::class);
  456. $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
  457. $calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger);
  458. $calendar->setPublishStatus(true);
  459. $this->assertNotEquals(false, $calendar->getPublishStatus());
  460. $publicCalendars = $this->backend->getPublicCalendars();
  461. $this->assertCount(1, $publicCalendars);
  462. $this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']);
  463. $this->assertEquals('User\'s displayname', $publicCalendars[0]['{http://nextcloud.com/ns}owner-displayname']);
  464. $publicCalendarURI = $publicCalendars[0]['uri'];
  465. $publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI);
  466. $this->assertEquals(true, $publicCalendar['{http://owncloud.org/ns}public']);
  467. $calendar->setPublishStatus(false);
  468. $this->assertEquals(false, $calendar->getPublishStatus());
  469. $this->expectException(NotFound::class);
  470. $this->backend->getPublicCalendar($publicCalendarURI);
  471. }
  472. public function testSubscriptions(): void {
  473. $id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [
  474. '{http://calendarserver.org/ns/}source' => new Href('test-source'),
  475. '{http://apple.com/ns/ical/}calendar-color' => '#1C4587',
  476. '{http://calendarserver.org/ns/}subscribed-strip-todos' => ''
  477. ]);
  478. $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
  479. $this->assertCount(1, $subscriptions);
  480. $this->assertEquals('#1C4587', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
  481. $this->assertEquals(true, $subscriptions[0]['{http://calendarserver.org/ns/}subscribed-strip-todos']);
  482. $this->assertEquals($id, $subscriptions[0]['id']);
  483. $patch = new PropPatch([
  484. '{DAV:}displayname' => 'Unit test',
  485. '{http://apple.com/ns/ical/}calendar-color' => '#ac0606',
  486. ]);
  487. $this->backend->updateSubscription($id, $patch);
  488. $patch->commit();
  489. $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
  490. $this->assertCount(1, $subscriptions);
  491. $this->assertEquals($id, $subscriptions[0]['id']);
  492. $this->assertEquals('Unit test', $subscriptions[0]['{DAV:}displayname']);
  493. $this->assertEquals('#ac0606', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
  494. $this->backend->deleteSubscription($id);
  495. $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
  496. $this->assertCount(0, $subscriptions);
  497. }
  498. public function providesSchedulingData() {
  499. $data = <<<EOS
  500. BEGIN:VCALENDAR
  501. VERSION:2.0
  502. PRODID:-//Sabre//Sabre VObject 3.5.0//EN
  503. CALSCALE:GREGORIAN
  504. METHOD:REQUEST
  505. BEGIN:VTIMEZONE
  506. TZID:Europe/Warsaw
  507. BEGIN:DAYLIGHT
  508. TZOFFSETFROM:+0100
  509. TZOFFSETTO:+0200
  510. TZNAME:CEST
  511. DTSTART:19700329T020000
  512. RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
  513. END:DAYLIGHT
  514. BEGIN:STANDARD
  515. TZOFFSETFROM:+0200
  516. TZOFFSETTO:+0100
  517. TZNAME:CET
  518. DTSTART:19701025T030000
  519. RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
  520. END:STANDARD
  521. END:VTIMEZONE
  522. BEGIN:VEVENT
  523. CREATED:20170320T131655Z
  524. LAST-MODIFIED:20170320T135019Z
  525. DTSTAMP:20170320T135019Z
  526. UID:7e908a6d-4c4e-48d7-bd62-59ab80fbf1a3
  527. SUMMARY:TEST Z pg_escape_bytea
  528. ORGANIZER;RSVP=TRUE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:k.klimczak@gromar.e
  529. u
  530. ATTENDEE;RSVP=TRUE;CN=Zuzanna Leszek;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICI
  531. PANT:mailto:z.leszek@gromar.eu
  532. ATTENDEE;RSVP=TRUE;CN=Marcin Pisarski;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTIC
  533. IPANT:mailto:m.pisarski@gromar.eu
  534. ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:klimcz
  535. ak.k@gmail.com
  536. ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:k_klim
  537. czak@tlen.pl
  538. DTSTART;TZID=Europe/Warsaw:20170325T150000
  539. DTEND;TZID=Europe/Warsaw:20170325T160000
  540. TRANSP:OPAQUE
  541. DESCRIPTION:Magiczna treść uzyskana za pomocą magicznego proszku.\n\nę
  542. żźćńłóÓŻŹĆŁĘ€śśśŚŚ\n \,\,))))))))\;\,\n
  543. __))))))))))))))\,\n \\|/ -\\(((((''''((((((((.\n -*-==///
  544. ///(('' . `))))))\,\n /|\\ ))| o \;-. '(((((
  545. \,(\,\n ( `| / ) \;))))'
  546. \,_))^\;(~\n | | | \,))((((_ _____-
  547. -----~~~-. %\,\;(\;(>'\;'~\n o_)\; \; )))(((` ~---
  548. ~ `:: \\ %%~~)(v\;(`('~\n \; ''''````
  549. `: `:::|\\\,__\,%% )\;`'\; ~\n | _
  550. ) / `:|`----' `-'\n ______/\\/~ |
  551. / /\n /~\;\;.____/\;\;' / ___--\
  552. ,-( `\;\;\;/\n / // _\;______\;'------~~~~~ /\;\;/\\ /\n
  553. // | | / \; \\\;\;\,\\\n (<_ | \;
  554. /'\,/-----' _>\n \\_| ||_
  555. //~\;~~~~~~~~~\n `\\_| (\,~~ -Tua Xiong\n
  556. \\~\\\n
  557. ~~\n\n
  558. SEQUENCE:1
  559. X-MOZ-GENERATION:1
  560. END:VEVENT
  561. END:VCALENDAR
  562. EOS;
  563. return [
  564. 'no data' => [''],
  565. 'failing on postgres' => [$data]
  566. ];
  567. }
  568. /**
  569. * @dataProvider providesSchedulingData
  570. * @param $objectData
  571. */
  572. public function testScheduling($objectData): void {
  573. $this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', $objectData);
  574. $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
  575. $this->assertCount(1, $sos);
  576. $so = $this->backend->getSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
  577. $this->assertNotNull($so);
  578. $this->backend->deleteSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
  579. $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
  580. $this->assertCount(0, $sos);
  581. }
  582. /**
  583. * @dataProvider providesCalDataForGetDenormalizedData
  584. */
  585. public function testGetDenormalizedData($expected, $key, $calData): void {
  586. try {
  587. $actual = $this->backend->getDenormalizedData($calData);
  588. $this->assertEquals($expected, $actual[$key]);
  589. } catch (\Throwable $e) {
  590. if (($e->getMessage() === 'Epoch doesn\'t fit in a PHP integer') && (PHP_INT_SIZE < 8)) {
  591. $this->markTestSkipped('This fail on 32bits because of PHP limitations in DateTime');
  592. }
  593. throw $e;
  594. }
  595. }
  596. public function providesCalDataForGetDenormalizedData(): array {
  597. return [
  598. 'first occurrence before unix epoch starts' => [0, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  599. 'no first occurrence because yearly' => [null, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  600. 'last occurrence is max when only last VEVENT in group is weekly' => [(new DateTime(CalDavBackend::MAX_DATE))->getTimestamp(), 'lastOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20200812T103000\r\nDTEND;TZID=America/Los_Angeles:20200812T110000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdfasdfasdf@google.com\r\nRECURRENCE-ID;TZID=America/Los_Angeles:20200811T123000\r\nCREATED:20200626T181848Z\r\nLAST-MODIFIED:20200922T192707Z\r\nSUMMARY:Weekly 1:1\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20200728T123000\r\nDTEND;TZID=America/Los_Angeles:20200728T130000\r\nEXDATE;TZID=America/Los_Angeles:20200818T123000\r\nRRULE:FREQ=WEEKLY;BYDAY=TU\r\nDTSTAMP:20200927T180638Z\r\nUID:asdfasdfasdf@google.com\r\nCREATED:20200626T181848Z\r\nDESCRIPTION:Setting up recurring time on our calendars\r\nLAST-MODIFIED:20200922T192707Z\r\nSUMMARY:Weekly 1:1\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  601. 'last occurrence before unix epoch starts' => [0, 'lastOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:19110324\r\nDTEND;VALUE=DATE:19110325\r\nDTSTAMP:20200927T180638Z\r\nUID:asdfasdfasdf@google.com\r\nCREATED:20200626T181848Z\r\nDESCRIPTION:Very old event\r\nLAST-MODIFIED:20200922T192707Z\r\nSUMMARY:Some old event\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  602. 'first occurrence is found when not first VEVENT in group' => [(new DateTime('2020-09-01T110000', new DateTimeZone("America/Los_Angeles")))->getTimestamp(), 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20201013T110000\r\nDTEND;TZID=America/Los_Angeles:20201013T120000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nRECURRENCE-ID;TZID=America/Los_Angeles:20201013T110000\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200925T042014Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20200901T110000\r\nDTEND;TZID=America/Los_Angeles:20200901T120000\r\nRRULE:FREQ=WEEKLY;BYDAY=TU\r\nEXDATE;TZID=America/Los_Angeles:20200922T110000\r\nEXDATE;TZID=America/Los_Angeles:20200915T110000\r\nEXDATE;TZID=America/Los_Angeles:20200908T110000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200915T162810Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  603. 'CLASS:PRIVATE' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PRIVATE\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"],
  604. 'CLASS:PUBLIC' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PUBLIC\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"],
  605. 'CLASS:CONFIDENTIAL' => [CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:CONFIDENTIAL\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"],
  606. 'no class set -> public' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nTRANSP:OPAQUE\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"],
  607. 'unknown class -> private' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:VERTRAULICH\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"],
  608. ];
  609. }
  610. public function testCalendarSearch(): void {
  611. $calendarId = $this->createTestCalendar();
  612. $uri = static::getUniqueID('calobj');
  613. $calData = <<<EOD
  614. BEGIN:VCALENDAR
  615. VERSION:2.0
  616. PRODID:ownCloud Calendar
  617. BEGIN:VEVENT
  618. CREATED;VALUE=DATE-TIME:20130910T125139Z
  619. UID:47d15e3ec8
  620. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  621. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  622. SUMMARY:Test Event
  623. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  624. DTEND;VALUE=DATE-TIME:20130912T140000Z
  625. CLASS:PUBLIC
  626. END:VEVENT
  627. END:VCALENDAR
  628. EOD;
  629. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  630. $search1 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
  631. 'comps' => [
  632. 'VEVENT',
  633. 'VTODO'
  634. ],
  635. 'props' => [
  636. 'SUMMARY',
  637. 'LOCATION'
  638. ],
  639. 'search-term' => 'Test',
  640. ]);
  641. $this->assertEquals(count($search1), 1);
  642. // update the card
  643. $calData = <<<'EOD'
  644. BEGIN:VCALENDAR
  645. VERSION:2.0
  646. PRODID:ownCloud Calendar
  647. BEGIN:VEVENT
  648. CREATED;VALUE=DATE-TIME:20130910T125139Z
  649. UID:47d15e3ec8
  650. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  651. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  652. SUMMARY:123 Event 🙈
  653. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  654. DTEND;VALUE=DATE-TIME:20130912T140000Z
  655. ATTENDEE;CN=test:mailto:foo@bar.com
  656. END:VEVENT
  657. END:VCALENDAR
  658. EOD;
  659. $this->backend->updateCalendarObject($calendarId, $uri, $calData);
  660. $search2 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
  661. 'comps' => [
  662. 'VEVENT',
  663. 'VTODO'
  664. ],
  665. 'props' => [
  666. 'SUMMARY',
  667. 'LOCATION'
  668. ],
  669. 'search-term' => 'Test',
  670. ]);
  671. $this->assertEquals(count($search2), 0);
  672. $search3 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
  673. 'comps' => [
  674. 'VEVENT',
  675. 'VTODO'
  676. ],
  677. 'props' => [
  678. 'SUMMARY',
  679. 'LOCATION'
  680. ],
  681. 'params' => [
  682. [
  683. 'property' => 'ATTENDEE',
  684. 'parameter' => 'CN'
  685. ]
  686. ],
  687. 'search-term' => 'Test',
  688. ]);
  689. $this->assertEquals(count($search3), 1);
  690. // t matches both summary and attendee's CN, but we want unique results
  691. $search4 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
  692. 'comps' => [
  693. 'VEVENT',
  694. 'VTODO'
  695. ],
  696. 'props' => [
  697. 'SUMMARY',
  698. 'LOCATION'
  699. ],
  700. 'params' => [
  701. [
  702. 'property' => 'ATTENDEE',
  703. 'parameter' => 'CN'
  704. ]
  705. ],
  706. 'search-term' => 't',
  707. ]);
  708. $this->assertEquals(count($search4), 1);
  709. $this->backend->deleteCalendarObject($calendarId, $uri);
  710. $search5 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
  711. 'comps' => [
  712. 'VEVENT',
  713. 'VTODO'
  714. ],
  715. 'props' => [
  716. 'SUMMARY',
  717. 'LOCATION'
  718. ],
  719. 'params' => [
  720. [
  721. 'property' => 'ATTENDEE',
  722. 'parameter' => 'CN'
  723. ]
  724. ],
  725. 'search-term' => 't',
  726. ]);
  727. $this->assertEquals(count($search5), 0);
  728. }
  729. /**
  730. * @dataProvider searchDataProvider
  731. */
  732. public function testSearch(bool $isShared, array $searchOptions, int $count): void {
  733. $calendarId = $this->createTestCalendar();
  734. $uris = [];
  735. $calData = [];
  736. $uris[] = static::getUniqueID('calobj');
  737. $calData[] = <<<EOD
  738. BEGIN:VCALENDAR
  739. VERSION:2.0
  740. PRODID:Nextcloud Calendar
  741. BEGIN:VEVENT
  742. CREATED;VALUE=DATE-TIME:20130910T125139Z
  743. UID:47d15e3ec8-1
  744. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  745. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  746. SUMMARY:Test Event
  747. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  748. DTEND;VALUE=DATE-TIME:20130912T140000Z
  749. CLASS:PUBLIC
  750. END:VEVENT
  751. END:VCALENDAR
  752. EOD;
  753. $uris[] = static::getUniqueID('calobj');
  754. $calData[] = <<<EOD
  755. BEGIN:VCALENDAR
  756. VERSION:2.0
  757. PRODID:Nextcloud Calendar
  758. BEGIN:VEVENT
  759. CREATED;VALUE=DATE-TIME:20130910T125139Z
  760. UID:47d15e3ec8-2
  761. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  762. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  763. SUMMARY:123
  764. LOCATION:Test
  765. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  766. DTEND;VALUE=DATE-TIME:20130912T140000Z
  767. CLASS:PUBLIC
  768. END:VEVENT
  769. END:VCALENDAR
  770. EOD;
  771. $uris[] = static::getUniqueID('calobj');
  772. $calData[] = <<<EOD
  773. BEGIN:VCALENDAR
  774. VERSION:2.0
  775. PRODID:Nextcloud Calendar
  776. BEGIN:VEVENT
  777. CREATED;VALUE=DATE-TIME:20130910T125139Z
  778. UID:47d15e3ec8-3
  779. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  780. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  781. SUMMARY:123
  782. ATTENDEE;CN=test:mailto:foo@bar.com
  783. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  784. DTEND;VALUE=DATE-TIME:20130912T140000Z
  785. CLASS:PRIVATE
  786. END:VEVENT
  787. END:VCALENDAR
  788. EOD;
  789. $uris[] = static::getUniqueID('calobj');
  790. $calData[] = <<<EOD
  791. BEGIN:VCALENDAR
  792. VERSION:2.0
  793. PRODID:Nextcloud Calendar
  794. BEGIN:VEVENT
  795. CREATED;VALUE=DATE-TIME:20130910T125139Z
  796. UID:47d15e3ec8-4
  797. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  798. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  799. SUMMARY:123
  800. ATTENDEE;CN=foobar:mailto:test@bar.com
  801. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  802. DTEND;VALUE=DATE-TIME:20130912T140000Z
  803. CLASS:CONFIDENTIAL
  804. END:VEVENT
  805. END:VCALENDAR
  806. EOD;
  807. $uriCount = count($uris);
  808. for ($i = 0; $i < $uriCount; $i++) {
  809. $this->backend->createCalendarObject($calendarId,
  810. $uris[$i], $calData[$i]);
  811. }
  812. $calendarInfo = [
  813. 'id' => $calendarId,
  814. 'principaluri' => 'user1',
  815. '{http://owncloud.org/ns}owner-principal' => $isShared ? 'user2' : 'user1',
  816. ];
  817. $result = $this->backend->search($calendarInfo, 'Test',
  818. ['SUMMARY', 'LOCATION', 'ATTENDEE'], $searchOptions, null, null);
  819. $this->assertCount($count, $result);
  820. }
  821. public function searchDataProvider() {
  822. return [
  823. [false, [], 4],
  824. [true, ['timerange' => ['start' => new DateTime('2013-09-12 13:00:00'), 'end' => new DateTime('2013-09-12 14:00:00')]], 2],
  825. [true, ['timerange' => ['start' => new DateTime('2013-09-12 15:00:00'), 'end' => new DateTime('2013-09-12 16:00:00')]], 0],
  826. ];
  827. }
  828. public function testSameUriSameIdForDifferentCalendarTypes(): void {
  829. $calendarId = $this->createTestCalendar();
  830. $subscriptionId = $this->createTestSubscription();
  831. $uri = static::getUniqueID('calobj');
  832. $calData = <<<EOD
  833. BEGIN:VCALENDAR
  834. VERSION:2.0
  835. PRODID:ownCloud Calendar
  836. BEGIN:VEVENT
  837. CREATED;VALUE=DATE-TIME:20130910T125139Z
  838. UID:47d15e3ec8
  839. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  840. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  841. SUMMARY:Test Event
  842. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  843. DTEND;VALUE=DATE-TIME:20130912T140000Z
  844. CLASS:PUBLIC
  845. END:VEVENT
  846. END:VCALENDAR
  847. EOD;
  848. $calData2 = <<<EOD
  849. BEGIN:VCALENDAR
  850. VERSION:2.0
  851. PRODID:ownCloud Calendar
  852. BEGIN:VEVENT
  853. CREATED;VALUE=DATE-TIME:20130910T125139Z
  854. UID:47d15e3ec8
  855. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  856. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  857. SUMMARY:Test Event 123
  858. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  859. DTEND;VALUE=DATE-TIME:20130912T140000Z
  860. CLASS:PUBLIC
  861. END:VEVENT
  862. END:VCALENDAR
  863. EOD;
  864. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  865. $this->backend->createCalendarObject($subscriptionId, $uri, $calData2, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
  866. $this->assertEquals($calData, $this->backend->getCalendarObject($calendarId, $uri, CalDavBackend::CALENDAR_TYPE_CALENDAR)['calendardata']);
  867. $this->assertEquals($calData2, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION)['calendardata']);
  868. }
  869. public function testPurgeAllCachedEventsForSubscription(): void {
  870. $subscriptionId = $this->createTestSubscription();
  871. $uri = static::getUniqueID('calobj');
  872. $calData = <<<EOD
  873. BEGIN:VCALENDAR
  874. VERSION:2.0
  875. PRODID:ownCloud Calendar
  876. BEGIN:VEVENT
  877. CREATED;VALUE=DATE-TIME:20130910T125139Z
  878. UID:47d15e3ec8
  879. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  880. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  881. SUMMARY:Test Event
  882. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  883. DTEND;VALUE=DATE-TIME:20130912T140000Z
  884. CLASS:PUBLIC
  885. END:VEVENT
  886. END:VCALENDAR
  887. EOD;
  888. $this->backend->createCalendarObject($subscriptionId, $uri, $calData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
  889. $this->backend->purgeAllCachedEventsForSubscription($subscriptionId);
  890. $this->assertEquals(null, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION));
  891. }
  892. public function testCalendarMovement(): void {
  893. $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
  894. $this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
  895. $calendarInfoUser = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
  896. $this->backend->moveCalendar('Example', self::UNIT_TEST_USER, self::UNIT_TEST_USER1);
  897. $this->assertCount(0, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
  898. $this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1));
  899. $calendarInfoUser1 = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1)[0];
  900. $this->assertEquals($calendarInfoUser['id'], $calendarInfoUser1['id']);
  901. $this->assertEquals($calendarInfoUser['uri'], $calendarInfoUser1['uri']);
  902. }
  903. public function testSearchPrincipal(): void {
  904. $myPublic = <<<EOD
  905. BEGIN:VCALENDAR
  906. VERSION:2.0
  907. PRODID:-//dmfs.org//mimedir.icalendar//EN
  908. BEGIN:VTIMEZONE
  909. TZID:Europe/Berlin
  910. X-LIC-LOCATION:Europe/Berlin
  911. BEGIN:DAYLIGHT
  912. TZOFFSETFROM:+0100
  913. TZOFFSETTO:+0200
  914. TZNAME:CEST
  915. DTSTART:19700329T020000
  916. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  917. END:DAYLIGHT
  918. BEGIN:STANDARD
  919. TZOFFSETFROM:+0200
  920. TZOFFSETTO:+0100
  921. TZNAME:CET
  922. DTSTART:19701025T030000
  923. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  924. END:STANDARD
  925. END:VTIMEZONE
  926. BEGIN:VEVENT
  927. DTSTART;TZID=Europe/Berlin:20160419T130000
  928. SUMMARY:My Test (public)
  929. CLASS:PUBLIC
  930. TRANSP:OPAQUE
  931. STATUS:CONFIRMED
  932. DTEND;TZID=Europe/Berlin:20160419T140000
  933. LAST-MODIFIED:20160419T074202Z
  934. DTSTAMP:20160419T074202Z
  935. CREATED:20160419T074202Z
  936. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-1
  937. END:VEVENT
  938. END:VCALENDAR
  939. EOD;
  940. $myPrivate = <<<EOD
  941. BEGIN:VCALENDAR
  942. VERSION:2.0
  943. PRODID:-//dmfs.org//mimedir.icalendar//EN
  944. BEGIN:VTIMEZONE
  945. TZID:Europe/Berlin
  946. X-LIC-LOCATION:Europe/Berlin
  947. BEGIN:DAYLIGHT
  948. TZOFFSETFROM:+0100
  949. TZOFFSETTO:+0200
  950. TZNAME:CEST
  951. DTSTART:19700329T020000
  952. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  953. END:DAYLIGHT
  954. BEGIN:STANDARD
  955. TZOFFSETFROM:+0200
  956. TZOFFSETTO:+0100
  957. TZNAME:CET
  958. DTSTART:19701025T030000
  959. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  960. END:STANDARD
  961. END:VTIMEZONE
  962. BEGIN:VEVENT
  963. DTSTART;TZID=Europe/Berlin:20160419T130000
  964. SUMMARY:My Test (private)
  965. CLASS:PRIVATE
  966. TRANSP:OPAQUE
  967. STATUS:CONFIRMED
  968. DTEND;TZID=Europe/Berlin:20160419T140000
  969. LAST-MODIFIED:20160419T074202Z
  970. DTSTAMP:20160419T074202Z
  971. CREATED:20160419T074202Z
  972. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-2
  973. END:VEVENT
  974. END:VCALENDAR
  975. EOD;
  976. $myConfidential = <<<EOD
  977. BEGIN:VCALENDAR
  978. VERSION:2.0
  979. PRODID:-//dmfs.org//mimedir.icalendar//EN
  980. BEGIN:VTIMEZONE
  981. TZID:Europe/Berlin
  982. X-LIC-LOCATION:Europe/Berlin
  983. BEGIN:DAYLIGHT
  984. TZOFFSETFROM:+0100
  985. TZOFFSETTO:+0200
  986. TZNAME:CEST
  987. DTSTART:19700329T020000
  988. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  989. END:DAYLIGHT
  990. BEGIN:STANDARD
  991. TZOFFSETFROM:+0200
  992. TZOFFSETTO:+0100
  993. TZNAME:CET
  994. DTSTART:19701025T030000
  995. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  996. END:STANDARD
  997. END:VTIMEZONE
  998. BEGIN:VEVENT
  999. DTSTART;TZID=Europe/Berlin:20160419T130000
  1000. SUMMARY:My Test (confidential)
  1001. CLASS:CONFIDENTIAL
  1002. TRANSP:OPAQUE
  1003. STATUS:CONFIRMED
  1004. DTEND;TZID=Europe/Berlin:20160419T140000
  1005. LAST-MODIFIED:20160419T074202Z
  1006. DTSTAMP:20160419T074202Z
  1007. CREATED:20160419T074202Z
  1008. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-3
  1009. END:VEVENT
  1010. END:VCALENDAR
  1011. EOD;
  1012. $sharerPublic = <<<EOD
  1013. BEGIN:VCALENDAR
  1014. VERSION:2.0
  1015. PRODID:-//dmfs.org//mimedir.icalendar//EN
  1016. BEGIN:VTIMEZONE
  1017. TZID:Europe/Berlin
  1018. X-LIC-LOCATION:Europe/Berlin
  1019. BEGIN:DAYLIGHT
  1020. TZOFFSETFROM:+0100
  1021. TZOFFSETTO:+0200
  1022. TZNAME:CEST
  1023. DTSTART:19700329T020000
  1024. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  1025. END:DAYLIGHT
  1026. BEGIN:STANDARD
  1027. TZOFFSETFROM:+0200
  1028. TZOFFSETTO:+0100
  1029. TZNAME:CET
  1030. DTSTART:19701025T030000
  1031. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  1032. END:STANDARD
  1033. END:VTIMEZONE
  1034. BEGIN:VEVENT
  1035. DTSTART;TZID=Europe/Berlin:20160419T130000
  1036. SUMMARY:Sharer Test (public)
  1037. CLASS:PUBLIC
  1038. TRANSP:OPAQUE
  1039. STATUS:CONFIRMED
  1040. DTEND;TZID=Europe/Berlin:20160419T140000
  1041. LAST-MODIFIED:20160419T074202Z
  1042. DTSTAMP:20160419T074202Z
  1043. CREATED:20160419T074202Z
  1044. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-4
  1045. END:VEVENT
  1046. END:VCALENDAR
  1047. EOD;
  1048. $sharerPrivate = <<<EOD
  1049. BEGIN:VCALENDAR
  1050. VERSION:2.0
  1051. PRODID:-//dmfs.org//mimedir.icalendar//EN
  1052. BEGIN:VTIMEZONE
  1053. TZID:Europe/Berlin
  1054. X-LIC-LOCATION:Europe/Berlin
  1055. BEGIN:DAYLIGHT
  1056. TZOFFSETFROM:+0100
  1057. TZOFFSETTO:+0200
  1058. TZNAME:CEST
  1059. DTSTART:19700329T020000
  1060. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  1061. END:DAYLIGHT
  1062. BEGIN:STANDARD
  1063. TZOFFSETFROM:+0200
  1064. TZOFFSETTO:+0100
  1065. TZNAME:CET
  1066. DTSTART:19701025T030000
  1067. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  1068. END:STANDARD
  1069. END:VTIMEZONE
  1070. BEGIN:VEVENT
  1071. DTSTART;TZID=Europe/Berlin:20160419T130000
  1072. SUMMARY:Sharer Test (private)
  1073. CLASS:PRIVATE
  1074. TRANSP:OPAQUE
  1075. STATUS:CONFIRMED
  1076. DTEND;TZID=Europe/Berlin:20160419T140000
  1077. LAST-MODIFIED:20160419T074202Z
  1078. DTSTAMP:20160419T074202Z
  1079. CREATED:20160419T074202Z
  1080. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-5
  1081. END:VEVENT
  1082. END:VCALENDAR
  1083. EOD;
  1084. $sharerConfidential = <<<EOD
  1085. BEGIN:VCALENDAR
  1086. VERSION:2.0
  1087. PRODID:-//dmfs.org//mimedir.icalendar//EN
  1088. BEGIN:VTIMEZONE
  1089. TZID:Europe/Berlin
  1090. X-LIC-LOCATION:Europe/Berlin
  1091. BEGIN:DAYLIGHT
  1092. TZOFFSETFROM:+0100
  1093. TZOFFSETTO:+0200
  1094. TZNAME:CEST
  1095. DTSTART:19700329T020000
  1096. RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
  1097. END:DAYLIGHT
  1098. BEGIN:STANDARD
  1099. TZOFFSETFROM:+0200
  1100. TZOFFSETTO:+0100
  1101. TZNAME:CET
  1102. DTSTART:19701025T030000
  1103. RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
  1104. END:STANDARD
  1105. END:VTIMEZONE
  1106. BEGIN:VEVENT
  1107. DTSTART;TZID=Europe/Berlin:20160419T130000
  1108. SUMMARY:Sharer Test (confidential)
  1109. CLASS:CONFIDENTIAL
  1110. TRANSP:OPAQUE
  1111. STATUS:CONFIRMED
  1112. DTEND;TZID=Europe/Berlin:20160419T140000
  1113. LAST-MODIFIED:20160419T074202Z
  1114. DTSTAMP:20160419T074202Z
  1115. CREATED:20160419T074202Z
  1116. UID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310-6
  1117. END:VEVENT
  1118. END:VCALENDAR
  1119. EOD;
  1120. $l10n = $this->createMock(IL10N::class);
  1121. $l10n
  1122. ->expects($this->any())
  1123. ->method('t')
  1124. ->willReturnCallback(function ($text, $parameters = []) {
  1125. return vsprintf($text, $parameters);
  1126. });
  1127. $config = $this->createMock(IConfig::class);
  1128. $this->userManager->expects($this->any())
  1129. ->method('userExists')
  1130. ->willReturn(true);
  1131. $this->groupManager->expects($this->any())
  1132. ->method('groupExists')
  1133. ->willReturn(true);
  1134. $me = self::UNIT_TEST_USER;
  1135. $sharer = self::UNIT_TEST_USER1;
  1136. $this->backend->createCalendar($me, 'calendar-uri-me', []);
  1137. $this->backend->createCalendar($sharer, 'calendar-uri-sharer', []);
  1138. $myCalendars = $this->backend->getCalendarsForUser($me);
  1139. $this->assertCount(1, $myCalendars);
  1140. $sharerCalendars = $this->backend->getCalendarsForUser($sharer);
  1141. $this->assertCount(1, $sharerCalendars);
  1142. $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
  1143. $sharerCalendar = new Calendar($this->backend, $sharerCalendars[0], $l10n, $config, $logger);
  1144. $this->backend->updateShares($sharerCalendar, [
  1145. [
  1146. 'href' => 'principal:' . $me,
  1147. 'readOnly' => false,
  1148. ],
  1149. ], []);
  1150. $this->assertCount(2, $this->backend->getCalendarsForUser($me));
  1151. $this->backend->createCalendarObject($myCalendars[0]['id'], 'event0.ics', $myPublic);
  1152. $this->backend->createCalendarObject($myCalendars[0]['id'], 'event1.ics', $myPrivate);
  1153. $this->backend->createCalendarObject($myCalendars[0]['id'], 'event2.ics', $myConfidential);
  1154. $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event3.ics', $sharerPublic);
  1155. $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event4.ics', $sharerPrivate);
  1156. $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event5.ics', $sharerConfidential);
  1157. $mySearchResults = $this->backend->searchPrincipalUri($me, 'Test', ['VEVENT'], ['SUMMARY'], []);
  1158. $sharerSearchResults = $this->backend->searchPrincipalUri($sharer, 'Test', ['VEVENT'], ['SUMMARY'], []);
  1159. $this->assertCount(4, $mySearchResults);
  1160. $this->assertCount(3, $sharerSearchResults);
  1161. $this->assertEquals($myPublic, $mySearchResults[0]['calendardata']);
  1162. $this->assertEquals($myPrivate, $mySearchResults[1]['calendardata']);
  1163. $this->assertEquals($myConfidential, $mySearchResults[2]['calendardata']);
  1164. $this->assertEquals($sharerPublic, $mySearchResults[3]['calendardata']);
  1165. $this->assertEquals($sharerPublic, $sharerSearchResults[0]['calendardata']);
  1166. $this->assertEquals($sharerPrivate, $sharerSearchResults[1]['calendardata']);
  1167. $this->assertEquals($sharerConfidential, $sharerSearchResults[2]['calendardata']);
  1168. }
  1169. /**
  1170. * @throws \OCP\DB\Exception
  1171. * @throws \Sabre\DAV\Exception\BadRequest
  1172. */
  1173. public function testPruneOutdatedSyncTokens(): void {
  1174. $calendarId = $this->createTestCalendar();
  1175. $changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
  1176. $syncToken = $changes['syncToken'];
  1177. $uri = static::getUniqueID('calobj');
  1178. $calData = <<<EOD
  1179. BEGIN:VCALENDAR
  1180. VERSION:2.0
  1181. PRODID:Nextcloud Calendar
  1182. BEGIN:VEVENT
  1183. CREATED;VALUE=DATE-TIME:20130910T125139Z
  1184. UID:47d15e3ec8
  1185. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  1186. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  1187. SUMMARY:Test Event
  1188. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  1189. DTEND;VALUE=DATE-TIME:20130912T140000Z
  1190. CLASS:PUBLIC
  1191. END:VEVENT
  1192. END:VCALENDAR
  1193. EOD;
  1194. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  1195. // update the card
  1196. $calData = <<<'EOD'
  1197. BEGIN:VCALENDAR
  1198. VERSION:2.0
  1199. PRODID:Nextcloud Calendar
  1200. BEGIN:VEVENT
  1201. CREATED;VALUE=DATE-TIME:20130910T125139Z
  1202. UID:47d15e3ec8
  1203. LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
  1204. DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
  1205. SUMMARY:123 Event 🙈
  1206. DTSTART;VALUE=DATE-TIME:20130912T130000Z
  1207. DTEND;VALUE=DATE-TIME:20130912T140000Z
  1208. ATTENDEE;CN=test:mailto:foo@bar.com
  1209. END:VEVENT
  1210. END:VCALENDAR
  1211. EOD;
  1212. $this->backend->updateCalendarObject($calendarId, $uri, $calData);
  1213. $deleted = $this->backend->pruneOutdatedSyncTokens(0);
  1214. // At least one from the object creation and one from the object update
  1215. $this->assertGreaterThanOrEqual(2, $deleted);
  1216. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
  1217. $this->assertEmpty($changes['added']);
  1218. $this->assertEmpty($changes['modified']);
  1219. $this->assertEmpty($changes['deleted']);
  1220. // Test that objects remain
  1221. // Currently changes are empty
  1222. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
  1223. $this->assertEquals(0, count($changes['added'] + $changes['modified'] + $changes['deleted']));
  1224. // Create card
  1225. $uri = static::getUniqueID('calobj');
  1226. $calData = <<<EOD
  1227. BEGIN:VCALENDAR
  1228. VERSION:2.0
  1229. PRODID:Nextcloud Calendar
  1230. BEGIN:VEVENT
  1231. CREATED;VALUE=DATE-TIME:20230910T125139Z
  1232. UID:47d15e3ec9
  1233. LAST-MODIFIED;VALUE=DATE-TIME:20230910T125139Z
  1234. DTSTAMP;VALUE=DATE-TIME:20230910T125139Z
  1235. SUMMARY:Test Event
  1236. DTSTART;VALUE=DATE-TIME:20230912T130000Z
  1237. DTEND;VALUE=DATE-TIME:20230912T140000Z
  1238. CLASS:PUBLIC
  1239. END:VEVENT
  1240. END:VCALENDAR
  1241. EOD;
  1242. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  1243. // We now have one add
  1244. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
  1245. $this->assertEquals(1, count($changes['added']));
  1246. $this->assertEmpty($changes['modified']);
  1247. $this->assertEmpty($changes['deleted']);
  1248. // update the card
  1249. $calData = <<<'EOD'
  1250. BEGIN:VCALENDAR
  1251. VERSION:2.0
  1252. PRODID:Nextcloud Calendar
  1253. BEGIN:VEVENT
  1254. CREATED;VALUE=DATE-TIME:20230910T125139Z
  1255. UID:47d15e3ec9
  1256. LAST-MODIFIED;VALUE=DATE-TIME:20230910T125139Z
  1257. DTSTAMP;VALUE=DATE-TIME:20230910T125139Z
  1258. SUMMARY:123 Event 🙈
  1259. DTSTART;VALUE=DATE-TIME:20230912T130000Z
  1260. DTEND;VALUE=DATE-TIME:20230912T140000Z
  1261. ATTENDEE;CN=test:mailto:foo@bar.com
  1262. END:VEVENT
  1263. END:VCALENDAR
  1264. EOD;
  1265. $this->backend->updateCalendarObject($calendarId, $uri, $calData);
  1266. // One add, one modify, but shortened to modify
  1267. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
  1268. $this->assertEmpty($changes['added']);
  1269. $this->assertEquals(1, count($changes['modified']));
  1270. $this->assertEmpty($changes['deleted']);
  1271. // Delete all but last change
  1272. $deleted = $this->backend->pruneOutdatedSyncTokens(1);
  1273. $this->assertEquals(1, $deleted); // We had two changes before, now one
  1274. // Only update should remain
  1275. $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
  1276. $this->assertEmpty($changes['added']);
  1277. $this->assertEquals(1, count($changes['modified']));
  1278. $this->assertEmpty($changes['deleted']);
  1279. // Check that no crash occurs when prune is called without current changes
  1280. $deleted = $this->backend->pruneOutdatedSyncTokens(1);
  1281. }
  1282. public function testSearchAndExpandRecurrences() {
  1283. $calendarId = $this->createTestCalendar();
  1284. $calendarInfo = [
  1285. 'id' => $calendarId,
  1286. 'principaluri' => 'user1',
  1287. '{http://owncloud.org/ns}owner-principal' => 'user1',
  1288. ];
  1289. $calData = <<<'EOD'
  1290. BEGIN:VCALENDAR
  1291. PRODID:-//IDN nextcloud.com//Calendar app 4.5.0-alpha.2//EN
  1292. CALSCALE:GREGORIAN
  1293. VERSION:2.0
  1294. BEGIN:VEVENT
  1295. CREATED:20230921T133401Z
  1296. DTSTAMP:20230921T133448Z
  1297. LAST-MODIFIED:20230921T133448Z
  1298. SEQUENCE:2
  1299. UID:7b7d5d12-683c-48ce-973a-b3e1cb0bae2a
  1300. DTSTART;VALUE=DATE:20230912
  1301. DTEND;VALUE=DATE:20230913
  1302. STATUS:CONFIRMED
  1303. SUMMARY:Daily Event
  1304. RRULE:FREQ=DAILY
  1305. END:VEVENT
  1306. END:VCALENDAR
  1307. EOD;
  1308. $uri = static::getUniqueID('calobj');
  1309. $this->backend->createCalendarObject($calendarId, $uri, $calData);
  1310. $start = new DateTimeImmutable('2023-09-20T00:00:00Z');
  1311. $end = $start->add(new DateInterval('P14D'));
  1312. $results = $this->backend->search(
  1313. $calendarInfo,
  1314. '',
  1315. [],
  1316. [
  1317. 'timerange' => [
  1318. 'start' => $start,
  1319. 'end' => $end,
  1320. ]
  1321. ],
  1322. null,
  1323. null,
  1324. );
  1325. $this->assertCount(1, $results);
  1326. $this->assertCount(14, $results[0]['objects']);
  1327. foreach ($results as $result) {
  1328. foreach ($result['objects'] as $object) {
  1329. $this->assertEquals($object['UID'][0], '7b7d5d12-683c-48ce-973a-b3e1cb0bae2a');
  1330. $this->assertEquals($object['SUMMARY'][0], 'Daily Event');
  1331. $this->assertGreaterThanOrEqual(
  1332. $start->getTimestamp(),
  1333. $object['DTSTART'][0]->getTimestamp(),
  1334. 'Recurrence starting before requested start',
  1335. );
  1336. $this->assertLessThanOrEqual(
  1337. $end->getTimestamp(),
  1338. $object['DTSTART'][0]->getTimestamp(),
  1339. 'Recurrence starting after requested end',
  1340. );
  1341. }
  1342. }
  1343. }
  1344. }