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 310 and 402] [Versions 311 and 402] [Versions 39 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  namespace core_analytics;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  require_once (__DIR__ . '/fixtures/test_indicator_max.php');
  22  require_once (__DIR__ . '/fixtures/test_indicator_min.php');
  23  require_once (__DIR__ . '/fixtures/test_indicator_fullname.php');
  24  require_once (__DIR__ . '/fixtures/test_target_course_level_shortname.php');
  25  
  26  /**
  27   * Unit tests for the core_analytics manager.
  28   *
  29   * @package   core_analytics
  30   * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com}
  31   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   * @covers    \core_analytics\manager
  33   */
  34  class manager_test extends \advanced_testcase {
  35  
  36      /**
  37       * test_deleted_context
  38       */
  39      public function test_deleted_context() {
  40          global $DB;
  41  
  42          $this->resetAfterTest(true);
  43          $this->setAdminuser();
  44          set_config('enabled_stores', 'logstore_standard', 'tool_log');
  45  
  46          $target = \core_analytics\manager::get_target('test_target_course_level_shortname');
  47          $indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
  48          foreach ($indicators as $key => $indicator) {
  49              $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
  50          }
  51  
  52          $model = \core_analytics\model::create($target, $indicators);
  53          $modelobj = $model->get_model_obj();
  54  
  55          $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
  56          $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
  57          $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
  58          $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
  59  
  60          $model->enable('\core\analytics\time_splitting\no_splitting');
  61  
  62          $model->train();
  63          $model->predict();
  64  
  65          // Generate a prediction action to confirm that it is deleted when there is an important update.
  66          $predictions = $DB->get_records('analytics_predictions');
  67          $prediction = reset($predictions);
  68          $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
  69          $prediction->action_executed(\core_analytics\prediction::ACTION_USEFUL, $model->get_target());
  70  
  71          $predictioncontextid = $prediction->get_prediction_data()->contextid;
  72  
  73          $npredictions = $DB->count_records('analytics_predictions', array('contextid' => $predictioncontextid));
  74          $npredictionactions = $DB->count_records('analytics_prediction_actions',
  75              array('predictionid' => $prediction->get_prediction_data()->id));
  76          $nindicatorcalc = $DB->count_records('analytics_indicator_calc', array('contextid' => $predictioncontextid));
  77  
  78          \core_analytics\manager::cleanup();
  79  
  80          // Nothing is incorrectly deleted.
  81          $this->assertEquals($npredictions, $DB->count_records('analytics_predictions',
  82              array('contextid' => $predictioncontextid)));
  83          $this->assertEquals($npredictionactions, $DB->count_records('analytics_prediction_actions',
  84              array('predictionid' => $prediction->get_prediction_data()->id)));
  85          $this->assertEquals($nindicatorcalc, $DB->count_records('analytics_indicator_calc',
  86              array('contextid' => $predictioncontextid)));
  87  
  88          // Now we delete a context, the course predictions and prediction actions should be deleted.
  89          $deletedcontext = \context::instance_by_id($predictioncontextid);
  90          delete_course($deletedcontext->instanceid, false);
  91  
  92          \core_analytics\manager::cleanup();
  93  
  94          $this->assertEmpty($DB->count_records('analytics_predictions', array('contextid' => $predictioncontextid)));
  95          $this->assertEmpty($DB->count_records('analytics_prediction_actions',
  96              array('predictionid' => $prediction->get_prediction_data()->id)));
  97          $this->assertEmpty($DB->count_records('analytics_indicator_calc', array('contextid' => $predictioncontextid)));
  98  
  99          set_config('enabled_stores', '', 'tool_log');
 100          get_log_manager(true);
 101      }
 102  
 103      /**
 104       * test_deleted_analysable
 105       */
 106      public function test_deleted_analysable() {
 107          global $DB;
 108  
 109          $this->resetAfterTest(true);
 110          $this->setAdminuser();
 111          set_config('enabled_stores', 'logstore_standard', 'tool_log');
 112  
 113          $target = \core_analytics\manager::get_target('test_target_course_level_shortname');
 114          $indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
 115          foreach ($indicators as $key => $indicator) {
 116              $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
 117          }
 118  
 119          $model = \core_analytics\model::create($target, $indicators);
 120          $modelobj = $model->get_model_obj();
 121  
 122          $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
 123          $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
 124          $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
 125          $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
 126  
 127          $model->enable('\core\analytics\time_splitting\no_splitting');
 128  
 129          $model->train();
 130          $model->predict();
 131  
 132          $this->assertNotEmpty($DB->count_records('analytics_predict_samples'));
 133          $this->assertNotEmpty($DB->count_records('analytics_train_samples'));
 134          $this->assertNotEmpty($DB->count_records('analytics_used_analysables'));
 135  
 136          // Now we delete an analysable, stored predict and training samples should be deleted.
 137          $deletedcontext = \context_course::instance($coursepredict1->id);
 138          delete_course($coursepredict1, false);
 139  
 140          \core_analytics\manager::cleanup();
 141  
 142          $this->assertEmpty($DB->count_records('analytics_predict_samples', array('analysableid' => $coursepredict1->id)));
 143          $this->assertEmpty($DB->count_records('analytics_train_samples', array('analysableid' => $coursepredict1->id)));
 144          $this->assertEmpty($DB->count_records('analytics_used_analysables', array('analysableid' => $coursepredict1->id)));
 145  
 146          set_config('enabled_stores', '', 'tool_log');
 147          get_log_manager(true);
 148      }
 149  
 150      /**
 151       * Tests for the {@link \core_analytics\manager::load_default_models_for_component()} implementation.
 152       */
 153      public function test_load_default_models_for_component() {
 154          $this->resetAfterTest();
 155  
 156          // Attempting to load builtin models should always work without throwing exception.
 157          \core_analytics\manager::load_default_models_for_component('core');
 158  
 159          // Attempting to load from a core subsystem without its own subsystem directory.
 160          $this->assertSame([], \core_analytics\manager::load_default_models_for_component('core_access'));
 161  
 162          // Attempting to load from a non-existing subsystem.
 163          $this->assertSame([], \core_analytics\manager::load_default_models_for_component('core_nonexistingsubsystem'));
 164  
 165          // Attempting to load from a non-existing plugin of a known plugin type.
 166          $this->assertSame([], \core_analytics\manager::load_default_models_for_component('mod_foobarbazquaz12240996776'));
 167  
 168          // Attempting to load from a non-existing plugin type.
 169          $this->assertSame([], \core_analytics\manager::load_default_models_for_component('foo_bar2776327736558'));
 170      }
 171  
 172      /**
 173       * Tests for the {@link \core_analytics\manager::load_default_models_for_all_components()} implementation.
 174       */
 175      public function test_load_default_models_for_all_components() {
 176          $this->resetAfterTest();
 177  
 178          $models = \core_analytics\manager::load_default_models_for_all_components();
 179  
 180          $this->assertTrue(is_array($models['core']));
 181          $this->assertNotEmpty($models['core']);
 182          $this->assertNotEmpty($models['core'][0]['target']);
 183          $this->assertNotEmpty($models['core'][0]['indicators']);
 184      }
 185  
 186      /**
 187       * Tests for the successful execution of the {@link \core_analytics\manager::validate_models_declaration()}.
 188       */
 189      public function test_validate_models_declaration() {
 190          $this->resetAfterTest();
 191  
 192          // This is expected to run without an exception.
 193          $models = $this->load_models_from_fixture_file('no_teaching');
 194          \core_analytics\manager::validate_models_declaration($models);
 195      }
 196  
 197      /**
 198       * Tests for the exceptions thrown by {@link \core_analytics\manager::validate_models_declaration()}.
 199       *
 200       * @dataProvider validate_models_declaration_exceptions_provider
 201       * @param array $models Models declaration.
 202       * @param string $exception Expected coding exception message.
 203       */
 204      public function test_validate_models_declaration_exceptions(array $models, string $exception) {
 205          $this->resetAfterTest();
 206  
 207          $this->expectException(\coding_exception::class);
 208          $this->expectExceptionMessage($exception);
 209          \core_analytics\manager::validate_models_declaration($models);
 210      }
 211  
 212      /**
 213       * Data provider for the {@link self::test_validate_models_declaration_exceptions()}.
 214       *
 215       * @return array of (string)testcase => [(array)models, (string)expected exception message]
 216       */
 217      public function validate_models_declaration_exceptions_provider() {
 218          return [
 219              'missing_target' => [
 220                  $this->load_models_from_fixture_file('missing_target'),
 221                  'Missing target declaration',
 222              ],
 223              'invalid_target' => [
 224                  $this->load_models_from_fixture_file('invalid_target'),
 225                  'Invalid target classname',
 226              ],
 227              'missing_indicators' => [
 228                  $this->load_models_from_fixture_file('missing_indicators'),
 229                  'Missing indicators declaration',
 230              ],
 231              'invalid_indicators' => [
 232                  $this->load_models_from_fixture_file('invalid_indicators'),
 233                  'Invalid indicator classname',
 234              ],
 235              'invalid_time_splitting' => [
 236                  $this->load_models_from_fixture_file('invalid_time_splitting'),
 237                  'Invalid time splitting classname',
 238              ],
 239              'invalid_time_splitting_fq' => [
 240                  $this->load_models_from_fixture_file('invalid_time_splitting_fq'),
 241                  'Expecting fully qualified time splitting classname',
 242              ],
 243              'invalid_enabled' => [
 244                  $this->load_models_from_fixture_file('invalid_enabled'),
 245                  'Cannot enable a model without time splitting method specified',
 246              ],
 247          ];
 248      }
 249  
 250      /**
 251       * Loads models as declared in the given fixture file.
 252       *
 253       * @param string $filename
 254       * @return array
 255       */
 256      protected function load_models_from_fixture_file(string $filename) {
 257          global $CFG;
 258  
 259          $models = null;
 260  
 261          require($CFG->dirroot.'/analytics/tests/fixtures/db_analytics_php/'.$filename.'.php');
 262  
 263          return $models;
 264      }
 265  
 266      /**
 267       * Test the implementation of the {@link \core_analytics\manager::create_declared_model()}.
 268       */
 269      public function test_create_declared_model() {
 270          global $DB;
 271  
 272          $this->resetAfterTest();
 273          $this->setAdminuser();
 274  
 275          $declaration = [
 276              'target' => 'test_target_course_level_shortname',
 277              'indicators' => [
 278                  'test_indicator_max',
 279                  'test_indicator_min',
 280                  'test_indicator_fullname',
 281              ],
 282          ];
 283  
 284          $declarationwithtimesplitting = array_merge($declaration, [
 285              'timesplitting' => '\core\analytics\time_splitting\no_splitting',
 286          ]);
 287  
 288          $declarationwithtimesplittingenabled = array_merge($declarationwithtimesplitting, [
 289              'enabled' => true,
 290          ]);
 291  
 292          // Check that no such model exists yet.
 293          $target = \core_analytics\manager::get_target('test_target_course_level_shortname');
 294          $this->assertEquals(0, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 295          $this->assertFalse(\core_analytics\model::exists($target));
 296  
 297          // Check that the model is created.
 298          $created = \core_analytics\manager::create_declared_model($declaration);
 299          $this->assertTrue($created instanceof \core_analytics\model);
 300          $this->assertTrue(\core_analytics\model::exists($target));
 301          $this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 302          $modelid = $created->get_id();
 303  
 304          // Check that created models are disabled by default.
 305          $existing = new \core_analytics\model($modelid);
 306          $this->assertEquals(0, $existing->get_model_obj()->enabled);
 307          $this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
 308  
 309          // Let the admin enable the model.
 310          $existing->enable('\core\analytics\time_splitting\no_splitting');
 311          $this->assertEquals(1, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
 312  
 313          // Check that further calls create a new model.
 314          $repeated = \core_analytics\manager::create_declared_model($declaration);
 315          $this->assertTrue($repeated instanceof \core_analytics\model);
 316          $this->assertEquals(2, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 317  
 318          // Delete the models.
 319          $existing->delete();
 320          $repeated->delete();
 321          $this->assertEquals(0, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 322          $this->assertFalse(\core_analytics\model::exists($target));
 323  
 324          // Create it again, this time with time splitting method specified.
 325          $created = \core_analytics\manager::create_declared_model($declarationwithtimesplitting);
 326          $this->assertTrue($created instanceof \core_analytics\model);
 327          $this->assertTrue(\core_analytics\model::exists($target));
 328          $this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 329          $modelid = $created->get_id();
 330  
 331          // Even if the time splitting method was specified, the model is still not enabled automatically.
 332          $existing = new \core_analytics\model($modelid);
 333          $this->assertEquals(0, $existing->get_model_obj()->enabled);
 334          $this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
 335          $existing->delete();
 336  
 337          // Let's define the model so that it is enabled by default.
 338          $enabled = \core_analytics\manager::create_declared_model($declarationwithtimesplittingenabled);
 339          $this->assertTrue($enabled instanceof \core_analytics\model);
 340          $this->assertTrue(\core_analytics\model::exists($target));
 341          $this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
 342          $modelid = $enabled->get_id();
 343          $existing = new \core_analytics\model($modelid);
 344          $this->assertEquals(1, $existing->get_model_obj()->enabled);
 345          $this->assertEquals(1, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
 346  
 347          // Let the admin disable the model.
 348          $existing->update(0, false, false);
 349          $this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
 350      }
 351  
 352      /**
 353       * Test the implementation of the {@link \core_analytics\manager::update_default_models_for_component()}.
 354       */
 355      public function test_update_default_models_for_component() {
 356  
 357          $this->resetAfterTest();
 358          $this->setAdminuser();
 359  
 360          $noteaching = \core_analytics\manager::get_target('\core_course\analytics\target\no_teaching');
 361          $dropout = \core_analytics\manager::get_target('\core_course\analytics\target\course_dropout');
 362          $upcomingactivities = \core_analytics\manager::get_target('\core_user\analytics\target\upcoming_activities_due');
 363          $norecentaccesses = \core_analytics\manager::get_target('\core_course\analytics\target\no_recent_accesses');
 364          $noaccesssincestart = \core_analytics\manager::get_target('\core_course\analytics\target\no_access_since_course_start');
 365  
 366          $this->assertTrue(\core_analytics\model::exists($noteaching));
 367          $this->assertTrue(\core_analytics\model::exists($dropout));
 368          $this->assertTrue(\core_analytics\model::exists($upcomingactivities));
 369          $this->assertTrue(\core_analytics\model::exists($norecentaccesses));
 370          $this->assertTrue(\core_analytics\model::exists($noaccesssincestart));
 371  
 372          foreach (\core_analytics\manager::get_all_models() as $model) {
 373              $model->delete();
 374          }
 375  
 376          $this->assertFalse(\core_analytics\model::exists($noteaching));
 377          $this->assertFalse(\core_analytics\model::exists($dropout));
 378          $this->assertFalse(\core_analytics\model::exists($upcomingactivities));
 379          $this->assertFalse(\core_analytics\model::exists($norecentaccesses));
 380          $this->assertFalse(\core_analytics\model::exists($noaccesssincestart));
 381  
 382          $updated = \core_analytics\manager::update_default_models_for_component('moodle');
 383  
 384          $this->assertEquals(5, count($updated));
 385          $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
 386          $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
 387          $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
 388          $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
 389          $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
 390          $this->assertTrue(\core_analytics\model::exists($noteaching));
 391          $this->assertTrue(\core_analytics\model::exists($dropout));
 392          $this->assertTrue(\core_analytics\model::exists($upcomingactivities));
 393          $this->assertTrue(\core_analytics\model::exists($norecentaccesses));
 394          $this->assertTrue(\core_analytics\model::exists($noaccesssincestart));
 395  
 396          $repeated = \core_analytics\manager::update_default_models_for_component('moodle');
 397  
 398          $this->assertSame([], $repeated);
 399      }
 400  
 401      /**
 402       * test_get_time_splitting_methods description
 403       * @return null
 404       */
 405      public function test_get_time_splitting_methods() {
 406          $this->resetAfterTest(true);
 407  
 408          $all = \core_analytics\manager::get_all_time_splittings();
 409          $this->assertArrayHasKey('\core\analytics\time_splitting\upcoming_week', $all);
 410          $this->assertArrayHasKey('\core\analytics\time_splitting\quarters', $all);
 411  
 412          $allforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation(true);
 413          $this->assertArrayNotHasKey('\core\analytics\time_splitting\upcoming_week', $allforevaluation);
 414          $this->assertArrayHasKey('\core\analytics\time_splitting\quarters', $allforevaluation);
 415  
 416          $defaultforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation(false);
 417          $this->assertArrayNotHasKey('\core\analytics\time_splitting\upcoming_week', $defaultforevaluation);
 418          $this->assertArrayHasKey('\core\analytics\time_splitting\quarters', $defaultforevaluation);
 419  
 420          $sometimesplittings = '\core\analytics\time_splitting\single_range,' .
 421              '\core\analytics\time_splitting\tenths';
 422          set_config('defaulttimesplittingsevaluation', $sometimesplittings, 'analytics');
 423  
 424          $defaultforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation(false);
 425          $this->assertArrayNotHasKey('\core\analytics\time_splitting\quarters', $defaultforevaluation);
 426      }
 427  
 428      /**
 429       * Test the implementation of the {@link \core_analytics\manager::model_declaration_identifier()}.
 430       */
 431      public function test_model_declaration_identifier() {
 432  
 433          $noteaching1 = $this->load_models_from_fixture_file('no_teaching');
 434          $noteaching2 = $this->load_models_from_fixture_file('no_teaching');
 435          $noteaching3 = $this->load_models_from_fixture_file('no_teaching');
 436  
 437          // Same model declaration should always lead to same identifier.
 438          $this->assertEquals(
 439              \core_analytics\manager::model_declaration_identifier(reset($noteaching1)),
 440              \core_analytics\manager::model_declaration_identifier(reset($noteaching2))
 441          );
 442  
 443          // If something is changed, the identifier should change, too.
 444          $noteaching2[0]['target'] .= '_';
 445          $this->assertNotEquals(
 446              \core_analytics\manager::model_declaration_identifier(reset($noteaching1)),
 447              \core_analytics\manager::model_declaration_identifier(reset($noteaching2))
 448          );
 449  
 450          $noteaching3[0]['indicators'][] = '\core_analytics\local\indicator\binary';
 451          $this->assertNotEquals(
 452              \core_analytics\manager::model_declaration_identifier(reset($noteaching1)),
 453              \core_analytics\manager::model_declaration_identifier(reset($noteaching3))
 454          );
 455  
 456          // The identifier is supposed to contain PARAM_ALPHANUM only.
 457          $this->assertEquals(
 458              \core_analytics\manager::model_declaration_identifier(reset($noteaching1)),
 459              clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching1)), PARAM_ALPHANUM)
 460          );
 461          $this->assertEquals(
 462              \core_analytics\manager::model_declaration_identifier(reset($noteaching2)),
 463              clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching2)), PARAM_ALPHANUM)
 464          );
 465          $this->assertEquals(
 466              \core_analytics\manager::model_declaration_identifier(reset($noteaching3)),
 467              clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching3)), PARAM_ALPHANUM)
 468          );
 469      }
 470  
 471      /**
 472       * Tests for the {@link \core_analytics\manager::get_declared_target_and_indicators_instances()}.
 473       */
 474      public function test_get_declared_target_and_indicators_instances() {
 475          $this->resetAfterTest();
 476  
 477          $definition = $this->load_models_from_fixture_file('no_teaching');
 478  
 479          list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition[0]);
 480  
 481          $this->assertTrue($target instanceof \core_analytics\local\target\base);
 482          $this->assertNotEmpty($indicators);
 483          $this->assertContainsOnlyInstancesOf(\core_analytics\local\indicator\base::class, $indicators);
 484      }
 485  
 486      /**
 487       * test_get_potential_context_restrictions description
 488       */
 489      public function test_get_potential_context_restrictions() {
 490          $this->resetAfterTest();
 491  
 492          // No potential context restrictions.
 493          $this->assertFalse(\core_analytics\manager::get_potential_context_restrictions([]));
 494  
 495          $defaultcategory = \core_course_category::get_default();
 496          $defaultcategorycontext = $defaultcategory->get_context();
 497  
 498          // Include the all context levels so the misc. category get included.
 499          $this->assertEquals([
 500              $defaultcategorycontext->id => "Category: {$defaultcategory->name}",
 501          ], manager::get_potential_context_restrictions());
 502  
 503          $category = $this->getDataGenerator()->create_category(['name' => 'My category']);
 504          $categorycontext = $category->get_context();
 505  
 506          $courseone = $this->getDataGenerator()->create_course(['fullname' => 'Course one', 'shortname' => 'CS1']);
 507          $courseonecontext = \context_course::instance($courseone->id);
 508  
 509          $coursetwo = $this->getDataGenerator()->create_course(['fullname' => 'Course two', 'shortname' => 'CS2']);
 510          $coursetwocontext = \context_course::instance($coursetwo->id);
 511  
 512          // All context levels.
 513          $this->assertEqualsCanonicalizing([
 514              $defaultcategorycontext->id => "Category: {$defaultcategory->name}",
 515              $categorycontext->id => "Category: {$category->name}",
 516              $courseonecontext->id => "Course: {$courseone->shortname}",
 517              $coursetwocontext->id => "Course: {$coursetwo->shortname}",
 518          ], manager::get_potential_context_restrictions());
 519  
 520          // All category/course context levels.
 521          $this->assertEqualsCanonicalizing([
 522              $defaultcategorycontext->id => "Category: {$defaultcategory->name}",
 523              $categorycontext->id => "Category: {$category->name}",
 524              $courseonecontext->id => "Course: {$courseone->shortname}",
 525              $coursetwocontext->id => "Course: {$coursetwo->shortname}",
 526          ], manager::get_potential_context_restrictions([CONTEXT_COURSECAT, CONTEXT_COURSE]));
 527  
 528          // All category context levels.
 529          $this->assertEqualsCanonicalizing([
 530              $defaultcategorycontext->id => "Category: {$defaultcategory->name}",
 531              $categorycontext->id => "Category: {$category->name}",
 532          ], manager::get_potential_context_restrictions([CONTEXT_COURSECAT]));
 533  
 534          // Filtered category context levels.
 535          $this->assertEquals([
 536              $categorycontext->id => "Category: {$category->name}",
 537          ], manager::get_potential_context_restrictions([CONTEXT_COURSECAT], 'My cat'));
 538  
 539          $this->assertEmpty(manager::get_potential_context_restrictions([CONTEXT_COURSECAT], 'nothing'));
 540  
 541          // All course context levels.
 542          $this->assertEqualsCanonicalizing([
 543              $courseonecontext->id => "Course: {$courseone->shortname}",
 544              $coursetwocontext->id => "Course: {$coursetwo->shortname}",
 545          ], manager::get_potential_context_restrictions([CONTEXT_COURSE]));
 546  
 547          // Filtered course context levels.
 548          $this->assertEquals([
 549              $courseonecontext->id => "Course: {$courseone->shortname}",
 550          ], manager::get_potential_context_restrictions([CONTEXT_COURSE], 'one'));
 551  
 552          $this->assertEmpty(manager::get_potential_context_restrictions([CONTEXT_COURSE], 'nothing'));
 553      }
 554  }