Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace repository_dropbox;
18
 
19
/**
20
 * Tests for the Dropbox API (v2).
21
 *
22
 * @package     repository_dropbox
23
 * @copyright   Andrew Nicols <andrew@nicols.co.uk>
24
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
class api_test extends \advanced_testcase {
27
    /**
28
     * Data provider for has_additional_results.
29
     *
30
     * @return array
31
     */
32
    public function has_additional_results_provider() {
33
        return [
34
            'No more results' => [
35
                (object) [
36
                    'has_more'  => false,
37
                    'cursor'    => '',
38
                ],
39
                false
40
            ],
41
            'Has more, No cursor' => [
42
                (object) [
43
                    'has_more'  => true,
44
                    'cursor'    => '',
45
                ],
46
                false
47
            ],
48
            'Has more, Has cursor' => [
49
                (object) [
50
                    'has_more'  => true,
51
                    'cursor'    => 'example_cursor',
52
                ],
53
                true
54
            ],
55
            'Missing has_more' => [
56
                (object) [
57
                    'cursor'    => 'example_cursor',
58
                ],
59
                false
60
            ],
61
            'Missing cursor' => [
62
                (object) [
63
                    'has_more'  => 'example_cursor',
64
                ],
65
                false
66
            ],
67
        ];
68
    }
69
 
70
    /**
71
     * Tests for the has_additional_results API function.
72
     *
73
     * @dataProvider has_additional_results_provider
74
     * @param   object      $result     The data to test
75
     * @param   bool        $expected   The expected result
76
     */
11 efrain 77
    public function test_has_additional_results($result, $expected): void {
1 efrain 78
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
79
            ->disableOriginalConstructor()
80
            ->onlyMethods([])
81
            ->getMock();
82
 
83
        $this->assertEquals($expected, $mock->has_additional_results($result));
84
    }
85
 
86
    /**
87
     * Data provider for check_and_handle_api_errors.
88
     *
89
     * @return array
90
     */
91
    public function check_and_handle_api_errors_provider() {
92
        return [
93
            '200 http_code' => [
94
                ['http_code' => 200],
95
                '',
96
                null,
97
                null,
98
            ],
99
            '400 http_code' => [
100
                ['http_code' => 400],
101
                'Unused',
102
                'coding_exception',
103
                'Invalid input parameter passed to DropBox API.',
104
            ],
105
            '401 http_code' => [
106
                ['http_code' => 401],
107
                'Unused',
108
                \repository_dropbox\authentication_exception::class,
109
                'Authentication token expired',
110
            ],
111
            '409 http_code' => [
112
                ['http_code' => 409],
113
                json_decode('{"error": "Some value", "error_summary": "Some data here"}'),
114
                'coding_exception',
115
                'Endpoint specific error: Some data here',
116
            ],
117
            '429 http_code' => [
118
                ['http_code' => 429],
119
                'Unused',
120
                \repository_dropbox\rate_limit_exception::class,
121
                'Rate limit hit',
122
            ],
123
            '500 http_code' => [
124
                ['http_code' => 500],
125
                'Response body',
126
                'invalid_response_exception',
127
                '500: Response body',
128
            ],
129
            '599 http_code' => [
130
                ['http_code' => 599],
131
                'Response body',
132
                'invalid_response_exception',
133
                '599: Response body',
134
            ],
135
            '600 http_code (invalid, but not officially an error)' => [
136
                ['http_code' => 600],
137
                '',
138
                null,
139
                null,
140
            ],
141
        ];
142
    }
143
 
144
    /**
145
     * Tests for check_and_handle_api_errors.
146
     *
147
     * @dataProvider check_and_handle_api_errors_provider
148
     * @param   object      $info       The response to test
149
     * @param   string      $data       The contented returned by the curl call
150
     * @param   string      $exception  The name of the expected exception
151
     * @param   string      $exceptionmessage  The expected message in the exception
152
     */
