Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of the Zoom plugin for 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
 * Unit tests for get_meeting_reports task class.
19
 *
20
 * @package    mod_zoom
21
 * @copyright  2019 UC Regents
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace mod_zoom;
26
 
27
use advanced_testcase;
28
 
29
/**
30
 * PHPunit testcase class.
31
 * @covers \mod_zoom\webservice
32
 */
33
final class mod_zoom_webservice_test extends advanced_testcase {
34
    /**
35
     * @var object Anonymous class to mock \curl.
36
     */
37
    private $notfoundmockcurl;
38
 
39
    /**
40
     * Setup to ensure that fixtures are loaded.
41
     */
42
    public static function setUpBeforeClass(): void {
43
        global $CFG;
44
        require_once($CFG->dirroot . '/mod/zoom/locallib.php');
45
    }
46
 
47
    /**
48
     * Setup before every test.
49
     */
50
    public function setUp(): void {
51
        $this->resetAfterTest();
52
        // Set fake values so we can test methods in class.
53
        set_config('clientid', 'test', 'zoom');
54
        set_config('clientsecret', 'test', 'zoom');
55
        set_config('accountid', 'test', 'zoom');
56
 
57
        // @codingStandardsIgnoreStart
58
        $this->notfoundmockcurl = new class {
59
            /**
60
             * Stub for curl setHeader().
61
             * @param string $unusedparam
62
             * @return void
63
             */
64
            public function setHeader($unusedparam) {
65
            // @codingStandardsIgnoreEnd
66
                return;
67
            }
68
            /**
69
             * Stub for curl get_errno().
70
             * @return boolean
71
             */
72
            public function get_errno() {
73
                return false;
74
            }
75
            /**
76
             * Returns 404 error code.
77
             * @return array
78
             */
79
            public function get_info() {
80
                return ['http_code' => 404];
81
            }
82
        };
83
    }
84
 
85
    /**
86
     * Tests that uuid are encoded properly for use in web service calls.
87
     */
88
    public function test_encode_uuid(): void {
89
        $service = zoom_webservice();
90
 
91
        // If uuid includes / or // it needs to be double encoded.
92
        $uuid = $service->encode_uuid('/u2F0gUNSqqC7DT+08xKrw==');
93
        $this->assertEquals('%252Fu2F0gUNSqqC7DT%252B08xKrw%253D%253D', $uuid);
94
 
95
        $uuid = $service->encode_uuid('Ahqu+zVcQpO//RcAUUWkNA==');
96
        $this->assertEquals('Ahqu%252BzVcQpO%252F%252FRcAUUWkNA%253D%253D', $uuid);
97
 
98
        // If not, then it can be used as is.
99
        $uuid = $service->encode_uuid('M8TigfzxRTKJmhXnV7bNjw==');
100
        $this->assertEquals('M8TigfzxRTKJmhXnV7bNjw==', $uuid);
101
    }
102
 
103
    /**
104
     * Tests whether the meeting not found errors are properly parsed.
105
     */
106
    public function test_meeting_not_found_exception(): void {
107
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
108
            ->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
109
            ->getMock();
110
 
111
        $mockservice->expects($this->any())
112
            ->method('make_curl_call')
113
            ->willReturn('{"code":3001,"message":"réunion introuvable"}');
114
 
115
        $mockservice->expects($this->any())
116
            ->method('get_curl_object')
117
            ->willReturn($this->notfoundmockcurl);
118
 
119
        $mockservice->expects($this->any())
120
            ->method('get_access_token')
121
            ->willReturn('token123');
122
 
123
        $foundexception = false;
124
        try {
125
            $response = $mockservice->get_meeting_webinar_info('-1', false);
126
        } catch (webservice_exception $error) {
127
            $this->assertEquals(3001, $error->zoomerrorcode);
128
            $this->assertTrue(zoom_is_meeting_gone_error($error));
129
            $foundexception = true;
130
        }
131
 
132
        $this->assertTrue($foundexception);
133
    }
134
 
135
    /**
136
     * Tests whether user not found errors are properly parsed.
137
     */
138
    public function test_user_not_found_exception(): void {
139
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
140
            ->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
141
            ->getMock();
142
 
143
        $mockservice->expects($this->any())
144
            ->method('make_curl_call')
145
            ->willReturn('{"code":1001,"message":"n’existe pas ou n’appartient pas à ce compte"}');
146
 
147
        $mockservice->expects($this->any())
148
            ->method('get_curl_object')
149
            ->willReturn($this->notfoundmockcurl);
150
 
151
        $mockservice->expects($this->any())
152
            ->method('get_access_token')
153
            ->willReturn('token123');
154
 
155
        $foundexception = false;
156
        try {
157
            $founduser = $mockservice->get_user('-1');
158
        } catch (webservice_exception $error) {
159
            $this->assertEquals(1001, $error->zoomerrorcode);
160
            $this->assertTrue(zoom_is_meeting_gone_error($error));
161
            $this->assertTrue(zoom_is_user_not_found_error($error));
162
            $foundexception = true;
163
        }
164
 
165
        $this->assertTrue($foundexception || !$founduser);
166
    }
167
 
168
    /**
169
     * Tests whether invalid user errors are parsed properly
170
     */
171
    public function test_invalid_user_exception(): void {
172
        // @codingStandardsIgnoreStart
173
        $invalidmockcurl = new class {
174
            /**
175
             * Stub for curl setHeader().
176
             * @param string $unusedparam
177
             * @return void
178
             */
179
            public function setHeader($unusedparam) {
180
            // @codingStandardsIgnoreEnd
181
                return;
182
            }
183
            /**
184
             * Stub for curl get_errno().
185
             * @return boolean
186
             */
187
            public function get_errno() {
188
                return false;
189
            }
190
            /**
191
             * Returns 400 error code.
192
             * @return array
193
             */
194
            public function get_info() {
195
                return ['http_code' => 400];
196
            }
197
        };
198
 
199
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
200
            ->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
201
            ->getMock();
202
 
203
        $mockservice->expects($this->any())
204
            ->method('make_curl_call')
205
            ->willReturn('{"code":1120,"message":"utilisateur invalide"}');
206
 
207
        $mockservice->expects($this->any())
208
            ->method('get_curl_object')
209
            ->willReturn($invalidmockcurl);
210
 
211
        $mockservice->expects($this->any())
212
            ->method('get_access_token')
213
            ->willReturn('token123');
214
 
215
        $foundexception = false;
216
        try {
217
            $founduser = $mockservice->get_user('-1');
218
        } catch (webservice_exception $error) {
219
            $this->assertEquals(1120, $error->zoomerrorcode);
220
            $this->assertTrue(zoom_is_meeting_gone_error($error));
221
            $this->assertTrue(zoom_is_user_not_found_error($error));
222
            $foundexception = true;
223
        }
224
 
225
        $this->assertTrue($foundexception || !$founduser);
226
    }
227
 
228
    /**
229
     * Tests whether the retry on a 429 works properly when the Retry-After header
230
     * is in the curl response to specify the time that the retry should be sent.
231
     */
232
    public function test_retry_with_header(): void {
233
        // @codingStandardsIgnoreStart
234
        $retrywithheadermockcurl = new class {
235
            public $numgetinfocalls = 0;
236
            /**
237
             * Stub for curl setHeader().
238
             * @param string $unusedparam
239
             * @return void
240
             */
241
            public function setHeader($unusedparam) {
242
            // @codingStandardsIgnoreEnd
243
                return;
244
            }
245
            /**
246
             * Stub for curl get_errno().
247
             * @return boolean
248
             */
249
            public function get_errno() {
250
                return false;
251
            }
252
            /**
253
             * Returns 429 for first 3 calls, then 200.
254
             * @return array
255
             */
256
            public function get_info() {
257
                $this->numgetinfocalls++;
258
                if ($this->numgetinfocalls <= 3) {
259
                    return ['http_code' => 429];
260
                }
261
 
262
                return ['http_code' => 200];
263
            }
264
            // @codingStandardsIgnoreStart
265
            /**
266
             * Returns retry to be 1 second later.
267
             * @return array
268
             */
269
            public function getResponse() {
270
            // @codingStandardsIgnoreEnd
271
                // Set retry time to be 1 second. Format is 2020-05-31T00:00:00Z.
272
                $retrytime = time() + 1;
273
                return [
274
                    'X-RateLimit-Type' => 'Daily',
275
                    'X-RateLimit-Remaining' => 100,
276
                    'Retry-After' => gmdate('Y-m-d\TH:i:s\Z', $retrytime),
277
                ];
278
            }
279
        };
280
 
281
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
282
            ->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
283
            ->getMock();
284
 
285
        $mockservice->expects($this->any())
286
            ->method('make_curl_call')
287
            ->willReturn('{"response":"success", "message": "", "code": 200}');
288
 
289
        // Class retrywithheadermockcurl will give 429 retry error 3 times
290
        // before giving a 200.
291
        $mockservice->expects($this->any())
292
            ->method('get_curl_object')
293
            ->willReturn($retrywithheadermockcurl);
294
 
295
        $mockservice->expects($this->any())
296
            ->method('get_access_token')
297
            ->willReturn('token123');
298
 
299
        $result = $mockservice->get_user("1");
300
        // Expect 3 debugging calls for each retry attempt.
301
        $this->assertDebuggingCalledCount($expectedcount = 3);
302
        // Expect 3 calls to get_info() for the retries and 1 for success.
303
        $this->assertEquals($retrywithheadermockcurl->numgetinfocalls, 4);
304
        $this->assertEquals($result->response, 'success');
305
    }
306
 
307
    /**
308
     * Tests whether the retry on a 429 response works when the Retry-After
309
     * header is not sent in the curl response.
310
     */
311
    public function test_retry_without_header(): void {
312
        // @codingStandardsIgnoreStart
313
        $retrynoheadermockcurl = new class {
314
            public $numgetinfocalls = 0;
315
            /**
316
             * Stub for curl setHeader().
317
             * @param string $unusedparam
318
             * @return void
319
             */
320
            public function setHeader($unusedparam) {
321
            // @codingStandardsIgnoreEnd
322
                return;
323
            }
324
            /**
325
             * Stub for curl get_errno().
326
             * @return boolean
327
             */
328
            public function get_errno() {
329
                return false;
330
            }
331
            /**
332
             * Returns 429 for first 3 calls, then 200.
333
             * @return array
334
             */
335
            public function get_info() {
336
                $this->numgetinfocalls++;
337
                if ($this->numgetinfocalls <= 3) {
338
                    return ['http_code' => 429];
339
                }
340
 
341
                return ['http_code' => 200];
342
            }
343
            // @codingStandardsIgnoreStart
344
            /**
345
             * Returns empty response.
346
             * @return array
347
             */
348
            public function getResponse() {
349
            // @codingStandardsIgnoreEnd
350
                return [];
351
            }
352
        };
353
 
354
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
355
            ->setMethods(['make_curl_call', 'get_curl_object', 'get_access_token'])
356
            ->getMock();
357
 
358
        $mockservice->expects($this->any())
359
            ->method('make_curl_call')
360
            ->willReturn('{"response":"success"}');
361
 
362
        $mockservice->expects($this->any())
363
            ->method('get_curl_object')
364
            ->willReturn($retrynoheadermockcurl);
365
 
366
        $mockservice->expects($this->any())
367
            ->method('get_access_token')
368
            ->willReturn('token123');
369
 
370
        $result = $mockservice->get_user("1");
371
        $this->assertDebuggingCalledCount($expectedcount = 3);
372
        $this->assertEquals($retrynoheadermockcurl->numgetinfocalls, 4);
373
        $this->assertEquals($result->response, 'success');
374
    }
375
 
376
    /**
377
     * Tests that we throw error if we tried more than max retries.
378
     */
379
    public function test_retry_exception(): void {
380
        // @codingStandardsIgnoreStart
381
        $retryfailuremockcurl = new class {
382
            public $urlpath = null;
383
            /**
384
             * Stub for curl setHeader().
385
             * @param string $unusedparam
386
             * @return void
387
             */
388
            public function setHeader($unusedparam) {
389
            // @codingStandardsIgnoreend
390
                return;
391
            }
392
            /**
393
             * Stub for curl get_errno().
394
             * @return boolean
395
             */
396
            public function get_errno() {
397
                return false;
398
            }
399
            /**
400
             * Returns 429.
401
             * @return array
402
             */
403
            public function get_info() {
404
                return array('http_code' => 429);
405
            }
406
            /**
407
             * Returns error code and message.
408
             * @param string $url
409
             * @param array $data
410
             * @return string
411
             */
412
            public function get($url, $data) {
413
                if ($this->urlpath === null) {
414
                    $this->urlpath = $url;
415
                } else if ($this->urlpath !== $url) {
416
                    // We should be getting the same path every time.
417
                    return '{"code":-1, "message":"incorrect url"}';
418
                }
419
                return '{"code":-1, "message":"too many retries"}';
420
            }
421
            // @codingStandardsIgnoreStart
422
            /**
423
             * Returns retry to be 1 second later.
424
             * @return array
425
             */
426
            public function getResponse() {
427
            // @codingStandardsIgnoreEnd
428
                // Set retry time after 1 second. Format is 2020-05-31T00:00:00Z.
429
                $retrytime = time() + 1;
430
                return [
431
                    'X-RateLimit-Type' => 'Daily',
432
                    'X-RateLimit-Remaining' => 100,
433
                    'Retry-After' => gmdate('Y-m-d\TH:i:s\Z', $retrytime),
434
                ];
435
            }
436
        };
437
 
438
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
439
            ->setMethods(['get_curl_object', 'get_access_token'])
440
            ->getMock();
441
 
442
        $mockservice->expects($this->any())
443
            ->method('get_curl_object')
444
            ->willReturn($retryfailuremockcurl);
445
 
446
        $mockservice->expects($this->any())
447
            ->method('get_access_token')
448
            ->willReturn('token123');
449
 
450
        $foundexception = false;
451
        try {
452
            $result = $mockservice->get_user("1");
453
        } catch (retry_failed_exception $error) {
454
            $foundexception = true;
455
            $this->assertEquals($error->response, 'too many retries');
456
        }
457
 
458
        $this->assertTrue($foundexception);
459
        // Check that we retried MAX_RETRIES times.
460
        $this->assertDebuggingCalledCount(webservice::MAX_RETRIES);
461
    }
462
 
463
    /**
464
     * Tests that we are waiting 1 minute for QPS rate limit types.
465
     */
466
    public function test_retryqps_exception(): void {
467
        // @codingStandardsIgnoreStart
468
        $retryqpsmockcurl = new class {
469
            public $urlpath = null;
470
            /**
471
             * Stub for curl setHeader().
472
             * @param string $unusedparam
473
             * @return void
474
             */
475
            public function setHeader($unusedparam) {
476
            // @codingStandardsIgnoreEnd
477
                return;
478
            }
479
            /**
480
             * Stub for curl get_errno().
481
             * @return boolean
482
             */
483
            public function get_errno() {
484
                return false;
485
            }
486
            /**
487
             * Returns 429.
488
             * @return array
489
             */
490
            public function get_info() {
491
                return ['http_code' => 429];
492
            }
493
            /**
494
             * Returns error code and message.
495
             * @param string $url
496
             * @param array $data
497
             * @return string
498
             */
499
            public function get($url, $data) {
500
                if ($this->urlpath === null) {
501
                    $this->urlpath = $url;
502
                } else if ($this->urlpath !== $url) {
503
                    // We should be getting the same path every time.
504
                    return '{"code":-1, "message":"incorrect url"}';
505
                }
506
 
507
                return '{"code":-1, "message":"too many retries"}';
508
            }
509
            // @codingStandardsIgnoreStart
510
            /**
511
             * Returns retry to be 1 second later.
512
             * @return array
513
             */
514
            public function getResponse() {
515
            // @codingStandardsIgnoreEnd
516
                // Signify that we reached max per second/minute rate limit.
517
                return ['X-RateLimit-Type' => 'QPS'];
518
            }
519
        };
520
 
521
        $mockservice = $this->getMockBuilder('\mod_zoom\webservice')
522
            ->setMethods(['get_curl_object', 'get_access_token'])
523
            ->getMock();
524
 
525
        $mockservice->expects($this->any())
526
            ->method('get_curl_object')
527
            ->willReturn($retryqpsmockcurl);
528
 
529
        $mockservice->expects($this->any())
530
            ->method('get_access_token')
531
            ->willReturn('token123');
532
 
533
        $foundexception = false;
534
        try {
535
            $result = $mockservice->get_meetings('2020-01-01', '2020-01-02');
536
        } catch (webservice_exception $error) {
537
            $foundexception = true;
538
            $this->assertEquals($error->response, 'too many retries');
539
        }
540
 
541
        $this->assertTrue($foundexception);
542
 
543
        // Check that we waited 1 minute.
544
        $debugging = $this->getDebuggingMessages();
545
 
546
        $debuggingmsg = array_pop($debugging);
547
        $this->assertEquals('Received 429 response, sleeping 60 seconds ' .
548
                'until next retry. Current retry: 5', $debuggingmsg->message);
549
 
550
        // Check that we retried MAX_RETRIES times.
551
        $this->assertDebuggingCalledCount(webservice::MAX_RETRIES);
552
    }
553
}