Completed
Push — master ( 453450...6f0537 )
by
unknown
37:16
created
apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php 2 patches
Indentation   +434 added lines, -434 removed lines patch added patch discarded remove patch
@@ -27,438 +27,438 @@
 block discarded – undo
27 27
 
28 28
 #[\PHPUnit\Framework\Attributes\Group('DB')]
29 29
 class CustomPropertiesBackendTest extends TestCase {
30
-	private const BASE_URI = '/remote.php/dav/';
31
-
32
-	private Server&MockObject $server;
33
-	private Tree&MockObject $tree;
34
-	private IDBConnection $dbConnection;
35
-	private IUser&MockObject $user;
36
-	private DefaultCalendarValidator&MockObject $defaultCalendarValidator;
37
-	private CustomPropertiesBackend $backend;
38
-	private PropertyMapper $propertyMapper;
39
-
40
-	protected function setUp(): void {
41
-		parent::setUp();
42
-
43
-		$this->server = $this->createMock(Server::class);
44
-		$this->server->method('getBaseUri')
45
-			->willReturn(self::BASE_URI);
46
-		$this->tree = $this->createMock(Tree::class);
47
-		$this->user = $this->createMock(IUser::class);
48
-		$this->user->method('getUID')
49
-			->with()
50
-			->willReturn('dummy_user_42');
51
-		$this->dbConnection = \OCP\Server::get(IDBConnection::class);
52
-		$this->propertyMapper = \OCP\Server::get(PropertyMapper::class);
53
-		$this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class);
54
-
55
-		$this->backend = new CustomPropertiesBackend(
56
-			$this->server,
57
-			$this->tree,
58
-			$this->dbConnection,
59
-			$this->user,
60
-			$this->propertyMapper,
61
-			$this->defaultCalendarValidator,
62
-		);
63
-	}
64
-
65
-	protected function tearDown(): void {
66
-		$query = $this->dbConnection->getQueryBuilder();
67
-		$query->delete('properties');
68
-		$query->executeStatement();
69
-
70
-		parent::tearDown();
71
-	}
72
-
73
-	private function formatPath(string $path): string {
74
-		if (strlen($path) > 250) {
75
-			return sha1($path);
76
-		} else {
77
-			return $path;
78
-		}
79
-	}
80
-
81
-	protected function insertProps(string $user, string $path, array $props): void {
82
-		foreach ($props as $name => $value) {
83
-			$this->insertProp($user, $path, $name, $value);
84
-		}
85
-	}
86
-
87
-	protected function insertProp(string $user, string $path, string $name, mixed $value): void {
88
-		$type = CustomPropertiesBackend::PROPERTY_TYPE_STRING;
89
-		if ($value instanceof Href) {
90
-			$value = $value->getHref();
91
-			$type = CustomPropertiesBackend::PROPERTY_TYPE_HREF;
92
-		}
93
-
94
-		$query = $this->dbConnection->getQueryBuilder();
95
-		$query->insert('properties')
96
-			->values([
97
-				'userid' => $query->createNamedParameter($user),
98
-				'propertypath' => $query->createNamedParameter($this->formatPath($path)),
99
-				'propertyname' => $query->createNamedParameter($name),
100
-				'propertyvalue' => $query->createNamedParameter($value),
101
-				'valuetype' => $query->createNamedParameter($type, IQueryBuilder::PARAM_INT)
102
-			]);
103
-		$query->executeStatement();
104
-	}
105
-
106
-	protected function getProps(string $user, string $path): array {
107
-		$query = $this->dbConnection->getQueryBuilder();
108
-		$query->select('propertyname', 'propertyvalue', 'valuetype')
109
-			->from('properties')
110
-			->where($query->expr()->eq('userid', $query->createNamedParameter($user)))
111
-			->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($this->formatPath($path))));
112
-
113
-		$result = $query->executeQuery();
114
-		$data = [];
115
-		while ($row = $result->fetchAssociative()) {
116
-			$value = $row['propertyvalue'];
117
-			if ((int)$row['valuetype'] === CustomPropertiesBackend::PROPERTY_TYPE_HREF) {
118
-				$value = new Href($value);
119
-			}
120
-			$data[$row['propertyname']] = $value;
121
-		}
122
-		$result->closeCursor();
123
-
124
-		return $data;
125
-	}
126
-
127
-	public function testPropFindNoDbCalls(): void {
128
-		$db = $this->createMock(IDBConnection::class);
129
-		$backend = new CustomPropertiesBackend(
130
-			$this->server,
131
-			$this->tree,
132
-			$db,
133
-			$this->user,
134
-			$this->propertyMapper,
135
-			$this->defaultCalendarValidator,
136
-		);
137
-
138
-		$propFind = $this->createMock(PropFind::class);
139
-		$propFind->expects($this->once())
140
-			->method('get404Properties')
141
-			->with()
142
-			->willReturn([
143
-				'{http://owncloud.org/ns}permissions',
144
-				'{http://owncloud.org/ns}downloadURL',
145
-				'{http://owncloud.org/ns}dDC',
146
-				'{http://owncloud.org/ns}size',
147
-			]);
148
-
149
-		$db->expects($this->never())
150
-			->method($this->anything());
151
-
152
-		$backend->propFind('foo_bar_path_1337_0', $propFind);
153
-	}
154
-
155
-	public function testPropFindCalendarCall(): void {
156
-		$propFind = $this->createMock(PropFind::class);
157
-		$propFind->method('get404Properties')
158
-			->with()
159
-			->willReturn([
160
-				'{DAV:}getcontentlength',
161
-				'{DAV:}getcontenttype',
162
-				'{DAV:}getetag',
163
-				'{abc}def',
164
-			]);
165
-
166
-		$propFind->method('getRequestedProperties')
167
-			->with()
168
-			->willReturn([
169
-				'{DAV:}getcontentlength',
170
-				'{DAV:}getcontenttype',
171
-				'{DAV:}getetag',
172
-				'{DAV:}displayname',
173
-				'{urn:ietf:params:xml:ns:caldav}calendar-description',
174
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone',
175
-				'{abc}def',
176
-			]);
177
-
178
-		$props = [
179
-			'{abc}def' => 'a',
180
-			'{DAV:}displayname' => 'b',
181
-			'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'c',
182
-			'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'd',
183
-		];
184
-
185
-		$this->insertProps('dummy_user_42', 'calendars/foo/bar_path_1337_0', $props);
186
-
187
-		$setProps = [];
188
-		$propFind->method('set')
189
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
190
-				$setProps[$name] = $value;
191
-			});
192
-
193
-		$this->backend->propFind('calendars/foo/bar_path_1337_0', $propFind);
194
-		$this->assertEquals($props, $setProps);
195
-	}
196
-
197
-	public function testPropFindPrincipalCall(): void {
198
-		$this->tree->method('getNodeForPath')
199
-			->willReturnCallback(function ($uri) {
200
-				$node = $this->createMock(Calendar::class);
201
-				$node->method('getOwner')
202
-					->willReturn('principals/users/dummy_user_42');
203
-				return $node;
204
-			});
205
-
206
-		$propFind = $this->createMock(PropFind::class);
207
-		$propFind->method('get404Properties')
208
-			->with()
209
-			->willReturn([
210
-				'{DAV:}getcontentlength',
211
-				'{DAV:}getcontenttype',
212
-				'{DAV:}getetag',
213
-				'{abc}def',
214
-			]);
215
-
216
-		$propFind->method('getRequestedProperties')
217
-			->with()
218
-			->willReturn([
219
-				'{DAV:}getcontentlength',
220
-				'{DAV:}getcontenttype',
221
-				'{DAV:}getetag',
222
-				'{abc}def',
223
-				'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL',
224
-			]);
225
-
226
-		$props = [
227
-			'{abc}def' => 'a',
228
-			'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/admin/personal'),
229
-		];
230
-		$this->insertProps('dummy_user_42', 'principals/users/dummy_user_42', $props);
231
-
232
-		$setProps = [];
233
-		$propFind->method('set')
234
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
235
-				$setProps[$name] = $value;
236
-			});
237
-
238
-		$this->backend->propFind('principals/users/dummy_user_42', $propFind);
239
-		$this->assertEquals($props, $setProps);
240
-	}
241
-
242
-	public static function propFindPrincipalScheduleDefaultCalendarProviderUrlProvider(): array {
243
-		// [ user, nodes, existingProps, requestedProps, returnedProps ]
244
-		return [
245
-			[ // Exists
246
-				'dummy_user_42',
247
-				['calendars/dummy_user_42/foo/' => Calendar::class],
248
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
249
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
250
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
251
-			],
252
-			[ // Doesn't exist
253
-				'dummy_user_42',
254
-				['calendars/dummy_user_42/foo/' => Calendar::class],
255
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/bar/')],
256
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
257
-				[],
258
-			],
259
-			[ // No privilege
260
-				'dummy_user_42',
261
-				['calendars/user2/baz/' => Calendar::class],
262
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/user2/baz/')],
263
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
264
-				[],
265
-			],
266
-			[ // Not a calendar
267
-				'dummy_user_42',
268
-				['foo/dummy_user_42/bar/' => IACL::class],
269
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/dummy_user_42/bar/')],
270
-				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
271
-				[],
272
-			],
273
-		];
274
-
275
-	}
276
-
277
-	#[\PHPUnit\Framework\Attributes\DataProvider('propFindPrincipalScheduleDefaultCalendarProviderUrlProvider')]
278
-	public function testPropFindPrincipalScheduleDefaultCalendarUrl(
279
-		string $user,
280
-		array $nodes,
281
-		array $existingProps,
282
-		array $requestedProps,
283
-		array $returnedProps,
284
-	): void {
285
-		$propFind = $this->createMock(PropFind::class);
286
-		$propFind->method('get404Properties')
287
-			->with()
288
-			->willReturn([
289
-				'{DAV:}getcontentlength',
290
-				'{DAV:}getcontenttype',
291
-				'{DAV:}getetag',
292
-			]);
293
-
294
-		$propFind->method('getRequestedProperties')
295
-			->with()
296
-			->willReturn(array_merge([
297
-				'{DAV:}getcontentlength',
298
-				'{DAV:}getcontenttype',
299
-				'{DAV:}getetag',
300
-				'{abc}def',
301
-			],
302
-				$requestedProps,
303
-			));
304
-
305
-		$this->server->method('calculateUri')
306
-			->willReturnCallback(function ($uri) {
307
-				if (!str_starts_with($uri, self::BASE_URI)) {
308
-					return trim(substr($uri, strlen(self::BASE_URI)), '/');
309
-				}
310
-				return null;
311
-			});
312
-		$this->tree->method('getNodeForPath')
313
-			->willReturnCallback(function ($uri) use ($nodes) {
314
-				if (str_starts_with($uri, 'principals/')) {
315
-					return $this->createMock(IPrincipal::class);
316
-				}
317
-				if (array_key_exists($uri, $nodes)) {
318
-					$owner = explode('/', $uri)[1];
319
-					$node = $this->createMock($nodes[$uri]);
320
-					$node->method('getOwner')
321
-						->willReturn("principals/users/$owner");
322
-					return $node;
323
-				}
324
-				throw new NotFound('Node not found');
325
-			});
326
-
327
-		$this->insertProps($user, "principals/users/$user", $existingProps);
328
-
329
-		$setProps = [];
330
-		$propFind->method('set')
331
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
332
-				$setProps[$name] = $value;
333
-			});
334
-
335
-		$this->backend->propFind("principals/users/$user", $propFind);
336
-		$this->assertEquals($returnedProps, $setProps);
337
-	}
338
-
339
-	#[\PHPUnit\Framework\Attributes\DataProvider('propPatchProvider')]
340
-	public function testPropPatch(string $path, array $existing, array $props, array $result): void {
341
-		$this->server->method('calculateUri')
342
-			->willReturnCallback(function ($uri) {
343
-				if (str_starts_with($uri, self::BASE_URI)) {
344
-					return trim(substr($uri, strlen(self::BASE_URI)), '/');
345
-				}
346
-				return null;
347
-			});
348
-		$this->tree->method('getNodeForPath')
349
-			->willReturnCallback(function ($uri) {
350
-				$node = $this->createMock(Calendar::class);
351
-				$node->method('getOwner')
352
-					->willReturn('principals/users/' . $this->user->getUID());
353
-				return $node;
354
-			});
355
-
356
-		$this->insertProps($this->user->getUID(), $path, $existing);
357
-		$propPatch = new PropPatch($props);
358
-
359
-		$this->backend->propPatch($path, $propPatch);
360
-		$propPatch->commit();
361
-
362
-		$storedProps = $this->getProps($this->user->getUID(), $path);
363
-		$this->assertEquals($result, $storedProps);
364
-	}
365
-
366
-	public static function propPatchProvider(): array {
367
-		$longPath = str_repeat('long_path', 100);
368
-		return [
369
-			['foo_bar_path_1337', [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
370
-			['foo_bar_path_1337', ['{DAV:}displayname' => 'foo'], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
371
-			['foo_bar_path_1337', ['{DAV:}displayname' => 'foo'], ['{DAV:}displayname' => null], []],
372
-			[$longPath, [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
373
-			['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
374
-			['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href(self::BASE_URI . 'foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
375
-		];
376
-	}
377
-
378
-	public function testPropPatchWithUnsuitableCalendar(): void {
379
-		$path = 'principals/users/' . $this->user->getUID();
380
-
381
-		$node = $this->createMock(Calendar::class);
382
-		$node->expects(self::once())
383
-			->method('getOwner')
384
-			->willReturn($path);
385
-
386
-		$this->defaultCalendarValidator->expects(self::once())
387
-			->method('validateScheduleDefaultCalendar')
388
-			->with($node)
389
-			->willThrowException(new \Sabre\DAV\Exception('Invalid calendar'));
390
-
391
-		$this->server->method('calculateUri')
392
-			->willReturnCallback(function ($uri) {
393
-				if (str_starts_with($uri, self::BASE_URI)) {
394
-					return trim(substr($uri, strlen(self::BASE_URI)), '/');
395
-				}
396
-				return null;
397
-			});
398
-		$this->tree->expects(self::once())
399
-			->method('getNodeForPath')
400
-			->with('foo/bar/')
401
-			->willReturn($node);
402
-
403
-		$storedProps = $this->getProps($this->user->getUID(), $path);
404
-		$this->assertEquals([], $storedProps);
405
-
406
-		$propPatch = new PropPatch([
407
-			'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/'),
408
-		]);
409
-		$this->backend->propPatch($path, $propPatch);
410
-		try {
411
-			$propPatch->commit();
412
-		} catch (\Throwable $e) {
413
-			$this->assertInstanceOf(\Sabre\DAV\Exception::class, $e);
414
-		}
415
-
416
-		$storedProps = $this->getProps($this->user->getUID(), $path);
417
-		$this->assertEquals([], $storedProps);
418
-	}
419
-
420
-	#[\PHPUnit\Framework\Attributes\DataProvider('deleteProvider')]
421
-	public function testDelete(string $path): void {
422
-		$this->insertProps('dummy_user_42', $path, ['foo' => 'bar']);
423
-		$this->backend->delete($path);
424
-		$this->assertEquals([], $this->getProps('dummy_user_42', $path));
425
-	}
426
-
427
-	public static function deleteProvider(): array {
428
-		return [
429
-			['foo_bar_path_1337'],
430
-			[str_repeat('long_path', 100)]
431
-		];
432
-	}
433
-
434
-	#[\PHPUnit\Framework\Attributes\DataProvider('moveProvider')]
435
-	public function testMove(string $source, string $target): void {
436
-		$this->insertProps('dummy_user_42', $source, ['foo' => 'bar']);
437
-		$this->backend->move($source, $target);
438
-		$this->assertEquals([], $this->getProps('dummy_user_42', $source));
439
-		$this->assertEquals(['foo' => 'bar'], $this->getProps('dummy_user_42', $target));
440
-	}
441
-
442
-	public static function moveProvider(): array {
443
-		return [
444
-			['foo_bar_path_1337', 'foo_bar_path_7333'],
445
-			[str_repeat('long_path1', 100), str_repeat('long_path2', 100)]
446
-		];
447
-	}
448
-
449
-	public function testDecodeValueFromDatabaseObjectCurrent(): void {
450
-		$propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"\x00*\x00value";s:6:"opaque";}';
451
-		$propertyType = 3;
452
-		$decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]);
453
-		$this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue);
454
-		$this->assertEquals('opaque', $decodeValue->getValue());
455
-	}
456
-
457
-	public function testDecodeValueFromDatabaseObjectLegacy(): void {
458
-		$propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"' . chr(0) . '*' . chr(0) . 'value";s:6:"opaque";}';
459
-		$propertyType = 3;
460
-		$decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]);
461
-		$this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue);
462
-		$this->assertEquals('opaque', $decodeValue->getValue());
463
-	}
30
+    private const BASE_URI = '/remote.php/dav/';
31
+
32
+    private Server&MockObject $server;
33
+    private Tree&MockObject $tree;
34
+    private IDBConnection $dbConnection;
35
+    private IUser&MockObject $user;
36
+    private DefaultCalendarValidator&MockObject $defaultCalendarValidator;
37
+    private CustomPropertiesBackend $backend;
38
+    private PropertyMapper $propertyMapper;
39
+
40
+    protected function setUp(): void {
41
+        parent::setUp();
42
+
43
+        $this->server = $this->createMock(Server::class);
44
+        $this->server->method('getBaseUri')
45
+            ->willReturn(self::BASE_URI);
46
+        $this->tree = $this->createMock(Tree::class);
47
+        $this->user = $this->createMock(IUser::class);
48
+        $this->user->method('getUID')
49
+            ->with()
50
+            ->willReturn('dummy_user_42');
51
+        $this->dbConnection = \OCP\Server::get(IDBConnection::class);
52
+        $this->propertyMapper = \OCP\Server::get(PropertyMapper::class);
53
+        $this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class);
54
+
55
+        $this->backend = new CustomPropertiesBackend(
56
+            $this->server,
57
+            $this->tree,
58
+            $this->dbConnection,
59
+            $this->user,
60
+            $this->propertyMapper,
61
+            $this->defaultCalendarValidator,
62
+        );
63
+    }
64
+
65
+    protected function tearDown(): void {
66
+        $query = $this->dbConnection->getQueryBuilder();
67
+        $query->delete('properties');
68
+        $query->executeStatement();
69
+
70
+        parent::tearDown();
71
+    }
72
+
73
+    private function formatPath(string $path): string {
74
+        if (strlen($path) > 250) {
75
+            return sha1($path);
76
+        } else {
77
+            return $path;
78
+        }
79
+    }
80
+
81
+    protected function insertProps(string $user, string $path, array $props): void {
82
+        foreach ($props as $name => $value) {
83
+            $this->insertProp($user, $path, $name, $value);
84
+        }
85
+    }
86
+
87
+    protected function insertProp(string $user, string $path, string $name, mixed $value): void {
88
+        $type = CustomPropertiesBackend::PROPERTY_TYPE_STRING;
89
+        if ($value instanceof Href) {
90
+            $value = $value->getHref();
91
+            $type = CustomPropertiesBackend::PROPERTY_TYPE_HREF;
92
+        }
93
+
94
+        $query = $this->dbConnection->getQueryBuilder();
95
+        $query->insert('properties')
96
+            ->values([
97
+                'userid' => $query->createNamedParameter($user),
98
+                'propertypath' => $query->createNamedParameter($this->formatPath($path)),
99
+                'propertyname' => $query->createNamedParameter($name),
100
+                'propertyvalue' => $query->createNamedParameter($value),
101
+                'valuetype' => $query->createNamedParameter($type, IQueryBuilder::PARAM_INT)
102
+            ]);
103
+        $query->executeStatement();
104
+    }
105
+
106
+    protected function getProps(string $user, string $path): array {
107
+        $query = $this->dbConnection->getQueryBuilder();
108
+        $query->select('propertyname', 'propertyvalue', 'valuetype')
109
+            ->from('properties')
110
+            ->where($query->expr()->eq('userid', $query->createNamedParameter($user)))
111
+            ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($this->formatPath($path))));
112
+
113
+        $result = $query->executeQuery();
114
+        $data = [];
115
+        while ($row = $result->fetchAssociative()) {
116
+            $value = $row['propertyvalue'];
117
+            if ((int)$row['valuetype'] === CustomPropertiesBackend::PROPERTY_TYPE_HREF) {
118
+                $value = new Href($value);
119
+            }
120
+            $data[$row['propertyname']] = $value;
121
+        }
122
+        $result->closeCursor();
123
+
124
+        return $data;
125
+    }
126
+
127
+    public function testPropFindNoDbCalls(): void {
128
+        $db = $this->createMock(IDBConnection::class);
129
+        $backend = new CustomPropertiesBackend(
130
+            $this->server,
131
+            $this->tree,
132
+            $db,
133
+            $this->user,
134
+            $this->propertyMapper,
135
+            $this->defaultCalendarValidator,
136
+        );
137
+
138
+        $propFind = $this->createMock(PropFind::class);
139
+        $propFind->expects($this->once())
140
+            ->method('get404Properties')
141
+            ->with()
142
+            ->willReturn([
143
+                '{http://owncloud.org/ns}permissions',
144
+                '{http://owncloud.org/ns}downloadURL',
145
+                '{http://owncloud.org/ns}dDC',
146
+                '{http://owncloud.org/ns}size',
147
+            ]);
148
+
149
+        $db->expects($this->never())
150
+            ->method($this->anything());
151
+
152
+        $backend->propFind('foo_bar_path_1337_0', $propFind);
153
+    }
154
+
155
+    public function testPropFindCalendarCall(): void {
156
+        $propFind = $this->createMock(PropFind::class);
157
+        $propFind->method('get404Properties')
158
+            ->with()
159
+            ->willReturn([
160
+                '{DAV:}getcontentlength',
161
+                '{DAV:}getcontenttype',
162
+                '{DAV:}getetag',
163
+                '{abc}def',
164
+            ]);
165
+
166
+        $propFind->method('getRequestedProperties')
167
+            ->with()
168
+            ->willReturn([
169
+                '{DAV:}getcontentlength',
170
+                '{DAV:}getcontenttype',
171
+                '{DAV:}getetag',
172
+                '{DAV:}displayname',
173
+                '{urn:ietf:params:xml:ns:caldav}calendar-description',
174
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone',
175
+                '{abc}def',
176
+            ]);
177
+
178
+        $props = [
179
+            '{abc}def' => 'a',
180
+            '{DAV:}displayname' => 'b',
181
+            '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'c',
182
+            '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'd',
183
+        ];
184
+
185
+        $this->insertProps('dummy_user_42', 'calendars/foo/bar_path_1337_0', $props);
186
+
187
+        $setProps = [];
188
+        $propFind->method('set')
189
+            ->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
190
+                $setProps[$name] = $value;
191
+            });
192
+
193
+        $this->backend->propFind('calendars/foo/bar_path_1337_0', $propFind);
194
+        $this->assertEquals($props, $setProps);
195
+    }
196
+
197
+    public function testPropFindPrincipalCall(): void {
198
+        $this->tree->method('getNodeForPath')
199
+            ->willReturnCallback(function ($uri) {
200
+                $node = $this->createMock(Calendar::class);
201
+                $node->method('getOwner')
202
+                    ->willReturn('principals/users/dummy_user_42');
203
+                return $node;
204
+            });
205
+
206
+        $propFind = $this->createMock(PropFind::class);
207
+        $propFind->method('get404Properties')
208
+            ->with()
209
+            ->willReturn([
210
+                '{DAV:}getcontentlength',
211
+                '{DAV:}getcontenttype',
212
+                '{DAV:}getetag',
213
+                '{abc}def',
214
+            ]);
215
+
216
+        $propFind->method('getRequestedProperties')
217
+            ->with()
218
+            ->willReturn([
219
+                '{DAV:}getcontentlength',
220
+                '{DAV:}getcontenttype',
221
+                '{DAV:}getetag',
222
+                '{abc}def',
223
+                '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL',
224
+            ]);
225
+
226
+        $props = [
227
+            '{abc}def' => 'a',
228
+            '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/admin/personal'),
229
+        ];
230
+        $this->insertProps('dummy_user_42', 'principals/users/dummy_user_42', $props);
231
+
232
+        $setProps = [];
233
+        $propFind->method('set')
234
+            ->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
235
+                $setProps[$name] = $value;
236
+            });
237
+
238
+        $this->backend->propFind('principals/users/dummy_user_42', $propFind);
239
+        $this->assertEquals($props, $setProps);
240
+    }
241
+
242
+    public static function propFindPrincipalScheduleDefaultCalendarProviderUrlProvider(): array {
243
+        // [ user, nodes, existingProps, requestedProps, returnedProps ]
244
+        return [
245
+            [ // Exists
246
+                'dummy_user_42',
247
+                ['calendars/dummy_user_42/foo/' => Calendar::class],
248
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
249
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
250
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
251
+            ],
252
+            [ // Doesn't exist
253
+                'dummy_user_42',
254
+                ['calendars/dummy_user_42/foo/' => Calendar::class],
255
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/bar/')],
256
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
257
+                [],
258
+            ],
259
+            [ // No privilege
260
+                'dummy_user_42',
261
+                ['calendars/user2/baz/' => Calendar::class],
262
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/user2/baz/')],
263
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
264
+                [],
265
+            ],
266
+            [ // Not a calendar
267
+                'dummy_user_42',
268
+                ['foo/dummy_user_42/bar/' => IACL::class],
269
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/dummy_user_42/bar/')],
270
+                ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
271
+                [],
272
+            ],
273
+        ];
274
+
275
+    }
276
+
277
+    #[\PHPUnit\Framework\Attributes\DataProvider('propFindPrincipalScheduleDefaultCalendarProviderUrlProvider')]
278
+    public function testPropFindPrincipalScheduleDefaultCalendarUrl(
279
+        string $user,
280
+        array $nodes,
281
+        array $existingProps,
282
+        array $requestedProps,
283
+        array $returnedProps,
284
+    ): void {
285
+        $propFind = $this->createMock(PropFind::class);
286
+        $propFind->method('get404Properties')
287
+            ->with()
288
+            ->willReturn([
289
+                '{DAV:}getcontentlength',
290
+                '{DAV:}getcontenttype',
291
+                '{DAV:}getetag',
292
+            ]);
293
+
294
+        $propFind->method('getRequestedProperties')
295
+            ->with()
296
+            ->willReturn(array_merge([
297
+                '{DAV:}getcontentlength',
298
+                '{DAV:}getcontenttype',
299
+                '{DAV:}getetag',
300
+                '{abc}def',
301
+            ],
302
+                $requestedProps,
303
+            ));
304
+
305
+        $this->server->method('calculateUri')
306
+            ->willReturnCallback(function ($uri) {
307
+                if (!str_starts_with($uri, self::BASE_URI)) {
308
+                    return trim(substr($uri, strlen(self::BASE_URI)), '/');
309
+                }
310
+                return null;
311
+            });
312
+        $this->tree->method('getNodeForPath')
313
+            ->willReturnCallback(function ($uri) use ($nodes) {
314
+                if (str_starts_with($uri, 'principals/')) {
315
+                    return $this->createMock(IPrincipal::class);
316
+                }
317
+                if (array_key_exists($uri, $nodes)) {
318
+                    $owner = explode('/', $uri)[1];
319
+                    $node = $this->createMock($nodes[$uri]);
320
+                    $node->method('getOwner')
321
+                        ->willReturn("principals/users/$owner");
322
+                    return $node;
323
+                }
324
+                throw new NotFound('Node not found');
325
+            });
326
+
327
+        $this->insertProps($user, "principals/users/$user", $existingProps);
328
+
329
+        $setProps = [];
330
+        $propFind->method('set')
331
+            ->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
332
+                $setProps[$name] = $value;
333
+            });
334
+
335
+        $this->backend->propFind("principals/users/$user", $propFind);
336
+        $this->assertEquals($returnedProps, $setProps);
337
+    }
338
+
339
+    #[\PHPUnit\Framework\Attributes\DataProvider('propPatchProvider')]
340
+    public function testPropPatch(string $path, array $existing, array $props, array $result): void {
341
+        $this->server->method('calculateUri')
342
+            ->willReturnCallback(function ($uri) {
343
+                if (str_starts_with($uri, self::BASE_URI)) {
344
+                    return trim(substr($uri, strlen(self::BASE_URI)), '/');
345
+                }
346
+                return null;
347
+            });
348
+        $this->tree->method('getNodeForPath')
349
+            ->willReturnCallback(function ($uri) {
350
+                $node = $this->createMock(Calendar::class);
351
+                $node->method('getOwner')
352
+                    ->willReturn('principals/users/' . $this->user->getUID());
353
+                return $node;
354
+            });
355
+
356
+        $this->insertProps($this->user->getUID(), $path, $existing);
357
+        $propPatch = new PropPatch($props);
358
+
359
+        $this->backend->propPatch($path, $propPatch);
360
+        $propPatch->commit();
361
+
362
+        $storedProps = $this->getProps($this->user->getUID(), $path);
363
+        $this->assertEquals($result, $storedProps);
364
+    }
365
+
366
+    public static function propPatchProvider(): array {
367
+        $longPath = str_repeat('long_path', 100);
368
+        return [
369
+            ['foo_bar_path_1337', [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
370
+            ['foo_bar_path_1337', ['{DAV:}displayname' => 'foo'], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
371
+            ['foo_bar_path_1337', ['{DAV:}displayname' => 'foo'], ['{DAV:}displayname' => null], []],
372
+            [$longPath, [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
373
+            ['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
374
+            ['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href(self::BASE_URI . 'foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
375
+        ];
376
+    }
377
+
378
+    public function testPropPatchWithUnsuitableCalendar(): void {
379
+        $path = 'principals/users/' . $this->user->getUID();
380
+
381
+        $node = $this->createMock(Calendar::class);
382
+        $node->expects(self::once())
383
+            ->method('getOwner')
384
+            ->willReturn($path);
385
+
386
+        $this->defaultCalendarValidator->expects(self::once())
387
+            ->method('validateScheduleDefaultCalendar')
388
+            ->with($node)
389
+            ->willThrowException(new \Sabre\DAV\Exception('Invalid calendar'));
390
+
391
+        $this->server->method('calculateUri')
392
+            ->willReturnCallback(function ($uri) {
393
+                if (str_starts_with($uri, self::BASE_URI)) {
394
+                    return trim(substr($uri, strlen(self::BASE_URI)), '/');
395
+                }
396
+                return null;
397
+            });
398
+        $this->tree->expects(self::once())
399
+            ->method('getNodeForPath')
400
+            ->with('foo/bar/')
401
+            ->willReturn($node);
402
+
403
+        $storedProps = $this->getProps($this->user->getUID(), $path);
404
+        $this->assertEquals([], $storedProps);
405
+
406
+        $propPatch = new PropPatch([
407
+            '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/'),
408
+        ]);
409
+        $this->backend->propPatch($path, $propPatch);
410
+        try {
411
+            $propPatch->commit();
412
+        } catch (\Throwable $e) {
413
+            $this->assertInstanceOf(\Sabre\DAV\Exception::class, $e);
414
+        }
415
+
416
+        $storedProps = $this->getProps($this->user->getUID(), $path);
417
+        $this->assertEquals([], $storedProps);
418
+    }
419
+
420
+    #[\PHPUnit\Framework\Attributes\DataProvider('deleteProvider')]
421
+    public function testDelete(string $path): void {
422
+        $this->insertProps('dummy_user_42', $path, ['foo' => 'bar']);
423
+        $this->backend->delete($path);
424
+        $this->assertEquals([], $this->getProps('dummy_user_42', $path));
425
+    }
426
+
427
+    public static function deleteProvider(): array {
428
+        return [
429
+            ['foo_bar_path_1337'],
430
+            [str_repeat('long_path', 100)]
431
+        ];
432
+    }
433
+
434
+    #[\PHPUnit\Framework\Attributes\DataProvider('moveProvider')]
435
+    public function testMove(string $source, string $target): void {
436
+        $this->insertProps('dummy_user_42', $source, ['foo' => 'bar']);
437
+        $this->backend->move($source, $target);
438
+        $this->assertEquals([], $this->getProps('dummy_user_42', $source));
439
+        $this->assertEquals(['foo' => 'bar'], $this->getProps('dummy_user_42', $target));
440
+    }
441
+
442
+    public static function moveProvider(): array {
443
+        return [
444
+            ['foo_bar_path_1337', 'foo_bar_path_7333'],
445
+            [str_repeat('long_path1', 100), str_repeat('long_path2', 100)]
446
+        ];
447
+    }
448
+
449
+    public function testDecodeValueFromDatabaseObjectCurrent(): void {
450
+        $propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"\x00*\x00value";s:6:"opaque";}';
451
+        $propertyType = 3;
452
+        $decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]);
453
+        $this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue);
454
+        $this->assertEquals('opaque', $decodeValue->getValue());
455
+    }
456
+
457
+    public function testDecodeValueFromDatabaseObjectLegacy(): void {
458
+        $propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"' . chr(0) . '*' . chr(0) . 'value";s:6:"opaque";}';
459
+        $propertyType = 3;
460
+        $decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]);
461
+        $this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue);
462
+        $this->assertEquals('opaque', $decodeValue->getValue());
463
+    }
464 464
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
 		$data = [];