11 efrain 153
    public function test_check_and_handle_api_errors($info, $data, $exception, $exceptionmessage): void {
1 efrain 154
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
155
            ->disableOriginalConstructor()
156
            ->onlyMethods([])
157
            ->getMock();
158
 
159
        $mock->info = $info;
160
 
161
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
162
        $rcm = $rc->getMethod('check_and_handle_api_errors');
163
 
164
        if ($exception) {
165
            $this->expectException($exception);
166
        }
167
 
168
        if ($exceptionmessage) {
169
            $this->expectExceptionMessage($exceptionmessage);
170
        }
171
 
172
        $result = $rcm->invoke($mock, $data);
173
 
174
        $this->assertNull($result);
175
    }
176
 
177
    /**
178
     * Data provider for the supports_thumbnail function.
179
     *
180
     * @return array
181
     */
182
    public function supports_thumbnail_provider() {
183
        $tests = [
184
            'Only files support thumbnails' => [
185
                (object) ['.tag' => 'folder'],
186
                false,
187
            ],
188
            'Dropbox currently only supports thumbnail generation for files under 20MB' => [
189
                (object) [
190
                    '.tag'          => 'file',
191
                    'size'          => 21 * 1024 * 1024,
192
                ],
193
                false,
194
            ],
195
            'Unusual file extension containing a working format but ending in a non-working one' => [
196
                (object) [
197
                    '.tag'          => 'file',
198
                    'size'          => 100 * 1024,
199
                    'path_lower'    => 'Example.jpg.pdf',
200
                ],
201
                false,
202
            ],
203
            'Unusual file extension ending in a working extension' => [
204
                (object) [
205
                    '.tag'          => 'file',
206
                    'size'          => 100 * 1024,
207
                    'path_lower'    => 'Example.pdf.jpg',
208
                ],
209
                true,
210
            ],
211
        ];
212
 
213
        // See docs at https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail.
214
        $types = [
215
                'pdf'   => false,
216
                'doc'   => false,
217
                'docx'  => false,
218
                'jpg'   => true,
219
                'jpeg'  => true,
220
                'png'   => true,
221
                'tiff'  => true,
222
                'tif'   => true,
223
                'gif'   => true,
224
                'bmp'   => true,
225
            ];
226
        foreach ($types as $type => $result) {
227
            $tests["Test support for {$type}"] = [
228
                (object) [
229
                    '.tag'          => 'file',
230
                    'size'          => 100 * 1024,
231
                    'path_lower'    => "example_filename.{$type}",
232
                ],
233
                $result,
234
            ];
235
        }
236
 
237
        return $tests;
238
    }
239
 
240
    /**
241
     * Test the supports_thumbnail function.
242
     *
243
     * @dataProvider supports_thumbnail_provider
244
     * @param   object      $entry      The entry to test
245
     * @param   bool        $expected   Whether this entry supports thumbnail generation
246
     */
11 efrain 247
    public function test_supports_thumbnail($entry, $expected): void {
1 efrain 248
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
249
            ->disableOriginalConstructor()
250
            ->onlyMethods([])
251
            ->getMock();
252
 
253
        $this->assertEquals($expected, $mock->supports_thumbnail($entry));
254
    }
255
 
256
    /**
257
     * Test that the logout makes a call to the correct revocation endpoint.
258
     */
11 efrain 259
    public function test_logout_revocation(): void {
1 efrain 260
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
261
            ->disableOriginalConstructor()
262
            ->onlyMethods(['fetch_dropbox_data'])
263
            ->getMock();
264
 
265
        $mock->expects($this->once())
266
            ->method('fetch_dropbox_data')
267
            ->with($this->equalTo('auth/token/revoke'), $this->equalTo(null));
268
 
269
        $this->assertNull($mock->logout());
270
    }
271
 
272
    /**
273
     * Test that the logout function catches authentication_exception exceptions and discards them.
274
     */
