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