Autoría | Ultima modificación | Ver Log |
<?php
// This file is part of the Zoom plugin for Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for get_meeting_reports task class.
*
* @package mod_zoom
* @copyright 2019 UC Regents
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_zoom;
use advanced_testcase;
/**
* PHPunit testcase class.
* @covers \mod_zoom\webservice
*/
final class mod_zoom_webservice_test extends advanced_testcase {
/**
* @var object Anonymous class to mock \curl.
*/
private $notfoundmockcurl;
/**
* Setup to ensure that fixtures are loaded.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/mod/zoom/locallib.php');
}
/**
* Setup before every test.
*/
public function setUp(): void {
$this->resetAfterTest();
// Set fake values so we can test methods in class.
set_config('clientid', 'test', 'zoom');
set_config('clientsecret', 'test', 'zoom');
set_config('accountid', 'test', 'zoom');
// @codingStandardsIgnoreStart
$this->notfoundmockcurl = new class {
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreEnd
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 404 error code.
* @return array
*/
public function get_info() {
return ['http_code' => 404];
}
};
}
/**
* Tests that uuid are encoded properly for use in web service calls.
*/
public function test_encode_uuid(): void {
$service = zoom_webservice();
// If uuid includes / or // it needs to be double encoded.
$uuid = $service->encode_uuid('/u2F0gUNSqqC7DT+08xKrw==');
$this->assertEquals('%252Fu2F0gUNSqqC7DT%252B08xKrw%253D%253D', $uuid);
$uuid = $service->encode_uuid('Ahqu+zVcQpO//RcAUUWkNA==');
$this->assertEquals('Ahqu%252BzVcQpO%252F%252FRcAUUWkNA%253D%253D', $uuid);
// If not, then it can be used as is.
$uuid = $service->encode_uuid('M8TigfzxRTKJmhXnV7bNjw==');
$this->assertEquals('M8TigfzxRTKJmhXnV7bNjw==', $uuid);
}
/**
* Tests whether the meeting not found errors are properly parsed.
*/
public function test_meeting_not_found_exception(): void {
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('make_curl_call')
->willReturn('{"code":3001,"message":"réunion introuvable"}');
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($this->notfoundmockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$foundexception = false;
try {
$response = $mockservice->get_meeting_webinar_info('-1', false);
} catch (webservice_exception $error) {
$this->assertEquals(3001, $error->zoomerrorcode);
$this->assertTrue(zoom_is_meeting_gone_error($error));
$foundexception = true;
}
$this->assertTrue($foundexception);
}
/**
* Tests whether user not found errors are properly parsed.
*/
public function test_user_not_found_exception(): void {
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('make_curl_call')
->willReturn('{"code":1001,"message":"n’existe pas ou n’appartient pas à ce compte"}');
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($this->notfoundmockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$foundexception = false;
try {
$founduser = $mockservice->get_user('-1');
} catch (webservice_exception $error) {
$this->assertEquals(1001, $error->zoomerrorcode);
$this->assertTrue(zoom_is_meeting_gone_error($error));
$this->assertTrue(zoom_is_user_not_found_error($error));
$foundexception = true;
}
$this->assertTrue($foundexception || !$founduser);
}
/**
* Tests whether invalid user errors are parsed properly
*/
public function test_invalid_user_exception(): void {
// @codingStandardsIgnoreStart
$invalidmockcurl = new class {
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreEnd
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 400 error code.
* @return array
*/
public function get_info() {
return ['http_code' => 400];
}
};
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('make_curl_call')
->willReturn('{"code":1120,"message":"utilisateur invalide"}');
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($invalidmockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$foundexception = false;
try {
$founduser = $mockservice->get_user('-1');
} catch (webservice_exception $error) {
$this->assertEquals(1120, $error->zoomerrorcode);
$this->assertTrue(zoom_is_meeting_gone_error($error));
$this->assertTrue(zoom_is_user_not_found_error($error));
$foundexception = true;
}
$this->assertTrue($foundexception || !$founduser);
}
/**
* Tests whether the retry on a 429 works properly when the Retry-After header
* is in the curl response to specify the time that the retry should be sent.
*/
public function test_retry_with_header(): void {
// @codingStandardsIgnoreStart
$retrywithheadermockcurl = new class {
public $numgetinfocalls = 0;
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreEnd
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 429 for first 3 calls, then 200.
* @return array
*/
public function get_info() {
$this->numgetinfocalls++;
if ($this->numgetinfocalls <= 3) {
return ['http_code' => 429];
}
return ['http_code' => 200];
}
// @codingStandardsIgnoreStart
/**
* Returns retry to be 1 second later.
* @return array
*/
public function getResponse() {
// @codingStandardsIgnoreEnd
// Set retry time to be 1 second. Format is 2020-05-31T00:00:00Z.
$retrytime = time() + 1;
return [
'X-RateLimit-Type' => 'Daily',
'X-RateLimit-Remaining' => 100,
'Retry-After' => gmdate('Y-m-d\TH:i:s\Z', $retrytime),
];
}
};
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('make_curl_call')
->willReturn('{"response":"success", "message": "", "code": 200}');
// Class retrywithheadermockcurl will give 429 retry error 3 times
// before giving a 200.
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($retrywithheadermockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$result = $mockservice->get_user("1");
// Expect 3 debugging calls for each retry attempt.
$this->assertDebuggingCalledCount($expectedcount = 3);
// Expect 3 calls to get_info() for the retries and 1 for success.
$this->assertEquals($retrywithheadermockcurl->numgetinfocalls, 4);
$this->assertEquals($result->response, 'success');
}
/**
* Tests whether the retry on a 429 response works when the Retry-After
* header is not sent in the curl response.
*/
public function test_retry_without_header(): void {
// @codingStandardsIgnoreStart
$retrynoheadermockcurl = new class {
public $numgetinfocalls = 0;
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreEnd
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 429 for first 3 calls, then 200.
* @return array
*/
public function get_info() {
$this->numgetinfocalls++;
if ($this->numgetinfocalls <= 3) {
return ['http_code' => 429];
}
return ['http_code' => 200];
}
// @codingStandardsIgnoreStart
/**
* Returns empty response.
* @return array
*/
public function getResponse() {
// @codingStandardsIgnoreEnd
return [];
}
};
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('make_curl_call')
->willReturn('{"response":"success"}');
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($retrynoheadermockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$result = $mockservice->get_user("1");
$this->assertDebuggingCalledCount($expectedcount = 3);
$this->assertEquals($retrynoheadermockcurl->numgetinfocalls, 4);
$this->assertEquals($result->response, 'success');
}
/**
* Tests that we throw error if we tried more than max retries.
*/
public function test_retry_exception(): void {
// @codingStandardsIgnoreStart
$retryfailuremockcurl = new class {
public $urlpath = null;
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreend
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 429.
* @return array
*/
public function get_info() {
return array('http_code' => 429);
}
/**
* Returns error code and message.
* @param string $url
* @param array $data
* @return string
*/
public function get($url, $data) {
if ($this->urlpath === null) {
$this->urlpath = $url;
} else if ($this->urlpath !== $url) {
// We should be getting the same path every time.
return '{"code":-1, "message":"incorrect url"}';
}
return '{"code":-1, "message":"too many retries"}';
}
// @codingStandardsIgnoreStart
/**
* Returns retry to be 1 second later.
* @return array
*/
public function getResponse() {
// @codingStandardsIgnoreEnd
// Set retry time after 1 second. Format is 2020-05-31T00:00:00Z.
$retrytime = time() + 1;
return [
'X-RateLimit-Type' => 'Daily',
'X-RateLimit-Remaining' => 100,
'Retry-After' => gmdate('Y-m-d\TH:i:s\Z', $retrytime),
];
}
};
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($retryfailuremockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$foundexception = false;
try {
$result = $mockservice->get_user("1");
} catch (retry_failed_exception $error) {
$foundexception = true;
$this->assertEquals($error->response, 'too many retries');
}
$this->assertTrue($foundexception);
// Check that we retried MAX_RETRIES times.
$this->assertDebuggingCalledCount(webservice::MAX_RETRIES);
}
/**
* Tests that we are waiting 1 minute for QPS rate limit types.
*/
public function test_retryqps_exception(): void {
// @codingStandardsIgnoreStart
$retryqpsmockcurl = new class {
public $urlpath = null;
/**
* Stub for curl setHeader().
* @param string $unusedparam
* @return void
*/
public function setHeader($unusedparam) {
// @codingStandardsIgnoreEnd
return;
}
/**
* Stub for curl get_errno().
* @return boolean
*/
public function get_errno() {
return false;
}
/**
* Returns 429.
* @return array
*/
public function get_info() {
return ['http_code' => 429];
}
/**
* Returns error code and message.
* @param string $url
* @param array $data
* @return string
*/
public function get($url, $data) {
if ($this->urlpath === null) {
$this->urlpath = $url;
} else if ($this->urlpath !== $url) {
// We should be getting the same path every time.
return '{"code":-1, "message":"incorrect url"}';
}
return '{"code":-1, "message":"too many retries"}';
}
// @codingStandardsIgnoreStart
/**
* Returns retry to be 1 second later.
* @return array
*/
public function getResponse() {
// @codingStandardsIgnoreEnd
// Signify that we reached max per second/minute rate limit.
return ['X-RateLimit-Type' => 'QPS'];
}
};
$mockservice = $this->getMockBuilder('\mod_zoom\webservice')
->setMethods(['get_curl_object', 'get_access_token'])
->getMock();
$mockservice->expects($this->any())
->method('get_curl_object')
->willReturn($retryqpsmockcurl);
$mockservice->expects($this->any())
->method('get_access_token')
->willReturn('token123');
$foundexception = false;
try {
$result = $mockservice->get_meetings('2020-01-01', '2020-01-02');
} catch (webservice_exception $error) {
$foundexception = true;
$this->assertEquals($error->response, 'too many retries');
}
$this->assertTrue($foundexception);
// Check that we waited 1 minute.
$debugging = $this->getDebuggingMessages();
$debuggingmsg = array_pop($debugging);
$this->assertEquals('Received 429 response, sleeping 60 seconds ' .
'until next retry. Current retry: 5', $debuggingmsg->message);
// Check that we retried MAX_RETRIES times.
$this->assertDebuggingCalledCount(webservice::MAX_RETRIES);
}
}