11 efrain 275
    public function test_logout_revocation_catch_auth_exception(): void {
1 efrain 276
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
277
            ->disableOriginalConstructor()
278
            ->onlyMethods(['fetch_dropbox_data'])
279
            ->getMock();
280
 
281
        $mock->expects($this->once())
282
            ->method('fetch_dropbox_data')
283
            ->will($this->throwException(new \repository_dropbox\authentication_exception('Exception should be caught')));
284
 
285
        $this->assertNull($mock->logout());
286
    }
287
 
288
    /**
289
     * Test that the logout function does not catch any other exception.
290
     */
11 efrain 291
    public function test_logout_revocation_does_not_catch_other_exceptions(): void {
1 efrain 292
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
293
            ->disableOriginalConstructor()
294
            ->onlyMethods(['fetch_dropbox_data'])
295
            ->getMock();
296
 
297
        $mock->expects($this->once())
298
            ->method('fetch_dropbox_data')
299
            ->will($this->throwException(new \repository_dropbox\rate_limit_exception));
300
 
301
        $this->expectException(\repository_dropbox\rate_limit_exception::class);
302
        $mock->logout();
303
    }
304
 
305
    /**
306
     * Test basic fetch_dropbox_data function.
307
     */
11 efrain 308
    public function test_fetch_dropbox_data_endpoint(): void {
1 efrain 309
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
310
            ->disableOriginalConstructor()
311
            ->onlyMethods([
312
                'request',
313
                'get_api_endpoint',
314
                'get_content_endpoint',
315
            ])
316
            ->getMock();
317
 
318
        $endpoint = 'testEndpoint';
319
 
320
        // The fetch_dropbox_data call should be called against the standard endpoint only.
321
        $mock->expects($this->once())
322
            ->method('get_api_endpoint')
323
            ->with($endpoint)
324
            ->will($this->returnValue("https://example.com/api/2/{$endpoint}"));
325
 
326
        $mock->expects($this->never())
327
            ->method('get_content_endpoint');
328
 
329
        $mock->expects($this->once())
330
            ->method('request')
331
            ->will($this->returnValue(json_encode([])));
332
 
333
        // Make the call.
334
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
335
        $rcm = $rc->getMethod('fetch_dropbox_data');
336
        $rcm->invoke($mock, $endpoint);
337
    }
338
 
339
    /**
340
     * Some Dropbox endpoints require that the POSTFIELDS be set to null exactly.
341
     */
11 efrain 342
    public function test_fetch_dropbox_data_postfields_null(): void {
1 efrain 343
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
344
            ->disableOriginalConstructor()
345
            ->onlyMethods([
346
                'request',
347
            ])
348
            ->getMock();
349
 
350
        $endpoint = 'testEndpoint';
351
 
352
        $mock->expects($this->once())
353
            ->method('request')
354
            ->with($this->anything(), $this->callback(function($d) {
355
                    return $d['CURLOPT_POSTFIELDS'] === 'null';
356
                }))
357
            ->will($this->returnValue(json_encode([])));
358
 
359
        // Make the call.
360
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
361
        $rcm = $rc->getMethod('fetch_dropbox_data');
362
        $rcm->invoke($mock, $endpoint, null);
363
    }
364
 
365
    /**
366
     * When data is specified, it should be json_encoded in POSTFIELDS.
367
     */
11 efrain 368
    public function test_fetch_dropbox_data_postfields_data(): void {
1 efrain 369
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
370
            ->disableOriginalConstructor()
371
            ->onlyMethods([
372
                'request',
373
            ])
374
            ->getMock();
375
 
376
        $endpoint = 'testEndpoint';
377
        $data = ['something' => 'somevalue'];
378
 
379
        $mock->expects($this->once())
380
            ->method('request')
381
            ->with($this->anything(), $this->callback(function($d) use ($data) {
382
                    return $d['CURLOPT_POSTFIELDS'] === json_encode($data);
383
                }))
384
            ->will($this->returnValue(json_encode([])));
385
 
386
        // Make the call.
387
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
388
        $rcm = $rc->getMethod('fetch_dropbox_data');
389
        $rcm->invoke($mock, $endpoint, $data);
390
    }
