BirthdayServiceTest.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\DAV\Tests\unit\CardDAV;
  8. use OCA\DAV\CalDAV\BirthdayService;
  9. use OCA\DAV\CalDAV\CalDavBackend;
  10. use OCA\DAV\CardDAV\CardDavBackend;
  11. use OCA\DAV\DAV\GroupPrincipalBackend;
  12. use OCP\IConfig;
  13. use OCP\IDBConnection;
  14. use OCP\IL10N;
  15. use Sabre\VObject\Component\VCalendar;
  16. use Sabre\VObject\Reader;
  17. use Test\TestCase;
  18. class BirthdayServiceTest extends TestCase {
  19. /** @var BirthdayService */
  20. private $service;
  21. /** @var CalDavBackend | \PHPUnit\Framework\MockObject\MockObject */
  22. private $calDav;
  23. /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject */
  24. private $cardDav;
  25. /** @var GroupPrincipalBackend | \PHPUnit\Framework\MockObject\MockObject */
  26. private $groupPrincipalBackend;
  27. /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */
  28. private $config;
  29. /** @var IDBConnection | \PHPUnit\Framework\MockObject\MockObject */
  30. private $dbConnection;
  31. /** @var IL10N | \PHPUnit\Framework\MockObject\MockObject */
  32. private $l10n;
  33. protected function setUp(): void {
  34. parent::setUp();
  35. $this->calDav = $this->createMock(CalDavBackend::class);
  36. $this->cardDav = $this->createMock(CardDavBackend::class);
  37. $this->groupPrincipalBackend = $this->createMock(GroupPrincipalBackend::class);
  38. $this->config = $this->createMock(IConfig::class);
  39. $this->dbConnection = $this->createMock(IDBConnection::class);
  40. $this->l10n = $this->createMock(IL10N::class);
  41. $this->l10n->expects($this->any())
  42. ->method('t')
  43. ->willReturnCallback(function ($string, $args) {
  44. return vsprintf($string, $args);
  45. });
  46. $this->service = new BirthdayService($this->calDav, $this->cardDav,
  47. $this->groupPrincipalBackend, $this->config,
  48. $this->dbConnection, $this->l10n);
  49. }
  50. /**
  51. * @dataProvider providesVCards
  52. * @param string $expectedSummary
  53. * @param string $expectedDTStart
  54. * @param string $expectedRrule
  55. * @param string $expectedFieldType
  56. * @param string $expectedUnknownYear
  57. * @param string $expectedOriginalYear
  58. * @param string|null $expectedReminder
  59. * @param string | null $data
  60. */
  61. public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Bytes, $configuredReminder): void {
  62. $this->dbConnection->method('supports4ByteText')->willReturn($supports4Bytes);
  63. $cal = $this->service->buildDateFromContact($data, $fieldType, $prefix, $configuredReminder);
  64. if ($expectedSummary === null) {
  65. $this->assertNull($cal);
  66. } else {
  67. $this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal);
  68. $this->assertEquals('-//IDN nextcloud.com//Birthday calendar//EN', $cal->PRODID->getValue());
  69. $this->assertTrue(isset($cal->VEVENT));
  70. $this->assertEquals($expectedRrule, $cal->VEVENT->RRULE->getValue());
  71. $this->assertEquals($expectedSummary, $cal->VEVENT->SUMMARY->getValue());
  72. $this->assertEquals($expectedDTStart, $cal->VEVENT->DTSTART->getValue());
  73. $this->assertEquals($expectedFieldType, $cal->VEVENT->{'X-NEXTCLOUD-BC-FIELD-TYPE'}->getValue());
  74. $this->assertEquals($expectedUnknownYear, $cal->VEVENT->{'X-NEXTCLOUD-BC-UNKNOWN-YEAR'}->getValue());
  75. if ($expectedOriginalYear) {
  76. $this->assertEquals($expectedOriginalYear, $cal->VEVENT->{'X-NEXTCLOUD-BC-YEAR'}->getValue());
  77. }
  78. if ($expectedReminder) {
  79. $this->assertEquals($expectedReminder, $cal->VEVENT->VALARM->TRIGGER->getValue());
  80. $this->assertEquals('DURATION', $cal->VEVENT->VALARM->TRIGGER->getValueType());
  81. }
  82. $this->assertEquals('TRANSPARENT', $cal->VEVENT->TRANSP->getValue());
  83. }
  84. }
  85. public function testOnCardDeleteGloballyDisabled(): void {
  86. $this->config->expects($this->once())
  87. ->method('getAppValue')
  88. ->with('dav', 'generateBirthdayCalendar', 'yes')
  89. ->willReturn('no');
  90. $this->cardDav->expects($this->never())->method('getAddressBookById');
  91. $this->service->onCardDeleted(666, 'gump.vcf');
  92. }
  93. public function testOnCardDeleteUserDisabled(): void {
  94. $this->config->expects($this->once())
  95. ->method('getAppValue')
  96. ->with('dav', 'generateBirthdayCalendar', 'yes')
  97. ->willReturn('yes');
  98. $this->config->expects($this->once())
  99. ->method('getUserValue')
  100. ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes')
  101. ->willReturn('no');
  102. $this->cardDav->expects($this->once())->method('getAddressBookById')
  103. ->with(666)
  104. ->willReturn([
  105. 'principaluri' => 'principals/users/user01',
  106. 'uri' => 'default'
  107. ]);
  108. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  109. $this->calDav->expects($this->never())->method('getCalendarByUri');
  110. $this->calDav->expects($this->never())->method('deleteCalendarObject');
  111. $this->service->onCardDeleted(666, 'gump.vcf');
  112. }
  113. public function testOnCardDeleted(): 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('yes');
  122. $this->cardDav->expects($this->once())->method('getAddressBookById')
  123. ->with(666)
  124. ->willReturn([
  125. 'principaluri' => 'principals/users/user01',
  126. 'uri' => 'default'
  127. ]);
  128. $this->calDav->expects($this->once())->method('getCalendarByUri')
  129. ->with('principals/users/user01', 'contact_birthdays')
  130. ->willReturn([
  131. 'id' => 1234
  132. ]);
  133. $this->calDav->expects($this->exactly(3))
  134. ->method('deleteCalendarObject')
  135. ->withConsecutive(
  136. [1234, 'default-gump.vcf.ics'],
  137. [1234, 'default-gump.vcf-death.ics'],
  138. [1234, 'default-gump.vcf-anniversary.ics'],
  139. );
  140. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  141. $this->service->onCardDeleted(666, 'gump.vcf');
  142. }
  143. public function testOnCardChangedGloballyDisabled(): void {
  144. $this->config->expects($this->once())
  145. ->method('getAppValue')
  146. ->with('dav', 'generateBirthdayCalendar', 'yes')
  147. ->willReturn('no');
  148. $this->cardDav->expects($this->never())->method('getAddressBookById');
  149. $service = $this->getMockBuilder(BirthdayService::class)
  150. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  151. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  152. ->getMock();
  153. $service->onCardChanged(666, 'gump.vcf', '');
  154. }
  155. public function testOnCardChangedUserDisabled(): void {
  156. $this->config->expects($this->once())
  157. ->method('getAppValue')
  158. ->with('dav', 'generateBirthdayCalendar', 'yes')
  159. ->willReturn('yes');
  160. $this->config->expects($this->once())
  161. ->method('getUserValue')
  162. ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes')
  163. ->willReturn('no');
  164. $this->cardDav->expects($this->once())->method('getAddressBookById')
  165. ->with(666)
  166. ->willReturn([
  167. 'principaluri' => 'principals/users/user01',
  168. 'uri' => 'default'
  169. ]);
  170. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  171. $this->calDav->expects($this->never())->method('getCalendarByUri');
  172. /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */
  173. $service = $this->getMockBuilder(BirthdayService::class)
  174. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  175. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  176. ->getMock();
  177. $service->onCardChanged(666, 'gump.vcf', '');
  178. }
  179. /**
  180. * @dataProvider providesCardChanges
  181. */
  182. public function testOnCardChanged($expectedOp): void {
  183. $this->config->expects($this->once())
  184. ->method('getAppValue')
  185. ->with('dav', 'generateBirthdayCalendar', 'yes')
  186. ->willReturn('yes');
  187. $this->config->expects($this->exactly(2))
  188. ->method('getUserValue')
  189. ->withConsecutive(
  190. ['user01', 'dav', 'generateBirthdayCalendar', 'yes'],
  191. ['user01', 'dav', 'birthdayCalendarReminderOffset', 'PT9H'],
  192. )
  193. ->willReturnOnConsecutiveCalls('yes', 'PT9H');
  194. $this->cardDav->expects($this->once())->method('getAddressBookById')
  195. ->with(666)
  196. ->willReturn([
  197. 'principaluri' => 'principals/users/user01',
  198. 'uri' => 'default'
  199. ]);
  200. $this->calDav->expects($this->once())->method('getCalendarByUri')
  201. ->with('principals/users/user01', 'contact_birthdays')
  202. ->willReturn([
  203. 'id' => 1234
  204. ]);
  205. $this->cardDav->expects($this->once())->method('getShares')->willReturn([]);
  206. /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */
  207. $service = $this->getMockBuilder(BirthdayService::class)
  208. ->setMethods(['buildDateFromContact', 'birthdayEvenChanged'])
  209. ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n])
  210. ->getMock();
  211. if ($expectedOp === 'delete') {
  212. $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn('');
  213. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn(null);
  214. $this->calDav->expects($this->exactly(3))->method('deleteCalendarObject')->withConsecutive(
  215. [1234, 'default-gump.vcf.ics'],
  216. [1234, 'default-gump.vcf-death.ics'],
  217. [1234, 'default-gump.vcf-anniversary.ics']
  218. );
  219. }
  220. if ($expectedOp === 'create') {
  221. $vCal = new VCalendar();
  222. $vCal->PRODID = '-//Nextcloud testing//mocked object//';
  223. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal);
  224. $this->calDav->expects($this->exactly(3))->method('createCalendarObject')->withConsecutive(
  225. [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"],
  226. [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"],
  227. [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"]
  228. );
  229. }
  230. if ($expectedOp === 'update') {
  231. $vCal = new VCalendar();
  232. $vCal->PRODID = '-//Nextcloud testing//mocked object//';
  233. $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal);
  234. $service->expects($this->exactly(3))->method('birthdayEvenChanged')->willReturn(true);
  235. $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(['calendardata' => '']);
  236. $this->calDav->expects($this->exactly(3))->method('updateCalendarObject')->withConsecutive(
  237. [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"],
  238. [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"],
  239. [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"]
  240. );
  241. }
  242. $service->onCardChanged(666, 'gump.vcf', '');
  243. }
  244. /**
  245. * @dataProvider providesBirthday
  246. * @param $expected
  247. * @param $old
  248. * @param $new
  249. */
  250. public function testBirthdayEvenChanged($expected, $old, $new): void {
  251. $new = Reader::read($new);
  252. $this->assertEquals($expected, $this->service->birthdayEvenChanged($old, $new));
  253. }
  254. public function testGetAllAffectedPrincipals(): void {
  255. $this->cardDav->expects($this->once())->method('getShares')->willReturn([
  256. [
  257. '{http://owncloud.org/ns}group-share' => false,
  258. '{http://owncloud.org/ns}principal' => 'principals/users/user01'
  259. ],
  260. [
  261. '{http://owncloud.org/ns}group-share' => false,
  262. '{http://owncloud.org/ns}principal' => 'principals/users/user01'
  263. ],
  264. [
  265. '{http://owncloud.org/ns}group-share' => false,
  266. '{http://owncloud.org/ns}principal' => 'principals/users/user02'
  267. ],
  268. [
  269. '{http://owncloud.org/ns}group-share' => true,
  270. '{http://owncloud.org/ns}principal' => 'principals/groups/users'
  271. ],
  272. ]);
  273. $this->groupPrincipalBackend->expects($this->once())->method('getGroupMemberSet')
  274. ->willReturn([
  275. [
  276. 'uri' => 'principals/users/user01',
  277. ],
  278. [
  279. 'uri' => 'principals/users/user02',
  280. ],
  281. [
  282. 'uri' => 'principals/users/user03',
  283. ],
  284. ]);
  285. $users = $this->invokePrivate($this->service, 'getAllAffectedPrincipals', [6666]);
  286. $this->assertEquals([
  287. 'principals/users/user01',
  288. 'principals/users/user02',
  289. 'principals/users/user03'
  290. ], $users);
  291. }
  292. public function testBirthdayCalendarHasComponentEvent(): void {
  293. $this->calDav->expects($this->once())
  294. ->method('createCalendar')
  295. ->with('principal001', 'contact_birthdays', [
  296. '{DAV:}displayname' => 'Contact birthdays',
  297. '{http://apple.com/ns/ical/}calendar-color' => '#E9D859',
  298. 'components' => 'VEVENT',
  299. ]);
  300. $this->service->ensureCalendarExists('principal001');
  301. }
  302. public function testResetForUser(): void {
  303. $this->calDav->expects($this->once())
  304. ->method('getCalendarByUri')
  305. ->with('principals/users/user123', 'contact_birthdays')
  306. ->willReturn(['id' => 42]);
  307. $this->calDav->expects($this->once())
  308. ->method('getCalendarObjects')
  309. ->with(42, 0)
  310. ->willReturn([['uri' => '1.ics'], ['uri' => '2.ics'], ['uri' => '3.ics']]);
  311. $this->calDav->expects($this->exactly(3))
  312. ->method('deleteCalendarObject')
  313. ->withConsecutive(
  314. [42, '1.ics', 0],
  315. [42, '2.ics', 0],
  316. [42, '3.ics', 0],
  317. );
  318. $this->service->resetForUser('user123');
  319. }
  320. public function providesBirthday() {
  321. return [
  322. [true,
  323. '',
  324. "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"],
  325. [false,
  326. "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",
  327. "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"],
  328. [true,
  329. "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",
  330. "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"],
  331. [true,
  332. "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",
  333. "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"]
  334. ];
  335. }
  336. public function providesCardChanges() {
  337. return[
  338. ['delete'],
  339. ['create'],
  340. ['update']
  341. ];
  342. }
  343. public function providesVCards() {
  344. return [
  345. // $expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Byte, $configuredReminder
  346. [null, null, null, null, null, null, null, 'yasfewf', '', '', true, null],
  347. [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],
  348. [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],
  349. [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],
  350. ['🎂 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],
  351. ['🎂 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],
  352. ['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],
  353. ['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],
  354. ['💍 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],
  355. ['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],
  356. ['🎂 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],
  357. ['🎂 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],
  358. [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],
  359. [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],
  360. ['🎂 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],
  361. ['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],
  362. ['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],
  363. ['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],
  364. ['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],
  365. [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],
  366. [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],
  367. ['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],
  368. ['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'],
  369. ['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'],
  370. ['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'],
  371. [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],
  372. [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],
  373. [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],
  374. ['🎂 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],
  375. ];
  376. }
  377. }