1
0

BirthdayServiceTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Georg Ehrke <oc.list@georgehrke.com>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Roeland Jago Douma <roeland@famdouma.nl>
  10. * @author Thomas Müller <thomas.mueller@tmit.eu>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OCA\DAV\Tests\unit\CardDAV;
  28. use OCA\DAV\CalDAV\BirthdayService;
  29. use OCA\DAV\CalDAV\CalDavBackend;
  30. use OCA\DAV\CardDAV\CardDavBackend;
  31. use OCA\DAV\DAV\GroupPrincipalBackend;
  32. use OCP\IConfig;
  33. use OCP\IDBConnection;
  34. use OCP\IL10N;
  35. use Sabre\VObject\Component\VCalendar;
  36. use Sabre\VObject\Reader;
  37. use Test\TestCase;
  38. class BirthdayServiceTest extends TestCase {
  39. /** @var BirthdayService */
  40. private $service;
  41. /** @var CalDavBackend | \PHPUnit\Framework\MockObject\MockObject */
  42. private $calDav;
  43. /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject */
  44. private $cardDav;
  45. /** @var GroupPrincipalBackend | \PHPUnit\Framework\MockObject\MockObject */
  46. private $groupPrincipalBackend;
  47. /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */
  48. private $config;
  49. /** @var IDBConnection | \PHPUnit\Framework\MockObject\MockObject */
  50. private $dbConnection;
  51. /** @var IL10N | \PHPUnit\Framework\MockObject\MockObject */
  52. private $l10n;
  53. protected function setUp(): void {
  54. parent::setUp();
  55. $this->calDav = $this->createMock(CalDavBackend::class);
  56. $this->cardDav = $this->createMock(CardDavBackend::class);
  57. $this->groupPrincipalBackend = $this->createMock(GroupPrincipalBackend::class);
  58. $this->config = $this->createMock(IConfig::class);
  59. $this->dbConnection = $this->createMock(IDBConnection::class);
  60. $this->l10n = $this->createMock(IL10N::class);
  61. $this->l10n->expects($this->any())
  62. ->method('t')
  63. ->willReturnCallback(function ($string, $args) {
  64. return vsprintf($string, $args);
  65. });
  66. $this->service = new BirthdayService($this->calDav, $this->cardDav,
  67. $this->groupPrincipalBackend, $this->config,
  68. $this->dbConnection, $this->l10n);
  69. }
  70. /**
  71. * @dataProvider providesVCards
  72. * @param string $expectedSummary
  73. * @param string $expectedDTStart
  74. * @param string $expectedRrule
  75. * @param string $expectedFieldType
  76. * @param string $expectedUnknownYear
  77. * @param string $expectedOriginalYear
  78. * @param string|null $expectedReminder
  79. * @param string | null $data
  80. */
  81. public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Bytes, $configuredReminder): void {
  82. $this->dbConnection->method('supports4ByteText')->willReturn($supports4Bytes);
  83. $cal = $this->service->buildDateFromContact($data, $fieldType, $prefix, $configuredReminder);
  84. if ($expectedSummary === null) {
  85. $this->assertNull($cal);
  86. } else {
  87. $this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal);
  88. $this->assertEquals('-//IDN nextcloud.com//Birthday calendar//EN', $cal->PRODID->getValue());
  89. $this->assertTrue(isset($cal->VEVENT));
  90. $this->assertEquals($expectedRrule, $cal->VEVENT->RRULE->getValue());
  91. $this->assertEquals($expectedSummary, $cal->VEVENT->SUMMARY->getValue());
  92. $this->assertEquals($expectedDTStart, $cal->VEVENT->DTSTART->getValue());
  93. $this->assertEquals($expectedFieldType, $cal->VEVENT->{'X-NEXTCLOUD-BC-FIELD-TYPE'}->getValue());
  94. $this->assertEquals($expectedUnknownYear, $cal->VEVENT->{'X-NEXTCLOUD-BC-UNKNOWN-YEAR'}->getValue());
  95. if ($expectedOriginalYear) {
  96. $this->assertEquals($expectedOriginalYear, $cal->VEVENT->{'X-NEXTCLOUD-BC-YEAR'}->getValue());
  97. }
  98. if ($expectedReminder) {
  99. $this->assertEquals($expectedReminder, $cal->VEVENT->VALARM->TRIGGER->getValue());
  100. $this->assertEquals('DURATION', $cal->VEVENT->VALARM->TRIGGER->getValueType());
  101. }
  102. $this->assertEquals('TRANSPARENT', $cal->VEVENT->TRANSP->getValue());
  103. }
  104. }
  105. public function testOnCardDeleteGloballyDisabled(): void {
  106. $this->config->expects($this->once())
  107. ->method('getAppValue')
  108. ->with('dav', 'generateBirthdayCalendar', 'yes')
  109. ->willReturn('no');
  110. $this->cardDav->expects($this->never())->method('getAddressBookById');
  111. $this->service->onCardDeleted(666, 'gump.vcf');
  112. }
  113. public function testOnCardDeleteUserDisabled(): void {
  114. $this->config->expects($this->once())
  115. ->method('getAppValue')
  116. ->with('dav', 'generateBirthdayCalendar', 'yes')
  117. ->willReturn('yes');
  118. $this->config->expects($this->once())
  119. ->method('getUserValue')
  120. ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes')
  121. ->willReturn('no');
  122. $this->cardDav->expects($this->once())->method('getAddressBookById')
  123. ->with(666)
  124. ->willReturn([
  125. 'principaluri' => 'principals/users/user01',
  126. 'uri' => 'default'
  127. ]);
  128. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  129. $this->calDav->expects($this->never())->method('getCalendarByUri');
  130. $this->calDav->expects($this->never())->method('deleteCalendarObject');
  131. $this->service->onCardDeleted(666, 'gump.vcf');
  132. }
  133. public function testOnCardDeleted(): void {
  134. $this->config->expects($this->once())
  135. ->method('getAppValue')
  136. ->with('dav', 'generateBirthdayCalendar', 'yes')
  137. ->willReturn('yes');
  138. $this->config->expects($this->once())
  139. ->method('getUserValue')
  140. ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes')
  141. ->willReturn('yes');
  142. $this->cardDav->expects($this->once())->method('getAddressBookById')
  143. ->with(666)
  144. ->willReturn([
  145. 'principaluri' => 'principals/users/user01',
  146. 'uri' => 'default'
  147. ]);
  148. $this->calDav->expects($this->once())->method('getCalendarByUri')
  149. ->with('principals/users/user01', 'contact_birthdays')
  150. ->willReturn([
  151. 'id' => 1234
  152. ]);
  153. $this->calDav->expects($this->exactly(3))
  154. ->method('deleteCalendarObject')
  155. ->withConsecutive(
  156. [1234, 'default-gump.vcf.ics'],
  157. [1234, 'default-gump.vcf-death.ics'],
  158. [1234, 'default-gump.vcf-anniversary.ics'],
  159. );
  160. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  161. $this->service->onCardDeleted(666, 'gump.vcf');
  162. }
  163. public function testOnCardChangedGloballyDisabled(): void {
  164. $this->config->expects($this->once())
  165. ->method('getAppValue')
  166. ->with('dav', 'generateBirthdayCalendar', 'yes')
  167. ->willReturn('no');
  168. $this->cardDav->expects($this->never())->method('getAddressBookById');
  169. $service = $this->getMockBuilder(BirthdayService::class)
  170. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  171. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  172. ->getMock();
  173. $service->onCardChanged(666, 'gump.vcf', '');
  174. }
  175. public function testOnCardChangedUserDisabled(): void {
  176. $this->config->expects($this->once())
  177. ->method('getAppValue')
  178. ->with('dav', 'generateBirthdayCalendar', 'yes')
  179. ->willReturn('yes');
  180. $this->config->expects($this->once())
  181. ->method('getUserValue')
  182. ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes')
  183. ->willReturn('no');
  184. $this->cardDav->expects($this->once())->method('getAddressBookById')
  185. ->with(666)
  186. ->willReturn([
  187. 'principaluri' => 'principals/users/user01',
  188. 'uri' => 'default'
  189. ]);
  190. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  191. $this->calDav->expects($this->never())->method('getCalendarByUri');
  192. /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */
  193. $service = $this->getMockBuilder(BirthdayService::class)
  194. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  195. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  196. ->getMock();
  197. $service->onCardChanged(666, 'gump.vcf', '');
  198. }
  199. /**
  200. * @dataProvider providesCardChanges
  201. */
  202. public function testOnCardChanged($expectedOp): void {
  203. $this->config->expects($this->once())
  204. ->method('getAppValue')
  205. ->with('dav', 'generateBirthdayCalendar', 'yes')
  206. ->willReturn('yes');
  207. $this->config->expects($this->exactly(2))
  208. ->method('getUserValue')
  209. ->withConsecutive(
  210. ['user01', 'dav', 'generateBirthdayCalendar', 'yes'],
  211. ['user01', 'dav', 'birthdayCalendarReminderOffset', 'PT9H'],
  212. )
  213. ->willReturnOnConsecutiveCalls('yes', 'PT9H');
  214. $this->cardDav->expects($this->once())->method('getAddressBookById')
  215. ->with(666)
  216. ->willReturn([
  217. 'principaluri' => 'principals/users/user01',
  218. 'uri' => 'default'
  219. ]);
  220. $this->calDav->expects($this->once())->method('getCalendarByUri')
  221. ->with('principals/users/user01', 'contact_birthdays')
  222. ->willReturn([
  223. 'id' => 1234
  224. ]);
  225. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  226. /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */
  227. $service = $this->getMockBuilder(BirthdayService::class)
  228. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  229. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  230. ->getMock();
  231. if ($expectedOp === 'delete') {
  232. $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn('');
  233. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn(null);
  234. $this->calDav->expects($this->exactly(3))->method('deleteCalendarObject')->withConsecutive(
  235. [1234, 'default-gump.vcf.ics'],
  236. [1234, 'default-gump.vcf-death.ics'],
  237. [1234, 'default-gump.vcf-anniversary.ics']
  238. );
  239. }
  240. if ($expectedOp === 'create') {
  241. $vCal = new VCalendar();
  242. $vCal->PRODID = '-//Nextcloud testing//mocked object//';
  243. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal);
  244. $this->calDav->expects($this->exactly(3))->method('createCalendarObject')->withConsecutive(
  245. [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"],
  246. [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"],
  247. [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"]
  248. );
  249. }
  250. if ($expectedOp === 'update') {
  251. $vCal = new VCalendar();
  252. $vCal->PRODID = '-//Nextcloud testing//mocked object//';
  253. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal);
  254. $service->expects($this->exactly(3))->method('birthdayEvenChanged')->willReturn(true);
  255. $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(['calendardata' => '']);
  256. $this->calDav->expects($this->exactly(3))->method('updateCalendarObject')->withConsecutive(
  257. [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"],
  258. [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"],
  259. [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"]
  260. );
  261. }
  262. $service->onCardChanged(666, 'gump.vcf', '');
  263. }
  264. /**
  265. * @dataProvider providesBirthday
  266. * @param $expected
  267. * @param $old
  268. * @param $new
  269. */
  270. public function testBirthdayEvenChanged($expected, $old, $new): void {
  271. $new = Reader::read($new);
  272. $this->assertEquals($expected, $this->service->birthdayEvenChanged($old, $new));
  273. }
  274. public function testGetAllAffectedPrincipals(): void {
  275. $this->cardDav->expects($this->once())->method('getShares')->willReturn([
  276. [
  277. '{http://owncloud.org/ns}group-share' => false,
  278. '{http://owncloud.org/ns}principal' => 'principals/users/user01'
  279. ],
  280. [
  281. '{http://owncloud.org/ns}group-share' => false,
  282. '{http://owncloud.org/ns}principal' => 'principals/users/user01'
  283. ],
  284. [
  285. '{http://owncloud.org/ns}group-share' => false,
  286. '{http://owncloud.org/ns}principal' => 'principals/users/user02'
  287. ],
  288. [
  289. '{http://owncloud.org/ns}group-share' => true,
  290. '{http://owncloud.org/ns}principal' => 'principals/groups/users'
  291. ],
  292. ]);
  293. $this->groupPrincipalBackend->expects($this->once())->method('getGroupMemberSet')
  294. ->willReturn([
  295. [
  296. 'uri' => 'principals/users/user01',
  297. ],
  298. [
  299. 'uri' => 'principals/users/user02',
  300. ],
  301. [
  302. 'uri' => 'principals/users/user03',
  303. ],
  304. ]);
  305. $users = $this->invokePrivate($this->service, 'getAllAffectedPrincipals', [6666]);
  306. $this->assertEquals([
  307. 'principals/users/user01',
  308. 'principals/users/user02',
  309. 'principals/users/user03'
  310. ], $users);
  311. }
  312. public function testBirthdayCalendarHasComponentEvent(): void {
  313. $this->calDav->expects($this->once())
  314. ->method('createCalendar')
  315. ->with('principal001', 'contact_birthdays', [
  316. '{DAV:}displayname' => 'Contact birthdays',
  317. '{http://apple.com/ns/ical/}calendar-color' => '#E9D859',
  318. 'components' => 'VEVENT',
  319. ]);
  320. $this->service->ensureCalendarExists('principal001');
  321. }
  322. public function testResetForUser(): void {
  323. $this->calDav->expects($this->once())
  324. ->method('getCalendarByUri')
  325. ->with('principals/users/user123', 'contact_birthdays')
  326. ->willReturn(['id' => 42]);
  327. $this->calDav->expects($this->once())
  328. ->method('getCalendarObjects')
  329. ->with(42, 0)
  330. ->willReturn([['uri' => '1.ics'], ['uri' => '2.ics'], ['uri' => '3.ics']]);
  331. $this->calDav->expects($this->exactly(3))
  332. ->method('deleteCalendarObject')
  333. ->withConsecutive(
  334. [42, '1.ics', 0],
  335. [42, '2.ics', 0],
  336. [42, '3.ics', 0],
  337. );
  338. $this->service->resetForUser('user123');
  339. }
  340. public function providesBirthday() {
  341. return [
  342. [true,
  343. '',
  344. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  345. [false,
  346. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
  347. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  348. [true,
  349. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:4567's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
  350. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
  351. [true,
  352. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
  353. "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000102\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"]
  354. ];
  355. }
  356. public function providesCardChanges() {
  357. return[
  358. ['delete'],
  359. ['create'],
  360. ['update']
  361. ];
  362. }
  363. public function providesVCards() {
  364. return [
  365. // $expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Byte, $configuredReminder
  366. [null, null, null, null, null, null, null, 'yasfewf', '', '', true, null],
  367. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  368. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  369. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  370. ['🎂 12345 (1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  371. ['🎂 12345 (1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  372. ['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null],
  373. ['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', false, null],
  374. ['💍 12345 (1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null],
  375. ['12345 (⚭1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', false, null],
  376. ['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  377. ['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  378. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  379. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  380. ['🎂 12345 (900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  381. ['12345 (*1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  382. ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  383. ['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  384. ['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  385. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  386. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  387. ['12345 (*900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null],
  388. ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', 'PT9H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, 'PT9H'],
  389. ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-PT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-PT15H'],
  390. ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-P6DT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-P6DT15H'],
  391. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  392. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null],
  393. [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null],
  394. ['🎂 12345 (1902)', '19720229', 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1', 'BDAY', '0', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19020229\r\nEND:VCARD\r\n", 'BDAY', '', true, null],
  395. ];
  396. }
  397. }