Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 402 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  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   * Unit tests for {@see activity_sender}.
  34   *
  35   * @coversDefaultClass \core\moodlenet\activity_sender
  36   * @package core
  37   * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
  38   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class activity_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      /** @var stdClass Activity object, */
  47      private stdClass $moduleinstance;
  48      /** @var context_course Course context instance. */
  49      private context_course $coursecontext;
  50      /** @var issuer $issuer Dummy issuer. */
  51      private issuer $issuer;
  52      /** @var MockObject $mockoauthclient Mock OAuth client. */
  53      private MockObject $mockoauthclient;
  54  
  55      public static function setUpBeforeClass(): void {
  56          parent::setUpBeforeClass();
  57  
  58          require_once (__DIR__ . '/helpers.php');
  59      }
  60  
  61      /**
  62       * Set up function for tests.
  63       */
  64      protected function setUp(): void {
  65          parent::setUp();
  66  
  67          $this->resetAfterTest();
  68          // Get data generator.
  69          $this->generator = $this->getDataGenerator();
  70          // Create course.
  71          $this->course = $this->generator->create_course();
  72          $this->moduleinstance = $this->generator->create_module('assign', ['course' => $this->course->id]);
  73          $this->coursecontext = context_course::instance($this->course->id);
  74          // Create mock issuer.
  75          $this->issuer = helpers::get_mock_issuer(1);
  76          // Create mock builder for OAuth2 client.
  77          $mockbuilder = $this->getMockBuilder('core\oauth2\client');
  78          $mockbuilder->onlyMethods(['get_issuer', 'is_logged_in', 'get_accesstoken']);
  79          $mockbuilder->setConstructorArgs([$this->issuer, '', '']);
  80          // Get the OAuth2 client mock.
  81          $this->mockoauthclient = $mockbuilder->getMock();
  82      }
  83  
  84      /**
  85       * Test prepare_share_contents method.
  86       *
  87       * @covers ::prepare_share_contents
  88       */
  89      public function test_prepare_share_contents(): void {
  90          global $USER;
  91          $this->setAdminUser();
  92  
  93          $httpclient = new http_client();
  94          $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
  95  
  96          // Set get_file method accessibility.
  97          $method = new ReflectionMethod(activity_sender::class, 'prepare_share_contents');
  98          $method->setAccessible(true);
  99  
 100          // Test with invalid share format.
 101          $this->expectException(\moodle_exception::class);
 102          $this->expectExceptionMessage(get_string('moodlenet:invalidshareformat', 'error'));
 103          $package = $method->invoke(new activity_sender(
 104              $this->moduleinstance->cmid,
 105              $USER->id,
 106              $moodlenetclient,
 107              $this->mockoauthclient,
 108              random_int(5, 30)
 109          ));
 110  
 111          // Test with valid share format and invalid course module.
 112          $package = $method->invoke(new activity_sender(
 113              random_int(5, 30),
 114              $USER->id,
 115              $moodlenetclient,
 116              $this->mockoauthclient,
 117              activity_sender::SHARE_FORMAT_BACKUP
 118          ));
 119          $this->assertEmpty($package);
 120  
 121          // Test with valid share format and valid course module.
 122          $package = $method->invoke(new activity_sender(
 123              $this->moduleinstance->cmid,
 124              $USER->id,
 125              $moodlenetclient,
 126              $this->mockoauthclient,
 127              activity_sender::SHARE_FORMAT_BACKUP
 128          ));
 129          $this->assertNotEmpty($package);
 130  
 131          // Confirm the expected stored_file object is returned.
 132          $this->assertInstanceOf(\stored_file::class, $package);
 133      }
 134  
 135      /**
 136       * Test get_resource_description method.
 137       *
 138       * @covers ::get_resource_description
 139       */
 140      public function test_get_resource_description(): void {
 141          global $USER;
 142          $this->setAdminUser();
 143  
 144          $activity = $this->generator->create_module('assign', [
 145              'course' => $this->course->id,
 146              'intro' => '<p>This is an example Moodle activity description.</p>
 147  <p>&nbsp;</p>
 148  <p>This is a formatted intro</p>
 149  <p>&nbsp;</p>
 150  <p>This thing has many lines.</p>
 151  <p>&nbsp;</p>
 152  <p>The last word of this sentence is in <strong>bold</strong></p>'
 153          ]);
 154  
 155          $httpclient = new http_client();
 156          $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
 157  
 158          // Set get_resource_description method accessibility.
 159          $method = new ReflectionMethod(activity_sender::class, 'get_resource_description');
 160          $method->setAccessible(true);
 161  
 162          // Test the processed description.
 163          $processeddescription = $method->invoke(new activity_sender(
 164              $activity->cmid,
 165              $USER->id,
 166              $moodlenetclient,
 167              $this->mockoauthclient,
 168              activity_sender::SHARE_FORMAT_BACKUP
 169          ), $this->coursecontext);
 170  
 171          $this->assertEquals('This is an example Moodle activity description.
 172   
 173  This is a formatted intro
 174   
 175  This thing has many lines.
 176   
 177  The last word of this sentence is in bold', $processeddescription);
 178      }
 179  
 180      /**
 181       * Test share_activity() method.
 182       *
 183       * @dataProvider share_activity_provider
 184       * @covers ::share_activity
 185       * @covers ::log_event
 186       * @covers \core\moodlenet\moodlenet_client::create_resource_from_stored_file
 187       * @covers \core\moodlenet\moodlenet_client::prepare_file_share_request_data
 188       * @param ResponseInterface $httpresponse
 189       * @param array $expected
 190       */
 191      public function test_share_activity(ResponseInterface $httpresponse, array $expected): void {
 192          global $CFG, $USER;
 193          $this->setAdminUser();
 194  
 195          // Enable the experimental flag.
 196          $CFG->enablesharingtomoodlenet = true;
 197  
 198          // Set OAuth 2 service in the outbound setting to the dummy issuer.
 199          set_config('oauthservice', $this->issuer->get('id'), 'moodlenet');
 200  
 201          // Generate access token for the mock.
 202          $accesstoken = new stdClass();
 203          $accesstoken->token = random_string(64);
 204  
 205          // Get the OAuth2 client mock and set the return value for necessary methods.
 206          $this->mockoauthclient->method('get_issuer')->will($this->returnValue($this->issuer));
 207          $this->mockoauthclient->method('is_logged_in')->will($this->returnValue(true));
 208          $this->mockoauthclient->method('get_accesstoken')->will($this->returnValue($accesstoken));
 209  
 210          // Create Guzzle mock.
 211          $mockguzzlehandler = new MockHandler([$httpresponse]);
 212          $handlerstack = HandlerStack::create($mockguzzlehandler);
 213          $httpclient = new http_client(['handler' => $handlerstack]);
 214  
 215          // Create events sink.
 216          $sink = $this->redirectEvents();
 217  
 218          // Create activity sender.
 219          $moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
 220          $activitysender = new activity_sender(
 221              $this->moduleinstance->cmid,
 222              $USER->id,
 223              $moodlenetclient,
 224              $this->mockoauthclient,
 225              activity_sender::SHARE_FORMAT_BACKUP
 226          );
 227  
 228          if (isset($expected['exception'])) {
 229              $this->expectException(ClientException::class);
 230              $this->expectExceptionMessage($expected['exception']);
 231          }
 232          // Call the API.
 233          $result = $activitysender->share_activity();
 234  
 235          // Verify the result.
 236          $this->assertEquals($expected['response_code'], $result['responsecode']);
 237          $this->assertEquals($expected['resource_url'], $result['drafturl']);
 238  
 239          // Verify the events.
 240          $events = $sink->get_events();
 241          $event = reset($events);
 242          $this->assertInstanceOf('\core\event\moodlenet_resource_exported', $event);
 243          $this->assertEquals($USER->id, $event->userid);
 244  
 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}': '{$this->moduleinstance->cmid}'.";
 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}': '{$this->moduleinstance->cmid}'.";
 251          }
 252          $this->assertEquals($description, $event->get_description());
 253      }
 254  
 255      /**
 256       * Provider for test share_activity().
 257       *
 258       * @return array Test data.
 259       */
 260      public function share_activity_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/activity_backup_1.mbz',
 268                      ]),
 269                  ),
 270                  'expected' => [
 271                      'response_code' => 201,
 272                      'resource_url' => 'https://moodlenet.example.com/drafts/view/activity_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/activity_backup_2.mbz',
 281                      ]),
 282                  ),
 283                  'expected' => [
 284                      'response_code' => 200,
 285                      'resource_url' => 'https://moodlenet.example.com/drafts/view/activity_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  }