2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 namespace repository_dropbox
;
20 * Tests for the Dropbox API (v2).
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
26 class api_test
extends \advanced_testcase
{
28 * Data provider for has_additional_results.
32 public function has_additional_results_provider() {
34 'No more results' => [
41 'Has more, No cursor' => [
48 'Has more, Has cursor' => [
51 'cursor' => 'example_cursor',
55 'Missing has_more' => [
57 'cursor' => 'example_cursor',
63 'has_more' => 'example_cursor',
71 * Tests for the has_additional_results API function.
73 * @dataProvider has_additional_results_provider
74 * @param object $result The data to test
75 * @param bool $expected The expected result
77 public function test_has_additional_results($result, $expected) {
78 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
79 ->disableOriginalConstructor()
83 $this->assertEquals($expected, $mock->has_additional_results($result));
87 * Data provider for check_and_handle_api_errors.
91 public function check_and_handle_api_errors_provider() {
100 ['http_code' => 400],
103 'Invalid input parameter passed to DropBox API.',
106 ['http_code' => 401],
108 \repository_dropbox\authentication_exception
::class,
109 'Authentication token expired',
112 ['http_code' => 409],
113 json_decode('{"error": "Some value", "error_summary": "Some data here"}'),
115 'Endpoint specific error: Some data here',
118 ['http_code' => 429],
120 \repository_dropbox\rate_limit_exception
::class,
124 ['http_code' => 500],
126 'invalid_response_exception',
127 '500: Response body',
130 ['http_code' => 599],
132 'invalid_response_exception',
133 '599: Response body',
135 '600 http_code (invalid, but not officially an error)' => [
136 ['http_code' => 600],
145 * Tests for check_and_handle_api_errors.
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
153 public function test_check_and_handle_api_errors($info, $data, $exception, $exceptionmessage) {
154 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
155 ->disableOriginalConstructor()
161 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
162 $rcm = $rc->getMethod('check_and_handle_api_errors');
165 $this->expectException($exception);
168 if ($exceptionmessage) {
169 $this->expectExceptionMessage($exceptionmessage);
172 $result = $rcm->invoke($mock, $data);
174 $this->assertNull($result);
178 * Data provider for the supports_thumbnail function.
182 public function supports_thumbnail_provider() {
184 'Only files support thumbnails' => [
185 (object) ['.tag' => 'folder'],
188 'Dropbox currently only supports thumbnail generation for files under 20MB' => [
191 'size' => 21 * 1024 * 1024,
195 'Unusual file extension containing a working format but ending in a non-working one' => [
198 'size' => 100 * 1024,
199 'path_lower' => 'Example.jpg.pdf',
203 'Unusual file extension ending in a working extension' => [
206 'size' => 100 * 1024,
207 'path_lower' => 'Example.pdf.jpg',
213 // See docs at https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail.
226 foreach ($types as $type => $result) {
227 $tests["Test support for {$type}"] = [
230 'size' => 100 * 1024,
231 'path_lower' => "example_filename.{$type}",
241 * Test the supports_thumbnail function.
243 * @dataProvider supports_thumbnail_provider
244 * @param object $entry The entry to test
245 * @param bool $expected Whether this entry supports thumbnail generation
247 public function test_supports_thumbnail($entry, $expected) {
248 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
249 ->disableOriginalConstructor()
253 $this->assertEquals($expected, $mock->supports_thumbnail($entry));
257 * Test that the logout makes a call to the correct revocation endpoint.
259 public function test_logout_revocation() {
260 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
261 ->disableOriginalConstructor()
262 ->onlyMethods(['fetch_dropbox_data'])
265 $mock->expects($this->once())
266 ->method('fetch_dropbox_data')
267 ->with($this->equalTo('auth/token/revoke'), $this->equalTo(null));
269 $this->assertNull($mock->logout());
273 * Test that the logout function catches authentication_exception exceptions and discards them.
275 public function test_logout_revocation_catch_auth_exception() {
276 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
277 ->disableOriginalConstructor()
278 ->onlyMethods(['fetch_dropbox_data'])
281 $mock->expects($this->once())
282 ->method('fetch_dropbox_data')
283 ->will($this->throwException(new \repository_dropbox\authentication_exception
('Exception should be caught')));
285 $this->assertNull($mock->logout());
289 * Test that the logout function does not catch any other exception.
291 public function test_logout_revocation_does_not_catch_other_exceptions() {
292 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
293 ->disableOriginalConstructor()
294 ->onlyMethods(['fetch_dropbox_data'])
297 $mock->expects($this->once())
298 ->method('fetch_dropbox_data')
299 ->will($this->throwException(new \repository_dropbox\rate_limit_exception
));
301 $this->expectException(\repository_dropbox\rate_limit_exception
::class);
306 * Test basic fetch_dropbox_data function.
308 public function test_fetch_dropbox_data_endpoint() {
309 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
310 ->disableOriginalConstructor()
314 'get_content_endpoint',
318 $endpoint = 'testEndpoint';
320 // The fetch_dropbox_data call should be called against the standard endpoint only.
321 $mock->expects($this->once())
322 ->method('get_api_endpoint')
324 ->will($this->returnValue("https://example.com/api/2/{$endpoint}"));
326 $mock->expects($this->never())
327 ->method('get_content_endpoint');
329 $mock->expects($this->once())
331 ->will($this->returnValue(json_encode([])));
334 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
335 $rcm = $rc->getMethod('fetch_dropbox_data');
336 $rcm->invoke($mock, $endpoint);
340 * Some Dropbox endpoints require that the POSTFIELDS be set to null exactly.
342 public function test_fetch_dropbox_data_postfields_null() {
343 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
344 ->disableOriginalConstructor()
350 $endpoint = 'testEndpoint';
352 $mock->expects($this->once())
354 ->with($this->anything(), $this->callback(function($d) {
355 return $d['CURLOPT_POSTFIELDS'] === 'null';
357 ->will($this->returnValue(json_encode([])));
360 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
361 $rcm = $rc->getMethod('fetch_dropbox_data');
362 $rcm->invoke($mock, $endpoint, null);
366 * When data is specified, it should be json_encoded in POSTFIELDS.
368 public function test_fetch_dropbox_data_postfields_data() {
369 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
370 ->disableOriginalConstructor()
376 $endpoint = 'testEndpoint';
377 $data = ['something' => 'somevalue'];
379 $mock->expects($this->once())
381 ->with($this->anything(), $this->callback(function($d) use ($data) {
382 return $d['CURLOPT_POSTFIELDS'] === json_encode($data);
384 ->will($this->returnValue(json_encode([])));
387 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
388 $rcm = $rc->getMethod('fetch_dropbox_data');
389 $rcm->invoke($mock, $endpoint, $data);
393 * When more results are available, these should be fetched until there are no more.
395 public function test_fetch_dropbox_data_recurse_on_additional_records() {
396 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
397 ->disableOriginalConstructor()
404 $endpoint = 'testEndpoint';
406 // We can't detect if fetch_dropbox_data was called twice because
408 $mock->expects($this->exactly(3))
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']])
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'
427 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
428 $rcm = $rc->getMethod('fetch_dropbox_data');
429 $result = $rcm->invoke($mock, $endpoint, null, 'matches');
431 $this->assertEquals([
436 ], $result->matches
);
438 $this->assertFalse(isset($result->cursor
));
439 $this->assertFalse(isset($result->has_more
));
443 * Base tests for the fetch_dropbox_content function.
445 public function test_fetch_dropbox_content() {
446 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
447 ->disableOriginalConstructor()
451 'get_content_endpoint',
453 'check_and_handle_api_errors',
457 $data = ['exampledata' => 'examplevalue'];
458 $endpoint = 'getContent';
459 $url = "https://example.com/api/2/{$endpoint}";
460 $response = 'Example content';
462 // Only the content endpoint should be called.
463 $mock->expects($this->once())
464 ->method('get_content_endpoint')
466 ->will($this->returnValue($url));
468 $mock->expects($this->never())
469 ->method('get_api_endpoint');
471 $mock->expects($this->exactly(2))
472 ->method('setHeader')
474 [$this->equalTo('Content-Type: ')],
475 [$this->equalTo('Dropbox-API-Arg: ' . json_encode($data))]
478 // Only one request should be made, and it should forcibly be a POST.
479 $mock->expects($this->once())
481 ->with($this->equalTo($url), $this->callback(function($options) {
482 return $options['CURLOPT_POST'] === 1;
484 ->willReturn($response);
486 $mock->expects($this->once())
487 ->method('check_and_handle_api_errors')
488 ->with($this->equalTo($response))
492 $rc = new \
ReflectionClass(\repository_dropbox\dropbox
::class);
493 $rcm = $rc->getMethod('fetch_dropbox_content');
494 $result = $rcm->invoke($mock, $endpoint, $data);
496 $this->assertEquals($response, $result);
500 * Test that the get_file_share_info function returns an existing link if one is available.
502 public function test_get_file_share_info_existing() {
503 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
504 ->disableOriginalConstructor()
506 'fetch_dropbox_data',
507 'normalize_file_share_info',
511 $id = 'LifeTheUniverseAndEverything';
512 $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
513 $sharelink = 'https://example.com/share/link';
515 // Mock fetch_dropbox_data to return an existing file.
516 $mock->expects($this->once())
517 ->method('fetch_dropbox_data')
519 $this->equalTo('sharing/list_shared_links'),
520 $this->equalTo(['path' => $id])
522 ->willReturn((object) ['links' => [$file]]);
524 $mock->expects($this->once())
525 ->method('normalize_file_share_info')
526 ->with($this->equalTo($file))
527 ->will($this->returnValue($sharelink));
529 $this->assertEquals($sharelink, $mock->get_file_share_info($id));
533 * Test that the get_file_share_info function creates a new link if one is not available.
535 public function test_get_file_share_info_new() {
536 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
537 ->disableOriginalConstructor()
539 'fetch_dropbox_data',
540 'normalize_file_share_info',
544 $id = 'LifeTheUniverseAndEverything';
545 $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
546 $sharelink = 'https://example.com/share/link';
548 // Mock fetch_dropbox_data to return an existing file.
549 $mock->expects($this->exactly(2))
550 ->method('fetch_dropbox_data')
552 [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
553 [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
556 'requested_visibility' => 'public',
560 ->will($this->onConsecutiveCalls(
561 (object) ['links' => []],
565 $mock->expects($this->once())
566 ->method('normalize_file_share_info')
567 ->with($this->equalTo($file))
568 ->will($this->returnValue($sharelink));
570 $this->assertEquals($sharelink, $mock->get_file_share_info($id));
574 * Test failure behaviour with get_file_share_info fails to create a new link.
576 public function test_get_file_share_info_new_failure() {
577 $mock = $this->getMockBuilder(\repository_dropbox\dropbox
::class)
578 ->disableOriginalConstructor()
580 'fetch_dropbox_data',
581 'normalize_file_share_info',
585 $id = 'LifeTheUniverseAndEverything';
587 // Mock fetch_dropbox_data to return an existing file.
588 $mock->expects($this->exactly(2))
589 ->method('fetch_dropbox_data')
591 [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
592 [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
595 'requested_visibility' => 'public',
599 ->will($this->onConsecutiveCalls(
600 (object) ['links' => []],
604 $mock->expects($this->never())
605 ->method('normalize_file_share_info');
607 $this->assertNull($mock->get_file_share_info($id));