1<?php
2
3namespace Sabre\DAV\Sync;
4
5use
6    Sabre\DAV,
7    Sabre\HTTP;
8
9require_once __DIR__ . '/MockSyncCollection.php';
10
11class PluginTest extends \Sabre\DAVServerTest {
12
13    protected $collection;
14
15    function setUp() {
16
17        parent::setUp();
18        $this->server->addPlugin(new Plugin());
19
20    }
21
22    function testGetInfo() {
23
24        $this->assertArrayHasKey(
25            'name',
26            (new Plugin())->getPluginInfo()
27        );
28
29    }
30
31    function setUpTree() {
32
33        $this->collection =
34            new MockSyncCollection('coll', [
35                new DAV\SimpleFile('file1.txt','foo'),
36                new DAV\SimpleFile('file2.txt','bar'),
37            ]);
38        $this->tree = [
39            $this->collection,
40            new DAV\SimpleCollection('normalcoll', [])
41        ];
42
43    }
44
45    function testSupportedReportSet() {
46
47        $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']);
48        $this->assertFalse($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection'));
49
50        // Making a change
51        $this->collection->addChange(['file1.txt'], [], []);
52
53        $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']);
54        $this->assertTrue($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection'));
55
56    }
57
58    function testGetSyncToken() {
59
60        $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']);
61        $this->assertFalse(isset($result['{DAV:}sync-token']));
62
63        // Making a change
64        $this->collection->addChange(['file1.txt'], [], []);
65
66        $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']);
67        $this->assertTrue(isset($result['{DAV:}sync-token']));
68
69        // non-sync-enabled collection
70        $this->collection->addChange(['file1.txt'], [], []);
71
72        $result = $this->server->getProperties('/normalcoll', ['{DAV:}sync-token']);
73        $this->assertFalse(isset($result['{DAV:}sync-token']));
74    }
75
76    function testSyncInitialSyncCollection() {
77
78        // Making a change
79        $this->collection->addChange(['file1.txt'], [], []);
80
81        $request = new HTTP\Request('REPORT', '/coll/', ['Content-Type' => 'application/xml']);
82
83        $body = <<<BLA
84<?xml version="1.0" encoding="utf-8" ?>
85<D:sync-collection xmlns:D="DAV:">
86     <D:sync-token/>
87     <D:sync-level>1</D:sync-level>
88      <D:prop>
89        <D:getcontentlength/>
90      </D:prop>
91</D:sync-collection>
92BLA;
93
94        $request->setBody($body);
95
96        $response = $this->request($request);
97
98        $this->assertEquals(207, $response->status, 'Full response body:' . $response->body);
99
100        $multiStatus = $this->server->xml->parse($response->getBodyAsString());
101
102        // Checking the sync-token
103        $this->assertEquals(
104            'http://sabre.io/ns/sync/1',
105            $multiStatus->getSyncToken()
106        );
107
108        $responses = $multiStatus->getResponses();
109        $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response');
110
111        $response = $responses[0];
112
113        $this->assertNull($response->getHttpStatus());
114        $this->assertEquals('/coll/file1.txt', $response->getHref());
115        $this->assertEquals([
116            200 => [
117                '{DAV:}getcontentlength' => 3,
118            ]
119        ], $response->getResponseProperties());
120
121        $response = $responses[1];
122
123        $this->assertNull($response->getHttpStatus());
124        $this->assertEquals('/coll/file2.txt', $response->getHref());
125        $this->assertEquals([
126            200 => [
127                '{DAV:}getcontentlength' => 3,
128            ]
129        ], $response->getResponseProperties());
130
131    }
132
133    function testSubsequentSyncSyncCollection() {
134
135        // Making a change
136        $this->collection->addChange(['file1.txt'], [], []);
137        // Making another change
138        $this->collection->addChange([], ['file2.txt'], ['file3.txt']);
139
140        $request = HTTP\Sapi::createFromServerArray([
141            'REQUEST_METHOD' => 'REPORT',
142            'REQUEST_URI'    => '/coll/',
143            'CONTENT_TYPE'    => 'application/xml',
144        ]);
145
146        $body = <<<BLA
147<?xml version="1.0" encoding="utf-8" ?>
148<D:sync-collection xmlns:D="DAV:">
149     <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token>
150     <D:sync-level>infinite</D:sync-level>
151      <D:prop>
152        <D:getcontentlength/>
153      </D:prop>
154</D:sync-collection>
155BLA;
156
157        $request->setBody($body);
158
159        $response = $this->request($request);
160
161        $this->assertEquals(207, $response->status, 'Full response body:' . $response->body);
162
163        $multiStatus = $this->server->xml->parse($response->getBodyAsString());
164
165        // Checking the sync-token
166        $this->assertEquals(
167            'http://sabre.io/ns/sync/2',
168            $multiStatus->getSyncToken()
169        );
170
171        $responses = $multiStatus->getResponses();
172        $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response');
173
174        $response = $responses[0];
175
176        $this->assertNull($response->getHttpStatus());
177        $this->assertEquals('/coll/file2.txt', $response->getHref());
178        $this->assertEquals([
179            200 => [
180                '{DAV:}getcontentlength' => 3,
181            ]
182        ], $response->getResponseProperties());
183
184        $response = $responses[1];
185
186        $this->assertEquals('404', $response->getHttpStatus());
187        $this->assertEquals('/coll/file3.txt', $response->getHref());
188        $this->assertEquals([], $response->getResponseProperties());
189
190    }
191
192    function testSubsequentSyncSyncCollectionLimit() {
193
194        // Making a change
195        $this->collection->addChange(['file1.txt'], [], []);
196        // Making another change
197        $this->collection->addChange([], ['file2.txt'], ['file3.txt']);
198
199        $request = HTTP\Sapi::createFromServerArray([
200            'REQUEST_METHOD' => 'REPORT',
201            'REQUEST_URI'    => '/coll/',
202            'CONTENT_TYPE'    => 'application/xml',
203        ]);
204
205        $body = <<<BLA
206<?xml version="1.0" encoding="utf-8" ?>
207<D:sync-collection xmlns:D="DAV:">
208    <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token>
209    <D:sync-level>infinite</D:sync-level>
210    <D:prop>
211        <D:getcontentlength/>
212    </D:prop>
213    <D:limit><D:nresults>1</D:nresults></D:limit>
214</D:sync-collection>
215BLA;
216
217        $request->setBody($body);
218
219        $response = $this->request($request);
220
221        $this->assertEquals(207, $response->status, 'Full response body:' . $response->body);
222
223        $multiStatus = $this->server->xml->parse(
224            $response->getBodyAsString()
225        );
226
227        // Checking the sync-token
228        $this->assertEquals(
229            'http://sabre.io/ns/sync/2',
230            $multiStatus->getSyncToken()
231        );
232
233        $responses = $multiStatus->getResponses();
234        $this->assertEquals(1, count($responses), 'We expected exactly 1 {DAV:}response');
235
236        $response = $responses[0];
237
238        $this->assertEquals('404', $response->getHttpStatus());
239        $this->assertEquals('/coll/file3.txt', $response->getHref());
240        $this->assertEquals([], $response->getResponseProperties());
241
242    }
243
244    function testSubsequentSyncSyncCollectionDepthFallBack() {
245
246        // Making a change
247        $this->collection->addChange(['file1.txt'], [], []);
248        // Making another change
249        $this->collection->addChange([], ['file2.txt'], ['file3.txt']);
250
251        $request = HTTP\Sapi::createFromServerArray([
252            'REQUEST_METHOD' => 'REPORT',
253            'REQUEST_URI'    => '/coll/',
254            'CONTENT_TYPE'   => 'application/xml',
255            'HTTP_DEPTH'     => "1",
256        ]);
257
258        $body = <<<BLA
259<?xml version="1.0" encoding="utf-8" ?>
260<D:sync-collection xmlns:D="DAV:">
261     <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token>
262      <D:prop>
263        <D:getcontentlength/>
264      </D:prop>
265</D:sync-collection>
266BLA;
267
268        $request->setBody($body);
269
270        $response = $this->request($request);
271
272        $this->assertEquals(207, $response->status, 'Full response body:' . $response->body);
273
274        $multiStatus = $this->server->xml->parse(
275            $response->getBodyAsString()
276        );
277
278        // Checking the sync-token
279        $this->assertEquals(
280            'http://sabre.io/ns/sync/2',
281            $multiStatus->getSyncToken()
282        );
283
284        $responses = $multiStatus->getResponses();
285        $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response');
286
287        $response = $responses[0];
288
289        $this->assertNull($response->getHttpStatus());
290        $this->assertEquals('/coll/file2.txt', $response->getHref());
291        $this->assertEquals([
292            200 => [
293                '{DAV:}getcontentlength' => 3,
294            ]
295        ], $response->getResponseProperties());
296
297        $response = $responses[1];
298
299        $this->assertEquals('404', $response->getHttpStatus());
300        $this->assertEquals('/coll/file3.txt', $response->getHref());
301        $this->assertEquals([], $response->getResponseProperties());
302
303    }
304
305    function testSyncNoSyncInfo() {
306
307        $request = HTTP\Sapi::createFromServerArray([
308            'REQUEST_METHOD' => 'REPORT',
309            'REQUEST_URI'    => '/coll/',
310            'CONTENT_TYPE'    => 'application/xml',
311        ]);
312
313        $body = <<<BLA
314<?xml version="1.0" encoding="utf-8" ?>
315<D:sync-collection xmlns:D="DAV:">
316     <D:sync-token/>
317     <D:sync-level>1</D:sync-level>
318      <D:prop>
319        <D:getcontentlength/>
320      </D:prop>
321</D:sync-collection>
322BLA;
323
324        $request->setBody($body);
325
326        $response = $this->request($request);
327
328        // The default state has no sync-token, so this report should not yet
329        // be supported.
330        $this->assertEquals(415, $response->status, 'Full response body:' . $response->body);
331
332    }
333
334    function testSyncNoSyncCollection() {
335
336        $request = HTTP\Sapi::createFromServerArray([
337            'REQUEST_METHOD' => 'REPORT',
338            'REQUEST_URI'    => '/normalcoll/',
339            'CONTENT_TYPE'    => 'application/xml',
340        ]);
341
342        $body = <<<BLA
343<?xml version="1.0" encoding="utf-8" ?>
344<D:sync-collection xmlns:D="DAV:">
345     <D:sync-token/>
346     <D:sync-level>1</D:sync-level>
347      <D:prop>
348        <D:getcontentlength/>
349      </D:prop>
350</D:sync-collection>
351BLA;
352
353        $request->setBody($body);
354
355        $response = $this->request($request);
356
357        // The default state has no sync-token, so this report should not yet
358        // be supported.
359        $this->assertEquals(415, $response->status, 'Full response body:' . $response->body);
360
361    }
362
363    function testSyncInvalidToken() {
364
365        $this->collection->addChange(['file1.txt'], [], []);
366        $request = HTTP\Sapi::createFromServerArray([
367            'REQUEST_METHOD' => 'REPORT',
368            'REQUEST_URI'    => '/coll/',
369            'CONTENT_TYPE'    => 'application/xml',
370        ]);
371
372        $body = <<<BLA
373<?xml version="1.0" encoding="utf-8" ?>
374<D:sync-collection xmlns:D="DAV:">
375     <D:sync-token>http://sabre.io/ns/sync/invalid</D:sync-token>
376     <D:sync-level>1</D:sync-level>
377      <D:prop>
378        <D:getcontentlength/>
379      </D:prop>
380</D:sync-collection>
381BLA;
382
383        $request->setBody($body);
384
385        $response = $this->request($request);
386
387        // The default state has no sync-token, so this report should not yet
388        // be supported.
389        $this->assertEquals(403, $response->status, 'Full response body:' . $response->body);
390
391    }
392    function testSyncInvalidTokenNoPrefix() {
393
394        $this->collection->addChange(['file1.txt'], [], []);
395        $request = HTTP\Sapi::createFromServerArray([
396            'REQUEST_METHOD' => 'REPORT',
397            'REQUEST_URI'    => '/coll/',
398            'CONTENT_TYPE'    => 'application/xml',
399        ]);
400
401        $body = <<<BLA
402<?xml version="1.0" encoding="utf-8" ?>
403<D:sync-collection xmlns:D="DAV:">
404     <D:sync-token>invalid</D:sync-token>
405     <D:sync-level>1</D:sync-level>
406      <D:prop>
407        <D:getcontentlength/>
408      </D:prop>
409</D:sync-collection>
410BLA;
411
412        $request->setBody($body);
413
414        $response = $this->request($request);
415
416        // The default state has no sync-token, so this report should not yet
417        // be supported.
418        $this->assertEquals(403, $response->status, 'Full response body:' . $response->body);
419
420    }
421
422    function testSyncNoSyncToken() {
423
424        $request = HTTP\Sapi::createFromServerArray([
425            'REQUEST_METHOD' => 'REPORT',
426            'REQUEST_URI'    => '/coll/',
427            'CONTENT_TYPE'    => 'application/xml',
428        ]);
429
430        $body = <<<BLA
431<?xml version="1.0" encoding="utf-8" ?>
432<D:sync-collection xmlns:D="DAV:">
433     <D:sync-level>1</D:sync-level>
434      <D:prop>
435        <D:getcontentlength/>
436      </D:prop>
437</D:sync-collection>
438BLA;
439
440        $request->setBody($body);
441
442        $response = $this->request($request);
443
444        // The default state has no sync-token, so this report should not yet
445        // be supported.
446        $this->assertEquals(400, $response->status, 'Full response body:' . $response->body);
447
448    }
449
450    function testSyncNoProp() {
451
452        $this->collection->addChange(['file1.txt'], [], []);
453        $request = HTTP\Sapi::createFromServerArray([
454            'REQUEST_METHOD' => 'REPORT',
455            'REQUEST_URI'    => '/coll/',
456            'CONTENT_TYPE'   => 'application/xml',
457        ]);
458
459        $body = <<<BLA
460<?xml version="1.0" encoding="utf-8" ?>
461<D:sync-collection xmlns:D="DAV:">
462     <D:sync-token />
463     <D:sync-level>1</D:sync-level>
464</D:sync-collection>
465BLA;
466
467        $request->setBody($body);
468
469        $response = $this->request($request);
470
471        // The default state has no sync-token, so this report should not yet
472        // be supported.
473        $this->assertEquals(400, $response->status, 'Full response body:' . $response->body);
474
475    }
476
477    function testIfConditions() {
478
479        $this->collection->addChange(['file1.txt'], [], []);
480        $request = HTTP\Sapi::createFromServerArray([
481            'REQUEST_METHOD' => 'DELETE',
482            'REQUEST_URI'    => '/coll/file1.txt',
483            'HTTP_IF'        => '</coll> (<http://sabre.io/ns/sync/1>)',
484        ]);
485        $response = $this->request($request);
486
487        // If a 403 is thrown this works correctly. The file in questions
488        // doesn't allow itself to be deleted.
489        // If the If conditions failed, it would have been a 412 instead.
490        $this->assertEquals(403, $response->status);
491
492    }
493
494    function testIfConditionsNot() {
495
496        $this->collection->addChange(['file1.txt'], [], []);
497        $request = HTTP\Sapi::createFromServerArray([
498            'REQUEST_METHOD' => 'DELETE',
499            'REQUEST_URI'    => '/coll/file1.txt',
500            'HTTP_IF'        => '</coll> (Not <http://sabre.io/ns/sync/2>)',
501        ]);
502        $response = $this->request($request);
503
504        // If a 403 is thrown this works correctly. The file in questions
505        // doesn't allow itself to be deleted.
506        // If the If conditions failed, it would have been a 412 instead.
507        $this->assertEquals(403, $response->status);
508
509    }
510
511    function testIfConditionsNoSyncToken() {
512
513        $this->collection->addChange(['file1.txt'], [], []);
514        $request = HTTP\Sapi::createFromServerArray([
515            'REQUEST_METHOD' => 'DELETE',
516            'REQUEST_URI'    => '/coll/file1.txt',
517            'HTTP_IF'        => '</coll> (<opaquelocktoken:foo>)',
518        ]);
519        $response = $this->request($request);
520
521        $this->assertEquals(412, $response->status);
522
523    }
524}
525