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