Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 403] [Versions 39 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  /**
  18   * Unit tests for mod_survey lib
  19   *
  20   * @package    mod_survey
  21   * @category   external
  22   * @copyright  2015 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.0
  25   */
  26  namespace mod_survey;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  
  31  /**
  32   * Unit tests for mod_survey lib
  33   *
  34   * @package    mod_survey
  35   * @category   external
  36   * @copyright  2015 Juan Leyva <juan@moodle.com>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   * @since      Moodle 3.0
  39   */
  40  class lib_test extends \advanced_testcase {
  41  
  42      /**
  43       * Prepares things before this test case is initialised
  44       * @return void
  45       */
  46      public static function setUpBeforeClass(): void {
  47          global $CFG;
  48          require_once($CFG->dirroot . '/mod/survey/lib.php');
  49      }
  50  
  51      /**
  52       * Test survey_view
  53       * @return void
  54       */
  55      public function test_survey_view() {
  56          global $CFG;
  57  
  58          $CFG->enablecompletion = 1;
  59          $this->resetAfterTest();
  60  
  61          $this->setAdminUser();
  62          // Setup test data.
  63          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  64          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id),
  65                                                              array('completion' => 2, 'completionview' => 1));
  66          $context = \context_module::instance($survey->cmid);
  67          $cm = get_coursemodule_from_instance('survey', $survey->id);
  68  
  69          // Trigger and capture the event.
  70          $sink = $this->redirectEvents();
  71  
  72          survey_view($survey, $course, $cm, $context, 'form');
  73  
  74          $events = $sink->get_events();
  75          // 2 additional events thanks to completion.
  76          $this->assertCount(3, $events);
  77          $event = array_shift($events);
  78  
  79          // Checking that the event contains the expected values.
  80          $this->assertInstanceOf('\mod_survey\event\course_module_viewed', $event);
  81          $this->assertEquals($context, $event->get_context());
  82          $moodleurl = new \moodle_url('/mod/survey/view.php', array('id' => $cm->id));
  83          $this->assertEquals($moodleurl, $event->get_url());
  84          $this->assertEquals('form', $event->other['viewed']);
  85          $this->assertEventContextNotUsed($event);
  86          $this->assertNotEmpty($event->get_name());
  87          // Check completion status.
  88          $completion = new \completion_info($course);
  89          $completiondata = $completion->get_data($cm);
  90          $this->assertEquals(1, $completiondata->completionstate);
  91  
  92      }
  93  
  94      /**
  95       * Test survey_order_questions
  96       */
  97      public function test_survey_order_questions() {
  98          global $DB;
  99  
 100          $this->resetAfterTest();
 101          $course = $this->getDataGenerator()->create_course();
 102          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id));
 103  
 104          $orderedquestionids = explode(',', $survey->questions);
 105          $surveyquestions = $DB->get_records_list("survey_questions", "id", $orderedquestionids);
 106  
 107          $questionsordered = survey_order_questions($surveyquestions, $orderedquestionids);
 108  
 109          // Check one by one the correct order.
 110          for ($i = 0; $i < count($orderedquestionids); $i++) {
 111              $this->assertEquals($orderedquestionids[$i], $questionsordered[$i]->id);
 112          }
 113      }
 114  
 115      /**
 116       * Test survey_save_answers
 117       */
 118      public function test_survey_save_answers() {
 119          global $DB;
 120  
 121          $this->resetAfterTest();
 122          $this->setAdminUser();
 123  
 124          // Setup test data.
 125          $course = $this->getDataGenerator()->create_course();
 126          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id));
 127          $context = \context_module::instance($survey->cmid);
 128  
 129          // Build our questions and responses array.
 130          $realquestions = array();
 131          $questions = survey_get_questions($survey);
 132          $i = 5;
 133          foreach ($questions as $q) {
 134              if ($q->type > 0) {
 135                  if ($q->multi) {
 136                      $subquestions = survey_get_subquestions($q);
 137                      foreach ($subquestions as $sq) {
 138                          $key = 'q' . $sq->id;
 139                          $realquestions[$key] = $i % 5 + 1;
 140                          $i++;
 141                      }
 142                  } else {
 143                      $key = 'q' . $q->id;
 144                      $realquestions[$key] = $i % 5 + 1;
 145                      $i++;
 146                  }
 147              }
 148          }
 149  
 150          $sink = $this->redirectEvents();
 151          survey_save_answers($survey, $realquestions, $course, $context);
 152  
 153          // Check the stored answers, they must match.
 154          $dbanswers = $DB->get_records_menu('survey_answers', array('survey' => $survey->id), '', 'question, answer1');
 155          foreach ($realquestions as $key => $value) {
 156              $id = str_replace('q', '', $key);
 157              $this->assertEquals($value, $dbanswers[$id]);
 158          }
 159  
 160          // Check events.
 161          $events = $sink->get_events();
 162          $this->assertCount(1, $events);
 163          $event = array_shift($events);
 164  
 165          // Checking that the event contains the expected values.
 166          $this->assertInstanceOf('\mod_survey\event\response_submitted', $event);
 167          $this->assertEquals($context, $event->get_context());
 168          $this->assertEquals($survey->id, $event->other['surveyid']);
 169      }
 170  
 171      public function test_survey_core_calendar_provide_event_action() {
 172          $this->resetAfterTest();
 173          $this->setAdminUser();
 174  
 175          // Create the activity.
 176          $course = $this->getDataGenerator()->create_course();
 177          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id));
 178  
 179          // Create a calendar event.
 180          $event = $this->create_action_event($course->id, $survey->id,
 181              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 182  
 183          // Create an action factory.
 184          $factory = new \core_calendar\action_factory();
 185  
 186          // Decorate action event.
 187          $actionevent = mod_survey_core_calendar_provide_event_action($event, $factory);
 188  
 189          // Confirm the event was decorated.
 190          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 191          $this->assertEquals(get_string('view'), $actionevent->get_name());
 192          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 193          $this->assertEquals(1, $actionevent->get_item_count());
 194          $this->assertTrue($actionevent->is_actionable());
 195      }
 196  
 197      public function test_survey_core_calendar_provide_event_action_for_user() {
 198          global $CFG;
 199  
 200          $this->resetAfterTest();
 201          $this->setAdminUser();
 202  
 203          // Create the activity.
 204          $course = $this->getDataGenerator()->create_course();
 205          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id));
 206  
 207          // Create a student and enrol into the course.
 208          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 209  
 210          // Create a calendar event.
 211          $event = $this->create_action_event($course->id, $survey->id,
 212              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 213  
 214          // Now log out.
 215          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 216          $this->setUser();
 217  
 218          // Create an action factory.
 219          $factory = new \core_calendar\action_factory();
 220  
 221          // Decorate action event for the student.
 222          $actionevent = mod_survey_core_calendar_provide_event_action($event, $factory, $student->id);
 223  
 224          // Confirm the event was decorated.
 225          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 226          $this->assertEquals(get_string('view'), $actionevent->get_name());
 227          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 228          $this->assertEquals(1, $actionevent->get_item_count());
 229          $this->assertTrue($actionevent->is_actionable());
 230      }
 231  
 232      public function test_survey_core_calendar_provide_event_action_as_non_user() {
 233          global $CFG;
 234  
 235          $this->resetAfterTest();
 236          $this->setAdminUser();
 237  
 238          // Create the activity.
 239          $course = $this->getDataGenerator()->create_course();
 240          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id));
 241  
 242          // Create a calendar event.
 243          $event = $this->create_action_event($course->id, $survey->id,
 244              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 245  
 246          // Log out the user and set force login to true.
 247          \core\session\manager::init_empty_session();
 248          $CFG->forcelogin = true;
 249  
 250          // Create an action factory.
 251          $factory = new \core_calendar\action_factory();
 252  
 253          // Decorate action event.
 254          $actionevent = mod_survey_core_calendar_provide_event_action($event, $factory);
 255  
 256          // Ensure result was null.
 257          $this->assertNull($actionevent);
 258      }
 259  
 260      public function test_survey_core_calendar_provide_event_action_already_completed() {
 261          global $CFG;
 262  
 263          $this->resetAfterTest();
 264          $this->setAdminUser();
 265  
 266          $CFG->enablecompletion = 1;
 267  
 268          // Create the activity.
 269          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 270          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id),
 271              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 272  
 273          // Get some additional data.
 274          $cm = get_coursemodule_from_instance('survey', $survey->id);
 275  
 276          // Create a calendar event.
 277          $event = $this->create_action_event($course->id, $survey->id,
 278              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 279  
 280          // Mark the activity as completed.
 281          $completion = new \completion_info($course);
 282          $completion->set_module_viewed($cm);
 283  
 284          // Create an action factory.
 285          $factory = new \core_calendar\action_factory();
 286  
 287          // Decorate action event.
 288          $actionevent = mod_survey_core_calendar_provide_event_action($event, $factory);
 289  
 290          // Ensure result was null.
 291          $this->assertNull($actionevent);
 292      }
 293  
 294      public function test_survey_core_calendar_provide_event_action_already_completed_for_user() {
 295          global $CFG;
 296  
 297          $this->resetAfterTest();
 298          $this->setAdminUser();
 299  
 300          $CFG->enablecompletion = 1;
 301  
 302          // Create the activity.
 303          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 304          $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id),
 305              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 306  
 307          // Create 2 students and enrol them into the course.
 308          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 309          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 310  
 311          // Get some additional data.
 312          $cm = get_coursemodule_from_instance('survey', $survey->id);
 313  
 314          // Create a calendar event.
 315          $event = $this->create_action_event($course->id, $survey->id,
 316              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 317  
 318          // Mark the activity as completed for the $student1.
 319          $completion = new \completion_info($course);
 320          $completion->set_module_viewed($cm, $student1->id);
 321  
 322          // Now log in as $student2.
 323          $this->setUser($student2);
 324  
 325          // Create an action factory.
 326          $factory = new \core_calendar\action_factory();
 327  
 328          // Decorate action event for $student1.
 329          $actionevent = mod_survey_core_calendar_provide_event_action($event, $factory, $student1->id);
 330  
 331          // Ensure result was null.
 332          $this->assertNull($actionevent);
 333      }
 334  
 335      /**
 336       * Creates an action event.
 337       *
 338       * @param int $courseid The course id.
 339       * @param int $instanceid The instance id.
 340       * @param string $eventtype The event type.
 341       * @return bool|calendar_event
 342       */
 343      private function create_action_event($courseid, $instanceid, $eventtype) {
 344          $event = new \stdClass();
 345          $event->name = 'Calendar event';
 346          $event->modulename  = 'survey';
 347          $event->courseid = $courseid;
 348          $event->instance = $instanceid;
 349          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 350          $event->eventtype = $eventtype;
 351          $event->timestart = time();
 352  
 353          return \calendar_event::create($event);
 354      }
 355  
 356      /**
 357       * Test the callback responsible for returning the completion rule descriptions.
 358       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 359       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 360       */
 361      public function test_mod_survey_completion_get_active_rule_descriptions() {
 362          $this->resetAfterTest();
 363          $this->setAdminUser();
 364  
 365          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 366          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
 367          $survey1 = $this->getDataGenerator()->create_module('survey', [
 368              'course' => $course->id,
 369              'completion' => 2,
 370              'completionsubmit' => 1,
 371          ]);
 372          $survey2 = $this->getDataGenerator()->create_module('survey', [
 373              'course' => $course->id,
 374              'completion' => 2,
 375              'completionsubmit' => 0,
 376          ]);
 377          $cm1 = \cm_info::create(get_coursemodule_from_instance('survey', $survey1->id));
 378          $cm2 = \cm_info::create(get_coursemodule_from_instance('survey', $survey2->id));
 379  
 380          // Data for the stdClass input type.
 381          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 382          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 383          $moddefaults = new \stdClass();
 384          $moddefaults->customdata = ['customcompletionrules' => ['completionsubmit' => 1]];
 385          $moddefaults->completion = 2;
 386  
 387          $activeruledescriptions = [get_string('completionsubmit', 'survey')];
 388          $this->assertEquals(mod_survey_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 389          $this->assertEquals(mod_survey_get_completion_active_rule_descriptions($cm2), []);
 390          $this->assertEquals(mod_survey_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 391          $this->assertEquals(mod_survey_get_completion_active_rule_descriptions(new \stdClass()), []);
 392      }
 393  }