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 311 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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  /**
  18   * External tests.
  19   *
  20   * @package    tool_dataprivacy
  21   * @copyright  2018 Jun Pataleta
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace tool_dataprivacy\external;
  26  
  27  use core_external\external_api;
  28  use externallib_advanced_testcase;
  29  use tool_dataprivacy\api;
  30  use tool_dataprivacy\context_instance;
  31  use tool_dataprivacy\external;
  32  
  33  defined('MOODLE_INTERNAL') || die();
  34  global $CFG;
  35  
  36  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  37  
  38  /**
  39   * External testcase.
  40   *
  41   * @package    tool_dataprivacy
  42   * @copyright  2018 Jun Pataleta
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class external_test extends externallib_advanced_testcase {
  46  
  47      /**
  48       * Test for external::approve_data_request() with the user not logged in.
  49       */
  50      public function test_approve_data_request_not_logged_in() {
  51          $this->resetAfterTest();
  52  
  53          $generator = new \testing_data_generator();
  54          $requester = $generator->create_user();
  55          $comment = 'sample comment';
  56  
  57          // Test data request creation.
  58          $this->setUser($requester);
  59          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
  60  
  61          // Log out the user and set force login to true.
  62          $this->setUser();
  63  
  64          $this->expectException(\require_login_exception::class);
  65          external::approve_data_request($datarequest->get('id'));
  66      }
  67  
  68      /**
  69       * Test for external::approve_data_request() with the user not having a DPO role.
  70       */
  71      public function test_approve_data_request_not_dpo() {
  72          $this->resetAfterTest();
  73  
  74          $generator = new \testing_data_generator();
  75          $requester = $generator->create_user();
  76          $comment = 'sample comment';
  77  
  78          // Test data request creation.
  79          $this->setUser($requester);
  80          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
  81  
  82          // Login as the requester.
  83          $this->setUser($requester);
  84          $this->expectException(\required_capability_exception::class);
  85          external::approve_data_request($datarequest->get('id'));
  86      }
  87  
  88      /**
  89       * Test for external::approve_data_request() for request that's not ready for approval
  90       */
  91      public function test_approve_data_request_not_waiting_for_approval() {
  92          $this->resetAfterTest();
  93  
  94          $generator = new \testing_data_generator();
  95          $requester = $generator->create_user();
  96          $comment = 'sample comment';
  97  
  98          // Test data request creation.
  99          $this->setUser($requester);
 100          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 101          $datarequest->set('status', api::DATAREQUEST_STATUS_CANCELLED)->save();
 102  
 103          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 104          $this->setAdminUser();
 105          $this->expectException(\moodle_exception::class);
 106          external::approve_data_request($datarequest->get('id'));
 107      }
 108  
 109      /**
 110       * Test for external::approve_data_request()
 111       */
 112      public function test_approve_data_request() {
 113          $this->resetAfterTest();
 114  
 115          $generator = new \testing_data_generator();
 116          $requester = $generator->create_user();
 117          $comment = 'sample comment';
 118  
 119          // Test data request creation.
 120          $this->setUser($requester);
 121          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 122  
 123          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 124          $this->setAdminUser();
 125          api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 126          $result = external::approve_data_request($datarequest->get('id'));
 127          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 128          $this->assertTrue($return->result);
 129          $this->assertEmpty($return->warnings);
 130      }
 131  
 132      /**
 133       * Test for external::approve_data_request() for a non-existent request ID.
 134       */
 135      public function test_approve_data_request_non_existent() {
 136          $this->resetAfterTest();
 137  
 138          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 139          $this->setAdminUser();
 140  
 141          $result = external::approve_data_request(1);
 142  
 143          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 144          $this->assertFalse($return->result);
 145          $this->assertCount(1, $return->warnings);
 146          $warning = reset($return->warnings);
 147          $this->assertEquals('errorrequestnotfound', $warning['warningcode']);
 148      }
 149  
 150      /**
 151       * Test for external::cancel_data_request() of another user.
 152       */
 153      public function test_cancel_data_request_other_user() {
 154          $this->resetAfterTest();
 155  
 156          $generator = new \testing_data_generator();
 157          $requester = $generator->create_user();
 158          $otheruser = $generator->create_user();
 159          $comment = 'sample comment';
 160  
 161          // Test data request creation.
 162          $this->setUser($requester);
 163          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 164  
 165          // Login as other user.
 166          $this->setUser($otheruser);
 167  
 168          $result = external::cancel_data_request($datarequest->get('id'));
 169          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 170          $this->assertFalse($return->result);
 171          $this->assertCount(1, $return->warnings);
 172          $warning = reset($return->warnings);
 173          $this->assertEquals('errorrequestnotfound', $warning['warningcode']);
 174      }
 175  
 176      /**
 177       * Test cancellation of a request where you are the requester of another user's data.
 178       */
 179      public function test_cancel_data_request_other_user_as_requester() {
 180          $this->resetAfterTest();
 181  
 182          $generator = new \testing_data_generator();
 183          $requester = $generator->create_user();
 184          $otheruser = $generator->create_user();
 185          $comment = 'sample comment';
 186  
 187          // Assign requester as otheruser'sparent.
 188          $systemcontext = \context_system::instance();
 189          $parentrole = $generator->create_role();
 190          assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
 191          role_assign($parentrole, $requester->id, \context_user::instance($otheruser->id));
 192  
 193          // Test data request creation.
 194          $this->setUser($requester);
 195          $datarequest = api::create_data_request($otheruser->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 196  
 197          $result = external::cancel_data_request($datarequest->get('id'));
 198          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 199          $this->assertTrue($return->result);
 200          $this->assertEmpty($return->warnings);
 201      }
 202  
 203      /**
 204       * Test cancellation of a request where you are the requester of another user's data.
 205       */
 206      public function test_cancel_data_request_requester_lost_permissions() {
 207          $this->resetAfterTest();
 208  
 209          $generator = new \testing_data_generator();
 210          $requester = $generator->create_user();
 211          $otheruser = $generator->create_user();
 212          $comment = 'sample comment';
 213  
 214          // Assign requester as otheruser'sparent.
 215          $systemcontext = \context_system::instance();
 216          $parentrole = $generator->create_role();
 217          assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
 218          role_assign($parentrole, $requester->id, \context_user::instance($otheruser->id));
 219  
 220          $this->setUser($requester);
 221          $datarequest = api::create_data_request($otheruser->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 222  
 223          // Unassign the role.
 224          role_unassign($parentrole, $requester->id, \context_user::instance($otheruser->id)->id);
 225  
 226          // This user can no longer make the request.
 227          $this->expectException(\required_capability_exception::class);
 228  
 229          $result = external::cancel_data_request($datarequest->get('id'));
 230      }
 231  
 232      /**
 233       * Test cancellation of a request where you are the requester of another user's data.
 234       */
 235      public function test_cancel_data_request_other_user_as_child() {
 236          $this->resetAfterTest();
 237  
 238          $generator = new \testing_data_generator();
 239          $requester = $generator->create_user();
 240          $otheruser = $generator->create_user();
 241          $comment = 'sample comment';
 242  
 243          // Assign requester as otheruser'sparent.
 244          $systemcontext = \context_system::instance();
 245          $parentrole = $generator->create_role();
 246          assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
 247          role_assign($parentrole, $requester->id, \context_user::instance($otheruser->id));
 248  
 249          // Test data request creation.
 250          $this->setUser($otheruser);
 251          $datarequest = api::create_data_request($otheruser->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 252  
 253          $result = external::cancel_data_request($datarequest->get('id'));
 254          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 255          $this->assertTrue($return->result);
 256          $this->assertEmpty($return->warnings);
 257      }
 258  
 259      /**
 260       * Test for external::cancel_data_request()
 261       */
 262      public function test_cancel_data_request() {
 263          $this->resetAfterTest();
 264  
 265          $generator = new \testing_data_generator();
 266          $requester = $generator->create_user();
 267          $comment = 'sample comment';
 268  
 269          // Test data request creation.
 270          $this->setUser($requester);
 271          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 272  
 273          // Test cancellation.
 274          $this->setUser($requester);
 275          $result = external::cancel_data_request($datarequest->get('id'));
 276  
 277          $return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
 278          $this->assertTrue($return->result);
 279          $this->assertEmpty($return->warnings);
 280      }
 281  
 282      /**
 283       * Test contact DPO.
 284       */
 285      public function test_contact_dpo() {
 286          $this->resetAfterTest();
 287  
 288          $generator = new \testing_data_generator();
 289          $user = $generator->create_user();
 290  
 291          $this->setUser($user);
 292          $message = 'Hello world!';
 293          $result = external::contact_dpo($message);
 294          $return = (object) external_api::clean_returnvalue(external::contact_dpo_returns(), $result);
 295          $this->assertTrue($return->result);
 296          $this->assertEmpty($return->warnings);
 297      }
 298  
 299      /**
 300       * Test contact DPO with message containing invalid input.
 301       */
 302      public function test_contact_dpo_with_nasty_input() {
 303          $this->resetAfterTest();
 304  
 305          $generator = new \testing_data_generator();
 306          $user = $generator->create_user();
 307  
 308          $this->setUser($user);
 309          $this->expectException('invalid_parameter_exception');
 310          external::contact_dpo('de<>\\..scription');
 311      }
 312  
 313      /**
 314       * Test for external::deny_data_request() with the user not logged in.
 315       */
 316      public function test_deny_data_request_not_logged_in() {
 317          $this->resetAfterTest();
 318  
 319          $generator = new \testing_data_generator();
 320          $requester = $generator->create_user();
 321          $comment = 'sample comment';
 322  
 323          // Test data request creation.
 324          $this->setUser($requester);
 325          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 326  
 327          // Log out.
 328          $this->setUser();
 329          $this->expectException(\require_login_exception::class);
 330          external::deny_data_request($datarequest->get('id'));
 331      }
 332  
 333      /**
 334       * Test for external::deny_data_request() with the user not having a DPO role.
 335       */
 336      public function test_deny_data_request_not_dpo() {
 337          $this->resetAfterTest();
 338  
 339          $generator = new \testing_data_generator();
 340          $requester = $generator->create_user();
 341          $comment = 'sample comment';
 342  
 343          $this->setUser($requester);
 344          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 345  
 346          // Login as the requester.
 347          $this->setUser($requester);
 348          $this->expectException(\required_capability_exception::class);
 349          external::deny_data_request($datarequest->get('id'));
 350      }
 351  
 352      /**
 353       * Test for external::deny_data_request() for request that's not ready for approval
 354       */
 355      public function test_deny_data_request_not_waiting_for_approval() {
 356          $this->resetAfterTest();
 357  
 358          $generator = new \testing_data_generator();
 359          $requester = $generator->create_user();
 360          $comment = 'sample comment';
 361  
 362          $this->setUser($requester);
 363          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 364          $datarequest->set('status', api::DATAREQUEST_STATUS_CANCELLED)->save();
 365  
 366          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 367          $this->setAdminUser();
 368          $this->expectException(\moodle_exception::class);
 369          external::deny_data_request($datarequest->get('id'));
 370      }
 371  
 372      /**
 373       * Test for external::deny_data_request()
 374       */
 375      public function test_deny_data_request() {
 376          $this->resetAfterTest();
 377  
 378          $generator = new \testing_data_generator();
 379          $requester = $generator->create_user();
 380          $comment = 'sample comment';
 381  
 382          $this->setUser($requester);
 383          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 384  
 385          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 386          $this->setAdminUser();
 387          api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 388          $result = external::approve_data_request($datarequest->get('id'));
 389          $return = (object) external_api::clean_returnvalue(external::deny_data_request_returns(), $result);
 390          $this->assertTrue($return->result);
 391          $this->assertEmpty($return->warnings);
 392      }
 393  
 394      /**
 395       * Test for external::deny_data_request() for a non-existent request ID.
 396       */
 397      public function test_deny_data_request_non_existent() {
 398          $this->resetAfterTest();
 399  
 400          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 401          $this->setAdminUser();
 402          $result = external::deny_data_request(1);
 403  
 404          $return = (object) external_api::clean_returnvalue(external::deny_data_request_returns(), $result);
 405          $this->assertFalse($return->result);
 406          $this->assertCount(1, $return->warnings);
 407          $warning = reset($return->warnings);
 408          $this->assertEquals('errorrequestnotfound', $warning['warningcode']);
 409      }
 410  
 411      /**
 412       * Test for external::get_data_request() with the user not logged in.
 413       */
 414      public function test_get_data_request_not_logged_in() {
 415          $this->resetAfterTest();
 416  
 417          $generator = new \testing_data_generator();
 418          $requester = $generator->create_user();
 419          $comment = 'sample comment';
 420  
 421          $this->setUser($requester);
 422          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 423  
 424          $this->setUser();
 425          $this->expectException(\require_login_exception::class);
 426          external::get_data_request($datarequest->get('id'));
 427      }
 428  
 429      /**
 430       * Test for external::get_data_request() with the user not having a DPO role.
 431       */
 432      public function test_get_data_request_not_dpo() {
 433          $this->resetAfterTest();
 434  
 435          $generator = new \testing_data_generator();
 436          $requester = $generator->create_user();
 437          $otheruser = $generator->create_user();
 438          $comment = 'sample comment';
 439  
 440          $this->setUser($requester);
 441          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 442  
 443          // Login as the otheruser.
 444          $this->setUser($otheruser);
 445          $this->expectException(\required_capability_exception::class);
 446          external::get_data_request($datarequest->get('id'));
 447      }
 448  
 449      /**
 450       * Test for external::get_data_request()
 451       */
 452      public function test_get_data_request() {
 453          $this->resetAfterTest();
 454  
 455          $generator = new \testing_data_generator();
 456          $requester = $generator->create_user();
 457          $comment = 'sample comment';
 458  
 459          $this->setUser($requester);
 460          $datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
 461  
 462          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 463          $this->setAdminUser();
 464          $result = external::get_data_request($datarequest->get('id'));
 465  
 466          $return = (object) external_api::clean_returnvalue(external::get_data_request_returns(), $result);
 467          $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $return->result['type']);
 468          $this->assertEquals('sample comment', $return->result['comments']);
 469          $this->assertEquals($requester->id, $return->result['userid']);
 470          $this->assertEquals($requester->id, $return->result['requestedby']);
 471          $this->assertEmpty($return->warnings);
 472      }
 473  
 474      /**
 475       * Test for external::get_data_request() for a non-existent request ID.
 476       */
 477      public function test_get_data_request_non_existent() {
 478          $this->resetAfterTest();
 479  
 480          // Admin as DPO. (The default when no one's assigned as a DPO in the site).
 481          $this->setAdminUser();
 482          $this->expectException(\dml_missing_record_exception::class);
 483          external::get_data_request(1);
 484      }
 485  
 486      /**
 487       * Test for \tool_dataprivacy\external::set_context_defaults()
 488       * when called by a user that doesn't have the manage registry capability.
 489       */
 490      public function test_set_context_defaults_no_capability() {
 491          $this->resetAfterTest();
 492  
 493          $generator = $this->getDataGenerator();
 494          $user = $generator->create_user();
 495          $this->setUser($user);
 496          $this->expectException(\required_capability_exception::class);
 497          external::set_context_defaults(CONTEXT_COURSECAT, context_instance::INHERIT, context_instance::INHERIT, '', false);
 498      }
 499  
 500      /**
 501       * Test for \tool_dataprivacy\external::set_context_defaults().
 502       *
 503       * We're just checking the module context level here to test the WS function.
 504       * More testing is done in \tool_dataprivacy_api_testcase::test_set_context_defaults().
 505       *
 506       * @dataProvider get_options_provider
 507       * @param bool $modulelevel Whether defaults are to be applied on the module context level or for an activity only.
 508       * @param bool $override Whether to override instances.
 509       */
 510      public function test_set_context_defaults($modulelevel, $override) {
 511          $this->resetAfterTest();
 512  
 513          $this->setAdminUser();
 514          $generator = $this->getDataGenerator();
 515  
 516          // Generate course cat, course, block, assignment, forum instances.
 517          $coursecat = $generator->create_category();
 518          $course = $generator->create_course(['category' => $coursecat->id]);
 519          $assign = $generator->create_module('assign', ['course' => $course->id]);
 520          list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
 521          $assigncontext = \context_module::instance($assigncm->id);
 522  
 523          // Generate purpose and category.
 524          $category1 = api::create_category((object)['name' => 'Test category 1']);
 525          $category2 = api::create_category((object)['name' => 'Test category 2']);
 526          $purpose1 = api::create_purpose((object)[
 527              'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
 528          ]);
 529          $purpose2 = api::create_purpose((object)[
 530              'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
 531          ]);
 532  
 533          // Set a custom purpose and ID for this assignment instance.
 534          $assignctxinstance = api::set_context_instance((object) [
 535              'contextid' => $assigncontext->id,
 536              'purposeid' => $purpose1->get('id'),
 537              'categoryid' => $category1->get('id'),
 538          ]);
 539  
 540          $modulename = $modulelevel ? 'assign' : '';
 541          $categoryid = $category2->get('id');
 542          $purposeid = $purpose2->get('id');
 543          $result = external::set_context_defaults(CONTEXT_MODULE, $categoryid, $purposeid, $modulename, $override);
 544  
 545          // Extract the result.
 546          $return = external_api::clean_returnvalue(external::set_context_defaults_returns(), $result);
 547          $this->assertTrue($return['result']);
 548  
 549          // Check the assignment context instance.
 550          $instanceexists = context_instance::record_exists($assignctxinstance->get('id'));
 551          if ($override) {
 552              // The custom assign instance should have been deleted.
 553              $this->assertFalse($instanceexists);
 554          } else {
 555              // The custom assign instance should still exist.
 556              $this->assertTrue($instanceexists);
 557          }
 558  
 559          // Check the saved defaults.
 560          list($savedpurpose, $savedcategory) = \tool_dataprivacy\data_registry::get_defaults(CONTEXT_MODULE, $modulename);
 561          $this->assertEquals($categoryid, $savedcategory);
 562          $this->assertEquals($purposeid, $savedpurpose);
 563      }
 564  
 565      /**
 566       * Test for \tool_dataprivacy\external::get_category_options()
 567       * when called by a user that doesn't have the manage registry capability.
 568       */
 569      public function test_get_category_options_no_capability() {
 570          $this->resetAfterTest();
 571  
 572          $user = $this->getDataGenerator()->create_user();
 573          $this->setUser($user);
 574  
 575          $this->expectException(\required_capability_exception::class);
 576          external::get_category_options(true, true);
 577      }
 578  
 579      /**
 580       * Data provider for \tool_dataprivacy_external_testcase::test_XX_options().
 581       */
 582      public function get_options_provider() {
 583          return [
 584              [false, false],
 585              [false, true],
 586              [true, false],
 587              [true, true],
 588          ];
 589      }
 590  
 591      /**
 592       * Test for \tool_dataprivacy\external::get_category_options().
 593       *
 594       * @dataProvider get_options_provider
 595       * @param bool $includeinherit Whether "Inherit" would be included to the options.
 596       * @param bool $includenotset Whether "Not set" would be included to the options.
 597       */
 598      public function test_get_category_options($includeinherit, $includenotset) {
 599          $this->resetAfterTest();
 600          $this->setAdminUser();
 601  
 602          // Prepare our expected options.
 603          $expectedoptions = [];
 604          if ($includeinherit) {
 605              $expectedoptions[] = [
 606                  'id' => context_instance::INHERIT,
 607                  'name' => get_string('inherit', 'tool_dataprivacy'),
 608              ];
 609          }
 610  
 611          if ($includenotset) {
 612              $expectedoptions[] = [
 613                  'id' => context_instance::NOTSET,
 614                  'name' => get_string('notset', 'tool_dataprivacy'),
 615              ];
 616          }
 617  
 618          for ($i = 1; $i <= 3; $i++) {
 619              $category = api::create_category((object)['name' => 'Category ' . $i]);
 620              $expectedoptions[] = [
 621                  'id' => $category->get('id'),
 622                  'name' => $category->get('name'),
 623              ];
 624          }
 625  
 626          // Call the WS function.
 627          $result = external::get_category_options($includeinherit, $includenotset);
 628  
 629          // Extract the options.
 630          $return = (object) external_api::clean_returnvalue(external::get_category_options_returns(), $result);
 631          $options = $return->options;
 632  
 633          // Make sure everything checks out.
 634          $this->assertCount(count($expectedoptions), $options);
 635          foreach ($options as $option) {
 636              $this->assertContains($option, $expectedoptions);
 637          }
 638      }
 639  
 640      /**
 641       * Test for \tool_dataprivacy\external::get_purpose_options()
 642       * when called by a user that doesn't have the manage registry capability.
 643       */
 644      public function test_get_purpose_options_no_capability() {
 645          $this->resetAfterTest();
 646          $generator = $this->getDataGenerator();
 647          $user = $generator->create_user();
 648          $this->setUser($user);
 649          $this->expectException(\required_capability_exception::class);
 650          external::get_category_options(true, true);
 651      }
 652  
 653      /**
 654       * Test for \tool_dataprivacy\external::get_purpose_options().
 655       *
 656       * @dataProvider get_options_provider
 657       * @param bool $includeinherit Whether "Inherit" would be included to the options.
 658       * @param bool $includenotset Whether "Not set" would be included to the options.
 659       */
 660      public function test_get_purpose_options($includeinherit, $includenotset) {
 661          $this->resetAfterTest();
 662          $this->setAdminUser();
 663  
 664          // Prepare our expected options.
 665          $expectedoptions = [];
 666          if ($includeinherit) {
 667              $expectedoptions[] = [
 668                  'id' => context_instance::INHERIT,
 669                  'name' => get_string('inherit', 'tool_dataprivacy'),
 670              ];
 671          }
 672  
 673          if ($includenotset) {
 674              $expectedoptions[] = [
 675                  'id' => context_instance::NOTSET,
 676                  'name' => get_string('notset', 'tool_dataprivacy'),
 677              ];
 678          }
 679  
 680          for ($i = 1; $i <= 3; $i++) {
 681              $purpose = api::create_purpose((object)[
 682                  'name' => 'Purpose ' . $i, 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
 683              ]);
 684              $expectedoptions[] = [
 685                  'id' => $purpose->get('id'),
 686                  'name' => $purpose->get('name'),
 687              ];
 688          }
 689  
 690          // Call the WS function.
 691          $result = external::get_purpose_options($includeinherit, $includenotset);
 692  
 693          // Extract the options.
 694          $return = (object) external_api::clean_returnvalue(external::get_purpose_options_returns(), $result);
 695          $options = $return->options;
 696  
 697          // Make sure everything checks out.
 698          $this->assertCount(count($expectedoptions), $options);
 699          foreach ($options as $option) {
 700              $this->assertContains($option, $expectedoptions);
 701          }
 702      }
 703  
 704      /**
 705       * Data provider for \tool_dataprivacy_external_testcase::get_activity_options().
 706       */
 707      public function get_activity_options_provider() {
 708          return [
 709              [false, false, true],
 710              [false, true, true],
 711              [true, false, true],
 712              [true, true, true],
 713              [false, false, false],
 714              [false, true, false],
 715              [true, false, false],
 716              [true, true, false],
 717          ];
 718      }
 719  
 720      /**
 721       * Test for \tool_dataprivacy\external::get_activity_options().
 722       *
 723       * @dataProvider get_activity_options_provider
 724       * @param bool $inheritcategory Whether the category would be set to "Inherit".
 725       * @param bool $inheritpurpose Whether the purpose would be set to "Inherit".
 726       * @param bool $nodefaults Whether to fetch only activities that don't have defaults.
 727       */
 728      public function test_get_activity_options($inheritcategory, $inheritpurpose, $nodefaults) {
 729          $this->resetAfterTest();
 730          $this->setAdminUser();
 731  
 732          $category = api::create_category((object)['name' => 'Test category']);
 733          $purpose = api::create_purpose((object)[
 734              'name' => 'Test purpose ', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
 735          ]);
 736          $categoryid = $category->get('id');
 737          $purposeid = $purpose->get('id');
 738  
 739          if ($inheritcategory) {
 740              $categoryid = context_instance::INHERIT;
 741          }
 742          if ($inheritpurpose) {
 743              $purposeid = context_instance::INHERIT;
 744          }
 745  
 746          // Set the context default for the assignment module.
 747          api::set_context_defaults(CONTEXT_MODULE, $categoryid, $purposeid, 'assign');
 748  
 749          // Call the WS function.
 750          $result = external::get_activity_options($nodefaults);
 751  
 752          // Extract the options.
 753          $return = (object) external_api::clean_returnvalue(external::get_activity_options_returns(), $result);
 754          $options = $return->options;
 755  
 756          // Make sure the options list is not empty.
 757          $this->assertNotEmpty($options);
 758  
 759          $pluginwithdefaults = [
 760              'name' => 'assign',
 761              'displayname' => get_string('pluginname', 'assign')
 762          ];
 763  
 764          // If we don't want plugins with defaults to be listed or if both of the category and purpose are set to inherit,
 765          // the assign module should be listed.
 766          if (!$nodefaults || ($inheritcategory && $inheritpurpose)) {
 767              $this->assertContains($pluginwithdefaults, $options);
 768          } else {
 769              $this->assertNotContains($pluginwithdefaults, $options);
 770          }
 771      }
 772  
 773      /**
 774       * Test for external::bulk_approve_data_requests().
 775       */
 776      public function test_bulk_approve_data_requests() {
 777          $this->resetAfterTest();
 778  
 779          // Create delete data requests.
 780          $requester1 = $this->getDataGenerator()->create_user();
 781          $this->setUser($requester1->id);
 782          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 783          $requestid1 = $datarequest1->get('id');
 784  
 785          $requester2 = $this->getDataGenerator()->create_user();
 786          $this->setUser($requester2->id);
 787          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 788          $requestid2 = $datarequest2->get('id');
 789  
 790          // Approve the requests.
 791          $this->setAdminUser();
 792          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 793          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 794          $result = external::bulk_approve_data_requests([$requestid1, $requestid2]);
 795  
 796          $return = (object) external_api::clean_returnvalue(external::bulk_approve_data_requests_returns(), $result);
 797          $this->assertTrue($return->result);
 798          $this->assertEmpty($return->warnings);
 799      }
 800  
 801      /**
 802       * Test for external::bulk_approve_data_requests() for a non-existent request ID.
 803       */
 804      public function test_bulk_approve_data_requests_non_existent() {
 805          $this->resetAfterTest();
 806  
 807          $this->setAdminUser();
 808  
 809          $result = external::bulk_approve_data_requests([42]);
 810  
 811          $return = (object) external_api::clean_returnvalue(external::bulk_approve_data_requests_returns(), $result);
 812          $this->assertFalse($return->result);
 813          $this->assertCount(1, $return->warnings);
 814          $warning = reset($return->warnings);
 815          $this->assertEquals('errorrequestnotfound', $warning['warningcode']);
 816          $this->assertEquals(42, $warning['item']);
 817      }
 818  
 819      /**
 820       * Test for external::bulk_deny_data_requests() for a user without permission to deny requests.
 821       */
 822      public function test_bulk_approve_data_requests_no_permission() {
 823          $this->resetAfterTest();
 824  
 825          // Create delete data requests.
 826          $requester1 = $this->getDataGenerator()->create_user();
 827          $this->setUser($requester1->id);
 828          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 829          $requestid1 = $datarequest1->get('id');
 830  
 831          $requester2 = $this->getDataGenerator()->create_user();
 832          $this->setUser($requester2->id);
 833          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 834          $requestid2 = $datarequest2->get('id');
 835  
 836          $this->setAdminUser();
 837          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 838          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 839  
 840          // Approve the requests.
 841          $uut = $this->getDataGenerator()->create_user();
 842          $this->setUser($uut);
 843  
 844          $this->expectException(\required_capability_exception::class);
 845          $result = external::bulk_approve_data_requests([$requestid1, $requestid2]);
 846      }
 847  
 848      /**
 849       * Test for external::bulk_deny_data_requests() for a user without permission to deny requests.
 850       */
 851      public function test_bulk_approve_data_requests_own_request() {
 852          $this->resetAfterTest();
 853  
 854          // Create delete data requests.
 855          $requester1 = $this->getDataGenerator()->create_user();
 856          $this->setUser($requester1->id);
 857          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 858          $requestid1 = $datarequest1->get('id');
 859  
 860          $requester2 = $this->getDataGenerator()->create_user();
 861          $this->setUser($requester2->id);
 862          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 863          $requestid2 = $datarequest2->get('id');
 864  
 865          $this->setAdminUser();
 866          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 867          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 868  
 869          // Deny the requests.
 870          $this->setUser($requester1);
 871  
 872          $this->expectException(\required_capability_exception::class);
 873          $result = external::bulk_approve_data_requests([$requestid1]);
 874      }
 875  
 876      /**
 877       * Test for external::bulk_deny_data_requests().
 878       */
 879      public function test_bulk_deny_data_requests() {
 880          $this->resetAfterTest();
 881  
 882          // Create delete data requests.
 883          $requester1 = $this->getDataGenerator()->create_user();
 884          $this->setUser($requester1->id);
 885          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 886          $requestid1 = $datarequest1->get('id');
 887  
 888          $requester2 = $this->getDataGenerator()->create_user();
 889          $this->setUser($requester2->id);
 890          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 891          $requestid2 = $datarequest2->get('id');
 892  
 893          // Deny the requests.
 894          $this->setAdminUser();
 895          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 896          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 897          $result = external::bulk_deny_data_requests([$requestid1, $requestid2]);
 898  
 899          $return = (object) external_api::clean_returnvalue(external::bulk_approve_data_requests_returns(), $result);
 900          $this->assertTrue($return->result);
 901          $this->assertEmpty($return->warnings);
 902      }
 903  
 904      /**
 905       * Test for external::bulk_deny_data_requests() for a non-existent request ID.
 906       */
 907      public function test_bulk_deny_data_requests_non_existent() {
 908          $this->resetAfterTest();
 909  
 910          $this->setAdminUser();
 911          $result = external::bulk_deny_data_requests([42]);
 912          $return = (object) external_api::clean_returnvalue(external::bulk_approve_data_requests_returns(), $result);
 913  
 914          $this->assertFalse($return->result);
 915          $this->assertCount(1, $return->warnings);
 916          $warning = reset($return->warnings);
 917          $this->assertEquals('errorrequestnotfound', $warning['warningcode']);
 918          $this->assertEquals(42, $warning['item']);
 919      }
 920  
 921      /**
 922       * Test for external::bulk_deny_data_requests() for a user without permission to deny requests.
 923       */
 924      public function test_bulk_deny_data_requests_no_permission() {
 925          $this->resetAfterTest();
 926  
 927          // Create delete data requests.
 928          $requester1 = $this->getDataGenerator()->create_user();
 929          $this->setUser($requester1->id);
 930          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 931          $requestid1 = $datarequest1->get('id');
 932  
 933          $requester2 = $this->getDataGenerator()->create_user();
 934          $this->setUser($requester2->id);
 935          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 936          $requestid2 = $datarequest2->get('id');
 937  
 938          $this->setAdminUser();
 939          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 940          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 941  
 942          // Deny the requests.
 943          $uut = $this->getDataGenerator()->create_user();
 944          $this->setUser($uut);
 945  
 946          $this->expectException(\required_capability_exception::class);
 947          $result = external::bulk_deny_data_requests([$requestid1, $requestid2]);
 948      }
 949  
 950      /**
 951       * Test for external::bulk_deny_data_requests() for a user cannot approve their own request.
 952       */
 953      public function test_bulk_deny_data_requests_own_request() {
 954          $this->resetAfterTest();
 955  
 956          // Create delete data requests.
 957          $requester1 = $this->getDataGenerator()->create_user();
 958          $this->setUser($requester1->id);
 959          $datarequest1 = api::create_data_request($requester1->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 960          $requestid1 = $datarequest1->get('id');
 961  
 962          $requester2 = $this->getDataGenerator()->create_user();
 963          $this->setUser($requester2->id);
 964          $datarequest2 = api::create_data_request($requester2->id, api::DATAREQUEST_TYPE_DELETE, 'Example comment');
 965          $requestid2 = $datarequest2->get('id');
 966  
 967          $this->setAdminUser();
 968          api::update_request_status($requestid1, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 969          api::update_request_status($requestid2, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
 970  
 971          // Deny the requests.
 972          $this->setUser($requester1);
 973  
 974          $this->expectException(\required_capability_exception::class);
 975          $result = external::bulk_deny_data_requests([$requestid1]);
 976      }
 977  
 978      /**
 979       * Test for external::get_users(), case search using non-identity field without
 980       * facing any permission problem.
 981       *
 982       * @throws coding_exception
 983       * @throws dml_exception
 984       * @throws invalid_parameter_exception
 985       * @throws required_capability_exception
 986       * @throws restricted_context_exception
 987       */
 988      public function test_get_users_using_using_non_identity() {
 989          $this->resetAfterTest();
 990          $context = \context_system::instance();
 991          $requester = $this->getDataGenerator()->create_user();
 992          $role = $this->getDataGenerator()->create_role();
 993          role_assign($role, $requester->id, $context);
 994          assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $role, $context);
 995          $this->setUser($requester);
 996  
 997          $this->getDataGenerator()->create_user([
 998              'firstname' => 'First Student'
 999          ]);
1000          $student2 = $this->getDataGenerator()->create_user([
1001              'firstname' => 'Second Student'
1002          ]);
1003  
1004          $results = external::get_users('Second');
1005          $this->assertCount(1, $results);
1006          $this->assertEquals((object)[
1007              'id' => $student2->id,
1008              'fullname' => fullname($student2),
1009              'extrafields' => []
1010          ], $results[$student2->id]);
1011      }
1012  
1013      /**
1014       * Test for external::get_users(), case search using identity field but
1015       * don't have "moodle/site:viewuseridentity" permission.
1016       *
1017       * @throws coding_exception
1018       * @throws dml_exception
1019       * @throws invalid_parameter_exception
1020       * @throws required_capability_exception
1021       * @throws restricted_context_exception
1022       */
1023      public function test_get_users_using_identity_without_permission() {
1024          global $CFG;
1025  
1026          $this->resetAfterTest();
1027          $CFG->showuseridentity = 'institution';
1028  
1029          // Create requester user and assign correct capability.
1030          $context = \context_system::instance();
1031          $requester = $this->getDataGenerator()->create_user();
1032          $role = $this->getDataGenerator()->create_role();
1033          role_assign($role, $requester->id, $context);
1034          assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $role, $context);
1035          $this->setUser($requester);
1036  
1037          $this->getDataGenerator()->create_user([
1038              'institution' => 'University1'
1039          ]);
1040  
1041          $results = external::get_users('University1');
1042          $this->assertEmpty($results);
1043      }
1044  
1045      /**
1046       * Test for external::get_users(), case search using disabled identity field
1047       * even they have "moodle/site:viewuseridentity" permission.
1048       *
1049       * @throws coding_exception
1050       * @throws dml_exception
1051       * @throws invalid_parameter_exception
1052       * @throws required_capability_exception
1053       * @throws restricted_context_exception
1054       */
1055      public function test_get_users_using_field_not_in_identity() {
1056          $this->resetAfterTest();
1057  
1058          $context = \context_system::instance();
1059          $requester = $this->getDataGenerator()->create_user();
1060          $role = $this->getDataGenerator()->create_role();
1061          role_assign($role, $requester->id, $context);
1062          assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $role, $context);
1063          assign_capability('moodle/site:viewuseridentity', CAP_ALLOW, $role, $context);
1064          $this->setUser($requester);
1065  
1066          $this->getDataGenerator()->create_user([
1067              'institution' => 'University1'
1068          ]);
1069  
1070          $results = external::get_users('University1');
1071          $this->assertEmpty($results);
1072      }
1073  
1074      /**
1075       * Test for external::get_users(), case search using enabled identity field
1076       * with "moodle/site:viewuseridentity" permission.
1077       *
1078       * @throws coding_exception
1079       * @throws dml_exception
1080       * @throws invalid_parameter_exception
1081       * @throws required_capability_exception
1082       * @throws restricted_context_exception
1083       */
1084      public function test_get_users() {
1085          global $CFG;
1086          $this->resetAfterTest();
1087          $CFG->showuseridentity = 'institution';
1088          $context = \context_system::instance();
1089          $requester = $this->getDataGenerator()->create_user();
1090          $role = $this->getDataGenerator()->create_role();
1091          role_assign($role, $requester->id, $context);
1092          assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $role, $context);
1093          assign_capability('moodle/site:viewuseridentity', CAP_ALLOW, $role, $context);
1094          $this->setUser($requester);
1095  
1096          $student1 = $this->getDataGenerator()->create_user([
1097              'institution' => 'University1'
1098          ]);
1099          $this->getDataGenerator()->create_user([
1100              'institution' => 'University2'
1101          ]);
1102  
1103          $results = external::get_users('University1');
1104          $this->assertCount(1, $results);
1105          $this->assertEquals((object)[
1106              'id' => $student1->id,
1107              'fullname' => fullname($student1),
1108              'extrafields' => [
1109                  0 => (object)[
1110                      'name' => 'institution',
1111                      'value' => 'University1'
1112                  ]
1113              ]
1114          ], $results[$student1->id]);
1115      }
1116  }