115 115
 		while ($row = $result->fetchAssociative()) {
116 116
 			$value = $row['propertyvalue'];
117
-			if ((int)$row['valuetype'] === CustomPropertiesBackend::PROPERTY_TYPE_HREF) {
117
+			if ((int) $row['valuetype'] === CustomPropertiesBackend::PROPERTY_TYPE_HREF) {
118 118
 				$value = new Href($value);
119 119
 			}
120 120
 			$data[$row['propertyname']] = $value;
@@ -186,7 +186,7 @@  discard block
 block discarded – undo
186 186
 
187 187
 		$setProps = [];
188 188
 		$propFind->method('set')
189
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
189
+			->willReturnCallback(function($name, $value, $status) use (&$setProps): void {
190 190
 				$setProps[$name] = $value;
191 191
 			});
192 192
 
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 
197 197
 	public function testPropFindPrincipalCall(): void {
198 198
 		$this->tree->method('getNodeForPath')
199
-			->willReturnCallback(function ($uri) {
199
+			->willReturnCallback(function($uri) {
200 200
 				$node = $this->createMock(Calendar::class);
201 201
 				$node->method('getOwner')
202 202
 					->willReturn('principals/users/dummy_user_42');
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
 
232 232
 		$setProps = [];
233 233
 		$propFind->method('set')
234
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
234
+			->willReturnCallback(function($name, $value, $status) use (&$setProps): void {
235 235
 				$setProps[$name] = $value;
236 236
 			});
237 237
 
@@ -242,28 +242,28 @@  discard block
 block discarded – undo
242 242
 	public static function propFindPrincipalScheduleDefaultCalendarProviderUrlProvider(): array {
243 243
 		// [ user, nodes, existingProps, requestedProps, returnedProps ]
244 244
 		return [
245
-			[ // Exists
245
+			[// Exists
246 246
 				'dummy_user_42',
247 247
 				['calendars/dummy_user_42/foo/' => Calendar::class],
248 248
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
249 249
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
250 250
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')],
251 251
 			],
252
-			[ // Doesn't exist
252
+			[// Doesn't exist
253 253
 				'dummy_user_42',
254 254
 				['calendars/dummy_user_42/foo/' => Calendar::class],
255 255
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/bar/')],
256 256
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
257 257
 				[],
258 258
 			],
259
-			[ // No privilege
259
+			[// No privilege
260 260
 				'dummy_user_42',
261 261
 				['calendars/user2/baz/' => Calendar::class],
262 262
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/user2/baz/')],
263 263
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'],
264 264
 				[],
265 265
 			],
266
-			[ // Not a calendar
266
+			[// Not a calendar
267 267
 				'dummy_user_42',
268 268
 				['foo/dummy_user_42/bar/' => IACL::class],
269 269
 				['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/dummy_user_42/bar/')],
@@ -303,14 +303,14 @@  discard block
 block discarded – undo
303 303
 			));
304 304
 
305 305
 		$this->server->method('calculateUri')
306
-			->willReturnCallback(function ($uri) {
306
+			->willReturnCallback(function($uri) {
307 307
 				if (!str_starts_with($uri, self::BASE_URI)) {
308 308
 					return trim(substr($uri, strlen(self::BASE_URI)), '/');
309 309
 				}
310 310
 				return null;
311 311
 			});
312 312
 		$this->tree->method('getNodeForPath')
313
-			->willReturnCallback(function ($uri) use ($nodes) {
313
+			->willReturnCallback(function($uri) use ($nodes) {
314 314
 				if (str_starts_with($uri, 'principals/')) {
315 315
 					return $this->createMock(IPrincipal::class);
316 316
 				}
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
 
329 329
 		$setProps = [];
330 330
 		$propFind->method('set')
331
-			->willReturnCallback(function ($name, $value, $status) use (&$setProps): void {
331
+			->willReturnCallback(function($name, $value, $status) use (&$setProps): void {
332 332
 				$setProps[$name] = $value;
333 333
 			});
334 334
 
@@ -339,17 +339,17 @@  discard block
 block discarded – undo
339 339
 	#[\PHPUnit\Framework\Attributes\DataProvider('propPatchProvider')]
340 340
 	public function testPropPatch(string $path, array $existing, array $props, array $result): void {
341 341
 		$this->server->method('calculateUri')
342
-			->willReturnCallback(function ($uri) {
342
+			->willReturnCallback(function($uri) {
343 343
 				if (str_starts_with($uri, self::BASE_URI)) {
344 344
 					return trim(substr($uri, strlen(self::BASE_URI)), '/');
345 345
 				}
346 346
 				return null;
347 347
 			});
348 348
 		$this->tree->method('getNodeForPath')
349
-			->willReturnCallback(function ($uri) {
349
+			->willReturnCallback(function($uri) {
350 350
 				$node = $this->createMock(Calendar::class);
351 351
 				$node->method('getOwner')
352
-					->willReturn('principals/users/' . $this->user->getUID());
352
+					->willReturn('principals/users/'.$this->user->getUID());
353 353
 				return $node;
354 354
 			});
355 355
 
@@ -371,12 +371,12 @@  discard block
 block discarded – undo
371 371
 			['foo_bar_path_1337', ['{DAV:}displayname' => 'foo'], ['{DAV:}displayname' => null], []],
372 372
 			[$longPath, [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']],
373 373
 			['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
374
-			['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href(self::BASE_URI . 'foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
374
+			['principals/users/dummy_user_42', [], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href(self::BASE_URI.'foo/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/')]],
375 375
 		];
376 376
 	}
377 377
 
378 378
 	public function testPropPatchWithUnsuitableCalendar(): void {
379
-		$path = 'principals/users/' . $this->user->getUID();
379
+		$path = 'principals/users/'.$this->user->getUID();
380 380
 
381 381
 		$node = $this->createMock(Calendar::class);
382 382
 		$node->expects(self::once())
@@ -389,7 +389,7 @@  discard block
 block discarded – undo
389 389
 			->willThrowException(new \Sabre\DAV\Exception('Invalid calendar'));
390 390
 
391 391
 		$this->server->method('calculateUri')
392
-			->willReturnCallback(function ($uri) {
392
+			->willReturnCallback(function($uri) {
393 393
 				if (str_starts_with($uri, self::BASE_URI)) {
394 394
 					return trim(substr($uri, strlen(self::BASE_URI)), '/');
395 395
 				}
@@ -455,7 +455,7 @@  discard block
 block discarded – undo
455 455
 	}
456 456
 
457 457
 	public function testDecodeValueFromDatabaseObjectLegacy(): void {
458
-		$propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"' . chr(0) . '*' . chr(0) . 'value";s:6:"opaque";}';
458
+		$propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"'.chr(0).'*'.chr(0).'value";s:6:"opaque";}';
459 459
 		$propertyType = 3;
460 460
 		$decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]);
461 461
 		$this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue);
Please login to merge, or discard this patch.
apps/dav/tests/unit/BackgroundJob/CleanupOrphanedChildrenJobTest.php 1 patch
Indentation   +146 added lines, -146 removed lines patch added patch discarded remove patch
@@ -21,150 +21,150 @@
 block discarded – undo
21 21
 use Test\TestCase;
22 22
 
23 23
 class CleanupOrphanedChildrenJobTest extends TestCase {
24
-	private CleanupOrphanedChildrenJob $job;
25
-
26
-	private ITimeFactory&MockObject $timeFactory;
27
-	private IDBConnection&MockObject $connection;
28
-	private LoggerInterface&MockObject $logger;
29
-	private IJobList&MockObject $jobList;
30
-
31
-	protected function setUp(): void {
32
-		parent::setUp();
33
-
34
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
35
-		$this->connection = $this->createMock(IDBConnection::class);
36
-		$this->logger = $this->createMock(LoggerInterface::class);
37
-		$this->jobList = $this->createMock(IJobList::class);
38
-
39
-		$this->job = new CleanupOrphanedChildrenJob(
40
-			$this->timeFactory,
41
-			$this->connection,
42
-			$this->logger,
43
-			$this->jobList,
44
-		);
45
-	}
46
-
47
-	private function getArgument(): array {
48
-		return [
49
-			'childTable' => 'childTable',
50
-			'parentTable' => 'parentTable',
51
-			'parentId' => 'parentId',
52
-			'logMessage' => 'logMessage',
53
-		];
54
-	}
55
-
56
-	private function getMockQueryBuilder(): IQueryBuilder&MockObject {
57
-		$expr = $this->createMock(IExpressionBuilder::class);
58
-		$qb = $this->createMock(IQueryBuilder::class);
59
-		$qb->method('select')
60
-			->willReturnSelf();
61
-		$qb->method('from')
62
-			->willReturnSelf();
63
-		$qb->method('leftJoin')
64
-			->willReturnSelf();
65
-		$qb->method('where')
66
-			->willReturnSelf();
67
-		$qb->method('setMaxResults')
68
-			->willReturnSelf();
69
-		$qb->method('andWhere')
70
-			->willReturnSelf();
71
-		$qb->method('expr')
72
-			->willReturn($expr);
73
-		$qb->method('delete')
74
-			->willReturnSelf();
75
-		return $qb;
76
-	}
77
-
78
-	public function testRunWithoutOrphans(): void {
79
-		$argument = $this->getArgument();
80
-		$selectQb = $this->getMockQueryBuilder();
81
-		$result = $this->createMock(IResult::class);
82
-
83
-		$this->connection->expects(self::once())
84
-			->method('getQueryBuilder')
85
-			->willReturn($selectQb);
86
-		$selectQb->expects(self::once())
87
-			->method('executeQuery')
88
-			->willReturn($result);
89
-		$result->expects(self::once())
90
-			->method('fetchAllAssociative')
91
-			->willReturn([]);
92
-		$result->expects(self::once())
93
-			->method('closeCursor');
94
-		$this->jobList->expects(self::never())
95
-			->method('add');
96
-
97
-		self::invokePrivate($this->job, 'run', [$argument]);
98
-	}
99
-
100
-	public function testRunWithPartialBatch(): void {
101
-		$argument = $this->getArgument();
102
-		$selectQb = $this->getMockQueryBuilder();
103
-		$deleteQb = $this->getMockQueryBuilder();
104
-		$result = $this->createMock(IResult::class);
105
-
106
-		$calls = [
107
-			$selectQb,
108
-			$deleteQb,
109
-		];
110
-		$this->connection->method('getQueryBuilder')
111
-			->willReturnCallback(function () use (&$calls) {
112
-				return array_shift($calls);
113
-			});
114
-		$selectQb->expects(self::once())
115
-			->method('executeQuery')
116
-			->willReturn($result);
117
-		$result->expects(self::once())
118
-			->method('fetchAllAssociative')
119
-			->willReturn([
120
-				['id' => 42],
121
-				['id' => 43],
122
-			]);
123
-		$result->expects(self::once())
124
-			->method('closeCursor');
125
-		$deleteQb->expects(self::once())
126
-			->method('delete')
127
-			->willReturnSelf();
128
-		$deleteQb->expects(self::once())
129
-			->method('executeStatement');
130
-		$this->jobList->expects(self::never())
131
-			->method('add');
132
-
133
-		self::invokePrivate($this->job, 'run', [$argument]);
134
-	}
135
-
136
-	public function testRunWithFullBatch(): void {
137
-		$argument = $this->getArgument();
138
-		$selectQb = $this->getMockQueryBuilder();
139
-		$deleteQb = $this->getMockQueryBuilder();
140
-		$result = $this->createMock(IResult::class);
141
-
142
-		$calls = [
143
-			$selectQb,
144
-			$deleteQb,
145
-		];
146
-		$this->connection->method('getQueryBuilder')
147
-			->willReturnCallback(function () use (&$calls) {
148
-				return array_shift($calls);
149
-			});
150
-
151
-		$selectQb->expects(self::once())
152
-			->method('executeQuery')
153
-			->willReturn($result);
154
-		$result->expects(self::once())
155
-			->method('fetchAllAssociative')
156
-			->willReturn(array_map(static fn ($i) => ['id' => 42 + $i], range(0, 999)));
157
-		$result->expects(self::once())
158
-			->method('closeCursor');
159
-		$deleteQb->expects(self::once())
160
-			->method('delete')
161
-			->willReturnSelf();
162
-		$deleteQb->expects(self::once())
163
-			->method('executeStatement');
164
-		$this->jobList->expects(self::once())
165
-			->method('add')
166
-			->with(CleanupOrphanedChildrenJob::class, $argument);
167
-
168
-		self::invokePrivate($this->job, 'run', [$argument]);
169
-	}
24
+    private CleanupOrphanedChildrenJob $job;
25
+
26
+    private ITimeFactory&MockObject $timeFactory;
27
+    private IDBConnection&MockObject $connection;
28
+    private LoggerInterface&MockObject $logger;
29
+    private IJobList&MockObject $jobList;
30
+
31
+    protected function setUp(): void {
32
+        parent::setUp();
33
+
34
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
35
+        $this->connection = $this->createMock(IDBConnection::class);
36
+        $this->logger = $this->createMock(LoggerInterface::class);
37
+        $this->jobList = $this->createMock(IJobList::class);
38
+
39
+        $this->job = new CleanupOrphanedChildrenJob(
40
+            $this->timeFactory,
41
+            $this->connection,
42
+            $this->logger,
43
+            $this->jobList,
44
+        );
45
+    }
46
+
47
+    private function getArgument(): array {
48
+        return [
49
+            'childTable' => 'childTable',
50
+            'parentTable' => 'parentTable',
51
+            'parentId' => 'parentId',
52
+            'logMessage' => 'logMessage',
53
+        ];
54
+    }
55
+
56
+    private function getMockQueryBuilder(): IQueryBuilder&MockObject {
57
+        $expr = $this->createMock(IExpressionBuilder::class);
58
+        $qb = $this->createMock(IQueryBuilder::class);
59
+        $qb->method('select')
60
+            ->willReturnSelf();
61
+        $qb->method('from')
62
+            ->willReturnSelf();
63
+        $qb->method('leftJoin')
64
+            ->willReturnSelf();
65
+        $qb->method('where')
66
+            ->willReturnSelf();
67
+        $qb->method('setMaxResults')
68
+            ->willReturnSelf();
69
+        $qb->method('andWhere')
70
+            ->willReturnSelf();
71
+        $qb->method('expr')
72
+            ->willReturn($expr);
73
+        $qb->method('delete')
74
+            ->willReturnSelf();
75
+        return $qb;
76
+    }
77
+
78
+    public function testRunWithoutOrphans(): void {
79
+        $argument = $this->getArgument();
80
+        $selectQb = $this->getMockQueryBuilder();
81
+        $result = $this->createMock(IResult::class);
82
+
83
+        $this->connection->expects(self::once())
84
+            ->method('getQueryBuilder')
85
+            ->willReturn($selectQb);
86
+        $selectQb->expects(self::once())
87
+            ->method('executeQuery')
88
+            ->willReturn($result);
89
+        $result->expects(self::once())
90
+            ->method('fetchAllAssociative')
91
+            ->willReturn([]);
92
+        $result->expects(self::once())
93
+            ->method('closeCursor');
94
+        $this->jobList->expects(self::never())
95
+            ->method('add');
96
+
97
+        self::invokePrivate($this->job, 'run', [$argument]);
98
+    }
99
+
100
+    public function testRunWithPartialBatch(): void {
101
+        $argument = $this->getArgument();
102
+        $selectQb = $this->getMockQueryBuilder();
103
+        $deleteQb = $this->getMockQueryBuilder();
104
+        $result = $this->createMock(IResult::class);
105
+
106
+        $calls = [
107
+            $selectQb,
108
+            $deleteQb,
109
+        ];
110
+        $this->connection->method('getQueryBuilder')
111
+            ->willReturnCallback(function () use (&$calls) {
112
+                return array_shift($calls);
113
+            });
114
+        $selectQb->expects(self::once())
115
+            ->method('executeQuery')
116
+            ->willReturn($result);
117
+        $result->expects(self::once())
118
+            ->method('fetchAllAssociative')
119
+            ->willReturn([
120
+                ['id' => 42],
121
+                ['id' => 43],
122
+            ]);
123
+        $result->expects(self::once())
124
+            ->method('closeCursor');
125
+        $deleteQb->expects(self::once())
126
+            ->method('delete')
127
+            ->willReturnSelf();
128
+        $deleteQb->expects(self::once())
129
+            ->method('executeStatement');
130
+        $this->jobList->expects(self::never())
131
+            ->method('add');
132
+
133
+        self::invokePrivate($this->job, 'run', [$argument]);
134
+    }
135
+
136
+    public function testRunWithFullBatch(): void {
137
+        $argument = $this->getArgument();
138
+        $selectQb = $this->getMockQueryBuilder();
139
+        $deleteQb = $this->getMockQueryBuilder();
140
+        $result = $this->createMock(IResult::class);
141
+
142
+        $calls = [
143
+            $selectQb,
144
+            $deleteQb,
145
+        ];
146
+        $this->connection->method('getQueryBuilder')
147
+            ->willReturnCallback(function () use (&$calls) {
148
+                return array_shift($calls);
149
+            });
150
+
151
+        $selectQb->expects(self::once())
152
+            ->method('executeQuery')
153
+            ->willReturn($result);
154
+        $result->expects(self::once())
155
+            ->method('fetchAllAssociative')
156
+            ->willReturn(array_map(static fn ($i) => ['id' => 42 + $i], range(0, 999)));
157
+        $result->expects(self::once())
158
+            ->method('closeCursor');
159
+        $deleteQb->expects(self::once())
160
+            ->method('delete')
161
+            ->willReturnSelf();
162
+        $deleteQb->expects(self::once())
163
+            ->method('executeStatement');
164
+        $this->jobList->expects(self::once())
165
+            ->method('add')
166
+            ->with(CleanupOrphanedChildrenJob::class, $argument);
167
+
168
+        self::invokePrivate($this->job, 'run', [$argument]);
169
+    }
170 170
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php 1 patch
Indentation   +358 added lines, -358 removed lines patch added patch discarded remove patch
@@ -23,50 +23,50 @@  discard block
 block discarded – undo
23 23
 use Test\TestCase;
24 24
 
25 25
 class InvitationResponseControllerTest extends TestCase {
26
-	private IDBConnection&MockObject $dbConnection;
27
-	private IRequest&MockObject $request;
28
-	private ITimeFactory&MockObject $timeFactory;
29
-	private InvitationResponseServer&MockObject $responseServer;
30
-	private InvitationResponseController $controller;
31
-
32
-	protected function setUp(): void {
33
-		parent::setUp();
34
-
35
-		$this->dbConnection = $this->createMock(IDBConnection::class);
36
-		$this->request = $this->createMock(IRequest::class);
37
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
38
-		$this->responseServer = $this->createMock(InvitationResponseServer::class);
39
-
40
-		$this->controller = new InvitationResponseController(
41
-			'appName',
42
-			$this->request,
43
-			$this->dbConnection,
44
-			$this->timeFactory,
45
-			$this->responseServer
46
-		);
47
-	}
48
-
49
-	public static function attendeeProvider(): array {
50
-		return [
51
-			'local attendee' => [false],
52
-			'external attendee' => [true]
53
-		];
54
-	}
55
-
56
-	#[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
57
-	public function testAccept(bool $isExternalAttendee): void {
58
-		$this->buildQueryExpects('TOKEN123', [
59
-			'id' => 0,
60
-			'uid' => 'this-is-the-events-uid',
61
-			'recurrenceid' => null,
62
-			'attendee' => 'mailto:[email protected]',
63
-			'organizer' => 'mailto:[email protected]',
64
-			'sequence' => null,
65
-			'token' => 'TOKEN123',
66
-			'expiration' => 420000,
67
-		], 1337);
68
-
69
-		$expected = <<<EOF
26
+    private IDBConnection&MockObject $dbConnection;
27
+    private IRequest&MockObject $request;
28
+    private ITimeFactory&MockObject $timeFactory;
29
+    private InvitationResponseServer&MockObject $responseServer;
30
+    private InvitationResponseController $controller;
31
+
32
+    protected function setUp(): void {
33
+        parent::setUp();
34
+
35
+        $this->dbConnection = $this->createMock(IDBConnection::class);
36
+        $this->request = $this->createMock(IRequest::class);
37
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
38
+        $this->responseServer = $this->createMock(InvitationResponseServer::class);
39
+
40
+        $this->controller = new InvitationResponseController(
41
+            'appName',
42
+            $this->request,
43
+            $this->dbConnection,
44
+            $this->timeFactory,
45
+            $this->responseServer
46
+        );
47
+    }
48
+
49
+    public static function attendeeProvider(): array {
50
+        return [
51
+            'local attendee' => [false],
52
+            'external attendee' => [true]
53
+        ];
54
+    }
55
+
56
+    #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
57
+    public function testAccept(bool $isExternalAttendee): void {
58
+        $this->buildQueryExpects('TOKEN123', [
59
+            'id' => 0,
60
+            'uid' => 'this-is-the-events-uid',
61
+            'recurrenceid' => null,
62
+            'attendee' => 'mailto:[email protected]',
63
+            'organizer' => 'mailto:[email protected]',
64
+            'sequence' => null,
65
+            'token' => 'TOKEN123',
66
+            'expiration' => 420000,
67
+        ], 1337);
68
+
69
+        $expected = <<<EOF
70 70
 BEGIN:VCALENDAR
71 71
 VERSION:2.0
72 72
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
@@ -82,53 +82,53 @@  discard block
 block discarded – undo
82 82
 END:VCALENDAR
83 83
 
84 84
 EOF;
85
-		$expected = preg_replace('~\R~u', "\r\n", $expected);
86
-
87
-		$called = false;
88
-		$this->responseServer->expects($this->once())
89
-			->method('handleITipMessage')
90
-			->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
91
-				$called = true;
92
-				$this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
93
-				$this->assertEquals('VEVENT', $iTipMessage->component);
94
-				$this->assertEquals('REPLY', $iTipMessage->method);
95
-				$this->assertEquals(null, $iTipMessage->sequence);
96
-				$this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
97
-				if ($isExternalAttendee) {
98
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
99
-				} else {
100
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
101
-				}
102
-
103
-				$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
104
-
105
-				$this->assertEquals($expected, $iTipMessage->message->serialize());
106
-			});
107
-		$this->responseServer->expects($this->once())
108
-			->method('isExternalAttendee')
109
-			->willReturn($isExternalAttendee);
110
-
111
-		$response = $this->controller->accept('TOKEN123');
112
-		$this->assertInstanceOf(TemplateResponse::class, $response);
113
-		$this->assertEquals('schedule-response-success', $response->getTemplateName());
114
-		$this->assertEquals([], $response->getParams());
115
-		$this->assertTrue($called);
116
-	}
117
-
118
-	#[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
119
-	public function testAcceptSequence(bool $isExternalAttendee): void {
120
-		$this->buildQueryExpects('TOKEN123', [
121
-			'id' => 0,
122
-			'uid' => 'this-is-the-events-uid',
123
-			'recurrenceid' => null,
124
-			'attendee' => 'mailto:[email protected]',
125
-			'organizer' => 'mailto:[email protected]',
126
-			'sequence' => 1337,
127
-			'token' => 'TOKEN123',
128
-			'expiration' => 420000,
129
-		], 1337);
130
-
131
-		$expected = <<<EOF
85
+        $expected = preg_replace('~\R~u', "\r\n", $expected);
86
+
87
+        $called = false;
88
+        $this->responseServer->expects($this->once())
89
+            ->method('handleITipMessage')
90
+            ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
91
+                $called = true;
92
+                $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
93
+                $this->assertEquals('VEVENT', $iTipMessage->component);
94
+                $this->assertEquals('REPLY', $iTipMessage->method);
95
+                $this->assertEquals(null, $iTipMessage->sequence);
96
+                $this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
97
+                if ($isExternalAttendee) {
98
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
99
+                } else {
100
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
101
+                }
102
+
103
+                $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
104
+
105
+                $this->assertEquals($expected, $iTipMessage->message->serialize());
106
+            });
107
+        $this->responseServer->expects($this->once())
108
+            ->method('isExternalAttendee')
109
+            ->willReturn($isExternalAttendee);
110
+
111
+        $response = $this->controller->accept('TOKEN123');
112
+        $this->assertInstanceOf(TemplateResponse::class, $response);
113
+        $this->assertEquals('schedule-response-success', $response->getTemplateName());
114
+        $this->assertEquals([], $response->getParams());
115
+        $this->assertTrue($called);
116
+    }
117
+
118
+    #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
119
+    public function testAcceptSequence(bool $isExternalAttendee): void {
120
+        $this->buildQueryExpects('TOKEN123', [
121
+            'id' => 0,
122
+            'uid' => 'this-is-the-events-uid',
123
+            'recurrenceid' => null,
124
+            'attendee' => 'mailto:[email protected]',
125
+            'organizer' => 'mailto:[email protected]',
126
+            'sequence' => 1337,
127
+            'token' => 'TOKEN123',
128
+            'expiration' => 420000,
129
+        ], 1337);
130
+
131
+        $expected = <<<EOF
132 132
 BEGIN:VCALENDAR
133 133
 VERSION:2.0
134 134
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
@@ -144,53 +144,53 @@  discard block
 block discarded – undo
144 144
 END:VCALENDAR
145 145
 
146 146
 EOF;
147
-		$expected = preg_replace('~\R~u', "\r\n", $expected);
148
-
149
-		$called = false;
150
-		$this->responseServer->expects($this->once())
151
-			->method('handleITipMessage')
152
-			->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
153
-				$called = true;
154
-				$this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
155
-				$this->assertEquals('VEVENT', $iTipMessage->component);
156
-				$this->assertEquals('REPLY', $iTipMessage->method);
157
-				$this->assertEquals(1337, $iTipMessage->sequence);
158
-				$this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
159
-				if ($isExternalAttendee) {
160
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
161
-				} else {
162
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
163
-				}
164
-
165
-				$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
166
-
167
-				$this->assertEquals($expected, $iTipMessage->message->serialize());
168
-			});
169
-		$this->responseServer->expects($this->once())
170
-			->method('isExternalAttendee')
171
-			->willReturn($isExternalAttendee);
172
-
173
-		$response = $this->controller->accept('TOKEN123');
174
-		$this->assertInstanceOf(TemplateResponse::class, $response);
175
-		$this->assertEquals('schedule-response-success', $response->getTemplateName());
176
-		$this->assertEquals([], $response->getParams());
177
-		$this->assertTrue($called);
178
-	}
179
-
180
-	#[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
181
-	public function testAcceptRecurrenceId(bool $isExternalAttendee): void {
182
-		$this->buildQueryExpects('TOKEN123', [
183
-			'id' => 0,
184
-			'uid' => 'this-is-the-events-uid',
185
-			'recurrenceid' => "RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000\n",
186
-			'attendee' => 'mailto:[email protected]',
187
-			'organizer' => 'mailto:[email protected]',
188
-			'sequence' => null,
189
-			'token' => 'TOKEN123',
190
-			'expiration' => 420000,
191
-		], 1337);
192
-
193
-		$expected = <<<EOF
147
+        $expected = preg_replace('~\R~u', "\r\n", $expected);
148
+
149
+        $called = false;
150
+        $this->responseServer->expects($this->once())
151
+            ->method('handleITipMessage')
152
+            ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
153
+                $called = true;
154
+                $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
155
+                $this->assertEquals('VEVENT', $iTipMessage->component);
156
+                $this->assertEquals('REPLY', $iTipMessage->method);
157
+                $this->assertEquals(1337, $iTipMessage->sequence);
158
+                $this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
159
+                if ($isExternalAttendee) {
160
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
161
+                } else {
162
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
163
+                }
164
+
165
+                $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
166
+
167
+                $this->assertEquals($expected, $iTipMessage->message->serialize());
168
+            });
169
+        $this->responseServer->expects($this->once())
170
+            ->method('isExternalAttendee')
171
+            ->willReturn($isExternalAttendee);
172
+
173
+        $response = $this->controller->accept('TOKEN123');
174
+        $this->assertInstanceOf(TemplateResponse::class, $response);
175
+        $this->assertEquals('schedule-response-success', $response->getTemplateName());
176
+        $this->assertEquals([], $response->getParams());
177
+        $this->assertTrue($called);
178
+    }
179
+
180
+    #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
181
+    public function testAcceptRecurrenceId(bool $isExternalAttendee): void {
182
+        $this->buildQueryExpects('TOKEN123', [
183
+            'id' => 0,
184
+            'uid' => 'this-is-the-events-uid',
185
+            'recurrenceid' => "RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000\n",
186
+            'attendee' => 'mailto:[email protected]',
187
+            'organizer' => 'mailto:[email protected]',
188
+            'sequence' => null,
189
+            'token' => 'TOKEN123',
190
+            'expiration' => 420000,
191
+        ], 1337);
192
+
193
+        $expected = <<<EOF
194 194
 BEGIN:VCALENDAR
195 195
 VERSION:2.0
196 196
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
@@ -207,80 +207,80 @@  discard block
 block discarded – undo
207 207
 END:VCALENDAR
208 208
 
209 209
 EOF;
210
-		$expected = preg_replace('~\R~u', "\r\n", $expected);
211
-
212
-		$called = false;
213
-		$this->responseServer->expects($this->once())
214
-			->method('handleITipMessage')
215
-			->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
216
-				$called = true;
217
-				$this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
218
-				$this->assertEquals('VEVENT', $iTipMessage->component);
219
-				$this->assertEquals('REPLY', $iTipMessage->method);
220
-				$this->assertEquals(0, $iTipMessage->sequence);
221
-				$this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
222
-				if ($isExternalAttendee) {
223
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
224
-				} else {
225
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
226
-				}
227
-
228
-				$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
229
-
230
-				$this->assertEquals($expected, $iTipMessage->message->serialize());
231
-			});
232
-		$this->responseServer->expects($this->once())
233
-			->method('isExternalAttendee')
234
-			->willReturn($isExternalAttendee);
235
-
236
-		$response = $this->controller->accept('TOKEN123');
237
-		$this->assertInstanceOf(TemplateResponse::class, $response);
238
-		$this->assertEquals('schedule-response-success', $response->getTemplateName());
239
-		$this->assertEquals([], $response->getParams());
240
-		$this->assertTrue($called);
241
-	}
242
-
243
-	public function testAcceptTokenNotFound(): void {
244
-		$this->buildQueryExpects('TOKEN123', null, 1337);
245
-
246
-		$response = $this->controller->accept('TOKEN123');
247
-		$this->assertInstanceOf(TemplateResponse::class, $response);
248
-		$this->assertEquals('schedule-response-error', $response->getTemplateName());
249
-		$this->assertEquals([], $response->getParams());
250
-	}
251
-
252
-	public function testAcceptExpiredToken(): void {
253
-		$this->buildQueryExpects('TOKEN123', [
254
-			'id' => 0,
255
-			'uid' => 'this-is-the-events-uid',
256
-			'recurrenceid' => null,
257
-			'attendee' => 'mailto:[email protected]',
258
-			'organizer' => 'mailto:[email protected]',
259
-			'sequence' => null,
260
-			'token' => 'TOKEN123',
261
-			'expiration' => 42,
262
-		], 1337);
263
-
264
-		$response = $this->controller->accept('TOKEN123');
265
-		$this->assertInstanceOf(TemplateResponse::class, $response);
266
-		$this->assertEquals('schedule-response-error', $response->getTemplateName());
267
-		$this->assertEquals([], $response->getParams());
268
-	}
269
-
270
-	#[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
271
-	public function testDecline(bool $isExternalAttendee): void {
272
-		$this->buildQueryExpects('TOKEN123', [
273
-			'id' => 0,
274
-			'uid' => 'this-is-the-events-uid',
275
-			'recurrenceid' => null,
276
-			'attendee' => 'mailto:[email protected]',
277
-			'organizer' => 'mailto:[email protected]',
278
-			'sequence' => null,
279
-			'token' => 'TOKEN123',
280
-			'expiration' => 420000,
281
-		], 1337);
282
-
283
-		$expected = <<<EOF
210
+        $expected = preg_replace('~\R~u', "\r\n", $expected);
211
+
212
+        $called = false;
213
+        $this->responseServer->expects($this->once())
214
+            ->method('handleITipMessage')
215
+            ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
216
+                $called = true;
217
+                $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
218
+                $this->assertEquals('VEVENT', $iTipMessage->component);
219
+                $this->assertEquals('REPLY', $iTipMessage->method);
220
+                $this->assertEquals(0, $iTipMessage->sequence);
221
+                $this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
222
+                if ($isExternalAttendee) {
223
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
224
+                } else {
225
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
226
+                }
227
+
228
+                $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
229
+
230
+                $this->assertEquals($expected, $iTipMessage->message->serialize());
231
+            });
232
+        $this->responseServer->expects($this->once())
233
+            ->method('isExternalAttendee')
234
+            ->willReturn($isExternalAttendee);
235
+
236
+        $response = $this->controller->accept('TOKEN123');
237
+        $this->assertInstanceOf(TemplateResponse::class, $response);
238
+        $this->assertEquals('schedule-response-success', $response->getTemplateName());
239
+        $this->assertEquals([], $response->getParams());
240
+        $this->assertTrue($called);
241
+    }
242
+
243
+    public function testAcceptTokenNotFound(): void {
244
+        $this->buildQueryExpects('TOKEN123', null, 1337);
245
+
246
+        $response = $this->controller->accept('TOKEN123');
247
+        $this->assertInstanceOf(TemplateResponse::class, $response);
248
+        $this->assertEquals('schedule-response-error', $response->getTemplateName());
249
+        $this->assertEquals([], $response->getParams());
250
+    }
251
+
252
+    public function testAcceptExpiredToken(): void {
253
+        $this->buildQueryExpects('TOKEN123', [
254
+            'id' => 0,
255
+            'uid' => 'this-is-the-events-uid',
256
+            'recurrenceid' => null,
257
+            'attendee' => 'mailto:[email protected]',
258
+            'organizer' => 'mailto:[email protected]',
259
+            'sequence' => null,
260
+            'token' => 'TOKEN123',
261
+            'expiration' => 42,
262
+        ], 1337);
263
+
264
+        $response = $this->controller->accept('TOKEN123');
265
+        $this->assertInstanceOf(TemplateResponse::class, $response);
266
+        $this->assertEquals('schedule-response-error', $response->getTemplateName());
267
+        $this->assertEquals([], $response->getParams());
268
+    }
269
+
270
+    #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
271
+    public function testDecline(bool $isExternalAttendee): void {
272
+        $this->buildQueryExpects('TOKEN123', [
273
+            'id' => 0,
274
+            'uid' => 'this-is-the-events-uid',
275
+            'recurrenceid' => null,
276
+            'attendee' => 'mailto:[email protected]',
277
+            'organizer' => 'mailto:[email protected]',
278
+            'sequence' => null,
279
+            'token' => 'TOKEN123',
280
+            'expiration' => 420000,
281
+        ], 1337);
282
+
283
+        $expected = <<<EOF
284 284
 BEGIN:VCALENDAR
285 285
 VERSION:2.0
286 286
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
@@ -296,65 +296,65 @@  discard block
 block discarded – undo
296 296
 END:VCALENDAR
297 297
 
298 298
 EOF;
299
-		$expected = preg_replace('~\R~u', "\r\n", $expected);
300
-
301
-		$called = false;
302
-		$this->responseServer->expects($this->once())
303
-			->method('handleITipMessage')
304
-			->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
305
-				$called = true;
306
-				$this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
307
-				$this->assertEquals('VEVENT', $iTipMessage->component);
308
-				$this->assertEquals('REPLY', $iTipMessage->method);
309
-				$this->assertEquals(null, $iTipMessage->sequence);
310
-				$this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
311
-				if ($isExternalAttendee) {
312
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
313
-				} else {
314
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
315
-				}
316
-
317
-				$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
318
-
319
-				$this->assertEquals($expected, $iTipMessage->message->serialize());
320
-			});
321
-		$this->responseServer->expects($this->once())
322
-			->method('isExternalAttendee')
323
-			->willReturn($isExternalAttendee);
324
-
325
-		$response = $this->controller->decline('TOKEN123');
326
-		$this->assertInstanceOf(TemplateResponse::class, $response);
327
-		$this->assertEquals('schedule-response-success', $response->getTemplateName());
328
-		$this->assertEquals([], $response->getParams());
329
-		$this->assertTrue($called);
330
-	}
331
-
332
-	public function testOptions(): void {
333
-		$response = $this->controller->options('TOKEN123');
334
-		$this->assertInstanceOf(TemplateResponse::class, $response);
335
-		$this->assertEquals('schedule-response-options', $response->getTemplateName());
336
-		$this->assertEquals(['token' => 'TOKEN123'], $response->getParams());
337
-	}
338
-
339
-	#[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
340
-	public function testProcessMoreOptionsResult(bool $isExternalAttendee): void {
341
-		$this->request->expects($this->once())
342
-			->method('getParam')
343
-			->with('partStat')
344
-			->willReturn('TENTATIVE');
345
-
346
-		$this->buildQueryExpects('TOKEN123', [
347
-			'id' => 0,
348
-			'uid' => 'this-is-the-events-uid',
349
-			'recurrenceid' => null,
350
-			'attendee' => 'mailto:[email protected]',
351
-			'organizer' => 'mailto:[email protected]',
352
-			'sequence' => null,
353
-			'token' => 'TOKEN123',
354
-			'expiration' => 420000,
355
-		], 1337);
356
-
357
-		$expected = <<<EOF
299
+        $expected = preg_replace('~\R~u', "\r\n", $expected);
300
+
301
+        $called = false;
302
+        $this->responseServer->expects($this->once())
303
+            ->method('handleITipMessage')
304
+            ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
305
+                $called = true;
306
+                $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
307
+                $this->assertEquals('VEVENT', $iTipMessage->component);
308
+                $this->assertEquals('REPLY', $iTipMessage->method);
309
+                $this->assertEquals(null, $iTipMessage->sequence);
310
+                $this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
311
+                if ($isExternalAttendee) {
312
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
313
+                } else {
314
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
315
+                }
316
+
317
+                $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
318
+
319
+                $this->assertEquals($expected, $iTipMessage->message->serialize());
320
+            });
321
+        $this->responseServer->expects($this->once())
322
+            ->method('isExternalAttendee')
323
+            ->willReturn($isExternalAttendee);
324
+
325
+        $response = $this->controller->decline('TOKEN123');
326
+        $this->assertInstanceOf(TemplateResponse::class, $response);
327
+        $this->assertEquals('schedule-response-success', $response->getTemplateName());
328
+        $this->assertEquals([], $response->getParams());
329
+        $this->assertTrue($called);
330
+    }
331
+
332
+    public function testOptions(): void {
333
+        $response = $this->controller->options('TOKEN123');
334
+        $this->assertInstanceOf(TemplateResponse::class, $response);
335
+        $this->assertEquals('schedule-response-options', $response->getTemplateName());
336
+        $this->assertEquals(['token' => 'TOKEN123'], $response->getParams());
337
+    }
338
+
339
+    #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')]
340
+    public function testProcessMoreOptionsResult(bool $isExternalAttendee): void {
341
+        $this->request->expects($this->once())
342
+            ->method('getParam')
343
+            ->with('partStat')
344
+            ->willReturn('TENTATIVE');
345
+
346
+        $this->buildQueryExpects('TOKEN123', [
347
+            'id' => 0,
348
+            'uid' => 'this-is-the-events-uid',
349
+            'recurrenceid' => null,
350
+            'attendee' => 'mailto:[email protected]',
351
+            'organizer' => 'mailto:[email protected]',
352
+            'sequence' => null,
353
+            'token' => 'TOKEN123',
354
+            'expiration' => 420000,
355
+        ], 1337);
356
+
357
+        $expected = <<<EOF
358 358
 BEGIN:VCALENDAR
359 359
 VERSION:2.0
360 360
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
@@ -370,91 +370,91 @@  discard block
 block discarded – undo
370 370
 END:VCALENDAR
371 371
 
372 372
 EOF;
373
-		$expected = preg_replace('~\R~u', "\r\n", $expected);
374
-
375
-		$called = false;
376
-		$this->responseServer->expects($this->once())
377
-			->method('handleITipMessage')
378
-			->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
379
-				$called = true;
380
-				$this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
381
-				$this->assertEquals('VEVENT', $iTipMessage->component);
382
-				$this->assertEquals('REPLY', $iTipMessage->method);
383
-				$this->assertEquals(null, $iTipMessage->sequence);
384
-				$this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
385
-				if ($isExternalAttendee) {
386
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
387
-				} else {
388
-					$this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
389
-				}
390
-
391
-				$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
392
-
393
-				$this->assertEquals($expected, $iTipMessage->message->serialize());
394
-			});
395
-		$this->responseServer->expects($this->once())
396
-			->method('isExternalAttendee')
397
-			->willReturn($isExternalAttendee);
398
-
399
-
400
-		$response = $this->controller->processMoreOptionsResult('TOKEN123');
401
-		$this->assertInstanceOf(TemplateResponse::class, $response);
402
-		$this->assertEquals('schedule-response-success', $response->getTemplateName());
403
-		$this->assertEquals([], $response->getParams());
404
-		$this->assertTrue($called);
405
-	}
406
-
407
-	private function buildQueryExpects(string $token, ?array $return, int $time): void {
408
-		$queryBuilder = $this->createMock(IQueryBuilder::class);
409
-		$stmt = $this->createMock(IResult::class);
410
-		$expr = $this->createMock(IExpressionBuilder::class);
411
-
412
-		$this->dbConnection->expects($this->once())
413
-			->method('getQueryBuilder')
414
-			->with()
415
-			->willReturn($queryBuilder);
416
-		$queryBuilder->method('expr')
417
-			->willReturn($expr);
418
-		$queryBuilder->method('createNamedParameter')
419
-			->willReturnMap([
420
-				[$token, \PDO::PARAM_STR, null, 'namedParameterToken']
421
-			]);
422
-
423
-		$stmt->expects($this->once())
424
-			->method('fetchAssociative')
425
-			->willReturn($return ?? false);
426
-		$stmt->expects($this->once())
427
-			->method('closeCursor');
428
-
429
-		$function = 'functionToken';
430
-		$expr->expects($this->once())
431
-			->method('eq')
432
-			->with('token', 'namedParameterToken')
433
-			->willReturn((string)$function);
434
-
435
-		$this->dbConnection->expects($this->once())
436
-			->method('getQueryBuilder')
437
-			->with()
438
-			->willReturn($queryBuilder);
439
-
440
-		$queryBuilder->expects($this->once())
441
-			->method('select')
442
-			->with('*')
443
-			->willReturn($queryBuilder);
444
-		$queryBuilder->expects($this->once())
445
-			->method('from')
446
-			->with('calendar_invitations')
447
-			->willReturn($queryBuilder);
448
-		$queryBuilder->expects($this->once())
449
-			->method('where')
450
-			->with($function)
451
-			->willReturn($queryBuilder);
452
-		$queryBuilder->expects($this->once())
453
-			->method('executeQuery')
454
-			->with()
455
-			->willReturn($stmt);
456
-
457
-		$this->timeFactory->method('getTime')
458
-			->willReturn($time);
459
-	}
373
+        $expected = preg_replace('~\R~u', "\r\n", $expected);
374
+
375
+        $called = false;
376
+        $this->responseServer->expects($this->once())
377
+            ->method('handleITipMessage')
378
+            ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
379
+                $called = true;
380
+                $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
381
+                $this->assertEquals('VEVENT', $iTipMessage->component);
382
+                $this->assertEquals('REPLY', $iTipMessage->method);
383
+                $this->assertEquals(null, $iTipMessage->sequence);
384
+                $this->assertEquals('mailto:[email protected]', $iTipMessage->sender);
385
+                if ($isExternalAttendee) {
386
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
387
+                } else {
388
+                    $this->assertEquals('mailto:[email protected]', $iTipMessage->recipient);
389
+                }
390
+
391
+                $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
392
+
393
+                $this->assertEquals($expected, $iTipMessage->message->serialize());
394
+            });
395
+        $this->responseServer->expects($this->once())
396
+            ->method('isExternalAttendee')
397
+            ->willReturn($isExternalAttendee);
398
+
399
+
400
+        $response = $this->controller->processMoreOptionsResult('TOKEN123');
401
+        $this->assertInstanceOf(TemplateResponse::class, $response);
402
+        $this->assertEquals('schedule-response-success', $response->getTemplateName());
403
+        $this->assertEquals([], $response->getParams());
404
+        $this->assertTrue($called);
405
+    }
406
+
407
+    private function buildQueryExpects(string $token, ?array $return, int $time): void {
408
+        $queryBuilder = $this->createMock(IQueryBuilder::class);
409
+        $stmt = $this->createMock(IResult::class);
410
+        $expr = $this->createMock(IExpressionBuilder::class);
411
+
412
+        $this->dbConnection->expects($this->once())
413
+            ->method('getQueryBuilder')
414
+            ->with()
415
+            ->willReturn($queryBuilder);
416
+        $queryBuilder->method('expr')
417
+            ->willReturn($expr);
418
+        $queryBuilder->method('createNamedParameter')
419
+            ->willReturnMap([
420
+                [$token, \PDO::PARAM_STR, null, 'namedParameterToken']
421
+            ]);
422
+
423
+        $stmt->expects($this->once())
424
+            ->method('fetchAssociative')
425
+            ->willReturn($return ?? false);
426
+        $stmt->expects($this->once())
427
+            ->method('closeCursor');
428
+
429
+        $function = 'functionToken';
430
+        $expr->expects($this->once())
431
+            ->method('eq')
432
+            ->with('token', 'namedParameterToken')
433
+            ->willReturn((string)$function);
434
+
435
+        $this->dbConnection->expects($this->once())
436
+            ->method('getQueryBuilder')
437
+            ->with()
438
+            ->willReturn($queryBuilder);
439
+
440
+        $queryBuilder->expects($this->once())
441
+            ->method('select')
442
+            ->with('*')
443
+            ->willReturn($queryBuilder);
444
+        $queryBuilder->expects($this->once())
445
+            ->method('from')
446
+            ->with('calendar_invitations')
447
+            ->willReturn($queryBuilder);
448
+        $queryBuilder->expects($this->once())
449
+            ->method('where')
450
+            ->with($function)
451
+            ->willReturn($queryBuilder);
452
+        $queryBuilder->expects($this->once())
453
+            ->method('executeQuery')
454
+            ->with()
455
+            ->willReturn($stmt);
456
+
457
+        $this->timeFactory->method('getTime')
458
+            ->willReturn($time);
459
+    }
460 460
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CardDAV/CardDavBackendTest.php 1 patch
Indentation   +875 added lines, -875 removed lines patch added patch discarded remove patch
@@ -48,879 +48,879 @@
 block discarded – undo
48 48
  */
49 49
 #[\PHPUnit\Framework\Attributes\Group('DB')]
50 50
 class CardDavBackendTest extends TestCase {
51
-	private Principal&MockObject $principal;
52
-	private IUserManager&MockObject $userManager;
53
-	private IGroupManager&MockObject $groupManager;
54
-	private IEventDispatcher&MockObject $dispatcher;
55
-	private IConfig&MockObject $config;
56
-	private RemoteUserPrincipalBackend&MockObject $remoteUserPrincipalBackend;
57
-	private FederationSharingService&MockObject $federationSharingService;
58
-	private Backend $sharingBackend;
59
-	private IDBConnection $db;
60
-	private CardDavBackend $backend;
61
-	private string $dbCardsTable = 'cards';
62
-	private string $dbCardsPropertiesTable = 'cards_properties';
63
-
64
-	public const UNIT_TEST_USER = 'principals/users/carddav-unit-test';
65
-	public const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1';
66
-	public const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group';
67
-
68
-	private $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL
69
-		. 'VERSION:3.0' . PHP_EOL
70
-		. 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
71
-		. 'UID:Test' . PHP_EOL
72
-		. 'FN:Test' . PHP_EOL
73
-		. 'N:Test;;;;' . PHP_EOL
74
-		. 'END:VCARD';
75
-
76
-	private $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL
77
-		. 'VERSION:3.0' . PHP_EOL
78
-		. 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
79
-		. 'UID:Test2' . PHP_EOL
80
-		. 'FN:Test2' . PHP_EOL
81
-		. 'N:Test2;;;;' . PHP_EOL
82
-		. 'END:VCARD';
83
-
84
-	private $vcardTest2 = 'BEGIN:VCARD' . PHP_EOL
85
-		. 'VERSION:3.0' . PHP_EOL
86
-		. 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
87
-		. 'UID:Test3' . PHP_EOL
88
-		. 'FN:Test3' . PHP_EOL
89
-		. 'N:Test3;;;;' . PHP_EOL
90
-		. 'END:VCARD';
91
-
92
-	private $vcardTestNoUID = 'BEGIN:VCARD' . PHP_EOL
93
-		. 'VERSION:3.0' . PHP_EOL
94
-		. 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
95
-		. 'FN:TestNoUID' . PHP_EOL
96
-		. 'N:TestNoUID;;;;' . PHP_EOL
97
-		. 'END:VCARD';
98
-
99
-	protected function setUp(): void {
100
-		parent::setUp();
101
-
102
-		$this->userManager = $this->createMock(IUserManager::class);
103
-		$this->groupManager = $this->createMock(IGroupManager::class);
104
-		$this->config = $this->createMock(IConfig::class);
105
-		$this->principal = $this->getMockBuilder(Principal::class)
106
-			->setConstructorArgs([
107
-				$this->userManager,
108
-				$this->groupManager,
109
-				$this->createMock(IAccountManager::class),
110
-				$this->createMock(ShareManager::class),
111
-				$this->createMock(IUserSession::class),
112
-				$this->createMock(IAppManager::class),
113
-				$this->createMock(ProxyMapper::class),
114
-				$this->createMock(KnownUserService::class),
115
-				$this->config,
116
-				$this->createMock(IFactory::class)
117
-			])
118
-			->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri'])
119
-			->getMock();
120
-		$this->principal->method('getPrincipalByPath')
121
-			->willReturn([
122
-				'uri' => 'principals/best-friend',
123
-				'{DAV:}displayname' => 'User\'s displayname',
124
-			]);
125
-		$this->principal->method('getGroupMembership')
126
-			->withAnyParameters()
127
-			->willReturn([self::UNIT_TEST_GROUP]);
128
-		$this->dispatcher = $this->createMock(IEventDispatcher::class);
129
-		$this->remoteUserPrincipalBackend = $this->createMock(RemoteUserPrincipalBackend::class);
130
-		$this->federationSharingService = $this->createMock(FederationSharingService::class);
131
-
132
-		$this->db = Server::get(IDBConnection::class);
133
-		$this->sharingBackend = new Backend($this->userManager,
134
-			$this->groupManager,
135
-			$this->principal,
136
-			$this->remoteUserPrincipalBackend,
137
-			$this->createMock(ICacheFactory::class),
138
-			new Service(new SharingMapper($this->db)),
139
-			$this->federationSharingService,
140
-			$this->createMock(LoggerInterface::class)
141
-		);
142
-
143
-		$this->backend = new CardDavBackend($this->db,
144
-			$this->principal,
145
-			$this->userManager,
146
-			$this->dispatcher,
147
-			$this->sharingBackend,
148
-			$this->config,
149
-		);
150
-		// start every test with a empty cards_properties and cards table
151
-		$query = $this->db->getQueryBuilder();
152
-		$query->delete('cards_properties')->executeStatement();
153
-		$query = $this->db->getQueryBuilder();
154
-		$query->delete('cards')->executeStatement();
155
-
156
-		$this->principal->method('getGroupMembership')
157
-			->withAnyParameters()
158
-			->willReturn([self::UNIT_TEST_GROUP]);
159
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
160
-		foreach ($books as $book) {
161
-			$this->backend->deleteAddressBook($book['id']);
162
-		}
163
-	}
164
-
165
-	protected function tearDown(): void {
166
-		if (is_null($this->backend)) {
167
-			return;
168
-		}
169
-
170
-		$this->principal->method('getGroupMembership')
171
-			->withAnyParameters()
172
-			->willReturn([self::UNIT_TEST_GROUP]);
173
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
174
-		foreach ($books as $book) {
175
-			$this->backend->deleteAddressBook($book['id']);
176
-		}
177
-
178
-		parent::tearDown();
179
-	}
180
-
181
-	public function testAddressBookOperations(): void {
182
-		// create a new address book
183
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
184
-
185
-		$this->assertEquals(1, $this->backend->getAddressBooksForUserCount(self::UNIT_TEST_USER));
186
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
187
-		$this->assertEquals(1, count($books));
188
-		$this->assertEquals('Example', $books[0]['{DAV:}displayname']);
189
-		$this->assertEquals('User\'s displayname', $books[0]['{http://nextcloud.com/ns}owner-displayname']);
190
-
191
-		// update its display name
192
-		$patch = new PropPatch([
193
-			'{DAV:}displayname' => 'Unit test',
194
-			'{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Addressbook used for unit testing'
195
-		]);
196
-		$this->backend->updateAddressBook($books[0]['id'], $patch);
197
-		$patch->commit();
198
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
199
-		$this->assertEquals(1, count($books));
200
-		$this->assertEquals('Unit test', $books[0]['{DAV:}displayname']);
201
-		$this->assertEquals('Addressbook used for unit testing', $books[0]['{urn:ietf:params:xml:ns:carddav}addressbook-description']);
202
-
203
-		// delete the address book
204
-		$this->backend->deleteAddressBook($books[0]['id']);
205
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
206
-		$this->assertEquals(0, count($books));
207
-	}
208
-
209
-	public function testAddressBookSharing(): void {
210
-		$this->userManager->expects($this->any())
211
-			->method('userExists')
212
-			->willReturn(true);
213
-		$this->groupManager->expects($this->any())
214
-			->method('groupExists')
215
-			->willReturn(true);
216
-		$this->principal->expects(self::atLeastOnce())
217
-			->method('findByUri')
218
-			->willReturnOnConsecutiveCalls(self::UNIT_TEST_USER1, self::UNIT_TEST_GROUP);
219
-
220
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
221
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
222
-		$this->assertEquals(1, count($books));
223
-		$l = $this->createMock(IL10N::class);
224
-		$addressBook = new AddressBook($this->backend, $books[0], $l);
225
-		$this->backend->updateShares($addressBook, [
226
-			[
227
-				'href' => 'principal:' . self::UNIT_TEST_USER1,
228
-			],
229
-			[
230
-				'href' => 'principal:' . self::UNIT_TEST_GROUP,
231
-			]
232
-		], []);
233
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
234
-		$this->assertEquals(1, count($books));
235
-
236
-		// delete the address book
237
-		$this->backend->deleteAddressBook($books[0]['id']);
238
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
239
-		$this->assertEquals(0, count($books));
240
-	}
241
-
242
-	public function testCardOperations(): void {
243
-		/** @var CardDavBackend&MockObject $backend */
244
-		$backend = $this->getMockBuilder(CardDavBackend::class)
245
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
246
-			->onlyMethods(['updateProperties', 'purgeProperties'])
247
-			->getMock();
248
-
249
-		// create a new address book
250
-		$backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
251
-		$books = $backend->getAddressBooksForUser(self::UNIT_TEST_USER);
252
-		$this->assertEquals(1, count($books));
253
-		$bookId = $books[0]['id'];
254
-
255
-		$uri = $this->getUniqueID('card');
256
-		// updateProperties is expected twice, once for createCard and once for updateCard
257
-		$calls = [
258
-			[$bookId, $uri, $this->vcardTest0],
259
-			[$bookId, $uri, $this->vcardTest1],
260
-		];
261
-		$backend->expects($this->exactly(count($calls)))
262
-			->method('updateProperties')
263
-			->willReturnCallback(function () use (&$calls): void {
264
-				$expected = array_shift($calls);
265
-				$this->assertEquals($expected, func_get_args());
266
-			});
267
-
268
-		// Expect event
269
-		$this->dispatcher
270
-			->expects($this->exactly(3))
271
-			->method('dispatchTyped');
272
-
273
-		// create a card
274
-		$backend->createCard($bookId, $uri, $this->vcardTest0);
275
-
276
-		// get all the cards
277
-		$cards = $backend->getCards($bookId);
278
-		$this->assertEquals(1, count($cards));
279
-		$this->assertEquals($this->vcardTest0, $cards[0]['carddata']);
280
-
281
-		// get the cards
282
-		$card = $backend->getCard($bookId, $uri);
283
-		$this->assertNotNull($card);
284
-		$this->assertArrayHasKey('id', $card);
285
-		$this->assertArrayHasKey('uri', $card);
286
-		$this->assertArrayHasKey('lastmodified', $card);
287
-		$this->assertArrayHasKey('etag', $card);
288
-		$this->assertArrayHasKey('size', $card);
289
-		$this->assertEquals($this->vcardTest0, $card['carddata']);
290
-
291
-		// update the card
292
-		$backend->updateCard($bookId, $uri, $this->vcardTest1);
293
-		$card = $backend->getCard($bookId, $uri);
294
-		$this->assertEquals($this->vcardTest1, $card['carddata']);
295
-
296
-		// delete the card
297
-		$backend->expects($this->once())->method('purgeProperties')->with($bookId, $card['id']);
298
-		$backend->deleteCard($bookId, $uri);
299
-		$cards = $backend->getCards($bookId);
300
-		$this->assertEquals(0, count($cards));
301
-	}
302
-
303
-	public function testMultiCard(): void {
304
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
305
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
306
-			->onlyMethods(['updateProperties'])
307
-			->getMock();
308
-
309
-		// create a new address book
310
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
311
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
312
-		$this->assertEquals(1, count($books));
313
-		$bookId = $books[0]['id'];
314
-
315
-		// create a card
316
-		$uri0 = self::getUniqueID('card');
317
-		$this->backend->createCard($bookId, $uri0, $this->vcardTest0);
318
-		$uri1 = self::getUniqueID('card');
319
-		$this->backend->createCard($bookId, $uri1, $this->vcardTest1);
320
-		$uri2 = self::getUniqueID('card');
321
-		$this->backend->createCard($bookId, $uri2, $this->vcardTest2);
322
-
323
-		// get all the cards
324
-		$cards = $this->backend->getCards($bookId);
325
-		$this->assertEquals(3, count($cards));
326
-		usort($cards, function ($a, $b) {
327
-			return $a['id'] < $b['id'] ? -1 : 1;
328
-		});
329
-
330
-		$this->assertEquals($this->vcardTest0, $cards[0]['carddata']);
331
-		$this->assertEquals($this->vcardTest1, $cards[1]['carddata']);
332
-		$this->assertEquals($this->vcardTest2, $cards[2]['carddata']);
333
-
334
-		// get the cards 1 & 2 (not 0)
335
-		$cards = $this->backend->getMultipleCards($bookId, [$uri1, $uri2]);
336
-		$this->assertEquals(2, count($cards));
337
-		usort($cards, function ($a, $b) {
338
-			return $a['id'] < $b['id'] ? -1 : 1;
339
-		});
340
-		foreach ($cards as $index => $card) {
341
-			$this->assertArrayHasKey('id', $card);
342
-			$this->assertArrayHasKey('uri', $card);
343
-			$this->assertArrayHasKey('lastmodified', $card);
344
-			$this->assertArrayHasKey('etag', $card);
345
-			$this->assertArrayHasKey('size', $card);
346
-			$this->assertEquals($this->{ 'vcardTest' . ($index + 1) }, $card['carddata']);
347
-		}
348
-
349
-		// delete the card
350
-		$this->backend->deleteCard($bookId, $uri0);
351
-		$this->backend->deleteCard($bookId, $uri1);
352
-		$this->backend->deleteCard($bookId, $uri2);
353
-		$cards = $this->backend->getCards($bookId);
354
-		$this->assertEquals(0, count($cards));
355
-	}
356
-
357
-	public function testMultipleUIDOnDifferentAddressbooks(): void {
358
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
359
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
360
-			->onlyMethods(['updateProperties'])
361
-			->getMock();
362
-
363
-		// create 2 new address books
364
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
365
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example2', []);
366
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
367
-		$this->assertEquals(2, count($books));
368
-		$bookId0 = $books[0]['id'];
369
-		$bookId1 = $books[1]['id'];
370
-
371
-		// create a card
372
-		$uri0 = $this->getUniqueID('card');
373
-		$this->backend->createCard($bookId0, $uri0, $this->vcardTest0);
374
-
375
-		// create another card with same uid but in second address book
376
-		$uri1 = $this->getUniqueID('card');
377
-		$this->backend->createCard($bookId1, $uri1, $this->vcardTest0);
378
-	}
379
-
380
-	public function testMultipleUIDDenied(): void {
381
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
382
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
383
-			->onlyMethods(['updateProperties'])
384
-			->getMock();
385
-
386
-		// create a new address book
387
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
388
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
389
-		$this->assertEquals(1, count($books));
390
-		$bookId = $books[0]['id'];
391
-
392
-		// create a card
393
-		$uri0 = $this->getUniqueID('card');
394
-		$this->backend->createCard($bookId, $uri0, $this->vcardTest0);
395
-
396
-		// create another card with same uid
397
-		$uri1 = $this->getUniqueID('card');
398
-		$this->expectException(BadRequest::class);
399
-		$test = $this->backend->createCard($bookId, $uri1, $this->vcardTest0);
400
-	}
401
-
402
-	public function testNoValidUID(): void {
403
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
404
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
405
-			->onlyMethods(['updateProperties'])
406
-			->getMock();
407
-
408
-		// create a new address book
409
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
410
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
411
-		$this->assertEquals(1, count($books));
412
-		$bookId = $books[0]['id'];
413
-
414
-		// create a card without uid
415
-		$uri1 = $this->getUniqueID('card');
416
-		$this->expectException(BadRequest::class);
417
-		$test = $this->backend->createCard($bookId, $uri1, $this->vcardTestNoUID);
418
-	}
419
-
420
-	public function testDeleteWithoutCard(): void {
421
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
422
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
423
-			->onlyMethods([
424
-				'getCardId',
425
-				'addChange',
426
-				'purgeProperties',
427
-				'updateProperties',
428
-			])
429
-			->getMock();
430
-
431
-		// create a new address book
432
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
433
-		$books = $this->backend->getUsersOwnAddressBooks(self::UNIT_TEST_USER);
434
-		$this->assertEquals(1, count($books));
435
-
436
-		$bookId = $books[0]['id'];
437
-		$uri = $this->getUniqueID('card');
438
-
439
-		// create a new address book
440
-		$this->backend->expects($this->once())
441
-			->method('getCardId')
442
-			->with($bookId, $uri)
443
-			->willThrowException(new \InvalidArgumentException());
444
-
445
-		$calls = [
446
-			[$bookId, $uri, 1],
447
-			[$bookId, $uri, 3],
448
-		];
449
-		$this->backend->expects($this->exactly(count($calls)))
450
-			->method('addChange')
451
-			->willReturnCallback(function () use (&$calls): void {
452
-				$expected = array_shift($calls);
453
-				$this->assertEquals($expected, func_get_args());
454
-			});
455
-		$this->backend->expects($this->never())
456
-			->method('purgeProperties');
457
-
458
-		// create a card
459
-		$this->backend->createCard($bookId, $uri, $this->vcardTest0);
460
-
461
-		// delete the card
462
-		$this->assertTrue($this->backend->deleteCard($bookId, $uri));
463
-	}
464
-
465
-	public function testSyncSupport(): void {
466
-		$this->backend = $this->getMockBuilder(CardDavBackend::class)
467
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
468
-			->onlyMethods(['updateProperties'])
469
-			->getMock();
470
-
471
-		// create a new address book
472
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
473
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
474
-		$this->assertEquals(1, count($books));
475
-		$bookId = $books[0]['id'];
476
-
477
-		// fist call without synctoken
478
-		$changes = $this->backend->getChangesForAddressBook($bookId, '', 1);
479
-		$syncToken = $changes['syncToken'];
480
-
481
-		// add a change
482
-		$uri0 = $this->getUniqueID('card');
483
-		$this->backend->createCard($bookId, $uri0, $this->vcardTest0);
484
-
485
-		// look for changes
486
-		$changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1);
487
-		$this->assertEquals($uri0, $changes['added'][0]);
488
-	}
489
-
490
-	public function testSharing(): void {
491
-		$this->userManager->expects($this->any())
492
-			->method('userExists')
493
-			->willReturn(true);
494
-		$this->groupManager->expects($this->any())
495
-			->method('groupExists')
496
-			->willReturn(true);
497
-		$this->principal->expects(self::any())
498
-			->method('findByUri')
499
-			->willReturn(self::UNIT_TEST_USER1);
500
-
501
-		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
502
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
503
-		$this->assertEquals(1, count($books));
504
-
505
-		$l = $this->createMock(IL10N::class);
506
-		$exampleBook = new AddressBook($this->backend, $books[0], $l);
507
-		$this->backend->updateShares($exampleBook, [['href' => 'principal:' . self::UNIT_TEST_USER1]], []);
508
-
509
-		$shares = $this->backend->getShares($exampleBook->getResourceId());
510
-		$this->assertEquals(1, count($shares));
511
-
512
-		// adding the same sharee again has no effect
513
-		$this->backend->updateShares($exampleBook, [['href' => 'principal:' . self::UNIT_TEST_USER1]], []);
514
-
515
-		$shares = $this->backend->getShares($exampleBook->getResourceId());
516
-		$this->assertEquals(1, count($shares));
517
-
518
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
519
-		$this->assertEquals(1, count($books));
520
-
521
-		$this->backend->updateShares($exampleBook, [], ['principal:' . self::UNIT_TEST_USER1]);
522
-
523
-		$shares = $this->backend->getShares($exampleBook->getResourceId());
524
-		$this->assertEquals(0, count($shares));
525
-
526
-		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
527
-		$this->assertEquals(0, count($books));
528
-	}
529
-
530
-	public function testUpdateProperties(): void {
531
-		$bookId = 42;
532
-		$cardUri = 'card-uri';
533
-		$cardId = 2;
534
-
535
-		$backend = $this->getMockBuilder(CardDavBackend::class)
536
-			->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
537
-			->onlyMethods(['getCardId'])->getMock();
538
-
539
-		$backend->expects($this->any())->method('getCardId')->willReturn($cardId);
540
-
541
-		// add properties for new vCard
542
-		$vCard = new VCard();
543
-		$vCard->UID = $cardUri;
544
-		$vCard->FN = 'John Doe';
545
-		$this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
546
-
547
-		$query = $this->db->getQueryBuilder();
548
-		$query->select('*')
549
-			->from('cards_properties')
550
-			->orderBy('name');
551
-
552
-		$qResult = $query->executeQuery();
553
-		$result = $qResult->fetchAllAssociative();
554
-		$qResult->closeCursor();
555
-
556
-		$this->assertSame(2, count($result));
557
-
558
-		$this->assertSame('FN', $result[0]['name']);
559
-		$this->assertSame('John Doe', $result[0]['value']);
560
-		$this->assertSame($bookId, (int)$result[0]['addressbookid']);
561
-		$this->assertSame($cardId, (int)$result[0]['cardid']);
562
-
563
-		$this->assertSame('UID', $result[1]['name']);
564
-		$this->assertSame($cardUri, $result[1]['value']);
565
-		$this->assertSame($bookId, (int)$result[1]['addressbookid']);
566
-		$this->assertSame($cardId, (int)$result[1]['cardid']);
567
-
568
-		// update properties for existing vCard
569
-		$vCard = new VCard();
570
-		$vCard->UID = $cardUri;
571
-		$this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
572
-
573
-		$query = $this->db->getQueryBuilder();
574
-		$query->select('*')
575
-			->from('cards_properties');
576
-
577
-		$qResult = $query->executeQuery();
578
-		$result = $qResult->fetchAllAssociative();
579
-		$qResult->closeCursor();
580
-
581
-		$this->assertSame(1, count($result));
582
-
583
-		$this->assertSame('UID', $result[0]['name']);
584
-		$this->assertSame($cardUri, $result[0]['value']);
585
-		$this->assertSame($bookId, (int)$result[0]['addressbookid']);
586
-		$this->assertSame($cardId, (int)$result[0]['cardid']);
587
-	}
588
-
589
-	public function testPurgeProperties(): void {
590
-		$query = $this->db->getQueryBuilder();
591
-		$query->insert('cards_properties')
592
-			->values(
593
-				[
594
-					'addressbookid' => $query->createNamedParameter(1),
595
-					'cardid' => $query->createNamedParameter(1),
596
-					'name' => $query->createNamedParameter('name1'),
597
-					'value' => $query->createNamedParameter('value1'),
598
-					'preferred' => $query->createNamedParameter(0)
599
-				]
600
-			);
601
-		$query->executeStatement();
602
-
603
-		$query = $this->db->getQueryBuilder();
604
-		$query->insert('cards_properties')
605
-			->values(
606
-				[
607
-					'addressbookid' => $query->createNamedParameter(1),
608
-					'cardid' => $query->createNamedParameter(2),
609
-					'name' => $query->createNamedParameter('name2'),
610
-					'value' => $query->createNamedParameter('value2'),
611
-					'preferred' => $query->createNamedParameter(0)
612
-				]
613
-			);
614
-		$query->executeStatement();
615
-
616
-		$this->invokePrivate($this->backend, 'purgeProperties', [1, 1]);
617
-
618
-		$query = $this->db->getQueryBuilder();
619
-		$query->select('*')
620
-			->from('cards_properties');
621
-
622
-		$qResult = $query->executeQuery();
623
-		$result = $qResult->fetchAllAssociative();
624
-		$qResult->closeCursor();
625
-
626
-		$this->assertSame(1, count($result));
627
-		$this->assertSame(1, (int)$result[0]['addressbookid']);
628
-		$this->assertSame(2, (int)$result[0]['cardid']);
629
-	}
630
-
631
-	public function testGetCardId(): void {
632
-		$query = $this->db->getQueryBuilder();
633
-
634
-		$query->insert('cards')
635
-			->values(
636
-				[
637
-					'addressbookid' => $query->createNamedParameter(1),
638
-					'carddata' => $query->createNamedParameter(''),
639
-					'uri' => $query->createNamedParameter('uri'),
640
-					'lastmodified' => $query->createNamedParameter(4738743),
641
-					'etag' => $query->createNamedParameter('etag'),
642
-					'size' => $query->createNamedParameter(120)
643
-				]
644
-			);
645
-		$query->executeStatement();
646
-		$id = $query->getLastInsertId();
647
-
648
-		$this->assertSame($id,
649
-			$this->invokePrivate($this->backend, 'getCardId', [1, 'uri']));
650
-	}
651
-
652
-
653
-	public function testGetCardIdFailed(): void {
654
-		$this->expectException(\InvalidArgumentException::class);
655
-
656
-		$this->invokePrivate($this->backend, 'getCardId', [1, 'uri']);
657
-	}
658
-
659
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestSearch')]
660
-	public function testSearch(string $pattern, array $properties, array $options, array $expected): void {
661
-		/** @var VCard $vCards */
662
-		$vCards = [];
663
-		$vCards[0] = new VCard();
664
-		$vCards[0]->add(new Text($vCards[0], 'UID', 'uid'));
665
-		$vCards[0]->add(new Text($vCards[0], 'FN', 'John Doe'));
666
-		$vCards[0]->add(new Text($vCards[0], 'CLOUD', '[email protected]'));
667
-		$vCards[1] = new VCard();
668
-		$vCards[1]->add(new Text($vCards[1], 'UID', 'uid'));
669
-		$vCards[1]->add(new Text($vCards[1], 'FN', 'John M. Doe'));
670
-		$vCards[2] = new VCard();
671
-		$vCards[2]->add(new Text($vCards[2], 'UID', 'uid'));
672
-		$vCards[2]->add(new Text($vCards[2], 'FN', 'find without options'));
673
-		$vCards[2]->add(new Text($vCards[2], 'CLOUD', '[email protected]'));
674
-
675
-		$vCardIds = [];
676
-		$query = $this->db->getQueryBuilder();
677
-		for ($i = 0; $i < 3; $i++) {
678
-			$query->insert($this->dbCardsTable)
679
-				->values(
680
-					[
681
-						'addressbookid' => $query->createNamedParameter(0),
682
-						'carddata' => $query->createNamedParameter($vCards[$i]->serialize(), IQueryBuilder::PARAM_LOB),
683
-						'uri' => $query->createNamedParameter('uri' . $i),
684
-						'lastmodified' => $query->createNamedParameter(time()),
685
-						'etag' => $query->createNamedParameter('etag' . $i),
686
-						'size' => $query->createNamedParameter(120),
687
-					]
688
-				);
689
-			$query->executeStatement();
690
-			$vCardIds[] = $query->getLastInsertId();
691
-		}
692
-
693
-		$query = $this->db->getQueryBuilder();
694
-		$query->insert($this->dbCardsPropertiesTable)
695
-			->values(
696
-				[
697
-					'addressbookid' => $query->createNamedParameter(0),
698
-					'cardid' => $query->createNamedParameter($vCardIds[0]),
699
-					'name' => $query->createNamedParameter('FN'),
700
-					'value' => $query->createNamedParameter('John Doe'),
701
-					'preferred' => $query->createNamedParameter(0)
702
-				]
703
-			);
704
-		$query->executeStatement();
705
-		$query = $this->db->getQueryBuilder();
706
-		$query->insert($this->dbCardsPropertiesTable)
707
-			->values(
708
-				[
709
-					'addressbookid' => $query->createNamedParameter(0),
710
-					'cardid' => $query->createNamedParameter($vCardIds[0]),
711
-					'name' => $query->createNamedParameter('CLOUD'),
712
-					'value' => $query->createNamedParameter('[email protected]'),
713
-					'preferred' => $query->createNamedParameter(0)
714
-				]
715
-			);
716
-		$query->executeStatement();
717
-		$query = $this->db->getQueryBuilder();
718
-		$query->insert($this->dbCardsPropertiesTable)
719
-			->values(
720
-				[
721
-					'addressbookid' => $query->createNamedParameter(0),
722
-					'cardid' => $query->createNamedParameter($vCardIds[1]),
723
-					'name' => $query->createNamedParameter('FN'),
724
-					'value' => $query->createNamedParameter('John M. Doe'),
725
-					'preferred' => $query->createNamedParameter(0)
726
-				]
727
-			);
728
-		$query->executeStatement();
729
-		$query = $this->db->getQueryBuilder();
730
-		$query->insert($this->dbCardsPropertiesTable)
731
-			->values(
732
-				[
733
-					'addressbookid' => $query->createNamedParameter(0),
734
-					'cardid' => $query->createNamedParameter($vCardIds[2]),
735
-					'name' => $query->createNamedParameter('FN'),
736
-					'value' => $query->createNamedParameter('find without options'),
737
-					'preferred' => $query->createNamedParameter(0)
738
-				]
739
-			);
740
-		$query->executeStatement();
741
-		$query = $this->db->getQueryBuilder();
742
-		$query->insert($this->dbCardsPropertiesTable)
743
-			->values(
744
-				[
745
-					'addressbookid' => $query->createNamedParameter(0),
746
-					'cardid' => $query->createNamedParameter($vCardIds[2]),
747
-					'name' => $query->createNamedParameter('CLOUD'),
748
-					'value' => $query->createNamedParameter('[email protected]'),
749
-					'preferred' => $query->createNamedParameter(0)
750
-				]
751
-			);
752
-		$query->executeStatement();
753
-
754
-		$result = $this->backend->search(0, $pattern, $properties, $options);
755
-
756
-		// check result
757
-		$this->assertSame(count($expected), count($result));
758
-		$found = [];
759
-		foreach ($result as $r) {
760
-			foreach ($expected as $exp) {
761
-				if ($r['uri'] === $exp[0] && strpos($r['carddata'], $exp[1]) > 0) {
762
-					$found[$exp[1]] = true;
763
-					break;
764
-				}
765
-			}
766
-		}
767
-
768
-		$this->assertSame(count($expected), count($found));
769
-	}
770
-
771
-	public static function dataTestSearch(): array {
772
-		return [
773
-			['John', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
774
-			['M. Doe', ['FN'], [], [['uri1', 'John M. Doe']]],
775
-			['Do', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
776
-			'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
777
-			'case insensitive' => ['john', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
778
-			'limit' => ['john', ['FN'], ['limit' => 1], [['uri0', 'John Doe']]],
779
-			'limit and offset' => ['john', ['FN'], ['limit' => 1, 'offset' => 1], [['uri1', 'John M. Doe']]],
780
-			'find "_" escaped' => ['_', ['CLOUD'], [], [['uri2', 'find without options']]],
781
-			'find not empty CLOUD' => ['%_%', ['CLOUD'], ['escape_like_param' => false], [['uri0', 'John Doe'], ['uri2', 'find without options']]],
782
-		];
783
-	}
784
-
785
-	public function testGetCardUri(): void {
786
-		$query = $this->db->getQueryBuilder();
787
-		$query->insert($this->dbCardsTable)
788
-			->values(
789
-				[
790
-					'addressbookid' => $query->createNamedParameter(1),
791
-					'carddata' => $query->createNamedParameter('carddata', IQueryBuilder::PARAM_LOB),
792
-					'uri' => $query->createNamedParameter('uri'),
793
-					'lastmodified' => $query->createNamedParameter(5489543),
794
-					'etag' => $query->createNamedParameter('etag'),
795
-					'size' => $query->createNamedParameter(120),
796
-				]
797
-			);
798
-		$query->executeStatement();
799
-
800
-		$id = $query->getLastInsertId();
801
-
802
-		$this->assertSame('uri', $this->backend->getCardUri($id));
803
-	}
804
-
805
-
806
-	public function testGetCardUriFailed(): void {
807
-		$this->expectException(\InvalidArgumentException::class);
808
-
809
-		$this->backend->getCardUri(1);
810
-	}
811
-
812
-	public function testGetContact(): void {
813
-		$query = $this->db->getQueryBuilder();
814
-		for ($i = 0; $i < 2; $i++) {
815
-			$query->insert($this->dbCardsTable)
816
-				->values(
817
-					[
818
-						'addressbookid' => $query->createNamedParameter($i),
819
-						'carddata' => $query->createNamedParameter('carddata' . $i, IQueryBuilder::PARAM_LOB),
820
-						'uri' => $query->createNamedParameter('uri' . $i),
821
-						'lastmodified' => $query->createNamedParameter(5489543),
822
-						'etag' => $query->createNamedParameter('etag' . $i),
823
-						'size' => $query->createNamedParameter(120),
824
-					]
825
-				);
826
-			$query->executeStatement();
827
-		}
828
-
829
-		$result = $this->backend->getContact(0, 'uri0');
830
-		$this->assertSame(8, count($result));
831
-		$this->assertSame(0, (int)$result['addressbookid']);
832
-		$this->assertSame('uri0', $result['uri']);
833
-		$this->assertSame(5489543, (int)$result['lastmodified']);
834
-		$this->assertSame('"etag0"', $result['etag']);
835
-		$this->assertSame(120, (int)$result['size']);
836
-
837
-		// this shouldn't return any result because 'uri1' is in address book 1
838
-		// see https://github.com/nextcloud/server/issues/229
839
-		$result = $this->backend->getContact(0, 'uri1');
840
-		$this->assertEmpty($result);
841
-	}
842
-
843
-	public function testGetContactFail(): void {
844
-		$this->assertEmpty($this->backend->getContact(0, 'uri'));
845
-	}
846
-
847
-	public function testCollectCardProperties(): void {
848
-		$query = $this->db->getQueryBuilder();
849
-		$query->insert($this->dbCardsPropertiesTable)
850
-			->values(
851
-				[
852
-					'addressbookid' => $query->createNamedParameter(666),
853
-					'cardid' => $query->createNamedParameter(777),
854
-					'name' => $query->createNamedParameter('FN'),
855
-					'value' => $query->createNamedParameter('John Doe'),
856
-					'preferred' => $query->createNamedParameter(0)
857
-				]
858
-			)
859
-			->executeStatement();
860
-
861
-		$result = $this->backend->collectCardProperties(666, 'FN');
862
-		$this->assertEquals(['John Doe'], $result);
863
-	}
864
-
865
-	/**
866
-	 * @throws \OCP\DB\Exception
867
-	 * @throws \Sabre\DAV\Exception\BadRequest
868
-	 */
869
-	public function testPruneOutdatedSyncTokens(): void {
870
-		$addressBookId = $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
871
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, '', 1);
872
-		$syncToken = $changes['syncToken'];
873
-
874
-		$uri = $this->getUniqueID('card');
875
-		$this->backend->createCard($addressBookId, $uri, $this->vcardTest0);
876
-		$this->backend->updateCard($addressBookId, $uri, $this->vcardTest1);
877
-
878
-		// Do not delete anything if week data as old as ts=0
879
-		$deleted = $this->backend->pruneOutdatedSyncTokens(0, 0);
880
-		self::assertSame(0, $deleted);
881
-
882
-		$deleted = $this->backend->pruneOutdatedSyncTokens(0, time());
883
-		// At least one from the object creation and one from the object update
884
-		$this->assertGreaterThanOrEqual(2, $deleted);
885
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 1);
886
-		$this->assertEmpty($changes['added']);
887
-		$this->assertEmpty($changes['modified']);
888
-		$this->assertEmpty($changes['deleted']);
889
-
890
-		// Test that objects remain
891
-
892
-		// Currently changes are empty
893
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
894
-		$this->assertEquals(0, count($changes['added'] + $changes['modified'] + $changes['deleted']));
895
-
896
-		// Create card
897
-		$uri = $this->getUniqueID('card');
898
-		$this->backend->createCard($addressBookId, $uri, $this->vcardTest0);
899
-		// We now have one add
900
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
901
-		$this->assertEquals(1, count($changes['added']));
902
-		$this->assertEmpty($changes['modified']);
903
-		$this->assertEmpty($changes['deleted']);
904
-
905
-		// Update card
906
-		$this->backend->updateCard($addressBookId, $uri, $this->vcardTest1);
907
-		// One add, one modify, but shortened to modify
908
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
909
-		$this->assertEmpty($changes['added']);
910
-		$this->assertEquals(1, count($changes['modified']));
911
-		$this->assertEmpty($changes['deleted']);
912
-
913
-		// Delete all but last change
914
-		$deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
915
-		$this->assertEquals(1, $deleted); // We had two changes before, now one
916
-
917
-		// Only update should remain
918
-		$changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
919
-		$this->assertEmpty($changes['added']);
920
-		$this->assertEquals(1, count($changes['modified']));
921
-		$this->assertEmpty($changes['deleted']);
922
-
923
-		// Check that no crash occurs when prune is called without current changes
924
-		$deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
925
-	}
51
+    private Principal&MockObject $principal;
52
+    private IUserManager&MockObject $userManager;
53
+    private IGroupManager&MockObject $groupManager;
54
+    private IEventDispatcher&MockObject $dispatcher;
55
+    private IConfig&MockObject $config;
56
+    private RemoteUserPrincipalBackend&MockObject $remoteUserPrincipalBackend;
57
+    private FederationSharingService&MockObject $federationSharingService;
58
+    private Backend $sharingBackend;
59
+    private IDBConnection $db;
60
+    private CardDavBackend $backend;
61
+    private string $dbCardsTable = 'cards';
62
+    private string $dbCardsPropertiesTable = 'cards_properties';
63
+
64
+    public const UNIT_TEST_USER = 'principals/users/carddav-unit-test';
65
+    public const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1';
66
+    public const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group';
67
+
68
+    private $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL
69
+        . 'VERSION:3.0' . PHP_EOL
70
+        . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
71
+        . 'UID:Test' . PHP_EOL
72
+        . 'FN:Test' . PHP_EOL
73
+        . 'N:Test;;;;' . PHP_EOL
74
+        . 'END:VCARD';
75
+
76
+    private $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL
77
+        . 'VERSION:3.0' . PHP_EOL
78
+        . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
79
+        . 'UID:Test2' . PHP_EOL
80
+        . 'FN:Test2' . PHP_EOL
81
+        . 'N:Test2;;;;' . PHP_EOL
82
+        . 'END:VCARD';
83
+
84
+    private $vcardTest2 = 'BEGIN:VCARD' . PHP_EOL
85
+        . 'VERSION:3.0' . PHP_EOL
86
+        . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
87
+        . 'UID:Test3' . PHP_EOL
88
+        . 'FN:Test3' . PHP_EOL
89
+        . 'N:Test3;;;;' . PHP_EOL
90
+        . 'END:VCARD';
91
+
92
+    private $vcardTestNoUID = 'BEGIN:VCARD' . PHP_EOL
93
+        . 'VERSION:3.0' . PHP_EOL
94
+        . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL
95
+        . 'FN:TestNoUID' . PHP_EOL
96
+        . 'N:TestNoUID;;;;' . PHP_EOL
97
+        . 'END:VCARD';
98
+
99
+    protected function setUp(): void {
100
+        parent::setUp();
101
+
102
+        $this->userManager = $this->createMock(IUserManager::class);
103
+        $this->groupManager = $this->createMock(IGroupManager::class);
104
+        $this->config = $this->createMock(IConfig::class);
105
+        $this->principal = $this->getMockBuilder(Principal::class)
106
+            ->setConstructorArgs([
107
+                $this->userManager,
108
+                $this->groupManager,
109
+                $this->createMock(IAccountManager::class),
110
+                $this->createMock(ShareManager::class),
111
+                $this->createMock(IUserSession::class),
112
+                $this->createMock(IAppManager::class),
113
+                $this->createMock(ProxyMapper::class),
114
+                $this->createMock(KnownUserService::class),
115
+                $this->config,
116
+                $this->createMock(IFactory::class)
117
+            ])
118
+            ->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri'])
119
+            ->getMock();
120
+        $this->principal->method('getPrincipalByPath')
121
+            ->willReturn([
122
+                'uri' => 'principals/best-friend',
123
+                '{DAV:}displayname' => 'User\'s displayname',
124
+            ]);
125
+        $this->principal->method('getGroupMembership')
126
+            ->withAnyParameters()
127
+            ->willReturn([self::UNIT_TEST_GROUP]);
128
+        $this->dispatcher = $this->createMock(IEventDispatcher::class);
129
+        $this->remoteUserPrincipalBackend = $this->createMock(RemoteUserPrincipalBackend::class);
130
+        $this->federationSharingService = $this->createMock(FederationSharingService::class);
131
+
132
+        $this->db = Server::get(IDBConnection::class);
133
+        $this->sharingBackend = new Backend($this->userManager,
134
+            $this->groupManager,
135
+            $this->principal,
136
+            $this->remoteUserPrincipalBackend,
137
+            $this->createMock(ICacheFactory::class),
138
+            new Service(new SharingMapper($this->db)),
139
+            $this->federationSharingService,
140
+            $this->createMock(LoggerInterface::class)
141
+        );
142
+
143
+        $this->backend = new CardDavBackend($this->db,
144
+            $this->principal,
145
+            $this->userManager,
146
+            $this->dispatcher,
147
+            $this->sharingBackend,
148
+            $this->config,
149
+        );
150
+        // start every test with a empty cards_properties and cards table
151
+        $query = $this->db->getQueryBuilder();
152
+        $query->delete('cards_properties')->executeStatement();
153
+        $query = $this->db->getQueryBuilder();
154
+        $query->delete('cards')->executeStatement();
155
+
156
+        $this->principal->method('getGroupMembership')
157
+            ->withAnyParameters()
158
+            ->willReturn([self::UNIT_TEST_GROUP]);
159
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
160
+        foreach ($books as $book) {
161
+            $this->backend->deleteAddressBook($book['id']);
162
+        }
163
+    }
164
+
165
+    protected function tearDown(): void {
166
+        if (is_null($this->backend)) {
167
+            return;
168
+        }
169
+
170
+        $this->principal->method('getGroupMembership')
171
+            ->withAnyParameters()
172
+            ->willReturn([self::UNIT_TEST_GROUP]);
173
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
174
+        foreach ($books as $book) {
175
+            $this->backend->deleteAddressBook($book['id']);
176
+        }
177
+
178
+        parent::tearDown();
179
+    }
180
+
181
+    public function testAddressBookOperations(): void {
182
+        // create a new address book
183
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
184
+
185
+        $this->assertEquals(1, $this->backend->getAddressBooksForUserCount(self::UNIT_TEST_USER));
186
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
187
+        $this->assertEquals(1, count($books));
188
+        $this->assertEquals('Example', $books[0]['{DAV:}displayname']);
189
+        $this->assertEquals('User\'s displayname', $books[0]['{http://nextcloud.com/ns}owner-displayname']);
190
+
191
+        // update its display name
192
+        $patch = new PropPatch([
193
+            '{DAV:}displayname' => 'Unit test',
194
+            '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Addressbook used for unit testing'
195
+        ]);
196
+        $this->backend->updateAddressBook($books[0]['id'], $patch);
197
+        $patch->commit();
198
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
199
+        $this->assertEquals(1, count($books));
200
+        $this->assertEquals('Unit test', $books[0]['{DAV:}displayname']);
201
+        $this->assertEquals('Addressbook used for unit testing', $books[0]['{urn:ietf:params:xml:ns:carddav}addressbook-description']);
202
+
203
+        // delete the address book
204
+        $this->backend->deleteAddressBook($books[0]['id']);
205
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
206
+        $this->assertEquals(0, count($books));
207
+    }
208
+
209
+    public function testAddressBookSharing(): void {
210
+        $this->userManager->expects($this->any())
211
+            ->method('userExists')
212
+            ->willReturn(true);
213
+        $this->groupManager->expects($this->any())
214
+            ->method('groupExists')
215
+            ->willReturn(true);
216
+        $this->principal->expects(self::atLeastOnce())
217
+            ->method('findByUri')
218
+            ->willReturnOnConsecutiveCalls(self::UNIT_TEST_USER1, self::UNIT_TEST_GROUP);
219
+
220
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
221
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
222
+        $this->assertEquals(1, count($books));
223
+        $l = $this->createMock(IL10N::class);
224
+        $addressBook = new AddressBook($this->backend, $books[0], $l);
225
+        $this->backend->updateShares($addressBook, [
226
+            [
227
+                'href' => 'principal:' . self::UNIT_TEST_USER1,
228
+            ],
229
+            [
230
+                'href' => 'principal:' . self::UNIT_TEST_GROUP,
231
+            ]
232
+        ], []);
233
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
234
+        $this->assertEquals(1, count($books));
235
+
236
+        // delete the address book
237
+        $this->backend->deleteAddressBook($books[0]['id']);
238
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
239
+        $this->assertEquals(0, count($books));
240
+    }
241
+
242
+    public function testCardOperations(): void {
243
+        /** @var CardDavBackend&MockObject $backend */
244
+        $backend = $this->getMockBuilder(CardDavBackend::class)
245
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
246
+            ->onlyMethods(['updateProperties', 'purgeProperties'])
247
+            ->getMock();
248
+
249
+        // create a new address book
250
+        $backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
251
+        $books = $backend->getAddressBooksForUser(self::UNIT_TEST_USER);
252
+        $this->assertEquals(1, count($books));
253
+        $bookId = $books[0]['id'];
254
+
255
+        $uri = $this->getUniqueID('card');
256
+        // updateProperties is expected twice, once for createCard and once for updateCard
257
+        $calls = [
258
+            [$bookId, $uri, $this->vcardTest0],
259
+            [$bookId, $uri, $this->vcardTest1],
260
+        ];
261
+        $backend->expects($this->exactly(count($calls)))
262
+            ->method('updateProperties')
263
+            ->willReturnCallback(function () use (&$calls): void {
264
+                $expected = array_shift($calls);
265
+                $this->assertEquals($expected, func_get_args());
266
+            });
267
+
268
+        // Expect event
269
+        $this->dispatcher
270
+            ->expects($this->exactly(3))
271
+            ->method('dispatchTyped');
272
+
273
+        // create a card
274
+        $backend->createCard($bookId, $uri, $this->vcardTest0);
275
+
276
+        // get all the cards
277
+        $cards = $backend->getCards($bookId);
278
+        $this->assertEquals(1, count($cards));
279
+        $this->assertEquals($this->vcardTest0, $cards[0]['carddata']);
280
+
281
+        // get the cards
282
+        $card = $backend->getCard($bookId, $uri);
283
+        $this->assertNotNull($card);
284
+        $this->assertArrayHasKey('id', $card);
285
+        $this->assertArrayHasKey('uri', $card);
286
+        $this->assertArrayHasKey('lastmodified', $card);
287
+        $this->assertArrayHasKey('etag', $card);
288
+        $this->assertArrayHasKey('size', $card);
289
+        $this->assertEquals($this->vcardTest0, $card['carddata']);
290
+
291
+        // update the card
292
+        $backend->updateCard($bookId, $uri, $this->vcardTest1);
293
+        $card = $backend->getCard($bookId, $uri);
294
+        $this->assertEquals($this->vcardTest1, $card['carddata']);
295
+
296
+        // delete the card
297
+        $backend->expects($this->once())->method('purgeProperties')->with($bookId, $card['id']);
298
+        $backend->deleteCard($bookId, $uri);
299
+        $cards = $backend->getCards($bookId);
300
+        $this->assertEquals(0, count($cards));
301
+    }
302
+
303
+    public function testMultiCard(): void {
304
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
305
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
306
+            ->onlyMethods(['updateProperties'])
307
+            ->getMock();
308
+
309
+        // create a new address book
310
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
311
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
312
+        $this->assertEquals(1, count($books));
313
+        $bookId = $books[0]['id'];
314
+
315
+        // create a card
316
+        $uri0 = self::getUniqueID('card');
317
+        $this->backend->createCard($bookId, $uri0, $this->vcardTest0);
318
+        $uri1 = self::getUniqueID('card');
319
+        $this->backend->createCard($bookId, $uri1, $this->vcardTest1);
320
+        $uri2 = self::getUniqueID('card');
321
+        $this->backend->createCard($bookId, $uri2, $this->vcardTest2);
322
+
323
+        // get all the cards
324
+        $cards = $this->backend->getCards($bookId);
325
+        $this->assertEquals(3, count($cards));
326
+        usort($cards, function ($a, $b) {
327
+            return $a['id'] < $b['id'] ? -1 : 1;
328
+        });
329
+
330
+        $this->assertEquals($this->vcardTest0, $cards[0]['carddata']);
331
+        $this->assertEquals($this->vcardTest1, $cards[1]['carddata']);
332
+        $this->assertEquals($this->vcardTest2, $cards[2]['carddata']);
333
+
334
+        // get the cards 1 & 2 (not 0)
335
+        $cards = $this->backend->getMultipleCards($bookId, [$uri1, $uri2]);
336
+        $this->assertEquals(2, count($cards));
337
+        usort($cards, function ($a, $b) {
338
+            return $a['id'] < $b['id'] ? -1 : 1;
339
+        });
340
+        foreach ($cards as $index => $card) {
341
+            $this->assertArrayHasKey('id', $card);
342
+            $this->assertArrayHasKey('uri', $card);
343
+            $this->assertArrayHasKey('lastmodified', $card);
344
+            $this->assertArrayHasKey('etag', $card);
345
+            $this->assertArrayHasKey('size', $card);
346
+            $this->assertEquals($this->{ 'vcardTest' . ($index + 1) }, $card['carddata']);
347
+        }
348
+
349
+        // delete the card
350
+        $this->backend->deleteCard($bookId, $uri0);
351
+        $this->backend->deleteCard($bookId, $uri1);
352
+        $this->backend->deleteCard($bookId, $uri2);
353
+        $cards = $this->backend->getCards($bookId);
354
+        $this->assertEquals(0, count($cards));
355
+    }
356
+
357
+    public function testMultipleUIDOnDifferentAddressbooks(): void {
358
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
359
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config])
360
+            ->onlyMethods(['updateProperties'])
361
+            ->getMock();
362
+
363
+        // create 2 new address books
364
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
365
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example2', []);
366
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
367
+        $this->assertEquals(2, count($books));
368
+        $bookId0 = $books[0]['id'];
369
+        $bookId1 = $books[1]['id'];
370
+
371
+        // create a card
372
+        $uri0 = $this->getUniqueID('card');
373
+        $this->backend->createCard($bookId0, $uri0, $this->vcardTest0);
374
+
375
+        // create another card with same uid but in second address book
376
+        $uri1 = $this->getUniqueID('card');
377
+        $this->backend->createCard($bookId1, $uri1, $this->vcardTest0);
378
+    }
379
+
380
+    public function testMultipleUIDDenied(): void {
381
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
382
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
383
+            ->onlyMethods(['updateProperties'])
384
+            ->getMock();
385
+
386
+        // create a new address book
387
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
388
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
389
+        $this->assertEquals(1, count($books));
390
+        $bookId = $books[0]['id'];
391
+
392
+        // create a card
393
+        $uri0 = $this->getUniqueID('card');
394
+        $this->backend->createCard($bookId, $uri0, $this->vcardTest0);
395
+
396
+        // create another card with same uid
397
+        $uri1 = $this->getUniqueID('card');
398
+        $this->expectException(BadRequest::class);
399
+        $test = $this->backend->createCard($bookId, $uri1, $this->vcardTest0);
400
+    }
401
+
402
+    public function testNoValidUID(): void {
403
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
404
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
405
+            ->onlyMethods(['updateProperties'])
406
+            ->getMock();
407
+
408
+        // create a new address book
409
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
410
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
411
+        $this->assertEquals(1, count($books));
412
+        $bookId = $books[0]['id'];
413
+
414
+        // create a card without uid
415
+        $uri1 = $this->getUniqueID('card');
416
+        $this->expectException(BadRequest::class);
417
+        $test = $this->backend->createCard($bookId, $uri1, $this->vcardTestNoUID);
418
+    }
419
+
420
+    public function testDeleteWithoutCard(): void {
421
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
422
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
423
+            ->onlyMethods([
424
+                'getCardId',
425
+                'addChange',
426
+                'purgeProperties',
427
+                'updateProperties',
428
+            ])
429
+            ->getMock();
430
+
431
+        // create a new address book
432
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
433
+        $books = $this->backend->getUsersOwnAddressBooks(self::UNIT_TEST_USER);
434
+        $this->assertEquals(1, count($books));
435
+
436
+        $bookId = $books[0]['id'];
437
+        $uri = $this->getUniqueID('card');
438
+
439
+        // create a new address book
440
+        $this->backend->expects($this->once())
441
+            ->method('getCardId')
442
+            ->with($bookId, $uri)
443
+            ->willThrowException(new \InvalidArgumentException());
444
+
445
+        $calls = [
446
+            [$bookId, $uri, 1],
447
+            [$bookId, $uri, 3],
448
+        ];
449
+        $this->backend->expects($this->exactly(count($calls)))
450
+            ->method('addChange')
451
+            ->willReturnCallback(function () use (&$calls): void {
452
+                $expected = array_shift($calls);
453
+                $this->assertEquals($expected, func_get_args());
454
+            });
455
+        $this->backend->expects($this->never())
456
+            ->method('purgeProperties');
457
+
458
+        // create a card
459
+        $this->backend->createCard($bookId, $uri, $this->vcardTest0);
460
+
461
+        // delete the card
462
+        $this->assertTrue($this->backend->deleteCard($bookId, $uri));
463
+    }
464
+
465
+    public function testSyncSupport(): void {
466
+        $this->backend = $this->getMockBuilder(CardDavBackend::class)
467
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
468
+            ->onlyMethods(['updateProperties'])
469
+            ->getMock();
470
+
471
+        // create a new address book
472
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
473
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
474
+        $this->assertEquals(1, count($books));
475
+        $bookId = $books[0]['id'];
476
+
477
+        // fist call without synctoken
478
+        $changes = $this->backend->getChangesForAddressBook($bookId, '', 1);
479
+        $syncToken = $changes['syncToken'];
480
+
481
+        // add a change
482
+        $uri0 = $this->getUniqueID('card');
483
+        $this->backend->createCard($bookId, $uri0, $this->vcardTest0);
484
+
485
+        // look for changes
486
+        $changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1);
487
+        $this->assertEquals($uri0, $changes['added'][0]);
488
+    }
489
+
490
+    public function testSharing(): void {
491
+        $this->userManager->expects($this->any())
492
+            ->method('userExists')
493
+            ->willReturn(true);
494
+        $this->groupManager->expects($this->any())
495
+            ->method('groupExists')
496
+            ->willReturn(true);
497
+        $this->principal->expects(self::any())
498
+            ->method('findByUri')
499
+            ->willReturn(self::UNIT_TEST_USER1);
500
+
501
+        $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
502
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
503
+        $this->assertEquals(1, count($books));
504
+
505
+        $l = $this->createMock(IL10N::class);
506
+        $exampleBook = new AddressBook($this->backend, $books[0], $l);
507
+        $this->backend->updateShares($exampleBook, [['href' => 'principal:' . self::UNIT_TEST_USER1]], []);
508
+
509
+        $shares = $this->backend->getShares($exampleBook->getResourceId());
510
+        $this->assertEquals(1, count($shares));
511
+
512
+        // adding the same sharee again has no effect
513
+        $this->backend->updateShares($exampleBook, [['href' => 'principal:' . self::UNIT_TEST_USER1]], []);
514
+
515
+        $shares = $this->backend->getShares($exampleBook->getResourceId());
516
+        $this->assertEquals(1, count($shares));
517
+
518
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
519
+        $this->assertEquals(1, count($books));
520
+
521
+        $this->backend->updateShares($exampleBook, [], ['principal:' . self::UNIT_TEST_USER1]);
522
+
523
+        $shares = $this->backend->getShares($exampleBook->getResourceId());
524
+        $this->assertEquals(0, count($shares));
525
+
526
+        $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
527
+        $this->assertEquals(0, count($books));
528
+    }
529
+
530
+    public function testUpdateProperties(): void {
531
+        $bookId = 42;
532
+        $cardUri = 'card-uri';
533
+        $cardId = 2;
534
+
535
+        $backend = $this->getMockBuilder(CardDavBackend::class)
536
+            ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
537
+            ->onlyMethods(['getCardId'])->getMock();
538
+
539
+        $backend->expects($this->any())->method('getCardId')->willReturn($cardId);
540
+
541
+        // add properties for new vCard
542
+        $vCard = new VCard();
543
+        $vCard->UID = $cardUri;
544
+        $vCard->FN = 'John Doe';
545
+        $this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
546
+
547
+        $query = $this->db->getQueryBuilder();
548
+        $query->select('*')
549
+            ->from('cards_properties')
550
+            ->orderBy('name');
551
+
552
+        $qResult = $query->executeQuery();
553
+        $result = $qResult->fetchAllAssociative();
554
+        $qResult->closeCursor();
555
+
556
+        $this->assertSame(2, count($result));
557
+
558
+        $this->assertSame('FN', $result[0]['name']);
559
+        $this->assertSame('John Doe', $result[0]['value']);
560
+        $this->assertSame($bookId, (int)$result[0]['addressbookid']);
561
+        $this->assertSame($cardId, (int)$result[0]['cardid']);
562
+
563
+        $this->assertSame('UID', $result[1]['name']);
564
+        $this->assertSame($cardUri, $result[1]['value']);
565
+        $this->assertSame($bookId, (int)$result[1]['addressbookid']);
566
+        $this->assertSame($cardId, (int)$result[1]['cardid']);
567
+
568
+        // update properties for existing vCard
569
+        $vCard = new VCard();
570
+        $vCard->UID = $cardUri;
571
+        $this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
572
+
573
+        $query = $this->db->getQueryBuilder();
574
+        $query->select('*')
575
+            ->from('cards_properties');
576
+
577
+        $qResult = $query->executeQuery();
578
+        $result = $qResult->fetchAllAssociative();
579
+        $qResult->closeCursor();
580
+
581
+        $this->assertSame(1, count($result));
582
+
583
+        $this->assertSame('UID', $result[0]['name']);
584
+        $this->assertSame($cardUri, $result[0]['value']);
585
+        $this->assertSame($bookId, (int)$result[0]['addressbookid']);
586
+        $this->assertSame($cardId, (int)$result[0]['cardid']);
587
+    }
588
+
589
+    public function testPurgeProperties(): void {
590
+        $query = $this->db->getQueryBuilder();
591
+        $query->insert('cards_properties')
592
+            ->values(
593
+                [
594
+                    'addressbookid' => $query->createNamedParameter(1),
595
+                    'cardid' => $query->createNamedParameter(1),
596
+                    'name' => $query->createNamedParameter('name1'),
597
+                    'value' => $query->createNamedParameter('value1'),
598
+                    'preferred' => $query->createNamedParameter(0)
599
+                ]
600
+            );
601
+        $query->executeStatement();
602
+
603
+        $query = $this->db->getQueryBuilder();
604
+        $query->insert('cards_properties')
605
+            ->values(
606
+                [
607
+                    'addressbookid' => $query->createNamedParameter(1),
608
+                    'cardid' => $query->createNamedParameter(2),
609
+                    'name' => $query->createNamedParameter('name2'),
610
+                    'value' => $query->createNamedParameter('value2'),
611
+                    'preferred' => $query->createNamedParameter(0)
612
+                ]
613
+            );
614
+        $query->executeStatement();
615
+
616
+        $this->invokePrivate($this->backend, 'purgeProperties', [1, 1]);
617
+
618
+        $query = $this->db->getQueryBuilder();
619
+        $query->select('*')
620
+            ->from('cards_properties');
621
+
622
+        $qResult = $query->executeQuery();
623
+        $result = $qResult->fetchAllAssociative();
624
+        $qResult->closeCursor();
625
+
626
+        $this->assertSame(1, count($result));
627
+        $this->assertSame(1, (int)$result[0]['addressbookid']);
628
+        $this->assertSame(2, (int)$result[0]['cardid']);
629
+    }
630
+
631
+    public function testGetCardId(): void {
632
+        $query = $this->db->getQueryBuilder();
633
+
634
+        $query->insert('cards')
635
+            ->values(
636
+                [
637
+                    'addressbookid' => $query->createNamedParameter(1),
638
+                    'carddata' => $query->createNamedParameter(''),
639
+                    'uri' => $query->createNamedParameter('uri'),
640
+                    'lastmodified' => $query->createNamedParameter(4738743),
641
+                    'etag' => $query->createNamedParameter('etag'),
642
+                    'size' => $query->createNamedParameter(120)
643
+                ]
644
+            );
645
+        $query->executeStatement();
646
+        $id = $query->getLastInsertId();
647
+
648
+        $this->assertSame($id,
649
+            $this->invokePrivate($this->backend, 'getCardId', [1, 'uri']));
650
+    }
651
+
652
+
653
+    public function testGetCardIdFailed(): void {
654
+        $this->expectException(\InvalidArgumentException::class);
655
+
656
+        $this->invokePrivate($this->backend, 'getCardId', [1, 'uri']);
657
+    }
658
+
659
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSearch')]
660
+    public function testSearch(string $pattern, array $properties, array $options, array $expected): void {
661
+        /** @var VCard $vCards */
662
+        $vCards = [];
663
+        $vCards[0] = new VCard();
664
+        $vCards[0]->add(new Text($vCards[0], 'UID', 'uid'));
665
+        $vCards[0]->add(new Text($vCards[0], 'FN', 'John Doe'));
666
+        $vCards[0]->add(new Text($vCards[0], 'CLOUD', '[email protected]'));
667
+        $vCards[1] = new VCard();
668
+        $vCards[1]->add(new Text($vCards[1], 'UID', 'uid'));
669
+        $vCards[1]->add(new Text($vCards[1], 'FN', 'John M. Doe'));
670
+        $vCards[2] = new VCard();
671
+        $vCards[2]->add(new Text($vCards[2], 'UID', 'uid'));
672
+        $vCards[2]->add(new Text($vCards[2], 'FN', 'find without options'));
673
+        $vCards[2]->add(new Text($vCards[2], 'CLOUD', '[email protected]'));
674
+
675
+        $vCardIds = [];
676
+        $query = $this->db->getQueryBuilder();
677
+        for ($i = 0; $i < 3; $i++) {
678
+            $query->insert($this->dbCardsTable)
679
+                ->values(
680
+                    [
681
+                        'addressbookid' => $query->createNamedParameter(0),
682
+                        'carddata' => $query->createNamedParameter($vCards[$i]->serialize(), IQueryBuilder::PARAM_LOB),
683
+                        'uri' => $query->createNamedParameter('uri' . $i),
684
+                        'lastmodified' => $query->createNamedParameter(time()),
685
+                        'etag' => $query->createNamedParameter('etag' . $i),
686
+                        'size' => $query->createNamedParameter(120),
687
+                    ]
688
+                );
689
+            $query->executeStatement();
690
+            $vCardIds[] = $query->getLastInsertId();
691
+        }
692
+
693
+        $query = $this->db->getQueryBuilder();
694
+        $query->insert($this->dbCardsPropertiesTable)
695
+            ->values(
696
+                [
697
+                    'addressbookid' => $query->createNamedParameter(0),
698
+                    'cardid' => $query->createNamedParameter($vCardIds[0]),
699
+                    'name' => $query->createNamedParameter('FN'),
700
+                    'value' => $query->createNamedParameter('John Doe'),
701
+                    'preferred' => $query->createNamedParameter(0)
702
+                ]
703
+            );
704
+        $query->executeStatement();
705
+        $query = $this->db->getQueryBuilder();
706
+        $query->insert($this->dbCardsPropertiesTable)
707
+            ->values(
708
+                [
709
+                    'addressbookid' => $query->createNamedParameter(0),
710
+                    'cardid' => $query->createNamedParameter($vCardIds[0]),
711
+                    'name' => $query->createNamedParameter('CLOUD'),
712
+                    'value' => $query->createNamedParameter('[email protected]'),
713
+                    'preferred' => $query->createNamedParameter(0)
714
+                ]
715
+            );
716
+        $query->executeStatement();
717
+        $query = $this->db->getQueryBuilder();
718
+        $query->insert($this->dbCardsPropertiesTable)
719
+            ->values(
720
+                [
721
+                    'addressbookid' => $query->createNamedParameter(0),
722
+                    'cardid' => $query->createNamedParameter($vCardIds[1]),
723
+                    'name' => $query->createNamedParameter('FN'),
724
+                    'value' => $query->createNamedParameter('John M. Doe'),
725
+                    'preferred' => $query->createNamedParameter(0)
726
+                ]
727
+            );
728
+        $query->executeStatement();
729
+        $query = $this->db->getQueryBuilder();
730
+        $query->insert($this->dbCardsPropertiesTable)
731
+            ->values(
732
+                [
733
+                    'addressbookid' => $query->createNamedParameter(0),
734
+                    'cardid' => $query->createNamedParameter($vCardIds[2]),
735
+                    'name' => $query->createNamedParameter('FN'),
736
+                    'value' => $query->createNamedParameter('find without options'),
737
+                    'preferred' => $query->createNamedParameter(0)
738
+                ]
739
+            );
740
+        $query->executeStatement();
741
+        $query = $this->db->getQueryBuilder();
742
+        $query->insert($this->dbCardsPropertiesTable)
743
+            ->values(
744
+                [
745
+                    'addressbookid' => $query->createNamedParameter(0),
746
+                    'cardid' => $query->createNamedParameter($vCardIds[2]),
747
+                    'name' => $query->createNamedParameter('CLOUD'),
748
+                    'value' => $query->createNamedParameter('[email protected]'),
749
+                    'preferred' => $query->createNamedParameter(0)
750
+                ]
751
+            );
752
+        $query->executeStatement();
753
+
754
+        $result = $this->backend->search(0, $pattern, $properties, $options);
755
+
756
+        // check result
757
+        $this->assertSame(count($expected), count($result));
758
+        $found = [];
759
+        foreach ($result as $r) {
760
+            foreach ($expected as $exp) {
761
+                if ($r['uri'] === $exp[0] && strpos($r['carddata'], $exp[1]) > 0) {
762
+                    $found[$exp[1]] = true;
763
+                    break;
764
+                }
765
+            }
766
+        }
767
+
768
+        $this->assertSame(count($expected), count($found));
769
+    }
770
+
771
+    public static function dataTestSearch(): array {
772
+        return [
773
+            ['John', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
774
+            ['M. Doe', ['FN'], [], [['uri1', 'John M. Doe']]],
775
+            ['Do', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
776
+            'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
777
+            'case insensitive' => ['john', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
778
+            'limit' => ['john', ['FN'], ['limit' => 1], [['uri0', 'John Doe']]],
779
+            'limit and offset' => ['john', ['FN'], ['limit' => 1, 'offset' => 1], [['uri1', 'John M. Doe']]],
780
+            'find "_" escaped' => ['_', ['CLOUD'], [], [['uri2', 'find without options']]],
781
+            'find not empty CLOUD' => ['%_%', ['CLOUD'], ['escape_like_param' => false], [['uri0', 'John Doe'], ['uri2', 'find without options']]],
782
+        ];
783
+    }
784
+
785
+    public function testGetCardUri(): void {
786
+        $query = $this->db->getQueryBuilder();
787
+        $query->insert($this->dbCardsTable)
788
+            ->values(
789
+                [
790
+                    'addressbookid' => $query->createNamedParameter(1),
791
+                    'carddata' => $query->createNamedParameter('carddata', IQueryBuilder::PARAM_LOB),
792
+                    'uri' => $query->createNamedParameter('uri'),
793
+                    'lastmodified' => $query->createNamedParameter(5489543),
794
+                    'etag' => $query->createNamedParameter('etag'),
795
+                    'size' => $query->createNamedParameter(120),
796
+                ]
797
+            );
798
+        $query->executeStatement();
799
+
800
+        $id = $query->getLastInsertId();
801
+
802
+        $this->assertSame('uri', $this->backend->getCardUri($id));
803
+    }
804
+
805
+
806
+    public function testGetCardUriFailed(): void {
807
+        $this->expectException(\InvalidArgumentException::class);
808
+
809
+        $this->backend->getCardUri(1);
810
+    }
811
+
812
+    public function testGetContact(): void {
813
+        $query = $this->db->getQueryBuilder();
814
+        for ($i = 0; $i < 2; $i++) {
815
+            $query->insert($this->dbCardsTable)
816
+                ->values(
817
+                    [
818
+                        'addressbookid' => $query->createNamedParameter($i),
819
+                        'carddata' => $query->createNamedParameter('carddata' . $i, IQueryBuilder::PARAM_LOB),
820
+                        'uri' => $query->createNamedParameter('uri' . $i),
821
+                        'lastmodified' => $query->createNamedParameter(5489543),
822
+                        'etag' => $query->createNamedParameter('etag' . $i),
823
+                        'size' => $query->createNamedParameter(120),
824
+                    ]
825
+                );
826
+            $query->executeStatement();
827
+        }
828
+
829
+        $result = $this->backend->getContact(0, 'uri0');
830
+        $this->assertSame(8, count($result));
831
+        $this->assertSame(0, (int)$result['addressbookid']);
832
+        $this->assertSame('uri0', $result['uri']);
833
+        $this->assertSame(5489543, (int)$result['lastmodified']);
834
+        $this->assertSame('"etag0"', $result['etag']);
835
+        $this->assertSame(120, (int)$result['size']);
836
+
837
+        // this shouldn't return any result because 'uri1' is in address book 1
838
+        // see https://github.com/nextcloud/server/issues/229
839
+        $result = $this->backend->getContact(0, 'uri1');
840
+        $this->assertEmpty($result);
841
+    }
842
+
843
+    public function testGetContactFail(): void {
844
+        $this->assertEmpty($this->backend->getContact(0, 'uri'));
845
+    }
846
+
847
+    public function testCollectCardProperties(): void {
848
+        $query = $this->db->getQueryBuilder();
849
+        $query->insert($this->dbCardsPropertiesTable)
850
+            ->values(
851
+                [
852
+                    'addressbookid' => $query->createNamedParameter(666),
853
+                    'cardid' => $query->createNamedParameter(777),
854
+                    'name' => $query->createNamedParameter('FN'),
855
+                    'value' => $query->createNamedParameter('John Doe'),
856
+                    'preferred' => $query->createNamedParameter(0)
857
+                ]
858
+            )
859
+            ->executeStatement();
860
+
861
+        $result = $this->backend->collectCardProperties(666, 'FN');
862
+        $this->assertEquals(['John Doe'], $result);
863
+    }
864
+
865
+    /**
866
+     * @throws \OCP\DB\Exception
867
+     * @throws \Sabre\DAV\Exception\BadRequest
868
+     */
869
+    public function testPruneOutdatedSyncTokens(): void {
870
+        $addressBookId = $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
871
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, '', 1);
872
+        $syncToken = $changes['syncToken'];
873
+
874
+        $uri = $this->getUniqueID('card');
875
+        $this->backend->createCard($addressBookId, $uri, $this->vcardTest0);
876
+        $this->backend->updateCard($addressBookId, $uri, $this->vcardTest1);
877
+
878
+        // Do not delete anything if week data as old as ts=0
879
+        $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0);
880
+        self::assertSame(0, $deleted);
881
+
882
+        $deleted = $this->backend->pruneOutdatedSyncTokens(0, time());
883
+        // At least one from the object creation and one from the object update
884
+        $this->assertGreaterThanOrEqual(2, $deleted);
885
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 1);
886
+        $this->assertEmpty($changes['added']);
887
+        $this->assertEmpty($changes['modified']);
888
+        $this->assertEmpty($changes['deleted']);
889
+
890
+        // Test that objects remain
891
+
892
+        // Currently changes are empty
893
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
894
+        $this->assertEquals(0, count($changes['added'] + $changes['modified'] + $changes['deleted']));
895
+
896
+        // Create card
897
+        $uri = $this->getUniqueID('card');
898
+        $this->backend->createCard($addressBookId, $uri, $this->vcardTest0);
899
+        // We now have one add
900
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
901
+        $this->assertEquals(1, count($changes['added']));
902
+        $this->assertEmpty($changes['modified']);
903
+        $this->assertEmpty($changes['deleted']);
904
+
905
+        // Update card
906
+        $this->backend->updateCard($addressBookId, $uri, $this->vcardTest1);
907
+        // One add, one modify, but shortened to modify
908
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
909
+        $this->assertEmpty($changes['added']);
910
+        $this->assertEquals(1, count($changes['modified']));
911
+        $this->assertEmpty($changes['deleted']);
912
+
913
+        // Delete all but last change
914
+        $deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
915
+        $this->assertEquals(1, $deleted); // We had two changes before, now one
916
+
917
+        // Only update should remain
918
+        $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 100);
919
+        $this->assertEmpty($changes['added']);
920
+        $this->assertEquals(1, count($changes['modified']));
921
+        $this->assertEmpty($changes['deleted']);
922
+
923
+        // Check that no crash occurs when prune is called without current changes
924
+        $deleted = $this->backend->pruneOutdatedSyncTokens(1, time());
925
+    }
926 926
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/Paginate/PaginatePluginTest.php 2 patches
Indentation   +284 added lines, -284 removed lines patch added patch discarded remove patch
@@ -20,129 +20,129 @@  discard block
 block discarded – undo
20 20
 
21 21
 class PaginatePluginTest extends TestCase {
22 22
 
23
-	private PaginateCache&MockObject $cache;
24
-	private PaginatePlugin $plugin;
25
-	private Server&MockObject $server;
26
-	private RequestInterface&MockObject $request;
27
-	private ResponseInterface&MockObject $response;
28
-
29
-	public function testOnMultiStatusCachesAndUpdatesResponse(): void {
30
-		$this->initializePlugin();
31
-
32
-		$fileProperties = [
33
-			[
34
-				'href' => '/file1',
35
-				200 => [
36
-					'{DAV:}displayname' => 'File 1',
37
-					'{DAV:}resourcetype' => null
38
-				],
39
-			],
40
-			[
41
-				'href' => '/file2',
42
-				200 => [
43
-					'{DAV:}displayname' => 'File 2',
44
-					'{DAV:}resourcetype' => null
45
-				],
46
-			],
47
-			[
48
-				'href' => '/file3',
49
-				200 => [
50
-					'{DAV:}displayname' => 'File 3',
51
-					'{DAV:}resourcetype' => null
52
-				],
53
-			],
54
-		];
55
-
56
-		$this->request->expects(self::exactly(2))
57
-			->method('hasHeader')
58
-			->willReturnMap([
59
-				[PaginatePlugin::PAGINATE_HEADER, true],
60
-				[PaginatePlugin::PAGINATE_TOKEN_HEADER, false],
61
-			]);
62
-
63
-		$this->request->expects(self::once())
64
-			->method('getUrl')
65
-			->willReturn('url');
66
-
67
-		$this->request->expects(self::exactly(2))
68
-			->method('getHeader')
69
-			->willReturnMap([
70
-				[PaginatePlugin::PAGINATE_COUNT_HEADER, 2],
71
-				[PaginatePlugin::PAGINATE_OFFSET_HEADER, 0],
72
-			]);
73
-
74
-		$this->request->expects(self::once())
75
-			->method('setHeader')
76
-			->with(PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token');
77
-
78
-		$this->cache->expects(self::once())
79
-			->method('store')
80
-			->with(
81
-				'url',
82
-				$this->callback(function ($generator) {
83
-					self::assertInstanceOf(\Generator::class, $generator);
84
-					$items = iterator_to_array($generator);
85
-					self::assertCount(3, $items);
86
-					self::assertStringContainsString($this->getResponseXmlForFile('/dav/file1', 'File 1'), $items[0]);
87
-					self::assertStringContainsString($this->getResponseXmlForFile('/dav/file2', 'File 2'), $items[1]);
88
-					self::assertStringContainsString($this->getResponseXmlForFile('/dav/file3', 'File 3'), $items[2]);
89
-					return true;
90
-				}),
91
-			)
92
-			->willReturn([
93
-				'token' => 'token',
94
-				'count' => 3,
95
-			]);
96
-
97
-		$this->expectSequentialCalls(
98
-			$this->response,
99
-			'addHeader',
100
-			[
101
-				[PaginatePlugin::PAGINATE_HEADER, 'true'],
102
-				[PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'],
103
-				[PaginatePlugin::PAGINATE_TOTAL_HEADER, '3'],
104
-			],
105
-		);
106
-
107
-		$this->plugin->onMultiStatus($fileProperties);
108
-
109
-		self::assertInstanceOf(\Iterator::class, $fileProperties);
110
-		// the iterator should be replaced with one that has the amount of
111
-		// items for the page
112
-		$items = iterator_to_array($fileProperties, false);
113
-		$this->assertCount(2, $items);
114
-	}
115
-
116
-	private function initializePlugin(): void {
117
-		$this->expectSequentialCalls(
118
-			$this->server,
119
-			'on',
120
-			[
121
-				['beforeMultiStatus', [$this->plugin, 'onMultiStatus'], 100],
122
-				['method:SEARCH', [$this->plugin, 'onMethod'], 1],
123
-				['method:PROPFIND', [$this->plugin, 'onMethod'], 1],
124
-				['method:REPORT', [$this->plugin, 'onMethod'], 1],
125
-			],
126
-		);
127
-
128
-		$this->plugin->initialize($this->server);
129
-	}
130
-
131
-	/**
132
-	 * @param array<int, array<int, mixed>> $expectedCalls
133
-	 */
134
-	private function expectSequentialCalls(MockObject $mock, string $method, array $expectedCalls): void {
135
-		$mock->expects(self::exactly(\count($expectedCalls)))
136
-			->method($method)
137
-			->willReturnCallback(function (...$args) use (&$expectedCalls): void {
138
-				$expected = array_shift($expectedCalls);
139
-				self::assertNotNull($expected);
140
-				self::assertSame($expected, $args);
141
-			});
142
-	}
143
-
144
-	private function getResponseXmlForFile(string $fileName, string $displayName): string {
145
-		return preg_replace('/>\s+</', '><', <<<XML
23
+    private PaginateCache&MockObject $cache;
24
+    private PaginatePlugin $plugin;
25
+    private Server&MockObject $server;
26
+    private RequestInterface&MockObject $request;
27
+    private ResponseInterface&MockObject $response;
28
+
29
+    public function testOnMultiStatusCachesAndUpdatesResponse(): void {
30
+        $this->initializePlugin();
31
+
32
+        $fileProperties = [
33
+            [
34
+                'href' => '/file1',
35
+                200 => [
36
+                    '{DAV:}displayname' => 'File 1',
37
+                    '{DAV:}resourcetype' => null
38
+                ],
39
+            ],
40
+            [
41
+                'href' => '/file2',
42
+                200 => [
43
+                    '{DAV:}displayname' => 'File 2',
44
+                    '{DAV:}resourcetype' => null
45
+                ],
46
+            ],
47
+            [
48
+                'href' => '/file3',
49
+                200 => [
50
+                    '{DAV:}displayname' => 'File 3',
51
+                    '{DAV:}resourcetype' => null
52
+                ],
53
+            ],
54
+        ];
55
+
56
+        $this->request->expects(self::exactly(2))
57
+            ->method('hasHeader')
58
+            ->willReturnMap([
59
+                [PaginatePlugin::PAGINATE_HEADER, true],
60
+                [PaginatePlugin::PAGINATE_TOKEN_HEADER, false],
61
+            ]);
62
+
63
+        $this->request->expects(self::once())
64
+            ->method('getUrl')
65
+            ->willReturn('url');
66
+
67
+        $this->request->expects(self::exactly(2))
68
+            ->method('getHeader')
69
+            ->willReturnMap([
70
+                [PaginatePlugin::PAGINATE_COUNT_HEADER, 2],
71
+                [PaginatePlugin::PAGINATE_OFFSET_HEADER, 0],
72
+            ]);
73
+
74
+        $this->request->expects(self::once())
75
+            ->method('setHeader')
76
+            ->with(PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token');
77
+
78
+        $this->cache->expects(self::once())
79
+            ->method('store')
80
+            ->with(
81
+                'url',
82
+                $this->callback(function ($generator) {
83
+                    self::assertInstanceOf(\Generator::class, $generator);
84
+                    $items = iterator_to_array($generator);
85
+                    self::assertCount(3, $items);
86
+                    self::assertStringContainsString($this->getResponseXmlForFile('/dav/file1', 'File 1'), $items[0]);
87
+                    self::assertStringContainsString($this->getResponseXmlForFile('/dav/file2', 'File 2'), $items[1]);
88
+                    self::assertStringContainsString($this->getResponseXmlForFile('/dav/file3', 'File 3'), $items[2]);
89
+                    return true;
90
+                }),
91
+            )
92
+            ->willReturn([
93
+                'token' => 'token',
94
+                'count' => 3,
95
+            ]);
96
+
97
+        $this->expectSequentialCalls(
98
+            $this->response,
99
+            'addHeader',
100
+            [
101
+                [PaginatePlugin::PAGINATE_HEADER, 'true'],
102
+                [PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'],
103
+                [PaginatePlugin::PAGINATE_TOTAL_HEADER, '3'],
104
+            ],
105
+        );
106
+
107
+        $this->plugin->onMultiStatus($fileProperties);
108
+
109
+        self::assertInstanceOf(\Iterator::class, $fileProperties);
110
+        // the iterator should be replaced with one that has the amount of
111
+        // items for the page
112
+        $items = iterator_to_array($fileProperties, false);
113
+        $this->assertCount(2, $items);
114
+    }
115
+
116
+    private function initializePlugin(): void {
117
+        $this->expectSequentialCalls(
118
+            $this->server,
119
+            'on',
120
+            [
121
+                ['beforeMultiStatus', [$this->plugin, 'onMultiStatus'], 100],
122
+                ['method:SEARCH', [$this->plugin, 'onMethod'], 1],
123
+                ['method:PROPFIND', [$this->plugin, 'onMethod'], 1],
124
+                ['method:REPORT', [$this->plugin, 'onMethod'], 1],
125
+            ],
126
+        );
127
+
128
+        $this->plugin->initialize($this->server);
129
+    }
130
+
131
+    /**
132
+     * @param array<int, array<int, mixed>> $expectedCalls
133
+     */
134
+    private function expectSequentialCalls(MockObject $mock, string $method, array $expectedCalls): void {
135
+        $mock->expects(self::exactly(\count($expectedCalls)))
136
+            ->method($method)
137
+            ->willReturnCallback(function (...$args) use (&$expectedCalls): void {
138
+                $expected = array_shift($expectedCalls);
139
+                self::assertNotNull($expected);
140
+                self::assertSame($expected, $args);
141
+            });
142
+    }
143
+
144
+    private function getResponseXmlForFile(string $fileName, string $displayName): string {
145
+        return preg_replace('/>\s+</', '><', <<<XML
146 146
 			<d:response>
147 147
 				<d:href>$fileName</d:href>
148 148
 				<d:propstat>
@@ -154,184 +154,184 @@  discard block
 block discarded – undo
154 154
 				</d:propstat>
155 155
 			</d:response>
156 156
 			XML
157
-		);
158
-	}
159
-
160
-	public function testOnMultiStatusSkipsWhenHeadersAndCacheExist(): void {
161
-		$this->initializePlugin();
162
-
163
-		$fileProperties = [
164
-			[
165
-				'href' => '/file1',
166
-			],
167
-			[
168
-				'href' => '/file2',
169
-			],
170
-		];
171
-
172
-		$this->request->expects(self::exactly(2))
173
-			->method('hasHeader')
174
-			->willReturnMap([
175
-				[PaginatePlugin::PAGINATE_HEADER, true],
176
-				[PaginatePlugin::PAGINATE_TOKEN_HEADER, true],
177
-			]);
178
-
179
-		$this->request->expects(self::once())
180
-			->method('getUrl')
181
-			->willReturn('');
182
-
183
-		$this->request->expects(self::once())
184
-			->method('getHeader')
185
-			->with(PaginatePlugin::PAGINATE_TOKEN_HEADER)
186
-			->willReturn('token');
187
-
188
-		$this->cache->expects(self::once())
189
-			->method('exists')
190
-			->with('', 'token')
191
-			->willReturn(true);
192
-
193
-		$this->cache->expects(self::never())
194
-			->method('store');
195
-
196
-		$this->plugin->onMultiStatus($fileProperties);
197
-
198
-		self::assertInstanceOf(\Iterator::class, $fileProperties);
199
-		self::assertSame(
200
-			[
201
-				['href' => '/file1'],
202
-				['href' => '/file2'],
203
-			],
204
-			iterator_to_array($fileProperties)
205
-		);
206
-	}
207
-
208
-	public function testOnMethodReturnsCachedResponse(): void {
209
-		$this->initializePlugin();
210
-
211
-		$response = $this->createMock(ResponseInterface::class);
212
-
213
-		$this->request->expects(self::exactly(2))
214
-			->method('hasHeader')
215
-			->willReturnMap([
216
-				[PaginatePlugin::PAGINATE_TOKEN_HEADER, true],
217
-				[PaginatePlugin::PAGINATE_OFFSET_HEADER, true],
218
-			]);
219
-
220
-		$this->request->expects(self::once())
221
-			->method('getUrl')
222
-			->willReturn('url');
223
-
224
-		$this->request->expects(self::exactly(4))
225
-			->method('getHeader')
226
-			->willReturnMap([
227
-				[PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'],
228
-				[PaginatePlugin::PAGINATE_OFFSET_HEADER, '2'],
229
-				[PaginatePlugin::PAGINATE_COUNT_HEADER, '4'],
230
-			]);
231
-
232
-		$this->cache->expects(self::once())
233
-			->method('exists')
234
-			->with('url', 'token')
235
-			->willReturn(true);
236
-
237
-		$this->cache->expects(self::once())
238
-			->method('get')
239
-			->with('url', 'token', 2, 4)
240
-			->willReturn((function (): \Generator {
241
-				yield $this->getResponseXmlForFile('/file1', 'File 1');
242
-				yield $this->getResponseXmlForFile('/file2', 'File 2');
243
-			})());
244
-
245
-		$response->expects(self::once())
246
-			->method('setStatus')
247
-			->with(207);
248
-
249
-		$response->expects(self::once())
250
-			->method('addHeader')
251
-			->with(PaginatePlugin::PAGINATE_HEADER, 'true');
252
-
253
-		$this->expectSequentialCalls(
254
-			$response,
255
-			'setHeader',
256
-			[
257
-				['Content-Type', 'application/xml; charset=utf-8'],
258
-				['Vary', 'Brief,Prefer'],
259
-			],
260
-		);
261
-
262
-		$response->expects(self::once())
263
-			->method('setBody')
264
-			->with($this->callback(function (string $body) {
265
-				// header of the XML
266
-				self::assertStringContainsString(<<<XML
157
+        );
158
+    }
159
+
160
+    public function testOnMultiStatusSkipsWhenHeadersAndCacheExist(): void {
161
+        $this->initializePlugin();
162
+
163
+        $fileProperties = [
164
+            [
165
+                'href' => '/file1',
166
+            ],
167
+            [
168
+                'href' => '/file2',
169
+            ],
170
+        ];
171
+
172
+        $this->request->expects(self::exactly(2))
173
+            ->method('hasHeader')
174
+            ->willReturnMap([
175
+                [PaginatePlugin::PAGINATE_HEADER, true],
176
+                [PaginatePlugin::PAGINATE_TOKEN_HEADER, true],
177
+            ]);
178
+
179
+        $this->request->expects(self::once())
180
+            ->method('getUrl')
181
+            ->willReturn('');
182
+
183
+        $this->request->expects(self::once())
184
+            ->method('getHeader')
185
+            ->with(PaginatePlugin::PAGINATE_TOKEN_HEADER)
186
+            ->willReturn('token');
187
+
188
+        $this->cache->expects(self::once())
189
+            ->method('exists')
190
+            ->with('', 'token')
191
+            ->willReturn(true);
192
+
193
+        $this->cache->expects(self::never())
194
+            ->method('store');
195
+
196
+        $this->plugin->onMultiStatus($fileProperties);
197
+
198
+        self::assertInstanceOf(\Iterator::class, $fileProperties);
199
+        self::assertSame(
200
+            [
201
+                ['href' => '/file1'],
202
+                ['href' => '/file2'],
203
+            ],
204
+            iterator_to_array($fileProperties)
205
+        );
206
+    }
207
+
208
+    public function testOnMethodReturnsCachedResponse(): void {
209
+        $this->initializePlugin();
210
+
211
+        $response = $this->createMock(ResponseInterface::class);
212
+
213
+        $this->request->expects(self::exactly(2))
214
+            ->method('hasHeader')
215
+            ->willReturnMap([
216
+                [PaginatePlugin::PAGINATE_TOKEN_HEADER, true],
217
+                [PaginatePlugin::PAGINATE_OFFSET_HEADER, true],
218
+            ]);
219
+
220
+        $this->request->expects(self::once())
221
+            ->method('getUrl')
222
+            ->willReturn('url');
223
+
224
+        $this->request->expects(self::exactly(4))
225
+            ->method('getHeader')
226
+            ->willReturnMap([
227
+                [PaginatePlugin::PAGINATE_TOKEN_HEADER, 'token'],
228
+                [PaginatePlugin::PAGINATE_OFFSET_HEADER, '2'],
229
+                [PaginatePlugin::PAGINATE_COUNT_HEADER, '4'],
230
+            ]);
231
+
232
+        $this->cache->expects(self::once())
233
+            ->method('exists')
234
+            ->with('url', 'token')
235
+            ->willReturn(true);
236
+
237
+        $this->cache->expects(self::once())
238
+            ->method('get')
239
+            ->with('url', 'token', 2, 4)
240
+            ->willReturn((function (): \Generator {
241
+                yield $this->getResponseXmlForFile('/file1', 'File 1');
242
+                yield $this->getResponseXmlForFile('/file2', 'File 2');
243
+            })());
244
+
245
+        $response->expects(self::once())
246
+            ->method('setStatus')
247
+            ->with(207);
248
+
249
+        $response->expects(self::once())
250
+            ->method('addHeader')
251
+            ->with(PaginatePlugin::PAGINATE_HEADER, 'true');
252
+
253
+        $this->expectSequentialCalls(
254
+            $response,
255
+            'setHeader',
256
+            [
257
+                ['Content-Type', 'application/xml; charset=utf-8'],
258
+                ['Vary', 'Brief,Prefer'],
259
+            ],
260
+        );
261
+
262
+        $response->expects(self::once())
263
+            ->method('setBody')
264
+            ->with($this->callback(function (string $body) {
265
+                // header of the XML
266
+                self::assertStringContainsString(<<<XML
267 267
 					<?xml version="1.0"?>
268 268
 					<d:multistatus xmlns:d="DAV:">
269 269
 					XML,
270
-					$body);
271
-				self::assertStringContainsString($this->getResponseXmlForFile('/file1', 'File 1'), $body);
272
-				self::assertStringContainsString($this->getResponseXmlForFile('/file2', 'File 2'), $body);
273
-				// footer of the XML
274
-				self::assertStringContainsString('</d:multistatus>', $body);
270
+                    $body);
271
+                self::assertStringContainsString($this->getResponseXmlForFile('/file1', 'File 1'), $body);
272
+                self::assertStringContainsString($this->getResponseXmlForFile('/file2', 'File 2'), $body);
273
+                // footer of the XML
274
+                self::assertStringContainsString('</d:multistatus>', $body);
275 275
 
276
-				return true;
277
-			}));
276
+                return true;
277
+            }));
278 278
 
279
-		self::assertFalse($this->plugin->onMethod($this->request, $response));
280
-	}
279
+        self::assertFalse($this->plugin->onMethod($this->request, $response));
280
+    }
281 281
 
282
-	public function testOnMultiStatusNoPaginateHeaderShouldSucceed(): void {
283
-		$this->initializePlugin();
282
+    public function testOnMultiStatusNoPaginateHeaderShouldSucceed(): void {
283
+        $this->initializePlugin();
284 284
 
285
-		$this->request->expects(self::once())
286
-			->method('getUrl')
287
-			->willReturn('');
285
+        $this->request->expects(self::once())
286
+            ->method('getUrl')
287
+            ->willReturn('');
288 288
 
289
-		$this->cache->expects(self::never())
290
-			->method('exists');
291
-		$this->cache->expects(self::never())
292
-			->method('store');
289
+        $this->cache->expects(self::never())
290
+            ->method('exists');
291
+        $this->cache->expects(self::never())
292
+            ->method('store');
293 293
 
294
-		$this->plugin->onMultiStatus($this->request);
295
-	}
294
+        $this->plugin->onMultiStatus($this->request);
295
+    }
296 296
 
297
-	public function testOnMethodNoTokenHeaderShouldSucceed(): void {
298
-		$this->initializePlugin();
299
-		$this->request->expects(self::once())
300
-			->method('hasHeader')
301
-			->with(PaginatePlugin::PAGINATE_TOKEN_HEADER)
302
-			->willReturn(false);
297
+    public function testOnMethodNoTokenHeaderShouldSucceed(): void {
298
+        $this->initializePlugin();
299
+        $this->request->expects(self::once())
300
+            ->method('hasHeader')
301
+            ->with(PaginatePlugin::PAGINATE_TOKEN_HEADER)
302
+            ->willReturn(false);
303 303
 
304
-		$this->cache->expects(self::never())
305
-			->method('exists');
306
-		$this->cache->expects(self::never())
307
-			->method('get');
304
+        $this->cache->expects(self::never())
305
+            ->method('exists');
306
+        $this->cache->expects(self::never())
307
+            ->method('get');
308 308
 
309
-		$this->plugin->onMethod($this->request, $this->response);
310
-	}
309
+        $this->plugin->onMethod($this->request, $this->response);
310
+    }
311 311
 
312
-	protected function setUp(): void {
313
-		parent::setUp();
312
+    protected function setUp(): void {
313
+        parent::setUp();
314 314
 
315
-		$this->cache = $this->createMock(PaginateCache::class);
315
+        $this->cache = $this->createMock(PaginateCache::class);
316 316
 
317
-		$this->server = $this->getMockBuilder(Server::class)
318
-			->disableOriginalConstructor()
319
-			->onlyMethods(['on', 'getHTTPPrefer', 'getBaseUri'])
320
-			->getMock();
317
+        $this->server = $this->getMockBuilder(Server::class)
318
+            ->disableOriginalConstructor()
319
+            ->onlyMethods(['on', 'getHTTPPrefer', 'getBaseUri'])
320
+            ->getMock();
321 321
 
322
-		$this->request = $this->createMock(RequestInterface::class);
323
-		$this->response = $this->createMock(ResponseInterface::class);
322
+        $this->request = $this->createMock(RequestInterface::class);
323
+        $this->response = $this->createMock(ResponseInterface::class);
324 324
 
325
-		$this->server->httpRequest = $this->request;
326
-		$this->server->httpResponse = $this->response;
327
-		$this->server->xml = new Service();
328
-		$this->server->xml->namespaceMap = [ 'DAV:' => 'd' ];
325
+        $this->server->httpRequest = $this->request;
326
+        $this->server->httpResponse = $this->response;
327
+        $this->server->xml = new Service();
328
+        $this->server->xml->namespaceMap = [ 'DAV:' => 'd' ];
329 329
 
330
-		$this->server->method('getHTTPPrefer')
331
-			->willReturn(['return' => null]);
332
-		$this->server->method('getBaseUri')
333
-			->willReturn('/dav/');
330
+        $this->server->method('getHTTPPrefer')
331
+            ->willReturn(['return' => null]);
332
+        $this->server->method('getBaseUri')
333
+            ->willReturn('/dav/');
334 334
 
335
-		$this->plugin = new PaginatePlugin($this->cache, 2);
336
-	}
335
+        $this->plugin = new PaginatePlugin($this->cache, 2);
336
+    }
337 337
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
 			->method('store')
80 80
 			->with(
81 81
 				'url',
82
-				$this->callback(function ($generator) {
82
+				$this->callback(function($generator) {
83 83
 					self::assertInstanceOf(\Generator::class, $generator);
84 84
 					$items = iterator_to_array($generator);
85 85
 					self::assertCount(3, $items);
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
 	private function expectSequentialCalls(MockObject $mock, string $method, array $expectedCalls): void {
135 135
 		$mock->expects(self::exactly(\count($expectedCalls)))
136 136
 			->method($method)
137
-			->willReturnCallback(function (...$args) use (&$expectedCalls): void {
137
+			->willReturnCallback(function(...$args) use (&$expectedCalls): void {
138 138
 				$expected = array_shift($expectedCalls);
139 139
 				self::assertNotNull($expected);
140 140
 				self::assertSame($expected, $args);
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
 		$this->cache->expects(self::once())
238 238
 			->method('get')
239 239
 			->with('url', 'token', 2, 4)
240
-			->willReturn((function (): \Generator {
240
+			->willReturn((function(): \Generator {
241 241
 				yield $this->getResponseXmlForFile('/file1', 'File 1');
242 242
 				yield $this->getResponseXmlForFile('/file2', 'File 2');
243 243
 			})());
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
 
262 262
 		$response->expects(self::once())
263 263
 			->method('setBody')
264
-			->with($this->callback(function (string $body) {
264
+			->with($this->callback(function(string $body) {
265 265
 				// header of the XML
266 266
 				self::assertStringContainsString(<<<XML
267 267
 					<?xml version="1.0"?>
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
 		$this->server->httpRequest = $this->request;
326 326
 		$this->server->httpResponse = $this->response;
327 327
 		$this->server->xml = new Service();
328
-		$this->server->xml->namespaceMap = [ 'DAV:' => 'd' ];
328
+		$this->server->xml->namespaceMap = ['DAV:' => 'd'];
329 329
 
330 330
 		$this->server->method('getHTTPPrefer')
331 331
 			->willReturn(['return' => null]);
Please login to merge, or discard this patch.
apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php 1 patch
Indentation   +114 added lines, -114 removed lines patch added patch discarded remove patch
@@ -26,118 +26,118 @@
 block discarded – undo
26 26
  */
27 27
 #[\PHPUnit\Framework\Attributes\Group('DB')]
28 28
 class RemoveInvalidSharesTest extends TestCase {
29
-	private RemoveInvalidShares $command;
30
-
31
-	private IDBConnection $db;
32
-	private Principal&MockObject $principalBackend;
33
-	private RemoteUserPrincipalBackend&MockObject $remoteUserPrincipalBackend;
34
-
35
-	protected function setUp(): void {
36
-		parent::setUp();
37
-
38
-		$this->db = Server::get(IDBConnection::class);
39
-		$this->principalBackend = $this->createMock(Principal::class);
40
-		$this->remoteUserPrincipalBackend = $this->createMock(RemoteUserPrincipalBackend::class);
41
-
42
-		$this->db->insertIfNotExist('*PREFIX*dav_shares', [
43
-			'principaluri' => 'principal:unknown',
44
-			'type' => 'calendar',
45
-			'access' => 2,
46
-			'resourceid' => 666,
47
-		]);
48
-		$this->db->insertIfNotExist('*PREFIX*dav_shares', [
49
-			'principaluri' => 'principals/remote-users/foobar',
50
-			'type' => 'calendar',
51
-			'access' => 2,
52
-			'resourceid' => 666,
53
-		]);
54
-
55
-		$this->command = new RemoveInvalidShares(
56
-			$this->db,
57
-			$this->principalBackend,
58
-			$this->remoteUserPrincipalBackend,
59
-		);
60
-	}
61
-
62
-	private function selectShares(): array {
63
-		$query = $this->db->getQueryBuilder();
64
-		$query->select('*')
65
-			->from('dav_shares')
66
-			->where($query->expr()->in(
67
-				'principaluri',
68
-				$query->createNamedParameter(
69
-					['principal:unknown', 'principals/remote-users/foobar'],
70
-					IQueryBuilder::PARAM_STR_ARRAY,
71
-				),
72
-			));
73
-		$result = $query->executeQuery();
74
-		$data = $result->fetchAllAssociative();
75
-		$result->closeCursor();
76
-
77
-		return $data;
78
-	}
79
-
80
-	public function testWithoutPrincipals(): void {
81
-		$this->principalBackend->method('getPrincipalByPath')
82
-			->willReturnMap([
83
-				['principal:unknown', null],
84
-				['principals/remote-users/foobar', null],
85
-			]);
86
-		$this->remoteUserPrincipalBackend->method('getPrincipalByPath')
87
-			->willReturnMap([
88
-				['principal:unknown', null],
89
-				['principals/remote-users/foobar', null],
90
-			]);
91
-
92
-		$this->command->run(
93
-			$this->createMock(InputInterface::class),
94
-			$this->createMock(OutputInterface::class),
95
-		);
96
-
97
-		$data = $this->selectShares();
98
-		$this->assertCount(0, $data);
99
-	}
100
-
101
-	public function testWithLocalPrincipal(): void {
102
-		$this->principalBackend->method('getPrincipalByPath')
103
-			->willReturnMap([
104
-				['principal:unknown', ['uri' => 'principal:unknown']],
105
-				['principals/remote-users/foobar', null],
106
-			]);
107
-		$this->remoteUserPrincipalBackend->method('getPrincipalByPath')
108
-			->willReturnMap([
109
-				['principals/remote-users/foobar', null],
110
-			]);
111
-
112
-		$this->command->run(
113
-			$this->createMock(InputInterface::class),
114
-			$this->createMock(OutputInterface::class),
115
-		);
116
-
117
-		$data = $this->selectShares();
118
-		$this->assertCount(1, $data);
119
-		$this->assertEquals('principal:unknown', $data[0]['principaluri']);
120
-	}
121
-
122
-	public function testWithRemotePrincipal() {
123
-		$this->principalBackend->method('getPrincipalByPath')
124
-			->willReturnMap([
125
-				['principal:unknown', null],
126
-				['principals/remote-users/foobar', null],
127
-			]);
128
-		$this->remoteUserPrincipalBackend->method('getPrincipalByPath')
129
-			->willReturnMap([
130
-				['principal:unknown', null],
131
-				['principals/remote-users/foobar', ['uri' => 'principals/remote-users/foobar']],
132
-			]);
133
-
134
-		$this->command->run(
135
-			$this->createMock(InputInterface::class),
136
-			$this->createMock(OutputInterface::class),
137
-		);
138
-
139
-		$data = $this->selectShares();
140
-		$this->assertCount(1, $data);
141
-		$this->assertEquals('principals/remote-users/foobar', $data[0]['principaluri']);
142
-	}
29
+    private RemoveInvalidShares $command;
30
+
31
+    private IDBConnection $db;
32
+    private Principal&MockObject $principalBackend;
33
+    private RemoteUserPrincipalBackend&MockObject $remoteUserPrincipalBackend;
34
+
35
+    protected function setUp(): void {
36
+        parent::setUp();
37
+
38
+        $this->db = Server::get(IDBConnection::class);
39
+        $this->principalBackend = $this->createMock(Principal::class);
40
+        $this->remoteUserPrincipalBackend = $this->createMock(RemoteUserPrincipalBackend::class);
41
+
42
+        $this->db->insertIfNotExist('*PREFIX*dav_shares', [
43
+            'principaluri' => 'principal:unknown',
44
+            'type' => 'calendar',
45
+            'access' => 2,
46
+            'resourceid' => 666,
47
+        ]);
48
+        $this->db->insertIfNotExist('*PREFIX*dav_shares', [
49
+            'principaluri' => 'principals/remote-users/foobar',
50
+            'type' => 'calendar',
51
+            'access' => 2,
52
+            'resourceid' => 666,
53
+        ]);
54
+
55
+        $this->command = new RemoveInvalidShares(
56
+            $this->db,
57
+            $this->principalBackend,
58
+            $this->remoteUserPrincipalBackend,
59
+        );
60
+    }
61
+
62
+    private function selectShares(): array {
63
+        $query = $this->db->getQueryBuilder();
64
+        $query->select('*')
65
+            ->from('dav_shares')
66
+            ->where($query->expr()->in(
67
+                'principaluri',
68
+                $query->createNamedParameter(
69
+                    ['principal:unknown', 'principals/remote-users/foobar'],
70
+                    IQueryBuilder::PARAM_STR_ARRAY,
71
+                ),
72
+            ));
73
+        $result = $query->executeQuery();
74
+        $data = $result->fetchAllAssociative();
75
+        $result->closeCursor();
76
+
77
+        return $data;
78
+    }
79
+
80
+    public function testWithoutPrincipals(): void {
81
+        $this->principalBackend->method('getPrincipalByPath')
82
+            ->willReturnMap([
83
+                ['principal:unknown', null],
84
+                ['principals/remote-users/foobar', null],
85
+            ]);
86
+        $this->remoteUserPrincipalBackend->method('getPrincipalByPath')
87
+            ->willReturnMap([
88
+                ['principal:unknown', null],
89
+                ['principals/remote-users/foobar', null],
90
+            ]);
91
+
92
+        $this->command->run(
93
+            $this->createMock(InputInterface::class),
94
+            $this->createMock(OutputInterface::class),
95
+        );
96
+
97
+        $data = $this->selectShares();
98
+        $this->assertCount(0, $data);
99
+    }
100
+
101
+    public function testWithLocalPrincipal(): void {
102
+        $this->principalBackend->method('getPrincipalByPath')
103
+            ->willReturnMap([
104
+                ['principal:unknown', ['uri' => 'principal:unknown']],
105
+                ['principals/remote-users/foobar', null],
106
+            ]);
107
+        $this->remoteUserPrincipalBackend->method('getPrincipalByPath')
108
+            ->willReturnMap([
109
+                ['principals/remote-users/foobar', null],
110
+            ]);
111
+
112
+        $this->command->run(
113
+            $this->createMock(InputInterface::class),
114
+            $this->createMock(OutputInterface::class),
115
+        );
116
+
117
+        $data = $this->selectShares();
118
+        $this->assertCount(1, $data);
119
+        $this->assertEquals('principal:unknown', $data[0]['principaluri']);
120
+    }
121
+
122
+    public function testWithRemotePrincipal() {
123
+        $this->principalBackend->method('getPrincipalByPath')
124
+            ->willReturnMap([
125
+                ['principal:unknown', null],
126
+                ['principals/remote-users/foobar', null],
127
+            ]);
128
+        $this->remoteUserPrincipalBackend->method('getPrincipalByPath')
129
+            ->willReturnMap([
130
+                ['principal:unknown', null],
131
+                ['principals/remote-users/foobar', ['uri' => 'principals/remote-users/foobar']],
132
+            ]);
133
+
134
+        $this->command->run(
135
+            $this->createMock(InputInterface::class),
136
+            $this->createMock(OutputInterface::class),
137
+        );
138
+
139
+        $data = $this->selectShares();
140
+        $this->assertCount(1, $data);
141
+        $this->assertEquals('principals/remote-users/foobar', $data[0]['principaluri']);
142
+    }
143 143
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php 1 patch
Indentation   +97 added lines, -97 removed lines patch added patch discarded remove patch
@@ -18,101 +18,101 @@
 block discarded – undo
18 18
 use Test\TestCase;
19 19
 
20 20
 class RefreshWebcalJobRegistrarTest extends TestCase {
21
-	private IDBConnection&MockObject $db;
22
-	private IJobList&MockObject $jobList;
23
-	private RefreshWebcalJobRegistrar $migration;
24
-
25
-	protected function setUp(): void {
26
-		parent::setUp();
27
-
28
-		$this->db = $this->createMock(IDBConnection::class);
29
-		$this->jobList = $this->createMock(IJobList::class);
30
-
31
-		$this->migration = new RefreshWebcalJobRegistrar($this->db, $this->jobList);
32
-	}
33
-
34
-	public function testGetName(): void {
35
-		$this->assertEquals($this->migration->getName(), 'Registering background jobs to update cache for webcal calendars');
36
-	}
37
-
38
-	public function testRun(): void {
39
-		$output = $this->createMock(IOutput::class);
40
-
41
-		$queryBuilder = $this->createMock(IQueryBuilder::class);
42
-		$statement = $this->createMock(IResult::class);
43
-
44
-		$this->db->expects($this->once())
45
-			->method('getQueryBuilder')
46
-			->willReturn($queryBuilder);
47
-
48
-		$queryBuilder->expects($this->once())
49
-			->method('select')
50
-			->with(['principaluri', 'uri'])
51
-			->willReturn($queryBuilder);
52
-		$queryBuilder->expects($this->once())
53
-			->method('from')
54
-			->with('calendarsubscriptions')
55
-			->willReturn($queryBuilder);
56
-		$queryBuilder->expects($this->once())
57
-			->method('executeQuery')
58
-			->willReturn($statement);
59
-
60
-		$statement->expects($this->exactly(4))
61
-			->method('fetchAssociative')
62
-			->willReturnOnConsecutiveCalls(
63
-				[
64
-					'principaluri' => 'foo1',
65
-					'uri' => 'bar1',
66
-				],
67
-				[
68
-					'principaluri' => 'foo2',
69
-					'uri' => 'bar2',
70
-				],
71
-				[
72
-					'principaluri' => 'foo3',
73
-					'uri' => 'bar3',
74
-				],
75
-				false,
76
-			);
77
-
78
-		$this->jobList->expects($this->exactly(3))
79
-			->method('has')
80
-			->willReturnMap([
81
-				[RefreshWebcalJob::class, [
82
-					'principaluri' => 'foo1',
83
-					'uri' => 'bar1',
84
-				], false],
85
-				[RefreshWebcalJob::class, [
86
-					'principaluri' => 'foo2',
87
-					'uri' => 'bar2',
88
-				], true ],
89
-				[RefreshWebcalJob::class, [
90
-					'principaluri' => 'foo3',
91
-					'uri' => 'bar3',
92
-				], false],
93
-			]);
94
-
95
-		$calls = [
96
-			[RefreshWebcalJob::class, [
97
-				'principaluri' => 'foo1',
98
-				'uri' => 'bar1',
99
-			]],
100
-			[RefreshWebcalJob::class, [
101
-				'principaluri' => 'foo3',
102
-				'uri' => 'bar3',
103
-			]]
104
-		];
105
-		$this->jobList->expects($this->exactly(2))
106
-			->method('add')
107
-			->willReturnCallback(function () use (&$calls): void {
108
-				$expected = array_shift($calls);
109
-				$this->assertEquals($expected, func_get_args());
110
-			});
111
-
112
-		$output->expects($this->once())
113
-			->method('info')
114
-			->with('Added 2 background jobs to update webcal calendars');
115
-
116
-		$this->migration->run($output);
117
-	}
21
+    private IDBConnection&MockObject $db;
22
+    private IJobList&MockObject $jobList;
23
+    private RefreshWebcalJobRegistrar $migration;
24
+
25
+    protected function setUp(): void {
26
+        parent::setUp();
27
+
28
+        $this->db = $this->createMock(IDBConnection::class);
29
+        $this->jobList = $this->createMock(IJobList::class);
30
+
31
+        $this->migration = new RefreshWebcalJobRegistrar($this->db, $this->jobList);
32
+    }
33
+
34
+    public function testGetName(): void {
35
+        $this->assertEquals($this->migration->getName(), 'Registering background jobs to update cache for webcal calendars');
36
+    }
37
+
38
+    public function testRun(): void {
39
+        $output = $this->createMock(IOutput::class);
40
+
41
+        $queryBuilder = $this->createMock(IQueryBuilder::class);
42
+        $statement = $this->createMock(IResult::class);
43
+
44
+        $this->db->expects($this->once())
45
+            ->method('getQueryBuilder')
46
+            ->willReturn($queryBuilder);
47
+
48
+        $queryBuilder->expects($this->once())
49
+            ->method('select')
50
+            ->with(['principaluri', 'uri'])
51
+            ->willReturn($queryBuilder);
52
+        $queryBuilder->expects($this->once())
53
+            ->method('from')
54
+            ->with('calendarsubscriptions')
55
+            ->willReturn($queryBuilder);
56
+        $queryBuilder->expects($this->once())
57
+            ->method('executeQuery')
58
+            ->willReturn($statement);
59
+
60
+        $statement->expects($this->exactly(4))
61
+            ->method('fetchAssociative')
62
+            ->willReturnOnConsecutiveCalls(
63
+                [
64
+                    'principaluri' => 'foo1',
65
+                    'uri' => 'bar1',
66
+                ],
67
+                [
68
+                    'principaluri' => 'foo2',
69
+                    'uri' => 'bar2',
70
+                ],
71
+                [
72
+                    'principaluri' => 'foo3',
73
+                    'uri' => 'bar3',
74
+                ],
75
+                false,
76
+            );
77
+
78
+        $this->jobList->expects($this->exactly(3))
79
+            ->method('has')
80
+            ->willReturnMap([
81
+                [RefreshWebcalJob::class, [
82
+                    'principaluri' => 'foo1',
83
+                    'uri' => 'bar1',
84
+                ], false],
85
+                [RefreshWebcalJob::class, [
86
+                    'principaluri' => 'foo2',
87
+                    'uri' => 'bar2',
88
+                ], true ],
89
+                [RefreshWebcalJob::class, [
90
+                    'principaluri' => 'foo3',
91
+                    'uri' => 'bar3',
92
+                ], false],
93
+            ]);
94
+
95
+        $calls = [
96
+            [RefreshWebcalJob::class, [
97
+                'principaluri' => 'foo1',
98
+                'uri' => 'bar1',
99
+            ]],
100
+            [RefreshWebcalJob::class, [
101
+                'principaluri' => 'foo3',
102
+                'uri' => 'bar3',
103
+            ]]
104
+        ];
105
+        $this->jobList->expects($this->exactly(2))
106
+            ->method('add')
107
+            ->willReturnCallback(function () use (&$calls): void {
108
+                $expected = array_shift($calls);
109
+                $this->assertEquals($expected, func_get_args());
110
+            });
111
+
112
+        $output->expects($this->once())
113
+            ->method('info')
114
+            ->with('Added 2 background jobs to update webcal calendars');
115
+
116
+        $this->migration->run($output);
117
+    }
118 118
 }
Please login to merge, or discard this patch.
dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php 1 patch
Indentation   +113 added lines, -113 removed lines patch added patch discarded remove patch
@@ -22,119 +22,119 @@
 block discarded – undo
22 22
 use Test\TestCase;
23 23
 
24 24
 class RemoveDeletedUsersCalendarSubscriptionsTest extends TestCase {
25
-	private IDBConnection&MockObject $dbConnection;
26
-	private IUserManager&MockObject $userManager;
27
-	private IOutput&MockObject $output;
28
-	private RemoveDeletedUsersCalendarSubscriptions $migration;
25
+    private IDBConnection&MockObject $dbConnection;
26
+    private IUserManager&MockObject $userManager;
27
+    private IOutput&MockObject $output;
28
+    private RemoveDeletedUsersCalendarSubscriptions $migration;
29 29
 
30 30
 
31
-	protected function setUp(): void {
32
-		parent::setUp();
33
-
34
-		$this->dbConnection = $this->createMock(IDBConnection::class);
35
-		$this->userManager = $this->createMock(IUserManager::class);
36
-		$this->output = $this->createMock(IOutput::class);
37
-
38
-		$this->migration = new RemoveDeletedUsersCalendarSubscriptions($this->dbConnection, $this->userManager);
39
-	}
40
-
41
-	public function testGetName(): void {
42
-		$this->assertEquals(
43
-			'Clean up old calendar subscriptions from deleted users that were not cleaned-up',
44
-			$this->migration->getName()
45
-		);
46
-	}
47
-
48
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestRun')]
49
-	public function testRun(array $subscriptions, array $userExists, int $deletions): void {
50
-		$qb = $this->createMock(IQueryBuilder::class);
51
-
52
-		$qb->method('select')->willReturn($qb);
53
-
54
-		$functionBuilder = $this->createMock(IFunctionBuilder::class);
55
-
56
-		$qb->method('func')->willReturn($functionBuilder);
57
-		$functionBuilder->method('count')->willReturn($this->createMock(IQueryFunction::class));
58
-
59
-		$qb->method('selectDistinct')
60
-			->with(['id', 'principaluri'])
61
-			->willReturn($qb);
62
-
63
-		$qb->method('from')
64
-			->with('calendarsubscriptions')
65
-			->willReturn($qb);
66
-
67
-		$qb->method('setMaxResults')
68
-			->willReturn($qb);
69
-
70
-		$qb->method('setFirstResult')
71
-			->willReturn($qb);
72
-
73
-		$result = $this->createMock(IResult::class);
74
-
75
-		$qb->method('executeQuery')
76
-			->willReturn($result);
77
-
78
-		$result->expects($this->once())
79
-			->method('fetchOne')
80
-			->willReturn(count($subscriptions));
81
-
82
-		$result
83
-			->method('fetchAssociative')
84
-			->willReturnOnConsecutiveCalls(...$subscriptions);
85
-
86
-		$qb->method('delete')
87
-			->with('calendarsubscriptions')
88
-			->willReturn($qb);
89
-
90
-		$expr = $this->createMock(IExpressionBuilder::class);
91
-
92
-		$qb->method('expr')->willReturn($expr);
93
-		$qb->method('createNamedParameter')->willReturn($this->createMock(IParameter::class));
94
-		$qb->method('where')->willReturn($qb);
95
-		// Only when user exists
96
-		$qb->expects($this->exactly($deletions))->method('executeStatement');
97
-
98
-		$this->dbConnection->method('getQueryBuilder')->willReturn($qb);
99
-
100
-
101
-		$this->output->expects($this->once())->method('startProgress');
102
-
103
-		$this->output->expects($subscriptions === [] ? $this->never(): $this->once())->method('advance');
104
-		if (count($subscriptions)) {
105
-			$this->userManager->method('userExists')
106
-				->willReturnCallback(function (string $username) use ($userExists) {
107
-					return $userExists[$username];
108
-				});
109
-		}
110
-		$this->output->expects($this->once())->method('finishProgress');
111
-		$this->output->expects($this->once())->method('info')->with(sprintf('%d calendar subscriptions without an user have been cleaned up', $deletions));
112
-
113
-		$this->migration->run($this->output);
114
-	}
115
-
116
-	public static function dataTestRun(): array {
117
-		return [
118
-			[[], [], 0],
119
-			[
120
-				[
121
-					[
122
-						'id' => 1,
123
-						'principaluri' => 'users/principals/foo1',
124
-					],
125
-					[
126
-						'id' => 2,
127
-						'principaluri' => 'users/principals/bar1',
128
-					],
129
-					[
130
-						'id' => 3,
131
-						'principaluri' => 'users/principals/bar1',
132
-					],
133
-					[],
134
-				],
135
-				['foo1' => true, 'bar1' => false],
136
-				2
137
-			],
138
-		];
139
-	}
31
+    protected function setUp(): void {
32
+        parent::setUp();
33
+
34
+        $this->dbConnection = $this->createMock(IDBConnection::class);
35
+        $this->userManager = $this->createMock(IUserManager::class);
36
+        $this->output = $this->createMock(IOutput::class);
37
+
38
+        $this->migration = new RemoveDeletedUsersCalendarSubscriptions($this->dbConnection, $this->userManager);
39
+    }
40
+
41
+    public function testGetName(): void {
42
+        $this->assertEquals(
43
+            'Clean up old calendar subscriptions from deleted users that were not cleaned-up',
44
+            $this->migration->getName()
45
+        );
46
+    }
47
+
48
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestRun')]
49
+    public function testRun(array $subscriptions, array $userExists, int $deletions): void {
50
+        $qb = $this->createMock(IQueryBuilder::class);
51
+
52
+        $qb->method('select')->willReturn($qb);
53
+
54
+        $functionBuilder = $this->createMock(IFunctionBuilder::class);
55
+
56
+        $qb->method('func')->willReturn($functionBuilder);
57
+        $functionBuilder->method('count')->willReturn($this->createMock(IQueryFunction::class));
58
+
59
+        $qb->method('selectDistinct')
60
+            ->with(['id', 'principaluri'])
61
+            ->willReturn($qb);
62
+
63
+        $qb->method('from')
64
+            ->with('calendarsubscriptions')
65
+            ->willReturn($qb);
66
+
67
+        $qb->method('setMaxResults')
68
+            ->willReturn($qb);
69
+
70
+        $qb->method('setFirstResult')
71
+            ->willReturn($qb);
72
+
73
+        $result = $this->createMock(IResult::class);
74
+
75
+        $qb->method('executeQuery')
76
+            ->willReturn($result);
77
+
78
+        $result->expects($this->once())
79
+            ->method('fetchOne')
80
+            ->willReturn(count($subscriptions));
81
+
82
+        $result
83
+            ->method('fetchAssociative')
84
+            ->willReturnOnConsecutiveCalls(...$subscriptions);
85
+
86
+        $qb->method('delete')
87
+            ->with('calendarsubscriptions')
88
+            ->willReturn($qb);
89
+
90
+        $expr = $this->createMock(IExpressionBuilder::class);
91
+
92
+        $qb->method('expr')->willReturn($expr);
93
+        $qb->method('createNamedParameter')->willReturn($this->createMock(IParameter::class));
94
+        $qb->method('where')->willReturn($qb);
95
+        // Only when user exists
96
+        $qb->expects($this->exactly($deletions))->method('executeStatement');
97
+
98
+        $this->dbConnection->method('getQueryBuilder')->willReturn($qb);
99
+
100
+
101
+        $this->output->expects($this->once())->method('startProgress');
102
+
103
+        $this->output->expects($subscriptions === [] ? $this->never(): $this->once())->method('advance');
104
+        if (count($subscriptions)) {
105
+            $this->userManager->method('userExists')
106
+                ->willReturnCallback(function (string $username) use ($userExists) {
107
+                    return $userExists[$username];
108
+                });
109
+        }
110
+        $this->output->expects($this->once())->method('finishProgress');
111
+        $this->output->expects($this->once())->method('info')->with(sprintf('%d calendar subscriptions without an user have been cleaned up', $deletions));
112
+
113
+        $this->migration->run($this->output);
114
+    }
115
+
116
+    public static function dataTestRun(): array {
117
+        return [
118
+            [[], [], 0],
119
+            [
120
+                [
121
+                    [
122
+                        'id' => 1,
123
+                        'principaluri' => 'users/principals/foo1',
124
+                    ],
125
+                    [
126
+                        'id' => 2,
127
+                        'principaluri' => 'users/principals/bar1',
128
+                    ],
129
+                    [
130
+                        'id' => 3,
131
+                        'principaluri' => 'users/principals/bar1',
132
+                    ],
133
+                    [],
134
+                ],
135
+                ['foo1' => true, 'bar1' => false],
136
+                2
137
+            ],
138
+        ];
139
+    }
140 140
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Federation/FederatedCalendarMapper.php 1 patch
Indentation   +188 added lines, -188 removed lines patch added patch discarded remove patch
@@ -18,192 +18,192 @@
 block discarded – undo
18 18
 
19 19
 /** @template-extends QBMapper<FederatedCalendarEntity>  */
20 20
 class FederatedCalendarMapper extends QBMapper {
21
-	public const TABLE_NAME = 'calendars_federated';
22
-
23
-	public function __construct(
24
-		IDBConnection $db,
25
-		private readonly ITimeFactory $time,
26
-	) {
27
-		parent::__construct($db, self::TABLE_NAME, FederatedCalendarEntity::class);
28
-	}
29
-
30
-	/**
31
-	 * @throws DoesNotExistException If there is no federated calendar with the given id.
32
-	 */
33
-	public function find(int $id): FederatedCalendarEntity {
34
-		$qb = $this->db->getQueryBuilder();
35
-		$qb->select('*')
36
-			->from(self::TABLE_NAME)
37
-			->where($qb->expr()->eq(
38
-				'id',
39
-				$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
40
-				IQueryBuilder::PARAM_INT,
41
-			));
42
-		return $this->findEntity($qb);
43
-	}
44
-
45
-	/**
46
-	 * @return FederatedCalendarEntity[]
47
-	 */
48
-	public function findByPrincipalUri(string $principalUri): array {
49
-		$qb = $this->db->getQueryBuilder();
50
-		$qb->select('*')
51
-			->from(self::TABLE_NAME)
52
-			->where($qb->expr()->eq(
53
-				'principaluri',
54
-				$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
55
-				IQueryBuilder::PARAM_STR,
56
-			));
57
-		return $this->findEntities($qb);
58
-	}
59
-
60
-	public function findByUri(string $principalUri, string $uri): ?FederatedCalendarEntity {
61
-		$qb = $this->db->getQueryBuilder();
62
-		$qb->select('*')
63
-			->from(self::TABLE_NAME)
64
-			->where($qb->expr()->eq(
65
-				'principaluri',
66
-				$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
67
-				IQueryBuilder::PARAM_STR,
68
-			))
69
-			->andWhere($qb->expr()->eq(
70
-				'uri',
71
-				$qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
72
-				IQueryBuilder::PARAM_STR,
73
-			));
74
-
75
-		try {
76
-			return $this->findEntity($qb);
77
-		} catch (DoesNotExistException $e) {
78
-			return null;
79
-		} catch (MultipleObjectsReturnedException $e) {
80
-			// Should never happen
81
-			return null;
82
-		}
83
-	}
84
-
85
-	/**
86
-	 * @return FederatedCalendarEntity[]
87
-	 */
88
-	public function findUnsyncedSinceBefore(int $beforeTimestamp): array {
89
-		$qb = $this->db->getQueryBuilder();
90
-		$qb->select('*')
91
-			->from(self::TABLE_NAME)
92
-			->where($qb->expr()->lt(
93
-				'last_sync',
94
-				$qb->createNamedParameter($beforeTimestamp, IQueryBuilder::PARAM_INT),
95
-				IQueryBuilder::PARAM_INT,
96
-			))
97
-			// Omit unsynced calendars for now as they are synced by a separate job
98
-			->andWhere($qb->expr()->isNotNull('last_sync'));
99
-		return $this->findEntities($qb);
100
-	}
101
-
102
-	public function deleteById(int $id): void {
103
-		$qb = $this->db->getQueryBuilder();
104
-		$qb->delete(self::TABLE_NAME)
105
-			->where($qb->expr()->eq(
106
-				'id',
107
-				$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
108
-				IQueryBuilder::PARAM_INT,
109
-			));
110
-		$qb->executeStatement();
111
-	}
112
-
113
-	public function updateSyncTime(int $id): void {
114
-		$now = $this->time->getTime();
115
-
116
-		$qb = $this->db->getQueryBuilder();
117
-		$qb->update(self::TABLE_NAME)
118
-			->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
119
-			->where($qb->expr()->eq(
120
-				'id',
121
-				$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
122
-				IQueryBuilder::PARAM_INT,
123
-			));
124
-		$qb->executeStatement();
125
-	}
126
-
127
-	public function updateSyncTokenAndTime(int $id, int $syncToken): void {
128
-		$now = $this->time->getTime();
129
-
130
-		$qb = $this->db->getQueryBuilder();
131
-		$qb->update(self::TABLE_NAME)
132
-			->set('sync_token', $qb->createNamedParameter($syncToken, IQueryBuilder::PARAM_INT))
133
-			->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
134
-			->where($qb->expr()->eq(
135
-				'id',
136
-				$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
137
-				IQueryBuilder::PARAM_INT,
138
-			));
139
-		$qb->executeStatement();
140
-	}
141
-
142
-	/**
143
-	 * @return \Generator<mixed, FederatedCalendarEntity>
144
-	 */
145
-	public function findAll(): \Generator {
146
-		$qb = $this->db->getQueryBuilder();
147
-		$qb->select('*')
148
-			->from(self::TABLE_NAME);
149
-
150
-		$result = $qb->executeQuery();
151
-		while ($row = $result->fetchAssociative()) {
152
-			yield $this->mapRowToEntity($row);
153
-		}
154
-		$result->closeCursor();
155
-	}
156
-
157
-	public function countAll(): int {
158
-		$qb = $this->db->getQueryBuilder();
159
-		$qb->select($qb->func()->count('*'))
160
-			->from(self::TABLE_NAME);
161
-		$result = $qb->executeQuery();
162
-		$count = (int)$result->fetchOne();
163
-		$result->closeCursor();
164
-		return $count;
165
-	}
166
-
167
-	public function deleteByUri(string $principalUri, string $uri): void {
168
-		$qb = $this->db->getQueryBuilder();
169
-		$qb->delete(self::TABLE_NAME)
170
-			->where($qb->expr()->eq(
171
-				'principaluri',
172
-				$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
173
-				IQueryBuilder::PARAM_STR,
174
-			))
175
-			->andWhere($qb->expr()->eq(
176
-				'uri',
177
-				$qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
178
-				IQueryBuilder::PARAM_STR,
179
-			));
180
-
181
-		$qb->executeStatement();
182
-	}
183
-
184
-	/**
185
-	 * @return FederatedCalendarEntity[]
186
-	 */
187
-	public function findByRemoteUrl(string $remoteUrl, string $principalUri, string $token): array {
188
-		$qb = $this->db->getQueryBuilder();
189
-		$qb->select('*')
190
-			->from(self::TABLE_NAME)
191
-			->where($qb->expr()->eq(
192
-				'remote_url',
193
-				$qb->createNamedParameter($remoteUrl, IQueryBuilder::PARAM_STR),
194
-				IQueryBuilder::PARAM_STR,
195
-			))
196
-			->andWhere($qb->expr()->eq(
197
-				'principaluri',
198
-				$qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
199
-				IQueryBuilder::PARAM_STR,
200
-			))
201
-			->andWhere($qb->expr()->eq(
202
-				'token',
203
-				$qb->createNamedParameter($token, IQueryBuilder::PARAM_STR),
204
-				IQueryBuilder::PARAM_STR,
205
-			));
206
-
207
-		return $this->findEntities($qb);
208
-	}
21
+    public const TABLE_NAME = 'calendars_federated';
22
+
23
+    public function __construct(
24
+        IDBConnection $db,
25
+        private readonly ITimeFactory $time,
26
+    ) {
27
+        parent::__construct($db, self::TABLE_NAME, FederatedCalendarEntity::class);
28
+    }
29
+
30
+    /**
31
+     * @throws DoesNotExistException If there is no federated calendar with the given id.
32
+     */
33
+    public function find(int $id): FederatedCalendarEntity {
34
+        $qb = $this->db->getQueryBuilder();
35
+        $qb->select('*')
36
+            ->from(self::TABLE_NAME)
37
+            ->where($qb->expr()->eq(
38
+                'id',
39
+                $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
40
+                IQueryBuilder::PARAM_INT,
41
+            ));
42
+        return $this->findEntity($qb);
43
+    }
44
+
45
+    /**
46
+     * @return FederatedCalendarEntity[]
47
+     */
48
+    public function findByPrincipalUri(string $principalUri): array {
49
+        $qb = $this->db->getQueryBuilder();
50
+        $qb->select('*')
51
+            ->from(self::TABLE_NAME)
52
+            ->where($qb->expr()->eq(
53
+                'principaluri',
54
+                $qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
55
+                IQueryBuilder::PARAM_STR,
56
+            ));
57
+        return $this->findEntities($qb);
58
+    }
59
+
60
+    public function findByUri(string $principalUri, string $uri): ?FederatedCalendarEntity {
61
+        $qb = $this->db->getQueryBuilder();
62
+        $qb->select('*')
63
+            ->from(self::TABLE_NAME)
64
+            ->where($qb->expr()->eq(
65
+                'principaluri',
66
+                $qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
67
+                IQueryBuilder::PARAM_STR,
68
+            ))
69
+            ->andWhere($qb->expr()->eq(
70
+                'uri',
71
+                $qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
72
+                IQueryBuilder::PARAM_STR,
73
+            ));
74
+
75
+        try {
76
+            return $this->findEntity($qb);
77
+        } catch (DoesNotExistException $e) {
78
+            return null;
79
+        } catch (MultipleObjectsReturnedException $e) {
80
+            // Should never happen
81
+            return null;
82
+        }
83
+    }
84
+
85
+    /**
86
+     * @return FederatedCalendarEntity[]
87
+     */
88
+    public function findUnsyncedSinceBefore(int $beforeTimestamp): array {
89
+        $qb = $this->db->getQueryBuilder();
90
+        $qb->select('*')
91
+            ->from(self::TABLE_NAME)
92
+            ->where($qb->expr()->lt(
93
+                'last_sync',
94
+                $qb->createNamedParameter($beforeTimestamp, IQueryBuilder::PARAM_INT),
95
+                IQueryBuilder::PARAM_INT,
96
+            ))
97
+            // Omit unsynced calendars for now as they are synced by a separate job
98
+            ->andWhere($qb->expr()->isNotNull('last_sync'));
99
+        return $this->findEntities($qb);
100
+    }
101
+
102
+    public function deleteById(int $id): void {
103
+        $qb = $this->db->getQueryBuilder();
104
+        $qb->delete(self::TABLE_NAME)
105
+            ->where($qb->expr()->eq(
106
+                'id',
107
+                $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
108
+                IQueryBuilder::PARAM_INT,
109
+            ));
110
+        $qb->executeStatement();
111
+    }
112
+
113
+    public function updateSyncTime(int $id): void {
114
+        $now = $this->time->getTime();
115
+
116
+        $qb = $this->db->getQueryBuilder();
117
+        $qb->update(self::TABLE_NAME)
118
+            ->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
119
+            ->where($qb->expr()->eq(
120
+                'id',
121
+                $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
122
+                IQueryBuilder::PARAM_INT,
123
+            ));
124
+        $qb->executeStatement();
125
+    }
126
+
127
+    public function updateSyncTokenAndTime(int $id, int $syncToken): void {
128
+        $now = $this->time->getTime();
129
+
130
+        $qb = $this->db->getQueryBuilder();
131
+        $qb->update(self::TABLE_NAME)
132
+            ->set('sync_token', $qb->createNamedParameter($syncToken, IQueryBuilder::PARAM_INT))
133
+            ->set('last_sync', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
134
+            ->where($qb->expr()->eq(
135
+                'id',
136
+                $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
137
+                IQueryBuilder::PARAM_INT,
138
+            ));
139
+        $qb->executeStatement();
140
+    }
141
+
142
+    /**
143
+     * @return \Generator<mixed, FederatedCalendarEntity>
144
+     */
145
+    public function findAll(): \Generator {
146
+        $qb = $this->db->getQueryBuilder();
147
+        $qb->select('*')
148
+            ->from(self::TABLE_NAME);
149
+
150
+        $result = $qb->executeQuery();
151
+        while ($row = $result->fetchAssociative()) {
152
+            yield $this->mapRowToEntity($row);
153
+        }
154
+        $result->closeCursor();
155
+    }
156
+
157
+    public function countAll(): int {
158
+        $qb = $this->db->getQueryBuilder();
159
+        $qb->select($qb->func()->count('*'))
160
+            ->from(self::TABLE_NAME);
161
+        $result = $qb->executeQuery();
162
+        $count = (int)$result->fetchOne();
163
+        $result->closeCursor();
164
+        return $count;
165
+    }
166
+
167
+    public function deleteByUri(string $principalUri, string $uri): void {
168
+        $qb = $this->db->getQueryBuilder();
169
+        $qb->delete(self::TABLE_NAME)
170
+            ->where($qb->expr()->eq(
171
+                'principaluri',
172
+                $qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
173
+                IQueryBuilder::PARAM_STR,
174
+            ))
175
+            ->andWhere($qb->expr()->eq(
176
+                'uri',
177
+                $qb->createNamedParameter($uri, IQueryBuilder::PARAM_STR),
178
+                IQueryBuilder::PARAM_STR,
179
+            ));
180
+
181
+        $qb->executeStatement();
182
+    }
183
+
184
+    /**
185
+     * @return FederatedCalendarEntity[]
186
+     */
187
+    public function findByRemoteUrl(string $remoteUrl, string $principalUri, string $token): array {
188
+        $qb = $this->db->getQueryBuilder();
189
+        $qb->select('*')
190
+            ->from(self::TABLE_NAME)
191
+            ->where($qb->expr()->eq(
192
+                'remote_url',
193
+                $qb->createNamedParameter($remoteUrl, IQueryBuilder::PARAM_STR),
194
+                IQueryBuilder::PARAM_STR,
195
+            ))
196
+            ->andWhere($qb->expr()->eq(
197
+                'principaluri',
198
+                $qb->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR),
199
+                IQueryBuilder::PARAM_STR,
200
+            ))
201
+            ->andWhere($qb->expr()->eq(
202
+                'token',
203
+                $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR),
204
+                IQueryBuilder::PARAM_STR,
205
+            ));
206
+
207
+        return $this->findEntities($qb);
208
+    }
209 209
 }
Please login to merge, or discard this patch.