391
 
392
    /**
393
     * When more results are available, these should be fetched until there are no more.
394
     */
11 efrain 395
    public function test_fetch_dropbox_data_recurse_on_additional_records(): void {
1 efrain 396
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
397
            ->disableOriginalConstructor()
398
            ->onlyMethods([
399
                'request',
400
                'get_api_endpoint',
401
            ])
402
            ->getMock();
403
 
404
        $endpoint = 'testEndpoint';
405
 
406
        // We can't detect if fetch_dropbox_data was called twice because
407
        // we can'
408
        $mock->expects($this->exactly(3))
409
            ->method('request')
410
            ->will($this->onConsecutiveCalls(
411
                json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['foo', 'bar']]),
412
                json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['baz']]),
413
                json_encode(['has_more' => false, 'cursor' => '', 'matches' => ['bum']])
414
            ));
415
 
416
        // We automatically adjust for the /continue endpoint.
417
        $mock->expects($this->exactly(3))
418
            ->method('get_api_endpoint')
419
            ->withConsecutive(['testEndpoint'], ['testEndpoint/continue'], ['testEndpoint/continue'])
420
            ->willReturn($this->onConsecutiveCalls(
421
                'https://example.com/api/2/testEndpoint',
422
                'https://example.com/api/2/testEndpoint/continue',
423
                'https://example.com/api/2/testEndpoint/continue'
424
            ));
425
 
426
        // Make the call.
427
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
428
        $rcm = $rc->getMethod('fetch_dropbox_data');
429
        $result = $rcm->invoke($mock, $endpoint, null, 'matches');
430
 
431
        $this->assertEquals([
432
            'foo',
433
            'bar',
434
            'baz',
435
            'bum',
436
        ], $result->matches);
437
 
438
        $this->assertFalse(isset($result->cursor));
439
        $this->assertFalse(isset($result->has_more));
440
    }
441
 
442
    /**
443
     * Base tests for the fetch_dropbox_content function.
444
     */
11 efrain 445
    public function test_fetch_dropbox_content(): void {
1 efrain 446
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
447
            ->disableOriginalConstructor()
448
            ->onlyMethods([
449
                'request',
450
                'setHeader',
451
                'get_content_endpoint',
452
                'get_api_endpoint',
453
                'check_and_handle_api_errors',
454
            ])
455
            ->getMock();
456
 
457
        $data = ['exampledata' => 'examplevalue'];
458
        $endpoint = 'getContent';
459
        $url = "https://example.com/api/2/{$endpoint}";
460
        $response = 'Example content';
461
 
462
        // Only the content endpoint should be called.
463
        $mock->expects($this->once())
464
            ->method('get_content_endpoint')
465
            ->with($endpoint)
466
            ->will($this->returnValue($url));
467
 
468
        $mock->expects($this->never())
469
            ->method('get_api_endpoint');
470
 
471
        $mock->expects($this->exactly(2))
472
            ->method('setHeader')
473
            ->withConsecutive(
474
                [$this->equalTo('Content-Type: ')],
475
                [$this->equalTo('Dropbox-API-Arg: ' . json_encode($data))]
476
            );
477
 
478
        // Only one request should be made, and it should forcibly be a POST.
479
        $mock->expects($this->once())
480
            ->method('request')
481
            ->with($this->equalTo($url), $this->callback(function($options) {
482
                return $options['CURLOPT_POST'] === 1;
483
            }))
484
            ->willReturn($response);
485
 
486
        $mock->expects($this->once())
487
            ->method('check_and_handle_api_errors')
488
            ->with($this->equalTo($response))
489
            ;
490
 
491
        // Make the call.
492
        $rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
493
        $rcm = $rc->getMethod('fetch_dropbox_content');
494
        $result = $rcm->invoke($mock, $endpoint, $data);
495
 
496
        $this->assertEquals($response, $result);
497
    }
