Completed
Push — master ( ca4819...0ca6f7 )
by Joas
28:28 queued 14s
created
apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php 1 patch
Indentation   +2172 added lines, -2172 removed lines patch added patch discarded remove patch
@@ -24,2177 +24,2177 @@
 block discarded – undo
24 24
 use Test\TestCase;
25 25
 
26 26
 class IMipServiceTest extends TestCase {
27
-	private URLGenerator&MockObject $urlGenerator;
28
-	private IConfig&MockObject $config;
29
-	private IDBConnection&MockObject $db;
30
-	private ISecureRandom&MockObject $random;
31
-	private IFactory&MockObject $l10nFactory;
32
-	private IL10N&MockObject $l10n;
33
-	private ITimeFactory&MockObject $timeFactory;
34
-	private IMipService $service;
35
-
36
-
37
-	private VCalendar $vCalendar1a;
38
-	private VCalendar $vCalendar1b;
39
-	private VCalendar $vCalendar2;
40
-	private VCalendar $vCalendar3;
41
-	/** @var DateTime DateTime object that will be returned by DateTime() or DateTime('now') */
42
-	public static $datetimeNow;
43
-
44
-	protected function setUp(): void {
45
-		parent::setUp();
46
-
47
-		$this->urlGenerator = $this->createMock(URLGenerator::class);
48
-		$this->config = $this->createMock(IConfig::class);
49
-		$this->db = $this->createMock(IDBConnection::class);
50
-		$this->random = $this->createMock(ISecureRandom::class);
51
-		$this->l10nFactory = $this->createMock(IFactory::class);
52
-		$this->l10n = $this->createMock(IL10N::class);
53
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
54
-		$this->l10nFactory->expects(self::once())
55
-			->method('findGenericLanguage')
56
-			->willReturn('en');
57
-		$this->l10nFactory->expects(self::once())
58
-			->method('get')
59
-			->with('dav', 'en')
60
-			->willReturn($this->l10n);
61
-		$this->service = new IMipService(
62
-			$this->urlGenerator,
63
-			$this->config,
64
-			$this->db,
65
-			$this->random,
66
-			$this->l10nFactory,
67
-			$this->timeFactory
68
-		);
69
-
70
-		// construct calendar with a 1 hour event and same start/end time zones
71
-		$this->vCalendar1a = new VCalendar();
72
-		$vEvent = $this->vCalendar1a->add('VEVENT', []);
73
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
74
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
75
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
76
-		$vEvent->add('SUMMARY', 'Testing Event');
77
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
78
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
79
-			'CN' => 'Attendee One',
80
-			'CUTYPE' => 'INDIVIDUAL',
81
-			'PARTSTAT' => 'NEEDS-ACTION',
82
-			'ROLE' => 'REQ-PARTICIPANT',
83
-			'RSVP' => 'TRUE'
84
-		]);
85
-
86
-		// construct calendar with a 1 hour event and different start/end time zones
87
-		$this->vCalendar1b = new VCalendar();
88
-		$vEvent = $this->vCalendar1b->add('VEVENT', []);
89
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
90
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
91
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Vancouver']);
92
-		$vEvent->add('SUMMARY', 'Testing Event');
93
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
94
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
95
-			'CN' => 'Attendee One',
96
-			'CUTYPE' => 'INDIVIDUAL',
97
-			'PARTSTAT' => 'NEEDS-ACTION',
98
-			'ROLE' => 'REQ-PARTICIPANT',
99
-			'RSVP' => 'TRUE'
100
-		]);
101
-
102
-		// construct calendar with a full day event
103
-		$this->vCalendar2 = new VCalendar();
104
-		// time zone component
105
-		$vTimeZone = $this->vCalendar2->add('VTIMEZONE');
106
-		$vTimeZone->add('TZID', 'America/Toronto');
107
-		// event component
108
-		$vEvent = $this->vCalendar2->add('VEVENT', []);
109
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
110
-		$vEvent->add('DTSTART', '20240701');
111
-		$vEvent->add('DTEND', '20240702');
112
-		$vEvent->add('SUMMARY', 'Testing Event');
113
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
114
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
115
-			'CN' => 'Attendee One',
116
-			'CUTYPE' => 'INDIVIDUAL',
117
-			'PARTSTAT' => 'NEEDS-ACTION',
118
-			'ROLE' => 'REQ-PARTICIPANT',
119
-			'RSVP' => 'TRUE'
120
-		]);
121
-
122
-		// construct calendar with a multi day event
123
-		$this->vCalendar3 = new VCalendar();
124
-		// time zone component
125
-		$vTimeZone = $this->vCalendar3->add('VTIMEZONE');
126
-		$vTimeZone->add('TZID', 'America/Toronto');
127
-		// event component
128
-		$vEvent = $this->vCalendar3->add('VEVENT', []);
129
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
130
-		$vEvent->add('DTSTART', '20240701');
131
-		$vEvent->add('DTEND', '20240706');
132
-		$vEvent->add('SUMMARY', 'Testing Event');
133
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
134
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
135
-			'CN' => 'Attendee One',
136
-			'CUTYPE' => 'INDIVIDUAL',
137
-			'PARTSTAT' => 'NEEDS-ACTION',
138
-			'ROLE' => 'REQ-PARTICIPANT',
139
-			'RSVP' => 'TRUE'
140
-		]);
141
-	}
142
-
143
-	public function testGetFrom(): void {
144
-		$senderName = 'Detective McQueen';
145
-		$default = 'Twin Lakes Police Department - Darkside Division';
146
-		$expected = 'Detective McQueen via Twin Lakes Police Department - Darkside Division';
147
-
148
-		$this->l10n->expects(self::once())
149
-			->method('t')
150
-			->willReturn($expected);
151
-
152
-		$actual = $this->service->getFrom($senderName, $default);
153
-		$this->assertEquals($expected, $actual);
154
-	}
155
-
156
-	public function testBuildBodyDataCreated(): void {
157
-
158
-		// construct l10n return(s)
159
-		$this->l10n->method('l')->willReturnCallback(
160
-			function ($v1, $v2, $v3) {
161
-				return match (true) {
162
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
163
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
164
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
165
-				};
166
-			}
167
-		);
168
-		$this->l10n->method('n')->willReturnMap([
169
-			[
170
-				'In a day on %1$s between %2$s - %3$s',
171
-				'In %n days on %1$s between %2$s - %3$s',
172
-				1,
173
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
174
-				'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
175
-			]
176
-		]);
177
-		// construct time factory return(s)
178
-		$this->timeFactory->method('getDateTime')->willReturnCallback(
179
-			function ($v1, $v2) {
180
-				return match (true) {
181
-					$v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000'))
182
-				};
183
-			}
184
-		);
185
-		/** test singleton partial day event*/
186
-		$vCalendar = clone $this->vCalendar1a;
187
-		// construct event reader
188
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
189
-		// define expected output
190
-		$expected = [
191
-			'meeting_when' => $this->service->generateWhenString($eventReader),
192
-			'meeting_description' => '',
193
-			'meeting_title' => 'Testing Event',
194
-			'meeting_location' => '',
195
-			'meeting_url' => '',
196
-			'meeting_url_html' => '',
197
-		];
198
-		// generate actual output
199
-		$actual = $this->service->buildBodyData($vCalendar->VEVENT[0], null);
200
-		// test output
201
-		$this->assertEquals($expected, $actual);
202
-	}
203
-
204
-	public function testBuildBodyDataUpdate(): void {
205
-
206
-		// construct l10n return(s)
207
-		$this->l10n->method('l')->willReturnCallback(
208
-			function ($v1, $v2, $v3) {
209
-				return match (true) {
210
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
211
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
212
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
213
-				};
214
-			}
215
-		);
216
-		$this->l10n->method('n')->willReturnMap([
217
-			[
218
-				'In a day on %1$s between %2$s - %3$s',
219
-				'In %n days on %1$s between %2$s - %3$s',
220
-				1,
221
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
222
-				'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
223
-			]
224
-		]);
225
-		// construct time factory return(s)
226
-		$this->timeFactory->method('getDateTime')->willReturnCallback(
227
-			function ($v1, $v2) {
228
-				return match (true) {
229
-					$v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000'))
230
-				};
231
-			}
232
-		);
233
-		/** test singleton partial day event*/
234
-		$vCalendarNew = clone $this->vCalendar1a;
235
-		$vCalendarOld = clone $this->vCalendar1a;
236
-		// construct event reader
237
-		$eventReaderNew = new EventReader($vCalendarNew, $vCalendarNew->VEVENT[0]->UID->getValue());
238
-		// alter old event label/title
239
-		$vCalendarOld->VEVENT[0]->SUMMARY->setValue('Testing Singleton Event');
240
-		// define expected output
241
-		$expected = [
242
-			'meeting_when' => $this->service->generateWhenString($eventReaderNew),
243
-			'meeting_description' => '',
244
-			'meeting_title' => 'Testing Event',
245
-			'meeting_location' => '',
246
-			'meeting_url' => '',
247
-			'meeting_url_html' => '',
248
-			'meeting_when_html' => $this->service->generateWhenString($eventReaderNew),
249
-			'meeting_title_html' => sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", 'Testing Singleton Event', 'Testing Event'),
250
-			'meeting_description_html' => '',
251
-			'meeting_location_html' => ''
252
-		];
253
-		// generate actual output
254
-		$actual = $this->service->buildBodyData($vCalendarNew->VEVENT[0], $vCalendarOld->VEVENT[0]);
255
-		// test output
256
-		$this->assertEquals($expected, $actual);
257
-	}
258
-
259
-	public function testGetLastOccurrenceRRULE(): void {
260
-		$vCalendar = new VCalendar();
261
-		$vCalendar->add('VEVENT', [
262
-			'UID' => 'uid-1234',
263
-			'LAST-MODIFIED' => 123456,
264
-			'SEQUENCE' => 2,
265
-			'SUMMARY' => 'Fellowship meeting',
266
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
267
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
268
-		]);
269
-
270
-		$occurrence = $this->service->getLastOccurrence($vCalendar);
271
-		$this->assertEquals(1454284800, $occurrence);
272
-	}
273
-
274
-	public function testGetLastOccurrenceEndDate(): void {
275
-		$vCalendar = new VCalendar();
276
-		$vCalendar->add('VEVENT', [
277
-			'UID' => 'uid-1234',
278
-			'LAST-MODIFIED' => 123456,
279
-			'SEQUENCE' => 2,
280
-			'SUMMARY' => 'Fellowship meeting',
281
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
282
-			'DTEND' => new \DateTime('2017-01-01 00:00:00'),
283
-		]);
284
-
285
-		$occurrence = $this->service->getLastOccurrence($vCalendar);
286
-		$this->assertEquals(1483228800, $occurrence);
287
-	}
288
-
289
-	public function testGetLastOccurrenceDuration(): void {
290
-		$vCalendar = new VCalendar();
291
-		$vCalendar->add('VEVENT', [
292
-			'UID' => 'uid-1234',
293
-			'LAST-MODIFIED' => 123456,
294
-			'SEQUENCE' => 2,
295
-			'SUMMARY' => 'Fellowship meeting',
296
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
297
-			'DURATION' => 'P12W',
298
-		]);
299
-
300
-		$occurrence = $this->service->getLastOccurrence($vCalendar);
301
-		$this->assertEquals(1458864000, $occurrence);
302
-	}
303
-
304
-	public function testGetLastOccurrenceAllDay(): void {
305
-		$vCalendar = new VCalendar();
306
-		$vEvent = $vCalendar->add('VEVENT', [
307
-			'UID' => 'uid-1234',
308
-			'LAST-MODIFIED' => 123456,
309
-			'SEQUENCE' => 2,
310
-			'SUMMARY' => 'Fellowship meeting',
311
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
312
-		]);
313
-
314
-		// rewrite from DateTime to Date
315
-		$vEvent->DTSTART['VALUE'] = 'DATE';
316
-
317
-		$occurrence = $this->service->getLastOccurrence($vCalendar);
318
-		$this->assertEquals(1451692800, $occurrence);
319
-	}
320
-
321
-	public function testGetLastOccurrenceFallback(): void {
322
-		$vCalendar = new VCalendar();
323
-		$vCalendar->add('VEVENT', [
324
-			'UID' => 'uid-1234',
325
-			'LAST-MODIFIED' => 123456,
326
-			'SEQUENCE' => 2,
327
-			'SUMMARY' => 'Fellowship meeting',
328
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
329
-		]);
330
-
331
-		$occurrence = $this->service->getLastOccurrence($vCalendar);
332
-		$this->assertEquals(1451606400, $occurrence);
333
-	}
334
-
335
-	public function testGenerateWhenStringSingular(): void {
336
-
337
-		// construct l10n return(s)
338
-		$this->l10n->method('l')->willReturnCallback(
339
-			function ($v1, $v2, $v3) {
340
-				return match (true) {
341
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
342
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
343
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024',
344
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T000000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
345
-				};
346
-			}
347
-		);
348
-		$this->l10n->method('t')->willReturnMap([
349
-			[
350
-				'In the past on %1$s for the entire day',
351
-				['July 1, 2024'],
352
-				'In the past on July 1, 2024 for the entire day'
353
-			],
354
-			[
355
-				'In the past on %1$s between %2$s - %3$s',
356
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
357
-				'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
358
-			],
359
-		]);
360
-		$this->l10n->method('n')->willReturnMap([
361
-			// singular entire day
362
-			[
363
-				'In a minute on %1$s for the entire day',
364
-				'In %n minutes on %1$s for the entire day',
365
-				1,
366
-				['July 1, 2024'],
367
-				'In a minute on July 1, 2024 for the entire day'
368
-			],
369
-			[
370
-				'In a hour on %1$s for the entire day',
371
-				'In %n hours on %1$s for the entire day',
372
-				1,
373
-				['July 1, 2024'],
374
-				'In a hour on July 1, 2024 for the entire day'
375
-			],
376
-			[
377
-				'In a day on %1$s for the entire day',
378
-				'In %n days on %1$s for the entire day',
379
-				1,
380
-				['July 1, 2024'],
381
-				'In a day on July 1, 2024 for the entire day'
382
-			],
383
-			[
384
-				'In a week on %1$s for the entire day',
385
-				'In %n weeks on %1$s for the entire day',
386
-				1,
387
-				['July 1, 2024'],
388
-				'In a week on July 1, 2024 for the entire day'
389
-			],
390
-			[
391
-				'In a month on %1$s for the entire day',
392
-				'In %n months on %1$s for the entire day',
393
-				1,
394
-				['July 1, 2024'],
395
-				'In a month on July 1, 2024 for the entire day'
396
-			],
397
-			[
398
-				'In a year on %1$s for the entire day',
399
-				'In %n years on %1$s for the entire day',
400
-				1,
401
-				['July 1, 2024'],
402
-				'In a year on July 1, 2024 for the entire day'
403
-			],
404
-			// plural entire day
405
-			[
406
-				'In a minute on %1$s for the entire day',
407
-				'In %n minutes on %1$s for the entire day',
408
-				2,
409
-				['July 1, 2024'],
410
-				'In 2 minutes on July 1, 2024 for the entire day'
411
-			],
412
-			[
413
-				'In a hour on %1$s for the entire day',
414
-				'In %n hours on %1$s for the entire day',
415
-				2,
416
-				['July 1, 2024'],
417
-				'In 2 hours on July 1, 2024 for the entire day'
418
-			],
419
-			[
420
-				'In a day on %1$s for the entire day',
421
-				'In %n days on %1$s for the entire day',
422
-				2,
423
-				['July 1, 2024'],
424
-				'In 2 days on July 1, 2024 for the entire day'
425
-			],
426
-			[
427
-				'In a week on %1$s for the entire day',
428
-				'In %n weeks on %1$s for the entire day',
429
-				2,
430
-				['July 1, 2024'],
431
-				'In 2 weeks on July 1, 2024 for the entire day'
432
-			],
433
-			[
434
-				'In a month on %1$s for the entire day',
435
-				'In %n months on %1$s for the entire day',
436
-				2,
437
-				['July 1, 2024'],
438
-				'In 2 months on July 1, 2024 for the entire day'
439
-			],
440
-			[
441
-				'In a year on %1$s for the entire day',
442
-				'In %n years on %1$s for the entire day',
443
-				2,
444
-				['July 1, 2024'],
445
-				'In 2 years on July 1, 2024 for the entire day'
446
-			],
447
-			// singular partial day
448
-			[
449
-				'In a minute on %1$s between %2$s - %3$s',
450
-				'In %n minutes on %1$s between %2$s - %3$s',
451
-				1,
452
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
453
-				'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
454
-			],
455
-			[
456
-				'In a hour on %1$s between %2$s - %3$s',
457
-				'In %n hours on %1$s between %2$s - %3$s',
458
-				1,
459
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
460
-				'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
461
-			],
462
-			[
463
-				'In a day on %1$s between %2$s - %3$s',
464
-				'In %n days on %1$s between %2$s - %3$s',
465
-				1,
466
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
467
-				'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
468
-			],
469
-			[
470
-				'In a week on %1$s between %2$s - %3$s',
471
-				'In %n weeks on %1$s between %2$s - %3$s',
472
-				1,
473
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
474
-				'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
475
-			],
476
-			[
477
-				'In a month on %1$s between %2$s - %3$s',
478
-				'In %n months on %1$s between %2$s - %3$s',
479
-				1,
480
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
481
-				'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
482
-			],
483
-			[
484
-				'In a year on %1$s between %2$s - %3$s',
485
-				'In %n years on %1$s between %2$s - %3$s',
486
-				1,
487
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
488
-				'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
489
-			],
490
-			// plural partial day
491
-			[
492
-				'In a minute on %1$s between %2$s - %3$s',
493
-				'In %n minutes on %1$s between %2$s - %3$s',
494
-				2,
495
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
496
-				'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
497
-			],
498
-			[
499
-				'In a hour on %1$s between %2$s - %3$s',
500
-				'In %n hours on %1$s between %2$s - %3$s',
501
-				2,
502
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
503
-				'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
504
-			],
505
-			[
506
-				'In a day on %1$s between %2$s - %3$s',
507
-				'In %n days on %1$s between %2$s - %3$s',
508
-				2,
509
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
510
-				'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
511
-			],
512
-			[
513
-				'In a week on %1$s between %2$s - %3$s',
514
-				'In %n weeks on %1$s between %2$s - %3$s',
515
-				2,
516
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
517
-				'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
518
-			],
519
-			[
520
-				'In a month on %1$s between %2$s - %3$s',
521
-				'In %n months on %1$s between %2$s - %3$s',
522
-				2,
523
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
524
-				'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
525
-			],
526
-			[
527
-				'In a year on %1$s between %2$s - %3$s',
528
-				'In %n years on %1$s between %2$s - %3$s',
529
-				2,
530
-				['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
531
-				'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
532
-			],
533
-		]);
534
-
535
-		// construct time factory return(s)
536
-		$this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
537
-			// past interval test dates
538
-			(new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))),
539
-			(new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))),
540
-			(new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))),
541
-			(new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))),
542
-			// minute interval test dates
543
-			(new \DateTime('20240701T075900', (new \DateTimeZone('America/Toronto')))),
544
-			(new \DateTime('20240630T235900', (new \DateTimeZone('America/Toronto')))),
545
-			(new \DateTime('20240701T075800', (new \DateTimeZone('America/Toronto')))),
546
-			(new \DateTime('20240630T235800', (new \DateTimeZone('America/Toronto')))),
547
-			// hour interval test dates
548
-			(new \DateTime('20240701T070000', (new \DateTimeZone('America/Toronto')))),
549
-			(new \DateTime('20240630T230000', (new \DateTimeZone('America/Toronto')))),
550
-			(new \DateTime('20240701T060000', (new \DateTimeZone('America/Toronto')))),
551
-			(new \DateTime('20240630T220000', (new \DateTimeZone('America/Toronto')))),
552
-			// day interval test dates
553
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
554
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
555
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
556
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
557
-			// week interval test dates
558
-			(new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))),
559
-			(new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))),
560
-			(new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))),
561
-			(new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))),
562
-			// month interval test dates
563
-			(new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))),
564
-			(new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))),
565
-			(new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))),
566
-			(new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))),
567
-			// year interval test dates
568
-			(new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))),
569
-			(new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))),
570
-			(new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto')))),
571
-			(new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto'))))
572
-		);
573
-
574
-		/** test partial day event in 1 day in the past*/
575
-		$vCalendar = clone $this->vCalendar1a;
576
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
577
-		$this->assertEquals(
578
-			'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
579
-			$this->service->generateWhenString($eventReader)
580
-		);
581
-
582
-		/** test entire day event in 1 day in the past*/
583
-		$vCalendar = clone $this->vCalendar2;
584
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
585
-		$this->assertEquals(
586
-			'In the past on July 1, 2024 for the entire day',
587
-			$this->service->generateWhenString($eventReader)
588
-		);
589
-
590
-		/** test partial day event in 2 days in the past*/
591
-		$vCalendar = clone $this->vCalendar1a;
592
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
593
-		$this->assertEquals(
594
-			'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
595
-			$this->service->generateWhenString($eventReader)
596
-		);
597
-
598
-		/** test entire day event in 2 days in the past*/
599
-		$vCalendar = clone $this->vCalendar2;
600
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
601
-		$this->assertEquals(
602
-			'In the past on July 1, 2024 for the entire day',
603
-			$this->service->generateWhenString($eventReader)
604
-		);
605
-
606
-		/** test partial day event in 1 minute*/
607
-		$vCalendar = clone $this->vCalendar1a;
608
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
609
-		$this->assertEquals(
610
-			'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
611
-			$this->service->generateWhenString($eventReader)
612
-		);
613
-
614
-		/** test entire day event in 1 minute*/
615
-		$vCalendar = clone $this->vCalendar2;
616
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
617
-		$this->assertEquals(
618
-			'In a minute on July 1, 2024 for the entire day',
619
-			$this->service->generateWhenString($eventReader)
620
-		);
621
-
622
-		/** test partial day event in 2 minutes*/
623
-		$vCalendar = clone $this->vCalendar1a;
624
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
625
-		$this->assertEquals(
626
-			'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
627
-			$this->service->generateWhenString($eventReader)
628
-		);
629
-
630
-		/** test entire day event in 2 minutes*/
631
-		$vCalendar = clone $this->vCalendar2;
632
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
633
-		$this->assertEquals(
634
-			'In 2 minutes on July 1, 2024 for the entire day',
635
-			$this->service->generateWhenString($eventReader)
636
-		);
637
-
638
-		/** test partial day event in 1 hour*/
639
-		$vCalendar = clone $this->vCalendar1a;
640
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
641
-		$this->assertEquals(
642
-			'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
643
-			$this->service->generateWhenString($eventReader)
644
-		);
645
-
646
-		/** test entire day event in 1 hour*/
647
-		$vCalendar = clone $this->vCalendar2;
648
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
649
-		$this->assertEquals(
650
-			'In a hour on July 1, 2024 for the entire day',
651
-			$this->service->generateWhenString($eventReader)
652
-		);
653
-
654
-		/** test partial day event in 2 hours*/
655
-		$vCalendar = clone $this->vCalendar1a;
656
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
657
-		$this->assertEquals(
658
-			'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
659
-			$this->service->generateWhenString($eventReader)
660
-		);
661
-
662
-		/** test entire day event in 2 hours*/
663
-		$vCalendar = clone $this->vCalendar2;
664
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
665
-		$this->assertEquals(
666
-			'In 2 hours on July 1, 2024 for the entire day',
667
-			$this->service->generateWhenString($eventReader)
668
-		);
669
-
670
-		/** test patrial day event in 1 day*/
671
-		$vCalendar = clone $this->vCalendar1a;
672
-		// construct event reader
673
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
674
-		// test output
675
-		$this->assertEquals(
676
-			'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
677
-			$this->service->generateWhenString($eventReader)
678
-		);
679
-
680
-		/** test entire day event in 1 day*/
681
-		$vCalendar = clone $this->vCalendar2;
682
-		// construct event reader
683
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
684
-		// test output
685
-		$this->assertEquals(
686
-			'In a day on July 1, 2024 for the entire day',
687
-			$this->service->generateWhenString($eventReader)
688
-		);
689
-
690
-		/** test patrial day event in 2 days*/
691
-		$vCalendar = clone $this->vCalendar1a;
692
-		// construct event reader
693
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
694
-		// test output
695
-		$this->assertEquals(
696
-			'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
697
-			$this->service->generateWhenString($eventReader)
698
-		);
699
-
700
-		/** test entire day event in 2 days*/
701
-		$vCalendar = clone $this->vCalendar2;
702
-		// construct event reader
703
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
704
-		// test output
705
-		$this->assertEquals(
706
-			'In 2 days on July 1, 2024 for the entire day',
707
-			$this->service->generateWhenString($eventReader)
708
-		);
709
-
710
-		/** test patrial day event in 1 week*/
711
-		$vCalendar = clone $this->vCalendar1a;
712
-		// construct event reader
713
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
714
-		// test output
715
-		$this->assertEquals(
716
-			'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
717
-			$this->service->generateWhenString($eventReader)
718
-		);
719
-
720
-		/** test entire day event in 1 week*/
721
-		$vCalendar = clone $this->vCalendar2;
722
-		// construct event reader
723
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
724
-		// test output
725
-		$this->assertEquals(
726
-			'In a week on July 1, 2024 for the entire day',
727
-			$this->service->generateWhenString($eventReader)
728
-		);
729
-
730
-		/** test patrial day event in 2 weeks*/
731
-		$vCalendar = clone $this->vCalendar1a;
732
-		// construct event reader
733
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
734
-		// test output
735
-		$this->assertEquals(
736
-			'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
737
-			$this->service->generateWhenString($eventReader)
738
-		);
739
-
740
-		/** test entire day event in 2 weeks*/
741
-		$vCalendar = clone $this->vCalendar2;
742
-		// construct event reader
743
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
744
-		// test output
745
-		$this->assertEquals(
746
-			'In 2 weeks on July 1, 2024 for the entire day',
747
-			$this->service->generateWhenString($eventReader)
748
-		);
749
-
750
-		/** test patrial day event in 1 month*/
751
-		$vCalendar = clone $this->vCalendar1a;
752
-		// construct event reader
753
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
754
-		// test output
755
-		$this->assertEquals(
756
-			'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
757
-			$this->service->generateWhenString($eventReader)
758
-		);
759
-
760
-		/** test entire day event in 1 month*/
761
-		$vCalendar = clone $this->vCalendar2;
762
-		// construct event reader
763
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
764
-		// test output
765
-		$this->assertEquals(
766
-			'In a month on July 1, 2024 for the entire day',
767
-			$this->service->generateWhenString($eventReader)
768
-		);
769
-
770
-		/** test patrial day event in 2 months*/
771
-		$vCalendar = clone $this->vCalendar1a;
772
-		// construct event reader
773
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
774
-		// test output
775
-		$this->assertEquals(
776
-			'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
777
-			$this->service->generateWhenString($eventReader)
778
-		);
779
-
780
-		/** test entire day event in 2 months*/
781
-		$vCalendar = clone $this->vCalendar2;
782
-		// construct event reader
783
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
784
-		// test output
785
-		$this->assertEquals(
786
-			'In 2 months on July 1, 2024 for the entire day',
787
-			$this->service->generateWhenString($eventReader)
788
-		);
789
-
790
-		/** test patrial day event in 1 year*/
791
-		$vCalendar = clone $this->vCalendar1a;
792
-		// construct event reader
793
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
794
-		// test output
795
-		$this->assertEquals(
796
-			'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
797
-			$this->service->generateWhenString($eventReader)
798
-		);
799
-
800
-		/** test entire day event in 1 year*/
801
-		$vCalendar = clone $this->vCalendar2;
802
-		// construct event reader
803
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
804
-		// test output
805
-		$this->assertEquals(
806
-			'In a year on July 1, 2024 for the entire day',
807
-			$this->service->generateWhenString($eventReader)
808
-		);
809
-
810
-		/** test patrial day event in 2 years*/
811
-		$vCalendar = clone $this->vCalendar1a;
812
-		// construct event reader
813
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
814
-		// test output
815
-		$this->assertEquals(
816
-			'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
817
-			$this->service->generateWhenString($eventReader)
818
-		);
819
-
820
-		/** test entire day event in 2 years*/
821
-		$vCalendar = clone $this->vCalendar2;
822
-		// construct event reader
823
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
824
-		// test output
825
-		$this->assertEquals(
826
-			'In 2 years on July 1, 2024 for the entire day',
827
-			$this->service->generateWhenString($eventReader)
828
-		);
829
-
830
-	}
831
-
832
-	public function testGenerateWhenStringRecurringDaily(): void {
833
-
834
-		// construct l10n return maps
835
-		$this->l10n->method('l')->willReturnCallback(
836
-			function ($v1, $v2, $v3) {
837
-				return match (true) {
838
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
839
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
840
-					$v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
841
-				};
842
-			}
843
-		);
844
-		$this->l10n->method('t')->willReturnMap([
845
-			['Every Day for the entire day', [], 'Every Day for the entire day'],
846
-			['Every Day for the entire day until %1$s', ['July 13, 2024'], 'Every Day for the entire day until July 13, 2024'],
847
-			['Every Day between %1$s - %2$s', ['8:00 AM', '9:00 AM (America/Toronto)'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto)'],
848
-			['Every Day between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
849
-			['Every %1$d Days for the entire day', [3], 'Every 3 Days for the entire day'],
850
-			['Every %1$d Days for the entire day until %2$s', [3, 'July 13, 2024'], 'Every 3 Days for the entire day until July 13, 2024'],
851
-			['Every %1$d Days between %2$s - %3$s', [3, '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)'],
852
-			['Every %1$d Days between %2$s - %3$s until %4$s', [3, '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
853
-			['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
854
-		]);
855
-
856
-		/** test partial day event with every day interval and no conclusion*/
857
-		$vCalendar = clone $this->vCalendar1a;
858
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;');
859
-		// construct event reader
860
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
861
-		// test output
862
-		$this->assertEquals(
863
-			'Every Day between 8:00 AM - 9:00 AM (America/Toronto)',
864
-			$this->service->generateWhenString($eventReader)
865
-		);
866
-
867
-		/** test partial day event with every day interval and conclusion*/
868
-		$vCalendar = clone $this->vCalendar1a;
869
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z');
870
-		// construct event reader
871
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
872
-		// test output
873
-		$this->assertEquals(
874
-			'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
875
-			$this->service->generateWhenString($eventReader)
876
-		);
877
-
878
-		/** test partial day event every 3rd day interval and no conclusion*/
879
-		$vCalendar = clone $this->vCalendar1a;
880
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;');
881
-		// construct event reader
882
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
883
-		// test output
884
-		$this->assertEquals(
885
-			'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)',
886
-			$this->service->generateWhenString($eventReader)
887
-		);
888
-
889
-		/** test partial day event with every 3rd day interval and conclusion*/
890
-		$vCalendar = clone $this->vCalendar1a;
891
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z');
892
-		// construct event reader
893
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
894
-		// test output
895
-		$this->assertEquals(
896
-			'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
897
-			$this->service->generateWhenString($eventReader)
898
-		);
899
-
900
-		/** test entire day event with every day interval and no conclusion*/
901
-		$vCalendar = clone $this->vCalendar2;
902
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;');
903
-		// construct event reader
904
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
905
-		// test output
906
-		$this->assertEquals(
907
-			'Every Day for the entire day',
908
-			$this->service->generateWhenString($eventReader)
909
-		);
910
-
911
-		/** test entire day event with every day interval and conclusion*/
912
-		$vCalendar = clone $this->vCalendar2;
913
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z');
914
-		// construct event reader
915
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
916
-		// test output
917
-		$this->assertEquals(
918
-			'Every Day for the entire day until July 13, 2024',
919
-			$this->service->generateWhenString($eventReader)
920
-		);
921
-
922
-		/** test entire day event with every 3rd day interval and no conclusion*/
923
-		$vCalendar = clone $this->vCalendar2;
924
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;');
925
-		// construct event reader
926
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
927
-		// test output
928
-		$this->assertEquals(
929
-			'Every 3 Days for the entire day',
930
-			$this->service->generateWhenString($eventReader)
931
-		);
932
-
933
-		/** test entire day event with every 3rd day interval and conclusion*/
934
-		$vCalendar = clone $this->vCalendar2;
935
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z');
936
-		// construct event reader
937
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
938
-		// test output
939
-		$this->assertEquals(
940
-			'Every 3 Days for the entire day until July 13, 2024',
941
-			$this->service->generateWhenString($eventReader)
942
-		);
943
-
944
-	}
945
-
946
-	public function testGenerateWhenStringRecurringWeekly(): void {
947
-
948
-		// construct l10n return maps
949
-		$this->l10n->method('l')->willReturnCallback(
950
-			function ($v1, $v2, $v3) {
951
-				return match (true) {
952
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
953
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
954
-					$v1 === 'date' && $v2 == (new \DateTime('20240722T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
955
-				};
956
-			}
957
-		);
958
-		$this->l10n->method('t')->willReturnMap([
959
-			['Every Week on %1$s for the entire day', ['Monday, Wednesday, Friday'], 'Every Week on Monday, Wednesday, Friday for the entire day'],
960
-			['Every Week on %1$s for the entire day until %2$s', ['Monday, Wednesday, Friday', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024'],
961
-			['Every Week on %1$s between %2$s - %3$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'],
962
-			['Every Week on %1$s between %2$s - %3$s until %4$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
963
-			['Every %1$d Weeks on %2$s for the entire day', [2, 'Monday, Wednesday, Friday'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day'],
964
-			['Every %1$d Weeks on %2$s for the entire day until %3$s', [2, 'Monday, Wednesday, Friday', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024'],
965
-			['Every %1$d Weeks on %2$s between %3$s - %4$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'],
966
-			['Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
967
-			['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
968
-			['Monday', [], 'Monday'],
969
-			['Wednesday', [], 'Wednesday'],
970
-			['Friday', [], 'Friday'],
971
-		]);
972
-
973
-		/** test partial day event with every week interval on Mon, Wed, Fri and no conclusion*/
974
-		$vCalendar = clone $this->vCalendar1a;
975
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR');
976
-		// construct event reader
977
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
978
-		// test output
979
-		$this->assertEquals(
980
-			'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)',
981
-			$this->service->generateWhenString($eventReader)
982
-		);
983
-
984
-		/** test partial day event with every week interval on Mon, Wed, Fri and conclusion*/
985
-		$vCalendar = clone $this->vCalendar1a;
986
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;');
987
-		// construct event reader
988
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
989
-		// test output
990
-		$this->assertEquals(
991
-			'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
992
-			$this->service->generateWhenString($eventReader)
993
-		);
994
-
995
-		/** test partial day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/
996
-		$vCalendar = clone $this->vCalendar1a;
997
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;');
998
-		// construct event reader
999
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1000
-		// test output
1001
-		$this->assertEquals(
1002
-			'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)',
1003
-			$this->service->generateWhenString($eventReader)
1004
-		);
1005
-
1006
-		/** test partial day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/
1007
-		$vCalendar = clone $this->vCalendar1a;
1008
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;');
1009
-		// construct event reader
1010
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1011
-		// test output
1012
-		$this->assertEquals(
1013
-			'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
1014
-			$this->service->generateWhenString($eventReader)
1015
-		);
1016
-
1017
-		/** test entire day event with every week interval on Mon, Wed, Fri and no conclusion*/
1018
-		$vCalendar = clone $this->vCalendar2;
1019
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;');
1020
-		// construct event reader
1021
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1022
-		// test output
1023
-		$this->assertEquals(
1024
-			'Every Week on Monday, Wednesday, Friday for the entire day',
1025
-			$this->service->generateWhenString($eventReader)
1026
-		);
1027
-
1028
-		/** test entire day event with every week interval on Mon, Wed, Fri and conclusion*/
1029
-		$vCalendar = clone $this->vCalendar2;
1030
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;');
1031
-		// construct event reader
1032
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1033
-		// test output
1034
-		$this->assertEquals(
1035
-			'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024',
1036
-			$this->service->generateWhenString($eventReader)
1037
-		);
1038
-
1039
-		/** test entire day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/
1040
-		$vCalendar = clone $this->vCalendar2;
1041
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;');
1042
-		// construct event reader
1043
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1044
-		// test output
1045
-		$this->assertEquals(
1046
-			'Every 2 Weeks on Monday, Wednesday, Friday for the entire day',
1047
-			$this->service->generateWhenString($eventReader)
1048
-		);
1049
-
1050
-		/** test entire day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/
1051
-		$vCalendar = clone $this->vCalendar2;
1052
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;');
1053
-		// construct event reader
1054
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1055
-		// test output
1056
-		$this->assertEquals(
1057
-			'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024',
1058
-			$this->service->generateWhenString($eventReader)
1059
-		);
1060
-
1061
-	}
1062
-
1063
-	public function testGenerateWhenStringRecurringMonthly(): void {
1064
-
1065
-		// construct l10n return maps
1066
-		$this->l10n->method('l')->willReturnCallback(
1067
-			function ($v1, $v2, $v3) {
1068
-				return match (true) {
1069
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1070
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1071
-					$v1 === 'date' && $v2 == (new \DateTime('20241231T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'December 31, 2024'
1072
-				};
1073
-			}
1074
-		);
1075
-		$this->l10n->method('t')->willReturnMap([
1076
-			['Every Month on the %1$s for the entire day', ['1, 8'], 'Every Month on the 1, 8 for the entire day'],
1077
-			['Every Month on the %1$s for the entire day until %2$s', ['1, 8', 'December 31, 2024'], 'Every Month on the 1, 8 for the entire day until December 31, 2024'],
1078
-			['Every Month on the %1$s between %2$s - %3$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'],
1079
-			['Every Month on the %1$s between %2$s - %3$s until %4$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1080
-			['Every %1$d Months on the %2$s for the entire day', [2, '1, 8'], 'Every 2 Months on the 1, 8 for the entire day'],
1081
-			['Every %1$d Months on the %2$s for the entire day until %3$s', [2, '1, 8', 'December 31, 2024'], 'Every 2 Months on the 1, 8 for the entire day until December 31, 2024'],
1082
-			['Every %1$d Months on the %2$s between %3$s - %4$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'],
1083
-			['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1084
-			['Every Month on the %1$s for the entire day', ['First Sunday, Saturday'], 'Every Month on the First Sunday, Saturday for the entire day'],
1085
-			['Every Month on the %1$s for the entire day until %2$s', ['First Sunday, Saturday', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024'],
1086
-			['Every Month on the %1$s between %2$s - %3$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1087
-			['Every Month on the %1$s between %2$s - %3$s until %4$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1088
-			['Every %1$d Months on the %2$s for the entire day', [2, 'First Sunday, Saturday'], 'Every 2 Months on the First Sunday, Saturday for the entire day'],
1089
-			['Every %1$d Months on the %2$s for the entire day until %3$s', [2, 'First Sunday, Saturday', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024'],
1090
-			['Every %1$d Months on the %2$s between %3$s - %4$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1091
-			['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1092
-			['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
1093
-			['Saturday', [], 'Saturday'],
1094
-			['Sunday', [], 'Sunday'],
1095
-			['First', [], 'First'],
1096
-		]);
1097
-
1098
-		/** test absolute partial day event with every month interval on 1st, 8th and no conclusion*/
1099
-		$vCalendar = clone $this->vCalendar1a;
1100
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;');
1101
-		// construct event reader
1102
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1103
-		// test output
1104
-		$this->assertEquals(
1105
-			'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)',
1106
-			$this->service->generateWhenString($eventReader)
1107
-		);
1108
-
1109
-		/** test absolute partial day event with every Month interval on 1st, 8th and conclusion*/
1110
-		$vCalendar = clone $this->vCalendar1a;
1111
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;');
1112
-		// construct event reader
1113
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1114
-		// test output
1115
-		$this->assertEquals(
1116
-			'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1117
-			$this->service->generateWhenString($eventReader)
1118
-		);
1119
-
1120
-		/** test absolute partial day event with every 2nd Month interval on 1st, 8th and no conclusion*/
1121
-		$vCalendar = clone $this->vCalendar1a;
1122
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;');
1123
-		// construct event reader
1124
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1125
-		// test output
1126
-		$this->assertEquals(
1127
-			'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)',
1128
-			$this->service->generateWhenString($eventReader)
1129
-		);
1130
-
1131
-		/** test absolute partial day event with every 2nd Month interval on 1st, 8th and conclusion*/
1132
-		$vCalendar = clone $this->vCalendar1a;
1133
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;');
1134
-		// construct event reader
1135
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1136
-		// test output
1137
-		$this->assertEquals(
1138
-			'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1139
-			$this->service->generateWhenString($eventReader)
1140
-		);
1141
-
1142
-		/** test absolute entire day event with every Month interval on 1st, 8th and no conclusion*/
1143
-		$vCalendar = clone $this->vCalendar2;
1144
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;');
1145
-		// construct event reader
1146
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1147
-		// test output
1148
-		$this->assertEquals(
1149
-			'Every Month on the 1, 8 for the entire day',
1150
-			$this->service->generateWhenString($eventReader)
1151
-		);
1152
-
1153
-		/** test absolute entire day event with every Month interval on 1st, 8th and conclusion*/
1154
-		$vCalendar = clone $this->vCalendar2;
1155
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;');
1156
-		// construct event reader
1157
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1158
-		// test output
1159
-		$this->assertEquals(
1160
-			'Every Month on the 1, 8 for the entire day until December 31, 2024',
1161
-			$this->service->generateWhenString($eventReader)
1162
-		);
1163
-
1164
-		/** test absolute entire day event with every 2nd Month interval on 1st, 8th and no conclusion*/
1165
-		$vCalendar = clone $this->vCalendar2;
1166
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;');
1167
-		// construct event reader
1168
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1169
-		// test output
1170
-		$this->assertEquals(
1171
-			'Every 2 Months on the 1, 8 for the entire day',
1172
-			$this->service->generateWhenString($eventReader)
1173
-		);
1174
-
1175
-		/** test absolute entire day event with every 2nd Month interval on 1st, 8th and conclusion*/
1176
-		$vCalendar = clone $this->vCalendar2;
1177
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;');
1178
-		// construct event reader
1179
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1180
-		// test output
1181
-		$this->assertEquals(
1182
-			'Every 2 Months on the 1, 8 for the entire day until December 31, 2024',
1183
-			$this->service->generateWhenString($eventReader)
1184
-		);
1185
-
1186
-		/** test relative partial day event with every month interval on the 1st Saturday, Sunday and no conclusion*/
1187
-		$vCalendar = clone $this->vCalendar1a;
1188
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;');
1189
-		// construct event reader
1190
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1191
-		// test output
1192
-		$this->assertEquals(
1193
-			'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1194
-			$this->service->generateWhenString($eventReader)
1195
-		);
1196
-
1197
-		/** test relative partial day event with every Month interval on the 1st Saturday, Sunday and conclusion*/
1198
-		$vCalendar = clone $this->vCalendar1a;
1199
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;');
1200
-		// construct event reader
1201
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1202
-		// test output
1203
-		$this->assertEquals(
1204
-			'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1205
-			$this->service->generateWhenString($eventReader)
1206
-		);
1207
-
1208
-		/** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/
1209
-		$vCalendar = clone $this->vCalendar1a;
1210
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1211
-		// construct event reader
1212
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1213
-		// test output
1214
-		$this->assertEquals(
1215
-			'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1216
-			$this->service->generateWhenString($eventReader)
1217
-		);
1218
-
1219
-		/** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/
1220
-		$vCalendar = clone $this->vCalendar1a;
1221
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;');
1222
-		// construct event reader
1223
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1224
-		// test output
1225
-		$this->assertEquals(
1226
-			'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1227
-			$this->service->generateWhenString($eventReader)
1228
-		);
1229
-
1230
-		/** test relative entire day event with every Month interval on the 1st Saturday, Sunday and no conclusion*/
1231
-		$vCalendar = clone $this->vCalendar2;
1232
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;');
1233
-		// construct event reader
1234
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1235
-		// test output
1236
-		$this->assertEquals(
1237
-			'Every Month on the First Sunday, Saturday for the entire day',
1238
-			$this->service->generateWhenString($eventReader)
1239
-		);
1240
-
1241
-		/** test relative entire day event with every Month interval on the 1st Saturday, Sunday and conclusion*/
1242
-		$vCalendar = clone $this->vCalendar2;
1243
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;');
1244
-		// construct event reader
1245
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1246
-		// test output
1247
-		$this->assertEquals(
1248
-			'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024',
1249
-			$this->service->generateWhenString($eventReader)
1250
-		);
1251
-
1252
-		/** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/
1253
-		$vCalendar = clone $this->vCalendar2;
1254
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1255
-		// construct event reader
1256
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1257
-		// test output
1258
-		$this->assertEquals(
1259
-			'Every 2 Months on the First Sunday, Saturday for the entire day',
1260
-			$this->service->generateWhenString($eventReader)
1261
-		);
1262
-
1263
-		/** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/
1264
-		$vCalendar = clone $this->vCalendar2;
1265
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;');
1266
-		// construct event reader
1267
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1268
-		// test output
1269
-		$this->assertEquals(
1270
-			'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024',
1271
-			$this->service->generateWhenString($eventReader)
1272
-		);
1273
-
1274
-	}
1275
-
1276
-	public function testGenerateWhenStringRecurringYearly(): void {
1277
-
1278
-		// construct l10n return maps
1279
-		$this->l10n->method('l')->willReturnCallback(
1280
-			function ($v1, $v2, $v3) {
1281
-				return match (true) {
1282
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1283
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1284
-					$v1 === 'date' && $v2 == (new \DateTime('20260731T040000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 31, 2026'
1285
-				};
1286
-			}
1287
-		);
1288
-		$this->l10n->method('t')->willReturnMap([
1289
-			['Every Year in %1$s on the %2$s for the entire day', ['July', '1st'], 'Every Year in July on the 1st for the entire day'],
1290
-			['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', '1st', 'July 31, 2026'], 'Every Year in July on the 1st for the entire day until July 31, 2026'],
1291
-			['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'],
1292
-			['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1293
-			['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', '1st'], 'Every 2 Years in July on the 1st for the entire day'],
1294
-			['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', '1st', 'July 31, 2026'], 'Every 2 Years in July on the 1st for the entire day until July 31, 2026'],
1295
-			['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'],
1296
-			['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1297
-			['Every Year in %1$s on the %2$s for the entire day', ['July', 'First Sunday, Saturday'], 'Every Year in July on the First Sunday, Saturday for the entire day'],
1298
-			['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026'],
1299
-			['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1300
-			['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1301
-			['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', 'First Sunday, Saturday'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day'],
1302
-			['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026'],
1303
-			['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1304
-			['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1305
-			['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
1306
-			['July', [], 'July'],
1307
-			['Saturday', [], 'Saturday'],
1308
-			['Sunday', [], 'Sunday'],
1309
-			['First', [], 'First'],
1310
-		]);
1311
-
1312
-		/** test absolute partial day event with every year interval on July 1 and no conclusion*/
1313
-		$vCalendar = clone $this->vCalendar1a;
1314
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;');
1315
-		// construct event reader
1316
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1317
-		// test output
1318
-		$this->assertEquals(
1319
-			'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)',
1320
-			$this->service->generateWhenString($eventReader)
1321
-		);
1322
-
1323
-		/** test absolute partial day event with every year interval on July 1 and conclusion*/
1324
-		$vCalendar = clone $this->vCalendar1a;
1325
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z');
1326
-		// construct event reader
1327
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1328
-		// test output
1329
-		$this->assertEquals(
1330
-			'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1331
-			$this->service->generateWhenString($eventReader)
1332
-		);
1333
-
1334
-		/** test absolute partial day event with every 2nd year interval on July 1 and no conclusion*/
1335
-		$vCalendar = clone $this->vCalendar1a;
1336
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;');
1337
-		// construct event reader
1338
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1339
-		// test output
1340
-		$this->assertEquals(
1341
-			'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)',
1342
-			$this->service->generateWhenString($eventReader)
1343
-		);
1344
-
1345
-		/** test absolute partial day event with every 2nd year interval on July 1 and conclusion*/
1346
-		$vCalendar = clone $this->vCalendar1a;
1347
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;');
1348
-		// construct event reader
1349
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1350
-		// test output
1351
-		$this->assertEquals(
1352
-			'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1353
-			$this->service->generateWhenString($eventReader)
1354
-		);
1355
-
1356
-		/** test absolute entire day event with every year interval on July 1 and no conclusion*/
1357
-		$vCalendar = clone $this->vCalendar2;
1358
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;');
1359
-		// construct event reader
1360
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1361
-		// test output
1362
-		$this->assertEquals(
1363
-			'Every Year in July on the 1st for the entire day',
1364
-			$this->service->generateWhenString($eventReader)
1365
-		);
1366
-
1367
-		/** test absolute entire day event with every year interval on July 1 and conclusion*/
1368
-		$vCalendar = clone $this->vCalendar2;
1369
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z;');
1370
-		// construct event reader
1371
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1372
-		// test output
1373
-		$this->assertEquals(
1374
-			'Every Year in July on the 1st for the entire day until July 31, 2026',
1375
-			$this->service->generateWhenString($eventReader)
1376
-		);
1377
-
1378
-		/** test absolute entire day event with every 2nd year interval on July 1 and no conclusion*/
1379
-		$vCalendar = clone $this->vCalendar2;
1380
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;');
1381
-		// construct event reader
1382
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1383
-		// test output
1384
-		$this->assertEquals(
1385
-			'Every 2 Years in July on the 1st for the entire day',
1386
-			$this->service->generateWhenString($eventReader)
1387
-		);
1388
-
1389
-		/** test absolute entire day event with every 2nd year interval on July 1 and conclusion*/
1390
-		$vCalendar = clone $this->vCalendar2;
1391
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;');
1392
-		// construct event reader
1393
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1394
-		// test output
1395
-		$this->assertEquals(
1396
-			'Every 2 Years in July on the 1st for the entire day until July 31, 2026',
1397
-			$this->service->generateWhenString($eventReader)
1398
-		);
1399
-
1400
-		/** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/
1401
-		$vCalendar = clone $this->vCalendar1a;
1402
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;');
1403
-		// construct event reader
1404
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1405
-		// test output
1406
-		$this->assertEquals(
1407
-			'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1408
-			$this->service->generateWhenString($eventReader)
1409
-		);
1410
-
1411
-		/** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/
1412
-		$vCalendar = clone $this->vCalendar1a;
1413
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;');
1414
-		// construct event reader
1415
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1416
-		// test output
1417
-		$this->assertEquals(
1418
-			'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1419
-			$this->service->generateWhenString($eventReader)
1420
-		);
1421
-
1422
-		/** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/
1423
-		$vCalendar = clone $this->vCalendar1a;
1424
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1425
-		// construct event reader
1426
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1427
-		// test output
1428
-		$this->assertEquals(
1429
-			'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1430
-			$this->service->generateWhenString($eventReader)
1431
-		);
1432
-
1433
-		/** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/
1434
-		$vCalendar = clone $this->vCalendar1a;
1435
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;');
1436
-		// construct event reader
1437
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1438
-		// test output
1439
-		$this->assertEquals(
1440
-			'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1441
-			$this->service->generateWhenString($eventReader)
1442
-		);
1443
-
1444
-		/** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/
1445
-		$vCalendar = clone $this->vCalendar2;
1446
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;');
1447
-		// construct event reader
1448
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1449
-		// test output
1450
-		$this->assertEquals(
1451
-			'Every Year in July on the First Sunday, Saturday for the entire day',
1452
-			$this->service->generateWhenString($eventReader)
1453
-		);
1454
-
1455
-		/** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/
1456
-		$vCalendar = clone $this->vCalendar2;
1457
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;');
1458
-		// construct event reader
1459
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1460
-		// test output
1461
-		$this->assertEquals(
1462
-			'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026',
1463
-			$this->service->generateWhenString($eventReader)
1464
-		);
1465
-
1466
-		/** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/
1467
-		$vCalendar = clone $this->vCalendar2;
1468
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1469
-		// construct event reader
1470
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1471
-		// test output
1472
-		$this->assertEquals(
1473
-			'Every 2 Years in July on the First Sunday, Saturday for the entire day',
1474
-			$this->service->generateWhenString($eventReader)
1475
-		);
1476
-
1477
-		/** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/
1478
-		$vCalendar = clone $this->vCalendar2;
1479
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;');
1480
-		// construct event reader
1481
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1482
-		// test output
1483
-		$this->assertEquals(
1484
-			'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026',
1485
-			$this->service->generateWhenString($eventReader)
1486
-		);
1487
-
1488
-	}
1489
-
1490
-	public function testGenerateWhenStringRecurringFixed(): void {
1491
-
1492
-		// construct l10n return maps
1493
-		$this->l10n->method('l')->willReturnCallback(
1494
-			function ($v1, $v2, $v3) {
1495
-				return match (true) {
1496
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1497
-					$v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1498
-					$v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
1499
-				};
1500
-			}
1501
-		);
1502
-		$this->l10n->method('t')->willReturnMap([
1503
-			['On specific dates for the entire day until %1$s', ['July 13, 2024'], 'On specific dates for the entire day until July 13, 2024'],
1504
-			['On specific dates between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
1505
-		]);
1506
-
1507
-		/** test partial day event with every day interval and conclusion*/
1508
-		$vCalendar = clone $this->vCalendar1a;
1509
-		$vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000');
1510
-		// construct event reader
1511
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1512
-		// test output
1513
-		$this->assertEquals(
1514
-			'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
1515
-			$this->service->generateWhenString($eventReader)
1516
-		);
1517
-
1518
-		/** test entire day event with every day interval and no conclusion*/
1519
-		$vCalendar = clone $this->vCalendar2;
1520
-		$vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000');
1521
-		// construct event reader
1522
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1523
-		// test output
1524
-		$this->assertEquals(
1525
-			'On specific dates for the entire day until July 13, 2024',
1526
-			$this->service->generateWhenString($eventReader)
1527
-		);
1528
-
1529
-	}
1530
-
1531
-	public function testGenerateOccurringStringWithRrule(): void {
1532
-
1533
-		// construct l10n return(s)
1534
-		$this->l10n->method('l')->willReturnCallback(
1535
-			function ($v1, $v2, $v3) {
1536
-				return match (true) {
1537
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1538
-					$v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024',
1539
-					$v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024'
1540
-				};
1541
-			}
1542
-		);
1543
-		$this->l10n->method('n')->willReturnMap([
1544
-			// singular
1545
-			[
1546
-				'In a day on %1$s',
1547
-				'In %n days on %1$s',
1548
-				1,
1549
-				['July 1, 2024'],
1550
-				'In a day on July 1, 2024'
1551
-			],
1552
-			[
1553
-				'In a day on %1$s then on %2$s',
1554
-				'In %n days on %1$s then on %2$s',
1555
-				1,
1556
-				['July 1, 2024', 'July 3, 2024'],
1557
-				'In a day on July 1, 2024 then on July 3, 2024'
1558
-			],
1559
-			[
1560
-				'In a day on %1$s then on %2$s and %3$s',
1561
-				'In %n days on %1$s then on %2$s and %3$s',
1562
-				1,
1563
-				['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1564
-				'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1565
-			],
1566
-			// plural
1567
-			[
1568
-				'In a day on %1$s',
1569
-				'In %n days on %1$s',
1570
-				2,
1571
-				['July 1, 2024'],
1572
-				'In 2 days on July 1, 2024'
1573
-			],
1574
-			[
1575
-				'In a day on %1$s then on %2$s',
1576
-				'In %n days on %1$s then on %2$s',
1577
-				2,
1578
-				['July 1, 2024', 'July 3, 2024'],
1579
-				'In 2 days on July 1, 2024 then on July 3, 2024'
1580
-			],
1581
-			[
1582
-				'In a day on %1$s then on %2$s and %3$s',
1583
-				'In %n days on %1$s then on %2$s and %3$s',
1584
-				2,
1585
-				['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1586
-				'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1587
-			],
1588
-		]);
1589
-
1590
-		// construct time factory return(s)
1591
-		$this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1592
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1593
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1594
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1595
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1596
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1597
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1598
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1599
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1600
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1601
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1602
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1603
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1604
-		);
1605
-
1606
-		/** test patrial day recurring event in 1 day with single occurrence remaining */
1607
-		$vCalendar = clone $this->vCalendar1a;
1608
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1609
-		// construct event reader
1610
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1611
-		// test output
1612
-		$this->assertEquals(
1613
-			'In a day on July 1, 2024',
1614
-			$this->service->generateOccurringString($eventReader)
1615
-		);
1616
-
1617
-		/** test patrial day recurring event in 1 day with two occurrences remaining */
1618
-		$vCalendar = clone $this->vCalendar1a;
1619
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1620
-		// construct event reader
1621
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1622
-		// test output
1623
-		$this->assertEquals(
1624
-			'In a day on July 1, 2024 then on July 3, 2024',
1625
-			$this->service->generateOccurringString($eventReader)
1626
-		);
1627
-
1628
-		/** test patrial day recurring event in 1 day with three occurrences remaining */
1629
-		$vCalendar = clone $this->vCalendar1a;
1630
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1631
-		// construct event reader
1632
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1633
-		// test output
1634
-		$this->assertEquals(
1635
-			'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1636
-			$this->service->generateOccurringString($eventReader)
1637
-		);
1638
-
1639
-		/** test patrial day recurring event in 2 days with single occurrence remaining */
1640
-		$vCalendar = clone $this->vCalendar1a;
1641
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1642
-		// construct event reader
1643
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1644
-		// test output
1645
-		$this->assertEquals(
1646
-			'In 2 days on July 1, 2024',
1647
-			$this->service->generateOccurringString($eventReader)
1648
-		);
1649
-
1650
-		/** test patrial day recurring event in 2 days with two occurrences remaining */
1651
-		$vCalendar = clone $this->vCalendar1a;
1652
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1653
-		// construct event reader
1654
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1655
-		// test output
1656
-		$this->assertEquals(
1657
-			'In 2 days on July 1, 2024 then on July 3, 2024',
1658
-			$this->service->generateOccurringString($eventReader)
1659
-		);
1660
-
1661
-		/** test patrial day recurring event in 2 days with three occurrences remaining */
1662
-		$vCalendar = clone $this->vCalendar1a;
1663
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1664
-		// construct event reader
1665
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1666
-		// test output
1667
-		$this->assertEquals(
1668
-			'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1669
-			$this->service->generateOccurringString($eventReader)
1670
-		);
1671
-	}
1672
-
1673
-	public function testGenerateOccurringStringWithRdate(): void {
1674
-
1675
-		// construct l10n return(s)
1676
-		$this->l10n->method('l')->willReturnCallback(
1677
-			function ($v1, $v2, $v3) {
1678
-				return match (true) {
1679
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1680
-					$v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024',
1681
-					$v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024'
1682
-				};
1683
-			}
1684
-		);
1685
-		$this->l10n->method('n')->willReturnMap([
1686
-			// singular
1687
-			[
1688
-				'In a day on %1$s',
1689
-				'In %n days on %1$s',
1690
-				1,
1691
-				['July 1, 2024'],
1692
-				'In a day on July 1, 2024'
1693
-			],
1694
-			[
1695
-				'In a day on %1$s then on %2$s',
1696
-				'In %n days on %1$s then on %2$s',
1697
-				1,
1698
-				['July 1, 2024', 'July 3, 2024'],
1699
-				'In a day on July 1, 2024 then on July 3, 2024'
1700
-			],
1701
-			[
1702
-				'In a day on %1$s then on %2$s and %3$s',
1703
-				'In %n days on %1$s then on %2$s and %3$s',
1704
-				1,
1705
-				['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1706
-				'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1707
-			],
1708
-			// plural
1709
-			[
1710
-				'In a day on %1$s',
1711
-				'In %n days on %1$s',
1712
-				2,
1713
-				['July 1, 2024'],
1714
-				'In 2 days on July 1, 2024'
1715
-			],
1716
-			[
1717
-				'In a day on %1$s then on %2$s',
1718
-				'In %n days on %1$s then on %2$s',
1719
-				2,
1720
-				['July 1, 2024', 'July 3, 2024'],
1721
-				'In 2 days on July 1, 2024 then on July 3, 2024'
1722
-			],
1723
-			[
1724
-				'In a day on %1$s then on %2$s and %3$s',
1725
-				'In %n days on %1$s then on %2$s and %3$s',
1726
-				2,
1727
-				['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1728
-				'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1729
-			],
1730
-		]);
1731
-
1732
-		// construct time factory return(s)
1733
-		$this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1734
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1735
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1736
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1737
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1738
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1739
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1740
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1741
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1742
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1743
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1744
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1745
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1746
-		);
1747
-
1748
-		/** test patrial day recurring event in 1 day with single occurrence remaining */
1749
-		$vCalendar = clone $this->vCalendar1a;
1750
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1751
-		// construct event reader
1752
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1753
-		// test output
1754
-		$this->assertEquals(
1755
-			'In a day on July 1, 2024',
1756
-			$this->service->generateOccurringString($eventReader),
1757
-			'test patrial day recurring event in 1 day with single occurrence remaining'
1758
-		);
1759
-
1760
-		/** test patrial day recurring event in 1 day with two occurrences remaining */
1761
-		$vCalendar = clone $this->vCalendar1a;
1762
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000');
1763
-		// construct event reader
1764
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1765
-		// test output
1766
-		$this->assertEquals(
1767
-			'In a day on July 1, 2024 then on July 3, 2024',
1768
-			$this->service->generateOccurringString($eventReader),
1769
-			'test patrial day recurring event in 1 day with two occurrences remaining'
1770
-		);
1771
-
1772
-		/** test patrial day recurring event in 1 day with three occurrences remaining */
1773
-		$vCalendar = clone $this->vCalendar1a;
1774
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000,20240705T080000');
1775
-		// construct event reader
1776
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1777
-		// test output
1778
-		$this->assertEquals(
1779
-			'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1780
-			$this->service->generateOccurringString($eventReader),
1781
-			''
1782
-		);
1783
-
1784
-		/** test patrial day recurring event in 2 days with single occurrences remaining */
1785
-		$vCalendar = clone $this->vCalendar1a;
1786
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1787
-		// construct event reader
1788
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1789
-		// test output
1790
-		$this->assertEquals(
1791
-			'In 2 days on July 1, 2024',
1792
-			$this->service->generateOccurringString($eventReader),
1793
-			''
1794
-		);
1795
-
1796
-		/** test patrial day recurring event in 2 days with two occurrences remaining */
1797
-		$vCalendar = clone $this->vCalendar1a;
1798
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1799
-		$vCalendar->VEVENT[0]->add('RDATE', '20240703T080000');
1800
-		// construct event reader
1801
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1802
-		// test output
1803
-		$this->assertEquals(
1804
-			'In 2 days on July 1, 2024 then on July 3, 2024',
1805
-			$this->service->generateOccurringString($eventReader),
1806
-			''
1807
-		);
1808
-
1809
-		/** test patrial day recurring event in 2 days with three occurrences remaining */
1810
-		$vCalendar = clone $this->vCalendar1a;
1811
-		$vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1812
-		$vCalendar->VEVENT[0]->add('RDATE', '20240703T080000');
1813
-		$vCalendar->VEVENT[0]->add('RDATE', '20240705T080000');
1814
-		// construct event reader
1815
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1816
-		// test output
1817
-		$this->assertEquals(
1818
-			'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1819
-			$this->service->generateOccurringString($eventReader),
1820
-			'test patrial day recurring event in 2 days with three occurrences remaining'
1821
-		);
1822
-	}
1823
-
1824
-	public function testGenerateOccurringStringWithOneExdate(): void {
1825
-
1826
-		// construct l10n return(s)
1827
-		$this->l10n->method('l')->willReturnCallback(
1828
-			function ($v1, $v2, $v3) {
1829
-				return match (true) {
1830
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1831
-					$v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024',
1832
-					$v1 === 'date' && $v2 == (new \DateTime('20240707T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 7, 2024'
1833
-				};
1834
-			}
1835
-		);
1836
-		$this->l10n->method('n')->willReturnMap([
1837
-			// singular
1838
-			[
1839
-				'In a day on %1$s',
1840
-				'In %n days on %1$s',
1841
-				1,
1842
-				['July 1, 2024'],
1843
-				'In a day on July 1, 2024'
1844
-			],
1845
-			[
1846
-				'In a day on %1$s then on %2$s',
1847
-				'In %n days on %1$s then on %2$s',
1848
-				1,
1849
-				['July 1, 2024', 'July 5, 2024'],
1850
-				'In a day on July 1, 2024 then on July 5, 2024'
1851
-			],
1852
-			[
1853
-				'In a day on %1$s then on %2$s and %3$s',
1854
-				'In %n days on %1$s then on %2$s and %3$s',
1855
-				1,
1856
-				['July 1, 2024', 'July 5, 2024', 'July 7, 2024'],
1857
-				'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024'
1858
-			],
1859
-			// plural
1860
-			[
1861
-				'In a day on %1$s',
1862
-				'In %n days on %1$s',
1863
-				2,
1864
-				['July 1, 2024'],
1865
-				'In 2 days on July 1, 2024'
1866
-			],
1867
-			[
1868
-				'In a day on %1$s then on %2$s',
1869
-				'In %n days on %1$s then on %2$s',
1870
-				2,
1871
-				['July 1, 2024', 'July 5, 2024'],
1872
-				'In 2 days on July 1, 2024 then on July 5, 2024'
1873
-			],
1874
-			[
1875
-				'In a day on %1$s then on %2$s and %3$s',
1876
-				'In %n days on %1$s then on %2$s and %3$s',
1877
-				2,
1878
-				['July 1, 2024', 'July 5, 2024', 'July 7, 2024'],
1879
-				'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024'
1880
-			],
1881
-		]);
1882
-
1883
-		// construct time factory return(s)
1884
-		$this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1885
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1886
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1887
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1888
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1889
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1890
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1891
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1892
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1893
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1894
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1895
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1896
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1897
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1898
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1899
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1900
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1901
-		);
1902
-
1903
-		/** test patrial day recurring event in 1 day with single occurrence remaining and one exception */
1904
-		$vCalendar = clone $this->vCalendar1a;
1905
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1906
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1907
-		// construct event reader
1908
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1909
-		// test output
1910
-		$this->assertEquals(
1911
-			'In a day on July 1, 2024',
1912
-			$this->service->generateOccurringString($eventReader),
1913
-			'test patrial day recurring event in 1 day with single occurrence remaining and one exception'
1914
-		);
1915
-
1916
-		/** test patrial day recurring event in 1 day with two occurrences remaining and one exception */
1917
-		$vCalendar = clone $this->vCalendar1a;
1918
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1919
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1920
-		// construct event reader
1921
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1922
-		// test output
1923
-		$this->assertEquals(
1924
-			'In a day on July 1, 2024',
1925
-			$this->service->generateOccurringString($eventReader),
1926
-			'test patrial day recurring event in 1 day with two occurrences remaining and one exception'
1927
-		);
1928
-
1929
-		/** test patrial day recurring event in 1 day with three occurrences remaining and one exception */
1930
-		$vCalendar = clone $this->vCalendar1a;
1931
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1932
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1933
-		// construct event reader
1934
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1935
-		// test output
1936
-		$this->assertEquals(
1937
-			'In a day on July 1, 2024 then on July 5, 2024',
1938
-			$this->service->generateOccurringString($eventReader),
1939
-			'test patrial day recurring event in 1 day with three occurrences remaining and one exception'
1940
-		);
1941
-
1942
-		/** test patrial day recurring event in 1 day with four occurrences remaining and one exception */
1943
-		$vCalendar = clone $this->vCalendar1a;
1944
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4');
1945
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1946
-		// construct event reader
1947
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1948
-		// test output
1949
-		$this->assertEquals(
1950
-			'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024',
1951
-			$this->service->generateOccurringString($eventReader),
1952
-			'test patrial day recurring event in 1 day with four occurrences remaining and one exception'
1953
-		);
1954
-
1955
-		/** test patrial day recurring event in 2 days with single occurrences remaining and one exception */
1956
-		$vCalendar = clone $this->vCalendar1a;
1957
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1958
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1959
-		// construct event reader
1960
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1961
-		// test output
1962
-		$this->assertEquals(
1963
-			'In 2 days on July 1, 2024',
1964
-			$this->service->generateOccurringString($eventReader),
1965
-			'test patrial day recurring event in 2 days with single occurrences remaining and one exception'
1966
-		);
1967
-
1968
-		/** test patrial day recurring event in 2 days with two occurrences remaining and one exception */
1969
-		$vCalendar = clone $this->vCalendar1a;
1970
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1971
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1972
-		// construct event reader
1973
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1974
-		// test output
1975
-		$this->assertEquals(
1976
-			'In 2 days on July 1, 2024',
1977
-			$this->service->generateOccurringString($eventReader),
1978
-			'test patrial day recurring event in 2 days with two occurrences remaining and one exception'
1979
-		);
1980
-
1981
-		/** test patrial day recurring event in 2 days with three occurrences remaining and one exception */
1982
-		$vCalendar = clone $this->vCalendar1a;
1983
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1984
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1985
-		// construct event reader
1986
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1987
-		// test output
1988
-		$this->assertEquals(
1989
-			'In 2 days on July 1, 2024 then on July 5, 2024',
1990
-			$this->service->generateOccurringString($eventReader),
1991
-			'test patrial day recurring event in 2 days with three occurrences remaining and one exception'
1992
-		);
1993
-
1994
-		/** test patrial day recurring event in 2 days with four occurrences remaining and one exception */
1995
-		$vCalendar = clone $this->vCalendar1a;
1996
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4');
1997
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1998
-		// construct event reader
1999
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2000
-		// test output
2001
-		$this->assertEquals(
2002
-			'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024',
2003
-			$this->service->generateOccurringString($eventReader),
2004
-			'test patrial day recurring event in 2 days with four occurrences remaining and one exception'
2005
-		);
2006
-	}
2007
-
2008
-	public function testGenerateOccurringStringWithTwoExdate(): void {
2009
-
2010
-		// construct l10n return(s)
2011
-		$this->l10n->method('l')->willReturnCallback(
2012
-			function ($v1, $v2, $v3) {
2013
-				return match (true) {
2014
-					$v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
2015
-					$v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024',
2016
-					$v1 === 'date' && $v2 == (new \DateTime('20240709T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 9, 2024'
2017
-				};
2018
-			}
2019
-		);
2020
-		$this->l10n->method('n')->willReturnMap([
2021
-			// singular
2022
-			[
2023
-				'In a day on %1$s',
2024
-				'In %n days on %1$s',
2025
-				1,
2026
-				['July 1, 2024'],
2027
-				'In a day on July 1, 2024'
2028
-			],
2029
-			[
2030
-				'In a day on %1$s then on %2$s',
2031
-				'In %n days on %1$s then on %2$s',
2032
-				1,
2033
-				['July 1, 2024', 'July 5, 2024'],
2034
-				'In a day on July 1, 2024 then on July 5, 2024'
2035
-			],
2036
-			[
2037
-				'In a day on %1$s then on %2$s and %3$s',
2038
-				'In %n days on %1$s then on %2$s and %3$s',
2039
-				1,
2040
-				['July 1, 2024', 'July 5, 2024', 'July 9, 2024'],
2041
-				'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024'
2042
-			],
2043
-			// plural
2044
-			[
2045
-				'In a day on %1$s',
2046
-				'In %n days on %1$s',
2047
-				2,
2048
-				['July 1, 2024'],
2049
-				'In 2 days on July 1, 2024'
2050
-			],
2051
-			[
2052
-				'In a day on %1$s then on %2$s',
2053
-				'In %n days on %1$s then on %2$s',
2054
-				2,
2055
-				['July 1, 2024', 'July 5, 2024'],
2056
-				'In 2 days on July 1, 2024 then on July 5, 2024'
2057
-			],
2058
-			[
2059
-				'In a day on %1$s then on %2$s and %3$s',
2060
-				'In %n days on %1$s then on %2$s and %3$s',
2061
-				2,
2062
-				['July 1, 2024', 'July 5, 2024', 'July 9, 2024'],
2063
-				'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024'
2064
-			],
2065
-		]);
2066
-
2067
-		// construct time factory return(s)
2068
-		$this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
2069
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2070
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2071
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2072
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2073
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2074
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2075
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2076
-			(new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2077
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2078
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2079
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2080
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2081
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2082
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2083
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2084
-			(new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2085
-		);
2086
-
2087
-		/** test patrial day recurring event in 1 day with single occurrence remaining and two exception */
2088
-		$vCalendar = clone $this->vCalendar1a;
2089
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
2090
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2091
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2092
-		// construct event reader
2093
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2094
-		// test output
2095
-		$this->assertEquals(
2096
-			'In a day on July 1, 2024',
2097
-			$this->service->generateOccurringString($eventReader),
2098
-			'test patrial day recurring event in 1 day with single occurrence remaining and two exception'
2099
-		);
2100
-
2101
-		/** test patrial day recurring event in 1 day with two occurrences remaining and two exception */
2102
-		$vCalendar = clone $this->vCalendar1a;
2103
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
2104
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2105
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2106
-		// construct event reader
2107
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2108
-		// test output
2109
-		$this->assertEquals(
2110
-			'In a day on July 1, 2024',
2111
-			$this->service->generateOccurringString($eventReader),
2112
-			'test patrial day recurring event in 1 day with two occurrences remaining and two exception'
2113
-		);
2114
-
2115
-		/** test patrial day recurring event in 1 day with three occurrences remaining and two exception */
2116
-		$vCalendar = clone $this->vCalendar1a;
2117
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
2118
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2119
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2120
-		// construct event reader
2121
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2122
-		// test output
2123
-		$this->assertEquals(
2124
-			'In a day on July 1, 2024 then on July 5, 2024',
2125
-			$this->service->generateOccurringString($eventReader),
2126
-			'test patrial day recurring event in 1 day with three occurrences remaining and two exception'
2127
-		);
2128
-
2129
-		/** test patrial day recurring event in 1 day with four occurrences remaining and two exception */
2130
-		$vCalendar = clone $this->vCalendar1a;
2131
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5');
2132
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2133
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2134
-		// construct event reader
2135
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2136
-		// test output
2137
-		$this->assertEquals(
2138
-			'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024',
2139
-			$this->service->generateOccurringString($eventReader),
2140
-			'test patrial day recurring event in 1 day with four occurrences remaining and two exception'
2141
-		);
2142
-
2143
-		/** test patrial day recurring event in 2 days with single occurrences remaining and two exception */
2144
-		$vCalendar = clone $this->vCalendar1a;
2145
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
2146
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2147
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2148
-		// construct event reader
2149
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2150
-		// test output
2151
-		$this->assertEquals(
2152
-			'In 2 days on July 1, 2024',
2153
-			$this->service->generateOccurringString($eventReader),
2154
-			'test patrial day recurring event in 2 days with single occurrences remaining and two exception'
2155
-		);
2156
-
2157
-		/** test patrial day recurring event in 2 days with two occurrences remaining and two exception */
2158
-		$vCalendar = clone $this->vCalendar1a;
2159
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
2160
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2161
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2162
-		// construct event reader
2163
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2164
-		// test output
2165
-		$this->assertEquals(
2166
-			'In 2 days on July 1, 2024',
2167
-			$this->service->generateOccurringString($eventReader),
2168
-			'test patrial day recurring event in 2 days with two occurrences remaining and two exception'
2169
-		);
2170
-
2171
-		/** test patrial day recurring event in 2 days with three occurrences remaining and two exception */
2172
-		$vCalendar = clone $this->vCalendar1a;
2173
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
2174
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2175
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2176
-		// construct event reader
2177
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2178
-		// test output
2179
-		$this->assertEquals(
2180
-			'In 2 days on July 1, 2024 then on July 5, 2024',
2181
-			$this->service->generateOccurringString($eventReader),
2182
-			'test patrial day recurring event in 2 days with three occurrences remaining and two exception'
2183
-		);
2184
-
2185
-		/** test patrial day recurring event in 2 days with five occurrences remaining and two exception */
2186
-		$vCalendar = clone $this->vCalendar1a;
2187
-		$vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5');
2188
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2189
-		$vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2190
-		// construct event reader
2191
-		$eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2192
-		// test output
2193
-		$this->assertEquals(
2194
-			'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024',
2195
-			$this->service->generateOccurringString($eventReader),
2196
-			'test patrial day recurring event in 2 days with five occurrences remaining and two exception'
2197
-		);
2198
-	}
27
+    private URLGenerator&MockObject $urlGenerator;
28
+    private IConfig&MockObject $config;
29
+    private IDBConnection&MockObject $db;
30
+    private ISecureRandom&MockObject $random;
31
+    private IFactory&MockObject $l10nFactory;
32
+    private IL10N&MockObject $l10n;
33
+    private ITimeFactory&MockObject $timeFactory;
34
+    private IMipService $service;
35
+
36
+
37
+    private VCalendar $vCalendar1a;
38
+    private VCalendar $vCalendar1b;
39
+    private VCalendar $vCalendar2;
40
+    private VCalendar $vCalendar3;
41
+    /** @var DateTime DateTime object that will be returned by DateTime() or DateTime('now') */
42
+    public static $datetimeNow;
43
+
44
+    protected function setUp(): void {
45
+        parent::setUp();
46
+
47
+        $this->urlGenerator = $this->createMock(URLGenerator::class);
48
+        $this->config = $this->createMock(IConfig::class);
49
+        $this->db = $this->createMock(IDBConnection::class);
50
+        $this->random = $this->createMock(ISecureRandom::class);
51
+        $this->l10nFactory = $this->createMock(IFactory::class);
52
+        $this->l10n = $this->createMock(IL10N::class);
53
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
54
+        $this->l10nFactory->expects(self::once())
55
+            ->method('findGenericLanguage')
56
+            ->willReturn('en');
57
+        $this->l10nFactory->expects(self::once())
58
+            ->method('get')
59
+            ->with('dav', 'en')
60
+            ->willReturn($this->l10n);
61
+        $this->service = new IMipService(
62
+            $this->urlGenerator,
63
+            $this->config,
64
+            $this->db,
65
+            $this->random,
66
+            $this->l10nFactory,
67
+            $this->timeFactory
68
+        );
69
+
70
+        // construct calendar with a 1 hour event and same start/end time zones
71
+        $this->vCalendar1a = new VCalendar();
72
+        $vEvent = $this->vCalendar1a->add('VEVENT', []);
73
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
74
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
75
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
76
+        $vEvent->add('SUMMARY', 'Testing Event');
77
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
78
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
79
+            'CN' => 'Attendee One',
80
+            'CUTYPE' => 'INDIVIDUAL',
81
+            'PARTSTAT' => 'NEEDS-ACTION',
82
+            'ROLE' => 'REQ-PARTICIPANT',
83
+            'RSVP' => 'TRUE'
84
+        ]);
85
+
86
+        // construct calendar with a 1 hour event and different start/end time zones
87
+        $this->vCalendar1b = new VCalendar();
88
+        $vEvent = $this->vCalendar1b->add('VEVENT', []);
89
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
90
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
91
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Vancouver']);
92
+        $vEvent->add('SUMMARY', 'Testing Event');
93
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
94
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
95
+            'CN' => 'Attendee One',
96
+            'CUTYPE' => 'INDIVIDUAL',
97
+            'PARTSTAT' => 'NEEDS-ACTION',
98
+            'ROLE' => 'REQ-PARTICIPANT',
99
+            'RSVP' => 'TRUE'
100
+        ]);
101
+
102
+        // construct calendar with a full day event
103
+        $this->vCalendar2 = new VCalendar();
104
+        // time zone component
105
+        $vTimeZone = $this->vCalendar2->add('VTIMEZONE');
106
+        $vTimeZone->add('TZID', 'America/Toronto');
107
+        // event component
108
+        $vEvent = $this->vCalendar2->add('VEVENT', []);
109
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
110
+        $vEvent->add('DTSTART', '20240701');
111
+        $vEvent->add('DTEND', '20240702');
112
+        $vEvent->add('SUMMARY', 'Testing Event');
113
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
114
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
115
+            'CN' => 'Attendee One',
116
+            'CUTYPE' => 'INDIVIDUAL',
117
+            'PARTSTAT' => 'NEEDS-ACTION',
118
+            'ROLE' => 'REQ-PARTICIPANT',
119
+            'RSVP' => 'TRUE'
120
+        ]);
121
+
122
+        // construct calendar with a multi day event
123
+        $this->vCalendar3 = new VCalendar();
124
+        // time zone component
125
+        $vTimeZone = $this->vCalendar3->add('VTIMEZONE');
126
+        $vTimeZone->add('TZID', 'America/Toronto');
127
+        // event component
128
+        $vEvent = $this->vCalendar3->add('VEVENT', []);
129
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
130
+        $vEvent->add('DTSTART', '20240701');
131
+        $vEvent->add('DTEND', '20240706');
132
+        $vEvent->add('SUMMARY', 'Testing Event');
133
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
134
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
135
+            'CN' => 'Attendee One',
136
+            'CUTYPE' => 'INDIVIDUAL',
137
+            'PARTSTAT' => 'NEEDS-ACTION',
138
+            'ROLE' => 'REQ-PARTICIPANT',
139
+            'RSVP' => 'TRUE'
140
+        ]);
141
+    }
142
+
143
+    public function testGetFrom(): void {
144
+        $senderName = 'Detective McQueen';
145
+        $default = 'Twin Lakes Police Department - Darkside Division';
146
+        $expected = 'Detective McQueen via Twin Lakes Police Department - Darkside Division';
147
+
148
+        $this->l10n->expects(self::once())
149
+            ->method('t')
150
+            ->willReturn($expected);
151
+
152
+        $actual = $this->service->getFrom($senderName, $default);
153
+        $this->assertEquals($expected, $actual);
154
+    }
155
+
156
+    public function testBuildBodyDataCreated(): void {
157
+
158
+        // construct l10n return(s)
159
+        $this->l10n->method('l')->willReturnCallback(
160
+            function ($v1, $v2, $v3) {
161
+                return match (true) {
162
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
163
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
164
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
165
+                };
166
+            }
167
+        );
168
+        $this->l10n->method('n')->willReturnMap([
169
+            [
170
+                'In a day on %1$s between %2$s - %3$s',
171
+                'In %n days on %1$s between %2$s - %3$s',
172
+                1,
173
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
174
+                'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
175
+            ]
176
+        ]);
177
+        // construct time factory return(s)
178
+        $this->timeFactory->method('getDateTime')->willReturnCallback(
179
+            function ($v1, $v2) {
180
+                return match (true) {
181
+                    $v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000'))
182
+                };
183
+            }
184
+        );
185
+        /** test singleton partial day event*/
186
+        $vCalendar = clone $this->vCalendar1a;
187
+        // construct event reader
188
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
189
+        // define expected output
190
+        $expected = [
191
+            'meeting_when' => $this->service->generateWhenString($eventReader),
192
+            'meeting_description' => '',
193
+            'meeting_title' => 'Testing Event',
194
+            'meeting_location' => '',
195
+            'meeting_url' => '',
196
+            'meeting_url_html' => '',
197
+        ];
198
+        // generate actual output
199
+        $actual = $this->service->buildBodyData($vCalendar->VEVENT[0], null);
200
+        // test output
201
+        $this->assertEquals($expected, $actual);
202
+    }
203
+
204
+    public function testBuildBodyDataUpdate(): void {
205
+
206
+        // construct l10n return(s)
207
+        $this->l10n->method('l')->willReturnCallback(
208
+            function ($v1, $v2, $v3) {
209
+                return match (true) {
210
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
211
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
212
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
213
+                };
214
+            }
215
+        );
216
+        $this->l10n->method('n')->willReturnMap([
217
+            [
218
+                'In a day on %1$s between %2$s - %3$s',
219
+                'In %n days on %1$s between %2$s - %3$s',
220
+                1,
221
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
222
+                'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
223
+            ]
224
+        ]);
225
+        // construct time factory return(s)
226
+        $this->timeFactory->method('getDateTime')->willReturnCallback(
227
+            function ($v1, $v2) {
228
+                return match (true) {
229
+                    $v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000'))
230
+                };
231
+            }
232
+        );
233
+        /** test singleton partial day event*/
234
+        $vCalendarNew = clone $this->vCalendar1a;
235
+        $vCalendarOld = clone $this->vCalendar1a;
236
+        // construct event reader
237
+        $eventReaderNew = new EventReader($vCalendarNew, $vCalendarNew->VEVENT[0]->UID->getValue());
238
+        // alter old event label/title
239
+        $vCalendarOld->VEVENT[0]->SUMMARY->setValue('Testing Singleton Event');
240
+        // define expected output
241
+        $expected = [
242
+            'meeting_when' => $this->service->generateWhenString($eventReaderNew),
243
+            'meeting_description' => '',
244
+            'meeting_title' => 'Testing Event',
245
+            'meeting_location' => '',
246
+            'meeting_url' => '',
247
+            'meeting_url_html' => '',
248
+            'meeting_when_html' => $this->service->generateWhenString($eventReaderNew),
249
+            'meeting_title_html' => sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", 'Testing Singleton Event', 'Testing Event'),
250
+            'meeting_description_html' => '',
251
+            'meeting_location_html' => ''
252
+        ];
253
+        // generate actual output
254
+        $actual = $this->service->buildBodyData($vCalendarNew->VEVENT[0], $vCalendarOld->VEVENT[0]);
255
+        // test output
256
+        $this->assertEquals($expected, $actual);
257
+    }
258
+
259
+    public function testGetLastOccurrenceRRULE(): void {
260
+        $vCalendar = new VCalendar();
261
+        $vCalendar->add('VEVENT', [
262
+            'UID' => 'uid-1234',
263
+            'LAST-MODIFIED' => 123456,
264
+            'SEQUENCE' => 2,
265
+            'SUMMARY' => 'Fellowship meeting',
266
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
267
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
268
+        ]);
269
+
270
+        $occurrence = $this->service->getLastOccurrence($vCalendar);
271
+        $this->assertEquals(1454284800, $occurrence);
272
+    }
273
+
274
+    public function testGetLastOccurrenceEndDate(): void {
275
+        $vCalendar = new VCalendar();
276
+        $vCalendar->add('VEVENT', [
277
+            'UID' => 'uid-1234',
278
+            'LAST-MODIFIED' => 123456,
279
+            'SEQUENCE' => 2,
280
+            'SUMMARY' => 'Fellowship meeting',
281
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
282
+            'DTEND' => new \DateTime('2017-01-01 00:00:00'),
283
+        ]);
284
+
285
+        $occurrence = $this->service->getLastOccurrence($vCalendar);
286
+        $this->assertEquals(1483228800, $occurrence);
287
+    }
288
+
289
+    public function testGetLastOccurrenceDuration(): void {
290
+        $vCalendar = new VCalendar();
291
+        $vCalendar->add('VEVENT', [
292
+            'UID' => 'uid-1234',
293
+            'LAST-MODIFIED' => 123456,
294
+            'SEQUENCE' => 2,
295
+            'SUMMARY' => 'Fellowship meeting',
296
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
297
+            'DURATION' => 'P12W',
298
+        ]);
299
+
300
+        $occurrence = $this->service->getLastOccurrence($vCalendar);
301
+        $this->assertEquals(1458864000, $occurrence);
302
+    }
303
+
304
+    public function testGetLastOccurrenceAllDay(): void {
305
+        $vCalendar = new VCalendar();
306
+        $vEvent = $vCalendar->add('VEVENT', [
307
+            'UID' => 'uid-1234',
308
+            'LAST-MODIFIED' => 123456,
309
+            'SEQUENCE' => 2,
310
+            'SUMMARY' => 'Fellowship meeting',
311
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
312
+        ]);
313
+
314
+        // rewrite from DateTime to Date
315
+        $vEvent->DTSTART['VALUE'] = 'DATE';
316
+
317
+        $occurrence = $this->service->getLastOccurrence($vCalendar);
318
+        $this->assertEquals(1451692800, $occurrence);
319
+    }
320
+
321
+    public function testGetLastOccurrenceFallback(): void {
322
+        $vCalendar = new VCalendar();
323
+        $vCalendar->add('VEVENT', [
324
+            'UID' => 'uid-1234',
325
+            'LAST-MODIFIED' => 123456,
326
+            'SEQUENCE' => 2,
327
+            'SUMMARY' => 'Fellowship meeting',
328
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
329
+        ]);
330
+
331
+        $occurrence = $this->service->getLastOccurrence($vCalendar);
332
+        $this->assertEquals(1451606400, $occurrence);
333
+    }
334
+
335
+    public function testGenerateWhenStringSingular(): void {
336
+
337
+        // construct l10n return(s)
338
+        $this->l10n->method('l')->willReturnCallback(
339
+            function ($v1, $v2, $v3) {
340
+                return match (true) {
341
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
342
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
343
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024',
344
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T000000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024'
345
+                };
346
+            }
347
+        );
348
+        $this->l10n->method('t')->willReturnMap([
349
+            [
350
+                'In the past on %1$s for the entire day',
351
+                ['July 1, 2024'],
352
+                'In the past on July 1, 2024 for the entire day'
353
+            ],
354
+            [
355
+                'In the past on %1$s between %2$s - %3$s',
356
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
357
+                'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
358
+            ],
359
+        ]);
360
+        $this->l10n->method('n')->willReturnMap([
361
+            // singular entire day
362
+            [
363
+                'In a minute on %1$s for the entire day',
364
+                'In %n minutes on %1$s for the entire day',
365
+                1,
366
+                ['July 1, 2024'],
367
+                'In a minute on July 1, 2024 for the entire day'
368
+            ],
369
+            [
370
+                'In a hour on %1$s for the entire day',
371
+                'In %n hours on %1$s for the entire day',
372
+                1,
373
+                ['July 1, 2024'],
374
+                'In a hour on July 1, 2024 for the entire day'
375
+            ],
376
+            [
377
+                'In a day on %1$s for the entire day',
378
+                'In %n days on %1$s for the entire day',
379
+                1,
380
+                ['July 1, 2024'],
381
+                'In a day on July 1, 2024 for the entire day'
382
+            ],
383
+            [
384
+                'In a week on %1$s for the entire day',
385
+                'In %n weeks on %1$s for the entire day',
386
+                1,
387
+                ['July 1, 2024'],
388
+                'In a week on July 1, 2024 for the entire day'
389
+            ],
390
+            [
391
+                'In a month on %1$s for the entire day',
392
+                'In %n months on %1$s for the entire day',
393
+                1,
394
+                ['July 1, 2024'],
395
+                'In a month on July 1, 2024 for the entire day'
396
+            ],
397
+            [
398
+                'In a year on %1$s for the entire day',
399
+                'In %n years on %1$s for the entire day',
400
+                1,
401
+                ['July 1, 2024'],
402
+                'In a year on July 1, 2024 for the entire day'
403
+            ],
404
+            // plural entire day
405
+            [
406
+                'In a minute on %1$s for the entire day',
407
+                'In %n minutes on %1$s for the entire day',
408
+                2,
409
+                ['July 1, 2024'],
410
+                'In 2 minutes on July 1, 2024 for the entire day'
411
+            ],
412
+            [
413
+                'In a hour on %1$s for the entire day',
414
+                'In %n hours on %1$s for the entire day',
415
+                2,
416
+                ['July 1, 2024'],
417
+                'In 2 hours on July 1, 2024 for the entire day'
418
+            ],
419
+            [
420
+                'In a day on %1$s for the entire day',
421
+                'In %n days on %1$s for the entire day',
422
+                2,
423
+                ['July 1, 2024'],
424
+                'In 2 days on July 1, 2024 for the entire day'
425
+            ],
426
+            [
427
+                'In a week on %1$s for the entire day',
428
+                'In %n weeks on %1$s for the entire day',
429
+                2,
430
+                ['July 1, 2024'],
431
+                'In 2 weeks on July 1, 2024 for the entire day'
432
+            ],
433
+            [
434
+                'In a month on %1$s for the entire day',
435
+                'In %n months on %1$s for the entire day',
436
+                2,
437
+                ['July 1, 2024'],
438
+                'In 2 months on July 1, 2024 for the entire day'
439
+            ],
440
+            [
441
+                'In a year on %1$s for the entire day',
442
+                'In %n years on %1$s for the entire day',
443
+                2,
444
+                ['July 1, 2024'],
445
+                'In 2 years on July 1, 2024 for the entire day'
446
+            ],
447
+            // singular partial day
448
+            [
449
+                'In a minute on %1$s between %2$s - %3$s',
450
+                'In %n minutes on %1$s between %2$s - %3$s',
451
+                1,
452
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
453
+                'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
454
+            ],
455
+            [
456
+                'In a hour on %1$s between %2$s - %3$s',
457
+                'In %n hours on %1$s between %2$s - %3$s',
458
+                1,
459
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
460
+                'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
461
+            ],
462
+            [
463
+                'In a day on %1$s between %2$s - %3$s',
464
+                'In %n days on %1$s between %2$s - %3$s',
465
+                1,
466
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
467
+                'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
468
+            ],
469
+            [
470
+                'In a week on %1$s between %2$s - %3$s',
471
+                'In %n weeks on %1$s between %2$s - %3$s',
472
+                1,
473
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
474
+                'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
475
+            ],
476
+            [
477
+                'In a month on %1$s between %2$s - %3$s',
478
+                'In %n months on %1$s between %2$s - %3$s',
479
+                1,
480
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
481
+                'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
482
+            ],
483
+            [
484
+                'In a year on %1$s between %2$s - %3$s',
485
+                'In %n years on %1$s between %2$s - %3$s',
486
+                1,
487
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
488
+                'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
489
+            ],
490
+            // plural partial day
491
+            [
492
+                'In a minute on %1$s between %2$s - %3$s',
493
+                'In %n minutes on %1$s between %2$s - %3$s',
494
+                2,
495
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
496
+                'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
497
+            ],
498
+            [
499
+                'In a hour on %1$s between %2$s - %3$s',
500
+                'In %n hours on %1$s between %2$s - %3$s',
501
+                2,
502
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
503
+                'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
504
+            ],
505
+            [
506
+                'In a day on %1$s between %2$s - %3$s',
507
+                'In %n days on %1$s between %2$s - %3$s',
508
+                2,
509
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
510
+                'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
511
+            ],
512
+            [
513
+                'In a week on %1$s between %2$s - %3$s',
514
+                'In %n weeks on %1$s between %2$s - %3$s',
515
+                2,
516
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
517
+                'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
518
+            ],
519
+            [
520
+                'In a month on %1$s between %2$s - %3$s',
521
+                'In %n months on %1$s between %2$s - %3$s',
522
+                2,
523
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
524
+                'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
525
+            ],
526
+            [
527
+                'In a year on %1$s between %2$s - %3$s',
528
+                'In %n years on %1$s between %2$s - %3$s',
529
+                2,
530
+                ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'],
531
+                'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)'
532
+            ],
533
+        ]);
534
+
535
+        // construct time factory return(s)
536
+        $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
537
+            // past interval test dates
538
+            (new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))),
539
+            (new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))),
540
+            (new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))),
541
+            (new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))),
542
+            // minute interval test dates
543
+            (new \DateTime('20240701T075900', (new \DateTimeZone('America/Toronto')))),
544
+            (new \DateTime('20240630T235900', (new \DateTimeZone('America/Toronto')))),
545
+            (new \DateTime('20240701T075800', (new \DateTimeZone('America/Toronto')))),
546
+            (new \DateTime('20240630T235800', (new \DateTimeZone('America/Toronto')))),
547
+            // hour interval test dates
548
+            (new \DateTime('20240701T070000', (new \DateTimeZone('America/Toronto')))),
549
+            (new \DateTime('20240630T230000', (new \DateTimeZone('America/Toronto')))),
550
+            (new \DateTime('20240701T060000', (new \DateTimeZone('America/Toronto')))),
551
+            (new \DateTime('20240630T220000', (new \DateTimeZone('America/Toronto')))),
552
+            // day interval test dates
553
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
554
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
555
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
556
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
557
+            // week interval test dates
558
+            (new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))),
559
+            (new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))),
560
+            (new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))),
561
+            (new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))),
562
+            // month interval test dates
563
+            (new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))),
564
+            (new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))),
565
+            (new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))),
566
+            (new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))),
567
+            // year interval test dates
568
+            (new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))),
569
+            (new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))),
570
+            (new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto')))),
571
+            (new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto'))))
572
+        );
573
+
574
+        /** test partial day event in 1 day in the past*/
575
+        $vCalendar = clone $this->vCalendar1a;
576
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
577
+        $this->assertEquals(
578
+            'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
579
+            $this->service->generateWhenString($eventReader)
580
+        );
581
+
582
+        /** test entire day event in 1 day in the past*/
583
+        $vCalendar = clone $this->vCalendar2;
584
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
585
+        $this->assertEquals(
586
+            'In the past on July 1, 2024 for the entire day',
587
+            $this->service->generateWhenString($eventReader)
588
+        );
589
+
590
+        /** test partial day event in 2 days in the past*/
591
+        $vCalendar = clone $this->vCalendar1a;
592
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
593
+        $this->assertEquals(
594
+            'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
595
+            $this->service->generateWhenString($eventReader)
596
+        );
597
+
598
+        /** test entire day event in 2 days in the past*/
599
+        $vCalendar = clone $this->vCalendar2;
600
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
601
+        $this->assertEquals(
602
+            'In the past on July 1, 2024 for the entire day',
603
+            $this->service->generateWhenString($eventReader)
604
+        );
605
+
606
+        /** test partial day event in 1 minute*/
607
+        $vCalendar = clone $this->vCalendar1a;
608
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
609
+        $this->assertEquals(
610
+            'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
611
+            $this->service->generateWhenString($eventReader)
612
+        );
613
+
614
+        /** test entire day event in 1 minute*/
615
+        $vCalendar = clone $this->vCalendar2;
616
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
617
+        $this->assertEquals(
618
+            'In a minute on July 1, 2024 for the entire day',
619
+            $this->service->generateWhenString($eventReader)
620
+        );
621
+
622
+        /** test partial day event in 2 minutes*/
623
+        $vCalendar = clone $this->vCalendar1a;
624
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
625
+        $this->assertEquals(
626
+            'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
627
+            $this->service->generateWhenString($eventReader)
628
+        );
629
+
630
+        /** test entire day event in 2 minutes*/
631
+        $vCalendar = clone $this->vCalendar2;
632
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
633
+        $this->assertEquals(
634
+            'In 2 minutes on July 1, 2024 for the entire day',
635
+            $this->service->generateWhenString($eventReader)
636
+        );
637
+
638
+        /** test partial day event in 1 hour*/
639
+        $vCalendar = clone $this->vCalendar1a;
640
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
641
+        $this->assertEquals(
642
+            'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
643
+            $this->service->generateWhenString($eventReader)
644
+        );
645
+
646
+        /** test entire day event in 1 hour*/
647
+        $vCalendar = clone $this->vCalendar2;
648
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
649
+        $this->assertEquals(
650
+            'In a hour on July 1, 2024 for the entire day',
651
+            $this->service->generateWhenString($eventReader)
652
+        );
653
+
654
+        /** test partial day event in 2 hours*/
655
+        $vCalendar = clone $this->vCalendar1a;
656
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
657
+        $this->assertEquals(
658
+            'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
659
+            $this->service->generateWhenString($eventReader)
660
+        );
661
+
662
+        /** test entire day event in 2 hours*/
663
+        $vCalendar = clone $this->vCalendar2;
664
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
665
+        $this->assertEquals(
666
+            'In 2 hours on July 1, 2024 for the entire day',
667
+            $this->service->generateWhenString($eventReader)
668
+        );
669
+
670
+        /** test patrial day event in 1 day*/
671
+        $vCalendar = clone $this->vCalendar1a;
672
+        // construct event reader
673
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
674
+        // test output
675
+        $this->assertEquals(
676
+            'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
677
+            $this->service->generateWhenString($eventReader)
678
+        );
679
+
680
+        /** test entire day event in 1 day*/
681
+        $vCalendar = clone $this->vCalendar2;
682
+        // construct event reader
683
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
684
+        // test output
685
+        $this->assertEquals(
686
+            'In a day on July 1, 2024 for the entire day',
687
+            $this->service->generateWhenString($eventReader)
688
+        );
689
+
690
+        /** test patrial day event in 2 days*/
691
+        $vCalendar = clone $this->vCalendar1a;
692
+        // construct event reader
693
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
694
+        // test output
695
+        $this->assertEquals(
696
+            'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
697
+            $this->service->generateWhenString($eventReader)
698
+        );
699
+
700
+        /** test entire day event in 2 days*/
701
+        $vCalendar = clone $this->vCalendar2;
702
+        // construct event reader
703
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
704
+        // test output
705
+        $this->assertEquals(
706
+            'In 2 days on July 1, 2024 for the entire day',
707
+            $this->service->generateWhenString($eventReader)
708
+        );
709
+
710
+        /** test patrial day event in 1 week*/
711
+        $vCalendar = clone $this->vCalendar1a;
712
+        // construct event reader
713
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
714
+        // test output
715
+        $this->assertEquals(
716
+            'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
717
+            $this->service->generateWhenString($eventReader)
718
+        );
719
+
720
+        /** test entire day event in 1 week*/
721
+        $vCalendar = clone $this->vCalendar2;
722
+        // construct event reader
723
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
724
+        // test output
725
+        $this->assertEquals(
726
+            'In a week on July 1, 2024 for the entire day',
727
+            $this->service->generateWhenString($eventReader)
728
+        );
729
+
730
+        /** test patrial day event in 2 weeks*/
731
+        $vCalendar = clone $this->vCalendar1a;
732
+        // construct event reader
733
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
734
+        // test output
735
+        $this->assertEquals(
736
+            'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
737
+            $this->service->generateWhenString($eventReader)
738
+        );
739
+
740
+        /** test entire day event in 2 weeks*/
741
+        $vCalendar = clone $this->vCalendar2;
742
+        // construct event reader
743
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
744
+        // test output
745
+        $this->assertEquals(
746
+            'In 2 weeks on July 1, 2024 for the entire day',
747
+            $this->service->generateWhenString($eventReader)
748
+        );
749
+
750
+        /** test patrial day event in 1 month*/
751
+        $vCalendar = clone $this->vCalendar1a;
752
+        // construct event reader
753
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
754
+        // test output
755
+        $this->assertEquals(
756
+            'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
757
+            $this->service->generateWhenString($eventReader)
758
+        );
759
+
760
+        /** test entire day event in 1 month*/
761
+        $vCalendar = clone $this->vCalendar2;
762
+        // construct event reader
763
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
764
+        // test output
765
+        $this->assertEquals(
766
+            'In a month on July 1, 2024 for the entire day',
767
+            $this->service->generateWhenString($eventReader)
768
+        );
769
+
770
+        /** test patrial day event in 2 months*/
771
+        $vCalendar = clone $this->vCalendar1a;
772
+        // construct event reader
773
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
774
+        // test output
775
+        $this->assertEquals(
776
+            'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
777
+            $this->service->generateWhenString($eventReader)
778
+        );
779
+
780
+        /** test entire day event in 2 months*/
781
+        $vCalendar = clone $this->vCalendar2;
782
+        // construct event reader
783
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
784
+        // test output
785
+        $this->assertEquals(
786
+            'In 2 months on July 1, 2024 for the entire day',
787
+            $this->service->generateWhenString($eventReader)
788
+        );
789
+
790
+        /** test patrial day event in 1 year*/
791
+        $vCalendar = clone $this->vCalendar1a;
792
+        // construct event reader
793
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
794
+        // test output
795
+        $this->assertEquals(
796
+            'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
797
+            $this->service->generateWhenString($eventReader)
798
+        );
799
+
800
+        /** test entire day event in 1 year*/
801
+        $vCalendar = clone $this->vCalendar2;
802
+        // construct event reader
803
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
804
+        // test output
805
+        $this->assertEquals(
806
+            'In a year on July 1, 2024 for the entire day',
807
+            $this->service->generateWhenString($eventReader)
808
+        );
809
+
810
+        /** test patrial day event in 2 years*/
811
+        $vCalendar = clone $this->vCalendar1a;
812
+        // construct event reader
813
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
814
+        // test output
815
+        $this->assertEquals(
816
+            'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)',
817
+            $this->service->generateWhenString($eventReader)
818
+        );
819
+
820
+        /** test entire day event in 2 years*/
821
+        $vCalendar = clone $this->vCalendar2;
822
+        // construct event reader
823
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
824
+        // test output
825
+        $this->assertEquals(
826
+            'In 2 years on July 1, 2024 for the entire day',
827
+            $this->service->generateWhenString($eventReader)
828
+        );
829
+
830
+    }
831
+
832
+    public function testGenerateWhenStringRecurringDaily(): void {
833
+
834
+        // construct l10n return maps
835
+        $this->l10n->method('l')->willReturnCallback(
836
+            function ($v1, $v2, $v3) {
837
+                return match (true) {
838
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
839
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
840
+                    $v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
841
+                };
842
+            }
843
+        );
844
+        $this->l10n->method('t')->willReturnMap([
845
+            ['Every Day for the entire day', [], 'Every Day for the entire day'],
846
+            ['Every Day for the entire day until %1$s', ['July 13, 2024'], 'Every Day for the entire day until July 13, 2024'],
847
+            ['Every Day between %1$s - %2$s', ['8:00 AM', '9:00 AM (America/Toronto)'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto)'],
848
+            ['Every Day between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
849
+            ['Every %1$d Days for the entire day', [3], 'Every 3 Days for the entire day'],
850
+            ['Every %1$d Days for the entire day until %2$s', [3, 'July 13, 2024'], 'Every 3 Days for the entire day until July 13, 2024'],
851
+            ['Every %1$d Days between %2$s - %3$s', [3, '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)'],
852
+            ['Every %1$d Days between %2$s - %3$s until %4$s', [3, '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
853
+            ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
854
+        ]);
855
+
856
+        /** test partial day event with every day interval and no conclusion*/
857
+        $vCalendar = clone $this->vCalendar1a;
858
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;');
859
+        // construct event reader
860
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
861
+        // test output
862
+        $this->assertEquals(
863
+            'Every Day between 8:00 AM - 9:00 AM (America/Toronto)',
864
+            $this->service->generateWhenString($eventReader)
865
+        );
866
+
867
+        /** test partial day event with every day interval and conclusion*/
868
+        $vCalendar = clone $this->vCalendar1a;
869
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z');
870
+        // construct event reader
871
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
872
+        // test output
873
+        $this->assertEquals(
874
+            'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
875
+            $this->service->generateWhenString($eventReader)
876
+        );
877
+
878
+        /** test partial day event every 3rd day interval and no conclusion*/
879
+        $vCalendar = clone $this->vCalendar1a;
880
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;');
881
+        // construct event reader
882
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
883
+        // test output
884
+        $this->assertEquals(
885
+            'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)',
886
+            $this->service->generateWhenString($eventReader)
887
+        );
888
+
889
+        /** test partial day event with every 3rd day interval and conclusion*/
890
+        $vCalendar = clone $this->vCalendar1a;
891
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z');
892
+        // construct event reader
893
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
894
+        // test output
895
+        $this->assertEquals(
896
+            'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
897
+            $this->service->generateWhenString($eventReader)
898
+        );
899
+
900
+        /** test entire day event with every day interval and no conclusion*/
901
+        $vCalendar = clone $this->vCalendar2;
902
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;');
903
+        // construct event reader
904
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
905
+        // test output
906
+        $this->assertEquals(
907
+            'Every Day for the entire day',
908
+            $this->service->generateWhenString($eventReader)
909
+        );
910
+
911
+        /** test entire day event with every day interval and conclusion*/
912
+        $vCalendar = clone $this->vCalendar2;
913
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z');
914
+        // construct event reader
915
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
916
+        // test output
917
+        $this->assertEquals(
918
+            'Every Day for the entire day until July 13, 2024',
919
+            $this->service->generateWhenString($eventReader)
920
+        );
921
+
922
+        /** test entire day event with every 3rd day interval and no conclusion*/
923
+        $vCalendar = clone $this->vCalendar2;
924
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;');
925
+        // construct event reader
926
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
927
+        // test output
928
+        $this->assertEquals(
929
+            'Every 3 Days for the entire day',
930
+            $this->service->generateWhenString($eventReader)
931
+        );
932
+
933
+        /** test entire day event with every 3rd day interval and conclusion*/
934
+        $vCalendar = clone $this->vCalendar2;
935
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z');
936
+        // construct event reader
937
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
938
+        // test output
939
+        $this->assertEquals(
940
+            'Every 3 Days for the entire day until July 13, 2024',
941
+            $this->service->generateWhenString($eventReader)
942
+        );
943
+
944
+    }
945
+
946
+    public function testGenerateWhenStringRecurringWeekly(): void {
947
+
948
+        // construct l10n return maps
949
+        $this->l10n->method('l')->willReturnCallback(
950
+            function ($v1, $v2, $v3) {
951
+                return match (true) {
952
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
953
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
954
+                    $v1 === 'date' && $v2 == (new \DateTime('20240722T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
955
+                };
956
+            }
957
+        );
958
+        $this->l10n->method('t')->willReturnMap([
959
+            ['Every Week on %1$s for the entire day', ['Monday, Wednesday, Friday'], 'Every Week on Monday, Wednesday, Friday for the entire day'],
960
+            ['Every Week on %1$s for the entire day until %2$s', ['Monday, Wednesday, Friday', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024'],
961
+            ['Every Week on %1$s between %2$s - %3$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'],
962
+            ['Every Week on %1$s between %2$s - %3$s until %4$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
963
+            ['Every %1$d Weeks on %2$s for the entire day', [2, 'Monday, Wednesday, Friday'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day'],
964
+            ['Every %1$d Weeks on %2$s for the entire day until %3$s', [2, 'Monday, Wednesday, Friday', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024'],
965
+            ['Every %1$d Weeks on %2$s between %3$s - %4$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'],
966
+            ['Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
967
+            ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
968
+            ['Monday', [], 'Monday'],
969
+            ['Wednesday', [], 'Wednesday'],
970
+            ['Friday', [], 'Friday'],
971
+        ]);
972
+
973
+        /** test partial day event with every week interval on Mon, Wed, Fri and no conclusion*/
974
+        $vCalendar = clone $this->vCalendar1a;
975
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR');
976
+        // construct event reader
977
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
978
+        // test output
979
+        $this->assertEquals(
980
+            'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)',
981
+            $this->service->generateWhenString($eventReader)
982
+        );
983
+
984
+        /** test partial day event with every week interval on Mon, Wed, Fri and conclusion*/
985
+        $vCalendar = clone $this->vCalendar1a;
986
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;');
987
+        // construct event reader
988
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
989
+        // test output
990
+        $this->assertEquals(
991
+            'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
992
+            $this->service->generateWhenString($eventReader)
993
+        );
994
+
995
+        /** test partial day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/
996
+        $vCalendar = clone $this->vCalendar1a;
997
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;');
998
+        // construct event reader
999
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1000
+        // test output
1001
+        $this->assertEquals(
1002
+            'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)',
1003
+            $this->service->generateWhenString($eventReader)
1004
+        );
1005
+
1006
+        /** test partial day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/
1007
+        $vCalendar = clone $this->vCalendar1a;
1008
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;');
1009
+        // construct event reader
1010
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1011
+        // test output
1012
+        $this->assertEquals(
1013
+            'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
1014
+            $this->service->generateWhenString($eventReader)
1015
+        );
1016
+
1017
+        /** test entire day event with every week interval on Mon, Wed, Fri and no conclusion*/
1018
+        $vCalendar = clone $this->vCalendar2;
1019
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;');
1020
+        // construct event reader
1021
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1022
+        // test output
1023
+        $this->assertEquals(
1024
+            'Every Week on Monday, Wednesday, Friday for the entire day',
1025
+            $this->service->generateWhenString($eventReader)
1026
+        );
1027
+
1028
+        /** test entire day event with every week interval on Mon, Wed, Fri and conclusion*/
1029
+        $vCalendar = clone $this->vCalendar2;
1030
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;');
1031
+        // construct event reader
1032
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1033
+        // test output
1034
+        $this->assertEquals(
1035
+            'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024',
1036
+            $this->service->generateWhenString($eventReader)
1037
+        );
1038
+
1039
+        /** test entire day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/
1040
+        $vCalendar = clone $this->vCalendar2;
1041
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;');
1042
+        // construct event reader
1043
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1044
+        // test output
1045
+        $this->assertEquals(
1046
+            'Every 2 Weeks on Monday, Wednesday, Friday for the entire day',
1047
+            $this->service->generateWhenString($eventReader)
1048
+        );
1049
+
1050
+        /** test entire day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/
1051
+        $vCalendar = clone $this->vCalendar2;
1052
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;');
1053
+        // construct event reader
1054
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1055
+        // test output
1056
+        $this->assertEquals(
1057
+            'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024',
1058
+            $this->service->generateWhenString($eventReader)
1059
+        );
1060
+
1061
+    }
1062
+
1063
+    public function testGenerateWhenStringRecurringMonthly(): void {
1064
+
1065
+        // construct l10n return maps
1066
+        $this->l10n->method('l')->willReturnCallback(
1067
+            function ($v1, $v2, $v3) {
1068
+                return match (true) {
1069
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1070
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1071
+                    $v1 === 'date' && $v2 == (new \DateTime('20241231T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'December 31, 2024'
1072
+                };
1073
+            }
1074
+        );
1075
+        $this->l10n->method('t')->willReturnMap([
1076
+            ['Every Month on the %1$s for the entire day', ['1, 8'], 'Every Month on the 1, 8 for the entire day'],
1077
+            ['Every Month on the %1$s for the entire day until %2$s', ['1, 8', 'December 31, 2024'], 'Every Month on the 1, 8 for the entire day until December 31, 2024'],
1078
+            ['Every Month on the %1$s between %2$s - %3$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'],
1079
+            ['Every Month on the %1$s between %2$s - %3$s until %4$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1080
+            ['Every %1$d Months on the %2$s for the entire day', [2, '1, 8'], 'Every 2 Months on the 1, 8 for the entire day'],
1081
+            ['Every %1$d Months on the %2$s for the entire day until %3$s', [2, '1, 8', 'December 31, 2024'], 'Every 2 Months on the 1, 8 for the entire day until December 31, 2024'],
1082
+            ['Every %1$d Months on the %2$s between %3$s - %4$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'],
1083
+            ['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1084
+            ['Every Month on the %1$s for the entire day', ['First Sunday, Saturday'], 'Every Month on the First Sunday, Saturday for the entire day'],
1085
+            ['Every Month on the %1$s for the entire day until %2$s', ['First Sunday, Saturday', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024'],
1086
+            ['Every Month on the %1$s between %2$s - %3$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1087
+            ['Every Month on the %1$s between %2$s - %3$s until %4$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1088
+            ['Every %1$d Months on the %2$s for the entire day', [2, 'First Sunday, Saturday'], 'Every 2 Months on the First Sunday, Saturday for the entire day'],
1089
+            ['Every %1$d Months on the %2$s for the entire day until %3$s', [2, 'First Sunday, Saturday', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024'],
1090
+            ['Every %1$d Months on the %2$s between %3$s - %4$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1091
+            ['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'],
1092
+            ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
1093
+            ['Saturday', [], 'Saturday'],
1094
+            ['Sunday', [], 'Sunday'],
1095
+            ['First', [], 'First'],
1096
+        ]);
1097
+
1098
+        /** test absolute partial day event with every month interval on 1st, 8th and no conclusion*/
1099
+        $vCalendar = clone $this->vCalendar1a;
1100
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;');
1101
+        // construct event reader
1102
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1103
+        // test output
1104
+        $this->assertEquals(
1105
+            'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)',
1106
+            $this->service->generateWhenString($eventReader)
1107
+        );
1108
+
1109
+        /** test absolute partial day event with every Month interval on 1st, 8th and conclusion*/
1110
+        $vCalendar = clone $this->vCalendar1a;
1111
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;');
1112
+        // construct event reader
1113
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1114
+        // test output
1115
+        $this->assertEquals(
1116
+            'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1117
+            $this->service->generateWhenString($eventReader)
1118
+        );
1119
+
1120
+        /** test absolute partial day event with every 2nd Month interval on 1st, 8th and no conclusion*/
1121
+        $vCalendar = clone $this->vCalendar1a;
1122
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;');
1123
+        // construct event reader
1124
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1125
+        // test output
1126
+        $this->assertEquals(
1127
+            'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)',
1128
+            $this->service->generateWhenString($eventReader)
1129
+        );
1130
+
1131
+        /** test absolute partial day event with every 2nd Month interval on 1st, 8th and conclusion*/
1132
+        $vCalendar = clone $this->vCalendar1a;
1133
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;');
1134
+        // construct event reader
1135
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1136
+        // test output
1137
+        $this->assertEquals(
1138
+            'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1139
+            $this->service->generateWhenString($eventReader)
1140
+        );
1141
+
1142
+        /** test absolute entire day event with every Month interval on 1st, 8th and no conclusion*/
1143
+        $vCalendar = clone $this->vCalendar2;
1144
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;');
1145
+        // construct event reader
1146
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1147
+        // test output
1148
+        $this->assertEquals(
1149
+            'Every Month on the 1, 8 for the entire day',
1150
+            $this->service->generateWhenString($eventReader)
1151
+        );
1152
+
1153
+        /** test absolute entire day event with every Month interval on 1st, 8th and conclusion*/
1154
+        $vCalendar = clone $this->vCalendar2;
1155
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;');
1156
+        // construct event reader
1157
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1158
+        // test output
1159
+        $this->assertEquals(
1160
+            'Every Month on the 1, 8 for the entire day until December 31, 2024',
1161
+            $this->service->generateWhenString($eventReader)
1162
+        );
1163
+
1164
+        /** test absolute entire day event with every 2nd Month interval on 1st, 8th and no conclusion*/
1165
+        $vCalendar = clone $this->vCalendar2;
1166
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;');
1167
+        // construct event reader
1168
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1169
+        // test output
1170
+        $this->assertEquals(
1171
+            'Every 2 Months on the 1, 8 for the entire day',
1172
+            $this->service->generateWhenString($eventReader)
1173
+        );
1174
+
1175
+        /** test absolute entire day event with every 2nd Month interval on 1st, 8th and conclusion*/
1176
+        $vCalendar = clone $this->vCalendar2;
1177
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;');
1178
+        // construct event reader
1179
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1180
+        // test output
1181
+        $this->assertEquals(
1182
+            'Every 2 Months on the 1, 8 for the entire day until December 31, 2024',
1183
+            $this->service->generateWhenString($eventReader)
1184
+        );
1185
+
1186
+        /** test relative partial day event with every month interval on the 1st Saturday, Sunday and no conclusion*/
1187
+        $vCalendar = clone $this->vCalendar1a;
1188
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;');
1189
+        // construct event reader
1190
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1191
+        // test output
1192
+        $this->assertEquals(
1193
+            'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1194
+            $this->service->generateWhenString($eventReader)
1195
+        );
1196
+
1197
+        /** test relative partial day event with every Month interval on the 1st Saturday, Sunday and conclusion*/
1198
+        $vCalendar = clone $this->vCalendar1a;
1199
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;');
1200
+        // construct event reader
1201
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1202
+        // test output
1203
+        $this->assertEquals(
1204
+            'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1205
+            $this->service->generateWhenString($eventReader)
1206
+        );
1207
+
1208
+        /** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/
1209
+        $vCalendar = clone $this->vCalendar1a;
1210
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1211
+        // construct event reader
1212
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1213
+        // test output
1214
+        $this->assertEquals(
1215
+            'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1216
+            $this->service->generateWhenString($eventReader)
1217
+        );
1218
+
1219
+        /** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/
1220
+        $vCalendar = clone $this->vCalendar1a;
1221
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;');
1222
+        // construct event reader
1223
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1224
+        // test output
1225
+        $this->assertEquals(
1226
+            'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024',
1227
+            $this->service->generateWhenString($eventReader)
1228
+        );
1229
+
1230
+        /** test relative entire day event with every Month interval on the 1st Saturday, Sunday and no conclusion*/
1231
+        $vCalendar = clone $this->vCalendar2;
1232
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;');
1233
+        // construct event reader
1234
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1235
+        // test output
1236
+        $this->assertEquals(
1237
+            'Every Month on the First Sunday, Saturday for the entire day',
1238
+            $this->service->generateWhenString($eventReader)
1239
+        );
1240
+
1241
+        /** test relative entire day event with every Month interval on the 1st Saturday, Sunday and conclusion*/
1242
+        $vCalendar = clone $this->vCalendar2;
1243
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;');
1244
+        // construct event reader
1245
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1246
+        // test output
1247
+        $this->assertEquals(
1248
+            'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024',
1249
+            $this->service->generateWhenString($eventReader)
1250
+        );
1251
+
1252
+        /** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/
1253
+        $vCalendar = clone $this->vCalendar2;
1254
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1255
+        // construct event reader
1256
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1257
+        // test output
1258
+        $this->assertEquals(
1259
+            'Every 2 Months on the First Sunday, Saturday for the entire day',
1260
+            $this->service->generateWhenString($eventReader)
1261
+        );
1262
+
1263
+        /** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/
1264
+        $vCalendar = clone $this->vCalendar2;
1265
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;');
1266
+        // construct event reader
1267
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1268
+        // test output
1269
+        $this->assertEquals(
1270
+            'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024',
1271
+            $this->service->generateWhenString($eventReader)
1272
+        );
1273
+
1274
+    }
1275
+
1276
+    public function testGenerateWhenStringRecurringYearly(): void {
1277
+
1278
+        // construct l10n return maps
1279
+        $this->l10n->method('l')->willReturnCallback(
1280
+            function ($v1, $v2, $v3) {
1281
+                return match (true) {
1282
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1283
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1284
+                    $v1 === 'date' && $v2 == (new \DateTime('20260731T040000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 31, 2026'
1285
+                };
1286
+            }
1287
+        );
1288
+        $this->l10n->method('t')->willReturnMap([
1289
+            ['Every Year in %1$s on the %2$s for the entire day', ['July', '1st'], 'Every Year in July on the 1st for the entire day'],
1290
+            ['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', '1st', 'July 31, 2026'], 'Every Year in July on the 1st for the entire day until July 31, 2026'],
1291
+            ['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'],
1292
+            ['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1293
+            ['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', '1st'], 'Every 2 Years in July on the 1st for the entire day'],
1294
+            ['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', '1st', 'July 31, 2026'], 'Every 2 Years in July on the 1st for the entire day until July 31, 2026'],
1295
+            ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'],
1296
+            ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1297
+            ['Every Year in %1$s on the %2$s for the entire day', ['July', 'First Sunday, Saturday'], 'Every Year in July on the First Sunday, Saturday for the entire day'],
1298
+            ['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026'],
1299
+            ['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1300
+            ['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1301
+            ['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', 'First Sunday, Saturday'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day'],
1302
+            ['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026'],
1303
+            ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'],
1304
+            ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'],
1305
+            ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'],
1306
+            ['July', [], 'July'],
1307
+            ['Saturday', [], 'Saturday'],
1308
+            ['Sunday', [], 'Sunday'],
1309
+            ['First', [], 'First'],
1310
+        ]);
1311
+
1312
+        /** test absolute partial day event with every year interval on July 1 and no conclusion*/
1313
+        $vCalendar = clone $this->vCalendar1a;
1314
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;');
1315
+        // construct event reader
1316
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1317
+        // test output
1318
+        $this->assertEquals(
1319
+            'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)',
1320
+            $this->service->generateWhenString($eventReader)
1321
+        );
1322
+
1323
+        /** test absolute partial day event with every year interval on July 1 and conclusion*/
1324
+        $vCalendar = clone $this->vCalendar1a;
1325
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z');
1326
+        // construct event reader
1327
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1328
+        // test output
1329
+        $this->assertEquals(
1330
+            'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1331
+            $this->service->generateWhenString($eventReader)
1332
+        );
1333
+
1334
+        /** test absolute partial day event with every 2nd year interval on July 1 and no conclusion*/
1335
+        $vCalendar = clone $this->vCalendar1a;
1336
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;');
1337
+        // construct event reader
1338
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1339
+        // test output
1340
+        $this->assertEquals(
1341
+            'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)',
1342
+            $this->service->generateWhenString($eventReader)
1343
+        );
1344
+
1345
+        /** test absolute partial day event with every 2nd year interval on July 1 and conclusion*/
1346
+        $vCalendar = clone $this->vCalendar1a;
1347
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;');
1348
+        // construct event reader
1349
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1350
+        // test output
1351
+        $this->assertEquals(
1352
+            'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1353
+            $this->service->generateWhenString($eventReader)
1354
+        );
1355
+
1356
+        /** test absolute entire day event with every year interval on July 1 and no conclusion*/
1357
+        $vCalendar = clone $this->vCalendar2;
1358
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;');
1359
+        // construct event reader
1360
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1361
+        // test output
1362
+        $this->assertEquals(
1363
+            'Every Year in July on the 1st for the entire day',
1364
+            $this->service->generateWhenString($eventReader)
1365
+        );
1366
+
1367
+        /** test absolute entire day event with every year interval on July 1 and conclusion*/
1368
+        $vCalendar = clone $this->vCalendar2;
1369
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z;');
1370
+        // construct event reader
1371
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1372
+        // test output
1373
+        $this->assertEquals(
1374
+            'Every Year in July on the 1st for the entire day until July 31, 2026',
1375
+            $this->service->generateWhenString($eventReader)
1376
+        );
1377
+
1378
+        /** test absolute entire day event with every 2nd year interval on July 1 and no conclusion*/
1379
+        $vCalendar = clone $this->vCalendar2;
1380
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;');
1381
+        // construct event reader
1382
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1383
+        // test output
1384
+        $this->assertEquals(
1385
+            'Every 2 Years in July on the 1st for the entire day',
1386
+            $this->service->generateWhenString($eventReader)
1387
+        );
1388
+
1389
+        /** test absolute entire day event with every 2nd year interval on July 1 and conclusion*/
1390
+        $vCalendar = clone $this->vCalendar2;
1391
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;');
1392
+        // construct event reader
1393
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1394
+        // test output
1395
+        $this->assertEquals(
1396
+            'Every 2 Years in July on the 1st for the entire day until July 31, 2026',
1397
+            $this->service->generateWhenString($eventReader)
1398
+        );
1399
+
1400
+        /** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/
1401
+        $vCalendar = clone $this->vCalendar1a;
1402
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;');
1403
+        // construct event reader
1404
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1405
+        // test output
1406
+        $this->assertEquals(
1407
+            'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1408
+            $this->service->generateWhenString($eventReader)
1409
+        );
1410
+
1411
+        /** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/
1412
+        $vCalendar = clone $this->vCalendar1a;
1413
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;');
1414
+        // construct event reader
1415
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1416
+        // test output
1417
+        $this->assertEquals(
1418
+            'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1419
+            $this->service->generateWhenString($eventReader)
1420
+        );
1421
+
1422
+        /** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/
1423
+        $vCalendar = clone $this->vCalendar1a;
1424
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1425
+        // construct event reader
1426
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1427
+        // test output
1428
+        $this->assertEquals(
1429
+            'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)',
1430
+            $this->service->generateWhenString($eventReader)
1431
+        );
1432
+
1433
+        /** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/
1434
+        $vCalendar = clone $this->vCalendar1a;
1435
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;');
1436
+        // construct event reader
1437
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1438
+        // test output
1439
+        $this->assertEquals(
1440
+            'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026',
1441
+            $this->service->generateWhenString($eventReader)
1442
+        );
1443
+
1444
+        /** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/
1445
+        $vCalendar = clone $this->vCalendar2;
1446
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;');
1447
+        // construct event reader
1448
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1449
+        // test output
1450
+        $this->assertEquals(
1451
+            'Every Year in July on the First Sunday, Saturday for the entire day',
1452
+            $this->service->generateWhenString($eventReader)
1453
+        );
1454
+
1455
+        /** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/
1456
+        $vCalendar = clone $this->vCalendar2;
1457
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;');
1458
+        // construct event reader
1459
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1460
+        // test output
1461
+        $this->assertEquals(
1462
+            'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026',
1463
+            $this->service->generateWhenString($eventReader)
1464
+        );
1465
+
1466
+        /** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/
1467
+        $vCalendar = clone $this->vCalendar2;
1468
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;');
1469
+        // construct event reader
1470
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1471
+        // test output
1472
+        $this->assertEquals(
1473
+            'Every 2 Years in July on the First Sunday, Saturday for the entire day',
1474
+            $this->service->generateWhenString($eventReader)
1475
+        );
1476
+
1477
+        /** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/
1478
+        $vCalendar = clone $this->vCalendar2;
1479
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;');
1480
+        // construct event reader
1481
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1482
+        // test output
1483
+        $this->assertEquals(
1484
+            'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026',
1485
+            $this->service->generateWhenString($eventReader)
1486
+        );
1487
+
1488
+    }
1489
+
1490
+    public function testGenerateWhenStringRecurringFixed(): void {
1491
+
1492
+        // construct l10n return maps
1493
+        $this->l10n->method('l')->willReturnCallback(
1494
+            function ($v1, $v2, $v3) {
1495
+                return match (true) {
1496
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM',
1497
+                    $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM',
1498
+                    $v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 13, 2024'
1499
+                };
1500
+            }
1501
+        );
1502
+        $this->l10n->method('t')->willReturnMap([
1503
+            ['On specific dates for the entire day until %1$s', ['July 13, 2024'], 'On specific dates for the entire day until July 13, 2024'],
1504
+            ['On specific dates between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'],
1505
+        ]);
1506
+
1507
+        /** test partial day event with every day interval and conclusion*/
1508
+        $vCalendar = clone $this->vCalendar1a;
1509
+        $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000');
1510
+        // construct event reader
1511
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1512
+        // test output
1513
+        $this->assertEquals(
1514
+            'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024',
1515
+            $this->service->generateWhenString($eventReader)
1516
+        );
1517
+
1518
+        /** test entire day event with every day interval and no conclusion*/
1519
+        $vCalendar = clone $this->vCalendar2;
1520
+        $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000');
1521
+        // construct event reader
1522
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1523
+        // test output
1524
+        $this->assertEquals(
1525
+            'On specific dates for the entire day until July 13, 2024',
1526
+            $this->service->generateWhenString($eventReader)
1527
+        );
1528
+
1529
+    }
1530
+
1531
+    public function testGenerateOccurringStringWithRrule(): void {
1532
+
1533
+        // construct l10n return(s)
1534
+        $this->l10n->method('l')->willReturnCallback(
1535
+            function ($v1, $v2, $v3) {
1536
+                return match (true) {
1537
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1538
+                    $v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024',
1539
+                    $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024'
1540
+                };
1541
+            }
1542
+        );
1543
+        $this->l10n->method('n')->willReturnMap([
1544
+            // singular
1545
+            [
1546
+                'In a day on %1$s',
1547
+                'In %n days on %1$s',
1548
+                1,
1549
+                ['July 1, 2024'],
1550
+                'In a day on July 1, 2024'
1551
+            ],
1552
+            [
1553
+                'In a day on %1$s then on %2$s',
1554
+                'In %n days on %1$s then on %2$s',
1555
+                1,
1556
+                ['July 1, 2024', 'July 3, 2024'],
1557
+                'In a day on July 1, 2024 then on July 3, 2024'
1558
+            ],
1559
+            [
1560
+                'In a day on %1$s then on %2$s and %3$s',
1561
+                'In %n days on %1$s then on %2$s and %3$s',
1562
+                1,
1563
+                ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1564
+                'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1565
+            ],
1566
+            // plural
1567
+            [
1568
+                'In a day on %1$s',
1569
+                'In %n days on %1$s',
1570
+                2,
1571
+                ['July 1, 2024'],
1572
+                'In 2 days on July 1, 2024'
1573
+            ],
1574
+            [
1575
+                'In a day on %1$s then on %2$s',
1576
+                'In %n days on %1$s then on %2$s',
1577
+                2,
1578
+                ['July 1, 2024', 'July 3, 2024'],
1579
+                'In 2 days on July 1, 2024 then on July 3, 2024'
1580
+            ],
1581
+            [
1582
+                'In a day on %1$s then on %2$s and %3$s',
1583
+                'In %n days on %1$s then on %2$s and %3$s',
1584
+                2,
1585
+                ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1586
+                'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1587
+            ],
1588
+        ]);
1589
+
1590
+        // construct time factory return(s)
1591
+        $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1592
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1593
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1594
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1595
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1596
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1597
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1598
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1599
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1600
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1601
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1602
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1603
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1604
+        );
1605
+
1606
+        /** test patrial day recurring event in 1 day with single occurrence remaining */
1607
+        $vCalendar = clone $this->vCalendar1a;
1608
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1609
+        // construct event reader
1610
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1611
+        // test output
1612
+        $this->assertEquals(
1613
+            'In a day on July 1, 2024',
1614
+            $this->service->generateOccurringString($eventReader)
1615
+        );
1616
+
1617
+        /** test patrial day recurring event in 1 day with two occurrences remaining */
1618
+        $vCalendar = clone $this->vCalendar1a;
1619
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1620
+        // construct event reader
1621
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1622
+        // test output
1623
+        $this->assertEquals(
1624
+            'In a day on July 1, 2024 then on July 3, 2024',
1625
+            $this->service->generateOccurringString($eventReader)
1626
+        );
1627
+
1628
+        /** test patrial day recurring event in 1 day with three occurrences remaining */
1629
+        $vCalendar = clone $this->vCalendar1a;
1630
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1631
+        // construct event reader
1632
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1633
+        // test output
1634
+        $this->assertEquals(
1635
+            'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1636
+            $this->service->generateOccurringString($eventReader)
1637
+        );
1638
+
1639
+        /** test patrial day recurring event in 2 days with single occurrence remaining */
1640
+        $vCalendar = clone $this->vCalendar1a;
1641
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1642
+        // construct event reader
1643
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1644
+        // test output
1645
+        $this->assertEquals(
1646
+            'In 2 days on July 1, 2024',
1647
+            $this->service->generateOccurringString($eventReader)
1648
+        );
1649
+
1650
+        /** test patrial day recurring event in 2 days with two occurrences remaining */
1651
+        $vCalendar = clone $this->vCalendar1a;
1652
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1653
+        // construct event reader
1654
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1655
+        // test output
1656
+        $this->assertEquals(
1657
+            'In 2 days on July 1, 2024 then on July 3, 2024',
1658
+            $this->service->generateOccurringString($eventReader)
1659
+        );
1660
+
1661
+        /** test patrial day recurring event in 2 days with three occurrences remaining */
1662
+        $vCalendar = clone $this->vCalendar1a;
1663
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1664
+        // construct event reader
1665
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1666
+        // test output
1667
+        $this->assertEquals(
1668
+            'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1669
+            $this->service->generateOccurringString($eventReader)
1670
+        );
1671
+    }
1672
+
1673
+    public function testGenerateOccurringStringWithRdate(): void {
1674
+
1675
+        // construct l10n return(s)
1676
+        $this->l10n->method('l')->willReturnCallback(
1677
+            function ($v1, $v2, $v3) {
1678
+                return match (true) {
1679
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1680
+                    $v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024',
1681
+                    $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024'
1682
+                };
1683
+            }
1684
+        );
1685
+        $this->l10n->method('n')->willReturnMap([
1686
+            // singular
1687
+            [
1688
+                'In a day on %1$s',
1689
+                'In %n days on %1$s',
1690
+                1,
1691
+                ['July 1, 2024'],
1692
+                'In a day on July 1, 2024'
1693
+            ],
1694
+            [
1695
+                'In a day on %1$s then on %2$s',
1696
+                'In %n days on %1$s then on %2$s',
1697
+                1,
1698
+                ['July 1, 2024', 'July 3, 2024'],
1699
+                'In a day on July 1, 2024 then on July 3, 2024'
1700
+            ],
1701
+            [
1702
+                'In a day on %1$s then on %2$s and %3$s',
1703
+                'In %n days on %1$s then on %2$s and %3$s',
1704
+                1,
1705
+                ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1706
+                'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1707
+            ],
1708
+            // plural
1709
+            [
1710
+                'In a day on %1$s',
1711
+                'In %n days on %1$s',
1712
+                2,
1713
+                ['July 1, 2024'],
1714
+                'In 2 days on July 1, 2024'
1715
+            ],
1716
+            [
1717
+                'In a day on %1$s then on %2$s',
1718
+                'In %n days on %1$s then on %2$s',
1719
+                2,
1720
+                ['July 1, 2024', 'July 3, 2024'],
1721
+                'In 2 days on July 1, 2024 then on July 3, 2024'
1722
+            ],
1723
+            [
1724
+                'In a day on %1$s then on %2$s and %3$s',
1725
+                'In %n days on %1$s then on %2$s and %3$s',
1726
+                2,
1727
+                ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'],
1728
+                'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024'
1729
+            ],
1730
+        ]);
1731
+
1732
+        // construct time factory return(s)
1733
+        $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1734
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1735
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1736
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1737
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1738
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1739
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1740
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1741
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1742
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1743
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1744
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1745
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1746
+        );
1747
+
1748
+        /** test patrial day recurring event in 1 day with single occurrence remaining */
1749
+        $vCalendar = clone $this->vCalendar1a;
1750
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1751
+        // construct event reader
1752
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1753
+        // test output
1754
+        $this->assertEquals(
1755
+            'In a day on July 1, 2024',
1756
+            $this->service->generateOccurringString($eventReader),
1757
+            'test patrial day recurring event in 1 day with single occurrence remaining'
1758
+        );
1759
+
1760
+        /** test patrial day recurring event in 1 day with two occurrences remaining */
1761
+        $vCalendar = clone $this->vCalendar1a;
1762
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000');
1763
+        // construct event reader
1764
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1765
+        // test output
1766
+        $this->assertEquals(
1767
+            'In a day on July 1, 2024 then on July 3, 2024',
1768
+            $this->service->generateOccurringString($eventReader),
1769
+            'test patrial day recurring event in 1 day with two occurrences remaining'
1770
+        );
1771
+
1772
+        /** test patrial day recurring event in 1 day with three occurrences remaining */
1773
+        $vCalendar = clone $this->vCalendar1a;
1774
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000,20240705T080000');
1775
+        // construct event reader
1776
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1777
+        // test output
1778
+        $this->assertEquals(
1779
+            'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1780
+            $this->service->generateOccurringString($eventReader),
1781
+            ''
1782
+        );
1783
+
1784
+        /** test patrial day recurring event in 2 days with single occurrences remaining */
1785
+        $vCalendar = clone $this->vCalendar1a;
1786
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1787
+        // construct event reader
1788
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1789
+        // test output
1790
+        $this->assertEquals(
1791
+            'In 2 days on July 1, 2024',
1792
+            $this->service->generateOccurringString($eventReader),
1793
+            ''
1794
+        );
1795
+
1796
+        /** test patrial day recurring event in 2 days with two occurrences remaining */
1797
+        $vCalendar = clone $this->vCalendar1a;
1798
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1799
+        $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000');
1800
+        // construct event reader
1801
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1802
+        // test output
1803
+        $this->assertEquals(
1804
+            'In 2 days on July 1, 2024 then on July 3, 2024',
1805
+            $this->service->generateOccurringString($eventReader),
1806
+            ''
1807
+        );
1808
+
1809
+        /** test patrial day recurring event in 2 days with three occurrences remaining */
1810
+        $vCalendar = clone $this->vCalendar1a;
1811
+        $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000');
1812
+        $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000');
1813
+        $vCalendar->VEVENT[0]->add('RDATE', '20240705T080000');
1814
+        // construct event reader
1815
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1816
+        // test output
1817
+        $this->assertEquals(
1818
+            'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024',
1819
+            $this->service->generateOccurringString($eventReader),
1820
+            'test patrial day recurring event in 2 days with three occurrences remaining'
1821
+        );
1822
+    }
1823
+
1824
+    public function testGenerateOccurringStringWithOneExdate(): void {
1825
+
1826
+        // construct l10n return(s)
1827
+        $this->l10n->method('l')->willReturnCallback(
1828
+            function ($v1, $v2, $v3) {
1829
+                return match (true) {
1830
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
1831
+                    $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024',
1832
+                    $v1 === 'date' && $v2 == (new \DateTime('20240707T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 7, 2024'
1833
+                };
1834
+            }
1835
+        );
1836
+        $this->l10n->method('n')->willReturnMap([
1837
+            // singular
1838
+            [
1839
+                'In a day on %1$s',
1840
+                'In %n days on %1$s',
1841
+                1,
1842
+                ['July 1, 2024'],
1843
+                'In a day on July 1, 2024'
1844
+            ],
1845
+            [
1846
+                'In a day on %1$s then on %2$s',
1847
+                'In %n days on %1$s then on %2$s',
1848
+                1,
1849
+                ['July 1, 2024', 'July 5, 2024'],
1850
+                'In a day on July 1, 2024 then on July 5, 2024'
1851
+            ],
1852
+            [
1853
+                'In a day on %1$s then on %2$s and %3$s',
1854
+                'In %n days on %1$s then on %2$s and %3$s',
1855
+                1,
1856
+                ['July 1, 2024', 'July 5, 2024', 'July 7, 2024'],
1857
+                'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024'
1858
+            ],
1859
+            // plural
1860
+            [
1861
+                'In a day on %1$s',
1862
+                'In %n days on %1$s',
1863
+                2,
1864
+                ['July 1, 2024'],
1865
+                'In 2 days on July 1, 2024'
1866
+            ],
1867
+            [
1868
+                'In a day on %1$s then on %2$s',
1869
+                'In %n days on %1$s then on %2$s',
1870
+                2,
1871
+                ['July 1, 2024', 'July 5, 2024'],
1872
+                'In 2 days on July 1, 2024 then on July 5, 2024'
1873
+            ],
1874
+            [
1875
+                'In a day on %1$s then on %2$s and %3$s',
1876
+                'In %n days on %1$s then on %2$s and %3$s',
1877
+                2,
1878
+                ['July 1, 2024', 'July 5, 2024', 'July 7, 2024'],
1879
+                'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024'
1880
+            ],
1881
+        ]);
1882
+
1883
+        // construct time factory return(s)
1884
+        $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
1885
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1886
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1887
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1888
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1889
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1890
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1891
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1892
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
1893
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1894
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1895
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1896
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1897
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1898
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1899
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1900
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
1901
+        );
1902
+
1903
+        /** test patrial day recurring event in 1 day with single occurrence remaining and one exception */
1904
+        $vCalendar = clone $this->vCalendar1a;
1905
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1906
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1907
+        // construct event reader
1908
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1909
+        // test output
1910
+        $this->assertEquals(
1911
+            'In a day on July 1, 2024',
1912
+            $this->service->generateOccurringString($eventReader),
1913
+            'test patrial day recurring event in 1 day with single occurrence remaining and one exception'
1914
+        );
1915
+
1916
+        /** test patrial day recurring event in 1 day with two occurrences remaining and one exception */
1917
+        $vCalendar = clone $this->vCalendar1a;
1918
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1919
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1920
+        // construct event reader
1921
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1922
+        // test output
1923
+        $this->assertEquals(
1924
+            'In a day on July 1, 2024',
1925
+            $this->service->generateOccurringString($eventReader),
1926
+            'test patrial day recurring event in 1 day with two occurrences remaining and one exception'
1927
+        );
1928
+
1929
+        /** test patrial day recurring event in 1 day with three occurrences remaining and one exception */
1930
+        $vCalendar = clone $this->vCalendar1a;
1931
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1932
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1933
+        // construct event reader
1934
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1935
+        // test output
1936
+        $this->assertEquals(
1937
+            'In a day on July 1, 2024 then on July 5, 2024',
1938
+            $this->service->generateOccurringString($eventReader),
1939
+            'test patrial day recurring event in 1 day with three occurrences remaining and one exception'
1940
+        );
1941
+
1942
+        /** test patrial day recurring event in 1 day with four occurrences remaining and one exception */
1943
+        $vCalendar = clone $this->vCalendar1a;
1944
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4');
1945
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1946
+        // construct event reader
1947
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1948
+        // test output
1949
+        $this->assertEquals(
1950
+            'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024',
1951
+            $this->service->generateOccurringString($eventReader),
1952
+            'test patrial day recurring event in 1 day with four occurrences remaining and one exception'
1953
+        );
1954
+
1955
+        /** test patrial day recurring event in 2 days with single occurrences remaining and one exception */
1956
+        $vCalendar = clone $this->vCalendar1a;
1957
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
1958
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1959
+        // construct event reader
1960
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1961
+        // test output
1962
+        $this->assertEquals(
1963
+            'In 2 days on July 1, 2024',
1964
+            $this->service->generateOccurringString($eventReader),
1965
+            'test patrial day recurring event in 2 days with single occurrences remaining and one exception'
1966
+        );
1967
+
1968
+        /** test patrial day recurring event in 2 days with two occurrences remaining and one exception */
1969
+        $vCalendar = clone $this->vCalendar1a;
1970
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
1971
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1972
+        // construct event reader
1973
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1974
+        // test output
1975
+        $this->assertEquals(
1976
+            'In 2 days on July 1, 2024',
1977
+            $this->service->generateOccurringString($eventReader),
1978
+            'test patrial day recurring event in 2 days with two occurrences remaining and one exception'
1979
+        );
1980
+
1981
+        /** test patrial day recurring event in 2 days with three occurrences remaining and one exception */
1982
+        $vCalendar = clone $this->vCalendar1a;
1983
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
1984
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1985
+        // construct event reader
1986
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
1987
+        // test output
1988
+        $this->assertEquals(
1989
+            'In 2 days on July 1, 2024 then on July 5, 2024',
1990
+            $this->service->generateOccurringString($eventReader),
1991
+            'test patrial day recurring event in 2 days with three occurrences remaining and one exception'
1992
+        );
1993
+
1994
+        /** test patrial day recurring event in 2 days with four occurrences remaining and one exception */
1995
+        $vCalendar = clone $this->vCalendar1a;
1996
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4');
1997
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
1998
+        // construct event reader
1999
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2000
+        // test output
2001
+        $this->assertEquals(
2002
+            'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024',
2003
+            $this->service->generateOccurringString($eventReader),
2004
+            'test patrial day recurring event in 2 days with four occurrences remaining and one exception'
2005
+        );
2006
+    }
2007
+
2008
+    public function testGenerateOccurringStringWithTwoExdate(): void {
2009
+
2010
+        // construct l10n return(s)
2011
+        $this->l10n->method('l')->willReturnCallback(
2012
+            function ($v1, $v2, $v3) {
2013
+                return match (true) {
2014
+                    $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024',
2015
+                    $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024',
2016
+                    $v1 === 'date' && $v2 == (new \DateTime('20240709T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 9, 2024'
2017
+                };
2018
+            }
2019
+        );
2020
+        $this->l10n->method('n')->willReturnMap([
2021
+            // singular
2022
+            [
2023
+                'In a day on %1$s',
2024
+                'In %n days on %1$s',
2025
+                1,
2026
+                ['July 1, 2024'],
2027
+                'In a day on July 1, 2024'
2028
+            ],
2029
+            [
2030
+                'In a day on %1$s then on %2$s',
2031
+                'In %n days on %1$s then on %2$s',
2032
+                1,
2033
+                ['July 1, 2024', 'July 5, 2024'],
2034
+                'In a day on July 1, 2024 then on July 5, 2024'
2035
+            ],
2036
+            [
2037
+                'In a day on %1$s then on %2$s and %3$s',
2038
+                'In %n days on %1$s then on %2$s and %3$s',
2039
+                1,
2040
+                ['July 1, 2024', 'July 5, 2024', 'July 9, 2024'],
2041
+                'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024'
2042
+            ],
2043
+            // plural
2044
+            [
2045
+                'In a day on %1$s',
2046
+                'In %n days on %1$s',
2047
+                2,
2048
+                ['July 1, 2024'],
2049
+                'In 2 days on July 1, 2024'
2050
+            ],
2051
+            [
2052
+                'In a day on %1$s then on %2$s',
2053
+                'In %n days on %1$s then on %2$s',
2054
+                2,
2055
+                ['July 1, 2024', 'July 5, 2024'],
2056
+                'In 2 days on July 1, 2024 then on July 5, 2024'
2057
+            ],
2058
+            [
2059
+                'In a day on %1$s then on %2$s and %3$s',
2060
+                'In %n days on %1$s then on %2$s and %3$s',
2061
+                2,
2062
+                ['July 1, 2024', 'July 5, 2024', 'July 9, 2024'],
2063
+                'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024'
2064
+            ],
2065
+        ]);
2066
+
2067
+        // construct time factory return(s)
2068
+        $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls(
2069
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2070
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2071
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2072
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2073
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2074
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2075
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2076
+            (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))),
2077
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2078
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2079
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2080
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2081
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2082
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2083
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2084
+            (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))),
2085
+        );
2086
+
2087
+        /** test patrial day recurring event in 1 day with single occurrence remaining and two exception */
2088
+        $vCalendar = clone $this->vCalendar1a;
2089
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
2090
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2091
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2092
+        // construct event reader
2093
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2094
+        // test output
2095
+        $this->assertEquals(
2096
+            'In a day on July 1, 2024',
2097
+            $this->service->generateOccurringString($eventReader),
2098
+            'test patrial day recurring event in 1 day with single occurrence remaining and two exception'
2099
+        );
2100
+
2101
+        /** test patrial day recurring event in 1 day with two occurrences remaining and two exception */
2102
+        $vCalendar = clone $this->vCalendar1a;
2103
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
2104
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2105
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2106
+        // construct event reader
2107
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2108
+        // test output
2109
+        $this->assertEquals(
2110
+            'In a day on July 1, 2024',
2111
+            $this->service->generateOccurringString($eventReader),
2112
+            'test patrial day recurring event in 1 day with two occurrences remaining and two exception'
2113
+        );
2114
+
2115
+        /** test patrial day recurring event in 1 day with three occurrences remaining and two exception */
2116
+        $vCalendar = clone $this->vCalendar1a;
2117
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
2118
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2119
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2120
+        // construct event reader
2121
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2122
+        // test output
2123
+        $this->assertEquals(
2124
+            'In a day on July 1, 2024 then on July 5, 2024',
2125
+            $this->service->generateOccurringString($eventReader),
2126
+            'test patrial day recurring event in 1 day with three occurrences remaining and two exception'
2127
+        );
2128
+
2129
+        /** test patrial day recurring event in 1 day with four occurrences remaining and two exception */
2130
+        $vCalendar = clone $this->vCalendar1a;
2131
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5');
2132
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2133
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2134
+        // construct event reader
2135
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2136
+        // test output
2137
+        $this->assertEquals(
2138
+            'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024',
2139
+            $this->service->generateOccurringString($eventReader),
2140
+            'test patrial day recurring event in 1 day with four occurrences remaining and two exception'
2141
+        );
2142
+
2143
+        /** test patrial day recurring event in 2 days with single occurrences remaining and two exception */
2144
+        $vCalendar = clone $this->vCalendar1a;
2145
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1');
2146
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2147
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2148
+        // construct event reader
2149
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2150
+        // test output
2151
+        $this->assertEquals(
2152
+            'In 2 days on July 1, 2024',
2153
+            $this->service->generateOccurringString($eventReader),
2154
+            'test patrial day recurring event in 2 days with single occurrences remaining and two exception'
2155
+        );
2156
+
2157
+        /** test patrial day recurring event in 2 days with two occurrences remaining and two exception */
2158
+        $vCalendar = clone $this->vCalendar1a;
2159
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2');
2160
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2161
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2162
+        // construct event reader
2163
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2164
+        // test output
2165
+        $this->assertEquals(
2166
+            'In 2 days on July 1, 2024',
2167
+            $this->service->generateOccurringString($eventReader),
2168
+            'test patrial day recurring event in 2 days with two occurrences remaining and two exception'
2169
+        );
2170
+
2171
+        /** test patrial day recurring event in 2 days with three occurrences remaining and two exception */
2172
+        $vCalendar = clone $this->vCalendar1a;
2173
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3');
2174
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2175
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2176
+        // construct event reader
2177
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2178
+        // test output
2179
+        $this->assertEquals(
2180
+            'In 2 days on July 1, 2024 then on July 5, 2024',
2181
+            $this->service->generateOccurringString($eventReader),
2182
+            'test patrial day recurring event in 2 days with three occurrences remaining and two exception'
2183
+        );
2184
+
2185
+        /** test patrial day recurring event in 2 days with five occurrences remaining and two exception */
2186
+        $vCalendar = clone $this->vCalendar1a;
2187
+        $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5');
2188
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000');
2189
+        $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000');
2190
+        // construct event reader
2191
+        $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue());
2192
+        // test output
2193
+        $this->assertEquals(
2194
+            'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024',
2195
+            $this->service->generateOccurringString($eventReader),
2196
+            'test patrial day recurring event in 2 days with five occurrences remaining and two exception'
2197
+        );
2198
+    }
2199 2199
 
2200 2200
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php 2 patches
Indentation   +733 added lines, -733 removed lines patch added patch discarded remove patch
@@ -36,737 +36,737 @@
 block discarded – undo
36 36
 use Test\TestCase;
37 37
 
38 38
 class PluginTest extends TestCase {
39
-	private Plugin $plugin;
40
-	private Server&MockObject $server;
41
-	private IConfig&MockObject $config;
42
-	private LoggerInterface&MockObject $logger;
43
-	private DefaultCalendarValidator $calendarValidator;
44
-
45
-	protected function setUp(): void {
46
-		parent::setUp();
47
-
48
-		$this->config = $this->createMock(IConfig::class);
49
-		$this->logger = $this->createMock(LoggerInterface::class);
50
-		$this->calendarValidator = new DefaultCalendarValidator();
51
-
52
-		$this->server = $this->createMock(Server::class);
53
-		$this->server->httpResponse = $this->createMock(ResponseInterface::class);
54
-		$this->server->xml = new Service();
55
-
56
-		$this->plugin = new Plugin($this->config, $this->logger, $this->calendarValidator);
57
-		$this->plugin->initialize($this->server);
58
-	}
59
-
60
-	public function testInitialize(): void {
61
-		$calls = [
62
-			// Sabre\CalDAV\Schedule\Plugin events
63
-			['method:POST', [$this->plugin, 'httpPost'], 100],
64
-			['propFind', [$this->plugin, 'propFind'], 100],
65
-			['propPatch', [$this->plugin, 'propPatch'], 100],
66
-			['calendarObjectChange', [$this->plugin, 'calendarObjectChange'], 100],
67
-			['beforeUnbind', [$this->plugin, 'beforeUnbind'], 100],
68
-			['schedule', [$this->plugin, 'scheduleLocalDelivery'], 100],
69
-			['getSupportedPrivilegeSet', [$this->plugin, 'getSupportedPrivilegeSet'], 100],
70
-			// OCA\DAV\CalDAV\Schedule\Plugin events
71
-			['propFind', [$this->plugin, 'propFindDefaultCalendarUrl'], 90],
72
-			['afterWriteContent', [$this->plugin, 'dispatchSchedulingResponses'], 100],
73
-			['afterCreateFile', [$this->plugin, 'dispatchSchedulingResponses'], 100],
74
-		];
75
-		$this->server->expects($this->exactly(count($calls)))
76
-			->method('on')
77
-			->willReturnCallback(function () use (&$calls) {
78
-				$expected = array_shift($calls);
79
-				$this->assertEquals($expected, func_get_args());
80
-			});
81
-
82
-		$this->plugin->initialize($this->server);
83
-	}
84
-
85
-	public function testGetAddressesForPrincipal(): void {
86
-		$href = $this->createMock(Href::class);
87
-		$href
88
-			->expects($this->once())
89
-			->method('getHrefs')
90
-			->willReturn(['[email protected]', '[email protected]']);
91
-		$this->server
92
-			->expects($this->once())
93
-			->method('getProperties')
94
-			->with(
95
-				'MyPrincipal',
96
-				[
97
-					'{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
98
-				]
99
-			)
100
-			->willReturn([
101
-				'{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => $href
102
-			]);
103
-
104
-		$result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
105
-		$this->assertSame(['[email protected]', '[email protected]'], $result);
106
-	}
107
-
108
-	public function testGetAddressesForPrincipalEmpty(): void {
109
-		$this->server
110
-			->expects($this->once())
111
-			->method('getProperties')
112
-			->with(
113
-				'MyPrincipal',
114
-				[
115
-					'{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
116
-				]
117
-			)
118
-			->willReturn(null);
119
-
120
-		$result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
121
-		$this->assertSame([], $result);
122
-	}
123
-
124
-	public function testStripOffMailTo(): void {
125
-		$this->assertEquals('[email protected]', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['[email protected]']));
126
-		$this->assertEquals('[email protected]', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['mailto:[email protected]']));
127
-	}
128
-
129
-	public function testGetAttendeeRSVP(): void {
130
-		$property1 = $this->createMock(CalAddress::class);
131
-		$parameter1 = $this->createMock(Parameter::class);
132
-		$property1->expects($this->once())
133
-			->method('offsetGet')
134
-			->with('RSVP')
135
-			->willReturn($parameter1);
136
-		$parameter1->expects($this->once())
137
-			->method('getValue')
138
-			->with()
139
-			->willReturn('TRUE');
140
-
141
-		$property2 = $this->createMock(CalAddress::class);
142
-		$parameter2 = $this->createMock(Parameter::class);
143
-		$property2->expects($this->once())
144
-			->method('offsetGet')
145
-			->with('RSVP')
146
-			->willReturn($parameter2);
147
-		$parameter2->expects($this->once())
148
-			->method('getValue')
149
-			->with()
150
-			->willReturn('FALSE');
151
-
152
-		$property3 = $this->createMock(CalAddress::class);
153
-		$property3->expects($this->once())
154
-			->method('offsetGet')
155
-			->with('RSVP')
156
-			->willReturn(null);
157
-
158
-		$this->assertTrue($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property1]));
159
-		$this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property2]));
160
-		$this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property3]));
161
-	}
162
-
163
-	public static function propFindDefaultCalendarUrlProvider(): array {
164
-		return [
165
-			[
166
-				'principals/users/myuser',
167
-				'calendars/myuser',
168
-				false,
169
-				CalDavBackend::PERSONAL_CALENDAR_URI,
170
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
171
-				true
172
-			],
173
-			[
174
-				'principals/users/myuser',
175
-				'calendars/myuser',
176
-				false,
177
-				CalDavBackend::PERSONAL_CALENDAR_URI,
178
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
179
-				true,
180
-				true
181
-			],
182
-			[
183
-				'principals/users/myuser',
184
-				'calendars/myuser',
185
-				false,
186
-				CalDavBackend::PERSONAL_CALENDAR_URI,
187
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
188
-				false,
189
-				false,
190
-				true
191
-			],
192
-			[
193
-				'principals/users/myuser',
194
-				'calendars/myuser',
195
-				false,
196
-				CalDavBackend::PERSONAL_CALENDAR_URI,
197
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
198
-				false
199
-			],
200
-			[
201
-				'principals/users/myuser',
202
-				null,
203
-				false,
204
-				CalDavBackend::PERSONAL_CALENDAR_URI,
205
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
206
-				true
207
-			],
208
-			[
209
-				'principals/users/myuser',
210
-				'calendars/myuser',
211
-				false,
212
-				CalDavBackend::PERSONAL_CALENDAR_URI,
213
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
214
-				true,
215
-				false,
216
-				false,
217
-				false,
218
-			],
219
-			[
220
-				'principals/users/myuser',
221
-				'calendars/myuser',
222
-				false,
223
-				'my_other_calendar',
224
-				'My Other Calendar',
225
-				true
226
-			],
227
-			[
228
-				'principals/calendar-resources',
229
-				'system-calendars/calendar-resources/myuser',
230
-				true,
231
-				CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
232
-				CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
233
-				true
234
-			],
235
-			[
236
-				'principals/calendar-resources',
237
-				'system-calendars/calendar-resources/myuser',
238
-				true,
239
-				CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
240
-				CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
241
-				false
242
-			],
243
-			[
244
-				'principals/something-else',
245
-				'calendars/whatever',
246
-				false,
247
-				CalDavBackend::PERSONAL_CALENDAR_URI,
248
-				CalDavBackend::PERSONAL_CALENDAR_NAME,
249
-				true
250
-			],
251
-		];
252
-	}
253
-
254
-	/**
255
-	 * @dataProvider propFindDefaultCalendarUrlProvider
256
-	 */
257
-	public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void {
258
-		$propFind = new PropFind(
259
-			$principalUri,
260
-			[
261
-				Plugin::SCHEDULE_DEFAULT_CALENDAR_URL
262
-			],
263
-			0
264
-		);
265
-		/** @var IPrincipal&MockObject $node */
266
-		$node = $this->getMockBuilder(IPrincipal::class)
267
-			->disableOriginalConstructor()
268
-			->getMock();
269
-
270
-		$node->expects($this->once())
271
-			->method('getPrincipalUrl')
272
-			->with()
273
-			->willReturn($principalUri);
274
-
275
-		$calDAVPlugin = $this->getMockBuilder(CalDAVPlugin::class)
276
-			->disableOriginalConstructor()
277
-			->getMock();
278
-
279
-		$calDAVPlugin->expects($this->once())
280
-			->method('getCalendarHomeForPrincipal')
281
-			->willReturn($calendarHome);
282
-
283
-		$this->server->expects($this->once())
284
-			->method('getPlugin')
285
-			->with('caldav')
286
-			->willReturn($calDAVPlugin);
287
-		if (!$calendarHome) {
288
-			$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
289
-
290
-			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
291
-			return;
292
-		}
293
-		if ($principalUri === 'principals/something-else') {
294
-			$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
295
-
296
-			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
297
-			return;
298
-		}
299
-
300
-		if (!$isResource) {
301
-			$this->config->expects($this->once())
302
-				->method('getUserValue')
303
-				->with('myuser', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI)
304
-				->willReturn($calendarUri);
305
-		}
306
-
307
-		$calendarHomeObject = $this->createMock(CalendarHome::class);
308
-		$calendarHomeObject->expects($this->once())
309
-			->method('childExists')
310
-			->with($calendarUri)
311
-			->willReturn($exists);
312
-
313
-		if ($exists) {
314
-			$calendar = $this->createMock(Calendar::class);
315
-			$calendar->expects($this->once())->method('isDeleted')->willReturn($deleted);
316
-			$calendarHomeObject->expects($deleted && !$hasExistingCalendars ? $this->exactly(2) : $this->once())->method('getChild')->with($calendarUri)->willReturn($calendar);
317
-		}
318
-
319
-		$calendarBackend = $this->createMock(CalDavBackend::class);
320
-		$calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri;
321
-		$displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName;
322
-
323
-		$existingCalendars = $hasExistingCalendars ? [
324
-			new Calendar(
325
-				$calendarBackend,
326
-				['uri' => 'deleted', '{DAV:}displayname' => 'A deleted calendar', TrashbinPlugin::PROPERTY_DELETED_AT => 42],
327
-				$this->createMock(IL10N::class),
328
-				$this->config,
329
-				$this->createMock(LoggerInterface::class)
330
-			),
331
-			new Calendar(
332
-				$calendarBackend,
333
-				['uri' => $calendarUri, '{DAV:}displayname' => $displayName],
334
-				$this->createMock(IL10N::class),
335
-				$this->config,
336
-				$this->createMock(LoggerInterface::class)
337
-			)
338
-		] : [];
339
-
340
-		if (!$exists || $deleted) {
341
-			if (!$hasExistingCalendars) {
342
-				$calendarBackend->expects($this->once())
343
-					->method('createCalendar')
344
-					->with($principalUri, $calendarUri, [
345
-						'{DAV:}displayname' => $displayName,
346
-					]);
347
-
348
-				$calendarHomeObject->expects($this->exactly($deleted ? 2 : 1))
349
-					->method('getCalDAVBackend')
350
-					->with()
351
-					->willReturn($calendarBackend);
352
-			}
353
-
354
-			if (!$isResource) {
355
-				$calendarHomeObject->expects($this->once())
356
-					->method('getChildren')
357
-					->with()
358
-					->willReturn($existingCalendars);
359
-			}
360
-		}
361
-
362
-		/** @var Tree&MockObject $tree */
363
-		$tree = $this->createMock(Tree::class);
364
-		$tree->expects($this->once())
365
-			->method('getNodeForPath')
366
-			->with($calendarHome)
367
-			->willReturn($calendarHomeObject);
368
-		$this->server->tree = $tree;
369
-
370
-		$properties = $propertiesForPath ? [
371
-			['href' => '/remote.php/dav/' . $calendarHome . '/' . $calendarUri]
372
-		] : [];
373
-
374
-		$this->server->expects($this->once())
375
-			->method('getPropertiesForPath')
376
-			->with($calendarHome . '/' . $calendarUri, [], 1)
377
-			->willReturn($properties);
378
-
379
-		$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
380
-
381
-		if (!$propertiesForPath) {
382
-			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
383
-			return;
384
-		}
385
-
386
-		/** @var LocalHref $result */
387
-		$result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
388
-		$this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref());
389
-	}
390
-
391
-	/**
392
-	 * Test Calendar Event Creation for Personal Calendar
393
-	 *
394
-	 * Should generate 2 messages for attendees User 2 and User External
395
-	 */
396
-	public function testCalendarObjectChangePersonalCalendarCreate(): void {
397
-
398
-		// define place holders
399
-		/** @var Message[] $iTipMessages */
400
-		$iTipMessages = [];
401
-		// construct calendar node
402
-		$calendarNode = new Calendar(
403
-			$this->createMock(BackendInterface::class),
404
-			[
405
-				'uri' => 'personal',
406
-				'principaluri' => 'principals/users/user1',
407
-				'{DAV:}displayname' => 'Calendar Shared By User1',
408
-			],
409
-			$this->createMock(IL10N::class),
410
-			$this->config,
411
-			$this->logger
412
-		);
413
-		// construct server request object
414
-		$request = new Request(
415
-			'PUT',
416
-			'/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
417
-		);
418
-		$request->setBaseUrl('/remote.php/dav/');
419
-		// construct server response object
420
-		$response = new Response();
421
-		// construct server tree object
422
-		$tree = $this->createMock(Tree::class);
423
-		$tree->expects($this->once())
424
-			->method('getNodeForPath')
425
-			->with('calendars/user1/personal')
426
-			->willReturn($calendarNode);
427
-		// construct server properties and returns
428
-		$this->server->httpRequest = $request;
429
-		$this->server->tree = $tree;
430
-		$this->server->expects($this->exactly(1))->method('getProperties')
431
-			->willReturnMap([
432
-				[
433
-					'principals/users/user1',
434
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
435
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
436
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
437
-					)]
438
-				]
439
-			]);
440
-		$this->server->expects($this->exactly(2))->method('emit')->willReturnCallback(
441
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
442
-				$this->assertEquals('schedule', $eventName);
443
-				$this->assertCount(1, $arguments);
444
-				$iTipMessages[] = $arguments[0];
445
-				return true;
446
-			}
447
-		);
448
-		// construct calendar with a 1 hour event and same start/end time zones
449
-		$vCalendar = new VCalendar();
450
-		$vEvent = $vCalendar->add('VEVENT', []);
451
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
452
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
453
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
454
-		$vEvent->add('SUMMARY', 'Test Recurring Event');
455
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User One']);
456
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
457
-			'CN' => 'User Two',
458
-			'CUTYPE' => 'INDIVIDUAL',
459
-			'PARTSTAT' => 'NEEDS-ACTION',
460
-			'ROLE' => 'REQ-PARTICIPANT',
461
-			'RSVP' => 'TRUE'
462
-		]);
463
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
464
-			'CN' => 'User External',
465
-			'CUTYPE' => 'INDIVIDUAL',
466
-			'PARTSTAT' => 'NEEDS-ACTION',
467
-			'ROLE' => 'REQ-PARTICIPANT',
468
-			'RSVP' => 'TRUE'
469
-		]);
470
-		// define flags
471
-		$newFlag = true;
472
-		$modifiedFlag = false;
473
-		// execute method
474
-		$this->plugin->calendarObjectChange(
475
-			$request,
476
-			$response,
477
-			$vCalendar,
478
-			'calendars/user1/personal',
479
-			$modifiedFlag,
480
-			$newFlag
481
-		);
482
-		// test for correct iTip message count
483
-		$this->assertCount(2, $iTipMessages);
484
-		// test for Sharer Attendee
485
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
486
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
487
-		$this->assertTrue($iTipMessages[0]->significantChange);
488
-		// test for External Attendee
489
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
490
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
491
-		$this->assertTrue($iTipMessages[1]->significantChange);
492
-
493
-	}
494
-
495
-	/**
496
-	 * Test Calendar Event Creation for Shared Calendar as Sharer/Owner
497
-	 *
498
-	 * Should generate 3 messages for attendees User 2 (Sharee), User 3 (Non-Sharee) and User External
499
-	 */
500
-	public function testCalendarObjectChangeSharedCalendarSharerCreate(): void {
501
-
502
-		// define place holders
503
-		/** @var Message[] $iTipMessages */
504
-		$iTipMessages = [];
505
-		// construct calendar node
506
-		$calendarNode = new Calendar(
507
-			$this->createMock(BackendInterface::class),
508
-			[
509
-				'uri' => 'calendar_shared_by_user1',
510
-				'principaluri' => 'principals/users/user1',
511
-				'{DAV:}displayname' => 'Calendar Shared By User1',
512
-				'{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
513
-			],
514
-			$this->createMock(IL10N::class),
515
-			$this->config,
516
-			$this->logger
517
-		);
518
-		// construct server request object
519
-		$request = new Request(
520
-			'PUT',
521
-			'/remote.php/dav/calendars/user1/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
522
-		);
523
-		$request->setBaseUrl('/remote.php/dav/');
524
-		// construct server response object
525
-		$response = new Response();
526
-		// construct server tree object
527
-		$tree = $this->createMock(Tree::class);
528
-		$tree->expects($this->once())
529
-			->method('getNodeForPath')
530
-			->with('calendars/user1/calendar_shared_by_user1')
531
-			->willReturn($calendarNode);
532
-		// construct server properties and returns
533
-		$this->server->httpRequest = $request;
534
-		$this->server->tree = $tree;
535
-		$this->server->expects($this->exactly(1))->method('getProperties')
536
-			->willReturnMap([
537
-				[
538
-					'principals/users/user1',
539
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
540
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
541
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
542
-					)]
543
-				]
544
-			]);
545
-		$this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
546
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
547
-				$this->assertEquals('schedule', $eventName);
548
-				$this->assertCount(1, $arguments);
549
-				$iTipMessages[] = $arguments[0];
550
-				return true;
551
-			}
552
-		);
553
-		// construct calendar with a 1 hour event and same start/end time zones
554
-		$vCalendar = new VCalendar();
555
-		$vEvent = $vCalendar->add('VEVENT', []);
556
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
557
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
558
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
559
-		$vEvent->add('SUMMARY', 'Test Recurring Event');
560
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User One']);
561
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
562
-			'CN' => 'User Two',
563
-			'CUTYPE' => 'INDIVIDUAL',
564
-			'PARTSTAT' => 'NEEDS-ACTION',
565
-			'ROLE' => 'REQ-PARTICIPANT',
566
-			'RSVP' => 'TRUE'
567
-		]);
568
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
569
-			'CN' => 'User Three',
570
-			'CUTYPE' => 'INDIVIDUAL',
571
-			'PARTSTAT' => 'NEEDS-ACTION',
572
-			'ROLE' => 'REQ-PARTICIPANT',
573
-			'RSVP' => 'TRUE'
574
-		]);
575
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
576
-			'CN' => 'User External',
577
-			'CUTYPE' => 'INDIVIDUAL',
578
-			'PARTSTAT' => 'NEEDS-ACTION',
579
-			'ROLE' => 'REQ-PARTICIPANT',
580
-			'RSVP' => 'TRUE'
581
-		]);
582
-		// define flags
583
-		$newFlag = true;
584
-		$modifiedFlag = false;
585
-		// execute method
586
-		$this->plugin->calendarObjectChange(
587
-			$request,
588
-			$response,
589
-			$vCalendar,
590
-			'calendars/user1/calendar_shared_by_user1',
591
-			$modifiedFlag,
592
-			$newFlag
593
-		);
594
-		// test for correct iTip message count
595
-		$this->assertCount(3, $iTipMessages);
596
-		// test for Sharer Attendee
597
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
598
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
599
-		$this->assertTrue($iTipMessages[0]->significantChange);
600
-		// test for Non Shee Attendee
601
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
602
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
603
-		$this->assertTrue($iTipMessages[1]->significantChange);
604
-		// test for External Attendee
605
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[2]->sender);
606
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[2]->recipient);
607
-		$this->assertTrue($iTipMessages[2]->significantChange);
608
-
609
-	}
610
-
611
-	/**
612
-	 * Test Calendar Event Creation for Shared Calendar as Shree
613
-	 *
614
-	 * Should generate 3 messages for attendees User 1 (Sharer/Owner), User 3 (Non-Sharee) and User External
615
-	 */
616
-	public function testCalendarObjectChangeSharedCalendarShreeCreate(): void {
617
-
618
-		// define place holders
619
-		/** @var Message[] $iTipMessages */
620
-		$iTipMessages = [];
621
-		// construct calendar node
622
-		$calendarNode = new Calendar(
623
-			$this->createMock(BackendInterface::class),
624
-			[
625
-				'uri' => 'calendar_shared_by_user1',
626
-				'principaluri' => 'principals/users/user2',
627
-				'{DAV:}displayname' => 'Calendar Shared By User1',
628
-				'{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
629
-			],
630
-			$this->createMock(IL10N::class),
631
-			$this->config,
632
-			$this->logger
633
-		);
634
-		// construct server request object
635
-		$request = new Request(
636
-			'PUT',
637
-			'/remote.php/dav/calendars/user2/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
638
-		);
639
-		$request->setBaseUrl('/remote.php/dav/');
640
-		// construct server response object
641
-		$response = new Response();
642
-		// construct server tree object
643
-		$tree = $this->createMock(Tree::class);
644
-		$tree->expects($this->once())
645
-			->method('getNodeForPath')
646
-			->with('calendars/user2/calendar_shared_by_user1')
647
-			->willReturn($calendarNode);
648
-		// construct server properties and returns
649
-		$this->server->httpRequest = $request;
650
-		$this->server->tree = $tree;
651
-		$this->server->expects($this->exactly(2))->method('getProperties')
652
-			->willReturnMap([
653
-				[
654
-					'principals/users/user1',
655
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
656
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
657
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
658
-					)]
659
-				],
660
-				[
661
-					'principals/users/user2',
662
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
663
-					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
664
-						['mailto:[email protected]','/remote.php/dav/principals/users/user2/']
665
-					)]
666
-				]
667
-			]);
668
-		$this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
669
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
670
-				$this->assertEquals('schedule', $eventName);
671
-				$this->assertCount(1, $arguments);
672
-				$iTipMessages[] = $arguments[0];
673
-				return true;
674
-			}
675
-		);
676
-		// construct calendar with a 1 hour event and same start/end time zones
677
-		$vCalendar = new VCalendar();
678
-		$vEvent = $vCalendar->add('VEVENT', []);
679
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
680
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
681
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
682
-		$vEvent->add('SUMMARY', 'Test Recurring Event');
683
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User Two']);
684
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
685
-			'CN' => 'User One',
686
-			'CUTYPE' => 'INDIVIDUAL',
687
-			'PARTSTAT' => 'NEEDS-ACTION',
688
-			'ROLE' => 'REQ-PARTICIPANT',
689
-			'RSVP' => 'TRUE'
690
-		]);
691
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
692
-			'CN' => 'User Three',
693
-			'CUTYPE' => 'INDIVIDUAL',
694
-			'PARTSTAT' => 'NEEDS-ACTION',
695
-			'ROLE' => 'REQ-PARTICIPANT',
696
-			'RSVP' => 'TRUE'
697
-		]);
698
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
699
-			'CN' => 'User External',
700
-			'CUTYPE' => 'INDIVIDUAL',
701
-			'PARTSTAT' => 'NEEDS-ACTION',
702
-			'ROLE' => 'REQ-PARTICIPANT',
703
-			'RSVP' => 'TRUE'
704
-		]);
705
-		// define flags
706
-		$newFlag = true;
707
-		$modifiedFlag = false;
708
-		// execute method
709
-		$this->plugin->calendarObjectChange(
710
-			$request,
711
-			$response,
712
-			$vCalendar,
713
-			'calendars/user2/calendar_shared_by_user1',
714
-			$modifiedFlag,
715
-			$newFlag
716
-		);
717
-		// test for correct iTip message count
718
-		$this->assertCount(3, $iTipMessages);
719
-		// test for Sharer Attendee
720
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
721
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
722
-		$this->assertTrue($iTipMessages[0]->significantChange);
723
-		// test for Non Shee Attendee
724
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
725
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
726
-		$this->assertTrue($iTipMessages[1]->significantChange);
727
-		// test for External Attendee
728
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[2]->sender);
729
-		$this->assertEquals('mailto:[email protected]', $iTipMessages[2]->recipient);
730
-		$this->assertTrue($iTipMessages[2]->significantChange);
731
-
732
-	}
733
-
734
-	/**
735
-	 * Test Calendar Event Creation with iTip and iMip disabled
736
-	 *
737
-	 * Should generate 2 messages for attendees User 2 and User External
738
-	 */
739
-	public function testCalendarObjectChangeWithSchedulingDisabled(): void {
740
-		// construct server request
741
-		$request = new Request(
742
-			'PUT',
743
-			'/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics',
744
-			['x-nc-scheduling' => 'false']
745
-		);
746
-		$request->setBaseUrl('/remote.php/dav/');
747
-		// construct server response
748
-		$response = new Response();
749
-		// construct server tree
750
-		$tree = $this->createMock(Tree::class);
751
-		$tree->expects($this->never())
752
-			->method('getNodeForPath');
753
-		// construct server properties and returns
754
-		$this->server->httpRequest = $request;
755
-		$this->server->tree = $tree;
756
-		// construct empty calendar event
757
-		$vCalendar = new VCalendar();
758
-		$vEvent = $vCalendar->add('VEVENT', []);
759
-		// define flags
760
-		$newFlag = true;
761
-		$modifiedFlag = false;
762
-		// execute method
763
-		$this->plugin->calendarObjectChange(
764
-			$request,
765
-			$response,
766
-			$vCalendar,
767
-			'calendars/user1/personal',
768
-			$modifiedFlag,
769
-			$newFlag
770
-		);
771
-	}
39
+    private Plugin $plugin;
40
+    private Server&MockObject $server;
41
+    private IConfig&MockObject $config;
42
+    private LoggerInterface&MockObject $logger;
43
+    private DefaultCalendarValidator $calendarValidator;
44
+
45
+    protected function setUp(): void {
46
+        parent::setUp();
47
+
48
+        $this->config = $this->createMock(IConfig::class);
49
+        $this->logger = $this->createMock(LoggerInterface::class);
50
+        $this->calendarValidator = new DefaultCalendarValidator();
51
+
52
+        $this->server = $this->createMock(Server::class);
53
+        $this->server->httpResponse = $this->createMock(ResponseInterface::class);
54
+        $this->server->xml = new Service();
55
+
56
+        $this->plugin = new Plugin($this->config, $this->logger, $this->calendarValidator);
57
+        $this->plugin->initialize($this->server);
58
+    }
59
+
60
+    public function testInitialize(): void {
61
+        $calls = [
62
+            // Sabre\CalDAV\Schedule\Plugin events
63
+            ['method:POST', [$this->plugin, 'httpPost'], 100],
64
+            ['propFind', [$this->plugin, 'propFind'], 100],
65
+            ['propPatch', [$this->plugin, 'propPatch'], 100],
66
+            ['calendarObjectChange', [$this->plugin, 'calendarObjectChange'], 100],
67
+            ['beforeUnbind', [$this->plugin, 'beforeUnbind'], 100],
68
+            ['schedule', [$this->plugin, 'scheduleLocalDelivery'], 100],
69
+            ['getSupportedPrivilegeSet', [$this->plugin, 'getSupportedPrivilegeSet'], 100],
70
+            // OCA\DAV\CalDAV\Schedule\Plugin events
71
+            ['propFind', [$this->plugin, 'propFindDefaultCalendarUrl'], 90],
72
+            ['afterWriteContent', [$this->plugin, 'dispatchSchedulingResponses'], 100],
73
+            ['afterCreateFile', [$this->plugin, 'dispatchSchedulingResponses'], 100],
74
+        ];
75
+        $this->server->expects($this->exactly(count($calls)))
76
+            ->method('on')
77
+            ->willReturnCallback(function () use (&$calls) {
78
+                $expected = array_shift($calls);
79
+                $this->assertEquals($expected, func_get_args());
80
+            });
81
+
82
+        $this->plugin->initialize($this->server);
83
+    }
84
+
85
+    public function testGetAddressesForPrincipal(): void {
86
+        $href = $this->createMock(Href::class);
87
+        $href
88
+            ->expects($this->once())
89
+            ->method('getHrefs')
90
+            ->willReturn(['[email protected]', '[email protected]']);
91
+        $this->server
92
+            ->expects($this->once())
93
+            ->method('getProperties')
94
+            ->with(
95
+                'MyPrincipal',
96
+                [
97
+                    '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
98
+                ]
99
+            )
100
+            ->willReturn([
101
+                '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => $href
102
+            ]);
103
+
104
+        $result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
105
+        $this->assertSame(['[email protected]', '[email protected]'], $result);
106
+    }
107
+
108
+    public function testGetAddressesForPrincipalEmpty(): void {
109
+        $this->server
110
+            ->expects($this->once())
111
+            ->method('getProperties')
112
+            ->with(
113
+                'MyPrincipal',
114
+                [
115
+                    '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
116
+                ]
117
+            )
118
+            ->willReturn(null);
119
+
120
+        $result = $this->invokePrivate($this->plugin, 'getAddressesForPrincipal', ['MyPrincipal']);
121
+        $this->assertSame([], $result);
122
+    }
123
+
124
+    public function testStripOffMailTo(): void {
125
+        $this->assertEquals('[email protected]', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['[email protected]']));
126
+        $this->assertEquals('[email protected]', $this->invokePrivate($this->plugin, 'stripOffMailTo', ['mailto:[email protected]']));
127
+    }
128
+
129
+    public function testGetAttendeeRSVP(): void {
130
+        $property1 = $this->createMock(CalAddress::class);
131
+        $parameter1 = $this->createMock(Parameter::class);
132
+        $property1->expects($this->once())
133
+            ->method('offsetGet')
134
+            ->with('RSVP')
135
+            ->willReturn($parameter1);
136
+        $parameter1->expects($this->once())
137
+            ->method('getValue')
138
+            ->with()
139
+            ->willReturn('TRUE');
140
+
141
+        $property2 = $this->createMock(CalAddress::class);
142
+        $parameter2 = $this->createMock(Parameter::class);
143
+        $property2->expects($this->once())
144
+            ->method('offsetGet')
145
+            ->with('RSVP')
146
+            ->willReturn($parameter2);
147
+        $parameter2->expects($this->once())
148
+            ->method('getValue')
149
+            ->with()
150
+            ->willReturn('FALSE');
151
+
152
+        $property3 = $this->createMock(CalAddress::class);
153
+        $property3->expects($this->once())
154
+            ->method('offsetGet')
155
+            ->with('RSVP')
156
+            ->willReturn(null);
157
+
158
+        $this->assertTrue($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property1]));
159
+        $this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property2]));
160
+        $this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property3]));
161
+    }
162
+
163
+    public static function propFindDefaultCalendarUrlProvider(): array {
164
+        return [
165
+            [
166
+                'principals/users/myuser',
167
+                'calendars/myuser',
168
+                false,
169
+                CalDavBackend::PERSONAL_CALENDAR_URI,
170
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
171
+                true
172
+            ],
173
+            [
174
+                'principals/users/myuser',
175
+                'calendars/myuser',
176
+                false,
177
+                CalDavBackend::PERSONAL_CALENDAR_URI,
178
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
179
+                true,
180
+                true
181
+            ],
182
+            [
183
+                'principals/users/myuser',
184
+                'calendars/myuser',
185
+                false,
186
+                CalDavBackend::PERSONAL_CALENDAR_URI,
187
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
188
+                false,
189
+                false,
190
+                true
191
+            ],
192
+            [
193
+                'principals/users/myuser',
194
+                'calendars/myuser',
195
+                false,
196
+                CalDavBackend::PERSONAL_CALENDAR_URI,
197
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
198
+                false
199
+            ],
200
+            [
201
+                'principals/users/myuser',
202
+                null,
203
+                false,
204
+                CalDavBackend::PERSONAL_CALENDAR_URI,
205
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
206
+                true
207
+            ],
208
+            [
209
+                'principals/users/myuser',
210
+                'calendars/myuser',
211
+                false,
212
+                CalDavBackend::PERSONAL_CALENDAR_URI,
213
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
214
+                true,
215
+                false,
216
+                false,
217
+                false,
218
+            ],
219
+            [
220
+                'principals/users/myuser',
221
+                'calendars/myuser',
222
+                false,
223
+                'my_other_calendar',
224
+                'My Other Calendar',
225
+                true
226
+            ],
227
+            [
228
+                'principals/calendar-resources',
229
+                'system-calendars/calendar-resources/myuser',
230
+                true,
231
+                CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
232
+                CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
233
+                true
234
+            ],
235
+            [
236
+                'principals/calendar-resources',
237
+                'system-calendars/calendar-resources/myuser',
238
+                true,
239
+                CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
240
+                CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
241
+                false
242
+            ],
243
+            [
244
+                'principals/something-else',
245
+                'calendars/whatever',
246
+                false,
247
+                CalDavBackend::PERSONAL_CALENDAR_URI,
248
+                CalDavBackend::PERSONAL_CALENDAR_NAME,
249
+                true
250
+            ],
251
+        ];
252
+    }
253
+
254
+    /**
255
+     * @dataProvider propFindDefaultCalendarUrlProvider
256
+     */
257
+    public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void {
258
+        $propFind = new PropFind(
259
+            $principalUri,
260
+            [
261
+                Plugin::SCHEDULE_DEFAULT_CALENDAR_URL
262
+            ],
263
+            0
264
+        );
265
+        /** @var IPrincipal&MockObject $node */
266
+        $node = $this->getMockBuilder(IPrincipal::class)
267
+            ->disableOriginalConstructor()
268
+            ->getMock();
269
+
270
+        $node->expects($this->once())
271
+            ->method('getPrincipalUrl')
272
+            ->with()
273
+            ->willReturn($principalUri);
274
+
275
+        $calDAVPlugin = $this->getMockBuilder(CalDAVPlugin::class)
276
+            ->disableOriginalConstructor()
277
+            ->getMock();
278
+
279
+        $calDAVPlugin->expects($this->once())
280
+            ->method('getCalendarHomeForPrincipal')
281
+            ->willReturn($calendarHome);
282
+
283
+        $this->server->expects($this->once())
284
+            ->method('getPlugin')
285
+            ->with('caldav')
286
+            ->willReturn($calDAVPlugin);
287
+        if (!$calendarHome) {
288
+            $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
289
+
290
+            $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
291
+            return;
292
+        }
293
+        if ($principalUri === 'principals/something-else') {
294
+            $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
295
+
296
+            $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
297
+            return;
298
+        }
299
+
300
+        if (!$isResource) {
301
+            $this->config->expects($this->once())
302
+                ->method('getUserValue')
303
+                ->with('myuser', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI)
304
+                ->willReturn($calendarUri);
305
+        }
306
+
307
+        $calendarHomeObject = $this->createMock(CalendarHome::class);
308
+        $calendarHomeObject->expects($this->once())
309
+            ->method('childExists')
310
+            ->with($calendarUri)
311
+            ->willReturn($exists);
312
+
313
+        if ($exists) {
314
+            $calendar = $this->createMock(Calendar::class);
315
+            $calendar->expects($this->once())->method('isDeleted')->willReturn($deleted);
316
+            $calendarHomeObject->expects($deleted && !$hasExistingCalendars ? $this->exactly(2) : $this->once())->method('getChild')->with($calendarUri)->willReturn($calendar);
317
+        }
318
+
319
+        $calendarBackend = $this->createMock(CalDavBackend::class);
320
+        $calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri;
321
+        $displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName;
322
+
323
+        $existingCalendars = $hasExistingCalendars ? [
324
+            new Calendar(
325
+                $calendarBackend,
326
+                ['uri' => 'deleted', '{DAV:}displayname' => 'A deleted calendar', TrashbinPlugin::PROPERTY_DELETED_AT => 42],
327
+                $this->createMock(IL10N::class),
328
+                $this->config,
329
+                $this->createMock(LoggerInterface::class)
330
+            ),
331
+            new Calendar(
332
+                $calendarBackend,
333
+                ['uri' => $calendarUri, '{DAV:}displayname' => $displayName],
334
+                $this->createMock(IL10N::class),
335
+                $this->config,
336
+                $this->createMock(LoggerInterface::class)
337
+            )
338
+        ] : [];
339
+
340
+        if (!$exists || $deleted) {
341
+            if (!$hasExistingCalendars) {
342
+                $calendarBackend->expects($this->once())
343
+                    ->method('createCalendar')
344
+                    ->with($principalUri, $calendarUri, [
345
+                        '{DAV:}displayname' => $displayName,
346
+                    ]);
347
+
348
+                $calendarHomeObject->expects($this->exactly($deleted ? 2 : 1))
349
+                    ->method('getCalDAVBackend')
350
+                    ->with()
351
+                    ->willReturn($calendarBackend);
352
+            }
353
+
354
+            if (!$isResource) {
355
+                $calendarHomeObject->expects($this->once())
356
+                    ->method('getChildren')
357
+                    ->with()
358
+                    ->willReturn($existingCalendars);
359
+            }
360
+        }
361
+
362
+        /** @var Tree&MockObject $tree */
363
+        $tree = $this->createMock(Tree::class);
364
+        $tree->expects($this->once())
365
+            ->method('getNodeForPath')
366
+            ->with($calendarHome)
367
+            ->willReturn($calendarHomeObject);
368
+        $this->server->tree = $tree;
369
+
370
+        $properties = $propertiesForPath ? [
371
+            ['href' => '/remote.php/dav/' . $calendarHome . '/' . $calendarUri]
372
+        ] : [];
373
+
374
+        $this->server->expects($this->once())
375
+            ->method('getPropertiesForPath')
376
+            ->with($calendarHome . '/' . $calendarUri, [], 1)
377
+            ->willReturn($properties);
378
+
379
+        $this->plugin->propFindDefaultCalendarUrl($propFind, $node);
380
+
381
+        if (!$propertiesForPath) {
382
+            $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
383
+            return;
384
+        }
385
+
386
+        /** @var LocalHref $result */
387
+        $result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
388
+        $this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref());
389
+    }
390
+
391
+    /**
392
+     * Test Calendar Event Creation for Personal Calendar
393
+     *
394
+     * Should generate 2 messages for attendees User 2 and User External
395
+     */
396
+    public function testCalendarObjectChangePersonalCalendarCreate(): void {
397
+
398
+        // define place holders
399
+        /** @var Message[] $iTipMessages */
400
+        $iTipMessages = [];
401
+        // construct calendar node
402
+        $calendarNode = new Calendar(
403
+            $this->createMock(BackendInterface::class),
404
+            [
405
+                'uri' => 'personal',
406
+                'principaluri' => 'principals/users/user1',
407
+                '{DAV:}displayname' => 'Calendar Shared By User1',
408
+            ],
409
+            $this->createMock(IL10N::class),
410
+            $this->config,
411
+            $this->logger
412
+        );
413
+        // construct server request object
414
+        $request = new Request(
415
+            'PUT',
416
+            '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
417
+        );
418
+        $request->setBaseUrl('/remote.php/dav/');
419
+        // construct server response object
420
+        $response = new Response();
421
+        // construct server tree object
422
+        $tree = $this->createMock(Tree::class);
423
+        $tree->expects($this->once())
424
+            ->method('getNodeForPath')
425
+            ->with('calendars/user1/personal')
426
+            ->willReturn($calendarNode);
427
+        // construct server properties and returns
428
+        $this->server->httpRequest = $request;
429
+        $this->server->tree = $tree;
430
+        $this->server->expects($this->exactly(1))->method('getProperties')
431
+            ->willReturnMap([
432
+                [
433
+                    'principals/users/user1',
434
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
435
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
436
+                        ['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
437
+                    )]
438
+                ]
439
+            ]);
440
+        $this->server->expects($this->exactly(2))->method('emit')->willReturnCallback(
441
+            function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
442
+                $this->assertEquals('schedule', $eventName);
443
+                $this->assertCount(1, $arguments);
444
+                $iTipMessages[] = $arguments[0];
445
+                return true;
446
+            }
447
+        );
448
+        // construct calendar with a 1 hour event and same start/end time zones
449
+        $vCalendar = new VCalendar();
450
+        $vEvent = $vCalendar->add('VEVENT', []);
451
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
452
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
453
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
454
+        $vEvent->add('SUMMARY', 'Test Recurring Event');
455
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User One']);
456
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
457
+            'CN' => 'User Two',
458
+            'CUTYPE' => 'INDIVIDUAL',
459
+            'PARTSTAT' => 'NEEDS-ACTION',
460
+            'ROLE' => 'REQ-PARTICIPANT',
461
+            'RSVP' => 'TRUE'
462
+        ]);
463
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
464
+            'CN' => 'User External',
465
+            'CUTYPE' => 'INDIVIDUAL',
466
+            'PARTSTAT' => 'NEEDS-ACTION',
467
+            'ROLE' => 'REQ-PARTICIPANT',
468
+            'RSVP' => 'TRUE'
469
+        ]);
470
+        // define flags
471
+        $newFlag = true;
472
+        $modifiedFlag = false;
473
+        // execute method
474
+        $this->plugin->calendarObjectChange(
475
+            $request,
476
+            $response,
477
+            $vCalendar,
478
+            'calendars/user1/personal',
479
+            $modifiedFlag,
480
+            $newFlag
481
+        );
482
+        // test for correct iTip message count
483
+        $this->assertCount(2, $iTipMessages);
484
+        // test for Sharer Attendee
485
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
486
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
487
+        $this->assertTrue($iTipMessages[0]->significantChange);
488
+        // test for External Attendee
489
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
490
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
491
+        $this->assertTrue($iTipMessages[1]->significantChange);
492
+
493
+    }
494
+
495
+    /**
496
+     * Test Calendar Event Creation for Shared Calendar as Sharer/Owner
497
+     *
498
+     * Should generate 3 messages for attendees User 2 (Sharee), User 3 (Non-Sharee) and User External
499
+     */
500
+    public function testCalendarObjectChangeSharedCalendarSharerCreate(): void {
501
+
502
+        // define place holders
503
+        /** @var Message[] $iTipMessages */
504
+        $iTipMessages = [];
505
+        // construct calendar node
506
+        $calendarNode = new Calendar(
507
+            $this->createMock(BackendInterface::class),
508
+            [
509
+                'uri' => 'calendar_shared_by_user1',
510
+                'principaluri' => 'principals/users/user1',
511
+                '{DAV:}displayname' => 'Calendar Shared By User1',
512
+                '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
513
+            ],
514
+            $this->createMock(IL10N::class),
515
+            $this->config,
516
+            $this->logger
517
+        );
518
+        // construct server request object
519
+        $request = new Request(
520
+            'PUT',
521
+            '/remote.php/dav/calendars/user1/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
522
+        );
523
+        $request->setBaseUrl('/remote.php/dav/');
524
+        // construct server response object
525
+        $response = new Response();
526
+        // construct server tree object
527
+        $tree = $this->createMock(Tree::class);
528
+        $tree->expects($this->once())
529
+            ->method('getNodeForPath')
530
+            ->with('calendars/user1/calendar_shared_by_user1')
531
+            ->willReturn($calendarNode);
532
+        // construct server properties and returns
533
+        $this->server->httpRequest = $request;
534
+        $this->server->tree = $tree;
535
+        $this->server->expects($this->exactly(1))->method('getProperties')
536
+            ->willReturnMap([
537
+                [
538
+                    'principals/users/user1',
539
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
540
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
541
+                        ['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
542
+                    )]
543
+                ]
544
+            ]);
545
+        $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
546
+            function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
547
+                $this->assertEquals('schedule', $eventName);
548
+                $this->assertCount(1, $arguments);
549
+                $iTipMessages[] = $arguments[0];
550
+                return true;
551
+            }
552
+        );
553
+        // construct calendar with a 1 hour event and same start/end time zones
554
+        $vCalendar = new VCalendar();
555
+        $vEvent = $vCalendar->add('VEVENT', []);
556
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
557
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
558
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
559
+        $vEvent->add('SUMMARY', 'Test Recurring Event');
560
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User One']);
561
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
562
+            'CN' => 'User Two',
563
+            'CUTYPE' => 'INDIVIDUAL',
564
+            'PARTSTAT' => 'NEEDS-ACTION',
565
+            'ROLE' => 'REQ-PARTICIPANT',
566
+            'RSVP' => 'TRUE'
567
+        ]);
568
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
569
+            'CN' => 'User Three',
570
+            'CUTYPE' => 'INDIVIDUAL',
571
+            'PARTSTAT' => 'NEEDS-ACTION',
572
+            'ROLE' => 'REQ-PARTICIPANT',
573
+            'RSVP' => 'TRUE'
574
+        ]);
575
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
576
+            'CN' => 'User External',
577
+            'CUTYPE' => 'INDIVIDUAL',
578
+            'PARTSTAT' => 'NEEDS-ACTION',
579
+            'ROLE' => 'REQ-PARTICIPANT',
580
+            'RSVP' => 'TRUE'
581
+        ]);
582
+        // define flags
583
+        $newFlag = true;
584
+        $modifiedFlag = false;
585
+        // execute method
586
+        $this->plugin->calendarObjectChange(
587
+            $request,
588
+            $response,
589
+            $vCalendar,
590
+            'calendars/user1/calendar_shared_by_user1',
591
+            $modifiedFlag,
592
+            $newFlag
593
+        );
594
+        // test for correct iTip message count
595
+        $this->assertCount(3, $iTipMessages);
596
+        // test for Sharer Attendee
597
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
598
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
599
+        $this->assertTrue($iTipMessages[0]->significantChange);
600
+        // test for Non Shee Attendee
601
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
602
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
603
+        $this->assertTrue($iTipMessages[1]->significantChange);
604
+        // test for External Attendee
605
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[2]->sender);
606
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[2]->recipient);
607
+        $this->assertTrue($iTipMessages[2]->significantChange);
608
+
609
+    }
610
+
611
+    /**
612
+     * Test Calendar Event Creation for Shared Calendar as Shree
613
+     *
614
+     * Should generate 3 messages for attendees User 1 (Sharer/Owner), User 3 (Non-Sharee) and User External
615
+     */
616
+    public function testCalendarObjectChangeSharedCalendarShreeCreate(): void {
617
+
618
+        // define place holders
619
+        /** @var Message[] $iTipMessages */
620
+        $iTipMessages = [];
621
+        // construct calendar node
622
+        $calendarNode = new Calendar(
623
+            $this->createMock(BackendInterface::class),
624
+            [
625
+                'uri' => 'calendar_shared_by_user1',
626
+                'principaluri' => 'principals/users/user2',
627
+                '{DAV:}displayname' => 'Calendar Shared By User1',
628
+                '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1'
629
+            ],
630
+            $this->createMock(IL10N::class),
631
+            $this->config,
632
+            $this->logger
633
+        );
634
+        // construct server request object
635
+        $request = new Request(
636
+            'PUT',
637
+            '/remote.php/dav/calendars/user2/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics'
638
+        );
639
+        $request->setBaseUrl('/remote.php/dav/');
640
+        // construct server response object
641
+        $response = new Response();
642
+        // construct server tree object
643
+        $tree = $this->createMock(Tree::class);
644
+        $tree->expects($this->once())
645
+            ->method('getNodeForPath')
646
+            ->with('calendars/user2/calendar_shared_by_user1')
647
+            ->willReturn($calendarNode);
648
+        // construct server properties and returns
649
+        $this->server->httpRequest = $request;
650
+        $this->server->tree = $tree;
651
+        $this->server->expects($this->exactly(2))->method('getProperties')
652
+            ->willReturnMap([
653
+                [
654
+                    'principals/users/user1',
655
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
656
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
657
+                        ['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
658
+                    )]
659
+                ],
660
+                [
661
+                    'principals/users/user2',
662
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
663
+                    ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
664
+                        ['mailto:[email protected]','/remote.php/dav/principals/users/user2/']
665
+                    )]
666
+                ]
667
+            ]);
668
+        $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
669
+            function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
670
+                $this->assertEquals('schedule', $eventName);
671
+                $this->assertCount(1, $arguments);
672
+                $iTipMessages[] = $arguments[0];
673
+                return true;
674
+            }
675
+        );
676
+        // construct calendar with a 1 hour event and same start/end time zones
677
+        $vCalendar = new VCalendar();
678
+        $vEvent = $vCalendar->add('VEVENT', []);
679
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
680
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
681
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
682
+        $vEvent->add('SUMMARY', 'Test Recurring Event');
683
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'User Two']);
684
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
685
+            'CN' => 'User One',
686
+            'CUTYPE' => 'INDIVIDUAL',
687
+            'PARTSTAT' => 'NEEDS-ACTION',
688
+            'ROLE' => 'REQ-PARTICIPANT',
689
+            'RSVP' => 'TRUE'
690
+        ]);
691
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
692
+            'CN' => 'User Three',
693
+            'CUTYPE' => 'INDIVIDUAL',
694
+            'PARTSTAT' => 'NEEDS-ACTION',
695
+            'ROLE' => 'REQ-PARTICIPANT',
696
+            'RSVP' => 'TRUE'
697
+        ]);
698
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
699
+            'CN' => 'User External',
700
+            'CUTYPE' => 'INDIVIDUAL',
701
+            'PARTSTAT' => 'NEEDS-ACTION',
702
+            'ROLE' => 'REQ-PARTICIPANT',
703
+            'RSVP' => 'TRUE'
704
+        ]);
705
+        // define flags
706
+        $newFlag = true;
707
+        $modifiedFlag = false;
708
+        // execute method
709
+        $this->plugin->calendarObjectChange(
710
+            $request,
711
+            $response,
712
+            $vCalendar,
713
+            'calendars/user2/calendar_shared_by_user1',
714
+            $modifiedFlag,
715
+            $newFlag
716
+        );
717
+        // test for correct iTip message count
718
+        $this->assertCount(3, $iTipMessages);
719
+        // test for Sharer Attendee
720
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->sender);
721
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[0]->recipient);
722
+        $this->assertTrue($iTipMessages[0]->significantChange);
723
+        // test for Non Shee Attendee
724
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->sender);
725
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[1]->recipient);
726
+        $this->assertTrue($iTipMessages[1]->significantChange);
727
+        // test for External Attendee
728
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[2]->sender);
729
+        $this->assertEquals('mailto:[email protected]', $iTipMessages[2]->recipient);
730
+        $this->assertTrue($iTipMessages[2]->significantChange);
731
+
732
+    }
733
+
734
+    /**
735
+     * Test Calendar Event Creation with iTip and iMip disabled
736
+     *
737
+     * Should generate 2 messages for attendees User 2 and User External
738
+     */
739
+    public function testCalendarObjectChangeWithSchedulingDisabled(): void {
740
+        // construct server request
741
+        $request = new Request(
742
+            'PUT',
743
+            '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics',
744
+            ['x-nc-scheduling' => 'false']
745
+        );
746
+        $request->setBaseUrl('/remote.php/dav/');
747
+        // construct server response
748
+        $response = new Response();
749
+        // construct server tree
750
+        $tree = $this->createMock(Tree::class);
751
+        $tree->expects($this->never())
752
+            ->method('getNodeForPath');
753
+        // construct server properties and returns
754
+        $this->server->httpRequest = $request;
755
+        $this->server->tree = $tree;
756
+        // construct empty calendar event
757
+        $vCalendar = new VCalendar();
758
+        $vEvent = $vCalendar->add('VEVENT', []);
759
+        // define flags
760
+        $newFlag = true;
761
+        $modifiedFlag = false;
762
+        // execute method
763
+        $this->plugin->calendarObjectChange(
764
+            $request,
765
+            $response,
766
+            $vCalendar,
767
+            'calendars/user1/personal',
768
+            $modifiedFlag,
769
+            $newFlag
770
+        );
771
+    }
772 772
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 		];
75 75
 		$this->server->expects($this->exactly(count($calls)))
76 76
 			->method('on')
77
-			->willReturnCallback(function () use (&$calls) {
77
+			->willReturnCallback(function() use (&$calls) {
78 78
 				$expected = array_shift($calls);
79 79
 				$this->assertEquals($expected, func_get_args());
80 80
 			});
@@ -368,12 +368,12 @@  discard block
 block discarded – undo
368 368
 		$this->server->tree = $tree;
369 369
 
370 370
 		$properties = $propertiesForPath ? [
371
-			['href' => '/remote.php/dav/' . $calendarHome . '/' . $calendarUri]
371
+			['href' => '/remote.php/dav/'.$calendarHome.'/'.$calendarUri]
372 372
 		] : [];
373 373
 
374 374
 		$this->server->expects($this->once())
375 375
 			->method('getPropertiesForPath')
376
-			->with($calendarHome . '/' . $calendarUri, [], 1)
376
+			->with($calendarHome.'/'.$calendarUri, [], 1)
377 377
 			->willReturn($properties);
378 378
 
379 379
 		$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
@@ -385,7 +385,7 @@  discard block
 block discarded – undo
385 385
 
386 386
 		/** @var LocalHref $result */
387 387
 		$result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
388
-		$this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref());
388
+		$this->assertEquals('/remote.php/dav/'.$calendarHome.'/'.$calendarUri, $result->getHref());
389 389
 	}
390 390
 
391 391
 	/**
@@ -433,12 +433,12 @@  discard block
 block discarded – undo
433 433
 					'principals/users/user1',
434 434
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
435 435
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
436
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
436
+						['mailto:[email protected]', '/remote.php/dav/principals/users/user1/']
437 437
 					)]
438 438
 				]
439 439
 			]);
440 440
 		$this->server->expects($this->exactly(2))->method('emit')->willReturnCallback(
441
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
441
+			function(string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
442 442
 				$this->assertEquals('schedule', $eventName);
443 443
 				$this->assertCount(1, $arguments);
444 444
 				$iTipMessages[] = $arguments[0];
@@ -538,12 +538,12 @@  discard block
 block discarded – undo
538 538
 					'principals/users/user1',
539 539
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
540 540
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
541
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
541
+						['mailto:[email protected]', '/remote.php/dav/principals/users/user1/']
542 542
 					)]
543 543
 				]
544 544
 			]);
545 545
 		$this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
546
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
546
+			function(string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
547 547
 				$this->assertEquals('schedule', $eventName);
548 548
 				$this->assertCount(1, $arguments);
549 549
 				$iTipMessages[] = $arguments[0];
@@ -654,19 +654,19 @@  discard block
 block discarded – undo
654 654
 					'principals/users/user1',
655 655
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
656 656
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
657
-						['mailto:[email protected]','/remote.php/dav/principals/users/user1/']
657
+						['mailto:[email protected]', '/remote.php/dav/principals/users/user1/']
658 658
 					)]
659 659
 				],
660 660
 				[
661 661
 					'principals/users/user2',
662 662
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'],
663 663
 					['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref(
664
-						['mailto:[email protected]','/remote.php/dav/principals/users/user2/']
664
+						['mailto:[email protected]', '/remote.php/dav/principals/users/user2/']
665 665
 					)]
666 666
 				]
667 667
 			]);
668 668
 		$this->server->expects($this->exactly(3))->method('emit')->willReturnCallback(
669
-			function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
669
+			function(string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) {
670 670
 				$this->assertEquals('schedule', $eventName);
671 671
 				$this->assertCount(1, $arguments);
672 672
 				$iTipMessages[] = $arguments[0];
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Validation/CalDavValidatePluginTest.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -18,56 +18,56 @@
 block discarded – undo
18 18
 use Test\TestCase;
19 19
 
20 20
 class CalDavValidatePluginTest extends TestCase {
21
-	private IAppConfig&MockObject $config;
22
-	private RequestInterface&MockObject $request;
23
-	private ResponseInterface&MockObject $response;
21
+    private IAppConfig&MockObject $config;
22
+    private RequestInterface&MockObject $request;
23
+    private ResponseInterface&MockObject $response;
24 24
 
25
-	private CalDavValidatePlugin $plugin;
25
+    private CalDavValidatePlugin $plugin;
26 26
 
27
-	protected function setUp(): void {
28
-		parent::setUp();
29
-		// construct mock objects
30
-		$this->config = $this->createMock(IAppConfig::class);
31
-		$this->request = $this->createMock(RequestInterface::class);
32
-		$this->response = $this->createMock(ResponseInterface::class);
33
-		$this->plugin = new CalDavValidatePlugin(
34
-			$this->config,
35
-		);
36
-	}
27
+    protected function setUp(): void {
28
+        parent::setUp();
29
+        // construct mock objects
30
+        $this->config = $this->createMock(IAppConfig::class);
31
+        $this->request = $this->createMock(RequestInterface::class);
32
+        $this->response = $this->createMock(ResponseInterface::class);
33
+        $this->plugin = new CalDavValidatePlugin(
34
+            $this->config,
35
+        );
36
+    }
37 37
 
38
-	public function testPutSizeLessThenLimit(): void {
38
+    public function testPutSizeLessThenLimit(): void {
39 39
 
40
-		// construct method responses
41
-		$this->config
42
-			->method('getValueInt')
43
-			->with('dav', 'event_size_limit', 10485760)
44
-			->willReturn(10485760);
45
-		$this->request
46
-			->method('getRawServerValue')
47
-			->with('CONTENT_LENGTH')
48
-			->willReturn('1024');
49
-		// test condition
50
-		$this->assertTrue(
51
-			$this->plugin->beforePut($this->request, $this->response)
52
-		);
40
+        // construct method responses
41
+        $this->config
42
+            ->method('getValueInt')
43
+            ->with('dav', 'event_size_limit', 10485760)
44
+            ->willReturn(10485760);
45
+        $this->request
46
+            ->method('getRawServerValue')
47
+            ->with('CONTENT_LENGTH')
48
+            ->willReturn('1024');
49
+        // test condition
50
+        $this->assertTrue(
51
+            $this->plugin->beforePut($this->request, $this->response)
52
+        );
53 53
 
54
-	}
54
+    }
55 55
 
56
-	public function testPutSizeMoreThenLimit(): void {
56
+    public function testPutSizeMoreThenLimit(): void {
57 57
 
58
-		// construct method responses
59
-		$this->config
60
-			->method('getValueInt')
61
-			->with('dav', 'event_size_limit', 10485760)
62
-			->willReturn(10485760);
63
-		$this->request
64
-			->method('getRawServerValue')
65
-			->with('CONTENT_LENGTH')
66
-			->willReturn('16242880');
67
-		$this->expectException(Forbidden::class);
68
-		// test condition
69
-		$this->plugin->beforePut($this->request, $this->response);
58
+        // construct method responses
59
+        $this->config
60
+            ->method('getValueInt')
61
+            ->with('dav', 'event_size_limit', 10485760)
62
+            ->willReturn(10485760);
63
+        $this->request
64
+            ->method('getRawServerValue')
65
+            ->with('CONTENT_LENGTH')
66
+            ->willReturn('16242880');
67
+        $this->expectException(Forbidden::class);
68
+        // test condition
69
+        $this->plugin->beforePut($this->request, $this->response);
70 70
 
71
-	}
71
+    }
72 72
 
73 73
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/CalDavBackendTest.php 1 patch
Indentation   +1125 added lines, -1125 removed lines patch added patch discarded remove patch
@@ -32,128 +32,128 @@  discard block
 block discarded – undo
32 32
  * @group DB
33 33
  */
34 34
 class CalDavBackendTest extends AbstractCalDavBackend {
35
-	public function testCalendarOperations(): void {
36
-		$calendarId = $this->createTestCalendar();
37
-
38
-		// update its display name
39
-		$patch = new PropPatch([
40
-			'{DAV:}displayname' => 'Unit test',
41
-			'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing'
42
-		]);
43
-		$this->dispatcher->expects(self::atLeastOnce())
44
-			->method('dispatchTyped');
45
-		$this->backend->updateCalendar($calendarId, $patch);
46
-		$patch->commit();
47
-		$this->assertEquals(1, $this->backend->getCalendarsForUserCount(self::UNIT_TEST_USER));
48
-		$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
49
-		$this->assertCount(1, $calendars);
50
-		$this->assertEquals('Unit test', $calendars[0]['{DAV:}displayname']);
51
-		$this->assertEquals('Calendar used for unit testing', $calendars[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']);
52
-		$this->assertEquals('User\'s displayname', $calendars[0]['{http://nextcloud.com/ns}owner-displayname']);
53
-
54
-		// delete the address book
55
-		$this->dispatcher->expects(self::atLeastOnce())
56
-			->method('dispatchTyped');
57
-		$this->backend->deleteCalendar($calendars[0]['id'], true);
58
-		$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
59
-		self::assertEmpty($calendars);
60
-	}
61
-
62
-	public static function providesSharingData(): array {
63
-		return [
64
-			[true, true, true, false, [
65
-				[
66
-					'href' => 'principal:' . self::UNIT_TEST_USER1,
67
-					'readOnly' => false
68
-				],
69
-				[
70
-					'href' => 'principal:' . self::UNIT_TEST_GROUP,
71
-					'readOnly' => true
72
-				]
73
-			], [
74
-				self::UNIT_TEST_USER1,
75
-				self::UNIT_TEST_GROUP,
76
-			]],
77
-			[true, true, true, false, [
78
-				[
79
-					'href' => 'principal:' . self::UNIT_TEST_GROUP,
80
-					'readOnly' => true,
81
-				],
82
-				[
83
-					'href' => 'principal:' . self::UNIT_TEST_GROUP2,
84
-					'readOnly' => false,
85
-				],
86
-			], [
87
-				self::UNIT_TEST_GROUP,
88
-				self::UNIT_TEST_GROUP2,
89
-			]],
90
-			[true, true, true, true, [
91
-				[
92
-					'href' => 'principal:' . self::UNIT_TEST_GROUP,
93
-					'readOnly' => false,
94
-				],
95
-				[
96
-					'href' => 'principal:' . self::UNIT_TEST_GROUP2,
97
-					'readOnly' => true,
98
-				],
99
-			], [
100
-				self::UNIT_TEST_GROUP,
101
-				self::UNIT_TEST_GROUP2,
102
-			]],
103
-			[true, false, false, false, [
104
-				[
105
-					'href' => 'principal:' . self::UNIT_TEST_USER1,
106
-					'readOnly' => true
107
-				],
108
-			], [
109
-				self::UNIT_TEST_USER1,
110
-			]],
111
-
112
-		];
113
-	}
114
-
115
-	/**
116
-	 * @dataProvider providesSharingData
117
-	 */
118
-	public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add, $principals): void {
119
-		$logger = $this->createMock(\Psr\Log\LoggerInterface::class);
120
-		$config = $this->createMock(IConfig::class);
121
-
122
-		$l10n = $this->createMock(IL10N::class);
123
-		$l10n->expects($this->any())
124
-			->method('t')
125
-			->willReturnCallback(function ($text, $parameters = []) {
126
-				return vsprintf($text, $parameters);
127
-			});
128
-
129
-		$this->userManager->expects($this->any())
130
-			->method('userExists')
131
-			->willReturn(true);
132
-		$this->groupManager->expects($this->any())
133
-			->method('groupExists')
134
-			->willReturn(true);
135
-		$this->principal->expects(self::atLeastOnce())
136
-			->method('findByUri')
137
-			->willReturnOnConsecutiveCalls(...$principals);
138
-
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
-
154
-		// test acls on the child
155
-		$uri = static::getUniqueID('calobj');
156
-		$calData = <<<'EOD'
35
+    public function testCalendarOperations(): void {
36
+        $calendarId = $this->createTestCalendar();
37
+
38
+        // update its display name
39
+        $patch = new PropPatch([
40
+            '{DAV:}displayname' => 'Unit test',
41
+            '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing'
42
+        ]);
43
+        $this->dispatcher->expects(self::atLeastOnce())
44
+            ->method('dispatchTyped');
45
+        $this->backend->updateCalendar($calendarId, $patch);
46
+        $patch->commit();
47
+        $this->assertEquals(1, $this->backend->getCalendarsForUserCount(self::UNIT_TEST_USER));
48
+        $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
49
+        $this->assertCount(1, $calendars);
50
+        $this->assertEquals('Unit test', $calendars[0]['{DAV:}displayname']);
51
+        $this->assertEquals('Calendar used for unit testing', $calendars[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']);
52
+        $this->assertEquals('User\'s displayname', $calendars[0]['{http://nextcloud.com/ns}owner-displayname']);
53
+
54
+        // delete the address book
55
+        $this->dispatcher->expects(self::atLeastOnce())
56
+            ->method('dispatchTyped');
57
+        $this->backend->deleteCalendar($calendars[0]['id'], true);
58
+        $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
59
+        self::assertEmpty($calendars);
60
+    }
61
+
62
+    public static function providesSharingData(): array {
63
+        return [
64
+            [true, true, true, false, [
65
+                [
66
+                    'href' => 'principal:' . self::UNIT_TEST_USER1,
67
+                    'readOnly' => false
68
+                ],
69
+                [
70
+                    'href' => 'principal:' . self::UNIT_TEST_GROUP,
71
+                    'readOnly' => true
72
+                ]
73
+            ], [
74
+                self::UNIT_TEST_USER1,
75
+                self::UNIT_TEST_GROUP,
76
+            ]],
77
+            [true, true, true, false, [
78
+                [
79
+                    'href' => 'principal:' . self::UNIT_TEST_GROUP,
80
+                    'readOnly' => true,
81
+                ],
82
+                [
83
+                    'href' => 'principal:' . self::UNIT_TEST_GROUP2,
84
+                    'readOnly' => false,
85
+                ],
86
+            ], [
87
+                self::UNIT_TEST_GROUP,
88
+                self::UNIT_TEST_GROUP2,
89
+            ]],
90
+            [true, true, true, true, [
91
+                [
92
+                    'href' => 'principal:' . self::UNIT_TEST_GROUP,
93
+                    'readOnly' => false,
94
+                ],
95
+                [
96
+                    'href' => 'principal:' . self::UNIT_TEST_GROUP2,
97
+                    'readOnly' => true,
98
+                ],
99
+            ], [
100
+                self::UNIT_TEST_GROUP,
101
+                self::UNIT_TEST_GROUP2,
102
+            ]],
103
+            [true, false, false, false, [
104
+                [
105
+                    'href' => 'principal:' . self::UNIT_TEST_USER1,
106
+                    'readOnly' => true
107
+                ],
108
+            ], [
109
+                self::UNIT_TEST_USER1,
110
+            ]],
111
+
112
+        ];
113
+    }
114
+
115
+    /**
116
+     * @dataProvider providesSharingData
117
+     */
118
+    public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add, $principals): void {
119
+        $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
120
+        $config = $this->createMock(IConfig::class);
121
+
122
+        $l10n = $this->createMock(IL10N::class);
123
+        $l10n->expects($this->any())
124
+            ->method('t')
125
+            ->willReturnCallback(function ($text, $parameters = []) {
126
+                return vsprintf($text, $parameters);
127
+            });
128
+
129
+        $this->userManager->expects($this->any())
130
+            ->method('userExists')
131
+            ->willReturn(true);
132
+        $this->groupManager->expects($this->any())
133
+            ->method('groupExists')
134
+            ->willReturn(true);
135
+        $this->principal->expects(self::atLeastOnce())
136
+            ->method('findByUri')
137
+            ->willReturnOnConsecutiveCalls(...$principals);
138
+
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
+
154
+        // test acls on the child
155
+        $uri = static::getUniqueID('calobj');
156
+        $calData = <<<'EOD'
157 157
 BEGIN:VCALENDAR
158 158
 VERSION:2.0
159 159
 PRODID:ownCloud Calendar
@@ -170,35 +170,35 @@  discard block
 block discarded – undo
170 170
 END:VCALENDAR
171 171
 EOD;
172 172
 
173
-		$this->dispatcher->expects(self::atLeastOnce())
174
-			->method('dispatchTyped');
175
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
176
-
177
-		/** @var IACL $child */
178
-		$child = $calendar->getChild($uri);
179
-		$acl = $child->getACL();
180
-		$this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
181
-		$this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
182
-		$this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
183
-		$this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
184
-
185
-		// delete the calendar
186
-		$this->dispatcher->expects(self::once())
187
-			->method('dispatchTyped')
188
-			->with(self::callback(function ($event) {
189
-				return $event instanceof CalendarDeletedEvent;
190
-			}));
191
-		$this->backend->deleteCalendar($calendars[0]['id'], true);
192
-		$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
193
-		self::assertEmpty($calendars);
194
-	}
195
-
196
-	public function testCalendarObjectsOperations(): void {
197
-		$calendarId = $this->createTestCalendar();
198
-
199
-		// create a card
200
-		$uri = static::getUniqueID('calobj');
201
-		$calData = <<<'EOD'
173
+        $this->dispatcher->expects(self::atLeastOnce())
174
+            ->method('dispatchTyped');
175
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
176
+
177
+        /** @var IACL $child */
178
+        $child = $calendar->getChild($uri);
179
+        $acl = $child->getACL();
180
+        $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
181
+        $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
182
+        $this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
183
+        $this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
184
+
185
+        // delete the calendar
186
+        $this->dispatcher->expects(self::once())
187
+            ->method('dispatchTyped')
188
+            ->with(self::callback(function ($event) {
189
+                return $event instanceof CalendarDeletedEvent;
190
+            }));
191
+        $this->backend->deleteCalendar($calendars[0]['id'], true);
192
+        $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
193
+        self::assertEmpty($calendars);
194
+    }
195
+
196
+    public function testCalendarObjectsOperations(): void {
197
+        $calendarId = $this->createTestCalendar();
198
+
199
+        // create a card
200
+        $uri = static::getUniqueID('calobj');
201
+        $calData = <<<'EOD'
202 202
 BEGIN:VCALENDAR
203 203
 VERSION:2.0
204 204
 PRODID:ownCloud Calendar
@@ -215,30 +215,30 @@  discard block
 block discarded – undo
215 215
 END:VCALENDAR
216 216
 EOD;
217 217
 
218
-		$this->dispatcher->expects(self::atLeastOnce())
219
-			->method('dispatchTyped');
220
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
221
-
222
-		// get all the calendar objects
223
-		$calendarObjects = $this->backend->getCalendarObjects($calendarId);
224
-		$this->assertCount(1, $calendarObjects);
225
-		$this->assertEquals($calendarId, $calendarObjects[0]['calendarid']);
226
-		$this->assertArrayHasKey('classification', $calendarObjects[0]);
227
-
228
-		// get the calendar objects
229
-		$calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
230
-		$this->assertNotNull($calendarObject);
231
-		$this->assertArrayHasKey('id', $calendarObject);
232
-		$this->assertArrayHasKey('uri', $calendarObject);
233
-		$this->assertArrayHasKey('lastmodified', $calendarObject);
234
-		$this->assertArrayHasKey('etag', $calendarObject);
235
-		$this->assertArrayHasKey('size', $calendarObject);
236
-		$this->assertArrayHasKey('classification', $calendarObject);
237
-		$this->assertArrayHasKey('{' . SharingPlugin::NS_NEXTCLOUD . '}deleted-at', $calendarObject);
238
-		$this->assertEquals($calData, $calendarObject['calendardata']);
239
-
240
-		// update the card
241
-		$calData = <<<'EOD'
218
+        $this->dispatcher->expects(self::atLeastOnce())
219
+            ->method('dispatchTyped');
220
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
221
+
222
+        // get all the calendar objects
223
+        $calendarObjects = $this->backend->getCalendarObjects($calendarId);
224
+        $this->assertCount(1, $calendarObjects);
225
+        $this->assertEquals($calendarId, $calendarObjects[0]['calendarid']);
226
+        $this->assertArrayHasKey('classification', $calendarObjects[0]);
227
+
228
+        // get the calendar objects
229
+        $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
230
+        $this->assertNotNull($calendarObject);
231
+        $this->assertArrayHasKey('id', $calendarObject);
232
+        $this->assertArrayHasKey('uri', $calendarObject);
233
+        $this->assertArrayHasKey('lastmodified', $calendarObject);
234
+        $this->assertArrayHasKey('etag', $calendarObject);
235
+        $this->assertArrayHasKey('size', $calendarObject);
236
+        $this->assertArrayHasKey('classification', $calendarObject);
237
+        $this->assertArrayHasKey('{' . SharingPlugin::NS_NEXTCLOUD . '}deleted-at', $calendarObject);
238
+        $this->assertEquals($calData, $calendarObject['calendardata']);
239
+
240
+        // update the card
241
+        $calData = <<<'EOD'
242 242
 BEGIN:VCALENDAR
243 243
 VERSION:2.0
244 244
 PRODID:ownCloud Calendar
@@ -253,28 +253,28 @@  discard block
 block discarded – undo
253 253
 END:VEVENT
254 254
 END:VCALENDAR
255 255
 EOD;
256
-		$this->dispatcher->expects(self::atLeastOnce())
257
-			->method('dispatchTyped');
258
-		$this->backend->updateCalendarObject($calendarId, $uri, $calData);
259
-		$calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
260
-		$this->assertEquals($calData, $calendarObject['calendardata']);
256
+        $this->dispatcher->expects(self::atLeastOnce())
257
+            ->method('dispatchTyped');
258
+        $this->backend->updateCalendarObject($calendarId, $uri, $calData);
259
+        $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
260
+        $this->assertEquals($calData, $calendarObject['calendardata']);
261 261
 
262
-		// delete the card
263
-		$this->dispatcher->expects(self::atLeastOnce())
264
-			->method('dispatchTyped');
265
-		$this->backend->deleteCalendarObject($calendarId, $uri);
266
-		$calendarObjects = $this->backend->getCalendarObjects($calendarId);
267
-		$this->assertCount(0, $calendarObjects);
268
-	}
262
+        // delete the card
263
+        $this->dispatcher->expects(self::atLeastOnce())
264
+            ->method('dispatchTyped');
265
+        $this->backend->deleteCalendarObject($calendarId, $uri);
266
+        $calendarObjects = $this->backend->getCalendarObjects($calendarId);
267
+        $this->assertCount(0, $calendarObjects);
268
+    }
269 269
 
270 270
 
271
-	public function testMultipleCalendarObjectsWithSameUID(): void {
272
-		$this->expectException(\Sabre\DAV\Exception\BadRequest::class);
273
-		$this->expectExceptionMessage('Calendar object with uid already exists in this calendar collection.');
271
+    public function testMultipleCalendarObjectsWithSameUID(): void {
272
+        $this->expectException(\Sabre\DAV\Exception\BadRequest::class);
273
+        $this->expectExceptionMessage('Calendar object with uid already exists in this calendar collection.');
274 274
 
275
-		$calendarId = $this->createTestCalendar();
275
+        $calendarId = $this->createTestCalendar();
276 276
 
277
-		$calData = <<<'EOD'
277
+        $calData = <<<'EOD'
278 278
 BEGIN:VCALENDAR
279 279
 VERSION:2.0
280 280
 PRODID:ownCloud Calendar
@@ -291,18 +291,18 @@  discard block
 block discarded – undo
291 291
 END:VCALENDAR
292 292
 EOD;
293 293
 
294
-		$uri0 = static::getUniqueID('event');
295
-		$uri1 = static::getUniqueID('event');
296
-		$this->backend->createCalendarObject($calendarId, $uri0, $calData);
297
-		$this->backend->createCalendarObject($calendarId, $uri1, $calData);
298
-	}
294
+        $uri0 = static::getUniqueID('event');
295
+        $uri1 = static::getUniqueID('event');
296
+        $this->backend->createCalendarObject($calendarId, $uri0, $calData);
297
+        $this->backend->createCalendarObject($calendarId, $uri1, $calData);
298
+    }
299 299
 
300
-	public function testMultiCalendarObjects(): void {
301
-		$calendarId = $this->createTestCalendar();
300
+    public function testMultiCalendarObjects(): void {
301
+        $calendarId = $this->createTestCalendar();
302 302
 
303
-		// create an event
304
-		$calData = [];
305
-		$calData[] = <<<'EOD'
303
+        // create an event
304
+        $calData = [];
305
+        $calData[] = <<<'EOD'
306 306
 BEGIN:VCALENDAR
307 307
 VERSION:2.0
308 308
 PRODID:ownCloud Calendar
@@ -319,7 +319,7 @@  discard block
 block discarded – undo
319 319
 END:VCALENDAR
320 320
 EOD;
321 321
 
322
-		$calData[] = <<<'EOD'
322
+        $calData[] = <<<'EOD'
323 323
 BEGIN:VCALENDAR
324 324
 VERSION:2.0
325 325
 PRODID:ownCloud Calendar
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
 END:VCALENDAR
337 337
 EOD;
338 338
 
339
-		$calData[] = <<<'EOD'
339
+        $calData[] = <<<'EOD'
340 340
 BEGIN:VCALENDAR
341 341
 VERSION:2.0
342 342
 PRODID:ownCloud Calendar
@@ -353,88 +353,88 @@  discard block
 block discarded – undo
353 353
 END:VCALENDAR
354 354
 EOD;
355 355
 
356
-		$uri0 = static::getUniqueID('card');
357
-		$this->dispatcher->expects(self::atLeastOnce())
358
-			->method('dispatchTyped');
359
-		$this->backend->createCalendarObject($calendarId, $uri0, $calData[0]);
360
-		$uri1 = static::getUniqueID('card');
361
-		$this->dispatcher->expects(self::atLeastOnce())
362
-			->method('dispatchTyped');
363
-		$this->backend->createCalendarObject($calendarId, $uri1, $calData[1]);
364
-		$uri2 = static::getUniqueID('card');
365
-		$this->dispatcher->expects(self::atLeastOnce())
366
-			->method('dispatchTyped');
367
-		$this->backend->createCalendarObject($calendarId, $uri2, $calData[2]);
368
-
369
-		// get all the cards
370
-		$calendarObjects = $this->backend->getCalendarObjects($calendarId);
371
-		$this->assertCount(3, $calendarObjects);
372
-
373
-		// get the cards
374
-		$calendarObjects = $this->backend->getMultipleCalendarObjects($calendarId, [$uri1, $uri2]);
375
-		$this->assertCount(2, $calendarObjects);
376
-		foreach ($calendarObjects as $card) {
377
-			$this->assertArrayHasKey('id', $card);
378
-			$this->assertArrayHasKey('uri', $card);
379
-			$this->assertArrayHasKey('lastmodified', $card);
380
-			$this->assertArrayHasKey('etag', $card);
381
-			$this->assertArrayHasKey('size', $card);
382
-			$this->assertArrayHasKey('classification', $card);
383
-		}
384
-
385
-		usort($calendarObjects, function ($a, $b) {
386
-			return $a['id'] - $b['id'];
387
-		});
388
-
389
-		$this->assertEquals($calData[1], $calendarObjects[0]['calendardata']);
390
-		$this->assertEquals($calData[2], $calendarObjects[1]['calendardata']);
391
-
392
-		// delete the card
393
-		$this->dispatcher->expects(self::atLeastOnce())
394
-			->method('dispatchTyped');
395
-		$this->backend->deleteCalendarObject($calendarId, $uri0);
396
-		$this->dispatcher->expects(self::atLeastOnce())
397
-			->method('dispatchTyped');
398
-		$this->backend->deleteCalendarObject($calendarId, $uri1);
399
-		$this->dispatcher->expects(self::atLeastOnce())
400
-			->method('dispatchTyped');
401
-		$this->backend->deleteCalendarObject($calendarId, $uri2);
402
-		$calendarObjects = $this->backend->getCalendarObjects($calendarId);
403
-		$this->assertCount(0, $calendarObjects);
404
-	}
405
-
406
-	/**
407
-	 * @dataProvider providesCalendarQueryParameters
408
-	 */
409
-	public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter): void {
410
-		$calendarId = $this->createTestCalendar();
411
-		$events = [];
412
-		$events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
413
-		$events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z');
414
-		$events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z');
415
-		if (PHP_INT_SIZE > 8) {
416
-			$events[3] = $this->createEvent($calendarId, '21130912T130000Z', '22130912T130000Z');
417
-		} else {
418
-			/* On 32bit we do not support events after 2038 */
419
-			$events[3] = $this->createEvent($calendarId, '20370912T130000Z', '20370912T130000Z');
420
-		}
421
-
422
-		$result = $this->backend->calendarQuery($calendarId, [
423
-			'name' => '',
424
-			'prop-filters' => $propFilters,
425
-			'comp-filters' => $compFilter
426
-		]);
427
-
428
-		$expectedEventsInResult = array_map(function ($index) use ($events) {
429
-			return $events[$index];
430
-		}, $expectedEventsInResult);
431
-		$this->assertEqualsCanonicalizing($expectedEventsInResult, $result);
432
-	}
433
-
434
-	public function testGetCalendarObjectByUID(): void {
435
-		$calendarId = $this->createTestCalendar();
436
-		$uri = static::getUniqueID('calobj');
437
-		$calData = <<<'EOD'
356
+        $uri0 = static::getUniqueID('card');
357
+        $this->dispatcher->expects(self::atLeastOnce())
358
+            ->method('dispatchTyped');
359
+        $this->backend->createCalendarObject($calendarId, $uri0, $calData[0]);
360
+        $uri1 = static::getUniqueID('card');
361
+        $this->dispatcher->expects(self::atLeastOnce())
362
+            ->method('dispatchTyped');
363
+        $this->backend->createCalendarObject($calendarId, $uri1, $calData[1]);
364
+        $uri2 = static::getUniqueID('card');
365
+        $this->dispatcher->expects(self::atLeastOnce())
366
+            ->method('dispatchTyped');
367
+        $this->backend->createCalendarObject($calendarId, $uri2, $calData[2]);
368
+
369
+        // get all the cards
370
+        $calendarObjects = $this->backend->getCalendarObjects($calendarId);
371
+        $this->assertCount(3, $calendarObjects);
372
+
373
+        // get the cards
374
+        $calendarObjects = $this->backend->getMultipleCalendarObjects($calendarId, [$uri1, $uri2]);
375
+        $this->assertCount(2, $calendarObjects);
376
+        foreach ($calendarObjects as $card) {
377
+            $this->assertArrayHasKey('id', $card);
378
+            $this->assertArrayHasKey('uri', $card);
379
+            $this->assertArrayHasKey('lastmodified', $card);
380
+            $this->assertArrayHasKey('etag', $card);
381
+            $this->assertArrayHasKey('size', $card);
382
+            $this->assertArrayHasKey('classification', $card);
383
+        }
384
+
385
+        usort($calendarObjects, function ($a, $b) {
386
+            return $a['id'] - $b['id'];
387
+        });
388
+
389
+        $this->assertEquals($calData[1], $calendarObjects[0]['calendardata']);
390
+        $this->assertEquals($calData[2], $calendarObjects[1]['calendardata']);
391
+
392
+        // delete the card
393
+        $this->dispatcher->expects(self::atLeastOnce())
394
+            ->method('dispatchTyped');
395
+        $this->backend->deleteCalendarObject($calendarId, $uri0);
396
+        $this->dispatcher->expects(self::atLeastOnce())
397
+            ->method('dispatchTyped');
398
+        $this->backend->deleteCalendarObject($calendarId, $uri1);
399
+        $this->dispatcher->expects(self::atLeastOnce())
400
+            ->method('dispatchTyped');
401
+        $this->backend->deleteCalendarObject($calendarId, $uri2);
402
+        $calendarObjects = $this->backend->getCalendarObjects($calendarId);
403
+        $this->assertCount(0, $calendarObjects);
404
+    }
405
+
406
+    /**
407
+     * @dataProvider providesCalendarQueryParameters
408
+     */
409
+    public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter): void {
410
+        $calendarId = $this->createTestCalendar();
411
+        $events = [];
412
+        $events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
413
+        $events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z');
414
+        $events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z');
415
+        if (PHP_INT_SIZE > 8) {
416
+            $events[3] = $this->createEvent($calendarId, '21130912T130000Z', '22130912T130000Z');
417
+        } else {
418
+            /* On 32bit we do not support events after 2038 */
419
+            $events[3] = $this->createEvent($calendarId, '20370912T130000Z', '20370912T130000Z');
420
+        }
421
+
422
+        $result = $this->backend->calendarQuery($calendarId, [
423
+            'name' => '',
424
+            'prop-filters' => $propFilters,
425
+            'comp-filters' => $compFilter
426
+        ]);
427
+
428
+        $expectedEventsInResult = array_map(function ($index) use ($events) {
429
+            return $events[$index];
430
+        }, $expectedEventsInResult);
431
+        $this->assertEqualsCanonicalizing($expectedEventsInResult, $result);
432
+    }
433
+
434
+    public function testGetCalendarObjectByUID(): void {
435
+        $calendarId = $this->createTestCalendar();
436
+        $uri = static::getUniqueID('calobj');
437
+        $calData = <<<'EOD'
438 438
 BEGIN:VCALENDAR
439 439
 VERSION:2.0
440 440
 PRODID:ownCloud Calendar
@@ -452,175 +452,175 @@  discard block
 block discarded – undo
452 452
 EOD;
453 453
 
454 454
 
455
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
456
-
457
-		$co = $this->backend->getCalendarObjectByUID(self::UNIT_TEST_USER, '47d15e3ec8');
458
-		$this->assertNotNull($co);
459
-	}
460
-
461
-	public static function providesCalendarQueryParameters(): array {
462
-		return [
463
-			'all' => [[0, 1, 2, 3], [], []],
464
-			'only-todos' => [[], ['name' => 'VTODO'], []],
465
-			'only-events' => [[0, 1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],],
466
-			'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' => []]],],
467
-			'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' => []]],],
468
-			'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' => []]],],
469
-		];
470
-	}
471
-
472
-	public function testCalendarSynchronization(): void {
473
-
474
-		// construct calendar for testing
475
-		$calendarId = $this->createTestCalendar();
476
-
477
-		/** test fresh sync state with NO events in calendar */
478
-		// construct test state
479
-		$stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []];
480
-		// retrieve live state
481
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
482
-		// test live state
483
-		$this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with NO events in calendar');
484
-
485
-		/** test delta sync state with NO events in calendar */
486
-		// construct test state
487
-		$stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []];
488
-		// retrieve live state
489
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1);
490
-		// test live state
491
-		$this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with NO events in calendar');
492
-
493
-		/** add events to calendar */
494
-		$event1 = $this->createEvent($calendarId, '20240701T130000Z', '20240701T140000Z');
495
-		$event2 = $this->createEvent($calendarId, '20240701T140000Z', '20240701T150000Z');
496
-		$event3 = $this->createEvent($calendarId, '20240701T150000Z', '20240701T160000Z');
497
-
498
-		/** test fresh sync state with events in calendar */
499
-		// construct expected state
500
-		$stateTest = ['syncToken' => 4, 'added' => [$event1, $event2, $event3], 'modified' => [], 'deleted' => []];
501
-		sort($stateTest['added']);
502
-		// retrieve live state
503
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
504
-		// sort live state results
505
-		sort($stateLive['added']);
506
-		sort($stateLive['modified']);
507
-		sort($stateLive['deleted']);
508
-		// test live state
509
-		$this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with events in calendar');
510
-
511
-		/** test delta sync state with events in calendar */
512
-		// construct expected state
513
-		$stateTest = ['syncToken' => 4, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []];
514
-		sort($stateTest['added']);
515
-		// retrieve live state
516
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1);
517
-		// sort live state results
518
-		sort($stateLive['added']);
519
-		sort($stateLive['modified']);
520
-		sort($stateLive['deleted']);
521
-		// test live state
522
-		$this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with events in calendar');
523
-
524
-		/** modify/delete events in calendar */
525
-		$this->deleteEvent($calendarId, $event1);
526
-		$this->modifyEvent($calendarId, $event2, '20250701T140000Z', '20250701T150000Z');
527
-
528
-		/** test fresh sync state with modified/deleted events in calendar */
529
-		// construct expected state
530
-		$stateTest = ['syncToken' => 6, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []];
531
-		sort($stateTest['added']);
532
-		// retrieve live state
533
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
534
-		// sort live state results
535
-		sort($stateLive['added']);
536
-		sort($stateLive['modified']);
537
-		sort($stateLive['deleted']);
538
-		// test live state
539
-		$this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with modified/deleted events in calendar');
540
-
541
-		/** test delta sync state with modified/deleted events in calendar */
542
-		// construct expected state
543
-		$stateTest = ['syncToken' => 6, 'added' => [$event3], 'modified' => [$event2], 'deleted' => [$event1]];
544
-		// retrieve live state
545
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '3', 1);
546
-		// test live state
547
-		$this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar');
548
-
549
-		/** test delta sync state with modified/deleted events in calendar and invalid token */
550
-		// construct expected state
551
-		$stateTest = ['syncToken' => 6, 'added' => [], 'modified' => [], 'deleted' => []];
552
-		// retrieve live state
553
-		$stateLive = $this->backend->getChangesForCalendar($calendarId, '6', 1);
554
-		// test live state
555
-		$this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar and invalid token');
556
-
557
-	}
558
-
559
-	public function testPublications(): void {
560
-		$this->dispatcher->expects(self::atLeastOnce())
561
-			->method('dispatchTyped');
562
-
563
-		$this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
564
-
565
-		$calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
566
-
567
-		/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
568
-		$l10n = $this->createMock(IL10N::class);
569
-		$config = $this->createMock(IConfig::class);
570
-		$logger = $this->createMock(\Psr\Log\LoggerInterface::class);
571
-		$calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger);
572
-		$calendar->setPublishStatus(true);
573
-		$this->assertNotEquals(false, $calendar->getPublishStatus());
574
-
575
-		$publicCalendars = $this->backend->getPublicCalendars();
576
-		$this->assertCount(1, $publicCalendars);
577
-		$this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']);
578
-		$this->assertEquals('User\'s displayname', $publicCalendars[0]['{http://nextcloud.com/ns}owner-displayname']);
579
-
580
-		$publicCalendarURI = $publicCalendars[0]['uri'];
581
-		$publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI);
582
-		$this->assertEquals(true, $publicCalendar['{http://owncloud.org/ns}public']);
583
-
584
-		$calendar->setPublishStatus(false);
585
-		$this->assertEquals(false, $calendar->getPublishStatus());
586
-
587
-		$this->expectException(NotFound::class);
588
-		$this->backend->getPublicCalendar($publicCalendarURI);
589
-	}
590
-
591
-	public function testSubscriptions(): void {
592
-		$id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [
593
-			'{http://calendarserver.org/ns/}source' => new Href('test-source'),
594
-			'{http://apple.com/ns/ical/}calendar-color' => '#1C4587',
595
-			'{http://calendarserver.org/ns/}subscribed-strip-todos' => ''
596
-		]);
597
-
598
-		$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
599
-		$this->assertCount(1, $subscriptions);
600
-		$this->assertEquals('#1C4587', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
601
-		$this->assertEquals(true, $subscriptions[0]['{http://calendarserver.org/ns/}subscribed-strip-todos']);
602
-		$this->assertEquals($id, $subscriptions[0]['id']);
603
-
604
-		$patch = new PropPatch([
605
-			'{DAV:}displayname' => 'Unit test',
606
-			'{http://apple.com/ns/ical/}calendar-color' => '#ac0606',
607
-		]);
608
-		$this->backend->updateSubscription($id, $patch);
609
-		$patch->commit();
610
-
611
-		$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
612
-		$this->assertCount(1, $subscriptions);
613
-		$this->assertEquals($id, $subscriptions[0]['id']);
614
-		$this->assertEquals('Unit test', $subscriptions[0]['{DAV:}displayname']);
615
-		$this->assertEquals('#ac0606', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
616
-
617
-		$this->backend->deleteSubscription($id);
618
-		$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
619
-		$this->assertCount(0, $subscriptions);
620
-	}
621
-
622
-	public static function providesSchedulingData(): array {
623
-		$data = <<<EOS
455
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
456
+
457
+        $co = $this->backend->getCalendarObjectByUID(self::UNIT_TEST_USER, '47d15e3ec8');
458
+        $this->assertNotNull($co);
459
+    }
460
+
461
+    public static function providesCalendarQueryParameters(): array {
462
+        return [
463
+            'all' => [[0, 1, 2, 3], [], []],
464
+            'only-todos' => [[], ['name' => 'VTODO'], []],
465
+            'only-events' => [[0, 1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],],
466
+            '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' => []]],],
467
+            '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' => []]],],
468
+            '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' => []]],],
469
+        ];
470
+    }
471
+
472
+    public function testCalendarSynchronization(): void {
473
+
474
+        // construct calendar for testing
475
+        $calendarId = $this->createTestCalendar();
476
+
477
+        /** test fresh sync state with NO events in calendar */
478
+        // construct test state
479
+        $stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []];
480
+        // retrieve live state
481
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
482
+        // test live state
483
+        $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with NO events in calendar');
484
+
485
+        /** test delta sync state with NO events in calendar */
486
+        // construct test state
487
+        $stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []];
488
+        // retrieve live state
489
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1);
490
+        // test live state
491
+        $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with NO events in calendar');
492
+
493
+        /** add events to calendar */
494
+        $event1 = $this->createEvent($calendarId, '20240701T130000Z', '20240701T140000Z');
495
+        $event2 = $this->createEvent($calendarId, '20240701T140000Z', '20240701T150000Z');
496
+        $event3 = $this->createEvent($calendarId, '20240701T150000Z', '20240701T160000Z');
497
+
498
+        /** test fresh sync state with events in calendar */
499
+        // construct expected state
500
+        $stateTest = ['syncToken' => 4, 'added' => [$event1, $event2, $event3], 'modified' => [], 'deleted' => []];
501
+        sort($stateTest['added']);
502
+        // retrieve live state
503
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
504
+        // sort live state results
505
+        sort($stateLive['added']);
506
+        sort($stateLive['modified']);
507
+        sort($stateLive['deleted']);
508
+        // test live state
509
+        $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with events in calendar');
510
+
511
+        /** test delta sync state with events in calendar */
512
+        // construct expected state
513
+        $stateTest = ['syncToken' => 4, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []];
514
+        sort($stateTest['added']);
515
+        // retrieve live state
516
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1);
517
+        // sort live state results
518
+        sort($stateLive['added']);
519
+        sort($stateLive['modified']);
520
+        sort($stateLive['deleted']);
521
+        // test live state
522
+        $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with events in calendar');
523
+
524
+        /** modify/delete events in calendar */
525
+        $this->deleteEvent($calendarId, $event1);
526
+        $this->modifyEvent($calendarId, $event2, '20250701T140000Z', '20250701T150000Z');
527
+
528
+        /** test fresh sync state with modified/deleted events in calendar */
529
+        // construct expected state
530
+        $stateTest = ['syncToken' => 6, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []];
531
+        sort($stateTest['added']);
532
+        // retrieve live state
533
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1);
534
+        // sort live state results
535
+        sort($stateLive['added']);
536
+        sort($stateLive['modified']);
537
+        sort($stateLive['deleted']);
538
+        // test live state
539
+        $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with modified/deleted events in calendar');
540
+
541
+        /** test delta sync state with modified/deleted events in calendar */
542
+        // construct expected state
543
+        $stateTest = ['syncToken' => 6, 'added' => [$event3], 'modified' => [$event2], 'deleted' => [$event1]];
544
+        // retrieve live state
545
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '3', 1);
546
+        // test live state
547
+        $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar');
548
+
549
+        /** test delta sync state with modified/deleted events in calendar and invalid token */
550
+        // construct expected state
551
+        $stateTest = ['syncToken' => 6, 'added' => [], 'modified' => [], 'deleted' => []];
552
+        // retrieve live state
553
+        $stateLive = $this->backend->getChangesForCalendar($calendarId, '6', 1);
554
+        // test live state
555
+        $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar and invalid token');
556
+
557
+    }
558
+
559
+    public function testPublications(): void {
560
+        $this->dispatcher->expects(self::atLeastOnce())
561
+            ->method('dispatchTyped');
562
+
563
+        $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
564
+
565
+        $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
566
+
567
+        /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
568
+        $l10n = $this->createMock(IL10N::class);
569
+        $config = $this->createMock(IConfig::class);
570
+        $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
571
+        $calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger);
572
+        $calendar->setPublishStatus(true);
573
+        $this->assertNotEquals(false, $calendar->getPublishStatus());
574
+
575
+        $publicCalendars = $this->backend->getPublicCalendars();
576
+        $this->assertCount(1, $publicCalendars);
577
+        $this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']);
578
+        $this->assertEquals('User\'s displayname', $publicCalendars[0]['{http://nextcloud.com/ns}owner-displayname']);
579
+
580
+        $publicCalendarURI = $publicCalendars[0]['uri'];
581
+        $publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI);
582
+        $this->assertEquals(true, $publicCalendar['{http://owncloud.org/ns}public']);
583
+
584
+        $calendar->setPublishStatus(false);
585
+        $this->assertEquals(false, $calendar->getPublishStatus());
586
+
587
+        $this->expectException(NotFound::class);
588
+        $this->backend->getPublicCalendar($publicCalendarURI);
589
+    }
590
+
591
+    public function testSubscriptions(): void {
592
+        $id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [
593
+            '{http://calendarserver.org/ns/}source' => new Href('test-source'),
594
+            '{http://apple.com/ns/ical/}calendar-color' => '#1C4587',
595
+            '{http://calendarserver.org/ns/}subscribed-strip-todos' => ''
596
+        ]);
597
+
598
+        $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
599
+        $this->assertCount(1, $subscriptions);
600
+        $this->assertEquals('#1C4587', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
601
+        $this->assertEquals(true, $subscriptions[0]['{http://calendarserver.org/ns/}subscribed-strip-todos']);
602
+        $this->assertEquals($id, $subscriptions[0]['id']);
603
+
604
+        $patch = new PropPatch([
605
+            '{DAV:}displayname' => 'Unit test',
606
+            '{http://apple.com/ns/ical/}calendar-color' => '#ac0606',
607
+        ]);
608
+        $this->backend->updateSubscription($id, $patch);
609
+        $patch->commit();
610
+
611
+        $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
612
+        $this->assertCount(1, $subscriptions);
613
+        $this->assertEquals($id, $subscriptions[0]['id']);
614
+        $this->assertEquals('Unit test', $subscriptions[0]['{DAV:}displayname']);
615
+        $this->assertEquals('#ac0606', $subscriptions[0]['{http://apple.com/ns/ical/}calendar-color']);
616
+
617
+        $this->backend->deleteSubscription($id);
618
+        $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
619
+        $this->assertCount(0, $subscriptions);
620
+    }
621
+
622
+    public static function providesSchedulingData(): array {
623
+        $data = <<<EOS
624 624
 BEGIN:VCALENDAR
625 625
 VERSION:2.0
626 626
 PRODID:-//Sabre//Sabre VObject 3.5.0//EN
@@ -685,74 +685,74 @@  discard block
 block discarded – undo
685 685
 END:VCALENDAR
686 686
 EOS;
687 687
 
688
-		return [
689
-			'no data' => [''],
690
-			'failing on postgres' => [$data]
691
-		];
692
-	}
688
+        return [
689
+            'no data' => [''],
690
+            'failing on postgres' => [$data]
691
+        ];
692
+    }
693 693
 
694
-	/**
695
-	 * @dataProvider providesSchedulingData
696
-	 * @param $objectData
697
-	 */
698
-	public function testScheduling($objectData): void {
699
-		$this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', $objectData);
694
+    /**
695
+     * @dataProvider providesSchedulingData
696
+     * @param $objectData
697
+     */
698
+    public function testScheduling($objectData): void {
699
+        $this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', $objectData);
700 700
 
701
-		$sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
702
-		$this->assertCount(1, $sos);
701
+        $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
702
+        $this->assertCount(1, $sos);
703 703
 
704
-		$so = $this->backend->getSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
705
-		$this->assertNotNull($so);
704
+        $so = $this->backend->getSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
705
+        $this->assertNotNull($so);
706 706
 
707
-		$this->backend->deleteSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
707
+        $this->backend->deleteSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
708 708
 
709
-		$sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
710
-		$this->assertCount(0, $sos);
711
-	}
709
+        $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
710
+        $this->assertCount(0, $sos);
711
+    }
712 712
 
713
-	/**
714
-	 * @dataProvider providesCalDataForGetDenormalizedData
715
-	 */
716
-	public function testGetDenormalizedData($expected, $key, $calData): void {
717
-		try {
718
-			$actual = $this->backend->getDenormalizedData($calData);
719
-			$this->assertEquals($expected, $actual[$key]);
720
-		} catch (\Throwable $e) {
721
-			if (($e->getMessage() === 'Epoch doesn\'t fit in a PHP integer') && (PHP_INT_SIZE < 8)) {
722
-				$this->markTestSkipped('This fail on 32bits because of PHP limitations in DateTime');
723
-			}
724
-			throw $e;
725
-		}
726
-	}
713
+    /**
714
+     * @dataProvider providesCalDataForGetDenormalizedData
715
+     */
716
+    public function testGetDenormalizedData($expected, $key, $calData): void {
717
+        try {
718
+            $actual = $this->backend->getDenormalizedData($calData);
719
+            $this->assertEquals($expected, $actual[$key]);
720
+        } catch (\Throwable $e) {
721
+            if (($e->getMessage() === 'Epoch doesn\'t fit in a PHP integer') && (PHP_INT_SIZE < 8)) {
722
+                $this->markTestSkipped('This fail on 32bits because of PHP limitations in DateTime');
723
+            }
724
+            throw $e;
725
+        }
726
+    }
727 727
 
728
-	public static function providesCalDataForGetDenormalizedData(): array {
729
-		return [
730
-			'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"],
731
-			'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"],
728
+    public static function providesCalDataForGetDenormalizedData(): array {
729
+        return [
730
+            '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"],
731
+            '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"],
732 732
 
733
-			'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:[email protected]\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:[email protected]\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"],
733
+            '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:[email protected]\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:[email protected]\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"],
734 734
 
735
-			'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:[email protected]\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"],
735
+            '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:[email protected]\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"],
736 736
 
737
-			'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:[email protected]\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:[email protected]\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200915T162810Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
737
+            '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:[email protected]\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:[email protected]\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200915T162810Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
738 738
 
739
-			'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"],
739
+            '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"],
740 740
 
741
-			'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"],
741
+            '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"],
742 742
 
743
-			'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"],
743
+            '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"],
744 744
 
745
-			'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"],
745
+            '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"],
746 746
 
747
-			'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"],
748
-		];
749
-	}
747
+            '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"],
748
+        ];
749
+    }
750 750
 
751
-	public function testCalendarSearch(): void {
752
-		$calendarId = $this->createTestCalendar();
751
+    public function testCalendarSearch(): void {
752
+        $calendarId = $this->createTestCalendar();
753 753
 
754
-		$uri = static::getUniqueID('calobj');
755
-		$calData = <<<EOD
754
+        $uri = static::getUniqueID('calobj');
755
+        $calData = <<<EOD
756 756
 BEGIN:VCALENDAR
757 757
 VERSION:2.0
758 758
 PRODID:ownCloud Calendar
@@ -769,24 +769,24 @@  discard block
 block discarded – undo
769 769
 END:VCALENDAR
770 770
 EOD;
771 771
 
772
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
772
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
773 773
 
774
-		$search1 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
775
-			'comps' => [
776
-				'VEVENT',
777
-				'VTODO'
778
-			],
779
-			'props' => [
780
-				'SUMMARY',
781
-				'LOCATION'
782
-			],
783
-			'search-term' => 'Test',
784
-		]);
785
-		$this->assertEquals(count($search1), 1);
774
+        $search1 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
775
+            'comps' => [
776
+                'VEVENT',
777
+                'VTODO'
778
+            ],
779
+            'props' => [
780
+                'SUMMARY',
781
+                'LOCATION'
782
+            ],
783
+            'search-term' => 'Test',
784
+        ]);
785
+        $this->assertEquals(count($search1), 1);
786 786
 
787 787
 
788
-		// update the card
789
-		$calData = <<<'EOD'
788
+        // update the card
789
+        $calData = <<<'EOD'
790 790
 BEGIN:VCALENDAR
791 791
 VERSION:2.0
792 792
 PRODID:ownCloud Calendar
@@ -802,93 +802,93 @@  discard block
 block discarded – undo
802 802
 END:VEVENT
803 803
 END:VCALENDAR
804 804
 EOD;
805
-		$this->backend->updateCalendarObject($calendarId, $uri, $calData);
806
-
807
-		$search2 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
808
-			'comps' => [
809
-				'VEVENT',
810
-				'VTODO'
811
-			],
812
-			'props' => [
813
-				'SUMMARY',
814
-				'LOCATION'
815
-			],
816
-			'search-term' => 'Test',
817
-		]);
818
-		$this->assertEquals(count($search2), 0);
819
-
820
-		$search3 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
821
-			'comps' => [
822
-				'VEVENT',
823
-				'VTODO'
824
-			],
825
-			'props' => [
826
-				'SUMMARY',
827
-				'LOCATION'
828
-			],
829
-			'params' => [
830
-				[
831
-					'property' => 'ATTENDEE',
832
-					'parameter' => 'CN'
833
-				]
834
-			],
835
-			'search-term' => 'Test',
836
-		]);
837
-		$this->assertEquals(count($search3), 1);
838
-
839
-		// t matches both summary and attendee's CN, but we want unique results
840
-		$search4 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
841
-			'comps' => [
842
-				'VEVENT',
843
-				'VTODO'
844
-			],
845
-			'props' => [
846
-				'SUMMARY',
847
-				'LOCATION'
848
-			],
849
-			'params' => [
850
-				[
851
-					'property' => 'ATTENDEE',
852
-					'parameter' => 'CN'
853
-				]
854
-			],
855
-			'search-term' => 't',
856
-		]);
857
-		$this->assertEquals(count($search4), 1);
858
-
859
-		$this->backend->deleteCalendarObject($calendarId, $uri);
860
-
861
-		$search5 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
862
-			'comps' => [
863
-				'VEVENT',
864
-				'VTODO'
865
-			],
866
-			'props' => [
867
-				'SUMMARY',
868
-				'LOCATION'
869
-			],
870
-			'params' => [
871
-				[
872
-					'property' => 'ATTENDEE',
873
-					'parameter' => 'CN'
874
-				]
875
-			],
876
-			'search-term' => 't',
877
-		]);
878
-		$this->assertEquals(count($search5), 0);
879
-	}
880
-
881
-	/**
882
-	 * @dataProvider searchDataProvider
883
-	 */
884
-	public function testSearch(bool $isShared, array $searchOptions, int $count): void {
885
-		$calendarId = $this->createTestCalendar();
886
-
887
-		$uris = [];
888
-		$calData = [];
889
-
890
-		$uris[] = static::getUniqueID('calobj');
891
-		$calData[] = <<<EOD
805
+        $this->backend->updateCalendarObject($calendarId, $uri, $calData);
806
+
807
+        $search2 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
808
+            'comps' => [
809
+                'VEVENT',
810
+                'VTODO'
811
+            ],
812
+            'props' => [
813
+                'SUMMARY',
814
+                'LOCATION'
815
+            ],
816
+            'search-term' => 'Test',
817
+        ]);
818
+        $this->assertEquals(count($search2), 0);
819
+
820
+        $search3 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
821
+            'comps' => [
822
+                'VEVENT',
823
+                'VTODO'
824
+            ],
825
+            'props' => [
826
+                'SUMMARY',
827
+                'LOCATION'
828
+            ],
829
+            'params' => [
830
+                [
831
+                    'property' => 'ATTENDEE',
832
+                    'parameter' => 'CN'
833
+                ]
834
+            ],
835
+            'search-term' => 'Test',
836
+        ]);
837
+        $this->assertEquals(count($search3), 1);
838
+
839
+        // t matches both summary and attendee's CN, but we want unique results
840
+        $search4 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
841
+            'comps' => [
842
+                'VEVENT',
843
+                'VTODO'
844
+            ],
845
+            'props' => [
846
+                'SUMMARY',
847
+                'LOCATION'
848
+            ],
849
+            'params' => [
850
+                [
851
+                    'property' => 'ATTENDEE',
852
+                    'parameter' => 'CN'
853
+                ]
854
+            ],
855
+            'search-term' => 't',
856
+        ]);
857
+        $this->assertEquals(count($search4), 1);
858
+
859
+        $this->backend->deleteCalendarObject($calendarId, $uri);
860
+
861
+        $search5 = $this->backend->calendarSearch(self::UNIT_TEST_USER, [
862
+            'comps' => [
863
+                'VEVENT',
864
+                'VTODO'
865
+            ],
866
+            'props' => [
867
+                'SUMMARY',
868
+                'LOCATION'
869
+            ],
870
+            'params' => [
871
+                [
872
+                    'property' => 'ATTENDEE',
873
+                    'parameter' => 'CN'
874
+                ]
875
+            ],
876
+            'search-term' => 't',
877
+        ]);
878
+        $this->assertEquals(count($search5), 0);
879
+    }
880
+
881
+    /**
882
+     * @dataProvider searchDataProvider
883
+     */
884
+    public function testSearch(bool $isShared, array $searchOptions, int $count): void {
885
+        $calendarId = $this->createTestCalendar();
886
+
887
+        $uris = [];
888
+        $calData = [];
889
+
890
+        $uris[] = static::getUniqueID('calobj');
891
+        $calData[] = <<<EOD
892 892
 BEGIN:VCALENDAR
893 893
 VERSION:2.0
894 894
 PRODID:Nextcloud Calendar
@@ -905,8 +905,8 @@  discard block
 block discarded – undo
905 905
 END:VCALENDAR
906 906
 EOD;
907 907
 
908
-		$uris[] = static::getUniqueID('calobj');
909
-		$calData[] = <<<EOD
908
+        $uris[] = static::getUniqueID('calobj');
909
+        $calData[] = <<<EOD
910 910
 BEGIN:VCALENDAR
911 911
 VERSION:2.0
912 912
 PRODID:Nextcloud Calendar
@@ -924,8 +924,8 @@  discard block
 block discarded – undo
924 924
 END:VCALENDAR
925 925
 EOD;
926 926
 
927
-		$uris[] = static::getUniqueID('calobj');
928
-		$calData[] = <<<EOD
927
+        $uris[] = static::getUniqueID('calobj');
928
+        $calData[] = <<<EOD
929 929
 BEGIN:VCALENDAR
930 930
 VERSION:2.0
931 931
 PRODID:Nextcloud Calendar
@@ -943,8 +943,8 @@  discard block
 block discarded – undo
943 943
 END:VCALENDAR
944 944
 EOD;
945 945
 
946
-		$uris[] = static::getUniqueID('calobj');
947
-		$calData[] = <<<EOD
946
+        $uris[] = static::getUniqueID('calobj');
947
+        $calData[] = <<<EOD
948 948
 BEGIN:VCALENDAR
949 949
 VERSION:2.0
950 950
 PRODID:Nextcloud Calendar
@@ -962,38 +962,38 @@  discard block
 block discarded – undo
962 962
 END:VCALENDAR
963 963
 EOD;
964 964
 
965
-		$uriCount = count($uris);
966
-		for ($i = 0; $i < $uriCount; $i++) {
967
-			$this->backend->createCalendarObject($calendarId,
968
-				$uris[$i], $calData[$i]);
969
-		}
970
-
971
-		$calendarInfo = [
972
-			'id' => $calendarId,
973
-			'principaluri' => 'user1',
974
-			'{http://owncloud.org/ns}owner-principal' => $isShared ? 'user2' : 'user1',
975
-		];
976
-
977
-		$result = $this->backend->search($calendarInfo, 'Test',
978
-			['SUMMARY', 'LOCATION', 'ATTENDEE'], $searchOptions, null, null);
979
-
980
-		$this->assertCount($count, $result);
981
-	}
982
-
983
-	public static function searchDataProvider(): array {
984
-		return [
985
-			[false, [], 4],
986
-			[true, ['timerange' => ['start' => new DateTime('2013-09-12 13:00:00'), 'end' => new DateTime('2013-09-12 14:00:00')]], 2],
987
-			[true, ['timerange' => ['start' => new DateTime('2013-09-12 15:00:00'), 'end' => new DateTime('2013-09-12 16:00:00')]], 0],
988
-		];
989
-	}
990
-
991
-	public function testSameUriSameIdForDifferentCalendarTypes(): void {
992
-		$calendarId = $this->createTestCalendar();
993
-		$subscriptionId = $this->createTestSubscription();
994
-
995
-		$uri = static::getUniqueID('calobj');
996
-		$calData = <<<EOD
965
+        $uriCount = count($uris);
966
+        for ($i = 0; $i < $uriCount; $i++) {
967
+            $this->backend->createCalendarObject($calendarId,
968
+                $uris[$i], $calData[$i]);
969
+        }
970
+
971
+        $calendarInfo = [
972
+            'id' => $calendarId,
973
+            'principaluri' => 'user1',
974
+            '{http://owncloud.org/ns}owner-principal' => $isShared ? 'user2' : 'user1',
975
+        ];
976
+
977
+        $result = $this->backend->search($calendarInfo, 'Test',
978
+            ['SUMMARY', 'LOCATION', 'ATTENDEE'], $searchOptions, null, null);
979
+
980
+        $this->assertCount($count, $result);
981
+    }
982
+
983
+    public static function searchDataProvider(): array {
984
+        return [
985
+            [false, [], 4],
986
+            [true, ['timerange' => ['start' => new DateTime('2013-09-12 13:00:00'), 'end' => new DateTime('2013-09-12 14:00:00')]], 2],
987
+            [true, ['timerange' => ['start' => new DateTime('2013-09-12 15:00:00'), 'end' => new DateTime('2013-09-12 16:00:00')]], 0],
988
+        ];
989
+    }
990
+
991
+    public function testSameUriSameIdForDifferentCalendarTypes(): void {
992
+        $calendarId = $this->createTestCalendar();
993
+        $subscriptionId = $this->createTestSubscription();
994
+
995
+        $uri = static::getUniqueID('calobj');
996
+        $calData = <<<EOD
997 997
 BEGIN:VCALENDAR
998 998
 VERSION:2.0
999 999
 PRODID:ownCloud Calendar
@@ -1010,7 +1010,7 @@  discard block
 block discarded – undo
1010 1010
 END:VCALENDAR
1011 1011
 EOD;
1012 1012
 
1013
-		$calData2 = <<<EOD
1013
+        $calData2 = <<<EOD
1014 1014
 BEGIN:VCALENDAR
1015 1015
 VERSION:2.0
1016 1016
 PRODID:ownCloud Calendar
@@ -1027,17 +1027,17 @@  discard block
 block discarded – undo
1027 1027
 END:VCALENDAR
1028 1028
 EOD;
1029 1029
 
1030
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
1031
-		$this->backend->createCalendarObject($subscriptionId, $uri, $calData2, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
1030
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
1031
+        $this->backend->createCalendarObject($subscriptionId, $uri, $calData2, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
1032 1032
 
1033
-		$this->assertEquals($calData, $this->backend->getCalendarObject($calendarId, $uri, CalDavBackend::CALENDAR_TYPE_CALENDAR)['calendardata']);
1034
-		$this->assertEquals($calData2, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION)['calendardata']);
1035
-	}
1033
+        $this->assertEquals($calData, $this->backend->getCalendarObject($calendarId, $uri, CalDavBackend::CALENDAR_TYPE_CALENDAR)['calendardata']);
1034
+        $this->assertEquals($calData2, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION)['calendardata']);
1035
+    }
1036 1036
 
1037
-	public function testPurgeAllCachedEventsForSubscription(): void {
1038
-		$subscriptionId = $this->createTestSubscription();
1039
-		$uri = static::getUniqueID('calobj');
1040
-		$calData = <<<EOD
1037
+    public function testPurgeAllCachedEventsForSubscription(): void {
1038
+        $subscriptionId = $this->createTestSubscription();
1039
+        $uri = static::getUniqueID('calobj');
1040
+        $calData = <<<EOD
1041 1041
 BEGIN:VCALENDAR
1042 1042
 VERSION:2.0
1043 1043
 PRODID:ownCloud Calendar
@@ -1054,30 +1054,30 @@  discard block
 block discarded – undo
1054 1054
 END:VCALENDAR
1055 1055
 EOD;
1056 1056
 
1057
-		$this->backend->createCalendarObject($subscriptionId, $uri, $calData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
1058
-		$this->backend->purgeAllCachedEventsForSubscription($subscriptionId);
1057
+        $this->backend->createCalendarObject($subscriptionId, $uri, $calData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
1058
+        $this->backend->purgeAllCachedEventsForSubscription($subscriptionId);
1059 1059
 
1060
-		$this->assertEquals(null, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION));
1061
-	}
1060
+        $this->assertEquals(null, $this->backend->getCalendarObject($subscriptionId, $uri, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION));
1061
+    }
1062 1062
 
1063
-	public function testCalendarMovement(): void {
1064
-		$this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
1063
+    public function testCalendarMovement(): void {
1064
+        $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
1065 1065
 
1066
-		$this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
1066
+        $this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
1067 1067
 
1068
-		$calendarInfoUser = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
1068
+        $calendarInfoUser = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
1069 1069
 
1070
-		$this->backend->moveCalendar('Example', self::UNIT_TEST_USER, self::UNIT_TEST_USER1);
1071
-		$this->assertCount(0, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
1072
-		$this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1));
1070
+        $this->backend->moveCalendar('Example', self::UNIT_TEST_USER, self::UNIT_TEST_USER1);
1071
+        $this->assertCount(0, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER));
1072
+        $this->assertCount(1, $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1));
1073 1073
 
1074
-		$calendarInfoUser1 = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1)[0];
1075
-		$this->assertEquals($calendarInfoUser['id'], $calendarInfoUser1['id']);
1076
-		$this->assertEquals($calendarInfoUser['uri'], $calendarInfoUser1['uri']);
1077
-	}
1074
+        $calendarInfoUser1 = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1)[0];
1075
+        $this->assertEquals($calendarInfoUser['id'], $calendarInfoUser1['id']);
1076
+        $this->assertEquals($calendarInfoUser['uri'], $calendarInfoUser1['uri']);
1077
+    }
1078 1078
 
1079
-	public function testSearchPrincipal(): void {
1080
-		$myPublic = <<<EOD
1079
+    public function testSearchPrincipal(): void {
1080
+        $myPublic = <<<EOD
1081 1081
 BEGIN:VCALENDAR
1082 1082
 VERSION:2.0
1083 1083
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1113,7 +1113,7 @@  discard block
 block discarded – undo
1113 1113
 END:VEVENT
1114 1114
 END:VCALENDAR
1115 1115
 EOD;
1116
-		$myPrivate = <<<EOD
1116
+        $myPrivate = <<<EOD
1117 1117
 BEGIN:VCALENDAR
1118 1118
 VERSION:2.0
1119 1119
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1149,7 +1149,7 @@  discard block
 block discarded – undo
1149 1149
 END:VEVENT
1150 1150
 END:VCALENDAR
1151 1151
 EOD;
1152
-		$myConfidential = <<<EOD
1152
+        $myConfidential = <<<EOD
1153 1153
 BEGIN:VCALENDAR
1154 1154
 VERSION:2.0
1155 1155
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1186,7 +1186,7 @@  discard block
 block discarded – undo
1186 1186
 END:VCALENDAR
1187 1187
 EOD;
1188 1188
 
1189
-		$sharerPublic = <<<EOD
1189
+        $sharerPublic = <<<EOD
1190 1190
 BEGIN:VCALENDAR
1191 1191
 VERSION:2.0
1192 1192
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1222,7 +1222,7 @@  discard block
 block discarded – undo
1222 1222
 END:VEVENT
1223 1223
 END:VCALENDAR
1224 1224
 EOD;
1225
-		$sharerPrivate = <<<EOD
1225
+        $sharerPrivate = <<<EOD
1226 1226
 BEGIN:VCALENDAR
1227 1227
 VERSION:2.0
1228 1228
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1258,7 +1258,7 @@  discard block
 block discarded – undo
1258 1258
 END:VEVENT
1259 1259
 END:VCALENDAR
1260 1260
 EOD;
1261
-		$sharerConfidential = <<<EOD
1261
+        $sharerConfidential = <<<EOD
1262 1262
 BEGIN:VCALENDAR
1263 1263
 VERSION:2.0
1264 1264
 PRODID:-//dmfs.org//mimedir.icalendar//EN
@@ -1295,81 +1295,81 @@  discard block
 block discarded – undo
1295 1295
 END:VCALENDAR
1296 1296
 EOD;
1297 1297
 
1298
-		$l10n = $this->createMock(IL10N::class);
1299
-		$l10n
1300
-			->expects($this->any())
1301
-			->method('t')
1302
-			->willReturnCallback(function ($text, $parameters = []) {
1303
-				return vsprintf($text, $parameters);
1304
-			});
1305
-		$config = $this->createMock(IConfig::class);
1306
-		$this->userManager->expects($this->any())
1307
-			->method('userExists')
1308
-			->willReturn(true);
1309
-		$this->groupManager->expects($this->any())
1310
-			->method('groupExists')
1311
-			->willReturn(true);
1312
-		$this->principal->expects(self::atLeastOnce())
1313
-			->method('findByUri')
1314
-			->willReturn(self::UNIT_TEST_USER);
1315
-
1316
-		$me = self::UNIT_TEST_USER;
1317
-		$sharer = self::UNIT_TEST_USER1;
1318
-		$this->backend->createCalendar($me, 'calendar-uri-me', []);
1319
-		$this->backend->createCalendar($sharer, 'calendar-uri-sharer', []);
1320
-
1321
-		$myCalendars = $this->backend->getCalendarsForUser($me);
1322
-		$this->assertCount(1, $myCalendars);
1323
-
1324
-		$sharerCalendars = $this->backend->getCalendarsForUser($sharer);
1325
-		$this->assertCount(1, $sharerCalendars);
1326
-
1327
-		$logger = $this->createMock(\Psr\Log\LoggerInterface::class);
1328
-		$sharerCalendar = new Calendar($this->backend, $sharerCalendars[0], $l10n, $config, $logger);
1329
-		$this->backend->updateShares($sharerCalendar, [
1330
-			[
1331
-				'href' => 'principal:' . $me,
1332
-				'readOnly' => false,
1333
-			],
1334
-		], []);
1335
-
1336
-		$this->assertCount(2, $this->backend->getCalendarsForUser($me));
1337
-
1338
-		$this->backend->createCalendarObject($myCalendars[0]['id'], 'event0.ics', $myPublic);
1339
-		$this->backend->createCalendarObject($myCalendars[0]['id'], 'event1.ics', $myPrivate);
1340
-		$this->backend->createCalendarObject($myCalendars[0]['id'], 'event2.ics', $myConfidential);
1341
-
1342
-		$this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event3.ics', $sharerPublic);
1343
-		$this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event4.ics', $sharerPrivate);
1344
-		$this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event5.ics', $sharerConfidential);
1345
-
1346
-		$mySearchResults = $this->backend->searchPrincipalUri($me, 'Test', ['VEVENT'], ['SUMMARY'], []);
1347
-		$sharerSearchResults = $this->backend->searchPrincipalUri($sharer, 'Test', ['VEVENT'], ['SUMMARY'], []);
1348
-
1349
-		$this->assertCount(4, $mySearchResults);
1350
-		$this->assertCount(3, $sharerSearchResults);
1351
-
1352
-		$this->assertEquals($myPublic, $mySearchResults[0]['calendardata']);
1353
-		$this->assertEquals($myPrivate, $mySearchResults[1]['calendardata']);
1354
-		$this->assertEquals($myConfidential, $mySearchResults[2]['calendardata']);
1355
-		$this->assertEquals($sharerPublic, $mySearchResults[3]['calendardata']);
1356
-
1357
-		$this->assertEquals($sharerPublic, $sharerSearchResults[0]['calendardata']);
1358
-		$this->assertEquals($sharerPrivate, $sharerSearchResults[1]['calendardata']);
1359
-		$this->assertEquals($sharerConfidential, $sharerSearchResults[2]['calendardata']);
1360
-	}
1361
-
1362
-	/**
1363
-	 * @throws \OCP\DB\Exception
1364
-	 * @throws \Sabre\DAV\Exception\BadRequest
1365
-	 */
1366
-	public function testPruneOutdatedSyncTokens(): void {
1367
-		$calendarId = $this->createTestCalendar();
1368
-		$changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
1369
-		$syncToken = $changes['syncToken'];
1370
-
1371
-		$uri = static::getUniqueID('calobj');
1372
-		$calData = <<<EOD
1298
+        $l10n = $this->createMock(IL10N::class);
1299
+        $l10n
1300
+            ->expects($this->any())
1301
+            ->method('t')
1302
+            ->willReturnCallback(function ($text, $parameters = []) {
1303
+                return vsprintf($text, $parameters);
1304
+            });
1305
+        $config = $this->createMock(IConfig::class);
1306
+        $this->userManager->expects($this->any())
1307
+            ->method('userExists')
1308
+            ->willReturn(true);
1309
+        $this->groupManager->expects($this->any())
1310
+            ->method('groupExists')
1311
+            ->willReturn(true);
1312
+        $this->principal->expects(self::atLeastOnce())
1313
+            ->method('findByUri')
1314
+            ->willReturn(self::UNIT_TEST_USER);
1315
+
1316
+        $me = self::UNIT_TEST_USER;
1317
+        $sharer = self::UNIT_TEST_USER1;
1318
+        $this->backend->createCalendar($me, 'calendar-uri-me', []);
1319
+        $this->backend->createCalendar($sharer, 'calendar-uri-sharer', []);
1320
+
1321
+        $myCalendars = $this->backend->getCalendarsForUser($me);
1322
+        $this->assertCount(1, $myCalendars);
1323
+
1324
+        $sharerCalendars = $this->backend->getCalendarsForUser($sharer);
1325
+        $this->assertCount(1, $sharerCalendars);
1326
+
1327
+        $logger = $this->createMock(\Psr\Log\LoggerInterface::class);
1328
+        $sharerCalendar = new Calendar($this->backend, $sharerCalendars[0], $l10n, $config, $logger);
1329
+        $this->backend->updateShares($sharerCalendar, [
1330
+            [
1331
+                'href' => 'principal:' . $me,
1332
+                'readOnly' => false,
1333
+            ],
1334
+        ], []);
1335
+
1336
+        $this->assertCount(2, $this->backend->getCalendarsForUser($me));
1337
+
1338
+        $this->backend->createCalendarObject($myCalendars[0]['id'], 'event0.ics', $myPublic);
1339
+        $this->backend->createCalendarObject($myCalendars[0]['id'], 'event1.ics', $myPrivate);
1340
+        $this->backend->createCalendarObject($myCalendars[0]['id'], 'event2.ics', $myConfidential);
1341
+
1342
+        $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event3.ics', $sharerPublic);
1343
+        $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event4.ics', $sharerPrivate);
1344
+        $this->backend->createCalendarObject($sharerCalendars[0]['id'], 'event5.ics', $sharerConfidential);
1345
+
1346
+        $mySearchResults = $this->backend->searchPrincipalUri($me, 'Test', ['VEVENT'], ['SUMMARY'], []);
1347
+        $sharerSearchResults = $this->backend->searchPrincipalUri($sharer, 'Test', ['VEVENT'], ['SUMMARY'], []);
1348
+
1349
+        $this->assertCount(4, $mySearchResults);
1350
+        $this->assertCount(3, $sharerSearchResults);
1351
+
1352
+        $this->assertEquals($myPublic, $mySearchResults[0]['calendardata']);
1353
+        $this->assertEquals($myPrivate, $mySearchResults[1]['calendardata']);
1354
+        $this->assertEquals($myConfidential, $mySearchResults[2]['calendardata']);
1355
+        $this->assertEquals($sharerPublic, $mySearchResults[3]['calendardata']);
1356
+
1357
+        $this->assertEquals($sharerPublic, $sharerSearchResults[0]['calendardata']);
1358
+        $this->assertEquals($sharerPrivate, $sharerSearchResults[1]['calendardata']);
1359
+        $this->assertEquals($sharerConfidential, $sharerSearchResults[2]['calendardata']);
1360
+    }
1361
+
1362
+    /**
1363
+     * @throws \OCP\DB\Exception
1364
+     * @throws \Sabre\DAV\Exception\BadRequest
1365
+     */
1366
+    public function testPruneOutdatedSyncTokens(): void {
1367
+        $calendarId = $this->createTestCalendar();
1368
+        $changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
1369
+        $syncToken = $changes['syncToken'];
1370
+
1371
+        $uri = static::getUniqueID('calobj');
1372
+        $calData = <<<EOD
1373 1373
 BEGIN:VCALENDAR
1374 1374
 VERSION:2.0
1375 1375
 PRODID:Nextcloud Calendar
@@ -1386,10 +1386,10 @@  discard block
 block discarded – undo
1386 1386
 END:VCALENDAR
1387 1387
 EOD;
1388 1388
 
1389
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
1389
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
1390 1390
 
1391
-		// update the card
1392
-		$calData = <<<'EOD'
1391
+        // update the card
1392
+        $calData = <<<'EOD'
1393 1393
 BEGIN:VCALENDAR
1394 1394
 VERSION:2.0
1395 1395
 PRODID:Nextcloud Calendar
@@ -1405,29 +1405,29 @@  discard block
 block discarded – undo
1405 1405
 END:VEVENT
1406 1406
 END:VCALENDAR
1407 1407
 EOD;
1408
-		$this->backend->updateCalendarObject($calendarId, $uri, $calData);
1408
+        $this->backend->updateCalendarObject($calendarId, $uri, $calData);
1409 1409
 
1410
-		// Keep everything
1411
-		$deleted = $this->backend->pruneOutdatedSyncTokens(0, 0);
1412
-		self::assertSame(0, $deleted);
1410
+        // Keep everything
1411
+        $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0);
1412
+        self::assertSame(0, $deleted);
1413 1413
 
1414
-		$deleted = $this->backend->pruneOutdatedSyncTokens(0, time());
1415
-		// At least one from the object creation and one from the object update
1416
-		$this->assertGreaterThanOrEqual(2, $deleted);
1417
-		$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
1418
-		$this->assertEmpty($changes['added']);
1419
-		$this->assertEmpty($changes['modified']);
1420
-		$this->assertEmpty($changes['deleted']);
1414
+        $deleted = $this->backend->pruneOutdatedSyncTokens(0, time());
1415
+        // At least one from the object creation and one from the object update
1416
+        $this->assertGreaterThanOrEqual(2, $deleted);
1417
+        $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
1418
+        $this->assertEmpty($changes['added']);
1419
+        $this->assertEmpty($changes['modified']);
1420
+        $this->assertEmpty($changes['deleted']);
1421 1421
 
1422
-		// Test that objects remain
1422
+        // Test that objects remain
1423 1423
 
1424
-		// Currently changes are empty
1425
-		$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1426
-		$this->assertEquals(0, count($changes['added'] + $changes['modified'] + $changes['deleted']));
1424
+        // Currently changes are empty
1425
+        $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1426
+        $this->assertEquals(0, count($changes['added'] + $changes['modified'] + $changes['deleted']));
1427 1427
 
1428
-		// Create card
1429
-		$uri = static::getUniqueID('calobj');
1430
-		$calData = <<<EOD
1428
+        // Create card
1429
+        $uri = static::getUniqueID('calobj');
1430
+        $calData = <<<EOD
1431 1431
 BEGIN:VCALENDAR
1432 1432
 VERSION:2.0
1433 1433
 PRODID:Nextcloud Calendar
@@ -1443,16 +1443,16 @@  discard block
 block discarded – undo
1443 1443
 END:VEVENT
1444 1444
 END:VCALENDAR
1445 1445
 EOD;
1446
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
1446
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
1447 1447
 
1448
-		// We now have one add
1449
-		$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1450
-		$this->assertEquals(1, count($changes['added']));
1451
-		$this->assertEmpty($changes['modified']);
1452
-		$this->assertEmpty($changes['deleted']);
1448
+        // We now have one add
1449
+        $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1450
+        $this->assertEquals(1, count($changes['added']));
1451
+        $this->assertEmpty($changes['modified']);
1452
+        $this->assertEmpty($changes['deleted']);
1453 1453
 
1454
-		// update the card
1455
-		$calData = <<<'EOD'
1454
+        // update the card
1455
+        $calData = <<<'EOD'
1456 1456
 BEGIN:VCALENDAR
1457 1457
 VERSION:2.0
1458 1458
 PRODID:Nextcloud Calendar
@@ -1468,38 +1468,38 @@  discard block
 block discarded – undo
1468 1468
 END:VEVENT
1469 1469
 END:VCALENDAR
1470 1470
 EOD;
1471
-		$this->backend->updateCalendarObject($calendarId, $uri, $calData);
1472
-
1473
-		// One add, one modify, but shortened to modify
1474
-		$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1475
-		$this->assertEmpty($changes['added']);
1476
-		$this->assertEquals(1, count($changes['modified']));
1477
-		$this->assertEmpty($changes['deleted']);
1478
-
1479
-		// Delete all but last change
1480
-		$deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
1481
-		$this->assertEquals(1, $deleted); // We had two changes before, now one
1482
-
1483
-		// Only update should remain
1484
-		$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1485
-		$this->assertEmpty($changes['added']);
1486
-		$this->assertEquals(1, count($changes['modified']));
1487
-		$this->assertEmpty($changes['deleted']);
1488
-
1489
-		// Check that no crash occurs when prune is called without current changes
1490
-		$deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
1491
-		self::assertSame(0, $deleted);
1492
-	}
1493
-
1494
-	public function testSearchAndExpandRecurrences(): void {
1495
-		$calendarId = $this->createTestCalendar();
1496
-		$calendarInfo = [
1497
-			'id' => $calendarId,
1498
-			'principaluri' => 'user1',
1499
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
1500
-		];
1501
-
1502
-		$calData = <<<'EOD'
1471
+        $this->backend->updateCalendarObject($calendarId, $uri, $calData);
1472
+
1473
+        // One add, one modify, but shortened to modify
1474
+        $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1475
+        $this->assertEmpty($changes['added']);
1476
+        $this->assertEquals(1, count($changes['modified']));
1477
+        $this->assertEmpty($changes['deleted']);
1478
+
1479
+        // Delete all but last change
1480
+        $deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
1481
+        $this->assertEquals(1, $deleted); // We had two changes before, now one
1482
+
1483
+        // Only update should remain
1484
+        $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 100);
1485
+        $this->assertEmpty($changes['added']);
1486
+        $this->assertEquals(1, count($changes['modified']));
1487
+        $this->assertEmpty($changes['deleted']);
1488
+
1489
+        // Check that no crash occurs when prune is called without current changes
1490
+        $deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
1491
+        self::assertSame(0, $deleted);
1492
+    }
1493
+
1494
+    public function testSearchAndExpandRecurrences(): void {
1495
+        $calendarId = $this->createTestCalendar();
1496
+        $calendarInfo = [
1497
+            'id' => $calendarId,
1498
+            'principaluri' => 'user1',
1499
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
1500
+        ];
1501
+
1502
+        $calData = <<<'EOD'
1503 1503
 BEGIN:VCALENDAR
1504 1504
 PRODID:-//IDN nextcloud.com//Calendar app 4.5.0-alpha.2//EN
1505 1505
 CALSCALE:GREGORIAN
@@ -1518,50 +1518,50 @@  discard block
 block discarded – undo
1518 1518
 END:VEVENT
1519 1519
 END:VCALENDAR
1520 1520
 EOD;
1521
-		$uri = static::getUniqueID('calobj');
1522
-		$this->backend->createCalendarObject($calendarId, $uri, $calData);
1523
-
1524
-		$start = new DateTimeImmutable('2023-09-20T00:00:00Z');
1525
-		$end = $start->add(new DateInterval('P14D'));
1526
-
1527
-		$results = $this->backend->search(
1528
-			$calendarInfo,
1529
-			'',
1530
-			[],
1531
-			[
1532
-				'timerange' => [
1533
-					'start' => $start,
1534
-					'end' => $end,
1535
-				]
1536
-			],
1537
-			null,
1538
-			null,
1539
-		);
1540
-
1541
-		$this->assertCount(1, $results);
1542
-		$this->assertCount(14, $results[0]['objects']);
1543
-		foreach ($results as $result) {
1544
-			foreach ($result['objects'] as $object) {
1545
-				$this->assertEquals($object['UID'][0], '7b7d5d12-683c-48ce-973a-b3e1cb0bae2a');
1546
-				$this->assertEquals($object['SUMMARY'][0], 'Daily Event');
1547
-				$this->assertGreaterThanOrEqual(
1548
-					$start->getTimestamp(),
1549
-					$object['DTSTART'][0]->getTimestamp(),
1550
-					'Recurrence starting before requested start',
1551
-				);
1552
-				$this->assertLessThanOrEqual(
1553
-					$end->getTimestamp(),
1554
-					$object['DTSTART'][0]->getTimestamp(),
1555
-					'Recurrence starting after requested end',
1556
-				);
1557
-			}
1558
-		}
1559
-	}
1560
-
1561
-	public function testRestoreChanges(): void {
1562
-		$calendarId = $this->createTestCalendar();
1563
-		$uri1 = static::getUniqueID('calobj1') . '.ics';
1564
-		$calData = <<<EOD
1521
+        $uri = static::getUniqueID('calobj');
1522
+        $this->backend->createCalendarObject($calendarId, $uri, $calData);
1523
+
1524
+        $start = new DateTimeImmutable('2023-09-20T00:00:00Z');
1525
+        $end = $start->add(new DateInterval('P14D'));
1526
+
1527
+        $results = $this->backend->search(
1528
+            $calendarInfo,
1529
+            '',
1530
+            [],
1531
+            [
1532
+                'timerange' => [
1533
+                    'start' => $start,
1534
+                    'end' => $end,
1535
+                ]
1536
+            ],
1537
+            null,
1538
+            null,
1539
+        );
1540
+
1541
+        $this->assertCount(1, $results);
1542
+        $this->assertCount(14, $results[0]['objects']);
1543
+        foreach ($results as $result) {
1544
+            foreach ($result['objects'] as $object) {
1545
+                $this->assertEquals($object['UID'][0], '7b7d5d12-683c-48ce-973a-b3e1cb0bae2a');
1546
+                $this->assertEquals($object['SUMMARY'][0], 'Daily Event');
1547
+                $this->assertGreaterThanOrEqual(
1548
+                    $start->getTimestamp(),
1549
+                    $object['DTSTART'][0]->getTimestamp(),
1550
+                    'Recurrence starting before requested start',
1551
+                );
1552
+                $this->assertLessThanOrEqual(
1553
+                    $end->getTimestamp(),
1554
+                    $object['DTSTART'][0]->getTimestamp(),
1555
+                    'Recurrence starting after requested end',
1556
+                );
1557
+            }
1558
+        }
1559
+    }
1560
+
1561
+    public function testRestoreChanges(): void {
1562
+        $calendarId = $this->createTestCalendar();
1563
+        $uri1 = static::getUniqueID('calobj1') . '.ics';
1564
+        $calData = <<<EOD
1565 1565
 BEGIN:VCALENDAR
1566 1566
 VERSION:2.0
1567 1567
 PRODID:Nextcloud Calendar
@@ -1577,8 +1577,8 @@  discard block
 block discarded – undo
1577 1577
 END:VEVENT
1578 1578
 END:VCALENDAR
1579 1579
 EOD;
1580
-		$this->backend->createCalendarObject($calendarId, $uri1, $calData);
1581
-		$calData = <<<EOD
1580
+        $this->backend->createCalendarObject($calendarId, $uri1, $calData);
1581
+        $calData = <<<EOD
1582 1582
 BEGIN:VCALENDAR
1583 1583
 VERSION:2.0
1584 1584
 PRODID:Nextcloud Calendar
@@ -1595,9 +1595,9 @@  discard block
 block discarded – undo
1595 1595
 END:VEVENT
1596 1596
 END:VCALENDAR
1597 1597
 EOD;
1598
-		$this->backend->updateCalendarObject($calendarId, $uri1, $calData);
1599
-		$uri2 = static::getUniqueID('calobj2') . '.ics';
1600
-		$calData = <<<EOD
1598
+        $this->backend->updateCalendarObject($calendarId, $uri1, $calData);
1599
+        $uri2 = static::getUniqueID('calobj2') . '.ics';
1600
+        $calData = <<<EOD
1601 1601
 BEGIN:VCALENDAR
1602 1602
 VERSION:2.0
1603 1603
 PRODID:Nextcloud Calendar
@@ -1613,11 +1613,11 @@  discard block
 block discarded – undo
1613 1613
 END:VEVENT
1614 1614
 END:VCALENDAR
1615 1615
 EOD;
1616
-		$this->backend->createCalendarObject($calendarId, $uri2, $calData);
1617
-		$changesBefore = $this->backend->getChangesForCalendar($calendarId, null, 1);
1618
-		$this->backend->deleteCalendarObject($calendarId, $uri2);
1619
-		$uri3 = static::getUniqueID('calobj3') . '.ics';
1620
-		$calData = <<<EOD
1616
+        $this->backend->createCalendarObject($calendarId, $uri2, $calData);
1617
+        $changesBefore = $this->backend->getChangesForCalendar($calendarId, null, 1);
1618
+        $this->backend->deleteCalendarObject($calendarId, $uri2);
1619
+        $uri3 = static::getUniqueID('calobj3') . '.ics';
1620
+        $calData = <<<EOD
1621 1621
 BEGIN:VCALENDAR
1622 1622
 VERSION:2.0
1623 1623
 PRODID:Nextcloud Calendar
@@ -1633,261 +1633,261 @@  discard block
 block discarded – undo
1633 1633
 END:VEVENT
1634 1634
 END:VCALENDAR
1635 1635
 EOD;
1636
-		$this->backend->createCalendarObject($calendarId, $uri3, $calData);
1637
-		$deleteChanges = $this->db->getQueryBuilder();
1638
-		$deleteChanges->delete('calendarchanges')
1639
-			->where($deleteChanges->expr()->eq('calendarid', $deleteChanges->createNamedParameter($calendarId)));
1640
-		$deleteChanges->executeStatement();
1641
-
1642
-		$this->backend->restoreChanges($calendarId);
1643
-
1644
-		$changesAfter = $this->backend->getChangesForCalendar($calendarId, $changesBefore['syncToken'], 1);
1645
-		self::assertEquals([], $changesAfter['added']);
1646
-		self::assertEqualsCanonicalizing([$uri1, $uri3], $changesAfter['modified']);
1647
-		self::assertEquals([$uri2], $changesAfter['deleted']);
1648
-	}
1649
-
1650
-	public function testSearchWithLimitAndTimeRange(): void {
1651
-		$calendarId = $this->createTestCalendar();
1652
-		$calendarInfo = [
1653
-			'id' => $calendarId,
1654
-			'principaluri' => 'user1',
1655
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
1656
-		];
1657
-
1658
-		$testFiles = [
1659
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1660
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1661
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1662
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1663
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1664
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics',
1665
-		];
1666
-
1667
-		foreach ($testFiles as $testFile) {
1668
-			$objectUri = static::getUniqueID('search-limit-timerange-');
1669
-			$calendarData = \file_get_contents($testFile);
1670
-			$this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1671
-		}
1672
-
1673
-		$start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1674
-		$end = $start->add(new DateInterval('P14D'));
1675
-
1676
-		$results = $this->backend->search(
1677
-			$calendarInfo,
1678
-			'',
1679
-			[],
1680
-			[
1681
-				'timerange' => [
1682
-					'start' => $start,
1683
-					'end' => $end,
1684
-				]
1685
-			],
1686
-			4,
1687
-			null,
1688
-		);
1689
-
1690
-		$this->assertCount(2, $results);
1691
-
1692
-		$this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1693
-		$this->assertGreaterThanOrEqual(
1694
-			$start->getTimestamp(),
1695
-			$results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1696
-			'Recurrence starting before requested start',
1697
-		);
1698
-
1699
-		$this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1700
-		$this->assertGreaterThanOrEqual(
1701
-			$start->getTimestamp(),
1702
-			$results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
1703
-			'Recurrence starting before requested start',
1704
-		);
1705
-	}
1706
-
1707
-	public function testSearchWithLimitAndTimeRangeShouldNotReturnMoreObjectsThenLimit(): void {
1708
-		$calendarId = $this->createTestCalendar();
1709
-		$calendarInfo = [
1710
-			'id' => $calendarId,
1711
-			'principaluri' => 'user1',
1712
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
1713
-		];
1714
-
1715
-		$testFiles = [
1716
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1717
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1718
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1719
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1720
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1721
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics',
1722
-		];
1723
-
1724
-		foreach ($testFiles as $testFile) {
1725
-			$objectUri = static::getUniqueID('search-limit-timerange-');
1726
-			$calendarData = \file_get_contents($testFile);
1727
-			$this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1728
-		}
1729
-
1730
-		$start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1731
-		$end = $start->add(new DateInterval('P14D'));
1732
-
1733
-		$results = $this->backend->search(
1734
-			$calendarInfo,
1735
-			'',
1736
-			[],
1737
-			[
1738
-				'timerange' => [
1739
-					'start' => $start,
1740
-					'end' => $end,
1741
-				]
1742
-			],
1743
-			1,
1744
-			null,
1745
-		);
1746
-
1747
-		$this->assertCount(1, $results);
1748
-
1749
-		$this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1750
-		$this->assertGreaterThanOrEqual(
1751
-			$start->getTimestamp(),
1752
-			$results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1753
-			'Recurrence starting before requested start',
1754
-		);
1755
-	}
1756
-
1757
-	public function testSearchWithLimitAndTimeRangeShouldReturnObjectsInTheSameOrder(): void {
1758
-		$calendarId = $this->createTestCalendar();
1759
-		$calendarInfo = [
1760
-			'id' => $calendarId,
1761
-			'principaluri' => 'user1',
1762
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
1763
-		];
1764
-
1765
-		$testFiles = [
1766
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1767
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1768
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1769
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1770
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional!
1771
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1772
-		];
1773
-
1774
-		foreach ($testFiles as $testFile) {
1775
-			$objectUri = static::getUniqueID('search-limit-timerange-');
1776
-			$calendarData = \file_get_contents($testFile);
1777
-			$this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1778
-		}
1779
-
1780
-		$start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1781
-		$end = $start->add(new DateInterval('P14D'));
1782
-
1783
-		$results = $this->backend->search(
1784
-			$calendarInfo,
1785
-			'',
1786
-			[],
1787
-			[
1788
-				'timerange' => [
1789
-					'start' => $start,
1790
-					'end' => $end,
1791
-				]
1792
-			],
1793
-			2,
1794
-			null,
1795
-		);
1796
-
1797
-		$this->assertCount(2, $results);
1798
-
1799
-		$this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1800
-		$this->assertGreaterThanOrEqual(
1801
-			$start->getTimestamp(),
1802
-			$results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1803
-			'Recurrence starting before requested start',
1804
-		);
1805
-
1806
-		$this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1807
-		$this->assertGreaterThanOrEqual(
1808
-			$start->getTimestamp(),
1809
-			$results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
1810
-			'Recurrence starting before requested start',
1811
-		);
1812
-	}
1813
-
1814
-	public function testSearchShouldReturnObjectsInTheSameOrderMissingDate(): void {
1815
-		$calendarId = $this->createTestCalendar();
1816
-		$calendarInfo = [
1817
-			'id' => $calendarId,
1818
-			'principaluri' => 'user1',
1819
-			'{http://owncloud.org/ns}owner-principal' => 'user1',
1820
-		];
1821
-
1822
-		$testFiles = [
1823
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional!
1824
-			__DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1825
-			__DIR__ . '/../test_fixtures/caldav-search-missing-start-1.ics',
1826
-			__DIR__ . '/../test_fixtures/caldav-search-missing-start-2.ics',
1827
-		];
1828
-
1829
-		foreach ($testFiles as $testFile) {
1830
-			$objectUri = static::getUniqueID('search-return-objects-in-same-order-');
1831
-			$calendarData = \file_get_contents($testFile);
1832
-			$this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1833
-		}
1834
-
1835
-		$results = $this->backend->search(
1836
-			$calendarInfo,
1837
-			'',
1838
-			[],
1839
-			[],
1840
-			4,
1841
-			null,
1842
-		);
1843
-
1844
-		$this->assertCount(4, $results);
1845
-
1846
-		$this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1847
-		$this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1848
-		$this->assertEquals('Missing DTSTART 1', $results[2]['objects'][0]['SUMMARY'][0]);
1849
-		$this->assertEquals('Missing DTSTART 2', $results[3]['objects'][0]['SUMMARY'][0]);
1850
-	}
1851
-
1852
-	public function testUnshare(): void {
1853
-		$principalGroup = 'principal:' . self::UNIT_TEST_GROUP;
1854
-		$principalUser = 'principal:' . self::UNIT_TEST_USER;
1855
-
1856
-		$l10n = $this->createMock(IL10N::class);
1857
-		$l10n->method('t')
1858
-			->willReturnCallback(fn ($text, $parameters = []) => vsprintf($text, $parameters));
1859
-		$config = $this->createMock(IConfig::class);
1860
-		$logger = new NullLogger();
1861
-
1862
-		$this->principal->expects($this->exactly(2))
1863
-			->method('findByUri')
1864
-			->willReturnMap([
1865
-				[$principalGroup, '', self::UNIT_TEST_GROUP],
1866
-				[$principalUser, '', self::UNIT_TEST_USER],
1867
-			]);
1868
-		$this->groupManager->expects($this->once())
1869
-			->method('groupExists')
1870
-			->willReturn(true);
1871
-		$this->dispatcher->expects($this->exactly(2))
1872
-			->method('dispatchTyped');
1873
-
1874
-		$calendarId = $this->createTestCalendar();
1875
-		$calendarInfo = $this->backend->getCalendarById($calendarId);
1876
-
1877
-		$calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger);
1878
-
1879
-		$this->backend->updateShares(
1880
-			shareable: $calendar,
1881
-			add: [
1882
-				['href' => $principalGroup, 'readOnly' => false]
1883
-			],
1884
-			remove: []
1885
-		);
1886
-
1887
-		$this->backend->unshare(
1888
-			shareable: $calendar,
1889
-			principal: $principalUser
1890
-		);
1891
-
1892
-	}
1636
+        $this->backend->createCalendarObject($calendarId, $uri3, $calData);
1637
+        $deleteChanges = $this->db->getQueryBuilder();
1638
+        $deleteChanges->delete('calendarchanges')
1639
+            ->where($deleteChanges->expr()->eq('calendarid', $deleteChanges->createNamedParameter($calendarId)));
1640
+        $deleteChanges->executeStatement();
1641
+
1642
+        $this->backend->restoreChanges($calendarId);
1643
+
1644
+        $changesAfter = $this->backend->getChangesForCalendar($calendarId, $changesBefore['syncToken'], 1);
1645
+        self::assertEquals([], $changesAfter['added']);
1646
+        self::assertEqualsCanonicalizing([$uri1, $uri3], $changesAfter['modified']);
1647
+        self::assertEquals([$uri2], $changesAfter['deleted']);
1648
+    }
1649
+
1650
+    public function testSearchWithLimitAndTimeRange(): void {
1651
+        $calendarId = $this->createTestCalendar();
1652
+        $calendarInfo = [
1653
+            'id' => $calendarId,
1654
+            'principaluri' => 'user1',
1655
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
1656
+        ];
1657
+
1658
+        $testFiles = [
1659
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1660
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1661
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1662
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1663
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1664
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics',
1665
+        ];
1666
+
1667
+        foreach ($testFiles as $testFile) {
1668
+            $objectUri = static::getUniqueID('search-limit-timerange-');
1669
+            $calendarData = \file_get_contents($testFile);
1670
+            $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1671
+        }
1672
+
1673
+        $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1674
+        $end = $start->add(new DateInterval('P14D'));
1675
+
1676
+        $results = $this->backend->search(
1677
+            $calendarInfo,
1678
+            '',
1679
+            [],
1680
+            [
1681
+                'timerange' => [
1682
+                    'start' => $start,
1683
+                    'end' => $end,
1684
+                ]
1685
+            ],
1686
+            4,
1687
+            null,
1688
+        );
1689
+
1690
+        $this->assertCount(2, $results);
1691
+
1692
+        $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1693
+        $this->assertGreaterThanOrEqual(
1694
+            $start->getTimestamp(),
1695
+            $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1696
+            'Recurrence starting before requested start',
1697
+        );
1698
+
1699
+        $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1700
+        $this->assertGreaterThanOrEqual(
1701
+            $start->getTimestamp(),
1702
+            $results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
1703
+            'Recurrence starting before requested start',
1704
+        );
1705
+    }
1706
+
1707
+    public function testSearchWithLimitAndTimeRangeShouldNotReturnMoreObjectsThenLimit(): void {
1708
+        $calendarId = $this->createTestCalendar();
1709
+        $calendarInfo = [
1710
+            'id' => $calendarId,
1711
+            'principaluri' => 'user1',
1712
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
1713
+        ];
1714
+
1715
+        $testFiles = [
1716
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1717
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1718
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1719
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1720
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1721
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics',
1722
+        ];
1723
+
1724
+        foreach ($testFiles as $testFile) {
1725
+            $objectUri = static::getUniqueID('search-limit-timerange-');
1726
+            $calendarData = \file_get_contents($testFile);
1727
+            $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1728
+        }
1729
+
1730
+        $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1731
+        $end = $start->add(new DateInterval('P14D'));
1732
+
1733
+        $results = $this->backend->search(
1734
+            $calendarInfo,
1735
+            '',
1736
+            [],
1737
+            [
1738
+                'timerange' => [
1739
+                    'start' => $start,
1740
+                    'end' => $end,
1741
+                ]
1742
+            ],
1743
+            1,
1744
+            null,
1745
+        );
1746
+
1747
+        $this->assertCount(1, $results);
1748
+
1749
+        $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1750
+        $this->assertGreaterThanOrEqual(
1751
+            $start->getTimestamp(),
1752
+            $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1753
+            'Recurrence starting before requested start',
1754
+        );
1755
+    }
1756
+
1757
+    public function testSearchWithLimitAndTimeRangeShouldReturnObjectsInTheSameOrder(): void {
1758
+        $calendarId = $this->createTestCalendar();
1759
+        $calendarInfo = [
1760
+            'id' => $calendarId,
1761
+            'principaluri' => 'user1',
1762
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
1763
+        ];
1764
+
1765
+        $testFiles = [
1766
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics',
1767
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics',
1768
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics',
1769
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics',
1770
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional!
1771
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1772
+        ];
1773
+
1774
+        foreach ($testFiles as $testFile) {
1775
+            $objectUri = static::getUniqueID('search-limit-timerange-');
1776
+            $calendarData = \file_get_contents($testFile);
1777
+            $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1778
+        }
1779
+
1780
+        $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
1781
+        $end = $start->add(new DateInterval('P14D'));
1782
+
1783
+        $results = $this->backend->search(
1784
+            $calendarInfo,
1785
+            '',
1786
+            [],
1787
+            [
1788
+                'timerange' => [
1789
+                    'start' => $start,
1790
+                    'end' => $end,
1791
+                ]
1792
+            ],
1793
+            2,
1794
+            null,
1795
+        );
1796
+
1797
+        $this->assertCount(2, $results);
1798
+
1799
+        $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1800
+        $this->assertGreaterThanOrEqual(
1801
+            $start->getTimestamp(),
1802
+            $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
1803
+            'Recurrence starting before requested start',
1804
+        );
1805
+
1806
+        $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1807
+        $this->assertGreaterThanOrEqual(
1808
+            $start->getTimestamp(),
1809
+            $results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
1810
+            'Recurrence starting before requested start',
1811
+        );
1812
+    }
1813
+
1814
+    public function testSearchShouldReturnObjectsInTheSameOrderMissingDate(): void {
1815
+        $calendarId = $this->createTestCalendar();
1816
+        $calendarInfo = [
1817
+            'id' => $calendarId,
1818
+            'principaluri' => 'user1',
1819
+            '{http://owncloud.org/ns}owner-principal' => 'user1',
1820
+        ];
1821
+
1822
+        $testFiles = [
1823
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional!
1824
+            __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics',
1825
+            __DIR__ . '/../test_fixtures/caldav-search-missing-start-1.ics',
1826
+            __DIR__ . '/../test_fixtures/caldav-search-missing-start-2.ics',
1827
+        ];
1828
+
1829
+        foreach ($testFiles as $testFile) {
1830
+            $objectUri = static::getUniqueID('search-return-objects-in-same-order-');
1831
+            $calendarData = \file_get_contents($testFile);
1832
+            $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
1833
+        }
1834
+
1835
+        $results = $this->backend->search(
1836
+            $calendarInfo,
1837
+            '',
1838
+            [],
1839
+            [],
1840
+            4,
1841
+            null,
1842
+        );
1843
+
1844
+        $this->assertCount(4, $results);
1845
+
1846
+        $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
1847
+        $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
1848
+        $this->assertEquals('Missing DTSTART 1', $results[2]['objects'][0]['SUMMARY'][0]);
1849
+        $this->assertEquals('Missing DTSTART 2', $results[3]['objects'][0]['SUMMARY'][0]);
1850
+    }
1851
+
1852
+    public function testUnshare(): void {
1853
+        $principalGroup = 'principal:' . self::UNIT_TEST_GROUP;
1854
+        $principalUser = 'principal:' . self::UNIT_TEST_USER;
1855
+
1856
+        $l10n = $this->createMock(IL10N::class);
1857
+        $l10n->method('t')
1858
+            ->willReturnCallback(fn ($text, $parameters = []) => vsprintf($text, $parameters));
1859
+        $config = $this->createMock(IConfig::class);
1860
+        $logger = new NullLogger();
1861
+
1862
+        $this->principal->expects($this->exactly(2))
1863
+            ->method('findByUri')
1864
+            ->willReturnMap([
1865
+                [$principalGroup, '', self::UNIT_TEST_GROUP],
1866
+                [$principalUser, '', self::UNIT_TEST_USER],
1867
+            ]);
1868
+        $this->groupManager->expects($this->once())
1869
+            ->method('groupExists')
1870
+            ->willReturn(true);
1871
+        $this->dispatcher->expects($this->exactly(2))
1872
+            ->method('dispatchTyped');
1873
+
1874
+        $calendarId = $this->createTestCalendar();
1875
+        $calendarInfo = $this->backend->getCalendarById($calendarId);
1876
+
1877
+        $calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger);
1878
+
1879
+        $this->backend->updateShares(
1880
+            shareable: $calendar,
1881
+            add: [
1882
+                ['href' => $principalGroup, 'readOnly' => false]
1883
+            ],
1884
+            remove: []
1885
+        );
1886
+
1887
+        $this->backend->unshare(
1888
+            shareable: $calendar,
1889
+            principal: $principalUser
1890
+        );
1891
+
1892
+    }
1893 1893
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/TipBrokerTest.php 1 patch
Indentation   +163 added lines, -163 removed lines patch added patch discarded remove patch
@@ -13,168 +13,168 @@
 block discarded – undo
13 13
 
14 14
 class TipBrokerTest extends TestCase {
15 15
 
16
-	private TipBroker $broker;
17
-	private VCalendar $vCalendar1a;
18
-
19
-	protected function setUp(): void {
20
-		parent::setUp();
21
-
22
-		$this->broker = new TipBroker();
23
-		// construct calendar with a 1 hour event and same start/end time zones
24
-		$this->vCalendar1a = new VCalendar();
25
-		/** @var VEvent $vEvent */
26
-		$vEvent = $this->vCalendar1a->add('VEVENT', []);
27
-		$vEvent->add('UID', '96a0e6b1-d886-4a55-a60d-152b31401dcc');
28
-		$vEvent->add('DTSTAMP', '20240701T000000Z');
29
-		$vEvent->add('CREATED', '20240701T000000Z');
30
-		$vEvent->add('LAST-MODIFIED', '20240701T000000Z');
31
-		$vEvent->add('SEQUENCE', '1');
32
-		$vEvent->add('STATUS', 'CONFIRMED');
33
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
34
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
35
-		$vEvent->add('SUMMARY', 'Test Event');
36
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
37
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
38
-			'CN' => 'Attendee One',
39
-			'CUTYPE' => 'INDIVIDUAL',
40
-			'PARTSTAT' => 'NEEDS-ACTION',
41
-			'ROLE' => 'REQ-PARTICIPANT',
42
-			'RSVP' => 'TRUE'
43
-		]);
44
-	}
45
-
46
-	public function testParseEventForOrganizerOnCreate(): void {
47
-
48
-		// construct calendar and generate event info for newly created event with one attendee
49
-		$calendar = clone $this->vCalendar1a;
50
-		$previousEventInfo = [
51
-			'organizer' => null,
52
-			'significantChangeHash' => '',
53
-			'attendees' => [],
54
-		];
55
-		$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
56
-		// test iTip generation
57
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
58
-		$this->assertCount(1, $messages);
59
-		$this->assertEquals('REQUEST', $messages[0]->method);
60
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
61
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
62
-
63
-	}
64
-
65
-	public function testParseEventForOrganizerOnModify(): void {
66
-
67
-		// construct calendar and generate event info for modified event with one attendee
68
-		$calendar = clone $this->vCalendar1a;
69
-		$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
70
-		$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
71
-		$calendar->VEVENT->SEQUENCE->setValue(2);
72
-		$calendar->VEVENT->SUMMARY->setValue('Test Event Modified');
73
-		$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
74
-		// test iTip generation
75
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
76
-		$this->assertCount(1, $messages);
77
-		$this->assertEquals('REQUEST', $messages[0]->method);
78
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
79
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
80
-
81
-	}
82
-
83
-	public function testParseEventForOrganizerOnDelete(): void {
84
-
85
-		// construct calendar and generate event info for modified event with one attendee
86
-		$calendar = clone $this->vCalendar1a;
87
-		$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
88
-		$currentEventInfo = $previousEventInfo;
89
-		$currentEventInfo['attendees'] = [];
90
-		++$currentEventInfo['sequence'];
91
-		// test iTip generation
92
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
93
-		$this->assertCount(1, $messages);
94
-		$this->assertEquals('CANCEL', $messages[0]->method);
95
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
96
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
97
-
98
-	}
99
-
100
-	public function testParseEventForOrganizerOnStatusCancelled(): void {
101
-
102
-		// construct calendar and generate event info for modified event with one attendee
103
-		$calendar = clone $this->vCalendar1a;
104
-		$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
105
-		$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
106
-		$calendar->VEVENT->SEQUENCE->setValue(2);
107
-		$calendar->VEVENT->STATUS->setValue('CANCELLED');
108
-		$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
109
-		// test iTip generation
110
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
111
-		$this->assertCount(1, $messages);
112
-		$this->assertEquals('CANCEL', $messages[0]->method);
113
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
114
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
115
-
116
-	}
117
-
118
-	public function testParseEventForOrganizerOnAddAttendee(): void {
119
-
120
-		// construct calendar and generate event info for modified event with two attendees
121
-		$calendar = clone $this->vCalendar1a;
122
-		$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
123
-		$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
124
-		$calendar->VEVENT->SEQUENCE->setValue(2);
125
-		$calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
126
-			'CN' => 'Attendee Two',
127
-			'CUTYPE' => 'INDIVIDUAL',
128
-			'PARTSTAT' => 'NEEDS-ACTION',
129
-			'ROLE' => 'REQ-PARTICIPANT',
130
-			'RSVP' => 'TRUE'
131
-		]);
132
-		$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
133
-		// test iTip generation
134
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
135
-		$this->assertCount(2, $messages);
136
-		$this->assertEquals('REQUEST', $messages[0]->method);
137
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
138
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
139
-		$this->assertEquals('REQUEST', $messages[1]->method);
140
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
141
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[1]->getValue(), $messages[1]->recipient);
142
-
143
-	}
144
-
145
-	public function testParseEventForOrganizerOnRemoveAttendee(): void {
146
-
147
-		// construct calendar and generate event info for modified event with two attendees
148
-		$calendar = clone $this->vCalendar1a;
149
-		$calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
150
-			'CN' => 'Attendee Two',
151
-			'CUTYPE' => 'INDIVIDUAL',
152
-			'PARTSTAT' => 'NEEDS-ACTION',
153
-			'ROLE' => 'REQ-PARTICIPANT',
154
-			'RSVP' => 'TRUE'
155
-		]);
156
-		$previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
157
-		$calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
158
-		$calendar->VEVENT->SEQUENCE->setValue(2);
159
-		$calendar->VEVENT->remove('ATTENDEE');
160
-		$calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
161
-			'CN' => 'Attendee One',
162
-			'CUTYPE' => 'INDIVIDUAL',
163
-			'PARTSTAT' => 'NEEDS-ACTION',
164
-			'ROLE' => 'REQ-PARTICIPANT',
165
-			'RSVP' => 'TRUE'
166
-		]);
167
-		$currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
168
-		// test iTip generation
169
-		$messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
170
-		$this->assertCount(2, $messages);
171
-		$this->assertEquals('REQUEST', $messages[0]->method);
172
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
173
-		$this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
174
-		$this->assertEquals('CANCEL', $messages[1]->method);
175
-		$this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
176
-		$this->assertEquals('mailto:[email protected]', $messages[1]->recipient);
177
-
178
-	}
16
+    private TipBroker $broker;
17
+    private VCalendar $vCalendar1a;
18
+
19
+    protected function setUp(): void {
20
+        parent::setUp();
21
+
22
+        $this->broker = new TipBroker();
23
+        // construct calendar with a 1 hour event and same start/end time zones
24
+        $this->vCalendar1a = new VCalendar();
25
+        /** @var VEvent $vEvent */
26
+        $vEvent = $this->vCalendar1a->add('VEVENT', []);
27
+        $vEvent->add('UID', '96a0e6b1-d886-4a55-a60d-152b31401dcc');
28
+        $vEvent->add('DTSTAMP', '20240701T000000Z');
29
+        $vEvent->add('CREATED', '20240701T000000Z');
30
+        $vEvent->add('LAST-MODIFIED', '20240701T000000Z');
31
+        $vEvent->add('SEQUENCE', '1');
32
+        $vEvent->add('STATUS', 'CONFIRMED');
33
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
34
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
35
+        $vEvent->add('SUMMARY', 'Test Event');
36
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
37
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
38
+            'CN' => 'Attendee One',
39
+            'CUTYPE' => 'INDIVIDUAL',
40
+            'PARTSTAT' => 'NEEDS-ACTION',
41
+            'ROLE' => 'REQ-PARTICIPANT',
42
+            'RSVP' => 'TRUE'
43
+        ]);
44
+    }
45
+
46
+    public function testParseEventForOrganizerOnCreate(): void {
47
+
48
+        // construct calendar and generate event info for newly created event with one attendee
49
+        $calendar = clone $this->vCalendar1a;
50
+        $previousEventInfo = [
51
+            'organizer' => null,
52
+            'significantChangeHash' => '',
53
+            'attendees' => [],
54
+        ];
55
+        $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
56
+        // test iTip generation
57
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
58
+        $this->assertCount(1, $messages);
59
+        $this->assertEquals('REQUEST', $messages[0]->method);
60
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
61
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
62
+
63
+    }
64
+
65
+    public function testParseEventForOrganizerOnModify(): void {
66
+
67
+        // construct calendar and generate event info for modified event with one attendee
68
+        $calendar = clone $this->vCalendar1a;
69
+        $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
70
+        $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
71
+        $calendar->VEVENT->SEQUENCE->setValue(2);
72
+        $calendar->VEVENT->SUMMARY->setValue('Test Event Modified');
73
+        $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
74
+        // test iTip generation
75
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
76
+        $this->assertCount(1, $messages);
77
+        $this->assertEquals('REQUEST', $messages[0]->method);
78
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
79
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
80
+
81
+    }
82
+
83
+    public function testParseEventForOrganizerOnDelete(): void {
84
+
85
+        // construct calendar and generate event info for modified event with one attendee
86
+        $calendar = clone $this->vCalendar1a;
87
+        $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
88
+        $currentEventInfo = $previousEventInfo;
89
+        $currentEventInfo['attendees'] = [];
90
+        ++$currentEventInfo['sequence'];
91
+        // test iTip generation
92
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
93
+        $this->assertCount(1, $messages);
94
+        $this->assertEquals('CANCEL', $messages[0]->method);
95
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
96
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
97
+
98
+    }
99
+
100
+    public function testParseEventForOrganizerOnStatusCancelled(): void {
101
+
102
+        // construct calendar and generate event info for modified event with one attendee
103
+        $calendar = clone $this->vCalendar1a;
104
+        $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
105
+        $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
106
+        $calendar->VEVENT->SEQUENCE->setValue(2);
107
+        $calendar->VEVENT->STATUS->setValue('CANCELLED');
108
+        $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
109
+        // test iTip generation
110
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
111
+        $this->assertCount(1, $messages);
112
+        $this->assertEquals('CANCEL', $messages[0]->method);
113
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
114
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
115
+
116
+    }
117
+
118
+    public function testParseEventForOrganizerOnAddAttendee(): void {
119
+
120
+        // construct calendar and generate event info for modified event with two attendees
121
+        $calendar = clone $this->vCalendar1a;
122
+        $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
123
+        $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
124
+        $calendar->VEVENT->SEQUENCE->setValue(2);
125
+        $calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
126
+            'CN' => 'Attendee Two',
127
+            'CUTYPE' => 'INDIVIDUAL',
128
+            'PARTSTAT' => 'NEEDS-ACTION',
129
+            'ROLE' => 'REQ-PARTICIPANT',
130
+            'RSVP' => 'TRUE'
131
+        ]);
132
+        $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
133
+        // test iTip generation
134
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
135
+        $this->assertCount(2, $messages);
136
+        $this->assertEquals('REQUEST', $messages[0]->method);
137
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
138
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
139
+        $this->assertEquals('REQUEST', $messages[1]->method);
140
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
141
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[1]->getValue(), $messages[1]->recipient);
142
+
143
+    }
144
+
145
+    public function testParseEventForOrganizerOnRemoveAttendee(): void {
146
+
147
+        // construct calendar and generate event info for modified event with two attendees
148
+        $calendar = clone $this->vCalendar1a;
149
+        $calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
150
+            'CN' => 'Attendee Two',
151
+            'CUTYPE' => 'INDIVIDUAL',
152
+            'PARTSTAT' => 'NEEDS-ACTION',
153
+            'ROLE' => 'REQ-PARTICIPANT',
154
+            'RSVP' => 'TRUE'
155
+        ]);
156
+        $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
157
+        $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z');
158
+        $calendar->VEVENT->SEQUENCE->setValue(2);
159
+        $calendar->VEVENT->remove('ATTENDEE');
160
+        $calendar->VEVENT->add('ATTENDEE', 'mailto:[email protected]', [
161
+            'CN' => 'Attendee One',
162
+            'CUTYPE' => 'INDIVIDUAL',
163
+            'PARTSTAT' => 'NEEDS-ACTION',
164
+            'ROLE' => 'REQ-PARTICIPANT',
165
+            'RSVP' => 'TRUE'
166
+        ]);
167
+        $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]);
168
+        // test iTip generation
169
+        $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]);
170
+        $this->assertCount(2, $messages);
171
+        $this->assertEquals('REQUEST', $messages[0]->method);
172
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender);
173
+        $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient);
174
+        $this->assertEquals('CANCEL', $messages[1]->method);
175
+        $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender);
176
+        $this->assertEquals('mailto:[email protected]', $messages[1]->recipient);
177
+
178
+    }
179 179
 
180 180
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/EventComparisonServiceTest.php 1 patch
Indentation   +169 added lines, -169 removed lines patch added patch discarded remove patch
@@ -14,175 +14,175 @@
 block discarded – undo
14 14
 use Test\TestCase;
15 15
 
16 16
 class EventComparisonServiceTest extends TestCase {
17
-	private EventComparisonService $eventComparisonService;
18
-
19
-	protected function setUp(): void {
20
-		$this->eventComparisonService = new EventComparisonService();
21
-	}
22
-
23
-	public function testNoModifiedEvent(): void {
24
-		$vCalendarOld = new VCalendar();
25
-		$vCalendarNew = new VCalendar();
26
-
27
-		$vEventOld = $vCalendarOld->add('VEVENT', [
28
-			'UID' => 'uid-1234',
29
-			'LAST-MODIFIED' => 123456,
30
-			'SEQUENCE' => 2,
31
-			'SUMMARY' => 'Fellowship meeting',
32
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
33
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
34
-		]);
35
-		$vEventOld->add('ORGANIZER', 'mailto:[email protected]');
36
-		$vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
37
-
38
-		$vEventNew = $vCalendarNew->add('VEVENT', [
39
-			'UID' => 'uid-1234',
40
-			'LAST-MODIFIED' => 123456,
41
-			'SEQUENCE' => 2,
42
-			'SUMMARY' => 'Fellowship meeting',
43
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
44
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
45
-		]);
46
-		$vEventNew->add('ORGANIZER', 'mailto:[email protected]');
47
-		$vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
48
-
49
-		$result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
50
-		$this->assertEmpty($result['old']);
51
-		$this->assertEmpty($result['new']);
52
-	}
53
-
54
-	public function testNewEvent(): void {
55
-		$vCalendarOld = null;
56
-		$vCalendarNew = new VCalendar();
57
-
58
-		$vEventNew = $vCalendarNew->add('VEVENT', [
59
-			'UID' => 'uid-1234',
60
-			'LAST-MODIFIED' => 123456,
61
-			'SEQUENCE' => 2,
62
-			'SUMMARY' => 'Fellowship meeting',
63
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
64
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
65
-		]);
66
-		$vEventNew->add('ORGANIZER', 'mailto:[email protected]');
67
-		$vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
68
-
69
-		$result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
70
-		$this->assertNull($result['old']);
71
-		$this->assertEquals([$vEventNew], $result['new']);
72
-	}
73
-
74
-	public function testModifiedUnmodifiedEvent(): void {
75
-		$vCalendarOld = new VCalendar();
76
-		$vCalendarNew = new VCalendar();
77
-
78
-		$vEventOld1 = $vCalendarOld->add('VEVENT', [
79
-			'UID' => 'uid-1234',
80
-			'LAST-MODIFIED' => 123456,
81
-			'SEQUENCE' => 2,
82
-			'SUMMARY' => 'Fellowship meeting',
83
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
84
-		]);
85
-		$vEventOld1->add('ORGANIZER', 'mailto:[email protected]');
86
-		$vEventOld1->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
87
-
88
-		$vEventOld2 = $vCalendarOld->add('VEVENT', [
89
-			'UID' => 'uid-1235',
90
-			'LAST-MODIFIED' => 123456,
91
-			'SEQUENCE' => 2,
92
-			'SUMMARY' => 'Fellowship meeting',
93
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
94
-		]);
95
-		$vEventOld2->add('ORGANIZER', 'mailto:[email protected]');
96
-		$vEventOld2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
97
-
98
-		$vEventNew1 = $vCalendarNew->add('VEVENT', [
99
-			'UID' => 'uid-1234',
100
-			'LAST-MODIFIED' => 123456,
101
-			'SEQUENCE' => 2,
102
-			'SUMMARY' => 'Fellowship meeting',
103
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
104
-		]);
105
-		$vEventNew1->add('ORGANIZER', 'mailto:[email protected]');
106
-		$vEventNew1->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
107
-
108
-		$vEventNew2 = $vCalendarNew->add('VEVENT', [
109
-			'UID' => 'uid-1235',
110
-			'LAST-MODIFIED' => 123457,
111
-			'SEQUENCE' => 3,
112
-			'SUMMARY' => 'Fellowship meeting 2',
113
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
114
-		]);
115
-		$vEventNew2->add('ORGANIZER', 'mailto:[email protected]');
116
-		$vEventNew2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
117
-
118
-		$result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
119
-		$this->assertEquals([$vEventOld2], $result['old']);
120
-		$this->assertEquals([$vEventNew2], $result['new']);
121
-	}
122
-
123
-	// First test to certify fix for issue nextcloud/server#41084
124
-	public function testSequenceNumberIncrementDetectedForFirstModificationToEventWithoutZeroInit(): void {
125
-		$vCalendarOld = new VCalendar();
126
-		$vCalendarNew = new VCalendar();
127
-
128
-		$vEventOld = $vCalendarOld->add('VEVENT', [
129
-			'UID' => 'uid-1234',
130
-			'LAST-MODIFIED' => 123456,
131
-			// 'SEQUENCE' => 0,			// sequence number may not be set to zero during event creation and instead fully omitted
132
-			'SUMMARY' => 'Fellowship meeting',
133
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
134
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
135
-		]);
136
-		$vEventOld->add('ORGANIZER', 'mailto:[email protected]');
137
-		$vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
138
-
139
-		$vEventNew = $vCalendarNew->add('VEVENT', [
140
-			'UID' => 'uid-1234',
141
-			'LAST-MODIFIED' => 123456,
142
-			'SEQUENCE' => 1,
143
-			'SUMMARY' => 'Fellowship meeting',
144
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
145
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
146
-		]);
147
-		$vEventNew->add('ORGANIZER', 'mailto:[email protected]');
148
-		$vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
149
-
150
-		$result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
151
-		$this->assertEquals([$vEventOld], $result['old']);
152
-		$this->assertEquals([$vEventNew], $result['new']);
153
-	}
154
-
155
-	// Second test to certify fix for issue nextcloud/server#41084
156
-	public function testSequenceNumberIncrementDetectedForFirstModificationToEventWithZeroInit(): void {
157
-		$vCalendarOld = new VCalendar();
158
-		$vCalendarNew = new VCalendar();
159
-
160
-		$vEventOld = $vCalendarOld->add('VEVENT', [
161
-			'UID' => 'uid-1234',
162
-			'LAST-MODIFIED' => 123456,
163
-			'SEQUENCE' => 0,
164
-			'SUMMARY' => 'Fellowship meeting',
165
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
166
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
167
-		]);
168
-		$vEventOld->add('ORGANIZER', 'mailto:[email protected]');
169
-		$vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
170
-
171
-		$vEventNew = $vCalendarNew->add('VEVENT', [
172
-			'UID' => 'uid-1234',
173
-			'LAST-MODIFIED' => 123456,
174
-			'SEQUENCE' => 1,
175
-			'SUMMARY' => 'Fellowship meeting',
176
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
177
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
178
-		]);
179
-		$vEventNew->add('ORGANIZER', 'mailto:[email protected]');
180
-		$vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
181
-
182
-		$result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
183
-		$this->assertEquals([$vEventOld], $result['old']);
184
-		$this->assertEquals([$vEventNew], $result['new']);
185
-	}
17
+    private EventComparisonService $eventComparisonService;
18
+
19
+    protected function setUp(): void {
20
+        $this->eventComparisonService = new EventComparisonService();
21
+    }
22
+
23
+    public function testNoModifiedEvent(): void {
24
+        $vCalendarOld = new VCalendar();
25
+        $vCalendarNew = new VCalendar();
26
+
27
+        $vEventOld = $vCalendarOld->add('VEVENT', [
28
+            'UID' => 'uid-1234',
29
+            'LAST-MODIFIED' => 123456,
30
+            'SEQUENCE' => 2,
31
+            'SUMMARY' => 'Fellowship meeting',
32
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
33
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
34
+        ]);
35
+        $vEventOld->add('ORGANIZER', 'mailto:[email protected]');
36
+        $vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
37
+
38
+        $vEventNew = $vCalendarNew->add('VEVENT', [
39
+            'UID' => 'uid-1234',
40
+            'LAST-MODIFIED' => 123456,
41
+            'SEQUENCE' => 2,
42
+            'SUMMARY' => 'Fellowship meeting',
43
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
44
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
45
+        ]);
46
+        $vEventNew->add('ORGANIZER', 'mailto:[email protected]');
47
+        $vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
48
+
49
+        $result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
50
+        $this->assertEmpty($result['old']);
51
+        $this->assertEmpty($result['new']);
52
+    }
53
+
54
+    public function testNewEvent(): void {
55
+        $vCalendarOld = null;
56
+        $vCalendarNew = new VCalendar();
57
+
58
+        $vEventNew = $vCalendarNew->add('VEVENT', [
59
+            'UID' => 'uid-1234',
60
+            'LAST-MODIFIED' => 123456,
61
+            'SEQUENCE' => 2,
62
+            'SUMMARY' => 'Fellowship meeting',
63
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
64
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
65
+        ]);
66
+        $vEventNew->add('ORGANIZER', 'mailto:[email protected]');
67
+        $vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
68
+
69
+        $result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
70
+        $this->assertNull($result['old']);
71
+        $this->assertEquals([$vEventNew], $result['new']);
72
+    }
73
+
74
+    public function testModifiedUnmodifiedEvent(): void {
75
+        $vCalendarOld = new VCalendar();
76
+        $vCalendarNew = new VCalendar();
77
+
78
+        $vEventOld1 = $vCalendarOld->add('VEVENT', [
79
+            'UID' => 'uid-1234',
80
+            'LAST-MODIFIED' => 123456,
81
+            'SEQUENCE' => 2,
82
+            'SUMMARY' => 'Fellowship meeting',
83
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
84
+        ]);
85
+        $vEventOld1->add('ORGANIZER', 'mailto:[email protected]');
86
+        $vEventOld1->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
87
+
88
+        $vEventOld2 = $vCalendarOld->add('VEVENT', [
89
+            'UID' => 'uid-1235',
90
+            'LAST-MODIFIED' => 123456,
91
+            'SEQUENCE' => 2,
92
+            'SUMMARY' => 'Fellowship meeting',
93
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
94
+        ]);
95
+        $vEventOld2->add('ORGANIZER', 'mailto:[email protected]');
96
+        $vEventOld2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
97
+
98
+        $vEventNew1 = $vCalendarNew->add('VEVENT', [
99
+            'UID' => 'uid-1234',
100
+            'LAST-MODIFIED' => 123456,
101
+            'SEQUENCE' => 2,
102
+            'SUMMARY' => 'Fellowship meeting',
103
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
104
+        ]);
105
+        $vEventNew1->add('ORGANIZER', 'mailto:[email protected]');
106
+        $vEventNew1->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
107
+
108
+        $vEventNew2 = $vCalendarNew->add('VEVENT', [
109
+            'UID' => 'uid-1235',
110
+            'LAST-MODIFIED' => 123457,
111
+            'SEQUENCE' => 3,
112
+            'SUMMARY' => 'Fellowship meeting 2',
113
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
114
+        ]);
115
+        $vEventNew2->add('ORGANIZER', 'mailto:[email protected]');
116
+        $vEventNew2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
117
+
118
+        $result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
119
+        $this->assertEquals([$vEventOld2], $result['old']);
120
+        $this->assertEquals([$vEventNew2], $result['new']);
121
+    }
122
+
123
+    // First test to certify fix for issue nextcloud/server#41084
124
+    public function testSequenceNumberIncrementDetectedForFirstModificationToEventWithoutZeroInit(): void {
125
+        $vCalendarOld = new VCalendar();
126
+        $vCalendarNew = new VCalendar();
127
+
128
+        $vEventOld = $vCalendarOld->add('VEVENT', [
129
+            'UID' => 'uid-1234',
130
+            'LAST-MODIFIED' => 123456,
131
+            // 'SEQUENCE' => 0,			// sequence number may not be set to zero during event creation and instead fully omitted
132
+            'SUMMARY' => 'Fellowship meeting',
133
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
134
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
135
+        ]);
136
+        $vEventOld->add('ORGANIZER', 'mailto:[email protected]');
137
+        $vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
138
+
139
+        $vEventNew = $vCalendarNew->add('VEVENT', [
140
+            'UID' => 'uid-1234',
141
+            'LAST-MODIFIED' => 123456,
142
+            'SEQUENCE' => 1,
143
+            'SUMMARY' => 'Fellowship meeting',
144
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
145
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
146
+        ]);
147
+        $vEventNew->add('ORGANIZER', 'mailto:[email protected]');
148
+        $vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
149
+
150
+        $result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
151
+        $this->assertEquals([$vEventOld], $result['old']);
152
+        $this->assertEquals([$vEventNew], $result['new']);
153
+    }
154
+
155
+    // Second test to certify fix for issue nextcloud/server#41084
156
+    public function testSequenceNumberIncrementDetectedForFirstModificationToEventWithZeroInit(): void {
157
+        $vCalendarOld = new VCalendar();
158
+        $vCalendarNew = new VCalendar();
159
+
160
+        $vEventOld = $vCalendarOld->add('VEVENT', [
161
+            'UID' => 'uid-1234',
162
+            'LAST-MODIFIED' => 123456,
163
+            'SEQUENCE' => 0,
164
+            'SUMMARY' => 'Fellowship meeting',
165
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
166
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
167
+        ]);
168
+        $vEventOld->add('ORGANIZER', 'mailto:[email protected]');
169
+        $vEventOld->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
170
+
171
+        $vEventNew = $vCalendarNew->add('VEVENT', [
172
+            'UID' => 'uid-1234',
173
+            'LAST-MODIFIED' => 123456,
174
+            'SEQUENCE' => 1,
175
+            'SUMMARY' => 'Fellowship meeting',
176
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
177
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z',
178
+        ]);
179
+        $vEventNew->add('ORGANIZER', 'mailto:[email protected]');
180
+        $vEventNew->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
181
+
182
+        $result = $this->eventComparisonService->findModified($vCalendarNew, $vCalendarOld);
183
+        $this->assertEquals([$vEventOld], $result['old']);
184
+        $this->assertEquals([$vEventNew], $result['new']);
185
+    }
186 186
 
187 187
 
188 188
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php 1 patch
Indentation   +241 added lines, -241 removed lines patch added patch discarded remove patch
@@ -21,245 +21,245 @@
 block discarded – undo
21 21
 use Test\TestCase;
22 22
 
23 23
 class NotifierTest extends TestCase {
24
-	protected IFactory&MockObject $factory;
25
-	protected IURLGenerator&MockObject $urlGenerator;
26
-	protected IL10N&MockObject $l10n;
27
-	protected ITimeFactory&MockObject $timeFactory;
28
-	protected Notifier $notifier;
29
-
30
-	protected function setUp(): void {
31
-		parent::setUp();
32
-
33
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
34
-		$this->l10n = $this->createMock(IL10N::class);
35
-		$this->l10n->expects($this->any())
36
-			->method('t')
37
-			->willReturnCallback(function ($string, $args) {
38
-				if (!is_array($args)) {
39
-					$args = [$args];
40
-				}
41
-				return vsprintf($string, $args);
42
-			});
43
-		$this->l10n->expects($this->any())
44
-			->method('l')
45
-			->willReturnCallback(function ($string, $args) {
46
-				/** \DateTime $args */
47
-				return $args->format(\DateTime::ATOM);
48
-			});
49
-		$this->l10n->expects($this->any())
50
-			->method('n')
51
-			->willReturnCallback(function ($textSingular, $textPlural, $count, $args) {
52
-				$text = $count === 1 ? $textSingular : $textPlural;
53
-				$text = str_replace('%n', (string)$count, $text);
54
-				return vsprintf($text, $args);
55
-			});
56
-		$this->factory = $this->createMock(IFactory::class);
57
-		$this->factory->expects($this->any())
58
-			->method('get')
59
-			->willReturn($this->l10n);
60
-
61
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
62
-		$this->timeFactory
63
-			->method('getDateTime')
64
-			->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2005-08-15T14:00:00+02:00'));
65
-
66
-		$this->notifier = new Notifier(
67
-			$this->factory,
68
-			$this->urlGenerator,
69
-			$this->timeFactory
70
-		);
71
-	}
72
-
73
-	public function testGetId():void {
74
-		$this->assertEquals($this->notifier->getID(), 'dav');
75
-	}
76
-
77
-	public function testGetName():void {
78
-		$this->assertEquals($this->notifier->getName(), 'Calendar');
79
-	}
80
-
81
-
82
-	public function testPrepareWrongApp(): void {
83
-		$this->expectException(UnknownNotificationException::class);
84
-		$this->expectExceptionMessage('Notification not from this app');
85
-
86
-		/** @var INotification&MockObject $notification */
87
-		$notification = $this->createMock(INotification::class);
88
-
89
-		$notification->expects($this->once())
90
-			->method('getApp')
91
-			->willReturn('notifications');
92
-		$notification->expects($this->never())
93
-			->method('getSubject');
94
-
95
-		$this->notifier->prepare($notification, 'en');
96
-	}
97
-
98
-
99
-	public function testPrepareWrongSubject(): void {
100
-		$this->expectException(UnknownNotificationException::class);
101
-		$this->expectExceptionMessage('Unknown subject');
102
-
103
-		/** @var INotification&MockObject $notification */
104
-		$notification = $this->createMock(INotification::class);
105
-
106
-		$notification->expects($this->once())
107
-			->method('getApp')
108
-			->willReturn(Application::APP_ID);
109
-		$notification->expects($this->once())
110
-			->method('getSubject')
111
-			->willReturn('wrong subject');
112
-
113
-		$this->notifier->prepare($notification, 'en');
114
-	}
115
-
116
-	private static function hasPhpDatetimeDiffBug(): bool {
117
-		$d1 = \DateTime::createFromFormat(\DateTimeInterface::ATOM, '2023-11-22T11:52:00+01:00');
118
-		$d2 = new \DateTime('2023-11-22T10:52:03', new \DateTimeZone('UTC'));
119
-
120
-		// The difference is 3 seconds, not -1year+11months+…
121
-		return $d1->diff($d2)->y < 0;
122
-	}
123
-
124
-	public static function dataPrepare(): array {
125
-		return [
126
-			[
127
-				'calendar_reminder',
128
-				[
129
-					'title' => 'Title of this event',
130
-					'start_atom' => '2005-08-15T15:52:01+02:00'
131
-				],
132
-				self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (in 1 hour, 52 minutes)',
133
-				[
134
-					'title' => 'Title of this event',
135
-					'description' => null,
136
-					'location' => 'NC Headquarters',
137
-					'all_day' => false,
138
-					'start_atom' => '2005-08-15T15:52:01+02:00',
139
-					'start_is_floating' => false,
140
-					'start_timezone' => 'Europe/Berlin',
141
-					'end_atom' => '2005-08-15T17:52:01+02:00',
142
-					'end_is_floating' => false,
143
-					'end_timezone' => 'Europe/Berlin',
144
-					'calendar_displayname' => 'Personal',
145
-				],
146
-				"Calendar: Personal\r\nDate: 2005-08-15T15:52:01+02:00, 2005-08-15T15:52:01+02:00 - 2005-08-15T17:52:01+02:00 (Europe/Berlin)\r\nWhere: NC Headquarters"
147
-			],
148
-			[
149
-				'calendar_reminder',
150
-				[
151
-					'title' => 'Title of this event',
152
-					'start_atom' => '2005-08-15T13:00:00+02:00',
153
-				],
154
-				self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (1 hour ago)',
155
-				[
156
-					'title' => 'Title of this event',
157
-					'description' => null,
158
-					'location' => 'NC Headquarters',
159
-					'all_day' => false,
160
-					'start_atom' => '2005-08-15T13:00:00+02:00',
161
-					'start_is_floating' => false,
162
-					'start_timezone' => 'Europe/Berlin',
163
-					'end_atom' => '2005-08-15T15:00:00+02:00',
164
-					'end_is_floating' => false,
165
-					'end_timezone' => 'Europe/Berlin',
166
-					'calendar_displayname' => 'Personal',
167
-				],
168
-				"Calendar: Personal\r\nDate: 2005-08-15T13:00:00+02:00, 2005-08-15T13:00:00+02:00 - 2005-08-15T15:00:00+02:00 (Europe/Berlin)\r\nWhere: NC Headquarters"
169
-			],
170
-		];
171
-	}
172
-
173
-	/**
174
-	 * @dataProvider dataPrepare
175
-	 */
176
-	public function testPrepare(string $subjectType, array $subjectParams, string $subject, array $messageParams, string $message): void {
177
-		/** @var INotification&MockObject $notification */
178
-		$notification = $this->createMock(INotification::class);
179
-
180
-		$notification->expects($this->once())
181
-			->method('getApp')
182
-			->willReturn(Application::APP_ID);
183
-		$notification->expects($this->once())
184
-			->method('getSubject')
185
-			->willReturn($subjectType);
186
-		$notification->expects($this->once())
187
-			->method('getSubjectParameters')
188
-			->willReturn($subjectParams);
189
-		$notification->expects($this->once())
190
-			->method('getMessageParameters')
191
-			->willReturn($messageParams);
192
-
193
-		$notification->expects($this->once())
194
-			->method('setParsedSubject')
195
-			->with($subject)
196
-			->willReturnSelf();
197
-
198
-		$notification->expects($this->once())
199
-			->method('setParsedMessage')
200
-			->with($message)
201
-			->willReturnSelf();
202
-
203
-		$this->urlGenerator->expects($this->once())
204
-			->method('imagePath')
205
-			->with('core', 'places/calendar.svg')
206
-			->willReturn('icon-url');
207
-		$this->urlGenerator->expects($this->once())
208
-			->method('getAbsoluteURL')
209
-			->with('icon-url')
210
-			->willReturn('absolute-icon-url');
211
-		$notification->expects($this->once())
212
-			->method('setIcon')
213
-			->with('absolute-icon-url')
214
-			->willReturnSelf();
215
-
216
-		$return = $this->notifier->prepare($notification, 'en');
217
-
218
-		$this->assertEquals($notification, $return);
219
-	}
220
-
221
-	public function testPassedEvent(): void {
222
-		/** @var INotification&MockObject $notification */
223
-		$notification = $this->createMock(INotification::class);
224
-
225
-		$notification->expects($this->once())
226
-			->method('getApp')
227
-			->willReturn(Application::APP_ID);
228
-		$notification->expects($this->once())
229
-			->method('getSubject')
230
-			->willReturn('calendar_reminder');
231
-		$notification->expects($this->once())
232
-			->method('getSubjectParameters')
233
-			->willReturn([
234
-				'title' => 'Title of this event',
235
-				'start_atom' => '2005-08-15T08:00:00+02:00'
236
-			]);
237
-
238
-		$notification->expects($this->once())
239
-			->method('getMessageParameters')
240
-			->willReturn([
241
-				'title' => 'Title of this event',
242
-				'description' => null,
243
-				'location' => 'NC Headquarters',
244
-				'all_day' => false,
245
-				'start_atom' => '2005-08-15T08:00:00+02:00',
246
-				'start_is_floating' => false,
247
-				'start_timezone' => 'Europe/Berlin',
248
-				'end_atom' => '2005-08-15T13:00:00+02:00',
249
-				'end_is_floating' => false,
250
-				'end_timezone' => 'Europe/Berlin',
251
-				'calendar_displayname' => 'Personal',
252
-			]);
253
-
254
-		$notification->expects($this->once())
255
-			->method('setParsedSubject')
256
-			->with(self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (6 hours ago)')
257
-			->willReturnSelf();
258
-
259
-		$this->expectException(AlreadyProcessedException::class);
260
-
261
-		$return = $this->notifier->prepare($notification, 'en');
262
-
263
-		$this->assertEquals($notification, $return);
264
-	}
24
+    protected IFactory&MockObject $factory;
25
+    protected IURLGenerator&MockObject $urlGenerator;
26
+    protected IL10N&MockObject $l10n;
27
+    protected ITimeFactory&MockObject $timeFactory;
28
+    protected Notifier $notifier;
29
+
30
+    protected function setUp(): void {
31
+        parent::setUp();
32
+
33
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
34
+        $this->l10n = $this->createMock(IL10N::class);
35
+        $this->l10n->expects($this->any())
36
+            ->method('t')
37
+            ->willReturnCallback(function ($string, $args) {
38
+                if (!is_array($args)) {
39
+                    $args = [$args];
40
+                }
41
+                return vsprintf($string, $args);
42
+            });
43
+        $this->l10n->expects($this->any())
44
+            ->method('l')
45
+            ->willReturnCallback(function ($string, $args) {
46
+                /** \DateTime $args */
47
+                return $args->format(\DateTime::ATOM);
48
+            });
49
+        $this->l10n->expects($this->any())
50
+            ->method('n')
51
+            ->willReturnCallback(function ($textSingular, $textPlural, $count, $args) {
52
+                $text = $count === 1 ? $textSingular : $textPlural;
53
+                $text = str_replace('%n', (string)$count, $text);
54
+                return vsprintf($text, $args);
55
+            });
56
+        $this->factory = $this->createMock(IFactory::class);
57
+        $this->factory->expects($this->any())
58
+            ->method('get')
59
+            ->willReturn($this->l10n);
60
+
61
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
62
+        $this->timeFactory
63
+            ->method('getDateTime')
64
+            ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2005-08-15T14:00:00+02:00'));
65
+
66
+        $this->notifier = new Notifier(
67
+            $this->factory,
68
+            $this->urlGenerator,
69
+            $this->timeFactory
70
+        );
71
+    }
72
+
73
+    public function testGetId():void {
74
+        $this->assertEquals($this->notifier->getID(), 'dav');
75
+    }
76
+
77
+    public function testGetName():void {
78
+        $this->assertEquals($this->notifier->getName(), 'Calendar');
79
+    }
80
+
81
+
82
+    public function testPrepareWrongApp(): void {
83
+        $this->expectException(UnknownNotificationException::class);
84
+        $this->expectExceptionMessage('Notification not from this app');
85
+
86
+        /** @var INotification&MockObject $notification */
87
+        $notification = $this->createMock(INotification::class);
88
+
89
+        $notification->expects($this->once())
90
+            ->method('getApp')
91
+            ->willReturn('notifications');
92
+        $notification->expects($this->never())
93
+            ->method('getSubject');
94
+
95
+        $this->notifier->prepare($notification, 'en');
96
+    }
97
+
98
+
99
+    public function testPrepareWrongSubject(): void {
100
+        $this->expectException(UnknownNotificationException::class);
101
+        $this->expectExceptionMessage('Unknown subject');
102
+
103
+        /** @var INotification&MockObject $notification */
104
+        $notification = $this->createMock(INotification::class);
105
+
106
+        $notification->expects($this->once())
107
+            ->method('getApp')
108
+            ->willReturn(Application::APP_ID);
109
+        $notification->expects($this->once())
110
+            ->method('getSubject')
111
+            ->willReturn('wrong subject');
112
+
113
+        $this->notifier->prepare($notification, 'en');
114
+    }
115
+
116
+    private static function hasPhpDatetimeDiffBug(): bool {
117
+        $d1 = \DateTime::createFromFormat(\DateTimeInterface::ATOM, '2023-11-22T11:52:00+01:00');
118
+        $d2 = new \DateTime('2023-11-22T10:52:03', new \DateTimeZone('UTC'));
119
+
120
+        // The difference is 3 seconds, not -1year+11months+…
121
+        return $d1->diff($d2)->y < 0;
122
+    }
123
+
124
+    public static function dataPrepare(): array {
125
+        return [
126
+            [
127
+                'calendar_reminder',
128
+                [
129
+                    'title' => 'Title of this event',
130
+                    'start_atom' => '2005-08-15T15:52:01+02:00'
131
+                ],
132
+                self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (in 1 hour, 52 minutes)',
133
+                [
134
+                    'title' => 'Title of this event',
135
+                    'description' => null,
136
+                    'location' => 'NC Headquarters',
137
+                    'all_day' => false,
138
+                    'start_atom' => '2005-08-15T15:52:01+02:00',
139
+                    'start_is_floating' => false,
140
+                    'start_timezone' => 'Europe/Berlin',
141
+                    'end_atom' => '2005-08-15T17:52:01+02:00',
142
+                    'end_is_floating' => false,
143
+                    'end_timezone' => 'Europe/Berlin',
144
+                    'calendar_displayname' => 'Personal',
145
+                ],
146
+                "Calendar: Personal\r\nDate: 2005-08-15T15:52:01+02:00, 2005-08-15T15:52:01+02:00 - 2005-08-15T17:52:01+02:00 (Europe/Berlin)\r\nWhere: NC Headquarters"
147
+            ],
148
+            [
149
+                'calendar_reminder',
150
+                [
151
+                    'title' => 'Title of this event',
152
+                    'start_atom' => '2005-08-15T13:00:00+02:00',
153
+                ],
154
+                self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (1 hour ago)',
155
+                [
156
+                    'title' => 'Title of this event',
157
+                    'description' => null,
158
+                    'location' => 'NC Headquarters',
159
+                    'all_day' => false,
160
+                    'start_atom' => '2005-08-15T13:00:00+02:00',
161
+                    'start_is_floating' => false,
162
+                    'start_timezone' => 'Europe/Berlin',
163
+                    'end_atom' => '2005-08-15T15:00:00+02:00',
164
+                    'end_is_floating' => false,
165
+                    'end_timezone' => 'Europe/Berlin',
166
+                    'calendar_displayname' => 'Personal',
167
+                ],
168
+                "Calendar: Personal\r\nDate: 2005-08-15T13:00:00+02:00, 2005-08-15T13:00:00+02:00 - 2005-08-15T15:00:00+02:00 (Europe/Berlin)\r\nWhere: NC Headquarters"
169
+            ],
170
+        ];
171
+    }
172
+
173
+    /**
174
+     * @dataProvider dataPrepare
175
+     */
176
+    public function testPrepare(string $subjectType, array $subjectParams, string $subject, array $messageParams, string $message): void {
177
+        /** @var INotification&MockObject $notification */
178
+        $notification = $this->createMock(INotification::class);
179
+
180
+        $notification->expects($this->once())
181
+            ->method('getApp')
182
+            ->willReturn(Application::APP_ID);
183
+        $notification->expects($this->once())
184
+            ->method('getSubject')
185
+            ->willReturn($subjectType);
186
+        $notification->expects($this->once())
187
+            ->method('getSubjectParameters')
188
+            ->willReturn($subjectParams);
189
+        $notification->expects($this->once())
190
+            ->method('getMessageParameters')
191
+            ->willReturn($messageParams);
192
+
193
+        $notification->expects($this->once())
194
+            ->method('setParsedSubject')
195
+            ->with($subject)
196
+            ->willReturnSelf();
197
+
198
+        $notification->expects($this->once())
199
+            ->method('setParsedMessage')
200
+            ->with($message)
201
+            ->willReturnSelf();
202
+
203
+        $this->urlGenerator->expects($this->once())
204
+            ->method('imagePath')
205
+            ->with('core', 'places/calendar.svg')
206
+            ->willReturn('icon-url');
207
+        $this->urlGenerator->expects($this->once())
208
+            ->method('getAbsoluteURL')
209
+            ->with('icon-url')
210
+            ->willReturn('absolute-icon-url');
211
+        $notification->expects($this->once())
212
+            ->method('setIcon')
213
+            ->with('absolute-icon-url')
214
+            ->willReturnSelf();
215
+
216
+        $return = $this->notifier->prepare($notification, 'en');
217
+
218
+        $this->assertEquals($notification, $return);
219
+    }
220
+
221
+    public function testPassedEvent(): void {
222
+        /** @var INotification&MockObject $notification */
223
+        $notification = $this->createMock(INotification::class);
224
+
225
+        $notification->expects($this->once())
226
+            ->method('getApp')
227
+            ->willReturn(Application::APP_ID);
228
+        $notification->expects($this->once())
229
+            ->method('getSubject')
230
+            ->willReturn('calendar_reminder');
231
+        $notification->expects($this->once())
232
+            ->method('getSubjectParameters')
233
+            ->willReturn([
234
+                'title' => 'Title of this event',
235
+                'start_atom' => '2005-08-15T08:00:00+02:00'
236
+            ]);
237
+
238
+        $notification->expects($this->once())
239
+            ->method('getMessageParameters')
240
+            ->willReturn([
241
+                'title' => 'Title of this event',
242
+                'description' => null,
243
+                'location' => 'NC Headquarters',
244
+                'all_day' => false,
245
+                'start_atom' => '2005-08-15T08:00:00+02:00',
246
+                'start_is_floating' => false,
247
+                'start_timezone' => 'Europe/Berlin',
248
+                'end_atom' => '2005-08-15T13:00:00+02:00',
249
+                'end_is_floating' => false,
250
+                'end_timezone' => 'Europe/Berlin',
251
+                'calendar_displayname' => 'Personal',
252
+            ]);
253
+
254
+        $notification->expects($this->once())
255
+            ->method('setParsedSubject')
256
+            ->with(self::hasPhpDatetimeDiffBug() ? 'Title of this event' : 'Title of this event (6 hours ago)')
257
+            ->willReturnSelf();
258
+
259
+        $this->expectException(AlreadyProcessedException::class);
260
+
261
+        $return = $this->notifier->prepare($notification, 'en');
262
+
263
+        $this->assertEquals($notification, $return);
264
+    }
265 265
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php 2 patches
Indentation   +358 added lines, -358 removed lines patch added patch discarded remove patch
@@ -14,364 +14,364 @@
 block discarded – undo
14 14
 use Test\TestCase;
15 15
 
16 16
 class BackendTest extends TestCase {
17
-	private ReminderBackend $reminderBackend;
18
-	private ITimeFactory&MockObject $timeFactory;
17
+    private ReminderBackend $reminderBackend;
18
+    private ITimeFactory&MockObject $timeFactory;
19 19
 
20
-	protected function setUp(): void {
21
-		parent::setUp();
20
+    protected function setUp(): void {
21
+        parent::setUp();
22 22
 
23
-		$query = self::$realDatabase->getQueryBuilder();
24
-		$query->delete('calendar_reminders')->executeStatement();
25
-		$query->delete('calendarobjects')->executeStatement();
26
-		$query->delete('calendars')->executeStatement();
27
-
28
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
29
-		$this->reminderBackend = new ReminderBackend(self::$realDatabase, $this->timeFactory);
30
-
31
-		$this->createRemindersTestSet();
32
-	}
33
-
34
-	protected function tearDown(): void {
35
-		$query = self::$realDatabase->getQueryBuilder();
36
-		$query->delete('calendar_reminders')->executeStatement();
37
-		$query->delete('calendarobjects')->executeStatement();
38
-		$query->delete('calendars')->executeStatement();
39
-
40
-		parent::tearDown();
41
-	}
42
-
43
-
44
-	public function testCleanRemindersForEvent(): void {
45
-		$query = self::$realDatabase->getQueryBuilder();
46
-		$rows = $query->select('*')
47
-			->from('calendar_reminders')
48
-			->execute()
49
-			->fetchAll();
50
-
51
-		$this->assertCount(4, $rows);
52
-
53
-		$this->reminderBackend->cleanRemindersForEvent(1);
54
-
55
-		$query = self::$realDatabase->getQueryBuilder();
56
-		$rows = $query->select('*')
57
-			->from('calendar_reminders')
58
-			->execute()
59
-			->fetchAll();
60
-
61
-		$this->assertCount(2, $rows);
62
-	}
63
-
64
-	public function testCleanRemindersForCalendar(): void {
65
-		$query = self::$realDatabase->getQueryBuilder();
66
-		$rows = $query->select('*')
67
-			->from('calendar_reminders')
68
-			->execute()
69
-			->fetchAll();
70
-
71
-		$this->assertCount(4, $rows);
72
-
73
-		$this->reminderBackend->cleanRemindersForCalendar(1);
74
-
75
-		$query = self::$realDatabase->getQueryBuilder();
76
-		$rows = $query->select('*')
77
-			->from('calendar_reminders')
78
-			->execute()
79
-			->fetchAll();
80
-
81
-		$this->assertCount(1, $rows);
82
-	}
83
-
84
-	public function testRemoveReminder(): void {
85
-		$query = self::$realDatabase->getQueryBuilder();
86
-		$rows = $query->select('*')
87
-			->from('calendar_reminders')
88
-			->execute()
89
-			->fetchAll();
90
-
91
-		$this->assertCount(4, $rows);
92
-
93
-		$this->reminderBackend->removeReminder((int)$rows[3]['id']);
94
-
95
-		$query = self::$realDatabase->getQueryBuilder();
96
-		$rows = $query->select('*')
97
-			->from('calendar_reminders')
98
-			->execute()
99
-			->fetchAll();
100
-
101
-		$this->assertCount(3, $rows);
102
-	}
103
-
104
-	public function testGetRemindersToProcess(): void {
105
-		$this->timeFactory->expects($this->exactly(1))
106
-			->method('getTime')
107
-			->with()
108
-			->willReturn(123457);
109
-
110
-		$rows = $this->reminderBackend->getRemindersToProcess();
111
-
112
-		$this->assertCount(2, $rows);
113
-		unset($rows[0]['id']);
114
-		unset($rows[1]['id']);
115
-
116
-		$expected1 = [
117
-			'calendar_id' => 1,
118
-			'object_id' => 1,
119
-			'uid' => 'asd',
120
-			'is_recurring' => false,
121
-			'recurrence_id' => 123458,
122
-			'is_recurrence_exception' => false,
123
-			'event_hash' => 'asd123',
124
-			'alarm_hash' => 'asd567',
125
-			'type' => 'EMAIL',
126
-			'is_relative' => true,
127
-			'notification_date' => 123456,
128
-			'is_repeat_based' => false,
129
-			'calendardata' => 'Calendar data 123',
130
-			'displayname' => 'Displayname 123',
131
-			'principaluri' => 'principals/users/user001',
132
-		];
133
-		$expected2 = [
134
-			'calendar_id' => 1,
135
-			'object_id' => 1,
136
-			'uid' => 'asd',
137
-			'is_recurring' => false,
138
-			'recurrence_id' => 123458,
139
-			'is_recurrence_exception' => false,
140
-			'event_hash' => 'asd123',
141
-			'alarm_hash' => 'asd567',
142
-			'type' => 'AUDIO',
143
-			'is_relative' => true,
144
-			'notification_date' => 123456,
145
-			'is_repeat_based' => false,
146
-			'calendardata' => 'Calendar data 123',
147
-			'displayname' => 'Displayname 123',
148
-			'principaluri' => 'principals/users/user001',
149
-		];
150
-
151
-		$this->assertEqualsCanonicalizing([$rows[0],$rows[1]], [$expected1,$expected2]);
152
-	}
153
-
154
-	public function testGetAllScheduledRemindersForEvent(): void {
155
-		$rows = $this->reminderBackend->getAllScheduledRemindersForEvent(1);
156
-
157
-		$this->assertCount(2, $rows);
158
-		unset($rows[0]['id']);
159
-		unset($rows[1]['id']);
160
-
161
-		$this->assertEquals($rows[0], [
162
-			'calendar_id' => 1,
163
-			'object_id' => 1,
164
-			'uid' => 'asd',
165
-			'is_recurring' => false,
166
-			'recurrence_id' => 123458,
167
-			'is_recurrence_exception' => false,
168
-			'event_hash' => 'asd123',
169
-			'alarm_hash' => 'asd567',
170
-			'type' => 'EMAIL',
171
-			'is_relative' => true,
172
-			'notification_date' => 123456,
173
-			'is_repeat_based' => false,
174
-		]);
175
-		$this->assertEquals($rows[1], [
176
-			'calendar_id' => 1,
177
-			'object_id' => 1,
178
-			'uid' => 'asd',
179
-			'is_recurring' => false,
180
-			'recurrence_id' => 123458,
181
-			'is_recurrence_exception' => false,
182
-			'event_hash' => 'asd123',
183
-			'alarm_hash' => 'asd567',
184
-			'type' => 'AUDIO',
185
-			'is_relative' => true,
186
-			'notification_date' => 123456,
187
-			'is_repeat_based' => false,
188
-		]);
189
-	}
190
-
191
-	public function testInsertReminder(): void {
192
-		$query = self::$realDatabase->getQueryBuilder();
193
-		$rows = $query->select('*')
194
-			->from('calendar_reminders')
195
-			->execute()
196
-			->fetchAll();
197
-
198
-		$this->assertCount(4, $rows);
199
-
200
-		$this->reminderBackend->insertReminder(42, 1337, 'uid99', true, 12345678,
201
-			true, 'hash99', 'hash42', 'AUDIO', false, 12345670, false);
202
-
203
-		$query = self::$realDatabase->getQueryBuilder();
204
-		$rows = $query->select('*')
205
-			->from('calendar_reminders')
206
-			->execute()
207
-			->fetchAll();
208
-
209
-		$this->assertCount(5, $rows);
210
-
211
-		unset($rows[4]['id']);
212
-
213
-		$this->assertEquals($rows[4], [
214
-			'calendar_id' => '42',
215
-			'object_id' => '1337',
216
-			'is_recurring' => '1',
217
-			'uid' => 'uid99',
218
-			'recurrence_id' => '12345678',
219
-			'is_recurrence_exception' => '1',
220
-			'event_hash' => 'hash99',
221
-			'alarm_hash' => 'hash42',
222
-			'type' => 'AUDIO',
223
-			'is_relative' => '0',
224
-			'notification_date' => '12345670',
225
-			'is_repeat_based' => '0',
226
-		]);
227
-	}
228
-
229
-	public function testUpdateReminder(): void {
230
-		$query = self::$realDatabase->getQueryBuilder();
231
-		$rows = $query->select('*')
232
-			->from('calendar_reminders')
233
-			->executeQuery()
234
-			->fetchAll();
235
-
236
-		$this->assertCount(4, $rows);
237
-
238
-		$this->assertEquals($rows[3]['notification_date'], 123600);
239
-
240
-		$reminderId = (int)$rows[3]['id'];
241
-		$newNotificationDate = 123700;
242
-
243
-		$this->reminderBackend->updateReminder($reminderId, $newNotificationDate);
244
-
245
-		$query = self::$realDatabase->getQueryBuilder();
246
-		$row = $query->select('notification_date')
247
-			->from('calendar_reminders')
248
-			->where($query->expr()->eq('id', $query->createNamedParameter($reminderId)))
249
-			->executeQuery()
250
-			->fetch();
251
-
252
-		$this->assertEquals((int)$row['notification_date'], 123700);
253
-	}
254
-
255
-
256
-	private function createRemindersTestSet(): void {
257
-		$query = self::$realDatabase->getQueryBuilder();
258
-		$query->insert('calendars')
259
-			->values([
260
-				'id' => $query->createNamedParameter(1),
261
-				'principaluri' => $query->createNamedParameter('principals/users/user001'),
262
-				'displayname' => $query->createNamedParameter('Displayname 123'),
263
-			])
264
-			->executeStatement();
265
-
266
-		$query = self::$realDatabase->getQueryBuilder();
267
-		$query->insert('calendars')
268
-			->values([
269
-				'id' => $query->createNamedParameter(99),
270
-				'principaluri' => $query->createNamedParameter('principals/users/user002'),
271
-				'displayname' => $query->createNamedParameter('Displayname 99'),
272
-			])
273
-			->executeStatement();
274
-
275
-		$query = self::$realDatabase->getQueryBuilder();
276
-		$query->insert('calendarobjects')
277
-			->values([
278
-				'id' => $query->createNamedParameter(1),
279
-				'calendardata' => $query->createNamedParameter('Calendar data 123'),
280
-				'calendarid' => $query->createNamedParameter(1),
281
-				'size' => $query->createNamedParameter(42),
282
-			])
283
-			->executeStatement();
284
-
285
-		$query = self::$realDatabase->getQueryBuilder();
286
-		$query->insert('calendarobjects')
287
-			->values([
288
-				'id' => $query->createNamedParameter(2),
289
-				'calendardata' => $query->createNamedParameter('Calendar data 456'),
290
-				'calendarid' => $query->createNamedParameter(1),
291
-				'size' => $query->createNamedParameter(42),
292
-			])
293
-			->executeStatement();
294
-
295
-		$query = self::$realDatabase->getQueryBuilder();
296
-		$query->insert('calendarobjects')
297
-			->values([
298
-				'id' => $query->createNamedParameter(10),
299
-				'calendardata' => $query->createNamedParameter('Calendar data 789'),
300
-				'calendarid' => $query->createNamedParameter(99),
301
-				'size' => $query->createNamedParameter(42),
302
-			])
303
-			->executeStatement();
304
-
305
-		$query = self::$realDatabase->getQueryBuilder();
306
-		$query->insert('calendar_reminders')
307
-			->values([
308
-				'calendar_id' => $query->createNamedParameter(1),
309
-				'object_id' => $query->createNamedParameter(1),
310
-				'uid' => $query->createNamedParameter('asd'),
311
-				'is_recurring' => $query->createNamedParameter(0),
312
-				'recurrence_id' => $query->createNamedParameter(123458),
313
-				'is_recurrence_exception' => $query->createNamedParameter(0),
314
-				'event_hash' => $query->createNamedParameter('asd123'),
315
-				'alarm_hash' => $query->createNamedParameter('asd567'),
316
-				'type' => $query->createNamedParameter('EMAIL'),
317
-				'is_relative' => $query->createNamedParameter(1),
318
-				'notification_date' => $query->createNamedParameter(123456),
319
-				'is_repeat_based' => $query->createNamedParameter(0),
320
-			])
321
-			->executeStatement();
322
-
323
-		$query = self::$realDatabase->getQueryBuilder();
324
-		$query->insert('calendar_reminders')
325
-			->values([
326
-				'calendar_id' => $query->createNamedParameter(1),
327
-				'object_id' => $query->createNamedParameter(1),
328
-				'uid' => $query->createNamedParameter('asd'),
329
-				'is_recurring' => $query->createNamedParameter(0),
330
-				'recurrence_id' => $query->createNamedParameter(123458),
331
-				'is_recurrence_exception' => $query->createNamedParameter(0),
332
-				'event_hash' => $query->createNamedParameter('asd123'),
333
-				'alarm_hash' => $query->createNamedParameter('asd567'),
334
-				'type' => $query->createNamedParameter('AUDIO'),
335
-				'is_relative' => $query->createNamedParameter(1),
336
-				'notification_date' => $query->createNamedParameter(123456),
337
-				'is_repeat_based' => $query->createNamedParameter(0),
338
-			])
339
-			->executeStatement();
340
-
341
-		$query = self::$realDatabase->getQueryBuilder();
342
-		$query->insert('calendar_reminders')
343
-			->values([
344
-				'calendar_id' => $query->createNamedParameter(1),
345
-				'object_id' => $query->createNamedParameter(2),
346
-				'uid' => $query->createNamedParameter('asd'),
347
-				'is_recurring' => $query->createNamedParameter(0),
348
-				'recurrence_id' => $query->createNamedParameter(123900),
349
-				'is_recurrence_exception' => $query->createNamedParameter(0),
350
-				'event_hash' => $query->createNamedParameter('asd123'),
351
-				'alarm_hash' => $query->createNamedParameter('asd567'),
352
-				'type' => $query->createNamedParameter('EMAIL'),
353
-				'is_relative' => $query->createNamedParameter(1),
354
-				'notification_date' => $query->createNamedParameter(123499),
355
-				'is_repeat_based' => $query->createNamedParameter(0),
356
-			])
357
-			->executeStatement();
358
-
359
-		$query = self::$realDatabase->getQueryBuilder();
360
-		$query->insert('calendar_reminders')
361
-			->values([
362
-				'calendar_id' => $query->createNamedParameter(99),
363
-				'object_id' => $query->createNamedParameter(10),
364
-				'uid' => $query->createNamedParameter('asd'),
365
-				'is_recurring' => $query->createNamedParameter(0),
366
-				'recurrence_id' => $query->createNamedParameter(123900),
367
-				'is_recurrence_exception' => $query->createNamedParameter(0),
368
-				'event_hash' => $query->createNamedParameter('asd123'),
369
-				'alarm_hash' => $query->createNamedParameter('asd567'),
370
-				'type' => $query->createNamedParameter('DISPLAY'),
371
-				'is_relative' => $query->createNamedParameter(1),
372
-				'notification_date' => $query->createNamedParameter(123600),
373
-				'is_repeat_based' => $query->createNamedParameter(0),
374
-			])
375
-			->executeStatement();
376
-	}
23
+        $query = self::$realDatabase->getQueryBuilder();
24
+        $query->delete('calendar_reminders')->executeStatement();
25
+        $query->delete('calendarobjects')->executeStatement();
26
+        $query->delete('calendars')->executeStatement();
27
+
28
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
29
+        $this->reminderBackend = new ReminderBackend(self::$realDatabase, $this->timeFactory);
30
+
31
+        $this->createRemindersTestSet();
32
+    }
33
+
34
+    protected function tearDown(): void {
35
+        $query = self::$realDatabase->getQueryBuilder();
36
+        $query->delete('calendar_reminders')->executeStatement();
37
+        $query->delete('calendarobjects')->executeStatement();
38
+        $query->delete('calendars')->executeStatement();
39
+
40
+        parent::tearDown();
41
+    }
42
+
43
+
44
+    public function testCleanRemindersForEvent(): void {
45
+        $query = self::$realDatabase->getQueryBuilder();
46
+        $rows = $query->select('*')
47
+            ->from('calendar_reminders')
48
+            ->execute()
49
+            ->fetchAll();
50
+
51
+        $this->assertCount(4, $rows);
52
+
53
+        $this->reminderBackend->cleanRemindersForEvent(1);
54
+
55
+        $query = self::$realDatabase->getQueryBuilder();
56
+        $rows = $query->select('*')
57
+            ->from('calendar_reminders')
58
+            ->execute()
59
+            ->fetchAll();
60
+
61
+        $this->assertCount(2, $rows);
62
+    }
63
+
64
+    public function testCleanRemindersForCalendar(): void {
65
+        $query = self::$realDatabase->getQueryBuilder();
66
+        $rows = $query->select('*')
67
+            ->from('calendar_reminders')
68
+            ->execute()
69
+            ->fetchAll();
70
+
71
+        $this->assertCount(4, $rows);
72
+
73
+        $this->reminderBackend->cleanRemindersForCalendar(1);
74
+
75
+        $query = self::$realDatabase->getQueryBuilder();
76
+        $rows = $query->select('*')
77
+            ->from('calendar_reminders')
78
+            ->execute()
79
+            ->fetchAll();
80
+
81
+        $this->assertCount(1, $rows);
82
+    }
83
+
84
+    public function testRemoveReminder(): void {
85
+        $query = self::$realDatabase->getQueryBuilder();
86
+        $rows = $query->select('*')
87
+            ->from('calendar_reminders')
88
+            ->execute()
89
+            ->fetchAll();
90
+
91
+        $this->assertCount(4, $rows);
92
+
93
+        $this->reminderBackend->removeReminder((int)$rows[3]['id']);
94
+
95
+        $query = self::$realDatabase->getQueryBuilder();
96
+        $rows = $query->select('*')
97
+            ->from('calendar_reminders')
98
+            ->execute()
99
+            ->fetchAll();
100
+
101
+        $this->assertCount(3, $rows);
102
+    }
103
+
104
+    public function testGetRemindersToProcess(): void {
105
+        $this->timeFactory->expects($this->exactly(1))
106
+            ->method('getTime')
107
+            ->with()
108
+            ->willReturn(123457);
109
+
110
+        $rows = $this->reminderBackend->getRemindersToProcess();
111
+
112
+        $this->assertCount(2, $rows);
113
+        unset($rows[0]['id']);
114
+        unset($rows[1]['id']);
115
+
116
+        $expected1 = [
117
+            'calendar_id' => 1,
118
+            'object_id' => 1,
119
+            'uid' => 'asd',
120
+            'is_recurring' => false,
121
+            'recurrence_id' => 123458,
122
+            'is_recurrence_exception' => false,
123
+            'event_hash' => 'asd123',
124
+            'alarm_hash' => 'asd567',
125
+            'type' => 'EMAIL',
126
+            'is_relative' => true,
127
+            'notification_date' => 123456,
128
+            'is_repeat_based' => false,
129
+            'calendardata' => 'Calendar data 123',
130
+            'displayname' => 'Displayname 123',
131
+            'principaluri' => 'principals/users/user001',
132
+        ];
133
+        $expected2 = [
134
+            'calendar_id' => 1,
135
+            'object_id' => 1,
136
+            'uid' => 'asd',
137
+            'is_recurring' => false,
138
+            'recurrence_id' => 123458,
139
+            'is_recurrence_exception' => false,
140
+            'event_hash' => 'asd123',
141
+            'alarm_hash' => 'asd567',
142
+            'type' => 'AUDIO',
143
+            'is_relative' => true,
144
+            'notification_date' => 123456,
145
+            'is_repeat_based' => false,
146
+            'calendardata' => 'Calendar data 123',
147
+            'displayname' => 'Displayname 123',
148
+            'principaluri' => 'principals/users/user001',
149
+        ];
150
+
151
+        $this->assertEqualsCanonicalizing([$rows[0],$rows[1]], [$expected1,$expected2]);
152
+    }
153
+
154
+    public function testGetAllScheduledRemindersForEvent(): void {
155
+        $rows = $this->reminderBackend->getAllScheduledRemindersForEvent(1);
156
+
157
+        $this->assertCount(2, $rows);
158
+        unset($rows[0]['id']);
159
+        unset($rows[1]['id']);
160
+
161
+        $this->assertEquals($rows[0], [
162
+            'calendar_id' => 1,
163
+            'object_id' => 1,
164
+            'uid' => 'asd',
165
+            'is_recurring' => false,
166
+            'recurrence_id' => 123458,
167
+            'is_recurrence_exception' => false,
168
+            'event_hash' => 'asd123',
169
+            'alarm_hash' => 'asd567',
170
+            'type' => 'EMAIL',
171
+            'is_relative' => true,
172
+            'notification_date' => 123456,
173
+            'is_repeat_based' => false,
174
+        ]);
175
+        $this->assertEquals($rows[1], [
176
+            'calendar_id' => 1,
177
+            'object_id' => 1,
178
+            'uid' => 'asd',
179
+            'is_recurring' => false,
180
+            'recurrence_id' => 123458,
181
+            'is_recurrence_exception' => false,
182
+            'event_hash' => 'asd123',
183
+            'alarm_hash' => 'asd567',
184
+            'type' => 'AUDIO',
185
+            'is_relative' => true,
186
+            'notification_date' => 123456,
187
+            'is_repeat_based' => false,
188
+        ]);
189
+    }
190
+
191
+    public function testInsertReminder(): void {
192
+        $query = self::$realDatabase->getQueryBuilder();
193
+        $rows = $query->select('*')
194
+            ->from('calendar_reminders')
195
+            ->execute()
196
+            ->fetchAll();
197
+
198
+        $this->assertCount(4, $rows);
199
+
200
+        $this->reminderBackend->insertReminder(42, 1337, 'uid99', true, 12345678,
201
+            true, 'hash99', 'hash42', 'AUDIO', false, 12345670, false);
202
+
203
+        $query = self::$realDatabase->getQueryBuilder();
204
+        $rows = $query->select('*')
205
+            ->from('calendar_reminders')
206
+            ->execute()
207
+            ->fetchAll();
208
+
209
+        $this->assertCount(5, $rows);
210
+
211
+        unset($rows[4]['id']);
212
+
213
+        $this->assertEquals($rows[4], [
214
+            'calendar_id' => '42',
215
+            'object_id' => '1337',
216
+            'is_recurring' => '1',
217
+            'uid' => 'uid99',
218
+            'recurrence_id' => '12345678',
219
+            'is_recurrence_exception' => '1',
220
+            'event_hash' => 'hash99',
221
+            'alarm_hash' => 'hash42',
222
+            'type' => 'AUDIO',
223
+            'is_relative' => '0',
224
+            'notification_date' => '12345670',
225
+            'is_repeat_based' => '0',
226
+        ]);
227
+    }
228
+
229
+    public function testUpdateReminder(): void {
230
+        $query = self::$realDatabase->getQueryBuilder();
231
+        $rows = $query->select('*')
232
+            ->from('calendar_reminders')
233
+            ->executeQuery()
234
+            ->fetchAll();
235
+
236
+        $this->assertCount(4, $rows);
237
+
238
+        $this->assertEquals($rows[3]['notification_date'], 123600);
239
+
240
+        $reminderId = (int)$rows[3]['id'];
241
+        $newNotificationDate = 123700;
242
+
243
+        $this->reminderBackend->updateReminder($reminderId, $newNotificationDate);
244
+
245
+        $query = self::$realDatabase->getQueryBuilder();
246
+        $row = $query->select('notification_date')
247
+            ->from('calendar_reminders')
248
+            ->where($query->expr()->eq('id', $query->createNamedParameter($reminderId)))
249
+            ->executeQuery()
250
+            ->fetch();
251
+
252
+        $this->assertEquals((int)$row['notification_date'], 123700);
253
+    }
254
+
255
+
256
+    private function createRemindersTestSet(): void {
257
+        $query = self::$realDatabase->getQueryBuilder();
258
+        $query->insert('calendars')
259
+            ->values([
260
+                'id' => $query->createNamedParameter(1),
261
+                'principaluri' => $query->createNamedParameter('principals/users/user001'),
262
+                'displayname' => $query->createNamedParameter('Displayname 123'),
263
+            ])
264
+            ->executeStatement();
265
+
266
+        $query = self::$realDatabase->getQueryBuilder();
267
+        $query->insert('calendars')
268
+            ->values([
269
+                'id' => $query->createNamedParameter(99),
270
+                'principaluri' => $query->createNamedParameter('principals/users/user002'),
271
+                'displayname' => $query->createNamedParameter('Displayname 99'),
272
+            ])
273
+            ->executeStatement();
274
+
275
+        $query = self::$realDatabase->getQueryBuilder();
276
+        $query->insert('calendarobjects')
277
+            ->values([
278
+                'id' => $query->createNamedParameter(1),
279
+                'calendardata' => $query->createNamedParameter('Calendar data 123'),
280
+                'calendarid' => $query->createNamedParameter(1),
281
+                'size' => $query->createNamedParameter(42),
282
+            ])
283
+            ->executeStatement();
284
+
285
+        $query = self::$realDatabase->getQueryBuilder();
286
+        $query->insert('calendarobjects')
287
+            ->values([
288
+                'id' => $query->createNamedParameter(2),
289
+                'calendardata' => $query->createNamedParameter('Calendar data 456'),
290
+                'calendarid' => $query->createNamedParameter(1),
291
+                'size' => $query->createNamedParameter(42),
292
+            ])
293
+            ->executeStatement();
294
+
295
+        $query = self::$realDatabase->getQueryBuilder();
296
+        $query->insert('calendarobjects')
297
+            ->values([
298
+                'id' => $query->createNamedParameter(10),
299
+                'calendardata' => $query->createNamedParameter('Calendar data 789'),
300
+                'calendarid' => $query->createNamedParameter(99),
301
+                'size' => $query->createNamedParameter(42),
302
+            ])
303
+            ->executeStatement();
304
+
305
+        $query = self::$realDatabase->getQueryBuilder();
306
+        $query->insert('calendar_reminders')
307
+            ->values([
308
+                'calendar_id' => $query->createNamedParameter(1),
309
+                'object_id' => $query->createNamedParameter(1),
310
+                'uid' => $query->createNamedParameter('asd'),
311
+                'is_recurring' => $query->createNamedParameter(0),
312
+                'recurrence_id' => $query->createNamedParameter(123458),
313
+                'is_recurrence_exception' => $query->createNamedParameter(0),
314
+                'event_hash' => $query->createNamedParameter('asd123'),
315
+                'alarm_hash' => $query->createNamedParameter('asd567'),
316
+                'type' => $query->createNamedParameter('EMAIL'),
317
+                'is_relative' => $query->createNamedParameter(1),
318
+                'notification_date' => $query->createNamedParameter(123456),
319
+                'is_repeat_based' => $query->createNamedParameter(0),
320
+            ])
321
+            ->executeStatement();
322
+
323
+        $query = self::$realDatabase->getQueryBuilder();
324
+        $query->insert('calendar_reminders')
325
+            ->values([
326
+                'calendar_id' => $query->createNamedParameter(1),
327
+                'object_id' => $query->createNamedParameter(1),
328
+                'uid' => $query->createNamedParameter('asd'),
329
+                'is_recurring' => $query->createNamedParameter(0),
330
+                'recurrence_id' => $query->createNamedParameter(123458),
331
+                'is_recurrence_exception' => $query->createNamedParameter(0),
332
+                'event_hash' => $query->createNamedParameter('asd123'),
333
+                'alarm_hash' => $query->createNamedParameter('asd567'),
334
+                'type' => $query->createNamedParameter('AUDIO'),
335
+                'is_relative' => $query->createNamedParameter(1),
336
+                'notification_date' => $query->createNamedParameter(123456),
337
+                'is_repeat_based' => $query->createNamedParameter(0),
338
+            ])
339
+            ->executeStatement();
340
+
341
+        $query = self::$realDatabase->getQueryBuilder();
342
+        $query->insert('calendar_reminders')
343
+            ->values([
344
+                'calendar_id' => $query->createNamedParameter(1),
345
+                'object_id' => $query->createNamedParameter(2),
346
+                'uid' => $query->createNamedParameter('asd'),
347
+                'is_recurring' => $query->createNamedParameter(0),
348
+                'recurrence_id' => $query->createNamedParameter(123900),
349
+                'is_recurrence_exception' => $query->createNamedParameter(0),
350
+                'event_hash' => $query->createNamedParameter('asd123'),
351
+                'alarm_hash' => $query->createNamedParameter('asd567'),
352
+                'type' => $query->createNamedParameter('EMAIL'),
353
+                'is_relative' => $query->createNamedParameter(1),
354
+                'notification_date' => $query->createNamedParameter(123499),
355
+                'is_repeat_based' => $query->createNamedParameter(0),
356
+            ])
357
+            ->executeStatement();
358
+
359
+        $query = self::$realDatabase->getQueryBuilder();
360
+        $query->insert('calendar_reminders')
361
+            ->values([
362
+                'calendar_id' => $query->createNamedParameter(99),
363
+                'object_id' => $query->createNamedParameter(10),
364
+                'uid' => $query->createNamedParameter('asd'),
365
+                'is_recurring' => $query->createNamedParameter(0),
366
+                'recurrence_id' => $query->createNamedParameter(123900),
367
+                'is_recurrence_exception' => $query->createNamedParameter(0),
368
+                'event_hash' => $query->createNamedParameter('asd123'),
369
+                'alarm_hash' => $query->createNamedParameter('asd567'),
370
+                'type' => $query->createNamedParameter('DISPLAY'),
371
+                'is_relative' => $query->createNamedParameter(1),
372
+                'notification_date' => $query->createNamedParameter(123600),
373
+                'is_repeat_based' => $query->createNamedParameter(0),
374
+            ])
375
+            ->executeStatement();
376
+    }
377 377
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 
91 91
 		$this->assertCount(4, $rows);
92 92
 
93
-		$this->reminderBackend->removeReminder((int)$rows[3]['id']);
93
+		$this->reminderBackend->removeReminder((int) $rows[3]['id']);
94 94
 
95 95
 		$query = self::$realDatabase->getQueryBuilder();
96 96
 		$rows = $query->select('*')
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
 			'principaluri' => 'principals/users/user001',
149 149
 		];
150 150
 
151
-		$this->assertEqualsCanonicalizing([$rows[0],$rows[1]], [$expected1,$expected2]);
151
+		$this->assertEqualsCanonicalizing([$rows[0], $rows[1]], [$expected1, $expected2]);
152 152
 	}
153 153
 
154 154
 	public function testGetAllScheduledRemindersForEvent(): void {
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
 
238 238
 		$this->assertEquals($rows[3]['notification_date'], 123600);
239 239
 
240
-		$reminderId = (int)$rows[3]['id'];
240
+		$reminderId = (int) $rows[3]['id'];
241 241
 		$newNotificationDate = 123700;
242 242
 
243 243
 		$this->reminderBackend->updateReminder($reminderId, $newNotificationDate);
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
 			->executeQuery()
250 250
 			->fetch();
251 251
 
252
-		$this->assertEquals((int)$row['notification_date'], 123700);
252
+		$this->assertEquals((int) $row['notification_date'], 123700);
253 253
 	}
254 254
 
255 255
 
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php 2 patches
Indentation   +541 added lines, -541 removed lines patch added patch discarded remove patch
@@ -26,18 +26,18 @@  discard block
 block discarded – undo
26 26
 use Test\TestCase;
27 27
 
28 28
 class ReminderServiceTest extends TestCase {
29
-	private Backend&MockObject $backend;
30
-	private NotificationProviderManager&MockObject $notificationProviderManager;
31
-	private IUserManager&MockObject $userManager;
32
-	private IGroupManager&MockObject $groupManager;
33
-	private CalDavBackend&MockObject $caldavBackend;
34
-	private ITimeFactory&MockObject $timeFactory;
35
-	private IConfig&MockObject $config;
36
-	private LoggerInterface&MockObject $logger;
37
-	private Principal&MockObject $principalConnector;
38
-	private ReminderService $reminderService;
39
-
40
-	public const CALENDAR_DATA = <<<EOD
29
+    private Backend&MockObject $backend;
30
+    private NotificationProviderManager&MockObject $notificationProviderManager;
31
+    private IUserManager&MockObject $userManager;
32
+    private IGroupManager&MockObject $groupManager;
33
+    private CalDavBackend&MockObject $caldavBackend;
34
+    private ITimeFactory&MockObject $timeFactory;
35
+    private IConfig&MockObject $config;
36
+    private LoggerInterface&MockObject $logger;
37
+    private Principal&MockObject $principalConnector;
38
+    private ReminderService $reminderService;
39
+
40
+    public const CALENDAR_DATA = <<<EOD
41 41
 BEGIN:VCALENDAR
42 42
 PRODID:-//Nextcloud calendar v1.6.4
43 43
 BEGIN:VEVENT
@@ -62,7 +62,7 @@  discard block
 block discarded – undo
62 62
 END:VCALENDAR
63 63
 EOD;
64 64
 
65
-	public const CALENDAR_DATA_REPEAT = <<<EOD
65
+    public const CALENDAR_DATA_REPEAT = <<<EOD
66 66
 BEGIN:VCALENDAR
67 67
 PRODID:-//Nextcloud calendar v1.6.4
68 68
 BEGIN:VEVENT
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
 END:VCALENDAR
86 86
 EOD;
87 87
 
88
-	public const CALENDAR_DATA_RECURRING = <<<EOD
88
+    public const CALENDAR_DATA_RECURRING = <<<EOD
89 89
 BEGIN:VCALENDAR
90 90
 PRODID:-//Nextcloud calendar v1.6.4
91 91
 BEGIN:VEVENT
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 END:VCALENDAR
112 112
 EOD;
113 113
 
114
-	public const CALENDAR_DATA_RECURRING_REPEAT = <<<EOD
114
+    public const CALENDAR_DATA_RECURRING_REPEAT = <<<EOD
115 115
 BEGIN:VCALENDAR
116 116
 PRODID:-//Nextcloud calendar v1.6.4
117 117
 BEGIN:VEVENT
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 END:VCALENDAR
140 140
 EOD;
141 141
 
142
-	public const CALENDAR_DATA_NO_ALARM = <<<EOD
142
+    public const CALENDAR_DATA_NO_ALARM = <<<EOD
143 143
 BEGIN:VCALENDAR
144 144
 PRODID:-//Nextcloud calendar v1.6.4
145 145
 BEGIN:VEVENT
@@ -156,7 +156,7 @@  discard block
 block discarded – undo
156 156
 END:VCALENDAR
157 157
 EOD;
158 158
 
159
-	private const CALENDAR_DATA_ONE_TIME = <<<EOD
159
+    private const CALENDAR_DATA_ONE_TIME = <<<EOD
160 160
 BEGIN:VCALENDAR
161 161
 PRODID:-//IDN nextcloud.com//Calendar app 4.3.0-alpha.0//EN
162 162
 CALSCALE:GREGORIAN
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 END:VCALENDAR
197 197
 EOD;
198 198
 
199
-	private const CALENDAR_DATA_ALL_DAY = <<<EOD
199
+    private const CALENDAR_DATA_ALL_DAY = <<<EOD
200 200
 BEGIN:VCALENDAR
201 201
 PRODID:-//IDN nextcloud.com//Calendar app 4.3.0-alpha.0//EN
202 202
 CALSCALE:GREGORIAN
@@ -219,7 +219,7 @@  discard block
 block discarded – undo
219 219
 END:VCALENDAR
220 220
 EOD;
221 221
 
222
-	private const PAGO_PAGO_VTIMEZONE_ICS = <<<ICS
222
+    private const PAGO_PAGO_VTIMEZONE_ICS = <<<ICS
223 223
 BEGIN:VCALENDAR
224 224
 BEGIN:VTIMEZONE
225 225
 TZID:Pacific/Pago_Pago
@@ -233,82 +233,82 @@  discard block
 block discarded – undo
233 233
 END:VCALENDAR
234 234
 ICS;
235 235
 
236
-	private ?string $oldTimezone;
237
-
238
-	protected function setUp(): void {
239
-		parent::setUp();
240
-
241
-		$this->backend = $this->createMock(Backend::class);
242
-		$this->notificationProviderManager = $this->createMock(NotificationProviderManager::class);
243
-		$this->userManager = $this->createMock(IUserManager::class);
244
-		$this->groupManager = $this->createMock(IGroupManager::class);
245
-		$this->caldavBackend = $this->createMock(CalDavBackend::class);
246
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
247
-		$this->config = $this->createMock(IConfig::class);
248
-		$this->logger = $this->createMock(LoggerInterface::class);
249
-		$this->principalConnector = $this->createMock(Principal::class);
250
-
251
-		$this->caldavBackend->method('getShares')->willReturn([]);
252
-
253
-		$this->reminderService = new ReminderService(
254
-			$this->backend,
255
-			$this->notificationProviderManager,
256
-			$this->userManager,
257
-			$this->groupManager,
258
-			$this->caldavBackend,
259
-			$this->timeFactory,
260
-			$this->config,
261
-			$this->logger,
262
-			$this->principalConnector,
263
-		);
264
-	}
265
-
266
-	public function testOnCalendarObjectDelete():void {
267
-		$this->backend->expects($this->once())
268
-			->method('cleanRemindersForEvent')
269
-			->with(44);
270
-
271
-		$objectData = [
272
-			'id' => '44',
273
-			'component' => 'vevent',
274
-		];
275
-
276
-		$this->reminderService->onCalendarObjectDelete($objectData);
277
-	}
278
-
279
-	public function testOnCalendarObjectCreateSingleEntry():void {
280
-		$objectData = [
281
-			'calendardata' => self::CALENDAR_DATA,
282
-			'id' => '42',
283
-			'calendarid' => '1337',
284
-			'component' => 'vevent',
285
-		];
286
-
287
-		$calls = [
288
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false],
289
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false]
290
-		];
291
-		$this->backend->expects($this->exactly(count($calls)))
292
-			->method('insertReminder')
293
-			->willReturnCallback(function () use (&$calls) {
294
-				$expected = array_shift($calls);
295
-				$this->assertEquals($expected, func_get_args());
296
-				return 1;
297
-			});
298
-
299
-		$this->timeFactory->expects($this->once())
300
-			->method('getDateTime')
301
-			->with()
302
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
303
-
304
-		$this->reminderService->onCalendarObjectCreate($objectData);
305
-	}
306
-
307
-	/**
308
-	 * RFC5545 says DTSTART is REQUIRED, but we have seen event without the prop
309
-	 */
310
-	public function testOnCalendarObjectCreateNoDtstart(): void {
311
-		$calendarData = <<<EOD
236
+    private ?string $oldTimezone;
237
+
238
+    protected function setUp(): void {
239
+        parent::setUp();
240
+
241
+        $this->backend = $this->createMock(Backend::class);
242
+        $this->notificationProviderManager = $this->createMock(NotificationProviderManager::class);
243
+        $this->userManager = $this->createMock(IUserManager::class);
244
+        $this->groupManager = $this->createMock(IGroupManager::class);
245
+        $this->caldavBackend = $this->createMock(CalDavBackend::class);
246
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
247
+        $this->config = $this->createMock(IConfig::class);
248
+        $this->logger = $this->createMock(LoggerInterface::class);
249
+        $this->principalConnector = $this->createMock(Principal::class);
250
+
251
+        $this->caldavBackend->method('getShares')->willReturn([]);
252
+
253
+        $this->reminderService = new ReminderService(
254
+            $this->backend,
255
+            $this->notificationProviderManager,
256
+            $this->userManager,
257
+            $this->groupManager,
258
+            $this->caldavBackend,
259
+            $this->timeFactory,
260
+            $this->config,
261
+            $this->logger,
262
+            $this->principalConnector,
263
+        );
264
+    }
265
+
266
+    public function testOnCalendarObjectDelete():void {
267
+        $this->backend->expects($this->once())
268
+            ->method('cleanRemindersForEvent')
269
+            ->with(44);
270
+
271
+        $objectData = [
272
+            'id' => '44',
273
+            'component' => 'vevent',
274
+        ];
275
+
276
+        $this->reminderService->onCalendarObjectDelete($objectData);
277
+    }
278
+
279
+    public function testOnCalendarObjectCreateSingleEntry():void {
280
+        $objectData = [
281
+            'calendardata' => self::CALENDAR_DATA,
282
+            'id' => '42',
283
+            'calendarid' => '1337',
284
+            'component' => 'vevent',
285
+        ];
286
+
287
+        $calls = [
288
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false],
289
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false]
290
+        ];
291
+        $this->backend->expects($this->exactly(count($calls)))
292
+            ->method('insertReminder')
293
+            ->willReturnCallback(function () use (&$calls) {
294
+                $expected = array_shift($calls);
295
+                $this->assertEquals($expected, func_get_args());
296
+                return 1;
297
+            });
298
+
299
+        $this->timeFactory->expects($this->once())
300
+            ->method('getDateTime')
301
+            ->with()
302
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
303
+
304
+        $this->reminderService->onCalendarObjectCreate($objectData);
305
+    }
306
+
307
+    /**
308
+     * RFC5545 says DTSTART is REQUIRED, but we have seen event without the prop
309
+     */
310
+    public function testOnCalendarObjectCreateNoDtstart(): void {
311
+        $calendarData = <<<EOD
312 312
 BEGIN:VCALENDAR
313 313
 PRODID:-//Nextcloud calendar v1.6.4
314 314
 BEGIN:VEVENT
@@ -328,450 +328,450 @@  discard block
 block discarded – undo
328 328
 END:VEVENT
329 329
 END:VCALENDAR
330 330
 EOD;
331
-		$objectData = [
332
-			'calendardata' => $calendarData,
333
-			'id' => '42',
334
-			'calendarid' => '1337',
335
-			'component' => 'vevent',
336
-		];
337
-
338
-		$this->backend->expects($this->never())
339
-			->method('insertReminder');
340
-
341
-		$this->reminderService->onCalendarObjectCreate($objectData);
342
-	}
343
-
344
-	public function testOnCalendarObjectCreateSingleEntryWithRepeat(): void {
345
-		$objectData = [
346
-			'calendardata' => self::CALENDAR_DATA_REPEAT,
347
-			'id' => '42',
348
-			'calendarid' => '1337',
349
-			'component' => 'vevent',
350
-		];
351
-
352
-		$calls = [
353
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429500, false],
354
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429620, true],
355
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429740, true],
356
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429860, true],
357
-			[1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429980, true]
358
-		];
359
-		$this->backend->expects($this->exactly(count($calls)))
360
-			->method('insertReminder')
361
-			->willReturnCallback(function () use (&$calls) {
362
-				$expected = array_shift($calls);
363
-				$this->assertEquals($expected, func_get_args());
364
-				return 1;
365
-			});
366
-
367
-		$this->timeFactory->expects($this->once())
368
-			->method('getDateTime')
369
-			->with()
370
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
371
-
372
-		$this->reminderService->onCalendarObjectCreate($objectData);
373
-	}
374
-
375
-	public function testOnCalendarObjectCreateRecurringEntry(): void {
376
-		$objectData = [
377
-			'calendardata' => self::CALENDAR_DATA_RECURRING,
378
-			'id' => '42',
379
-			'calendarid' => '1337',
380
-			'component' => 'vevent',
381
-		];
382
-
383
-		$calls = [
384
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1467243900, false],
385
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false]
386
-		];
387
-		$this->backend->expects($this->exactly(count($calls)))
388
-			->method('insertReminder')
389
-			->willReturnCallback(function () use (&$calls) {
390
-				$expected = array_shift($calls);
391
-				$this->assertEquals($expected, func_get_args());
392
-				return 1;
393
-			});
394
-
395
-		$this->timeFactory->expects($this->once())
396
-			->method('getDateTime')
397
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-29T00:00:00+00:00'));
398
-
399
-		$this->reminderService->onCalendarObjectCreate($objectData);
400
-	}
401
-
402
-	public function testOnCalendarObjectCreateEmpty():void {
403
-		$objectData = [
404
-			'calendardata' => self::CALENDAR_DATA_NO_ALARM,
405
-			'id' => '42',
406
-			'calendarid' => '1337',
407
-			'component' => 'vevent',
408
-		];
409
-
410
-		$this->backend->expects($this->never())
411
-			->method('insertReminder');
412
-
413
-		$this->reminderService->onCalendarObjectCreate($objectData);
414
-	}
415
-
416
-	public function testOnCalendarObjectCreateAllDayWithNullTimezone(): void {
417
-		$objectData = [
418
-			'calendardata' => self::CALENDAR_DATA_ALL_DAY,
419
-			'id' => '42',
420
-			'calendarid' => '1337',
421
-			'component' => 'vevent',
422
-		];
423
-		$this->timeFactory->expects($this->once())
424
-			->method('getDateTime')
425
-			->with()
426
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
427
-		$this->caldavBackend->expects(self::once())
428
-			->method('getCalendarById')
429
-			->with(1337)
430
-			->willReturn([
431
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
432
-			]);
433
-
434
-		// One hour before midnight relative to the server's time
435
-		$expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00'))->getTimestamp();
436
-		$this->backend->expects(self::once())
437
-			->method('insertReminder')
438
-			->with(1337, 42, self::anything(), false, 1675468800, false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
439
-
440
-		$this->reminderService->onCalendarObjectCreate($objectData);
441
-	}
442
-
443
-	public function testOnCalendarObjectCreateAllDayWithBlankTimezone(): void {
444
-		$objectData = [
445
-			'calendardata' => self::CALENDAR_DATA_ALL_DAY,
446
-			'id' => '42',
447
-			'calendarid' => '1337',
448
-			'component' => 'vevent',
449
-		];
450
-		$this->timeFactory->expects($this->once())
451
-			->method('getDateTime')
452
-			->with()
453
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
454
-		$this->caldavBackend->expects(self::once())
455
-			->method('getCalendarById')
456
-			->with(1337)
457
-			->willReturn([
458
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => '',
459
-			]);
460
-
461
-		// One hour before midnight relative to the server's time
462
-		$expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00'))->getTimestamp();
463
-		$this->backend->expects(self::once())
464
-			->method('insertReminder')
465
-			->with(1337, 42, self::anything(), false, 1675468800, false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
466
-
467
-		$this->reminderService->onCalendarObjectCreate($objectData);
468
-	}
469
-
470
-	public function testOnCalendarObjectCreateAllDayWithTimezone(): void {
471
-		$objectData = [
472
-			'calendardata' => self::CALENDAR_DATA_ALL_DAY,
473
-			'id' => '42',
474
-			'calendarid' => '1337',
475
-			'component' => 'vevent',
476
-		];
477
-		$this->timeFactory->expects($this->once())
478
-			->method('getDateTime')
479
-			->with()
480
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
481
-		$this->caldavBackend->expects(self::once())
482
-			->method('getCalendarById')
483
-			->with(1337)
484
-			->willReturn([
485
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => self::PAGO_PAGO_VTIMEZONE_ICS,
486
-			]);
487
-
488
-		// One hour before midnight relative to the timezone
489
-		$expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00', new DateTimeZone('Pacific/Pago_Pago')))->getTimestamp();
490
-		$this->backend->expects(self::once())
491
-			->method('insertReminder')
492
-			->with(1337, 42, 'a163a056-ba26-44a2-8080-955f19611a8f', false, self::anything(), false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
493
-
494
-		$this->reminderService->onCalendarObjectCreate($objectData);
495
-	}
496
-
497
-	public function testOnCalendarObjectCreateRecurringEntryWithRepeat():void {
498
-		$objectData = [
499
-			'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT,
500
-			'id' => '42',
501
-			'calendarid' => '1337',
502
-			'component' => 'vevent',
503
-		];
504
-		$this->caldavBackend->expects(self::once())
505
-			->method('getCalendarById')
506
-			->with(1337)
507
-			->willReturn([
508
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
509
-			]);
510
-
511
-		$calls = [
512
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467243900, false],
513
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244020, true],
514
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244140, true],
515
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244260, true],
516
-			[1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244380, true],
517
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false]
518
-		];
519
-		$this->backend->expects($this->exactly(count($calls)))
520
-			->method('insertReminder')
521
-			->willReturnCallback(function () use (&$calls) {
522
-				$expected = array_shift($calls);
523
-				$this->assertEquals($expected, func_get_args());
524
-				return 1;
525
-			});
526
-
527
-		$this->timeFactory->expects($this->once())
528
-			->method('getDateTime')
529
-			->with()
530
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-29T00:00:00+00:00'));
531
-
532
-		$this->reminderService->onCalendarObjectCreate($objectData);
533
-	}
534
-
535
-	public function testOnCalendarObjectCreateWithEventTimezoneAndCalendarTimezone():void {
536
-		$objectData = [
537
-			'calendardata' => self::CALENDAR_DATA_ONE_TIME,
538
-			'id' => '42',
539
-			'calendarid' => '1337',
540
-			'component' => 'vevent',
541
-		];
542
-		$this->caldavBackend->expects(self::once())
543
-			->method('getCalendarById')
544
-			->with(1337)
545
-			->willReturn([
546
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => self::PAGO_PAGO_VTIMEZONE_ICS,
547
-			]);
548
-		$expectedReminderTimstamp = (new DateTime('2023-02-04T08:00:00', new DateTimeZone('Europe/Vienna')))->getTimestamp();
549
-		$this->backend->expects(self::once())
550
-			->method('insertReminder')
551
-			->with(1337, 42, self::anything(), false, self::anything(), false, self::anything(), self::anything(), self::anything(), true, $expectedReminderTimstamp, false)
552
-			->willReturn(1);
553
-		$this->caldavBackend->expects(self::once())
554
-			->method('getCalendarById')
555
-			->with(1337)
556
-			->willReturn([
557
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
558
-			]);
559
-		$this->timeFactory->expects($this->once())
560
-			->method('getDateTime')
561
-			->with()
562
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
563
-		;
564
-
565
-		$this->reminderService->onCalendarObjectCreate($objectData);
566
-	}
567
-
568
-	public function testProcessReminders():void {
569
-		$this->backend->expects($this->once())
570
-			->method('getRemindersToProcess')
571
-			->with()
572
-			->willReturn([
573
-				[
574
-					'id' => 1,
575
-					'calendar_id' => 1337,
576
-					'object_id' => 42,
577
-					'uid' => 'wej2z68l9h',
578
-					'is_recurring' => false,
579
-					'recurrence_id' => 1465430400,
580
-					'is_recurrence_exception' => false,
581
-					'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
582
-					'alarm_hash' => 'de919af7429d3b5c11e8b9d289b411a6',
583
-					'type' => 'EMAIL',
584
-					'is_relative' => true,
585
-					'notification_date' => 1465429500,
586
-					'is_repeat_based' => false,
587
-					'calendardata' => self::CALENDAR_DATA,
588
-					'displayname' => 'Displayname 123',
589
-					'principaluri' => 'principals/users/user001',
590
-				],
591
-				[
592
-					'id' => 2,
593
-					'calendar_id' => 1337,
594
-					'object_id' => 42,
595
-					'uid' => 'wej2z68l9h',
596
-					'is_recurring' => false,
597
-					'recurrence_id' => 1465430400,
598
-					'is_recurrence_exception' => false,
599
-					'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
600
-					'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602',
601
-					'type' => 'EMAIL',
602
-					'is_relative' => true,
603
-					'notification_date' => 1465429740,
604
-					'is_repeat_based' => true,
605
-					'calendardata' => self::CALENDAR_DATA_REPEAT,
606
-					'displayname' => 'Displayname 123',
607
-					'principaluri' => 'principals/users/user001',
608
-				],
609
-				[
610
-					'id' => 3,
611
-					'calendar_id' => 1337,
612
-					'object_id' => 42,
613
-					'uid' => 'wej2z68l9h',
614
-					'is_recurring' => false,
615
-					'recurrence_id' => 1465430400,
616
-					'is_recurrence_exception' => false,
617
-					'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
618
-					'alarm_hash' => '35b3eae8e792aa2209f0b4e1a302f105',
619
-					'type' => 'DISPLAY',
620
-					'is_relative' => false,
621
-					'notification_date' => 1465344000,
622
-					'is_repeat_based' => false,
623
-					'calendardata' => self::CALENDAR_DATA,
624
-					'displayname' => 'Displayname 123',
625
-					'principaluri' => 'principals/users/user001',
626
-				],
627
-				[
628
-					'id' => 4,
629
-					'calendar_id' => 1337,
630
-					'object_id' => 42,
631
-					'uid' => 'wej2z68l9h',
632
-					'is_recurring' => true,
633
-					'recurrence_id' => 1467244800,
634
-					'is_recurrence_exception' => false,
635
-					'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20',
636
-					'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602',
637
-					'type' => 'EMAIL',
638
-					'is_relative' => true,
639
-					'notification_date' => 1467243900,
640
-					'is_repeat_based' => false,
641
-					'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT,
642
-					'displayname' => 'Displayname 123',
643
-					'principaluri' => 'principals/users/user001',
644
-				],
645
-				[
646
-					'id' => 5,
647
-					'calendar_id' => 1337,
648
-					'object_id' => 42,
649
-					'uid' => 'wej2z68l9h',
650
-					'is_recurring' => true,
651
-					'recurrence_id' => 1467849600,
652
-					'is_recurrence_exception' => false,
653
-					'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20',
654
-					'alarm_hash' => '8996992118817f9f311ac5cc56d1cc97',
655
-					'type' => 'EMAIL',
656
-					'is_relative' => true,
657
-					'notification_date' => 1467158400,
658
-					'is_repeat_based' => false,
659
-					'calendardata' => self::CALENDAR_DATA_RECURRING,
660
-					'displayname' => 'Displayname 123',
661
-					'principaluri' => 'principals/users/user001',
662
-				]
663
-			]);
664
-
665
-		$this->notificationProviderManager->expects($this->exactly(5))
666
-			->method('hasProvider')
667
-			->willReturnMap([
668
-				['EMAIL', true],
669
-				['DISPLAY', true],
670
-			]);
671
-
672
-		$provider1 = $this->createMock(INotificationProvider::class);
673
-		$provider2 = $this->createMock(INotificationProvider::class);
674
-		$provider3 = $this->createMock(INotificationProvider::class);
675
-		$provider4 = $this->createMock(INotificationProvider::class);
676
-		$provider5 = $this->createMock(INotificationProvider::class);
677
-
678
-		$getProviderCalls = [
679
-			['EMAIL', $provider1],
680
-			['EMAIL', $provider2],
681
-			['DISPLAY', $provider3],
682
-			['EMAIL', $provider4],
683
-			['EMAIL', $provider5],
684
-		];
685
-		$this->notificationProviderManager->expects($this->exactly(count($getProviderCalls)))
686
-			->method('getProvider')
687
-			->willReturnCallback(function () use (&$getProviderCalls) {
688
-				$expected = array_shift($getProviderCalls);
689
-				$return = array_pop($expected);
690
-				$this->assertEquals($expected, func_get_args());
691
-				return $return;
692
-			});
693
-
694
-		$user = $this->createMock(IUser::class);
695
-		$this->userManager->expects($this->exactly(5))
696
-			->method('get')
697
-			->with('user001')
698
-			->willReturn($user);
699
-
700
-		$provider1->expects($this->once())
701
-			->method('send')
702
-			->with($this->callback(function ($vevent) {
703
-				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
704
-					return false;
705
-				}
706
-				return true;
707
-			}, 'Displayname 123', $user));
708
-		$provider2->expects($this->once())
709
-			->method('send')
710
-			->with($this->callback(function ($vevent) {
711
-				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
712
-					return false;
713
-				}
714
-				return true;
715
-			}, 'Displayname 123', $user));
716
-		$provider3->expects($this->once())
717
-			->method('send')
718
-			->with($this->callback(function ($vevent) {
719
-				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
720
-					return false;
721
-				}
722
-				return true;
723
-			}, 'Displayname 123', $user));
724
-		$provider4->expects($this->once())
725
-			->method('send')
726
-			->with($this->callback(function ($vevent) {
727
-				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-30T00:00:00+00:00') {
728
-					return false;
729
-				}
730
-				return true;
731
-			}, 'Displayname 123', $user));
732
-		$provider5->expects($this->once())
733
-			->method('send')
734
-			->with($this->callback(function ($vevent) {
735
-				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-07-07T00:00:00+00:00') {
736
-					return false;
737
-				}
738
-				return true;
739
-			}, 'Displayname 123', $user));
740
-
741
-		$removeReminderCalls = [
742
-			[1],
743
-			[2],
744
-			[3],
745
-			[4],
746
-			[5],
747
-		];
748
-		$this->backend->expects($this->exactly(5))
749
-			->method('removeReminder')
750
-			->willReturnCallback(function () use (&$removeReminderCalls) {
751
-				$expected = array_shift($removeReminderCalls);
752
-				$this->assertEquals($expected, func_get_args());
753
-			});
754
-
755
-
756
-		$insertReminderCalls = [
757
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848700, false],
758
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848820, true],
759
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848940, true],
760
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849060, true],
761
-			[1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849180, true],
762
-			[1337, 42, 'wej2z68l9h', true, 1468454400, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467763200, false],
763
-		];
764
-		$this->backend->expects($this->exactly(count($insertReminderCalls)))
765
-			->method('insertReminder')
766
-			->willReturnCallback(function () use (&$insertReminderCalls) {
767
-				$expected = array_shift($insertReminderCalls);
768
-				$this->assertEquals($expected, func_get_args());
769
-				return 99;
770
-			});
771
-
772
-		$this->timeFactory->method('getDateTime')
773
-			->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
774
-
775
-		$this->reminderService->processReminders();
776
-	}
331
+        $objectData = [
332
+            'calendardata' => $calendarData,
333
+            'id' => '42',
334
+            'calendarid' => '1337',
335
+            'component' => 'vevent',
336
+        ];
337
+
338
+        $this->backend->expects($this->never())
339
+            ->method('insertReminder');
340
+
341
+        $this->reminderService->onCalendarObjectCreate($objectData);
342
+    }
343
+
344
+    public function testOnCalendarObjectCreateSingleEntryWithRepeat(): void {
345
+        $objectData = [
346
+            'calendardata' => self::CALENDAR_DATA_REPEAT,
347
+            'id' => '42',
348
+            'calendarid' => '1337',
349
+            'component' => 'vevent',
350
+        ];
351
+
352
+        $calls = [
353
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429500, false],
354
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429620, true],
355
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429740, true],
356
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429860, true],
357
+            [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429980, true]
358
+        ];
359
+        $this->backend->expects($this->exactly(count($calls)))
360
+            ->method('insertReminder')
361
+            ->willReturnCallback(function () use (&$calls) {
362
+                $expected = array_shift($calls);
363
+                $this->assertEquals($expected, func_get_args());
364
+                return 1;
365
+            });
366
+
367
+        $this->timeFactory->expects($this->once())
368
+            ->method('getDateTime')
369
+            ->with()
370
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
371
+
372
+        $this->reminderService->onCalendarObjectCreate($objectData);
373
+    }
374
+
375
+    public function testOnCalendarObjectCreateRecurringEntry(): void {
376
+        $objectData = [
377
+            'calendardata' => self::CALENDAR_DATA_RECURRING,
378
+            'id' => '42',
379
+            'calendarid' => '1337',
380
+            'component' => 'vevent',
381
+        ];
382
+
383
+        $calls = [
384
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1467243900, false],
385
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false]
386
+        ];
387
+        $this->backend->expects($this->exactly(count($calls)))
388
+            ->method('insertReminder')
389
+            ->willReturnCallback(function () use (&$calls) {
390
+                $expected = array_shift($calls);
391
+                $this->assertEquals($expected, func_get_args());
392
+                return 1;
393
+            });
394
+
395
+        $this->timeFactory->expects($this->once())
396
+            ->method('getDateTime')
397
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-29T00:00:00+00:00'));
398
+
399
+        $this->reminderService->onCalendarObjectCreate($objectData);
400
+    }
401
+
402
+    public function testOnCalendarObjectCreateEmpty():void {
403
+        $objectData = [
404
+            'calendardata' => self::CALENDAR_DATA_NO_ALARM,
405
+            'id' => '42',
406
+            'calendarid' => '1337',
407
+            'component' => 'vevent',
408
+        ];
409
+
410
+        $this->backend->expects($this->never())
411
+            ->method('insertReminder');
412
+
413
+        $this->reminderService->onCalendarObjectCreate($objectData);
414
+    }
415
+
416
+    public function testOnCalendarObjectCreateAllDayWithNullTimezone(): void {
417
+        $objectData = [
418
+            'calendardata' => self::CALENDAR_DATA_ALL_DAY,
419
+            'id' => '42',
420
+            'calendarid' => '1337',
421
+            'component' => 'vevent',
422
+        ];
423
+        $this->timeFactory->expects($this->once())
424
+            ->method('getDateTime')
425
+            ->with()
426
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
427
+        $this->caldavBackend->expects(self::once())
428
+            ->method('getCalendarById')
429
+            ->with(1337)
430
+            ->willReturn([
431
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
432
+            ]);
433
+
434
+        // One hour before midnight relative to the server's time
435
+        $expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00'))->getTimestamp();
436
+        $this->backend->expects(self::once())
437
+            ->method('insertReminder')
438
+            ->with(1337, 42, self::anything(), false, 1675468800, false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
439
+
440
+        $this->reminderService->onCalendarObjectCreate($objectData);
441
+    }
442
+
443
+    public function testOnCalendarObjectCreateAllDayWithBlankTimezone(): void {
444
+        $objectData = [
445
+            'calendardata' => self::CALENDAR_DATA_ALL_DAY,
446
+            'id' => '42',
447
+            'calendarid' => '1337',
448
+            'component' => 'vevent',
449
+        ];
450
+        $this->timeFactory->expects($this->once())
451
+            ->method('getDateTime')
452
+            ->with()
453
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
454
+        $this->caldavBackend->expects(self::once())
455
+            ->method('getCalendarById')
456
+            ->with(1337)
457
+            ->willReturn([
458
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => '',
459
+            ]);
460
+
461
+        // One hour before midnight relative to the server's time
462
+        $expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00'))->getTimestamp();
463
+        $this->backend->expects(self::once())
464
+            ->method('insertReminder')
465
+            ->with(1337, 42, self::anything(), false, 1675468800, false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
466
+
467
+        $this->reminderService->onCalendarObjectCreate($objectData);
468
+    }
469
+
470
+    public function testOnCalendarObjectCreateAllDayWithTimezone(): void {
471
+        $objectData = [
472
+            'calendardata' => self::CALENDAR_DATA_ALL_DAY,
473
+            'id' => '42',
474
+            'calendarid' => '1337',
475
+            'component' => 'vevent',
476
+        ];
477
+        $this->timeFactory->expects($this->once())
478
+            ->method('getDateTime')
479
+            ->with()
480
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
481
+        $this->caldavBackend->expects(self::once())
482
+            ->method('getCalendarById')
483
+            ->with(1337)
484
+            ->willReturn([
485
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => self::PAGO_PAGO_VTIMEZONE_ICS,
486
+            ]);
487
+
488
+        // One hour before midnight relative to the timezone
489
+        $expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00', new DateTimeZone('Pacific/Pago_Pago')))->getTimestamp();
490
+        $this->backend->expects(self::once())
491
+            ->method('insertReminder')
492
+            ->with(1337, 42, 'a163a056-ba26-44a2-8080-955f19611a8f', false, self::anything(), false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false);
493
+
494
+        $this->reminderService->onCalendarObjectCreate($objectData);
495
+    }
496
+
497
+    public function testOnCalendarObjectCreateRecurringEntryWithRepeat():void {
498
+        $objectData = [
499
+            'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT,
500
+            'id' => '42',
501
+            'calendarid' => '1337',
502
+            'component' => 'vevent',
503
+        ];
504
+        $this->caldavBackend->expects(self::once())
505
+            ->method('getCalendarById')
506
+            ->with(1337)
507
+            ->willReturn([
508
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
509
+            ]);
510
+
511
+        $calls = [
512
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467243900, false],
513
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244020, true],
514
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244140, true],
515
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244260, true],
516
+            [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244380, true],
517
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false]
518
+        ];
519
+        $this->backend->expects($this->exactly(count($calls)))
520
+            ->method('insertReminder')
521
+            ->willReturnCallback(function () use (&$calls) {
522
+                $expected = array_shift($calls);
523
+                $this->assertEquals($expected, func_get_args());
524
+                return 1;
525
+            });
526
+
527
+        $this->timeFactory->expects($this->once())
528
+            ->method('getDateTime')
529
+            ->with()
530
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-29T00:00:00+00:00'));
531
+
532
+        $this->reminderService->onCalendarObjectCreate($objectData);
533
+    }
534
+
535
+    public function testOnCalendarObjectCreateWithEventTimezoneAndCalendarTimezone():void {
536
+        $objectData = [
537
+            'calendardata' => self::CALENDAR_DATA_ONE_TIME,
538
+            'id' => '42',
539
+            'calendarid' => '1337',
540
+            'component' => 'vevent',
541
+        ];
542
+        $this->caldavBackend->expects(self::once())
543
+            ->method('getCalendarById')
544
+            ->with(1337)
545
+            ->willReturn([
546
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => self::PAGO_PAGO_VTIMEZONE_ICS,
547
+            ]);
548
+        $expectedReminderTimstamp = (new DateTime('2023-02-04T08:00:00', new DateTimeZone('Europe/Vienna')))->getTimestamp();
549
+        $this->backend->expects(self::once())
550
+            ->method('insertReminder')
551
+            ->with(1337, 42, self::anything(), false, self::anything(), false, self::anything(), self::anything(), self::anything(), true, $expectedReminderTimstamp, false)
552
+            ->willReturn(1);
553
+        $this->caldavBackend->expects(self::once())
554
+            ->method('getCalendarById')
555
+            ->with(1337)
556
+            ->willReturn([
557
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null,
558
+            ]);
559
+        $this->timeFactory->expects($this->once())
560
+            ->method('getDateTime')
561
+            ->with()
562
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00'));
563
+        ;
564
+
565
+        $this->reminderService->onCalendarObjectCreate($objectData);
566
+    }
567
+
568
+    public function testProcessReminders():void {
569
+        $this->backend->expects($this->once())
570
+            ->method('getRemindersToProcess')
571
+            ->with()
572
+            ->willReturn([
573
+                [
574
+                    'id' => 1,
575
+                    'calendar_id' => 1337,
576
+                    'object_id' => 42,
577
+                    'uid' => 'wej2z68l9h',
578
+                    'is_recurring' => false,
579
+                    'recurrence_id' => 1465430400,
580
+                    'is_recurrence_exception' => false,
581
+                    'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
582
+                    'alarm_hash' => 'de919af7429d3b5c11e8b9d289b411a6',
583
+                    'type' => 'EMAIL',
584
+                    'is_relative' => true,
585
+                    'notification_date' => 1465429500,
586
+                    'is_repeat_based' => false,
587
+                    'calendardata' => self::CALENDAR_DATA,
588
+                    'displayname' => 'Displayname 123',
589
+                    'principaluri' => 'principals/users/user001',
590
+                ],
591
+                [
592
+                    'id' => 2,
593
+                    'calendar_id' => 1337,
594
+                    'object_id' => 42,
595
+                    'uid' => 'wej2z68l9h',
596
+                    'is_recurring' => false,
597
+                    'recurrence_id' => 1465430400,
598
+                    'is_recurrence_exception' => false,
599
+                    'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
600
+                    'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602',
601
+                    'type' => 'EMAIL',
602
+                    'is_relative' => true,
603
+                    'notification_date' => 1465429740,
604
+                    'is_repeat_based' => true,
605
+                    'calendardata' => self::CALENDAR_DATA_REPEAT,
606
+                    'displayname' => 'Displayname 123',
607
+                    'principaluri' => 'principals/users/user001',
608
+                ],
609
+                [
610
+                    'id' => 3,
611
+                    'calendar_id' => 1337,
612
+                    'object_id' => 42,
613
+                    'uid' => 'wej2z68l9h',
614
+                    'is_recurring' => false,
615
+                    'recurrence_id' => 1465430400,
616
+                    'is_recurrence_exception' => false,
617
+                    'event_hash' => '5c70531aab15c92b52518ae10a2f78a4',
618
+                    'alarm_hash' => '35b3eae8e792aa2209f0b4e1a302f105',
619
+                    'type' => 'DISPLAY',
620
+                    'is_relative' => false,
621
+                    'notification_date' => 1465344000,
622
+                    'is_repeat_based' => false,
623
+                    'calendardata' => self::CALENDAR_DATA,
624
+                    'displayname' => 'Displayname 123',
625
+                    'principaluri' => 'principals/users/user001',
626
+                ],
627
+                [
628
+                    'id' => 4,
629
+                    'calendar_id' => 1337,
630
+                    'object_id' => 42,
631
+                    'uid' => 'wej2z68l9h',
632
+                    'is_recurring' => true,
633
+                    'recurrence_id' => 1467244800,
634
+                    'is_recurrence_exception' => false,
635
+                    'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20',
636
+                    'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602',
637
+                    'type' => 'EMAIL',
638
+                    'is_relative' => true,
639
+                    'notification_date' => 1467243900,
640
+                    'is_repeat_based' => false,
641
+                    'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT,
642
+                    'displayname' => 'Displayname 123',
643
+                    'principaluri' => 'principals/users/user001',
644
+                ],
645
+                [
646
+                    'id' => 5,
647
+                    'calendar_id' => 1337,
648
+                    'object_id' => 42,
649
+                    'uid' => 'wej2z68l9h',
650
+                    'is_recurring' => true,
651
+                    'recurrence_id' => 1467849600,
652
+                    'is_recurrence_exception' => false,
653
+                    'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20',
654
+                    'alarm_hash' => '8996992118817f9f311ac5cc56d1cc97',
655
+                    'type' => 'EMAIL',
656
+                    'is_relative' => true,
657
+                    'notification_date' => 1467158400,
658
+                    'is_repeat_based' => false,
659
+                    'calendardata' => self::CALENDAR_DATA_RECURRING,
660
+                    'displayname' => 'Displayname 123',
661
+                    'principaluri' => 'principals/users/user001',
662
+                ]
663
+            ]);
664
+
665
+        $this->notificationProviderManager->expects($this->exactly(5))
666
+            ->method('hasProvider')
667
+            ->willReturnMap([
668
+                ['EMAIL', true],
669
+                ['DISPLAY', true],
670
+            ]);
671
+
672
+        $provider1 = $this->createMock(INotificationProvider::class);
673
+        $provider2 = $this->createMock(INotificationProvider::class);
674
+        $provider3 = $this->createMock(INotificationProvider::class);
675
+        $provider4 = $this->createMock(INotificationProvider::class);
676
+        $provider5 = $this->createMock(INotificationProvider::class);
677
+
678
+        $getProviderCalls = [
679
+            ['EMAIL', $provider1],
680
+            ['EMAIL', $provider2],
681
+            ['DISPLAY', $provider3],
682
+            ['EMAIL', $provider4],
683
+            ['EMAIL', $provider5],
684
+        ];
685
+        $this->notificationProviderManager->expects($this->exactly(count($getProviderCalls)))
686
+            ->method('getProvider')
687
+            ->willReturnCallback(function () use (&$getProviderCalls) {
688
+                $expected = array_shift($getProviderCalls);
689
+                $return = array_pop($expected);
690
+                $this->assertEquals($expected, func_get_args());
691
+                return $return;
692
+            });
693
+
694
+        $user = $this->createMock(IUser::class);
695
+        $this->userManager->expects($this->exactly(5))
696
+            ->method('get')
697
+            ->with('user001')
698
+            ->willReturn($user);
699
+
700
+        $provider1->expects($this->once())
701
+            ->method('send')
702
+            ->with($this->callback(function ($vevent) {
703
+                if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
704
+                    return false;
705
+                }
706
+                return true;
707
+            }, 'Displayname 123', $user));
708
+        $provider2->expects($this->once())
709
+            ->method('send')
710
+            ->with($this->callback(function ($vevent) {
711
+                if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
712
+                    return false;
713
+                }
714
+                return true;
715
+            }, 'Displayname 123', $user));
716
+        $provider3->expects($this->once())
717
+            ->method('send')
718
+            ->with($this->callback(function ($vevent) {
719
+                if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
720
+                    return false;
721
+                }
722
+                return true;
723
+            }, 'Displayname 123', $user));
724
+        $provider4->expects($this->once())
725
+            ->method('send')
726
+            ->with($this->callback(function ($vevent) {
727
+                if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-30T00:00:00+00:00') {
728
+                    return false;
729
+                }
730
+                return true;
731
+            }, 'Displayname 123', $user));
732
+        $provider5->expects($this->once())
733
+            ->method('send')
734
+            ->with($this->callback(function ($vevent) {
735
+                if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-07-07T00:00:00+00:00') {
736
+                    return false;
737
+                }
738
+                return true;
739
+            }, 'Displayname 123', $user));
740
+
741
+        $removeReminderCalls = [
742
+            [1],
743
+            [2],
744
+            [3],
745
+            [4],
746
+            [5],
747
+        ];
748
+        $this->backend->expects($this->exactly(5))
749
+            ->method('removeReminder')
750
+            ->willReturnCallback(function () use (&$removeReminderCalls) {
751
+                $expected = array_shift($removeReminderCalls);
752
+                $this->assertEquals($expected, func_get_args());
753
+            });
754
+
755
+
756
+        $insertReminderCalls = [
757
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848700, false],
758
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848820, true],
759
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848940, true],
760
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849060, true],
761
+            [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849180, true],
762
+            [1337, 42, 'wej2z68l9h', true, 1468454400, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467763200, false],
763
+        ];
764
+        $this->backend->expects($this->exactly(count($insertReminderCalls)))
765
+            ->method('insertReminder')
766
+            ->willReturnCallback(function () use (&$insertReminderCalls) {
767
+                $expected = array_shift($insertReminderCalls);
768
+                $this->assertEquals($expected, func_get_args());
769
+                return 99;
770
+            });
771
+
772
+        $this->timeFactory->method('getDateTime')
773
+            ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00'));
774
+
775
+        $this->reminderService->processReminders();
776
+    }
777 777
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
 		];
291 291
 		$this->backend->expects($this->exactly(count($calls)))
292 292
 			->method('insertReminder')
293
-			->willReturnCallback(function () use (&$calls) {
293
+			->willReturnCallback(function() use (&$calls) {
294 294
 				$expected = array_shift($calls);
295 295
 				$this->assertEquals($expected, func_get_args());
296 296
 				return 1;
@@ -358,7 +358,7 @@  discard block
 block discarded – undo
358 358
 		];
359 359
 		$this->backend->expects($this->exactly(count($calls)))
360 360
 			->method('insertReminder')
361
-			->willReturnCallback(function () use (&$calls) {
361
+			->willReturnCallback(function() use (&$calls) {
362 362
 				$expected = array_shift($calls);
363 363
 				$this->assertEquals($expected, func_get_args());
364 364
 				return 1;
@@ -386,7 +386,7 @@  discard block
 block discarded – undo
386 386
 		];
387 387
 		$this->backend->expects($this->exactly(count($calls)))
388 388
 			->method('insertReminder')
389
-			->willReturnCallback(function () use (&$calls) {
389
+			->willReturnCallback(function() use (&$calls) {
390 390
 				$expected = array_shift($calls);
391 391
 				$this->assertEquals($expected, func_get_args());
392 392
 				return 1;
@@ -518,7 +518,7 @@  discard block
 block discarded – undo
518 518
 		];
519 519
 		$this->backend->expects($this->exactly(count($calls)))
520 520
 			->method('insertReminder')
521
-			->willReturnCallback(function () use (&$calls) {
521
+			->willReturnCallback(function() use (&$calls) {
522 522
 				$expected = array_shift($calls);
523 523
 				$this->assertEquals($expected, func_get_args());
524 524
 				return 1;
@@ -684,7 +684,7 @@  discard block
 block discarded – undo
684 684
 		];
685 685
 		$this->notificationProviderManager->expects($this->exactly(count($getProviderCalls)))
686 686
 			->method('getProvider')
687
-			->willReturnCallback(function () use (&$getProviderCalls) {
687
+			->willReturnCallback(function() use (&$getProviderCalls) {
688 688
 				$expected = array_shift($getProviderCalls);
689 689
 				$return = array_pop($expected);
690 690
 				$this->assertEquals($expected, func_get_args());
@@ -699,7 +699,7 @@  discard block
 block discarded – undo
699 699
 
700 700
 		$provider1->expects($this->once())
701 701
 			->method('send')
702
-			->with($this->callback(function ($vevent) {
702
+			->with($this->callback(function($vevent) {
703 703
 				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
704 704
 					return false;
705 705
 				}
@@ -707,7 +707,7 @@  discard block
 block discarded – undo
707 707
 			}, 'Displayname 123', $user));
708 708
 		$provider2->expects($this->once())
709 709
 			->method('send')
710
-			->with($this->callback(function ($vevent) {
710
+			->with($this->callback(function($vevent) {
711 711
 				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
712 712
 					return false;
713 713
 				}
@@ -715,7 +715,7 @@  discard block
 block discarded – undo
715 715
 			}, 'Displayname 123', $user));
716 716
 		$provider3->expects($this->once())
717 717
 			->method('send')
718
-			->with($this->callback(function ($vevent) {
718
+			->with($this->callback(function($vevent) {
719 719
 				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') {
720 720
 					return false;
721 721
 				}
@@ -723,7 +723,7 @@  discard block
 block discarded – undo
723 723
 			}, 'Displayname 123', $user));
724 724
 		$provider4->expects($this->once())
725 725
 			->method('send')
726
-			->with($this->callback(function ($vevent) {
726
+			->with($this->callback(function($vevent) {
727 727
 				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-30T00:00:00+00:00') {
728 728
 					return false;
729 729
 				}
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
 			}, 'Displayname 123', $user));
732 732
 		$provider5->expects($this->once())
733 733
 			->method('send')
734
-			->with($this->callback(function ($vevent) {
734
+			->with($this->callback(function($vevent) {
735 735
 				if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-07-07T00:00:00+00:00') {
736 736
 					return false;
737 737
 				}
@@ -747,7 +747,7 @@  discard block
 block discarded – undo
747 747
 		];
748 748
 		$this->backend->expects($this->exactly(5))
749 749
 			->method('removeReminder')
750
-			->willReturnCallback(function () use (&$removeReminderCalls) {
750
+			->willReturnCallback(function() use (&$removeReminderCalls) {
751 751
 				$expected = array_shift($removeReminderCalls);
752 752
 				$this->assertEquals($expected, func_get_args());
753 753
 			});
@@ -763,7 +763,7 @@  discard block
 block discarded – undo
763 763
 		];
764 764
 		$this->backend->expects($this->exactly(count($insertReminderCalls)))
765 765
 			->method('insertReminder')
766
-			->willReturnCallback(function () use (&$insertReminderCalls) {
766
+			->willReturnCallback(function() use (&$insertReminderCalls) {
767 767
 				$expected = array_shift($insertReminderCalls);
768 768
 				$this->assertEquals($expected, func_get_args());
769 769
 				return 99;
Please login to merge, or discard this patch.