<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace mod_choice;

use core_external\external_api;
use externallib_advanced_testcase;
use mod_choice_external;

defined('MOODLE_INTERNAL') || die();

global $CFG;

require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/choice/lib.php');

/**
 * External choice functions unit tests
 *
 * @package    mod_choice
 * @category   external
 * @copyright  2015 Costantino Cito <ccito@cvaconsulting.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @covers     \mod_choice_external
 */
final class externallib_test extends externallib_advanced_testcase {

    /**
     * Test get_choice_results
     */
    public function test_get_choice_results(): void {
        global $DB;

        $this->resetAfterTest(true);

        $course = self::getDataGenerator()->create_course();
        $params = new \stdClass();
        $params->course = $course->id;
        $params->option = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza');
        $params->name = 'First Choice Activity';
        $params->showresults = CHOICE_SHOWRESULTS_AFTER_ANSWER;
        $params->publish = 1;
        $params->allowmultiple = 1;
        $params->showunanswered = 1;
        $choice = self::getDataGenerator()->create_module('choice', $params);

        $cm = get_coursemodule_from_id('choice', $choice->cmid);
        $choiceinstance = choice_get_choice($cm->instance);
        $options = array_keys($choiceinstance->option);
        $student1 = $this->getDataGenerator()->create_user();
        $student2 = $this->getDataGenerator()->create_user();
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));

        // Enroll Students in Course1.
        self::getDataGenerator()->enrol_user($student1->id,  $course->id, $studentrole->id);
        self::getDataGenerator()->enrol_user($student2->id,  $course->id, $studentrole->id);

        $this->setUser($student1);
        $myanswer = $options[2];
        choice_user_submit_response($myanswer, $choice, $student1->id, $course, $cm);
        $results = mod_choice_external::get_choice_results($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        // Create an array with optionID as Key.
        $resultsarr = array();
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        // The stundent1 is the userid who choosed the myanswer(option 3).
        $this->assertEquals($resultsarr[$myanswer][0]['userid'], $student1->id);
        // The stundent2 is the userid who didn't answered yet.
        $this->assertEquals($resultsarr[0][0]['userid'], $student2->id);

        // As Stundent2 we cannot see results (until we answered).
        $this->setUser($student2);
        $results = mod_choice_external::get_choice_results($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
        // We do not retrieve any response!
        foreach ($results['options'] as $option) {
            $this->assertCount(0, $option['userresponses']);
        }

        $timenow = time();
        // We can see results only after activity close (even if we didn't answered).
        $choice->showresults = CHOICE_SHOWRESULTS_AFTER_CLOSE;
        // Set timeopen and timeclose in the past.
        $choice->timeopen = $timenow - (60 * 60 * 24 * 3);
        $choice->timeclose = $timenow + (60 * 60 * 24 * 2);
        $DB->update_record('choice', $choice);

        $results = mod_choice_external::get_choice_results($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
        // We do not retrieve any response (activity is still open).
        foreach ($results['options'] as $option) {
            $this->assertCount(0, $option['userresponses']);
        }

        // We close the activity (setting timeclose in the past).
        $choice->timeclose = $timenow - (60 * 60 * 24 * 2);
        $DB->update_record('choice', $choice);
        // Now as Stundent2 we will see results!
        $results = mod_choice_external::get_choice_results($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
        // Create an array with optionID as Key.
        $resultsarr = array();
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        // The stundent1 is the userid who choosed the myanswer(option 3).
        $this->assertEquals($resultsarr[$myanswer][0]['userid'], $student1->id);
        // The stundent2 is the userid who didn't answered yet.
        $this->assertEquals($resultsarr[0][0]['userid'], $student2->id);

        // Do not publish user names!
        $choice->publish = 0;
        $DB->update_record('choice', $choice);
        $results = mod_choice_external::get_choice_results($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
        // Create an array with optionID as Key.
        $resultsarr = array();
        // Does not show any user response!
        foreach ($results['options'] as $option) {
            $this->assertCount(0, $option['userresponses']);
            $resultsarr[$option['id']] = $option;
        }
        // But we can see totals and percentages.
        $this->assertEquals(1, $resultsarr[$myanswer]['numberofuser']);
    }

    /**
     * Test get_choice_results using groups.
     */
    public function test_get_choice_results_with_groups(): void {
        $this->resetAfterTest(true);

        $course = $this->getDataGenerator()->create_course();
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
        $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');

        // Create 3 choices: one with separate groups, one with visible groups and one with no groups.
        $commonparams = [
            'course' => $course->id,
            'option' => ['fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'],
            'name' => 'Separate groups choice',
            'showresults' => CHOICE_SHOWRESULTS_ALWAYS,
            'publish' => 1,
        ];

        $nogroupschoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => NOGROUPS]));
        $separatechoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => SEPARATEGROUPS]));
        $visiblechoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => VISIBLEGROUPS]));
        $nogroupsoptions = array_keys(choice_get_choice($nogroupschoice->id)->option);
        $separateoptions = array_keys(choice_get_choice($separatechoice->id)->option);
        $visibleoptions = array_keys(choice_get_choice($visiblechoice->id)->option);
        $nogroupscm = get_coursemodule_from_id('choice', $nogroupschoice->cmid);
        $separatecm = get_coursemodule_from_id('choice', $separatechoice->cmid);
        $visiblecm = get_coursemodule_from_id('choice', $visiblechoice->cmid);

        // Enrol 3 students in the course. One student will have no group, the others will belong one to each group.
        $student1 = $this->getDataGenerator()->create_user();
        $student2 = $this->getDataGenerator()->create_user();
        $studentnogroup = $this->getDataGenerator()->create_user();

        self::getDataGenerator()->enrol_user($student1->id,  $course->id, 'student');
        self::getDataGenerator()->enrol_user($student2->id,  $course->id, 'student');
        self::getDataGenerator()->enrol_user($studentnogroup->id,  $course->id, 'student');

        groups_add_member($group1, $student1);
        groups_add_member($group2, $student2);

        // Add answers for both users in all choices.
        $this->setUser($student1);
        choice_user_submit_response($nogroupsoptions[0], $nogroupschoice, $student1->id, $course, $nogroupscm);
        choice_user_submit_response($separateoptions[0], $separatechoice, $student1->id, $course, $separatecm);
        choice_user_submit_response($visibleoptions[0], $visiblechoice, $student1->id, $course, $visiblecm);

        $this->setUser($student2);
        choice_user_submit_response($nogroupsoptions[1], $nogroupschoice, $student2->id, $course, $nogroupscm);
        choice_user_submit_response($separateoptions[1], $separatechoice, $student2->id, $course, $separatecm);
        choice_user_submit_response($visibleoptions[1], $visiblechoice, $student2->id, $course, $visiblecm);

        // No groups: check that the groupid parameter is ignored.
        $results = mod_choice_external::get_choice_results($nogroupschoice->id, $group1->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$nogroupsoptions[0]][0]['userid'], $student1->id);
        $this->assertEquals($resultsarr[$nogroupsoptions[1]][0]['userid'], $student2->id);

        $results = mod_choice_external::get_choice_results($nogroupschoice->id, $group2->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$nogroupsoptions[0]][0]['userid'], $student1->id);
        $this->assertEquals($resultsarr[$nogroupsoptions[1]][0]['userid'], $student2->id);

        // Separate groups: check that students can only see results of the group they belong.
        $this->setUser($student1);
        $results = mod_choice_external::get_choice_results($separatechoice->id, $group1->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$separateoptions[0]][0]['userid'], $student1->id);
        $this->assertCount(0, $resultsarr[$separateoptions[1]]); // The answer for the group 2 is not returned.

        try {
            mod_choice_external::get_choice_results($separatechoice->id, $group2->id);
            $this->fail('Exception expected due to not visible group.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('notingroup', $e->errorcode);
        }

        try {
            mod_choice_external::get_choice_results($separatechoice->id, 0); // All participants also throws error.
            $this->fail('Exception expected due to not visible group.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('notingroup', $e->errorcode);
        }

        $this->setUser($student2);
        $results = mod_choice_external::get_choice_results($separatechoice->id, $group2->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$separateoptions[1]][0]['userid'], $student2->id);
        $this->assertCount(0, $resultsarr[$separateoptions[0]]); // The answer for the group 1 is not returned.

        try {
            mod_choice_external::get_choice_results($separatechoice->id, $group1->id);
            $this->fail('Exception expected due to not visible group.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('notingroup', $e->errorcode);
        }

        // Visible groups: check that students can see results of all groups, including 'All participants'.
        $this->setUser($student1);
        $results = mod_choice_external::get_choice_results($visiblechoice->id, $group1->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
        $this->assertCount(0, $resultsarr[$visibleoptions[1]]); // The answer for the other group is not returned.

        $results = mod_choice_external::get_choice_results($visiblechoice->id, $group2->id);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);
        $this->assertCount(0, $resultsarr[$visibleoptions[0]]); // The answer for the other group is not returned.

        $results = mod_choice_external::get_choice_results($visiblechoice->id, 0);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
        $this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);

        // User with no groups can view results for the visible groups choice, but not for separate groups.
        $this->setUser($studentnogroup);
        $results = mod_choice_external::get_choice_results($visiblechoice->id, 0);
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);

        $resultsarr = [];
        foreach ($results['options'] as $option) {
            $resultsarr[$option['id']] = $option['userresponses'];
        }
        $this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
        $this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);

        try {
            mod_choice_external::get_choice_results($separatechoice->id, 0);
            $this->fail('Exception expected due to not visible group.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('notingroup', $e->errorcode);
        }
    }

    /**
     * Test get_choice_options
     */
    public function test_get_choice_options(): void {
        global $DB;

        // Warningcodes.
        $notopenyet = 1;
        $previewonly = 2;
        $expired = 3;

        $this->resetAfterTest(true);
        $timenow = time();
        $timeopen = $timenow + (60 * 60 * 24 * 2);
        $timeclose = $timenow + (60 * 60 * 24 * 7);
        $course = self::getDataGenerator()->create_course();
        $possibleoptions = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza');
        $params = array();
        $params['course'] = $course->id;
        $params['option'] = $possibleoptions;
        $params['name'] = 'First Choice Activity';
        $params['showpreview'] = 0;

        $generator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
        $choice = $generator->create_instance($params);

        $student1 = $this->getDataGenerator()->create_user();
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
        // Enroll Students in Course.
        self::getDataGenerator()->enrol_user($student1->id,  $course->id, $studentrole->id);
        $this->setUser($student1);

        $results = mod_choice_external::get_choice_options($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results);
        // We should retrieve all options.
        $this->assertCount(count($possibleoptions), $results['options']);

        // Here we force timeopen/close in the future.
        $choice->timeopen = $timeopen;
        $choice->timeclose = $timeclose;
        $DB->update_record('choice', $choice);

        $results = mod_choice_external::get_choice_options($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results);
        // We should retrieve no options.
        $this->assertCount(0, $results['options']);
        $this->assertEquals($notopenyet, $results['warnings'][0]['warningcode']);

        // Here we see the options because of preview!
        $choice->showpreview = 1;
        $DB->update_record('choice', $choice);
        $results = mod_choice_external::get_choice_options($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results);
        // We should retrieve all options.
        $this->assertCount(count($possibleoptions), $results['options']);

        foreach ($results['options'] as $option) {
            // Each option is disabled as this is only the preview!
            $this->assertEquals(1, $option['disabled']);
        }
        $warnings = array();
        foreach ($results['warnings'] as $warning) {
            $warnings[$warning['warningcode']] = $warning['message'];
        }
        $this->assertTrue(isset($warnings[$previewonly]));
        $this->assertTrue(isset($warnings[$notopenyet]));

        // Simulate activity as opened!
        $choice->timeopen = $timenow - (60 * 60 * 24 * 3);
        $choice->timeclose = $timenow + (60 * 60 * 24 * 2);
        $DB->update_record('choice', $choice);
        $cm = get_coursemodule_from_id('choice', $choice->cmid);
        $choiceinstance = choice_get_choice($cm->instance);
        $optionsids = array_keys($choiceinstance->option);
        $myanswerid = $optionsids[2];
        choice_user_submit_response($myanswerid, $choice, $student1->id, $course, $cm);

        $results = mod_choice_external::get_choice_options($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results);
        // We should retrieve all options.
        $this->assertCount(count($possibleoptions), $results['options']);
        foreach ($results['options'] as $option) {
            // When we answered and we cannot update our choice.
            if ($option['id'] == $myanswerid and !$choice->allowupdate) {
                $this->assertEquals(1, $option['disabled']);
                $this->assertEquals(1, $option['checked']);
            } else {
                $this->assertEquals(0, $option['disabled']);
            }
        }

        // Set timeopen and timeclose as older than today!
        // We simulate what happens when the activity is closed.
        $choice->timeopen = $timenow - (60 * 60 * 24 * 3);
        $choice->timeclose = $timenow - (60 * 60 * 24 * 2);
        $DB->update_record('choice', $choice);
        $results = mod_choice_external::get_choice_options($choice->id);
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results);
        // We should retrieve no options.
        $this->assertCount(0, $results['options']);
        $this->assertEquals($expired, $results['warnings'][0]['warningcode']);

    }

    /**
     * Test submit_choice_response
     */
    public function test_submit_choice_response(): void {
        global $DB;

        $this->resetAfterTest(true);

        $course = self::getDataGenerator()->create_course();
        $params = new \stdClass();
        $params->course = $course->id;
        $params->option = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza');
        $params->name = 'First Choice Activity';
        $params->showresults = CHOICE_SHOWRESULTS_ALWAYS;
        $params->allowmultiple = 1;
        $params->showunanswered = 1;
        $choice = self::getDataGenerator()->create_module('choice', $params);
        $cm = get_coursemodule_from_id('choice', $choice->cmid);
        $choiceinstance = choice_get_choice($cm->instance);
        $options = array_keys($choiceinstance->option);
        $student1 = $this->getDataGenerator()->create_user();
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));

        // Enroll Students in Course1.
        self::getDataGenerator()->enrol_user($student1->id,  $course->id, $studentrole->id);

        $this->setUser($student1);
        $myresponse = $options[2];
        $results = mod_choice_external::submit_choice_response($choice->id, array($myresponse));
        // We need to execute the return values cleaning process to simulate the web service server.
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);
        $myanswers = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $student1->id));
        $myanswer = reset($myanswers);
        $this->assertEquals($results['answers'][0]['id'], $myanswer->id);
        $this->assertEquals($results['answers'][0]['choiceid'], $myanswer->choiceid);
        $this->assertEquals($results['answers'][0]['userid'], $myanswer->userid);
        $this->assertEquals($results['answers'][0]['timemodified'], $myanswer->timemodified);
    }

    /**
     * Test view_choice
     */
    public function test_view_choice(): void {
        global $DB;

        $this->resetAfterTest(true);

        // Setup test data.
        $course = $this->getDataGenerator()->create_course();
        $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
        $context = \context_module::instance($choice->cmid);
        $cm = get_coursemodule_from_instance('choice', $choice->id);

        // Test invalid instance id.
        try {
            mod_choice_external::view_choice(0);
            $this->fail('Exception expected due to invalid mod_choice instance id.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('invalidcoursemodule', $e->errorcode);
        }

        // Test not-enrolled user.
        $user = self::getDataGenerator()->create_user();
        $this->setUser($user);
        try {
            mod_choice_external::view_choice($choice->id);
            $this->fail('Exception expected due to not enrolled user.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('requireloginerror', $e->errorcode);
        }

        // Test user with full capabilities.
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);

        // Trigger and capture the event.
        $sink = $this->redirectEvents();

        $result = mod_choice_external::view_choice($choice->id);
        $result = external_api::clean_returnvalue(mod_choice_external::view_choice_returns(), $result);

        $events = $sink->get_events();
        $this->assertCount(1, $events);
        $event = array_shift($events);

        // Checking that the event contains the expected values.
        $this->assertInstanceOf('\mod_choice\event\course_module_viewed', $event);
        $this->assertEquals($context, $event->get_context());
        $moodlechoice = new \moodle_url('/mod/choice/view.php', array('id' => $cm->id));
        $this->assertEquals($moodlechoice, $event->get_url());
        $this->assertEventContextNotUsed($event);
        $this->assertNotEmpty($event->get_name());

    }

    /**
     * Test get_choices_by_courses
     */
    public function test_get_choices_by_courses(): void {
        global $DB;
        $this->resetAfterTest(true);
        // As admin.
        $this->setAdminUser();
        $course1 = self::getDataGenerator()->create_course();
        $choiceoptions1 = array(
          'course' => $course1->id,
          'name' => 'First IMSCP'
        );
        $choice1 = self::getDataGenerator()->create_module('choice', $choiceoptions1);
        $course2 = self::getDataGenerator()->create_course();

        $choiceoptions2 = array(
          'course' => $course2->id,
          'name' => 'Second IMSCP'
        );
        $choice2 = self::getDataGenerator()->create_module('choice', $choiceoptions2);
        $student1 = $this->getDataGenerator()->create_user();

        $studentrole = $DB->get_record('role', array('shortname' => 'student'));

        // Enroll Student1 in Course1.
        self::getDataGenerator()->enrol_user($student1->id,  $course1->id, $studentrole->id);

        $this->setUser($student1);
        $choices = mod_choice_external::get_choices_by_courses(array());
        $choices = external_api::clean_returnvalue(mod_choice_external::get_choices_by_courses_returns(), $choices);
        $this->assertCount(1, $choices['choices']);
        $this->assertEquals('First IMSCP', $choices['choices'][0]['name']);
        // As Student you cannot see some IMSCP properties like 'section'.
        $this->assertFalse(isset($choices['choices'][0]['section']));

        // Student1 is not enrolled in this Course.
        // The webservice will give a warning!
        $choices = mod_choice_external::get_choices_by_courses(array($course2->id));
        $choices = external_api::clean_returnvalue(mod_choice_external::get_choices_by_courses_returns(), $choices);
        $this->assertCount(0, $choices['choices']);
        $this->assertEquals(1, $choices['warnings'][0]['warningcode']);

        // Now as admin.
        $this->setAdminUser();
        // As Admin we can see this IMSCP.
        $choices = mod_choice_external::get_choices_by_courses(array($course2->id));
        $choices = external_api::clean_returnvalue(mod_choice_external::get_choices_by_courses_returns(), $choices);
        $this->assertCount(1, $choices['choices']);
        $this->assertEquals('Second IMSCP', $choices['choices'][0]['name']);
        // As an Admin you can see some IMSCP properties like 'section'.
        $this->assertEquals(0, $choices['choices'][0]['section']);

        // Now, prohibit capabilities.
        $this->setUser($student1);
        $contextcourse1 = \context_course::instance($course1->id);
        // Prohibit capability = mod:choice:choose on Course1 for students.
        assign_capability('mod/choice:choose', CAP_PROHIBIT, $studentrole->id, $contextcourse1->id);
        accesslib_clear_all_caches_for_unit_testing();

        $choices = mod_choice_external::get_choices_by_courses(array($course1->id));
        $choices = external_api::clean_returnvalue(mod_choice_external::get_choices_by_courses_returns(), $choices);
        $this->assertFalse(isset($choices['choices'][0]['timeopen']));
    }

    /**
     * Test delete_choice_responses
     */
    public function test_delete_choice_responses(): void {
        global $DB;

        $this->resetAfterTest(true);

        $course = self::getDataGenerator()->create_course();
        $params = new \stdClass();
        $params->course = $course->id;
        $params->option = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza');
        $params->name = 'First Choice Activity';
        $params->showresults = CHOICE_SHOWRESULTS_ALWAYS;
        $params->allowmultiple = 1;
        $params->showunanswered = 1;
        $choice = self::getDataGenerator()->create_module('choice', $params);
        $cm = get_coursemodule_from_id('choice', $choice->cmid);

        $choiceinstance = choice_get_choice($cm->instance);
        $options = array_keys($choiceinstance->option);

        $student = $this->getDataGenerator()->create_user();
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));

        // Enroll student in Course1.
        self::getDataGenerator()->enrol_user($student->id,  $course->id, $studentrole->id);

        $this->setUser($student);
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);

        $myresponses = array_keys(choice_get_my_response($choice));

        // Try to delete responses when allow update is false.
        try {
            mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[0]));
            $this->fail('Exception expected due to missing permissions.');
        } catch (\required_capability_exception $e) {
            $this->assertEquals('nopermissions', $e->errorcode);
        }

        // Set allow update to true, and a passed time close.
        $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id));
        $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id));
        try {
            mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1]));
            $this->fail('Exception expected due to expired choice.');
        } catch (\moodle_exception $e) {
            $this->assertEquals('expired', $e->errorcode);
        }

        // Reset time close. We should be able now to delete all the responses.
        $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id));
        $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1]));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);

        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);
        // Now, in the DB 0 responses.
        $this->assertCount(0, choice_get_my_response($choice));

        // Submit again the responses.
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);

        $myresponses = array_keys(choice_get_my_response($choice));
        // Delete only one response.
        $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0]));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);
        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);
        // Now, in the DB 1 response still.
        $this->assertCount(1, choice_get_my_response($choice));

        // Delete the remaining response, passing 2 invalid responses ids.
        $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[1], $myresponses[0] + 2,
                                                                $myresponses[0] + 3));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);
        $this->assertTrue($results['status']);
        // 2 warnings, 2 invalid responses.
        $this->assertCount(2, $results['warnings']);
        // Now, in the DB 0 responses.
        $this->assertCount(0, choice_get_my_response($choice));

        // Now, as an admin we must be able to delete all the responses under any condition.
        $this->setUser($student);
        // Submit again the responses.
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);
        $studentresponses = array_keys(choice_get_my_response($choice));

        $this->setAdminUser();
        $DB->set_field('choice', 'allowupdate', 0, array('id' => $choice->id));
        $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id));

        $results = mod_choice_external::delete_choice_responses($choice->id, array($studentresponses[0], $studentresponses[1]));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);

        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);

        // Submit again the responses.
        $this->setUser($student);
        $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id));
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);

        // Test admin try to delete his own responses (he didn't respond so nothing should be deleted).
        $this->setAdminUser();
        $results = mod_choice_external::delete_choice_responses($choice->id);
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);
        $this->assertFalse($results['status']);
        $this->assertCount(0, $results['warnings']);
        $allresponses = choice_get_all_responses($choice);
        $this->assertCount(2, $allresponses);   // No responses deleted (admin didn't submit any).

        // Now admin submit a couple of responses more.
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);
        $allresponses = choice_get_all_responses($choice);
        $this->assertCount(4, $allresponses);
        // Admin responses are deleted when passing an empty array.
        $results = mod_choice_external::delete_choice_responses($choice->id);
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);
        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);
        $allresponses = choice_get_all_responses($choice);
        $this->assertCount(2, $allresponses);

        // Now admin will delete all the other users responses.
        $results = mod_choice_external::delete_choice_responses($choice->id, array_keys($allresponses));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);

        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);
        $allresponses = choice_get_all_responses($choice);
        $this->assertCount(0, $allresponses);   // Now all the responses were deleted.

        // Admin try do delete an invalid response.
        $results = mod_choice_external::delete_choice_responses($choice->id, array(-1));
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);

        $this->assertFalse($results['status']);
        $this->assertCount(1, $results['warnings']);

        // Now, in the DB 0 responses.
        $this->setUser($student);

        // Submit again respones.
        $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id));
        $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id));
        $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2]));
        $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results);

        // Delete all responses.
        $results = mod_choice_external::delete_choice_responses($choice->id);
        $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results);

        $this->assertTrue($results['status']);
        $this->assertCount(0, $results['warnings']);
        $this->assertCount(0, choice_get_my_response($choice));

    }
}