498
 
499
    /**
500
     * Test that the get_file_share_info function returns an existing link if one is available.
501
     */
11 efrain 502
    public function test_get_file_share_info_existing(): void {
1 efrain 503
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
504
            ->disableOriginalConstructor()
505
            ->onlyMethods([
506
                'fetch_dropbox_data',
507
                'normalize_file_share_info',
508
            ])
509
            ->getMock();
510
 
511
        $id = 'LifeTheUniverseAndEverything';
512
        $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
513
        $sharelink = 'https://example.com/share/link';
514
 
515
        // Mock fetch_dropbox_data to return an existing file.
516
        $mock->expects($this->once())
517
            ->method('fetch_dropbox_data')
518
            ->with(
519
                $this->equalTo('sharing/list_shared_links'),
520
                $this->equalTo(['path' => $id])
521
            )
522
            ->willReturn((object) ['links' => [$file]]);
523
 
524
        $mock->expects($this->once())
525
            ->method('normalize_file_share_info')
526
            ->with($this->equalTo($file))
527
            ->will($this->returnValue($sharelink));
528
 
529
        $this->assertEquals($sharelink, $mock->get_file_share_info($id));
530
    }
531
 
532
    /**
533
     * Test that the get_file_share_info function creates a new link if one is not available.
534
     */
11 efrain 535
    public function test_get_file_share_info_new(): void {
1 efrain 536
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
537
            ->disableOriginalConstructor()
538
            ->onlyMethods([
539
                'fetch_dropbox_data',
540
                'normalize_file_share_info',
541
            ])
542
            ->getMock();
543
 
544
        $id = 'LifeTheUniverseAndEverything';
545
        $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
546
        $sharelink = 'https://example.com/share/link';
547
 
548
        // Mock fetch_dropbox_data to return an existing file.
549
        $mock->expects($this->exactly(2))
550
            ->method('fetch_dropbox_data')
551
            ->withConsecutive(
552
                [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
553
                [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
554
                    'path' => $id,
555
                    'settings' => [
556
                        'requested_visibility' => 'public',
557
                    ]
558
                ])]
559
            )
560
            ->will($this->onConsecutiveCalls(
561
                (object) ['links' => []],
562
                $file
563
            ));
564
 
565
        $mock->expects($this->once())
566
            ->method('normalize_file_share_info')
567
            ->with($this->equalTo($file))
568
            ->will($this->returnValue($sharelink));
569
 
570
        $this->assertEquals($sharelink, $mock->get_file_share_info($id));
571
    }
572
 
573
    /**
574
     * Test failure behaviour with get_file_share_info fails to create a new link.
575
     */
11 efrain 576
    public function test_get_file_share_info_new_failure(): void {
1 efrain 577
        $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
578
            ->disableOriginalConstructor()
579
            ->onlyMethods([
580
                'fetch_dropbox_data',
581
                'normalize_file_share_info',
582
            ])
583
            ->getMock();
584
 
585
        $id = 'LifeTheUniverseAndEverything';
586
 
587
        // Mock fetch_dropbox_data to return an existing file.
588
        $mock->expects($this->exactly(2))
589
            ->method('fetch_dropbox_data')
590
            ->withConsecutive(
591
                [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
592
                [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
593
                    'path' => $id,
594
                    'settings' => [
595
                        'requested_visibility' => 'public',
596
                    ]
597
                ])]
598
            )
599
            ->will($this->onConsecutiveCalls(
600
                (object) ['links' => []],
601
                null
602
            ));
603
 
604
        $mock->expects($this->never())
605
            ->method('normalize_file_share_info');
606
 
607
        $this->assertNull($mock->get_file_share_info($id));
608
    }
609
}