Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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 ltiservice_gradebookservices;
  18  
  19  use ltiservice_gradebookservices\local\service\gradebookservices;
  20  
  21  /**
  22   * Unit tests for lti gradebookservices.
  23   *
  24   * @package    ltiservice_gradebookservices
  25   * @category   test
  26   * @copyright  2020 Claude Vervoort <claude.vervoort@cengage.com>
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   * @coversDefaultClass \mod_lti\service\gradebookservices\local\gradebookservices
  29   */
  30  class gradebookservices_test extends \advanced_testcase {
  31  
  32      /**
  33       * @covers ::instance_added
  34       *
  35       * Test saving a graded LTI with resource and tag info (as a result of
  36       * content item selection) creates a gradebookservices record
  37       * that can be retrieved using the gradebook service API.
  38       */
  39      public function test_lti_add_coupled_lineitem() {
  40          global $CFG;
  41          require_once($CFG->dirroot . '/mod/lti/locallib.php');
  42  
  43          $this->resetAfterTest();
  44          $this->setAdminUser();
  45  
  46          // Create a tool type, associated with that proxy.
  47  
  48          $typeid = $this->create_type();
  49          $course = $this->getDataGenerator()->create_course();
  50          $resourceid = 'test-resource-id';
  51          $tag = 'tag';
  52          $subreviewurl = 'https://subreview.example.com';
  53          $subreviewparams = 'a=2';
  54  
  55          $ltiinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag, $subreviewurl, $subreviewparams);
  56  
  57          $this->assertNotNull($ltiinstance);
  58  
  59          $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($ltiinstance->id);
  60  
  61          $this->assertNotNull($gbs);
  62          $this->assertEquals($resourceid, $gbs->resourceid);
  63          $this->assertEquals($tag, $gbs->tag);
  64          $this->assertEquals($subreviewurl, $gbs->subreviewurl);
  65          $this->assertEquals($subreviewparams, $gbs->subreviewparams);
  66          $this->assert_lineitems($course, $typeid, $ltiinstance->name,
  67              $ltiinstance, $resourceid, $tag, $subreviewurl, $subreviewparams);
  68      }
  69  
  70      /**
  71       * @covers ::instance_added
  72       *
  73       * Test saving a graded LTI with resource and tag info (as a result of
  74       * content item selection) creates a gradebookservices record
  75       * that can be retrieved using the gradebook service API.
  76       */
  77      public function test_lti_add_coupled_lineitem_default_subreview() {
  78          global $CFG;
  79          require_once($CFG->dirroot . '/mod/lti/locallib.php');
  80  
  81          $this->resetAfterTest();
  82          $this->setAdminUser();
  83  
  84          // Create a tool type, associated with that proxy.
  85  
  86          $typeid = $this->create_type();
  87          $course = $this->getDataGenerator()->create_course();
  88          $resourceid = 'test-resource-id';
  89          $tag = 'tag';
  90  
  91          $ltiinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag, 'DEFAULT');
  92  
  93          $this->assertNotNull($ltiinstance);
  94  
  95          $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($ltiinstance->id);
  96  
  97          $this->assertNotNull($gbs);
  98          $this->assertEquals('DEFAULT', $gbs->subreviewurl);
  99          $this->assert_lineitems($course, $typeid, $ltiinstance->name, $ltiinstance, $resourceid, $tag, 'DEFAULT');
 100      }
 101  
 102      /**
 103       * @covers ::add_standalone_lineitem
 104       *
 105       * Test saving a standalone LTI lineitem with resource and tag info
 106       * that can be retrieved using the gradebook service API.
 107       */
 108      public function test_lti_add_standalone_lineitem() {
 109          $this->resetAfterTest();
 110          $this->setAdminUser();
 111  
 112          $course = $this->getDataGenerator()->create_course();
 113          $resourceid = "test-resource-standalone";
 114          $tag = "test-tag-standalone";
 115          $typeid = $this->create_type();
 116  
 117          $this->create_standalone_lineitem($course->id, $typeid, $resourceid, $tag);
 118  
 119          $this->assert_lineitems($course, $typeid, "manualtest", null, $resourceid, $tag);
 120      }
 121  
 122      /**
 123       * @covers ::find_ltiservice_gradebookservice_for_lti
 124       *
 125       * Test line item URL is populated for coupled line item only
 126       * if there is not another line item bound to the lti instance,
 127       * since in that case there would be no rule to define which of
 128       * the line items should be actually passed.
 129       */
 130      public function test_get_launch_parameters_coupled() {
 131          global $CFG;
 132          require_once($CFG->dirroot . '/mod/lti/locallib.php');
 133  
 134          $this->resetAfterTest();
 135          $this->setAdminUser();
 136  
 137          // Create a tool type, associated with that proxy.
 138  
 139          $typeid = $this->create_type();
 140          $course = $this->getDataGenerator()->create_course();
 141  
 142          $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag', 'https://subreview.url', 'sub=review');
 143  
 144          $this->assertNotNull($ltiinstance);
 145  
 146          $gbservice = new gradebookservices();
 147          $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
 148          $this->assertEquals('$LineItem.url', $params['lineitem_url']);
 149          $this->assertEquals('$LineItem.url', $params['lineitem_url']);
 150  
 151          $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id);
 152          $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
 153          $this->assertEquals('$LineItems.url', $params['lineitems_url']);
 154          // 2 line items for a single link, we cannot return a single line item url.
 155          $this->assertFalse(array_key_exists('$LineItem.url', $params));
 156      }
 157  
 158      /**
 159       * @covers ::override_endpoint
 160       *
 161       * Test Submission Review URL and custom parameter is applied when the
 162       * launch is submission review.
 163       */
 164      public function test_get_launch_parameters_coupled_subreview_override() {
 165          global $CFG;
 166          require_once($CFG->dirroot . '/mod/lti/locallib.php');
 167  
 168          $this->resetAfterTest();
 169          $this->setAdminUser();
 170  
 171          // Create a tool type, associated with that proxy.
 172  
 173          $typeid = $this->create_type();
 174          $course = $this->getDataGenerator()->create_course();
 175  
 176          $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag',
 177              'https://example.com/subreview', 'action=review');
 178  
 179          $this->assertNotNull($ltiinstance);
 180  
 181          $gbservice = new gradebookservices();
 182          $overrides = $gbservice->override_endpoint('LtiSubmissionReviewRequest', 'https://example.com/lti',
 183              "color=blue", $course->id, $ltiinstance);
 184  
 185          $this->assertEquals('https://example.com/subreview', $overrides[0]);
 186          $this->assertEquals("color=blue\naction=review", $overrides[1]);
 187      }
 188  
 189      /**
 190       * @covers ::override_endpoint
 191       *
 192       * Test Submission Review URL and custom parameter is applied when the
 193       * launch is submission review.
 194       */
 195      public function test_get_launch_parameters_coupled_subreview_override_default() {
 196          global $CFG;
 197          require_once($CFG->dirroot . '/mod/lti/locallib.php');
 198  
 199          $this->resetAfterTest();
 200          $this->setAdminUser();
 201  
 202          // Create a tool type, associated with that proxy.
 203  
 204          $typeid = $this->create_type();
 205          $course = $this->getDataGenerator()->create_course();
 206  
 207          $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag',
 208              'DEFAULT', '');
 209  
 210          $this->assertNotNull($ltiinstance);
 211  
 212          $gbservice = new gradebookservices();
 213          $overrides = $gbservice->override_endpoint('LtiSubmissionReviewRequest', 'https://example.com/lti',
 214              "color=blue", $course->id, $ltiinstance);
 215  
 216          $this->assertEquals('https://example.com/lti', $overrides[0]);
 217          $this->assertEquals("color=blue", $overrides[1]);
 218      }
 219  
 220      /**
 221       * @covers ::get_launch_parameters
 222       *
 223       * Test line item URL is populated for not coupled line item only
 224       * if there is a single line item attached to that lti instance.
 225       */
 226      public function test_get_launch_parameters_decoupled() {
 227          global $CFG;
 228          require_once($CFG->dirroot . '/mod/lti/locallib.php');
 229  
 230          $this->resetAfterTest();
 231          $this->setAdminUser();
 232  
 233          // Create a tool type, associated with that proxy.
 234  
 235          $typeid = $this->create_type();
 236  
 237          $course = $this->getDataGenerator()->create_course();
 238  
 239          $ltiinstance = $this->create_notgraded_lti($typeid, $course);
 240  
 241          $this->assertNotNull($ltiinstance);
 242  
 243          $gbservice = new gradebookservices();
 244          $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
 245          $this->assertEquals('$LineItems.url', $params['lineitems_url']);
 246          $this->assertFalse(array_key_exists('$LineItem.url', $params));
 247  
 248          $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id);
 249          $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
 250          $this->assertEquals('$LineItems.url', $params['lineitems_url']);
 251          $this->assertEquals('$LineItem.url', $params['lineitem_url']);
 252  
 253          // 2 line items for a single link, we cannot return a single line item url.
 254          $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag-2', $ltiinstance->id);
 255          $this->assertFalse(array_key_exists('$LineItem.url', $params));
 256      }
 257  
 258      /**
 259       * @covers ::is_user_gradable_in_course
 260       *
 261       * Test if a user can be graded in a course.
 262       */
 263      public function test_is_user_gradable_in_course() {
 264          $this->resetAfterTest();
 265  
 266          $generator = $this->getDataGenerator();
 267          $course = $generator->create_course();
 268          $user1 = $generator->create_user();
 269          $user2 = $generator->create_user();
 270          $generator->enrol_user($user1->id, $course->id, 'student');
 271          $generator->enrol_user($user2->id, $course->id, 'editingteacher');
 272  
 273          $this->assertTrue(gradebookservices::is_user_gradable_in_course($course->id, $user1->id));
 274          $this->assertFalse(gradebookservices::is_user_gradable_in_course($course->id, $user2->id));
 275      }
 276  
 277      /**
 278       * Asserts a matching gradebookservices record exist with the matching tag and resourceid.
 279       *
 280       * @param object $course current course
 281       * @param int $typeid Type id of the tool
 282       * @param string $label Label of the line item
 283       * @param object|null $ltiinstance lti instance related to that line item
 284       * @param string|null $resourceid resourceid the line item should have
 285       * @param string|null $tag tag the line item should have
 286       * @param string|null $subreviewurl submission review url
 287       * @param string|null $subreviewparams submission review custom params
 288       */
 289      private function assert_lineitems(object $course, int $typeid,
 290              string $label, ?object $ltiinstance, ?string $resourceid, ?string $tag,
 291              ?string $subreviewurl = null, ?string $subreviewparams = null) : void {
 292          $gbservice = new gradebookservices();
 293          $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
 294  
 295          // The 1st item in the array is the items count.
 296          $this->assertEquals(1, $gradeitems[0]);
 297          $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
 298          $this->assertEquals(10, $lineitem->scoreMaximum);
 299          $this->assertEquals($resourceid, $lineitem->resourceId);
 300          $this->assertEquals($tag, $lineitem->tag);
 301          $this->assertEquals($label, $lineitem->label);
 302          $this->assertEquals(!empty($subreviewurl), isset($lineitem->submissionReview));
 303          if ($subreviewurl) {
 304              if ($subreviewurl == 'DEFAULT') {
 305                  $this->assertFalse(isset($this->submissionReview->url));
 306              } else {
 307                  $this->assertEquals($subreviewurl, $lineitem->submissionReview->url);
 308              }
 309              if ($subreviewparams) {
 310                  $custom = $lineitem->submissionReview->custom;
 311                  $this->assertEquals($subreviewparams, join("\n", array_map(fn($k) => $k.'='.$custom[$k], array_keys($custom))));
 312              } else {
 313                  $this->assertFalse(isset($this->submissionReview->custom));
 314              }
 315          }
 316  
 317          $gradeitems = $gbservice->get_lineitems($course->id, $resourceid, null, null, null, null, $typeid);
 318          $this->assertEquals(1, $gradeitems[0]);
 319  
 320          if (isset($ltiinstance)) {
 321              $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id, null, null, null, $typeid);
 322              $this->assertEquals(1, $gradeitems[0]);
 323              $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id + 1, null, null, null, $typeid);
 324              $this->assertEquals(0, $gradeitems[0]);
 325          }
 326  
 327          $gradeitems = $gbservice->get_lineitems($course->id, null, null, $tag, null, null, $typeid);
 328          $this->assertEquals(1, $gradeitems[0]);
 329  
 330          $gradeitems = $gbservice->get_lineitems($course->id, 'an unknown resource id', null, null, null, null, $typeid);
 331          $this->assertEquals(0, $gradeitems[0]);
 332  
 333          $gradeitems = $gbservice->get_lineitems($course->id, null, null, 'an unknown tag', null, null, $typeid);
 334          $this->assertEquals(0, $gradeitems[0]);
 335      }
 336  
 337      /**
 338       * Inserts a graded lti instance, which should create a grade_item and gradebookservices record.
 339       *
 340       * @param int $typeid Type ID of the LTI Tool.
 341       * @param object $course course where to add the lti instance.
 342       * @param string|null $resourceid resource id
 343       * @param string|null $tag tag
 344       * @param string|null $subreviewurl submission review url
 345       * @param string|null $subreviewparams submission review custom params
 346       *
 347       * @return object lti instance created
 348       */
 349      private function create_graded_lti(int $typeid, object $course, ?string $resourceid, ?string $tag,
 350              ?string $subreviewurl = null, ?string $subreviewparams = null) : object {
 351  
 352          $lti = ['course' => $course->id,
 353              'typeid' => $typeid,
 354              'instructorchoiceacceptgrades' => LTI_SETTING_ALWAYS,
 355              'grade' => 10,
 356              'lineitemresourceid' => $resourceid,
 357              'lineitemtag' => $tag,
 358              'lineitemsubreviewurl' => $subreviewurl,
 359              'lineitemsubreviewparams' => $subreviewparams];
 360  
 361          return $this->getDataGenerator()->create_module('lti', $lti, array());
 362      }
 363  
 364       /**
 365        * Inserts an lti instance that is not graded.
 366        *
 367        * @param int $typeid Type Id of the LTI Tool.
 368        * @param object $course course where to add the lti instance.
 369        *
 370        * @return object lti instance created
 371        */
 372      private function create_notgraded_lti(int $typeid, object $course) : object {
 373  
 374          $lti = ['course' => $course->id,
 375              'typeid' => $typeid,
 376              'instructorchoiceacceptgrades' => LTI_SETTING_NEVER];
 377  
 378          return $this->getDataGenerator()->create_module('lti', $lti, array());
 379      }
 380  
 381      /**
 382       * Inserts a standalone lineitem (gradeitem, gradebookservices entries).
 383       *
 384       * @param int $courseid Id of the course where the standalone line item will be added.
 385       * @param int $typeid of the LTI Tool
 386       * @param string|null $resourceid resource id
 387       * @param string|null $tag tag
 388       * @param int|null $ltiinstanceid Id of the LTI instance the standalone line item will be related to.
 389       *
 390       */
 391      private function create_standalone_lineitem(int $courseid, int $typeid, ?string $resourceid,
 392              ?string $tag, int $ltiinstanceid = null) : void {
 393          $gbservice = new gradebookservices();
 394          $gbservice->add_standalone_lineitem($courseid,
 395              "manualtest",
 396              10,
 397              "https://test.phpunit",
 398              $ltiinstanceid,
 399              $resourceid,
 400              $tag,
 401              $typeid,
 402              null /*toolproxyid*/);
 403      }
 404  
 405      /**
 406       * Creates a new LTI Tool Type.
 407       */
 408      private function create_type() {
 409          $type = new \stdClass();
 410          $type->state = LTI_TOOL_STATE_CONFIGURED;
 411          $type->name = "Test tool";
 412          $type->description = "Example description";
 413          $type->clientid = "Test client ID";
 414          $type->baseurl = $this->getExternalTestFileUrl('/test.html');
 415  
 416          $config = new \stdClass();
 417          $config->ltiservice_gradesynchronization = 2;
 418          return lti_add_type($type, $config);
 419      }
 420  }