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 core\moodlenet; 18 19 use context_course; 20 use core\http_client; 21 use core\oauth2\issuer; 22 use GuzzleHttp\Exception\ClientException; 23 use GuzzleHttp\Handler\MockHandler; 24 use GuzzleHttp\HandlerStack; 25 use GuzzleHttp\Psr7\Response; 26 use moodle_exception; 27 use PHPUnit\Framework\MockObject\MockObject; 28 use Psr\Http\Message\ResponseInterface; 29 use ReflectionMethod; 30 use stdClass; 31 use testing_data_generator; 32 33 defined('MOODLE_INTERNAL') || die(); 34 35 global $CFG; 36 37 require_once($CFG->dirroot . '/lib/tests/moodlenet/helpers.php'); 38 39 /** 40 * Unit tests for {@see \core\moodlenet\course_partial_sender}. 41 * 42 * @coversDefaultClass \core\moodlenet\course_partial_sender 43 * @package core 44 * @copyright 2023 Huong Nguyen <huongnv13@gmail.com> 45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 */ 47 class course_partial_sender_test extends \advanced_testcase { 48 49 /** @var testing_data_generator Data generator. */ 50 private testing_data_generator $generator; 51 /** @var stdClass Course object. */ 52 private stdClass $course; 53 /** @var context_course Course context instance. */ 54 private context_course $coursecontext; 55 /** @var array List of activities. */ 56 private array $activities; 57 /** @var issuer $issuer Dummy issuer. */ 58 private issuer $issuer; 59 /** @var MockObject $mockoauthclient Mock OAuth client. */ 60 private MockObject $mockoauthclient; 61 62 /** 63 * Set up function for tests. 64 */ 65 protected function setUp(): void { 66 parent::setUp(); 67 68 $this->resetAfterTest(); 69 // Get data generator. 70 $this->generator = $this->getDataGenerator(); 71 // Create course. 72 $this->course = $this->generator->create_course(['shortname' => 'testcourse']); 73 $this->coursecontext = context_course::instance($this->course->id); 74 // Create activities. 75 $this->activities[1] = $this->generator->create_module('page', ['course' => $this->course->id]); 76 $this->activities[2] = $this->generator->create_module('page', ['course' => $this->course->id]); 77 // Create mock issuer. 78 $this->issuer = helpers::get_mock_issuer(1); 79 // Create mock builder for OAuth2 client. 80 $mockbuilder = $this->getMockBuilder('core\oauth2\client'); 81 $mockbuilder->onlyMethods(['get_issuer', 'is_logged_in', 'get_accesstoken']); 82 $mockbuilder->setConstructorArgs([$this->issuer, '', '']); 83 // Get the OAuth2 client mock. 84 $this->mockoauthclient = $mockbuilder->getMock(); 85 } 86 87 /** 88 * Test prepare_share_contents method. 89 * 90 * @covers ::prepare_share_contents 91 */ 92 public function test_prepare_share_contents(): void { 93 global $USER; 94 $this->setAdminUser(); 95 96 // Set get_file method accessibility. 97 $method = new ReflectionMethod(course_partial_sender::class, 'prepare_share_contents'); 98 $method->setAccessible(true); 99 100 $httpclient = new http_client(); 101 $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient); 102 103 $this->expectException(moodle_exception::class); 104 $this->expectExceptionMessage(get_string('invalidcoursemodule', 'error')); 105 106 // Test with valid share format and invalid course module id. 107 $package = $method->invoke(new course_partial_sender( 108 $this->course->id, 109 $USER->id, 110 $moodlenetclient, 111 $this->mockoauthclient, 112 [random_int(5, 30)], 113 resource_sender::SHARE_FORMAT_BACKUP 114 )); 115 $this->assertEmpty($package); 116 117 // Test with valid share format and valid course module ids. 118 $package = $method->invoke(new course_partial_sender( 119 $this->course->id, 120 $USER->id, 121 $moodlenetclient, 122 $this->mockoauthclient, 123 [$this->activities[1]->cmid, $this->activities[2]->cmid], 124 resource_sender::SHARE_FORMAT_BACKUP 125 )); 126 $this->assertNotEmpty($package); 127 128 // Confirm the expected stored_file object is returned. 129 $this->assertInstanceOf(\stored_file::class, $package); 130 } 131 132 /** 133 * Test get_resource_description method. 134 * 135 * @covers ::get_resource_description 136 */ 137 public function test_get_resource_description(): void { 138 global $USER; 139 $this->setAdminUser(); 140 141 $course = $this->generator->create_course([ 142 'summary' => '<p>This is an example Moodle course description.</p> 143 <p> </p> 144 <p>This is a formatted intro</p> 145 <p> </p> 146 <p>This thing has many lines.</p> 147 <p> </p> 148 <p>The last word of this sentence is in <strong>bold</strong></p>' 149 ]); 150 $page = $this->generator->create_module('page', ['course' => $course->id]); 151 152 // Set get_resource_description method accessibility. 153 $method = new ReflectionMethod(course_partial_sender::class, 'get_resource_description'); 154 $method->setAccessible(true); 155 156 // Test the processed description. 157 $httpclient = new http_client(); 158 $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient); 159 $processeddescription = $method->invoke(new course_partial_sender( 160 $course->id, 161 $USER->id, 162 $moodlenetclient, 163 $this->mockoauthclient, 164 [$page->cmid], 165 resource_sender::SHARE_FORMAT_BACKUP 166 ), $this->coursecontext); 167 168 $this->assertEquals('This is an example Moodle course description. 169 170 This is a formatted intro 171 172 This thing has many lines. 173 174 The last word of this sentence is in bold', $processeddescription); 175 } 176 177 /** 178 * Test share_resource() method. 179 * 180 * @dataProvider share_resource_provider 181 * @covers ::share_resource 182 * @covers ::log_event 183 * @covers \core\moodlenet\moodlenet_client::create_resource_from_stored_file 184 * @covers \core\moodlenet\moodlenet_client::prepare_file_share_request_data 185 * @param ResponseInterface $httpresponse 186 * @param array $expected 187 */ 188 public function test_share_resource(ResponseInterface $httpresponse, array $expected): void { 189 global $CFG, $USER; 190 $this->setAdminUser(); 191 192 // Enable the experimental flag. 193 $CFG->enablesharingtomoodlenet = true; 194 195 // Set OAuth 2 service in the outbound setting to the dummy issuer. 196 set_config('oauthservice', $this->issuer->get('id'), 'moodlenet'); 197 198 // Generate access token for the mock. 199 $accesstoken = new stdClass(); 200 $accesstoken->token = random_string(64); 201 202 // Get the OAuth2 client mock and set the return value for necessary methods. 203 $this->mockoauthclient->method('get_issuer')->will($this->returnValue($this->issuer)); 204 $this->mockoauthclient->method('is_logged_in')->will($this->returnValue(true)); 205 $this->mockoauthclient->method('get_accesstoken')->will($this->returnValue($accesstoken)); 206 207 // Create Guzzle mock. 208 $mockguzzlehandler = new MockHandler([$httpresponse]); 209 $handlerstack = HandlerStack::create($mockguzzlehandler); 210 $httpclient = new http_client(['handler' => $handlerstack]); 211 212 // Create events sink. 213 $sink = $this->redirectEvents(); 214 215 // Create sender. 216 $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient); 217 $coursepartialsender = new course_partial_sender( 218 $this->course->id, 219 $USER->id, 220 $moodlenetclient, 221 $this->mockoauthclient, 222 [$this->activities[1]->cmid, $this->activities[2]->cmid], 223 resource_sender::SHARE_FORMAT_BACKUP 224 ); 225 226 if (isset($expected['exception'])) { 227 $this->expectException(ClientException::class); 228 $this->expectExceptionMessage($expected['exception']); 229 } 230 // Call the API. 231 $result = $coursepartialsender->share_resource(); 232 233 // Verify the result. 234 $this->assertEquals($expected['response_code'], $result['responsecode']); 235 $this->assertEquals($expected['resource_url'], $result['drafturl']); 236 237 // Verify the events. 238 $events = $sink->get_events(); 239 240 $event = end($events); 241 $this->assertInstanceOf('\core\event\moodlenet_resource_exported', $event); 242 $this->assertEquals($USER->id, $event->userid); 243 244 $cmidslist = implode("', '", [$this->activities[1]->cmid, $this->activities[2]->cmid]); 245 if ($result['responsecode'] == 201) { 246 $description = "The user with id '{$USER->id}' successfully shared activities to MoodleNet with the " . 247 "following course module ids, from context with id '{$this->coursecontext->id}': '{$cmidslist}'."; 248 } else { 249 $description = "The user with id '{$USER->id}' failed to share activities to MoodleNet with the " . 250 "following course module ids, from context with id '{$this->coursecontext->id}': '{$cmidslist}'."; 251 } 252 $this->assertEquals($description, $event->get_description()); 253 } 254 255 /** 256 * Provider for test share_resource(). 257 * 258 * @return array Test data. 259 */ 260 public function share_resource_provider(): array { 261 return [ 262 'Success' => [ 263 'http_response' => new Response( 264 201, 265 ['Content-Type' => 'application/json'], 266 json_encode([ 267 'homepage' => 'https://moodlenet.example.com/drafts/view/testcourse_backup_1.mbz', 268 ]), 269 ), 270 'expected' => [ 271 'response_code' => 201, 272 'resource_url' => 'https://moodlenet.example.com/drafts/view/testcourse_backup_1.mbz', 273 ], 274 ], 275 'Fail with 200 status code' => [ 276 'http_response' => new Response( 277 200, 278 ['Content-Type' => 'application/json'], 279 json_encode([ 280 'homepage' => 'https://moodlenet.example.com/drafts/view/testcourse_backup_2.mbz', 281 ]), 282 ), 283 'expected' => [ 284 'response_code' => 200, 285 'resource_url' => 'https://moodlenet.example.com/drafts/view/testcourse_backup_2.mbz', 286 ], 287 ], 288 'Fail with 401 status code' => [ 289 'http_response' => new Response( 290 401, 291 ), 292 'expected' => [ 293 'response_code' => 401, 294 'resource_url' => '', 295 'exception' => 'Client error: ' . 296 '`POST https://moodlenet.example.com/.pkg/@moodlenet/ed-resource/basic/v1/create` ' . 297 'resulted in a `401 Unauthorized` response', 298 ], 299 ], 300 'Fail with 404 status code' => [ 301 'http_response' => new Response( 302 404, 303 ), 304 'expected' => [ 305 'response_code' => 404, 306 'resource_url' => '', 307 'exception' => 'Client error: '. 308 '`POST https://moodlenet.example.com/.pkg/@moodlenet/ed-resource/basic/v1/create` ' . 309 'resulted in a `404 Not Found` response', 310 ], 311 ], 312 ]; 313 } 314 315 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body