See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 */ 77 public function test_has_additional_results($result, $expected) { 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 */ 153 public function test_check_and_handle_api_errors($info, $data, $exception, $exceptionmessage) { 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 $rcm->setAccessible(true); 164 165 if ($exception) { 166 $this->expectException($exception); 167 } 168 169 if ($exceptionmessage) { 170 $this->expectExceptionMessage($exceptionmessage); 171 } 172 173 $result = $rcm->invoke($mock, $data); 174 175 $this->assertNull($result); 176 } 177 178 /** 179 * Data provider for the supports_thumbnail function. 180 * 181 * @return array 182 */ 183 public function supports_thumbnail_provider() { 184 $tests = [ 185 'Only files support thumbnails' => [ 186 (object) ['.tag' => 'folder'], 187 false, 188 ], 189 'Dropbox currently only supports thumbnail generation for files under 20MB' => [ 190 (object) [ 191 '.tag' => 'file', 192 'size' => 21 * 1024 * 1024, 193 ], 194 false, 195 ], 196 'Unusual file extension containing a working format but ending in a non-working one' => [ 197 (object) [ 198 '.tag' => 'file', 199 'size' => 100 * 1024, 200 'path_lower' => 'Example.jpg.pdf', 201 ], 202 false, 203 ], 204 'Unusual file extension ending in a working extension' => [ 205 (object) [ 206 '.tag' => 'file', 207 'size' => 100 * 1024, 208 'path_lower' => 'Example.pdf.jpg', 209 ], 210 true, 211 ], 212 ]; 213 214 // See docs at https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail. 215 $types = [ 216 'pdf' => false, 217 'doc' => false, 218 'docx' => false, 219 'jpg' => true, 220 'jpeg' => true, 221 'png' => true, 222 'tiff' => true, 223 'tif' => true, 224 'gif' => true, 225 'bmp' => true, 226 ]; 227 foreach ($types as $type => $result) { 228 $tests["Test support for {$type}"] = [ 229 (object) [ 230 '.tag' => 'file', 231 'size' => 100 * 1024, 232 'path_lower' => "example_filename.{$type}", 233 ], 234 $result, 235 ]; 236 } 237 238 return $tests; 239 } 240 241 /** 242 * Test the supports_thumbnail function. 243 * 244 * @dataProvider supports_thumbnail_provider 245 * @param object $entry The entry to test 246 * @param bool $expected Whether this entry supports thumbnail generation 247 */ 248 public function test_supports_thumbnail($entry, $expected) { 249 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 250 ->disableOriginalConstructor() 251 ->onlyMethods([]) 252 ->getMock(); 253 254 $this->assertEquals($expected, $mock->supports_thumbnail($entry)); 255 } 256 257 /** 258 * Test that the logout makes a call to the correct revocation endpoint. 259 */ 260 public function test_logout_revocation() { 261 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 262 ->disableOriginalConstructor() 263 ->onlyMethods(['fetch_dropbox_data']) 264 ->getMock(); 265 266 $mock->expects($this->once()) 267 ->method('fetch_dropbox_data') 268 ->with($this->equalTo('auth/token/revoke'), $this->equalTo(null)); 269 270 $this->assertNull($mock->logout()); 271 } 272 273 /** 274 * Test that the logout function catches authentication_exception exceptions and discards them. 275 */ 276 public function test_logout_revocation_catch_auth_exception() { 277 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 278 ->disableOriginalConstructor() 279 ->onlyMethods(['fetch_dropbox_data']) 280 ->getMock(); 281 282 $mock->expects($this->once()) 283 ->method('fetch_dropbox_data') 284 ->will($this->throwException(new \repository_dropbox\authentication_exception('Exception should be caught'))); 285 286 $this->assertNull($mock->logout()); 287 } 288 289 /** 290 * Test that the logout function does not catch any other exception. 291 */ 292 public function test_logout_revocation_does_not_catch_other_exceptions() { 293 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 294 ->disableOriginalConstructor() 295 ->onlyMethods(['fetch_dropbox_data']) 296 ->getMock(); 297 298 $mock->expects($this->once()) 299 ->method('fetch_dropbox_data') 300 ->will($this->throwException(new \repository_dropbox\rate_limit_exception)); 301 302 $this->expectException(\repository_dropbox\rate_limit_exception::class); 303 $mock->logout(); 304 } 305 306 /** 307 * Test basic fetch_dropbox_data function. 308 */ 309 public function test_fetch_dropbox_data_endpoint() { 310 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 311 ->disableOriginalConstructor() 312 ->onlyMethods([ 313 'request', 314 'get_api_endpoint', 315 'get_content_endpoint', 316 ]) 317 ->getMock(); 318 319 $endpoint = 'testEndpoint'; 320 321 // The fetch_dropbox_data call should be called against the standard endpoint only. 322 $mock->expects($this->once()) 323 ->method('get_api_endpoint') 324 ->with($endpoint) 325 ->will($this->returnValue("https://example.com/api/2/{$endpoint}")); 326 327 $mock->expects($this->never()) 328 ->method('get_content_endpoint'); 329 330 $mock->expects($this->once()) 331 ->method('request') 332 ->will($this->returnValue(json_encode([]))); 333 334 // Make the call. 335 $rc = new \ReflectionClass(\repository_dropbox\dropbox::class); 336 $rcm = $rc->getMethod('fetch_dropbox_data'); 337 $rcm->setAccessible(true); 338 $rcm->invoke($mock, $endpoint); 339 } 340 341 /** 342 * Some Dropbox endpoints require that the POSTFIELDS be set to null exactly. 343 */ 344 public function test_fetch_dropbox_data_postfields_null() { 345 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 346 ->disableOriginalConstructor() 347 ->onlyMethods([ 348 'request', 349 ]) 350 ->getMock(); 351 352 $endpoint = 'testEndpoint'; 353 354 $mock->expects($this->once()) 355 ->method('request') 356 ->with($this->anything(), $this->callback(function($d) { 357 return $d['CURLOPT_POSTFIELDS'] === 'null'; 358 })) 359 ->will($this->returnValue(json_encode([]))); 360 361 // Make the call. 362 $rc = new \ReflectionClass(\repository_dropbox\dropbox::class); 363 $rcm = $rc->getMethod('fetch_dropbox_data'); 364 $rcm->setAccessible(true); 365 $rcm->invoke($mock, $endpoint, null); 366 } 367 368 /** 369 * When data is specified, it should be json_encoded in POSTFIELDS. 370 */ 371 public function test_fetch_dropbox_data_postfields_data() { 372 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 373 ->disableOriginalConstructor() 374 ->onlyMethods([ 375 'request', 376 ]) 377 ->getMock(); 378 379 $endpoint = 'testEndpoint'; 380 $data = ['something' => 'somevalue']; 381 382 $mock->expects($this->once()) 383 ->method('request') 384 ->with($this->anything(), $this->callback(function($d) use ($data) { 385 return $d['CURLOPT_POSTFIELDS'] === json_encode($data); 386 })) 387 ->will($this->returnValue(json_encode([]))); 388 389 // Make the call. 390 $rc = new \ReflectionClass(\repository_dropbox\dropbox::class); 391 $rcm = $rc->getMethod('fetch_dropbox_data'); 392 $rcm->setAccessible(true); 393 $rcm->invoke($mock, $endpoint, $data); 394 } 395 396 /** 397 * When more results are available, these should be fetched until there are no more. 398 */ 399 public function test_fetch_dropbox_data_recurse_on_additional_records() { 400 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 401 ->disableOriginalConstructor() 402 ->onlyMethods([ 403 'request', 404 'get_api_endpoint', 405 ]) 406 ->getMock(); 407 408 $endpoint = 'testEndpoint'; 409 410 // We can't detect if fetch_dropbox_data was called twice because 411 // we can' 412 $mock->expects($this->exactly(3)) 413 ->method('request') 414 ->will($this->onConsecutiveCalls( 415 json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['foo', 'bar']]), 416 json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['baz']]), 417 json_encode(['has_more' => false, 'cursor' => '', 'matches' => ['bum']]) 418 )); 419 420 // We automatically adjust for the /continue endpoint. 421 $mock->expects($this->exactly(3)) 422 ->method('get_api_endpoint') 423 ->withConsecutive(['testEndpoint'], ['testEndpoint/continue'], ['testEndpoint/continue']) 424 ->willReturn($this->onConsecutiveCalls( 425 'https://example.com/api/2/testEndpoint', 426 'https://example.com/api/2/testEndpoint/continue', 427 'https://example.com/api/2/testEndpoint/continue' 428 )); 429 430 // Make the call. 431 $rc = new \ReflectionClass(\repository_dropbox\dropbox::class); 432 $rcm = $rc->getMethod('fetch_dropbox_data'); 433 $rcm->setAccessible(true); 434 $result = $rcm->invoke($mock, $endpoint, null, 'matches'); 435 436 $this->assertEquals([ 437 'foo', 438 'bar', 439 'baz', 440 'bum', 441 ], $result->matches); 442 443 $this->assertFalse(isset($result->cursor)); 444 $this->assertFalse(isset($result->has_more)); 445 } 446 447 /** 448 * Base tests for the fetch_dropbox_content function. 449 */ 450 public function test_fetch_dropbox_content() { 451 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 452 ->disableOriginalConstructor() 453 ->onlyMethods([ 454 'request', 455 'setHeader', 456 'get_content_endpoint', 457 'get_api_endpoint', 458 'check_and_handle_api_errors', 459 ]) 460 ->getMock(); 461 462 $data = ['exampledata' => 'examplevalue']; 463 $endpoint = 'getContent'; 464 $url = "https://example.com/api/2/{$endpoint}"; 465 $response = 'Example content'; 466 467 // Only the content endpoint should be called. 468 $mock->expects($this->once()) 469 ->method('get_content_endpoint') 470 ->with($endpoint) 471 ->will($this->returnValue($url)); 472 473 $mock->expects($this->never()) 474 ->method('get_api_endpoint'); 475 476 $mock->expects($this->exactly(2)) 477 ->method('setHeader') 478 ->withConsecutive( 479 [$this->equalTo('Content-Type: ')], 480 [$this->equalTo('Dropbox-API-Arg: ' . json_encode($data))] 481 ); 482 483 // Only one request should be made, and it should forcibly be a POST. 484 $mock->expects($this->once()) 485 ->method('request') 486 ->with($this->equalTo($url), $this->callback(function($options) { 487 return $options['CURLOPT_POST'] === 1; 488 })) 489 ->willReturn($response); 490 491 $mock->expects($this->once()) 492 ->method('check_and_handle_api_errors') 493 ->with($this->equalTo($response)) 494 ; 495 496 // Make the call. 497 $rc = new \ReflectionClass(\repository_dropbox\dropbox::class); 498 $rcm = $rc->getMethod('fetch_dropbox_content'); 499 $rcm->setAccessible(true); 500 $result = $rcm->invoke($mock, $endpoint, $data); 501 502 $this->assertEquals($response, $result); 503 } 504 505 /** 506 * Test that the get_file_share_info function returns an existing link if one is available. 507 */ 508 public function test_get_file_share_info_existing() { 509 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 510 ->disableOriginalConstructor() 511 ->onlyMethods([ 512 'fetch_dropbox_data', 513 'normalize_file_share_info', 514 ]) 515 ->getMock(); 516 517 $id = 'LifeTheUniverseAndEverything'; 518 $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue']; 519 $sharelink = 'https://example.com/share/link'; 520 521 // Mock fetch_dropbox_data to return an existing file. 522 $mock->expects($this->once()) 523 ->method('fetch_dropbox_data') 524 ->with( 525 $this->equalTo('sharing/list_shared_links'), 526 $this->equalTo(['path' => $id]) 527 ) 528 ->willReturn((object) ['links' => [$file]]); 529 530 $mock->expects($this->once()) 531 ->method('normalize_file_share_info') 532 ->with($this->equalTo($file)) 533 ->will($this->returnValue($sharelink)); 534 535 $this->assertEquals($sharelink, $mock->get_file_share_info($id)); 536 } 537 538 /** 539 * Test that the get_file_share_info function creates a new link if one is not available. 540 */ 541 public function test_get_file_share_info_new() { 542 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 543 ->disableOriginalConstructor() 544 ->onlyMethods([ 545 'fetch_dropbox_data', 546 'normalize_file_share_info', 547 ]) 548 ->getMock(); 549 550 $id = 'LifeTheUniverseAndEverything'; 551 $file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue']; 552 $sharelink = 'https://example.com/share/link'; 553 554 // Mock fetch_dropbox_data to return an existing file. 555 $mock->expects($this->exactly(2)) 556 ->method('fetch_dropbox_data') 557 ->withConsecutive( 558 [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])], 559 [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([ 560 'path' => $id, 561 'settings' => [ 562 'requested_visibility' => 'public', 563 ] 564 ])] 565 ) 566 ->will($this->onConsecutiveCalls( 567 (object) ['links' => []], 568 $file 569 )); 570 571 $mock->expects($this->once()) 572 ->method('normalize_file_share_info') 573 ->with($this->equalTo($file)) 574 ->will($this->returnValue($sharelink)); 575 576 $this->assertEquals($sharelink, $mock->get_file_share_info($id)); 577 } 578 579 /** 580 * Test failure behaviour with get_file_share_info fails to create a new link. 581 */ 582 public function test_get_file_share_info_new_failure() { 583 $mock = $this->getMockBuilder(\repository_dropbox\dropbox::class) 584 ->disableOriginalConstructor() 585 ->onlyMethods([ 586 'fetch_dropbox_data', 587 'normalize_file_share_info', 588 ]) 589 ->getMock(); 590 591 $id = 'LifeTheUniverseAndEverything'; 592 593 // Mock fetch_dropbox_data to return an existing file. 594 $mock->expects($this->exactly(2)) 595 ->method('fetch_dropbox_data') 596 ->withConsecutive( 597 [$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])], 598 [$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([ 599 'path' => $id, 600 'settings' => [ 601 'requested_visibility' => 'public', 602 ] 603 ])] 604 ) 605 ->will($this->onConsecutiveCalls( 606 (object) ['links' => []], 607 null 608 )); 609 610 $mock->expects($this->never()) 611 ->method('normalize_file_share_info'); 612 613 $this->assertNull($mock->get_file_share_info($id)); 614 } 615 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body