first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
@core @core_user
Feature: Add blocks to my profile page
In order to add more functionality to my profile page
As a user
I need to add blocks to my profile page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
And I log in as "admin"
And I follow "View profile"
Scenario: Add blocks to page
When I turn editing mode on
And I add the "Latest announcements" block
Then I should see "Latest announcements"
+17
View File
@@ -0,0 +1,17 @@
@core @core_user
Feature: Manually create a user
In order create a user properly
As an admin
I need to be able to add new users and edit their fields.
Scenario: Change default language for a new user
Given I log in as "admin"
When I navigate to "Users > Accounts > Add a new user" in site administration
Then I should see "Preferred language"
Scenario: Language not displayed when editing an existing user
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
When I am on the "student1" "user > editing" page logged in as "admin"
Then I should not see "Preferred language"
+143
View File
@@ -0,0 +1,143 @@
<?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/>.
/**
* User steps definition.
*
* @package core_user
* @category test
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ExpectationException as ExpectationException;
/**
* Steps definitions for users.
*
* @package core_user
* @category test
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_user extends behat_base {
/**
* Choose from the bulk action menu.
*
* @Given /^I choose "(?P<nodetext_string>(?:[^"]|\\")*)" from the participants page bulk action menu$/
* @param string $nodetext The menu item to select.
*/
public function i_choose_from_the_participants_page_bulk_action_menu($nodetext) {
$this->execute("behat_forms::i_set_the_field_to", [
"With selected users...",
$this->escape($nodetext)
]);
}
/**
* The input field should have autocomplete set to this value.
*
* @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" should have purpose "(?P<purpose_string>(?:[^"]|\\")*)"$/
* @param string $field The field to select.
* @param string $purpose The expected purpose.
*/
public function the_field_should_have_purpose($field, $purpose) {
$fld = behat_field_manager::get_form_field_from_label($field, $this);
$value = $fld->get_attribute('autocomplete');
if ($value != $purpose) {
$reason = 'The "' . $field . '" field does not have purpose "' . $purpose . '"';
throw new ExpectationException($reason, $this->getSession());
}
}
/**
* The input field should not have autocomplete set to this value.
*
* @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" should not have purpose "(?P<purpose_string>(?:[^"]|\\")*)"$/
* @param string $field The field to select.
* @param string $purpose The expected purpose we do not want.
*/
public function the_field_should_not_have_purpose($field, $purpose) {
$fld = behat_field_manager::get_form_field_from_label($field, $this);
$value = $fld->get_attribute('autocomplete');
if ($value == $purpose) {
throw new ExpectationException('The "' . $field . '" field does have purpose "' . $purpose . '"', $this->getSession());
}
}
/**
* Convert page names to URLs for steps like 'When I am on the "[page name]" page'.
*
* Recognised page names are:
* | Page name | Description |
* | Contact Site Support | The Contact Site Support page (user/contactsitesupport.php) |
*
* @param string $page name of the page, with the component name removed e.g. 'Admin notification'.
* @return moodle_url the corresponding URL.
* @throws Exception with a meaningful error message if the specified page cannot be found.
*/
protected function resolve_page_url(string $page): moodle_url {
switch (strtolower($page)) {
case 'contact site support':
return new moodle_url('/user/contactsitesupport.php');
default:
throw new Exception("Unrecognised core_user page type '{$page}'.");
}
}
/**
* Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
*
* Recognised page names are:
* | Page Type | Identifier meaning | Description |
* | editing | username or email | User editing page (/user/editadvanced.php) |
* | profile | username or email | User profile page (/user/profile.php) |
*
* @param string $type identifies which type of page this is, e.g. 'Editing'.
* @param string $identifier identifies the user, e.g. 'student1'.
* @return moodle_url the corresponding URL.
* @throws Exception with a meaningful error message if the specified page cannot be found.
*/
protected function resolve_page_instance_url(string $type, string $identifier): moodle_url {
switch (strtolower($type)) {
case 'editing':
$userid = $this->get_user_id_by_identifier($identifier);
if (!$userid) {
throw new Exception('The specified user with username or email "' .
$identifier . '" does not exist');
}
return new moodle_url('/user/editadvanced.php', ['id' => $userid]);
case 'profile':
$userid = $this->get_user_id_by_identifier($identifier);
if (!$userid) {
throw new Exception('The specified user with username or email "' . $identifier . '" does not exist');
}
return new moodle_url('/user/profile.php', ['id' => $userid]);
default:
throw new Exception("Unrecognised page type '{$type}'.");
}
}
}
@@ -0,0 +1,91 @@
@core @core_user
Feature: Bulk enrolments
In order to manage a course site
As a teacher
I need to be able to bulk edit enrolments
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "cohorts" exist:
| name | idnumber |
| Cohort | cohortid1 |
@javascript
Scenario: Bulk edit enrolments
When I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Select all" "checkbox"
And I set the field "With selected users..." to "Edit selected user enrolments"
And I set the field "Alter status" to "Suspended"
And I press "Save changes"
Then I should see "Suspended" in the "Teacher 1" "table_row"
And I should see "Suspended" in the "Student 1" "table_row"
And I should see "Suspended" in the "Student 2" "table_row"
@javascript
Scenario: Bulk delete enrolments
When I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Select all" "checkbox"
And I set the field "With selected users..." to "Delete selected user enrolments"
And I press "Unenrol users"
Then I should not see "Student 1"
And I should not see "Student 2"
And I should not see "Teacher 1"
And I should see "3 unenrolled users"
@javascript
Scenario: Bulk delete enrolments when user is themselves enrolled
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
# Select all three users (the teacher themselves and both students).
And I click on "Select all" "checkbox"
And I set the field "With selected users..." to "Delete selected user enrolments"
# Teacher is informed that they've been removed from current selection.
Then I should see "User \"Teacher 1\" was removed from the selection."
And the following should exist in the "generaltable" table:
| Name | Status |
| Student 1 | Active |
| Student 2 | Active |
And I should not see "Teacher 1" in the "generaltable" "table"
And I press "Unenrol users"
And I should see "2 unenrolled users"
And I should see "Teacher 1" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
@javascript
Scenario: Bulk edit enrolment for deleted user
When I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "Student 1"
And I press "Add to selection"
And I set the field "Available" to "Student 2"
And I press "Add to selection"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I press "Delete" action in the "Student 1" report row
And I click on "Delete" "button" in the "Delete user" "dialogue"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "id_action" to "Add to cohort"
And I press "Go"
And I set the field "id_cohort" to "Cohort [cohortid1]"
And I press "Add to cohort"
And I navigate to "Users > Accounts > Cohorts" in site administration
And I press "Assign" action in the "cohortid1" report row
Then the "removeselect" select box should contain "Student 2 (student2@example.com)"
And the "removeselect" select box should not contain "Student 1 (student1@example.com)"
+35
View File
@@ -0,0 +1,35 @@
@core @core_user @javascript
Feature: Bulk message
In order to communicate with my students
As a teacher
I need to be able to send a message to all my students
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
Scenario: Send a message to students from participants list
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Select all" "checkbox"
And I set the field "With selected users..." to "Send a message"
And "Send message to 3 people" "dialogue" should exist
# Try to send an empty message.
When I press "Send message to 3 people"
Then I should see "Please enter message text"
And I set the following fields to these values:
| bulk-message | "Hello world!" |
And I press "Send message to 3 people"
And I should see "Message sent to 3 people"
@@ -0,0 +1,107 @@
@core @core_user
Feature: Contact site support method and availability can be customised
In order to effectively support people using my Moodle site
As an admin
I need to be able to configure the site support method and who has access to it
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | One | user1@example.com |
Scenario: Contact site support can be made available to all site visitors
Given the following config values are set as admin:
| supportavailability | 2 |
# Confirm unauthenticated visitor has access to the contact form.
When I am on site homepage
Then I should see "Contact site support" in the "page-footer" "region"
And I click on "Contact site support" "link" in the "page-footer" "region"
And I should see "Contact site support" in the "page-header" "region"
# Confirm someone logged in as guest has access to the contact form.
And I log in as "guest"
And I should see "Contact site support" in the "page-footer" "region"
And I click on "Contact site support" "link" in the "page-footer" "region"
And I should see "Contact site support" in the "page-header" "region"
And I log out
# Confirm logged in user has access to the contact form.
And I log in as "user1"
And I should see "Contact site support" in the "page-footer" "region"
And I click on "Contact site support" "link" in the "page-footer" "region"
And I should see "Contact site support" in the "page-header" "region"
Scenario: Contact site support can be limited to authenticated users
Given the following config values are set as admin:
| supportavailability | 1 |
# Confirm unauthenticated visitor cannot see the option or directly access the page.
When I am on site homepage
Then I should not see "Contact site support" in the "page-footer" "region"
And I am on the "user > Contact Site Support" page
And I should see "Acceptance test site" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
# Confirm someone logged in as guest cannot see the option or directly access the page.
And I log in as "guest"
And I should not see "Contact site support" in the "page-footer" "region"
And I am on the "user > Contact Site Support" page
And I should see "Acceptance test site" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
And I log out
# Confirm logged in user has access to the contact form.
And I log in as "user1"
And I should see "Contact site support" in the "page-footer" "region"
And I click on "Contact site support" "link" in the "page-footer" "region"
And I should see "Contact site support" in the "page-header" "region"
Scenario: Contact site support can be disabled
Given the following config values are set as admin:
| supportavailability | 0 |
| defaulthomepage | home |
# Confirm unauthenticated visitor cannot see the option.
When I am on site homepage
Then I should not see "Contact site support" in the "page-footer" "region"
# Confirm someone logged in as guest cannot see the option.
And I log in as "guest"
And I should not see "Contact site support" in the "page-footer" "region"
And I log out
# Confirm logged in user cannot see the option.
And I log in as "user1"
And I should not see "Contact site support" in the "page-footer" "region"
And I log out
# Confirm admin cannot see the option.
And I log in as "admin"
And I should not see "Contact site support" in the "page-footer" "region"
# Confirm visiting the contact form directly without permission redirects to the homepage.
And I am on the "user > Contact Site Support" page
And I should see "Acceptance test site" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
@javascript
Scenario: Contact site support link opens a custom support page URL if set
Given the following config values are set as admin:
| supportavailability | 1 |
| supportpage | user/profile.php |
When I log in as "user1"
And I am on site homepage
And I click on "Contact site support" "link" in the "page-footer" "region"
And I switch to a second window
Then I should see "User One" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
And I close all opened windows
Scenario: Visiting the contact site support page directly will redirect to the custom support page if set
Given the following config values are set as admin:
| supportavailability | 2 |
| supportpage | profile.php |
When I log in as "user1"
And I am on the "user > Contact Site Support" page
Then I should see "User One" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
Scenario: Visiting the contact site support page still redirects to homepage if access to support is disabled
Given the following config values are set as admin:
| supportavailability | 0 |
| supportpage | profile.php |
| defaulthomepage | home |
When I log in as "user1"
And I am on the "user > Contact Site Support" page
Then I should see "Acceptance test site" in the "page-header" "region"
And I should not see "Contact site support" in the "page-header" "region"
@@ -0,0 +1,268 @@
@core @core_user
Feature: Custom profile fields should be visible and editable by those with the correct permissions.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| userwithinformation | userwithinformation | 1 | userwithinformation@example.com |
And the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| userwithinformation | C1 | student |
And the following config values are set as admin:
| registerauth | email |
And the following "custom profile fields" exist:
| datatype | shortname | name | signup | visible |
| text | notvisible_field | notvisible_field | 1 | 0 |
| text | uservisible_field | uservisible_field | 1 | 1 |
| text | everyonevisible_field | everyonevisible_field | 0 | 2 |
| text | teachervisible_field | teachervisible_field | 1 | 3 |
And I am on the "userwithinformation" "user > editing" page logged in as "admin"
And I set the following fields to these values:
| notvisible_field | notvisible_field_information |
| uservisible_field | uservisible_field_information |
| everyonevisible_field | everyonevisible_field_information |
| teachervisible_field | teachervisible_field_information |
And I click on "Update profile" "button"
And I log out
@javascript
Scenario: Visible custom profile fields can be part of the sign up form for anonymous users.
Given I am on site homepage
And I follow "Log in"
When I click on "Create new account" "link"
And I expand all fieldsets
Then I should not see "notvisible_field"
And I should see "uservisible_field"
And I should not see "everyonevisible_field"
And I should see "teachervisible_field"
@javascript
Scenario: Visible custom profile fields can be part of the sign up form for guest users.
Given I log in as "guest"
And I am on site homepage
And I follow "Log in"
When I click on "Create new account" "link"
And I expand all fieldsets
Then I should not see "notvisible_field"
And I should see "uservisible_field"
And I should not see "everyonevisible_field"
And I should see "teachervisible_field"
@javascript
Scenario: User with moodle/user:update but without moodle/user:viewalldetails or moodle/site:viewuseridentity can only update visible profile fields.
Given the following "roles" exist:
| name | shortname | description | archetype |
| Update Users | updateusers | updateusers | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/user:update | Allow | updateusers | System | |
| moodle/site:viewuseridentity | Prohibit | updateusers | System | |
And the following "users" exist:
| username | firstname | lastname | email |
| user_updateusers | updateusers | 1 | updateusers@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user_updateusers | updateusers | System | |
And the following "course enrolments" exist:
| user | course | role |
| user_updateusers | C1 | editingteacher |
And I log in as "user_updateusers"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "userwithinformation 1"
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should not see "uservisible_field"
And I should not see "uservisible_field_information"
And I should not see "notvisible_field"
And I should not see "notvisible_field_information"
And I should not see "teachervisible_field"
And I should not see "teachervisible_field_information"
And I follow "Edit profile"
And the following fields match these values:
| everyonevisible_field | everyonevisible_field_information |
And I should not see "uservisible_field"
And I should not see "notvisible_field"
And I should not see "teachervisible_field"
@javascript
Scenario: User with moodle/user:viewalldetails and moodle/site:viewuseridentity but without moodle/user:update can view all profile fields.
Given the following "roles" exist:
| name | shortname | description | archetype |
| View All Details | viewalldetails | viewalldetails | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/user:viewalldetails | Allow | viewalldetails | System | |
And the following "users" exist:
| username | firstname | lastname | email |
| user_viewalldetails | viewalldetails | 1 | viewalldetails@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user_viewalldetails | viewalldetails | System | |
And the following "course enrolments" exist:
| user | course | role |
| user_viewalldetails | C1 | editingteacher |
And I log in as "user_viewalldetails"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "userwithinformation 1"
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should see "uservisible_field"
And I should see "uservisible_field_information"
And I should see "notvisible_field"
And I should see "notvisible_field_information"
And I should see "teachervisible_field"
And I should see "teachervisible_field_information"
And I should not see "Edit profile"
@javascript
Scenario: User with moodle/user:viewalldetails and moodle/user:update and moodle/site:viewuseridentity capabilities can view and edit all profile fields.
Given the following "roles" exist:
| name | shortname | description | archetype |
| View All Details and Update Users | viewalldetailsandupdateusers | viewalldetailsandupdateusers | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/user:viewalldetails | Allow | viewalldetailsandupdateusers | System | |
| moodle/user:update | Allow | viewalldetailsandupdateusers | System | |
And the following "users" exist:
| username | firstname | lastname | email |
| user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | 1 | viewalldetailsandupdateusers@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | System | |
And the following "course enrolments" exist:
| user | course | role |
| user_viewalldetailsandupdateusers | C1 | editingteacher |
And I log in as "user_viewalldetailsandupdateusers"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "userwithinformation 1"
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should see "uservisible_field"
And I should see "uservisible_field_information"
And I should see "notvisible_field"
And I should see "notvisible_field_information"
And I should see "teachervisible_field"
And I should see "teachervisible_field_information"
And I follow "Edit profile"
And the following fields match these values:
| everyonevisible_field | everyonevisible_field_information |
| uservisible_field | uservisible_field_information |
| notvisible_field | notvisible_field_information |
| teachervisible_field | teachervisible_field_information |
@javascript
Scenario: Users can view and edit custom profile fields except those marked as not visible.
Given I log in as "userwithinformation"
And I follow "Profile" in the user menu
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should see "uservisible_field"
And I should see "uservisible_field_information"
And I should see "teachervisible_field"
And I should see "teachervisible_field_information"
And I should not see "notvisible_field"
And I should not see "notvisible_field_information"
And I click on "Edit profile" "link" in the "region-main" "region"
Then the following fields match these values:
| everyonevisible_field | everyonevisible_field_information |
| uservisible_field | uservisible_field_information |
And I should not see "notvisible_field"
And I should not see "notvisible_field_information"
@javascript
Scenario: Users can view but not edit custom profile fields when denied the edit own profile capability.
Given the following "roles" exist:
| name | shortname | description | archetype |
| Deny editownprofile | denyeditownprofile | denyeditownprofile | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/user:editownprofile | Prohibit | denyeditownprofile | System | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| userwithinformation | denyeditownprofile | System | |
And I log in as "userwithinformation"
And I follow "Profile" in the user menu
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should see "uservisible_field"
And I should see "uservisible_field_information"
And I should see "teachervisible_field"
And I should see "teachervisible_field_information"
And I should not see "notvisible_field"
And I should not see "notvisible_field_information"
And I should not see "Edit profile"
@javascript
Scenario: User with parent permissions on other user context can view and edit all profile fields.
Given the following "roles" exist:
| name | shortname | description | archetype |
| Parent | parent | parent | |
And the following "users" exist:
| username | firstname | lastname | email |
| parent | Parent | user | parent@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| parent | parent | User | userwithinformation |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/user:viewalldetails | Allow | parent | User | userwithinformation |
| moodle/user:viewdetails | Allow | parent | User | userwithinformation |
| moodle/user:editprofile | Allow | parent | User | userwithinformation |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| mentees | System | 1 | site-index | side-pre |
And I log in as "parent"
And I am on site homepage
When I follow "userwithinformation"
Then I should see "everyonevisible_field"
And I should see "everyonevisible_field_information"
And I should see "uservisible_field"
And I should see "uservisible_field_information"
And I should see "teachervisible_field"
And I should see "teachervisible_field_information"
And I should not see "notvisible_field"
And I should not see "notvisible_field_information"
And I follow "Edit profile"
And the following fields match these values:
| everyonevisible_field | everyonevisible_field_information |
| uservisible_field | uservisible_field_information |
| teachervisible_field | teachervisible_field_information |
@javascript
Scenario: Menu profile field's default data works as expected when editing user profile
Given the following "custom profile fields" exist:
| datatype | shortname | name | visible | param1 | defaultdata |
| menu | menufield | Menu field | 2 | OptA\nOptB\nOptC | OptB |
And I log in as "userwithinformation"
When I follow "Profile" in the user menu
And I click on "Edit profile" "link" in the "region-main" "region"
Then the following fields match these values:
| Menu field | OptB |
@javascript
Scenario: Menu profile field successfully updated when editing user profile
Given the following "custom profile fields" exist:
| datatype | shortname | name | visible | param1 |
| menu | menufield | Menu field | 2 | OptA\nOptB\nOptC |
And I log in as "userwithinformation"
When I follow "Profile" in the user menu
And I click on "Edit profile" "link" in the "region-main" "region"
And I set the following fields to these values:
| Menu field | OptC |
And I click on "Update profile" "button"
Then I should see "OptC"
@@ -0,0 +1,50 @@
@core @core_user
Feature: Custom profile fields creation using UI
@javascript
Scenario Outline: Manual creation of basic custom profile fields
Given I log in as "admin"
And I navigate to "Users > Accounts > User profile fields" in site administration
And I click on "Create a new profile field" "link"
And I click on "<name>" "link"
And I set the following fields to these values:
| Short name | <shortname> |
| Name | <name> |
When I click on "Save changes" "button"
Then I should see "<name>"
Examples:
| shortname | name |
| checkbox | Checkbox |
| datetime | Date/Time |
| textarea | Text area |
| textinput | Text input |
@javascript
Scenario: Manual creation of drop-down menu custom profile field type
Given I log in as "admin"
And I navigate to "Users > Accounts > User profile fields" in site administration
And I click on "Create a new profile field" "link"
And I click on "Drop-down menu" "link"
And I set the following fields to these values:
| Short name | dropdownmenu |
| Name | Drop-down menu field |
And I set the field "Menu options (one per line)" to multiline:
"""
a
b
"""
When I click on "Save changes" "button"
Then I should see "Drop-down menu field"
@javascript
Scenario: Manual creation of social custom profile field type
Given I log in as "admin"
And I navigate to "Users > Accounts > User profile fields" in site administration
And I click on "Create a new profile field" "link"
And I click on "Social" "link"
And I set the following fields to these values:
| Network type | Web page |
| Short name | social |
When I click on "Save changes" "button"
Then I should see "Web page"
+122
View File
@@ -0,0 +1,122 @@
@core @core_user
Feature: Deleting users
In order to manage a Moodle site
As an admin
I need to be able to delete users
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | One | one@example.com |
| user2 | User | Two | two@example.com |
| user3 | User | Three | three@example.com |
| user4 | User | Four | four@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | student |
| user2 | C1 | student |
| user3 | C1 | student |
| user4 | C1 | student |
And the following config values are set as admin:
| messaging | 1 |
@javascript
Scenario: Deleting one user at a time
When I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And the "Available" select box should contain "User Four"
And I set the field "Available" to "User Four"
And I press "Add to selection"
And I set the field "id_action" to "Delete"
And I press "Go"
And I should see "Are you absolutely sure you want to completely delete the user User Four, including their enrolments, activity and other user data?"
And I press "Yes"
And I should see "Changes saved"
And I press "Continue"
Then the "Available" select box should not contain "User Four"
And the "Available" select box should contain "User One"
@javascript
Scenario: Deleting more than one user at a time
When I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "User Four"
And I press "Add to selection"
And I set the field "Available" to "User Three"
And I press "Add to selection"
And I set the field "id_action" to "Delete"
And I press "Go"
And I should see "Are you absolutely sure you want to completely delete the user User Four, User Three, including their enrolments, activity and other user data?"
And I press "Yes"
And I should see "Changes saved"
And I press "Continue"
Then the "Available" select box should not contain "User Four"
And the "Available" select box should not contain "User Three"
And the "Available" select box should contain "User One"
@javascript
Scenario: Deleting users from bulk actions in the user list
When I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "User Four" "checkbox"
And I click on "User Three" "checkbox"
And I set the field "Bulk user actions" to "Delete"
And I should see "Are you absolutely sure you want to completely delete the user User Four, User Three, including their enrolments, activity and other user data?"
And I press "Yes"
And I should see "Changes saved"
And I press "Continue"
And I should see "Browse list of users"
And I should not see "User Four"
And I should not see "User Three"
And I should see "User One"
@javascript @core_message
Scenario: Deleting users who have unread messages sent or received
When I log in as "user1"
And I send "Message 1 from user1 to user2" message to "User Two" user
And I log out
And I log in as "user3"
And I send "Message 2 from user3 to user4" message to "User Four" user
And I log out
And I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "User One"
And I press "Add to selection"
And I set the field "Available" to "User Four"
And I press "Add to selection"
And I set the field "id_action" to "Delete"
And I press "Go"
And I press "Yes"
Then I should see "Changes saved"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "User Two"
And I press "Add to selection"
And I set the field "Available" to "User Three"
And I press "Add to selection"
And I set the field "id_action" to "Delete"
And I press "Go"
And I press "Yes"
And I should see "Changes saved"
And I press "Continue"
And the "Available" select box should not contain "User Four"
And the "Available" select box should not contain "User Three"
And the "Available" select box should not contain "User One"
And the "Available" select box should not contain "User Two"
@javascript
Scenario: Deleting a bulked user
When I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "User Two"
And I press "Add to selection"
And I set the field "Available" to "User One"
And I press "Add to selection"
Then I should see "User One"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I press "Delete" action in the "User One" report row
And I click on "Delete" "button" in the "Delete user" "dialogue"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
Then I should not see "User One"
@@ -0,0 +1,54 @@
@core @core_user
Feature: Notification shown when user edit profile or preferences
In order to show notification
As a user
I press update profile button after make some changes in edit profile page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| unicorn | Unicorn | 1 | unicorn@example.com |
And the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| unicorn | C1 | student |
@javascript
Scenario: Change own profile and has notification shown
Given I log in as "unicorn"
And I open my profile in edit mode
And I should see "Unicorn"
And I should see "1"
Then I set the field "Last name" to "Lil"
And I click on "Update profile" "button"
And I should see "Changes saved"
And I press "Dismiss this notification"
And I should not see "Changes saved"
And I follow "Preferences" in the user menu
And I follow "Preferred language"
And I click on "Save changes" "button"
And I should see "Changes saved"
And I follow "Forum preferences"
And I set the field "Use experimental nested discussion view" to "Yes"
And I click on "Save changes" "button"
And I should see "Changes saved"
@javascript
Scenario: Do not show notification when cancel profile change
Given I log in as "unicorn"
And I open my profile in edit mode
And I should see "Unicorn"
And I should see "1"
Then I set the field "Last name" to "Lil"
And I click on "Cancel" "button"
And I should not see "Changes saved"
@javascript
Scenario: Show notification after admin edited profile of another user
Given I am on the "unicorn" "user > editing" page logged in as "admin"
And I expand all fieldsets
Then I set the field "Last name" to "Lil"
And I click on "Update profile" "button"
And I should see "Changes saved"
@@ -0,0 +1,140 @@
@core @core_user
Feature: Edit user enrolment
In order to manage students' enrolments
As a teacher
I need to be able to view enrolment details and edit student enrolments in the course participants page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role | status |
| teacher1 | C1 | editingteacher | 0 |
| student1 | C1 | student | 0 |
| student2 | C1 | student | 1 |
@javascript
Scenario: Edit a user's enrolment
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Edit enrolment" "icon" in the "student1" "table_row"
And I should see "Edit Student 1's enrolment"
And I set the field "Status" to "Suspended"
And I click on "Save changes" "button"
And I click on "Edit enrolment" "icon" in the "student2" "table_row"
And I should see "Edit Student 2's enrolment"
And I set the field "timeend[enabled]" to "1"
And I set the field "timeend[day]" to "1"
And I set the field "timeend[month]" to "January"
And I set the field "timeend[year]" to "2017"
And I set the field "Status" to "Active"
And I click on "Save changes" "button"
Then I should see "Suspended" in the "student1" "table_row"
And I should see "Not current" in the "student2" "table_row"
@javascript
Scenario: Unenrol a student
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Unenrol" "icon" in the "student1" "table_row"
And I click on "Unenrol" "button" in the "Unenrol" "dialogue"
Then I should not see "Student 1" in the "participants" "table"
@javascript
Scenario: View a student's enrolment details
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Manual enrolments" "icon" in the "student1" "table_row"
Then I should see "Enrolment details"
And I should see "Student 1" in the "Full name" "table_row"
And I should see "Active" in the "//td[@class='user-enrol-status']" "xpath_element"
And I should see "Manual enrolments" in the "Enrolment method" "table_row"
And I should see "Enrolment created"
And I click on "Cancel" "button" in the "Enrolment details" "dialogue"
And I click on "Manual enrolments" "icon" in the "student2" "table_row"
And I should see "Enrolment details"
And I should see "Student 2" in the "Full name" "table_row"
And I should see "Suspended" in the "//td[@class='user-enrol-status']" "xpath_element"
And I should see "Manual enrolments" in the "Enrolment method" "table_row"
And I should see "Enrolment created"
And "Edit enrolment" "icon" should exist in the "Enrolment method" "table_row"
@javascript
Scenario: View a student's enrolment details for a student enrolled via course meta link where editing can't be done
Given the following "users" exist:
| username | firstname | lastname | email |
| student3 | Student | 3 | student3@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 2 | C2 | topics |
And the following "course enrolments" exist:
| user | course | role | status |
| student3 | C2 | student | 0 |
And I log in as "admin"
And I navigate to "Plugins > Enrolments > Manage enrol plugins" in site administration
And I click on "Enable" "link" in the "Course meta link" "table_row"
And I add "Course meta link" enrolment method in "Course 1" with:
| Link course | C2 |
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Student 3" in the "participants" "table"
And "Edit enrolment" "icon" should not exist in the "student3" "table_row"
And "Unenrol" "icon" should not exist in the "student3" "table_row"
And I click on "Course meta link (Course 2)" "icon" in the "student3" "table_row"
And I should see "Enrolment details"
And I should see "Student 3" in the "Full name" "table_row"
And I should see "Active" in the "//td[@class='user-enrol-status']" "xpath_element"
And I should see "Course meta link (Course 2)" in the "Enrolment method" "table_row"
And "Edit enrolment" "icon" should not exist in the "Enrolment method" "table_row"
@javascript
Scenario: Edit a student's enrolment details from the status dialogue
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Manual enrolments" "icon" in the "student2" "table_row"
And I click on "Edit enrolment" "icon" in the "Enrolment method" "table_row"
And I should see "Edit Student 2's enrolment"
And I set the field "Status" to "Active"
And I click on "Save changes" "button"
Then I should see "Active" in the "student2" "table_row"
# Without JS, the user should be redirected to the original edit enrolment form.
Scenario: Edit a user's enrolment without JavaScript
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Edit enrolment" "link" in the "student1" "table_row"
And I should see "Student 1"
And I set the field "Status" to "Suspended"
And I click on "Save changes" "button"
And I click on "Edit enrolment" "link" in the "student2" "table_row"
And I should see "Student 2"
And I set the field "timeend[enabled]" to "1"
And I set the field "timeend[day]" to "1"
And I set the field "timeend[month]" to "January"
And I set the field "timeend[year]" to "2017"
And I set the field "Status" to "Active"
And I click on "Save changes" "button"
Then I should see "Suspended" in the "student1" "table_row"
And I should see "Not current" in the "student2" "table_row"
# Without JS, the user should be redirected to the original unenrol confirmation page.
Scenario: Unenrol a student
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Unenrol" "link" in the "student1" "table_row"
And I click on "Continue" "button"
Then I should not see "Student 1" in the "participants" "table"
+41
View File
@@ -0,0 +1,41 @@
@core @core_user
Feature: Edit user roles
In order to administer users in course
As a teacher
I need to be able to assign and unassign roles in the course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
@javascript
Scenario: Assign roles on participants page
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Student 1's role assignments" "link"
And I type "Non-editing teacher"
And I press the enter key
When I click on "Save changes" "link"
Then I should see "Student, Non-editing teacher" in the "Student 1" "table_row"
@javascript
Scenario: Remove roles on participants page
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Student 1's role assignments" "link"
And I click on "Student" "autocomplete_selection"
When I click on "Save changes" "link"
Then I should see "No roles" in the "Student 1" "table_row"
+42
View File
@@ -0,0 +1,42 @@
@core @core_user
Feature: Edit a users password
In order edit a user password properly
As an admin
I need to be able to edit their profile and change their password
@javascript
Scenario: Verify the password field is enabled/disabled based on authentication selected, in user edit advanced page.
Given I log in as "admin"
When I navigate to "Users > Accounts > Add a new user" in site administration
Then the "New password" "field" should be enabled
And I set the field "auth" to "Web services authentication"
And the "New password" "field" should be disabled
And I set the field "auth" to "Email-based self-registration"
And the "New password" "field" should be enabled
# We need to cancel/submit a form that has been modified.
And I press "Create user"
Scenario: Log out web apps field is not present if user doesn't have active token
Given the following "users" exist:
| username | firstname | lastname | email |
| user01 | User | One | user01@example.com |
When I am on the "user01" "user > editing" page logged in as "admin"
Then "Log out of all web apps" "field" should not exist
Scenario Outline: Log out web apps field is present based on expiry of active token
Given the following "users" exist:
| username | firstname | lastname | email |
| user01 | User | One | user01@example.com |
And the following "core_webservice > Service" exist:
| shortname | name |
| mytestservice | My test service |
And the following "core_webservice > Tokens" exist:
| user | service | validuntil |
| user01 | mytestservice | <validuntil> |
When I am on the "user01" "user > editing" page logged in as "admin"
Then "Log out of all web apps" "field" <shouldornot> exist
Examples:
| validuntil | shouldornot |
| ## -1 month ## | should not |
| 0 | should |
| ## +1 month ## | should |
@@ -0,0 +1,53 @@
@core @core_user
Feature: Viewing the list of cohorts to enrol in a course
In order to ensure we only display the cohorts when applicable
As a teacher
I should only see the list of cohorts under some circumstances
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
@javascript @skip_chrome_zerosize
Scenario: Check the teacher does not see the cohorts field without the proper capabilities
Given the following "cohort" exists:
| name | Test cohort name |
| idnumber | 1337 |
| description | Test cohort description |
And the following "role capability" exists:
| role | editingteacher |
| moodle/cohort:manage | prohibit |
| moodle/cohort:view | prohibit |
And I log out
And I am on the "Course 1" course page logged in as teacher1
And I navigate to course participants
When I press "Enrol users"
Then I should not see "Select cohorts"
And I should not see "Enrol selected users and cohorts"
@javascript
Scenario: Check we show the cohorts field if there are some present
Given the following "cohort" exists:
| name | Test cohort name |
| idnumber | 1337 |
| description | Test cohort description |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to course participants
When I press "Enrol users"
Then I should see "Select cohorts"
And I should see "Enrol selected users and cohorts"
@javascript
Scenario: Check we do not show the cohorts field if there are none present
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to course participants
When I press "Enrol users"
Then I should not see "Select cohorts"
And I should not see "Enrol selected users and cohorts"
+63
View File
@@ -0,0 +1,63 @@
@core @core_user
Feature: Filter users by idnumber
As a system administrator
I need to be able to filter users by their ID number
So that I can quickly find users based on an external key.
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher@example.com | 0000002 |
| student1 | Student1 | 1 | student1@example.com | 0000003 |
| student2 | Student2 | 1 | student2@example.com | 2000000 |
| student3 | Student3 | 1 | student3@example.com | 3000000 |
And I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
@javascript
Scenario: Filtering id numbers - with case "is empty"
# We should see see admin on the user list, the following e-mail is admin's e-mail.
Then I should see "moodle@example.com" in the "reportbuilder-table" "table"
And I should see "Teacher" in the "reportbuilder-table" "table"
And I should see "Student1" in the "reportbuilder-table" "table"
And I should see "Student2" in the "reportbuilder-table" "table"
And I should see "Student3" in the "reportbuilder-table" "table"
And I click on "Filters" "button"
And I set the following fields in the "ID number" "core_reportbuilder > Filter" to these values:
| ID number operator | Is empty |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I click on "Filters" "button"
# We should see admin on the user list, the following e-mail is admin's e-mail.
Then I should see "moodle@example.com" in the "reportbuilder-table" "table"
And I should not see "Teacher" in the "reportbuilder-table" "table"
And I should not see "Student1" in the "reportbuilder-table" "table"
And I should not see "Student2" in the "reportbuilder-table" "table"
And I should not see "Student3" in the "reportbuilder-table" "table"
@javascript
Scenario Outline: Filtering id numbers - with all other cases
# We should see see admin on the user list, the following e-mail is admin's e-mail.
Then I should see "moodle@example.com" in the "reportbuilder-table" "table"
And I should see "Teacher" in the "reportbuilder-table" "table"
And I should see "Student1" in the "reportbuilder-table" "table"
And I should see "Student2" in the "reportbuilder-table" "table"
And I should see "Student3" in the "reportbuilder-table" "table"
And I click on "Filters" "button"
And I set the following fields in the "ID number" "core_reportbuilder > Filter" to these values:
| ID number operator | <Category> |
| ID number value | <Argument> |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I click on "Filters" "button"
Then I should <Admin's Visibility> "moodle@example.com" in the "reportbuilder-table" "table"
And I should <Teacher's Vis> "Teacher" in the "reportbuilder-table" "table"
And I should <S1's Vis> "Student1" in the "reportbuilder-table" "table"
And I should <S2's Vis> "Student2" in the "reportbuilder-table" "table"
And I should <S3's Vis> "Student3" in the "reportbuilder-table" "table"
Examples:
| Category | Argument | Admin's Visibility | Teacher's Vis | S1's Vis | S2's Vis | S3's Vis |
| Contains | 0 | not see | see | see | see | see |
| Does not contain | 2 | see | not see | see | not see | see |
| Is equal to | 2000000 | not see | not see | not see | see | not see |
| Starts with | 0 | not see | see | see | not see | not see |
| Ends with | 0 | not see | not see | not see | see | see |
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
@core @core_user
Feature: Course participants can be filtered to display all the users
In order to filter the list of course participants
As a user
I need to visit the course participants page, apply the appropriate filters and show all users per page
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
| student4 | Student | 4 | student4@example.com |
| student5 | Student | 5 | student5@example.com |
| student6 | Student | 6 | student6@example.com |
| student7 | Student | 7 | student7@example.com |
| student8 | Student | 8 | student8@example.com |
| student9 | Student | 9 | student9@example.com |
| student10 | Student | 10 | student10@example.com |
| student11 | Student | 11 | student11@example.com |
| student12 | Student | 12 | student12@example.com |
| student13 | Student | 13 | student13@example.com |
| student14 | Student | 14 | student14@example.com |
| student15 | Student | 15 | student15@example.com |
| student16 | Student | 16 | student16@example.com |
| student17 | Student | 17 | student17@example.com |
| student18 | Student | 18 | student18@example.com |
| student19 | Student | 19 | student19@example.com |
| student20 | Student | 20 | student20@example.com |
| student21 | Student | 21 | student21@example.com |
| student22 | Student | 22 | student22@example.com |
| student23 | Student | 23 | student23@example.com |
| student24 | Student | 24 | student24@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role | status | timeend |
| student1 | C1 | student | 0 | |
| student2 | C1 | student | 0 | |
| student3 | C1 | student | 0 | |
| student4 | C1 | student | 0 | |
| student5 | C1 | student | 0 | |
| student6 | C1 | student | 0 | |
| student7 | C1 | student | 0 | |
| student8 | C1 | student | 0 | |
| student9 | C1 | student | 0 | |
| student10 | C1 | student | 0 | |
| student11 | C1 | student | 0 | |
| student12 | C1 | student | 0 | |
| student13 | C1 | student | 0 | |
| student14 | C1 | student | 0 | |
| student15 | C1 | student | 0 | |
| student16 | C1 | student | 0 | |
| student17 | C1 | student | 0 | |
| student18 | C1 | student | 0 | |
| student19 | C1 | student | 0 | |
| student20 | C1 | student | 0 | |
| student21 | C1 | student | 0 | |
| student22 | C1 | student | 0 | |
| student23 | C1 | student | 0 | |
| student24 | C1 | student | 1 | |
| student1 | C2 | student | 0 | |
| student2 | C2 | student | 0 | |
| student3 | C2 | student | 0 | |
| teacher1 | C1 | editingteacher | 0 | |
| teacher1 | C2 | editingteacher | 0 | |
@javascript
Scenario: Show all users in a course that match a single filter value
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
When I click on "Apply filters" "button"
Then I should see "24 participants found"
And I should see "Show all 24"
And I should not see "Show 20 per page"
And I should not see "of the following"
And I click on "Show all 24" "link"
And I should see "Show 20 per page"
And I should not see "Show all 24"
@javascript
Scenario: Show all users as a student
Given I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
When I click on "Apply filters" "button"
Then I should see "23 participants found"
And I should see "Show all 23"
And I should not see "Show 20 per page"
And I click on "Show all 23" "link"
And I should see "Show 20 per page"
And I should not see "Show all 23"
@javascript
Scenario: Apply one value for more than one filter and show all matching users
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Add condition" "button"
And I set the field "Match" to "All"
And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
And I set the field "Match" in the "Filter 2" "fieldset" to "Any"
And I set the field "type" in the "Filter 2" "fieldset" to "Status"
And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active"
When I click on "Apply filters" "button"
And I click on "Show all 23" "link"
Then I should see "23 participants found"
And I should see "Show 20 per page"
And I should see "of the following"
And I should see "Student 1"
And I should not see "Student 24"
And I should not see "Show all 23"
@@ -0,0 +1,83 @@
@core @core_user
Feature: Users' names are displayed across the site according to the user policy settings
In order to control the way students and teachers see users' names
As a teacher or admin
I need to be able to configure the name display formats 'fullnamedisplay' and 'alternativefullnameformat'
Background:
Given the following "users" exist:
| username | firstname | lastname | email | middlename | alternatename | firstnamephonetic | lastnamephonetic |
| user1 | Grainne | Beauchamp | one@example.com | Ann | Jill | Gronya | Beecham |
| user2 | Niamh | Cholmondely | two@example.com | Jane | Nina | Nee | Chumlee |
| user3 | Siobhan | Desforges | three@example.com | Sarah | Sev | Shevon | De-forjay |
| teacher1 | Teacher | 1 | teacher1@example.com | | | | |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| user1 | C1 | student |
| user2 | C1 | student |
And the following config values are set as admin:
| fullnamedisplay | firstnamephonetic,lastnamephonetic |
| alternativefullnameformat | middlename, alternatename, firstname, lastname |
Scenario: As a student, 'fullnamedisplay' should be used in the participants list and when viewing my own course profile
Given I log in as "user1"
And I am on "Course 1" course homepage
When I navigate to course participants
And I click on "Gronya,Beecham" "link" in the "Gronya,Beecham" "table_row"
Then I should see "Gronya,Beecham" in the "region-main" "region"
And I log out
Scenario: As a student, 'fullnamedisplay' should be used in the participants list and when viewing another user's course profile
Given I log in as "user2"
And I am on "Course 1" course homepage
When I navigate to course participants
And I click on "Gronya,Beecham" "link" in the "Gronya,Beecham" "table_row"
Then I should see "Gronya,Beecham" in the "region-main" "region"
And I log out
Scenario: As a teacher, 'alternativefullnameformat' should be used in the participants list but 'fullnamedisplay' used on the course profile
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Ann, Jill, Grainne, Beauchamp" in the "Ann, Jill, Grainne, Beauchamp" "table_row"
And I click on "Ann, Jill, Grainne, Beauchamp" "link" in the "Ann, Jill, Grainne, Beauchamp" "table_row"
And I should see "Gronya,Beecham" in the "region-main" "region"
And I log out
Scenario: As an authenticated user, 'fullnamedisplay' should be used in the navigation and when viewing my profile
Given I log in as "user1"
When I follow "Profile" in the user menu
Then I should see "Gronya,Beecham" in the ".page-context-header" "css_element"
And I should see "You are logged in as Gronya,Beecham" in the "page-footer" "region"
And I log out
Scenario: As an admin, 'fullnamedisplay' should be used when using the 'log in as' function
Given I log in as "admin"
When I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "Jane, Nina, Niamh, Cholmondely"
And I follow "Log in as"
Then I should see "You are logged in as Nee,Chumlee" in the ".usermenu" "css_element"
And I should see "You are logged in as Jane, Nina, Niamh, Cholmondely" in the "region-main" "region"
And I should see "You are logged in as Nee,Chumlee" in the "page-footer" "region"
And I log out
Scenario: As an admin, 'fullnamedisplay' should be used when viewing another user's site profile
Given I log in as "admin"
When I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "Ann, Jill, Grainne, Beauchamp"
Then I should see "Gronya,Beecham" in the ".page-header-headings" "css_element"
And I log out
@javascript
Scenario: As a teacher, the 'alternativefullnameformat' should be used when searching for and enrolling a user
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I navigate to course participants
And I press "Enrol users"
And I click on "Select users" "field"
And I type "three@example.com"
Then I should see "Sarah, Sev, Siobhan, Desforges"
@@ -0,0 +1,55 @@
@core @core_user
Feature: Hidden user fields behavior
In order to hide private information of users
As an admin
I can set Hide user fields setting
Background:
Given the following "users" exist:
| username | firstname | lastname | email | description | city |
| user | Profile | User | user@example.com | This is me | Donostia |
| student | Student | User | student@example.com | | |
| teacher | Teacher | User | teacher@example.com | | |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| user | C1 | student |
| student | C1 | student |
| teacher | C1 | editingteacher |
And the following config values are set as admin:
| hiddenuserfields | description,email |
Scenario Outline: Hidden user fields on course context profile based on role permission
Given I log in as "<user>"
And I am on "Course 1" course homepage
And I navigate to course participants
And I should see "Profile User"
When I click on "Profile User" "link"
Then I <expected> "This is me"
And I <expected> "user@example.com"
And I should see "Donostia"
Examples:
| user | expected |
| student | should not see |
| teacher | should see |
| admin | should see |
Scenario Outline: Hidden user fields on system context profile based on role permission
Given I log in as "<user>"
And I am on "Course 1" course homepage
And I navigate to course participants
And I should see "Profile User"
When I click on "Profile User" "link"
And I click on "Full profile" "link"
Then I <expected> "This is me"
And I <expected> "user@example.com"
And I should see "Donostia"
Examples:
| user | expected |
| student | should not see |
| teacher | should not see |
| admin | should see |
+40
View File
@@ -0,0 +1,40 @@
@core @core_user
Feature: The purpose of each input field collecting information about the user can be determined
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| unicorn | unicorn | 1 | unicorn@example.com |
And the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| unicorn | C1 | student |
@javascript
Scenario: Fields for other users are not auto filled
When I am on the "unicorn@example.com" "user > editing" page logged in as "admin"
And I expand all fieldsets
Then the field "Username" should not have purpose "username"
And the field "First name" should not have purpose "given-name"
And the field "Last name" should not have purpose "family-name"
And the field "Email" should not have purpose "email"
And the field "Select a country" should not have purpose "country"
And I press "Cancel"
And I follow "Preferred language"
And the field "Preferred language" should not have purpose "language"
@javascript
Scenario: My own user fields are auto filled
Given I log in as "unicorn"
When I open my profile in edit mode
And I expand all fieldsets
Then the field "First name" should have purpose "given-name"
And the field "Last name" should have purpose "family-name"
And the field "Email" should have purpose "email"
And the field "Select a country" should have purpose "country"
And I press "Cancel"
And I follow "Preferences" in the user menu
And I follow "Preferred language"
And the field "Preferred language" should have purpose "language"
+65
View File
@@ -0,0 +1,65 @@
@core @core_user
Feature: Both first name and last name are always available for every user
In order to easily identify and display users on Moodle pages
As any user
I need to rely on both first name and last name are always available
Scenario: Attempting to self-register as a new user with empty names
Given the following config values are set as admin:
| registerauth | email |
| passwordpolicy | 0 |
And I am on site homepage
And I follow "Log in"
And I click on "Create new account" "link"
When I set the following fields to these values:
| Username | mrwhitespace |
| Password | Gue$$m3ifY0uC&n |
| Email address | mrwhitespace@nas.ty |
| Email (again) | mrwhitespace@nas.ty |
And I set the field "First name" to " "
And I set the field "Last name" to " "
And I press "Create my new account"
Then I should see "Missing given name"
And I should see "Missing last name"
Scenario: Attempting to change own names to whitespace
Given the following "users" exist:
| username | firstname | lastname | email |
| foobar | Foo | Bar | foo@bar.com |
And I log in as "foobar"
# UI test covering "I open my profile in edit mode" -
# This should be one of the very few places where we directly call these 2 steps to open the current users profile
# in edit mode, the rest of the time you should use "I open my profile in edit mode" as it is faster.
And I follow "Profile" in the user menu
And I click on "Edit profile" "link" in the "region-main" "region"
# End UI test covering "I open my profile in edit mode"
When I set the field "First name" to " "
And I set the field "Last name" to " "
And I click on "Cancel" "button"
And I follow "Profile" in the user menu
And I click on "Edit profile" "link" in the "region-main" "region"
Then I should see "Foo"
And I should see "Bar"
When I set the field "First name" to " "
And I set the field "Last name" to " "
And I click on "Update profile" "button"
Then I should see "Missing given name"
And I should see "Missing last name"
Scenario: Attempting to change someone else's names to whitespace
Given the following "users" exist:
| username | firstname | lastname | email |
| foobar | Foo | Bar | foo@bar.com |
And I am on the "foobar" "user > editing" page logged in as "admin"
When I set the field "First name" to " "
And I set the field "Last name" to " "
And I click on "Cancel" "button"
And I follow "Foo Bar"
And I click on "Edit profile" "link" in the "region-main" "region"
Then I should see "Foo"
And I should see "Bar"
When I set the field "First name" to " "
And I set the field "Last name" to " "
And I click on "Update profile" "button"
Then I should see "Missing given name"
And I should see "Missing last name"
@@ -0,0 +1,186 @@
@core @core_course @javascript
Feature: Viewing participants page in different group modes
In order to view my students
As a user
I need to be able to browse participants who are grouped
Background:
Given the following "courses" exist:
| fullname | shortname | summary | groupmode | category |
| C1 nogroups | C1 | | 0 | 0 |
| C2 visgroups | C2 | | 2 | 0 |
| C3 sepgroups | C3 | | 1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| teacher1 | C3 | editingteacher |
| teacher2 | C1 | teacher |
| teacher2 | C2 | teacher |
| teacher2 | C3 | teacher |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student2 | C1 | student |
| student2 | C2 | student |
| student2 | C3 | student |
| student3 | C1 | student |
| student3 | C2 | student |
| student3 | C3 | student |
And the following "groups" exist:
| name | course | idnumber |
| G1 | C2 | C2G1 |
| G2 | C2 | C2G2 |
| G1 | C3 | C3G1 |
| G2 | C3 | C3G2 |
And the following "group members" exist:
| user | group |
| student1 | C2G1 |
| student1 | C3G1 |
| student2 | C2G2 |
| student2 | C3G2 |
| teacher2 | C2G1 |
| teacher2 | C3G1 |
Scenario: Viewing participants page as an editing teacher in a course without group mode
When I log in as "teacher1"
And I am on "C1 nogroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "No groups"
And "Student 2" row "Groups" column of "participants" table should contain "No groups"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "No groups"
Scenario: Viewing participants page as an editing teacher in a course in visible groups mode
When I log in as "teacher1"
And I am on "C2 visgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Student 2" row "Groups" column of "participants" table should contain "G2"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
Scenario: Viewing participants page as an editing teacher in a course in separate groups mode
When I log in as "teacher1"
And I am on "C3 sepgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Student 2" row "Groups" column of "participants" table should contain "G2"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
Scenario: Viewing participants page as a non-editing teacher in a course without group mode
When I log in as "teacher2"
And I am on "C1 nogroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "No groups"
And "Student 2" row "Groups" column of "participants" table should contain "No groups"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "No groups"
Scenario: Viewing participants page as a non-editing teacher in a course in visible groups mode
When I log in as "teacher2"
And I am on "C2 visgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Teacher 1"
And I should not see "Student 2"
And I should not see "Student 3"
When I click on "Clear filters" "button"
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Student 2" row "Groups" column of "participants" table should contain "G2"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
Scenario: Viewing participants page as a non-editing teacher in a course in separate groups mode
When I log in as "teacher2"
And I am on "C3 sepgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Teacher 1"
And I should not see "Student 2"
And I should not see "Student 3"
When I click on "Clear filters" "button"
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Teacher 1"
And I should not see "Student 2"
And I should not see "Student 3"
Scenario: Viewing participants page as a student in a course without group mode
When I log in as "student1"
And I am on "C1 nogroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "No groups"
And "Student 2" row "Groups" column of "participants" table should contain "No groups"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
Scenario: Viewing participants page as a student in a group in a course in visible groups mode
When I log in as "student1"
And I am on "C2 visgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Teacher 1"
When I click on "Clear filters" "button"
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Student 2" row "Groups" column of "participants" table should contain "G2"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
Scenario: Viewing participants page as a student in a group in a course in separate groups mode
When I log in as "student1"
And I am on "C3 sepgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Teacher 1"
When I click on "Clear filters" "button"
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Teacher 1"
Scenario: Viewing participants page as a student not in a group in a course in visible groups mode
When I log in as "student3"
And I am on "C2 visgroups" course homepage
And I navigate to course participants
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
And I should not see "Student 2"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Teacher 1"
When I click on "Clear filters" "button"
Then "Student 1" row "Groups" column of "participants" table should contain "G1"
And "Student 2" row "Groups" column of "participants" table should contain "G2"
And "Student 3" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 1" row "Groups" column of "participants" table should contain "No groups"
And "Teacher 2" row "Groups" column of "participants" table should contain "G1"
Scenario: Viewing participants page as a student not in a group in a course in separate groups mode
When I log in as "student3"
And I am on "C3 sepgroups" course homepage
And I navigate to course participants
Then I should see "Sorry, but you need to be part of a group to see this page."
+26
View File
@@ -0,0 +1,26 @@
@core @core_user
Feature: Reset my profile page to default
In order to remove customisations from my profile page
As a user
I need to reset my profile page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
And I log in as "admin"
And I follow "View profile"
Scenario: Add blocks to page and reset
When I turn editing mode on
And I add the "Latest announcements" block
And I press "Reset page to default"
Then I should not see "Latest announcements"
@@ -0,0 +1,76 @@
@core @core_user
Feature: Set the site home page and dashboard as the default home page
In order to set a page as my default home page
As a user
I need to choose which page I want and set it as my home page
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | One | user1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | student |
Scenario: Admin sets the site page and then the dashboard as the default home page
# This functionality does not work without the administration block.
Given I log in as "admin"
And I am on site homepage
And I turn editing mode on
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I add the "Navigation" block if not present
And I configure the "Navigation" block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
And I add the "Administration" block if not present
And I configure the "Administration" block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
And I navigate to "Appearance > Navigation" in site administration
And I set the field "Start page for users" to "User preference"
And I press "Save changes"
And I am on site homepage
And I follow "Make this my home page"
And I should not see "Make this my home page"
And I am on "Course 1" course homepage
And I should see "Home" in the "Navigation" "block"
And I should not see "Site home" in the "Navigation" "block"
And I am on site homepage
And I follow "Dashboard"
And I follow "Make this my home page"
And I should not see "Make this my home page"
And I am on "Course 1" course homepage
Then I should not see "Home" in the "Navigation" "block"
And I should see "Site home" in the "Navigation" "block"
Scenario: User cannot configure their preferred default home page unless allowed by admin
Given I log in as "user1"
When I follow "Preferences" in the user menu
Then I should not see "Home page"
Scenario Outline: User can configure their preferred default home page when allowed by admin
Given I log in as "admin"
And I navigate to "Appearance > Navigation" in site administration
And I set the field "Start page for users" to "User preference"
And I press "Save changes"
And I log out
When I log in as "user1"
And I follow "Preferences" in the user menu
And I follow "Start page"
And I set the field "Start page" to "<preference>"
And I press "Save changes"
And I log out
And I log in as "user1"
Then I should see "<breadcrumb>" is active in navigation
Examples:
| preference | breadcrumb |
| Home | Home |
| Dashboard | Dashboard |
| My courses | My courses |
@@ -0,0 +1,96 @@
@core @core_user
Feature: Set email display preference
In order to control who can see my email address on my profile page
As a student
I need my email to be shown to only the user groups chosen
Background:
Given the following "users" exist:
| username | firstname | lastname | email | maildisplay |
| teacher1 | Teacher | 1 | teacher1@example.com | 2 |
| studentp | Student | PEER | studentP@example.com | 2 |
| studentn | Student | NONE | studentN@example.com | 0 |
| studente | Student | EVERYONE | studentE@example.com | 1 |
| studentm | Student | MEMBERS | studentM@example.com | 2 |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role | status | timeend |
| teacher1 | C1 | teacher | 0 | 0 |
| studentp | C1 | student | 0 | 0 |
| studentn | C1 | student | 0 | 0 |
| studente | C1 | student | 0 | 0 |
| studentm | C1 | student | 0 | 0 |
@javascript
Scenario: Student viewing own profile
Given I log in as "studentp"
When I follow "Profile" in the user menu
Then I should see "studentP@example.com"
And I should see "(Visible to other course participants)"
@javascript
Scenario: Student peer on the same course viewing profiles
Given I log in as "studentp"
And I am on "Course 1" course homepage
And I navigate to course participants
When I follow "Student NONE"
Then I should not see "studentN@example.com"
And I navigate to course participants
When I follow "Student EVERYONE"
Then I should see "studentE@example.com"
And I navigate to course participants
When I follow "Student MEMBERS"
Then I should see "studentM@example.com"
@javascript
Scenario: Student viewing teacher email (whose maildisplay = MEMBERS)
Given I log in as "studentp"
And I am on "Course 1" course homepage
And I navigate to course participants
When I follow "Teacher 1"
Then I should see "teacher1@example.com"
@javascript
Scenario: Teacher viewing student email, whilst site:showuseridentity = “email”
Given the following config values are set as admin:
| showuseridentity | email |
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
When I follow "Student NONE"
Then I should see "studentN@example.com"
And I navigate to course participants
When I follow "Student MEMBERS"
Then I should see "studentM@example.com"
@javascript
Scenario: Teacher viewing student email, whilst site:showuseridentity = “”
Given I log in as "teacher1"
And the following config values are set as admin:
| showuseridentity | |
And I am on "Course 1" course homepage
And I navigate to course participants
When I follow "Student NONE"
Then I should not see "studentN@example.com"
And I navigate to course participants
When I follow "Student MEMBERS"
Then I should see "studentM@example.com"
@javascript
Scenario: User can see user's email address settings on own profile
Given I log in as "studentp"
And I follow "Profile" in the user menu
Then I should see "studentP@example.com"
And I should see "(Visible to other course participants)"
When I click on "Edit profile" "link" in the "region-main" "region"
And I set the following fields to these values:
| maildisplay | 0 |
And I click on "Update profile" "button"
Then I should see "(Hidden from everyone except users with appropriate permissions)"
When I click on "Edit profile" "link" in the "region-main" "region"
And I set the following fields to these values:
| maildisplay | 1 |
And I click on "Update profile" "button"
Then I should see "(Visible to everyone)"
@@ -0,0 +1,29 @@
@core @core_user @javascript
Feature: Verify the breadcrumbs in users account and cohort site administration pages
Whenever I navigate to pages under users tab in site administration
As an admin
The breadcrumbs should be visible
Background:
Given I log in as "admin"
@core @core_user
Scenario: Verify the breadcrumbs in users tab as an admin
Given I navigate to "Users > Accounts > Add a new user" in site administration
And "Add a new user" "text" should exist in the ".breadcrumb" "css_element"
And "Accounts" "link" should exist in the ".breadcrumb" "css_element"
And I navigate to "Users > Accounts > Cohorts" in site administration
And "Cohorts" "text" should exist in the ".breadcrumb" "css_element"
And "Accounts" "link" should exist in the ".breadcrumb" "css_element"
When I click on "All cohorts" "link"
Then "All cohorts" "text" should exist in the ".breadcrumb" "css_element"
And "Cohorts" "link" should exist in the ".breadcrumb" "css_element"
And "Accounts" "link" should exist in the ".breadcrumb" "css_element"
And I click on "Add new cohort" "link"
And "Add new cohort" "text" should exist in the ".breadcrumb" "css_element"
And "Cohorts" "link" should exist in the ".breadcrumb" "css_element"
And "Accounts" "link" should exist in the ".breadcrumb" "css_element"
And I click on "Upload cohorts" "link"
And "Upload cohorts" "text" should exist in the ".breadcrumb" "css_element"
And "Cohorts" "link" should exist in the ".breadcrumb" "css_element"
And "Accounts" "link" should exist in the ".breadcrumb" "css_element"
@@ -0,0 +1,49 @@
@core @core_user
Feature: The visibility of table columns can be toggled
In order to customise my view of participants data
As a user
I need to be able to hide and show columns in the participants table
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| t1 | Agatha | T | agatha@example.com |
| s1 | Matilda | W | matilda@example.com |
| s2 | Mick | H | mick@example.com |
And the following "course enrolments" exist:
| user | course | role |
| t1 | C1 | editingteacher |
| s1 | C1 | student |
| s2 | C1 | student |
@javascript
Scenario: The visibility of columns can be individually toggled within the participants table
Given I log in as "t1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I should see "Email address" in the "participants" "table"
And I should see "matilda@example.com" in the "participants" "table"
And I should see "Roles" in the "participants" "table"
And I should see "Student" in the "participants" "table"
When I follow "Hide Email address"
Then I should not see "Email address" in the "participants" "table"
And I should not see "matilda@example.com" in the "participants" "table"
And I should see "Roles" in the "participants" "table"
And I should see "Student" in the "participants" "table"
And I follow "Hide Roles"
And I should not see "Roles" in the "participants" "table"
And I should not see "Student" in the "participants" "table"
And I should not see "matilda@example.com" in the "participants" "table"
And I follow "Show Email address"
And I should see "Email address" in the "participants" "table"
And I should see "matilda@example.com" in the "participants" "table"
And I should not see "Roles" in the "participants" "table"
And I should not see "Student" in the "participants" "table"
And I follow "Show Roles"
And I should see "Roles" in the "participants" "table"
And I should see "Student" in the "participants" "table"
And I should see "Email address" in the "participants" "table"
And I should see "matilda@example.com" in the "participants" "table"
+47
View File
@@ -0,0 +1,47 @@
@core @core_user
Feature: Tables can be sorted by additional names
In order to sort fields by additional names
As a user
I need to browse to a page with users in a table.
Background:
Given the following "users" exist:
| username | firstname | lastname | middlename | alternatename | email | idnumber |
| student1 | Annie | Edison | Faith | Anne | student1@example.com | s1 |
| student2 | George | Bradley | David | Gman | student2@example.com | s2 |
| student3 | Travis | Sutcliff | Peter | Mr T | student3@example.com | s3 |
And I log in as "admin"
And I navigate to "Users > Permissions > User policies" in site administration
And the following config values are set as admin:
| fullnamedisplay | firstname middlename lastname |
| alternativefullnameformat | firstname middlename alternatename lastname |
@javascript
Scenario: All user names are show and sortable in the administration user list.
Given I navigate to "Users > Accounts > Browse list of users" in site administration
Then the following should exist in the "reportbuilder-table" table:
| First name | Email address |
| Admin User | moodle@example.com |
| Annie Faith Anne Edison | student1@example.com |
| George David Gman Bradley | student2@example.com |
| Travis Peter Mr T Sutcliff | student3@example.com |
And "Annie Faith Anne Edison" "table_row" should appear before "George David Gman Bradley" "table_row"
And "George David Gman Bradley" "table_row" should appear before "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Middle name"
And "George David Gman Bradley" "table_row" should appear before "Annie Faith Anne Edison" "table_row"
And "Annie Faith Anne Edison" "table_row" should appear before "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Middle name"
And "George David Gman Bradley" "table_row" should appear after "Annie Faith Anne Edison" "table_row"
And "Annie Faith Anne Edison" "table_row" should appear after "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Alternate name"
And "Annie Faith Anne Edison" "table_row" should appear after "George David Gman Bradley" "table_row"
And "George David Gman Bradley" "table_row" should appear after "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Alternate name"
And "Annie Faith Anne Edison" "table_row" should appear before "George David Gman Bradley" "table_row"
And "George David Gman Bradley" "table_row" should appear before "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Last name"
And "George David Gman Bradley" "table_row" should appear before "Annie Faith Anne Edison" "table_row"
And "Annie Faith Anne Edison" "table_row" should appear before "Travis Peter Mr T Sutcliff" "table_row"
And I follow "Last name"
And "George David Gman Bradley" "table_row" should appear after "Annie Faith Anne Edison" "table_row"
And "Annie Faith Anne Edison" "table_row" should appear after "Travis Peter Mr T Sutcliff" "table_row"
@@ -0,0 +1,91 @@
@core @core_user @javascript
Feature: The student can navigate to their grades page and user grade report.
In order to view my grades and the user grade report
As a user
I need to log in and browse to my grades.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
| parent1 | Parent | 1 | parent1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
| Course 2 | C2 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
| student1 | C2 | student |
And the following "activities" exist:
| activity | course | idnumber | name | grade |
| assign | C1 | a1 | Test assignment one | 300 |
| assign | C1 | a2 | Test assignment two | 100 |
| assign | C1 | a3 | Test assignment three | 150 |
| assign | C2 | a4 | Test assignment four | 150 |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment one | student1 | 150.00 |
| Test assignment two | student1 | 67.00 |
Scenario: Navigation to Grades and the user grade report.
When I log in as "student1"
And I follow "Grades" in the user menu
Then the following should exist in the "overview-grade" table:
| Course name | Grade |
| Course 2 | - |
| Course 1 | 217.00 |
And I click on "Course 1" "link" in the "region-main" "region"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 75.00 % | 150.00 | 0300 | 50.00 % | 37.50 % |
| Test assignment two | 25.00 % | 67.00 | 0100 | 67.00 % | 16.75 % |
| Test assignment three | 0.00 %( Empty ) | - | 0150 | - | 0.00 % |
Scenario: Change Grades settings to go to a custom url.
Given the following config values are set as admin:
| grade_mygrades_report | external |
| gradereport_mygradeurl | /badges/mybadges.php |
And I log in as "student1"
And I follow "Grades" in the user menu
Then I should see "My badges from Acceptance test site web site"
Scenario: Log in as a parent and view a childs grades.
Given the following "role" exists:
| shortname | Parent |
| name | Parent |
| context_user | 1 |
| moodle/user:editprofile | allow |
| moodle/user:viewalldetails | allow |
| moodle/user:viewuseractivitiesreport | allow |
| moodle/user:viewdetails | allow |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| mentees | System | 1 | site-index | side-pre |
When I log in as "admin"
And I am on site homepage
And I am on the "student1" "user > profile" page
And I click on "Preferences" "link" in the ".profile_tree" "css_element"
And I follow "Assign roles relative to this user"
And I follow "Parent"
And I set the field "Potential users" to "Parent 1 (parent1@example.com)"
And I click on "Add" "button" in the "#page-content" "css_element"
And I log out
And I log in as "parent1"
And I am on site homepage
And I follow "Student 1"
And I follow "Grades overview"
Then the following should exist in the "overview-grade" table:
| Course name | Grade |
| Course 2 | - |
| Course 1 | 217.00 |
And I click on "Course 1" "link" in the "region-main" "region"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 75.00 % | 150.00 | 0300 | 50.00 % | 37.50 % |
| Test assignment two | 25.00 % | 67.00 | 0100 | 67.00 % | 16.75 % |
| Test assignment three | 0.00 %( Empty ) | - | 0150 | - | 0.00 % |
+24
View File
@@ -0,0 +1,24 @@
@core @core_user @javascript
Feature: The admin can check student permission in moodle system.
In order to check permission of a user in moodle
As an admin user
I can search student and see their permission
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
Scenario: The search setting is saved for each user.
Given I log in as "admin"
And I navigate to "Users > Permissions > Check system permissions" in site administration
And I follow "Search options"
And the field "from start" matches value "1"
And I click on "anywhere" "radio"
And I click on "Keep selected users, even if they no longer match the search" "checkbox"
And I click on "If only one user matches the search, select them automatically" "checkbox"
And I reload the page
Then the field "from start" matches value "0"
And the field "anywhere" matches value "1"
And the field "Keep selected users, even if they no longer match the search" matches value "1"
And the field "If only one user matches the search, select them automatically" matches value "1"
@@ -0,0 +1,20 @@
@core @core_user @_file_upload @javascript
Feature: The private files page allows users to store files privately in moodle.
In order to store a private file in moodle
As an authenticated user
I can upload the file to my private files area from the private files page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
Scenario: Upload a file to the private files area from the private files page
Given I log in as "user1"
And I follow "Private files" in the user menu
And I should see "User 1" in the ".page-context-header" "css_element"
And I should see "Private files" in the "region-main" "region"
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
When I press "Save changes"
Then I should see "1" elements in "Files" filemanager
And I should see "empty.txt" in the ".fp-content .fp-file" "css_element"
+188
View File
@@ -0,0 +1,188 @@
@core @core_user
Feature: Access to full profiles of users
In order to allow visibility of full profiles
As an admin
I need to set global permission or disable forceloginforprofiles
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format | groupmode |
| Course 1 | C1 | topics | 0 |
| Course 2 | C2 | topics | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| student1 | C2 | student |
| student3 | C2 | student |
And the following config values are set as admin:
| messaging | 1 |
Scenario: Viewing full profiles with default settings
When I log in as "student1"
# Another student's full profile is visible
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 2"
Then I should see "Full profile"
# Teacher's full profile is visible
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Teacher 1"
And I follow "Full profile"
And I should see "First access to site"
# Own full profile is visible
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Student 1" "link" in the "#participants" "css_element"
And I follow "Full profile"
And I should see "First access to site"
Scenario: Viewing full profiles with forceloginforprofiles off
Given the following config values are set as admin:
| forceloginforprofiles | 0 |
When I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 2"
And I follow "Full profile"
Then I should see "First access to site"
Scenario: Viewing full profiles with global permission
Given the following "role capability" exists:
| role | user |
| moodle/user:viewdetails | allow |
When I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 2"
And I follow "Full profile"
Then I should see "First access to site"
Scenario: Viewing full profiles of students as a teacher
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 1"
And I follow "Full profile"
Then I should see "First access to site"
Scenario: Viewing own full profile
Given I log in as "student1"
When I follow "Profile" in the user menu
Then I should see "First access to site"
Scenario: View only shared groups in a course with separate groups forced
Given the following "groups" exist:
| name | course | idnumber |
| Group 1 | C2 | G1 |
| Group 2 | C2 | G2 |
And the following "group members" exist:
| user | group |
| student1 | G1 |
| student3 | G2 |
| teacher1 | G1 |
| teacher1 | G2 |
When I log in as "student3"
And I am on "Course 2" course homepage
And I navigate to course participants
And I follow "Teacher 1"
Then I should see "Group 2"
And I should not see "Group 1"
Scenario: View all groups in a course with visible groups
Given the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
| Group 2 | C1 | G2 |
And the following "group members" exist:
| user | group |
| student1 | G1 |
| student2 | G2 |
| teacher1 | G1 |
| teacher1 | G2 |
When I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Teacher 1"
Then I should see "Group 1"
And I should see "Group 2"
@javascript
Scenario: Viewing full profiles of someone with the course contact role
Given I log in as "admin"
And I navigate to "Appearance > Courses" in site administration
And I set the following fields to these values:
| Course creator | 1 |
And I press "Save changes"
And I navigate to "Users > Permissions > Assign system roles" in site administration
And I follow "Course creator"
And I click on "//div[@class='userselector']/descendant::option[contains(., 'Student 3')]" "xpath_element"
And I press "Add"
And I log out
# Message search will not return a course contact unless the searcher shares a course with them,
# or site-wide messaging is enabled ($CFG->messagingallusers).
When I log in as "student1"
And I open messaging
And I search for "Student 3" in messaging
Then I should see "Student 3"
And I log out
When I log in as "student2"
And I open messaging
And I search for "Student 3" in messaging
Then I should see "No results"
@javascript
Scenario: View full profiles of someone in the same group in a course with separate groups.
Given I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Group mode | Separate groups |
| Force group mode | Yes |
And I press "Save and display"
And I log out
And the following "message contacts" exist:
| user | contact |
| student1 | student2 |
When I log in as "student1"
And I view the "Student 2" contact in the message area
And I should not see "First access to site"
And I should see "The details of this user are not available to you"
And I log out
And I log in as "admin"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 1 |
And I press "Save changes"
And I add "Student 1 (student1@example.com)" user to "Group 1" group members
And I add "Student 2 (student2@example.com)" user to "Group 1" group members
And I log out
And I log in as "student1"
And I view the "Student 2" contact in the message area
Then I should see "First access to site"
@javascript
Scenario: Accessibility, users can not click on profile image when on user's profile page.
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to course participants
Then "//img[contains(@class, 'userpicture')]" "xpath_element" should exist
And "//a/child::img[contains(@class, 'userpicture')]" "xpath_element" should exist
When I follow "Teacher 1"
Then I should see "Teacher 1"
And "//img[contains(@class, 'userpicture')]" "xpath_element" should exist
And "//a/child::img[contains(@class, 'userpicture')]" "xpath_element" should not exist
When I follow "Full profile"
And I should see "Teacher 1"
Then "//img[contains(@class, 'userpicture')]" "xpath_element" should exist
And "//a/child::img[contains(@class, 'userpicture')]" "xpath_element" should not exist
+282
View File
@@ -0,0 +1,282 @@
@core @core_user
Feature: View course participants
In order to know who is on a course
As a teacher
I need to be able to view the participants on a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1x | Teacher | 1x | teacher1x@example.com |
| student0x | Student | 0x | student0x@example.com |
| student1x | Student | 1x | student1x@example.com |
| student2x | Student | 2x | student2x@example.com |
| student3x | Student | 3x | student3x@example.com |
| student4x | Student | 4x | student4x@example.com |
| student5x | Student | 5x | student5x@example.com |
| student6x | Student | 6x | student6x@example.com |
| student7x | Student | 7x | student7x@example.com |
| student8x | Student | 8x | student8x@example.com |
| student9x | Student | 9x | student9x@example.com |
| student10x | Student | 10x | student10x@example.com |
| student11x | Student | 11x | student11x@example.com |
| student12x | Student | 12x | student12x@example.com |
| student13x | Student | 13x | student13x@example.com |
| student14x | Student | 14x | student14x@example.com |
| student15x | Student | 15x | student15x@example.com |
| student16x | Student | 16x | student16x@example.com |
| student17x | Student | 17x | student17x@example.com |
| student18x | Student | 18x | student18x@example.com |
| student19x | Student | 19x | student19x@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role | status | timeend |
| teacher1x | C1 | editingteacher | 0 | 0 |
| student0x | C1 | student | 0 | 0 |
| student1x | C1 | student | 0 | 0 |
| student2x | C1 | student | 0 | 0 |
| student3x | C1 | student | 0 | 0 |
| student4x | C1 | student | 0 | 0 |
| student5x | C1 | student | 0 | 0 |
| student6x | C1 | student | 0 | 0 |
| student7x | C1 | student | 0 | 0 |
| student8x | C1 | student | 0 | 0 |
| student9x | C1 | student | 0 | 0 |
| student10x | C1 | student | 1 | 0 |
| student11x | C1 | student | 0 | 100 |
| student12x | C1 | student | 0 | 0 |
| student13x | C1 | student | 0 | 0 |
| student14x | C1 | student | 0 | 0 |
| student15x | C1 | student | 0 | 0 |
| student16x | C1 | student | 0 | 0 |
| student17x | C1 | student | 0 | 0 |
| student18x | C1 | student | 0 | 0 |
@javascript
Scenario: Use select and deselect all buttons
Given I log in as "teacher1x"
And I am on "Course 1" course homepage
And I navigate to course participants
When I click on "Select all" "checkbox"
Then the field "Select 'Teacher 1x'" matches value "1"
And the field "Select 'Student 0x'" matches value "1"
And the field "Select 'Student 1x'" matches value "1"
And the field "Select 'Student 2x'" matches value "1"
And the field "Select 'Student 3x'" matches value "1"
And the field "Select 'Student 4x'" matches value "1"
And the field "Select 'Student 5x'" matches value "1"
And the field "Select 'Student 6x'" matches value "1"
And the field "Select 'Student 7x'" matches value "1"
And the field "Select 'Student 8x'" matches value "1"
And the field "Select 'Student 9x'" matches value "1"
And the field "Select 'Student 10x'" matches value "1"
And the field "Select 'Student 11x'" matches value "1"
And the field "Select 'Student 12x'" matches value "1"
And the field "Select 'Student 13x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 15x'" matches value "1"
And the field "Select 'Student 16x'" matches value "1"
And the field "Select 'Student 17x'" matches value "1"
And the field "Select 'Student 18x'" matches value "1"
And I click on "Deselect all" "checkbox"
And the field "Select 'Teacher 1x'" matches value "0"
And the field "Select 'Student 0x'" matches value "0"
And the field "Select 'Student 1x'" matches value "0"
And the field "Select 'Student 2x'" matches value "0"
And the field "Select 'Student 3x'" matches value "0"
And the field "Select 'Student 4x'" matches value "0"
And the field "Select 'Student 5x'" matches value "0"
And the field "Select 'Student 6x'" matches value "0"
And the field "Select 'Student 7x'" matches value "0"
And the field "Select 'Student 8x'" matches value "0"
And the field "Select 'Student 9x'" matches value "0"
And the field "Select 'Student 10x'" matches value "0"
And the field "Select 'Student 11x'" matches value "0"
And the field "Select 'Student 12x'" matches value "0"
And the field "Select 'Student 13x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 15x'" matches value "0"
And the field "Select 'Student 16x'" matches value "0"
And the field "Select 'Student 17x'" matches value "0"
And the field "Select 'Student 18x'" matches value "0"
@javascript
Scenario: Sort and paginate the list of users
Given I log in as "teacher1x"
And the following "course enrolments" exist:
| user | course | role |
| student19x | C1 | student |
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Email address"
When I click on "2" "link" in the "//nav[@aria-label='Page']" "xpath_element"
Then I should not see "student0x@example.com"
And I should not see "student19x@example.com"
And I should see "teacher1x@example.com"
And I follow "Email address"
And I click on "2" "link" in the "//nav[@aria-label='Page']" "xpath_element"
And I should not see "teacher1x@example.com"
And I should not see "student19x@example.com"
And I should not see "student1x@example.com"
And I should see "student0x@example.com"
@javascript
Scenario: Use select all users on this page, select all users and deselect all
Given the following "course enrolments" exist:
| user | course | role |
| student19x | C1 | student |
When I log in as "teacher1x"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Select all" "checkbox"
Then I should not see "Student 9x"
And the field "Select 'Teacher 1x'" matches value "1"
And the field "Select 'Student 0x'" matches value "1"
And the field "Select 'Student 1x'" matches value "1"
And the field "Select 'Student 2x'" matches value "1"
And the field "Select 'Student 3x'" matches value "1"
And the field "Select 'Student 4x'" matches value "1"
And the field "Select 'Student 5x'" matches value "1"
And the field "Select 'Student 6x'" matches value "1"
And the field "Select 'Student 7x'" matches value "1"
And the field "Select 'Student 8x'" matches value "1"
And the field "Select 'Student 10x'" matches value "1"
And the field "Select 'Student 11x'" matches value "1"
And the field "Select 'Student 12x'" matches value "1"
And the field "Select 'Student 13x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 15x'" matches value "1"
And the field "Select 'Student 16x'" matches value "1"
And the field "Select 'Student 17x'" matches value "1"
And the field "Select 'Student 18x'" matches value "1"
And the field "Select 'Student 19x'" matches value "1"
And I click on "Deselect all" "checkbox"
And the field "Select 'Teacher 1x'" matches value "0"
And the field "Select 'Student 0x'" matches value "0"
And the field "Select 'Student 1x'" matches value "0"
And the field "Select 'Student 2x'" matches value "0"
And the field "Select 'Student 3x'" matches value "0"
And the field "Select 'Student 4x'" matches value "0"
And the field "Select 'Student 5x'" matches value "0"
And the field "Select 'Student 6x'" matches value "0"
And the field "Select 'Student 7x'" matches value "0"
And the field "Select 'Student 8x'" matches value "0"
And the field "Select 'Student 10x'" matches value "0"
And the field "Select 'Student 11x'" matches value "0"
And the field "Select 'Student 12x'" matches value "0"
And the field "Select 'Student 13x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 15x'" matches value "0"
And the field "Select 'Student 16x'" matches value "0"
And the field "Select 'Student 17x'" matches value "0"
And the field "Select 'Student 18x'" matches value "0"
And the field "Select 'Student 19x'" matches value "0"
# Pressing the "Select all X users" button should select all including the 21st user (Student 9x).
And I press "Select all 21 users"
And I should see "Student 9x"
And the field "Select 'Teacher 1x'" matches value "1"
And the field "Select 'Student 0x'" matches value "1"
And the field "Select 'Student 1x'" matches value "1"
And the field "Select 'Student 2x'" matches value "1"
And the field "Select 'Student 3x'" matches value "1"
And the field "Select 'Student 4x'" matches value "1"
And the field "Select 'Student 5x'" matches value "1"
And the field "Select 'Student 6x'" matches value "1"
And the field "Select 'Student 7x'" matches value "1"
And the field "Select 'Student 8x'" matches value "1"
And the field "Select 'Student 9x'" matches value "1"
And the field "Select 'Student 10x'" matches value "1"
And the field "Select 'Student 11x'" matches value "1"
And the field "Select 'Student 12x'" matches value "1"
And the field "Select 'Student 13x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 14x'" matches value "1"
And the field "Select 'Student 15x'" matches value "1"
And the field "Select 'Student 16x'" matches value "1"
And the field "Select 'Student 17x'" matches value "1"
And the field "Select 'Student 18x'" matches value "1"
And the field "Select 'Student 19x'" matches value "1"
And the "With selected users..." "select" should be enabled
And I click on "Deselect all" "checkbox"
And the field "Select 'Teacher 1x'" matches value "0"
And the field "Select 'Student 0x'" matches value "0"
And the field "Select 'Student 1x'" matches value "0"
And the field "Select 'Student 2x'" matches value "0"
And the field "Select 'Student 3x'" matches value "0"
And the field "Select 'Student 4x'" matches value "0"
And the field "Select 'Student 5x'" matches value "0"
And the field "Select 'Student 6x'" matches value "0"
And the field "Select 'Student 7x'" matches value "0"
And the field "Select 'Student 8x'" matches value "0"
And the field "Select 'Student 9x'" matches value "0"
And the field "Select 'Student 10x'" matches value "0"
And the field "Select 'Student 11x'" matches value "0"
And the field "Select 'Student 12x'" matches value "0"
And the field "Select 'Student 13x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 14x'" matches value "0"
And the field "Select 'Student 15x'" matches value "0"
And the field "Select 'Student 16x'" matches value "0"
And the field "Select 'Student 17x'" matches value "0"
And the field "Select 'Student 18x'" matches value "0"
And the field "Select 'Student 19x'" matches value "0"
Scenario: View the participants page as a teacher
Given I log in as "teacher1x"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Active" in the "student0x" "table_row"
Then I should see "Active" in the "student1x" "table_row"
And I should see "Active" in the "student2x" "table_row"
And I should see "Active" in the "student3x" "table_row"
And I should see "Active" in the "student4x" "table_row"
And I should see "Active" in the "student5x" "table_row"
And I should see "Active" in the "student6x" "table_row"
And I should see "Active" in the "student7x" "table_row"
And I should see "Active" in the "student8x" "table_row"
And I should see "Active" in the "student9x" "table_row"
And I should see "Suspended" in the "student10x" "table_row"
And I should see "Not current" in the "student11x" "table_row"
And I should see "Active" in the "student12x" "table_row"
And I should see "Active" in the "student13x" "table_row"
And I should see "Active" in the "student14x" "table_row"
And I should see "Active" in the "student15x" "table_row"
And I should see "Active" in the "student16x" "table_row"
And I should see "Active" in the "student17x" "table_row"
And I should see "Active" in the "student18x" "table_row"
Scenario: View the participants page as a student
Given I log in as "student1x"
And I am on "Course 1" course homepage
When I navigate to course participants
# Student should not see the status column.
Then I should not see "Status" in the "participants" "table"
# Student should be able to see the other actively-enrolled students.
And I should see "Student 1x" in the "participants" "table"
And I should see "Student 2x" in the "participants" "table"
And I should see "Student 3x" in the "participants" "table"
And I should see "Student 4x" in the "participants" "table"
And I should see "Student 5x" in the "participants" "table"
And I should see "Student 6x" in the "participants" "table"
And I should see "Student 7x" in the "participants" "table"
And I should see "Student 8x" in the "participants" "table"
# Suspended and non-current students should not be rendered.
And I should not see "Student 10x" in the "participants" "table"
And I should not see "Student 11x" in the "participants" "table"
Scenario: Check status after disabling manual enrolment
Given I log in as "admin"
And I am on the "Course 1" "enrolment methods" page
And I click on "Disable" "link" in the "Manual enrolments" "table_row"
Then I navigate to course participants
And I should see "Not current" in the "student0x" "table_row"
@@ -0,0 +1,86 @@
@core @core_user
Feature: View course participants groups
In order to know who is on a course
As a teacher
I need to be able to view the participants groups on a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1x | Teacher | 1x | teacher1x@example.com |
| student1x | Student | 1x | student1x@example.com |
| student2x | Student | 2x | student2x@example.com |
| student3x | Student | 3x | student3x@example.com |
| student4x | Student | 4x | student4x@example.com |
And the following "courses" exist:
| fullname | shortname | format | groupmode |
| Course 1 | C1 | topics | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1x | C1 | editingteacher |
| student1x | C1 | student |
| student2x | C1 | student |
| student3x | C1 | student |
| student4x | C1 | student |
And the following "groups" exist:
| name | course | idnumber |
| Group A | C1 | G1 |
| Group B | C1 | G2 |
And the following "group members" exist:
| user | group |
| student1x | G1 |
| student2x | G1 |
| student3x | G2 |
| student4x | G2 |
Scenario: User should not be able to see other groups in separated group mode
Given I log in as "student1x"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Group A"
And I should see "Student 1x"
And I should see "Student 2x"
And I should not see "Group B"
And I should not see "Student 3x"
And I should not see "Student 4x"
@javascript
Scenario: User should be able to see other groups in visible group mode
Given I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the field "Group mode" to "Visible groups"
And I press "Save and display"
And I log out
And I log in as "student1x"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Group A"
And I should see "Student 1x"
And I should see "Student 2x"
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I should see "Student 3x"
And I should see "Student 4x"
Scenario: User should be able to see all users in no groups mode
Given I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the field "Group mode" to "No groups"
And I press "Save and display"
And I log out
And I log in as "student1x"
And I am on "Course 1" course homepage
When I navigate to course participants
Then I should see "Group A"
And I should see "Student 1x"
And I should see "Student 2x"
And I should see "Group B"
And I should see "Student 3x"
And I should see "Student 4x"
And I should see "Teacher 1x"
And I should see "No groups"
@@ -0,0 +1,78 @@
@core @core_user
Feature: Access to preferences page
In order to view the preferences page
As a user
I need global permissions to view the page.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| manager1 | Manager | 1 | manager1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
| parent | Parent | 1 | parent1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
| Course 2 | C2 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "system role assigns" exist:
| user | course | role |
| manager1 | Acceptance test site | manager |
Scenario: A student and teacher with normal permissions can not view another user's permissions page.
Given I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 2"
And I should not see "Preferences" in the "region-main" "region"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
When I navigate to course participants
And I follow "Student 2"
Then I should not see "Preferences" in the "region-main" "region"
Scenario: Administrators and Managers can view another user's permissions page.
Given I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to course participants
And I follow "Student 2"
And I should see "Preferences" in the "region-main" "region"
And I log out
And I log in as "manager1"
And I am on "Course 1" course homepage
When I navigate to course participants
And I follow "Student 2"
Then I should see "Preferences" in the "region-main" "region"
Scenario: A user with the appropriate permissions can view another user's permissions page.
Given the following "role" exists:
| shortname | Parent |
| name | Parent |
| context_user | 1 |
| moodle/user:editprofile | allow |
| moodle/user:viewalldetails | allow |
| moodle/user:viewuseractivitiesreport | allow |
| moodle/user:viewdetails | allow |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| mentees | System | 1 | site-index | side-pre |
When I log in as "admin"
And I am on site homepage
And I am on the "student1" "user > profile" page
And I click on "Preferences" "link" in the ".profile_tree" "css_element"
And I follow "Assign roles relative to this user"
And I follow "Parent"
And I set the field "Potential users" to "Parent 1 (parent1@example.com)"
And I click on "Add" "button" in the "#page-content" "css_element"
And I log out
And I log in as "parent"
And I am on site homepage
When I follow "Student 1"
Then I should see "Preferences" in the "region-main" "region"
+28
View File
@@ -0,0 +1,28 @@
<?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/>.
/**
* Coverage information for the core_user subsystem.
*
* @copyright 2021 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
/** @var array The list of files relative to the plugin root to include in coverage generation. */
protected $includelistfiles = [
'editlib.php',
];
};
+70
View File
@@ -0,0 +1,70 @@
<?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 core_user;
use stdClass;
/**
* Tests for the devicekey class.
*
* @package core_user
* @covers \core_user\devicekey
*/
class devicekey_test extends \advanced_testcase {
/**
* Helper to create a device record.
*
* @return stdClass
*/
protected function create_device_record(): stdClass {
global $USER, $DB;
$device = (object) [
'appid' => 'com.moodle.moodlemobile',
'name' => 'occam',
'model' => 'Nexus 4',
'platform' => 'Android',
'version' => '4.2.2',
'pushid' => 'apushdkasdfj4835',
'uuid' => 'ABCDE3723ksdfhasfaasef859',
'userid' => $USER->id,
'timecreated' => time(),
'timemodified' => time(),
];
$device->id = $DB->insert_record('user_devices', $device);
return $device;
}
public function test_update_device_public_key_no_device(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$device = $this->create_device_record();
$devicekeypair = sodium_crypto_box_keypair();
$publickey = sodium_bin2base64(
sodium_crypto_box_publickey($devicekeypair),
SODIUM_BASE64_VARIANT_ORIGINAL
);
$this->assertTrue(devicekey::update_device_public_key($device->uuid, $device->appid, $publickey));
$this->assertEquals($publickey, $DB->get_field('user_devices', 'publickey', ['id' => $device->id]));
}
}
+112
View File
@@ -0,0 +1,112 @@
<?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 core_user;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/user/editlib.php');
/**
* Unit tests for user editlib api.
*
* @package core_user
* @category test
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editlib_test extends \advanced_testcase {
/**
* Test that the required fields are returned in the correct order.
*/
function test_useredit_get_required_name_fields(): void {
global $CFG;
// Back up config settings for restore later.
$originalcfg = new \stdClass();
$originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
$CFG->fullnamedisplay = 'language';
$expectedresult = array(5 => 'firstname', 21 => 'lastname');
$this->assertEquals(useredit_get_required_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstname';
$expectedresult = array(5 => 'firstname', 21 => 'lastname');
$this->assertEquals(useredit_get_required_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'lastname firstname';
$expectedresult = array('lastname', 9 => 'firstname');
$this->assertEquals(useredit_get_required_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstnamephonetic lastnamephonetic';
$expectedresult = array(5 => 'firstname', 21 => 'lastname');
$this->assertEquals(useredit_get_required_name_fields(), $expectedresult);
// Tidy up after we finish testing.
$CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
}
/**
* Test that the enabled fields are returned in the correct order.
*/
function test_useredit_get_enabled_name_fields(): void {
global $CFG;
// Back up config settings for restore later.
$originalcfg = new \stdClass();
$originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
$CFG->fullnamedisplay = 'language';
$expectedresult = array();
$this->assertEquals(useredit_get_enabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstname lastname firstnamephonetic';
$expectedresult = array(19 => 'firstnamephonetic');
$this->assertEquals(useredit_get_enabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstnamephonetic, lastname lastnamephonetic (alternatename)';
$expectedresult = array('firstnamephonetic', 28 => 'lastnamephonetic', 46 => 'alternatename');
$this->assertEquals(useredit_get_enabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstnamephonetic lastnamephonetic alternatename middlename';
$expectedresult = array('firstnamephonetic', 18 => 'lastnamephonetic', 35 => 'alternatename', 49 => 'middlename');
$this->assertEquals(useredit_get_enabled_name_fields(), $expectedresult);
// Tidy up after we finish testing.
$CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
}
/**
* Test that the disabled fields are returned.
*/
function test_useredit_get_disabled_name_fields(): void {
global $CFG;
// Back up config settings for restore later.
$originalcfg = new \stdClass();
$originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
$CFG->fullnamedisplay = 'language';
$expectedresult = array('firstnamephonetic' => 'firstnamephonetic', 'lastnamephonetic' => 'lastnamephonetic',
'middlename' => 'middlename', 'alternatename' => 'alternatename');
$this->assertEquals(useredit_get_disabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstname lastname firstnamephonetic';
$expectedresult = array('lastnamephonetic' => 'lastnamephonetic', 'middlename' => 'middlename', 'alternatename' => 'alternatename');
$this->assertEquals(useredit_get_disabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstnamephonetic, lastname lastnamephonetic (alternatename)';
$expectedresult = array('middlename' => 'middlename');
$this->assertEquals(useredit_get_disabled_name_fields(), $expectedresult);
$CFG->fullnamedisplay = 'firstnamephonetic lastnamephonetic alternatename middlename';
$expectedresult = array();
$this->assertEquals(useredit_get_disabled_name_fields(), $expectedresult);
// Tidy up after we finish testing.
$CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
}
}
@@ -0,0 +1,125 @@
<?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 core_user\external;
use core_external\external_api;
use stdClass;
/**
* Tests for the devicekey class.
*
* @package core_user
* @covers \core_user\external\update_user_device_public_key
*/
class update_user_device_public_key_test extends \advanced_testcase {
/**
* Helper to create a device record.
*
* @return stdClass
*/
protected function create_device_record(): stdClass {
global $USER, $DB;
$device = (object) [
'appid' => 'com.moodle.moodlemobile',
'name' => 'occam',
'model' => 'Nexus 4',
'platform' => 'Android',
'version' => '4.2.2',
'pushid' => 'apushdkasdfj4835',
'uuid' => 'ABCDE3723ksdfhasfaasef859',
'userid' => $USER->id,
'timecreated' => time(),
'timemodified' => time(),
];
$device->id = $DB->insert_record('user_devices', $device);
return $device;
}
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
$device = $this->create_device_record();
$devicekeypair = sodium_crypto_box_keypair();
$publickey = sodium_bin2base64(
sodium_crypto_box_publickey($devicekeypair),
SODIUM_BASE64_VARIANT_ORIGINAL
);
// Test sending a key to a valid device.
$result = update_user_device_public_key::execute(
$device->uuid,
$device->appid,
$publickey,
);
$result = external_api::clean_returnvalue(update_user_device_public_key::execute_returns(), $result);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);
}
public function test_execute_with_invalid_device_appid(): void {
$this->resetAfterTest();
$this->setAdminUser();
$device = $this->create_device_record();
$devicekeypair = sodium_crypto_box_keypair();
$publickey = sodium_bin2base64(
sodium_crypto_box_publickey($devicekeypair),
SODIUM_BASE64_VARIANT_ORIGINAL
);
// Invalid appid.
$result = update_user_device_public_key::execute(
$device->uuid,
'invalidappid',
$publickey,
);
$result = external_api::clean_returnvalue(update_user_device_public_key::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
}
public function test_execute_with_invalid_device_uuid(): void {
$this->resetAfterTest();
$this->setAdminUser();
$device = $this->create_device_record();
$devicekeypair = sodium_crypto_box_keypair();
$publickey = sodium_bin2base64(
sodium_crypto_box_publickey($devicekeypair),
SODIUM_BASE64_VARIANT_ORIGINAL
);
// Invalid appid.
$result = update_user_device_public_key::execute(
'invaliduuid',
$device->appid,
$publickey,
);
$result = external_api::clean_returnvalue(update_user_device_public_key::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
}
}
File diff suppressed because it is too large Load Diff
+619
View File
@@ -0,0 +1,619 @@
<?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 core_user;
/**
* Unit tests for \core_user\fields
*
* @package core
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core_user\fields
*/
class fields_test extends \advanced_testcase {
/**
* Tests getting the user picture fields.
*/
public function test_get_picture_fields(): void {
$this->assertEquals(['id', 'picture', 'firstname', 'lastname', 'firstnamephonetic',
'lastnamephonetic', 'middlename', 'alternatename', 'imagealt', 'email'],
fields::get_picture_fields());
}
/**
* Tests getting the user name fields.
*/
public function test_get_name_fields(): void {
$this->assertEquals(['firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename',
'firstname', 'lastname'],
fields::get_name_fields());
$this->assertEquals(['firstname', 'lastname',
'firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename'],
fields::get_name_fields(true));
}
/**
* Tests getting the identity fields.
*/
public function test_get_identity_fields(): void {
global $DB, $CFG, $COURSE;
$this->resetAfterTest();
require_once($CFG->dirroot . '/user/profile/lib.php');
// Create custom profile fields, one with each visibility option.
$generator = self::getDataGenerator();
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A',
'visible' => PROFILE_VISIBLE_ALL]);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B',
'visible' => PROFILE_VISIBLE_PRIVATE]);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'c', 'name' => 'C',
'visible' => PROFILE_VISIBLE_NONE]);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'd', 'name' => 'D',
'visible' => PROFILE_VISIBLE_TEACHERS]);
// Set the extra user fields to include email, department, and all custom profile fields.
set_config('showuseridentity', 'email,department,profile_field_a,profile_field_b,' .
'profile_field_c,profile_field_d');
set_config('hiddenuserfields', 'email');
// Create a test course and a student in the course.
$course = $generator->create_course();
$coursecontext = \context_course::instance($course->id);
$user = $generator->create_user();
$anotheruser = $generator->create_user();
$usercontext = \context_user::instance($anotheruser->id);
$generator->enrol_user($user->id, $course->id, 'student');
// When no context is provided, it does no access checks and should return all specified (other than non-visible).
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b', 'profile_field_d'],
fields::get_identity_fields(null));
// If you turn off custom profile fields, you don't get those.
$this->assertEquals(['email', 'department'], fields::get_identity_fields(null, false));
// Request in context as an administator.
$this->setAdminUser();
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b',
'profile_field_c', 'profile_field_d'],
fields::get_identity_fields($coursecontext));
$this->assertEquals(['email', 'department'],
fields::get_identity_fields($coursecontext, false));
// Request in context as a student - they don't have any of the capabilities to see identity
// fields or profile fields.
$this->setUser($user);
$this->assertEquals([], fields::get_identity_fields($coursecontext));
// Give the student the basic identity fields permission (also makes them count as 'teacher'
// for the teacher-restricted field).
$COURSE = $course; // Horrible hack, because PROFILE_VISIBLE_TEACHERS relies on this global.
$roleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
role_change_permission($roleid, $coursecontext, 'moodle/site:viewuseridentity', CAP_ALLOW);
$this->assertEquals(['department', 'profile_field_a', 'profile_field_d'],
fields::get_identity_fields($coursecontext));
$this->assertEquals(['department'],
fields::get_identity_fields($coursecontext, false));
// Give them permission to view hidden user fields.
role_change_permission($roleid, $coursecontext, 'moodle/course:viewhiddenuserfields', CAP_ALLOW);
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_d'],
fields::get_identity_fields($coursecontext));
$this->assertEquals(['email', 'department'],
fields::get_identity_fields($coursecontext, false));
// Also give them permission to view all profile fields.
role_change_permission($roleid, $coursecontext, 'moodle/user:viewalldetails', CAP_ALLOW);
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b',
'profile_field_c', 'profile_field_d'],
fields::get_identity_fields($coursecontext));
$this->assertEquals(['email', 'department'],
fields::get_identity_fields($coursecontext, false));
// Even if we give them student role in the user context they can't view anything...
$generator->role_assign($roleid, $user->id, $usercontext->id);
$this->assertEquals([], fields::get_identity_fields($usercontext));
// Give them basic permission.
role_change_permission($roleid, $usercontext, 'moodle/site:viewuseridentity', CAP_ALLOW);
$this->assertEquals(['department', 'profile_field_a', 'profile_field_d'],
fields::get_identity_fields($usercontext));
$this->assertEquals(['department'],
fields::get_identity_fields($usercontext, false));
// Give them the hidden user fields permission (it's a different one).
role_change_permission($roleid, $usercontext, 'moodle/user:viewhiddendetails', CAP_ALLOW);
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_d'],
fields::get_identity_fields($usercontext));
$this->assertEquals(['email', 'department'],
fields::get_identity_fields($usercontext, false));
// Also give them permission to view all profile fields.
role_change_permission($roleid, $usercontext, 'moodle/user:viewalldetails', CAP_ALLOW);
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b',
'profile_field_c', 'profile_field_d'],
fields::get_identity_fields($usercontext));
$this->assertEquals(['email', 'department'],
fields::get_identity_fields($usercontext, false));
}
/**
* Test getting identity fields, when one of them refers to a non-existing custom profile field
*/
public function test_get_identity_fields_invalid(): void {
$this->resetAfterTest();
$this->getDataGenerator()->create_custom_profile_field([
'datatype' => 'text',
'shortname' => 'real',
'name' => 'I\'m real',
]);
// The "fake" profile field does not exist.
set_config('showuseridentity', 'email,profile_field_real,profile_field_fake');
$this->assertEquals([
'email',
'profile_field_real',
], fields::get_identity_fields(null));
}
/**
* Tests the get_required_fields function.
*
* This function composes the results of get_identity/name/picture_fields, so we are not going
* to test the details of the identity permissions as that was already covered. Just how they
* are included/combined.
*/
public function test_get_required_fields(): void {
$this->resetAfterTest();
// Set up some profile fields.
$generator = self::getDataGenerator();
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']);
set_config('showuseridentity', 'email,department,profile_field_a');
// What happens if you don't ask for anything?
$fields = fields::empty();
$this->assertEquals([], $fields->get_required_fields());
// Try each invidual purpose.
$fields = fields::for_identity(null);
$this->assertEquals(['email', 'department', 'profile_field_a'], $fields->get_required_fields());
$fields = fields::for_userpic();
$this->assertEquals(fields::get_picture_fields(), $fields->get_required_fields());
$fields = fields::for_name();
$this->assertEquals(fields::get_name_fields(), $fields->get_required_fields());
// Try combining them all. There should be no duplicates (e.g. email), and the 'id' field
// should be moved to the start.
$fields = fields::for_identity(null)->with_name()->with_userpic();
$this->assertEquals(['id', 'email', 'department', 'profile_field_a', 'picture',
'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic', 'middlename',
'alternatename', 'imagealt'], $fields->get_required_fields());
// Add some specified fields to a default result.
$fields = fields::for_identity(null, true)->including('city', 'profile_field_b');
$this->assertEquals(['email', 'department', 'profile_field_a', 'city', 'profile_field_b'],
$fields->get_required_fields());
// Remove some fields, one of which actually is in the list.
$fields = fields::for_identity(null, true)->excluding('email', 'city');
$this->assertEquals(['department', 'profile_field_a'], $fields->get_required_fields());
// Add and remove fields.
$fields = fields::for_identity(null, true)->including('city', 'profile_field_b')->excluding('city', 'department');
$this->assertEquals(['email', 'profile_field_a', 'profile_field_b'],
$fields->get_required_fields());
// Request the list without profile fields, check that still works with both sources.
$fields = fields::for_identity(null, false)->including('city', 'profile_field_b')->excluding('city', 'department');
$this->assertEquals(['email'], $fields->get_required_fields());
}
/**
* Tests the get_required_fields function when you use the $limitpurposes parameter.
*/
public function test_get_required_fields_limitpurposes(): void {
$this->resetAfterTest();
// Set up some profile fields.
$generator = self::getDataGenerator();
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']);
set_config('showuseridentity', 'email,department,profile_field_a');
// Create a fields object with all three purposes, plus included and excluded fields.
$fields = fields::for_identity(null, true)->with_name()->with_userpic()
->including('city', 'profile_field_b')->excluding('firstnamephonetic', 'middlename', 'alternatename');
// Check the result with all purposes.
$this->assertEquals(['id', 'email', 'department', 'profile_field_a', 'picture',
'firstname', 'lastname', 'lastnamephonetic', 'imagealt', 'city',
'profile_field_b'],
$fields->get_required_fields([fields::PURPOSE_IDENTITY, fields::PURPOSE_NAME,
fields::PURPOSE_USERPIC, fields::CUSTOM_INCLUDE]));
// Limit to identity and custom includes.
$this->assertEquals(['email', 'department', 'profile_field_a', 'city', 'profile_field_b'],
$fields->get_required_fields([fields::PURPOSE_IDENTITY, fields::CUSTOM_INCLUDE]));
// Limit to name fields.
$this->assertEquals(['firstname', 'lastname', 'lastnamephonetic'],
$fields->get_required_fields([fields::PURPOSE_NAME]));
}
/**
* There should be an exception if you try to 'limit' purposes to one that wasn't even included.
*/
public function test_get_required_fields_limitpurposes_not_in_constructor(): void {
$fields = fields::for_identity(null);
$this->expectExceptionMessage('$limitpurposes can only include purposes defined in object');
$fields->get_required_fields([fields::PURPOSE_USERPIC]);
}
/**
* Sets up data and a fields object for all the get_sql tests.
*
* @return fields Constructed fields object for testing
*/
protected function init_for_sql_tests(): fields {
$generator = self::getDataGenerator();
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']);
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']);
// Create a couple of users. One doesn't have a profile field set, so we can test that.
$generator->create_user(['profile_field_a' => 'A1', 'profile_field_b' => 'B1',
'city' => 'C1', 'department' => 'D1', 'email' => 'e1@example.org',
'idnumber' => 'XXX1', 'username' => 'u1']);
$generator->create_user(['profile_field_a' => 'A2',
'city' => 'C2', 'department' => 'D2', 'email' => 'e2@example.org',
'idnumber' => 'XXX2', 'username' => 'u2']);
// It doesn't matter how we construct it (we already tested get_required_fields which is
// where all those values are actually used) so let's just list the fields we want manually.
return fields::empty()->including('department', 'city', 'profile_field_a', 'profile_field_b');
}
/**
* Tests getting SQL (and actually using it).
*/
public function test_get_sql_variations(): void {
global $DB;
$this->resetAfterTest();
$fields = $this->init_for_sql_tests();
fields::reset_unique_identifier();
// Basic SQL.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] =
(array)$fields->get_sql();
$sql = "SELECT idnumber
$selects
FROM {user}
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
$expected1 = (object)['profile_field_a' => 'A1', 'profile_field_b' => 'B1',
'city' => 'C1', 'department' => 'D1', 'idnumber' => 'XXX1'];
$expected2 = (object)['profile_field_a' => 'A2', 'profile_field_b' => null,
'city' => 'C2', 'department' => 'D2', 'idnumber' => 'XXX2'];
$this->assertEquals($expected1, $records['XXX1']);
$this->assertEquals($expected2, $records['XXX2']);
$this->assertEquals([
'department' => '{user}.department',
'city' => '{user}.city',
'profile_field_a' => $DB->sql_compare_text('uf1d_1.data', 255),
'profile_field_b' => $DB->sql_compare_text('uf1d_2.data', 255)], $mappings);
// SQL using named params.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] =
(array)$fields->get_sql('', true);
$sql = "SELECT idnumber
$selects
FROM {user}
$joins
WHERE idnumber LIKE :idnum
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['idnum' => 'X%']));
$this->assertCount(2, $records);
$this->assertEquals($expected1, $records['XXX1']);
$this->assertEquals($expected2, $records['XXX2']);
// SQL using alias for user table.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] =
(array)$fields->get_sql('u');
$sql = "SELECT idnumber
$selects
FROM {user} u
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
$this->assertEquals($expected1, $records['XXX1']);
$this->assertEquals($expected2, $records['XXX2']);
$this->assertEquals([
'department' => 'u.department',
'city' => 'u.city',
'profile_field_a' => $DB->sql_compare_text('uf3d_1.data', 255),
'profile_field_b' => $DB->sql_compare_text('uf3d_2.data', 255)], $mappings);
// Returning prefixed fields.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] =
(array)$fields->get_sql('', false, 'u_');
$sql = "SELECT idnumber
$selects
FROM {user}
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
$expected1 = (object)['u_profile_field_a' => 'A1', 'u_profile_field_b' => 'B1',
'u_city' => 'C1', 'u_department' => 'D1', 'idnumber' => 'XXX1'];
$this->assertEquals($expected1, $records['XXX1']);
// Renaming the id field. We need to use a different set of fields so it actually has the
// id field.
$fields = fields::for_userpic();
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] =
(array)$fields->get_sql('', false, '', 'userid');
$sql = "SELECT idnumber
$selects
FROM {user}
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
// User id was renamed.
$this->assertObjectNotHasProperty('id', $records['XXX1']);
$this->assertObjectHasProperty('userid', $records['XXX1']);
// Other fields are normal (just try a couple).
$this->assertObjectHasProperty('firstname', $records['XXX1']);
$this->assertObjectHasProperty('imagealt', $records['XXX1']);
// Check the user id is actually right.
$this->assertEquals('XXX1',
$DB->get_field('user', 'idnumber', ['id' => $records['XXX1']->userid]));
// Rename the id field and also use a prefix.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] =
(array)$fields->get_sql('', false, 'u_', 'userid');
$sql = "SELECT idnumber
$selects
FROM {user}
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
// User id was renamed.
$this->assertObjectNotHasProperty('id', $records['XXX1']);
$this->assertObjectNotHasProperty('u_id', $records['XXX1']);
$this->assertObjectHasProperty('userid', $records['XXX1']);
// Other fields are prefixed (just try a couple).
$this->assertObjectHasProperty('u_firstname', $records['XXX1']);
$this->assertObjectHasProperty('u_imagealt', $records['XXX1']);
// Without a leading comma.
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] =
(array)$fields->get_sql('', false, '', '', false);
$sql = "SELECT $selects
FROM {user}
$joins
WHERE idnumber LIKE ?
ORDER BY idnumber";
$records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%']));
$this->assertCount(2, $records);
foreach ($records as $key => $record) {
// ID should be the first field used by get_records_sql.
$this->assertEquals($key, $record->id);
// Check 2 other sample properties.
$this->assertObjectHasProperty('firstname', $record);
$this->assertObjectHasProperty('imagealt', $record);
}
}
/**
* Tests what happens if you use the SQL multiple times in a query (i.e. that it correctly
* creates the different identifiers).
*/
public function test_get_sql_multiple(): void {
global $DB;
$this->resetAfterTest();
$fields = $this->init_for_sql_tests();
// Inner SQL.
['selects' => $selects1, 'joins' => $joins1, 'params' => $joinparams1] =
(array)$fields->get_sql('u1', true);
// Outer SQL.
$fields2 = fields::empty()->including('profile_field_a', 'email');
['selects' => $selects2, 'joins' => $joins2, 'params' => $joinparams2] =
(array)$fields2->get_sql('u2', true);
// Crazy combined query.
$sql = "SELECT username, details.profile_field_b AS innerb, details.city AS innerc
$selects2
FROM {user} u2
$joins2
LEFT JOIN (
SELECT u1.id
$selects1
FROM {user} u1
$joins1
WHERE idnumber LIKE :idnum
) details ON details.id = u2.id
ORDER BY username";
$records = $DB->get_records_sql($sql, array_merge($joinparams1, $joinparams2, ['idnum' => 'X%']));
// The left join won't match for admin.
$this->assertNull($records['admin']->innerb);
$this->assertNull($records['admin']->innerc);
// It should match for one of the test users though.
$expected1 = (object)['username' => 'u1', 'innerb' => 'B1', 'innerc' => 'C1',
'profile_field_a' => 'A1', 'email' => 'e1@example.org'];
$this->assertEquals($expected1, $records['u1']);
}
/**
* Tests the get_sql function when there are no fields to retrieve.
*/
public function test_get_sql_nothing(): void {
$fields = fields::empty();
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = (array)$fields->get_sql();
$this->assertEquals('', $selects);
$this->assertEquals('', $joins);
$this->assertEquals([], $joinparams);
}
/**
* Tests get_sql when there are no custom fields; in this scenario, the joins and joinparams
* are always blank.
*/
public function test_get_sql_no_custom_fields(): void {
$fields = fields::empty()->including('city', 'country');
['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] =
(array)$fields->get_sql('u');
$this->assertEquals(', u.city, u.country', $selects);
$this->assertEquals('', $joins);
$this->assertEquals([], $joinparams);
$this->assertEquals(['city' => 'u.city', 'country' => 'u.country'], $mappings);
}
/**
* Tests the format of the $selects string, which is important particularly for backward
* compatibility.
*/
public function test_get_sql_selects_format(): void {
global $DB;
$this->resetAfterTest();
fields::reset_unique_identifier();
$generator = self::getDataGenerator();
$generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']);
// When we list fields that include custom profile fields...
$fields = fields::empty()->including('id', 'profile_field_a');
// Supplying an alias: all fields have alias.
$selects = $fields->get_sql('u')->selects;
$this->assertEquals(', u.id, ' . $DB->sql_compare_text('uf1d_1.data', 255) . ' AS profile_field_a', $selects);
// No alias: all files have {user} because of the joins.
$selects = $fields->get_sql()->selects;
$this->assertEquals(', {user}.id, ' . $DB->sql_compare_text('uf2d_1.data', 255) . ' AS profile_field_a', $selects);
// When the list doesn't include custom profile fields...
$fields = fields::empty()->including('id', 'city');
// Supplying an alias: all fields have alias.
$selects = $fields->get_sql('u')->selects;
$this->assertEquals(', u.id, u.city', $selects);
// No alias: fields do not have alias at all.
$selects = $fields->get_sql()->selects;
$this->assertEquals(', id, city', $selects);
}
/**
* Data provider for {@see test_get_sql_fullname}
*
* @return array
*/
public function get_sql_fullname_provider(): array {
return [
['firstname lastname', 'FN LN'],
['lastname, firstname', 'LN, FN'],
['alternatename \'middlename\' lastname!', 'AN \'MN\' LN!'],
['[firstname lastname alternatename]', '[FN LN AN]'],
['firstnamephonetic lastnamephonetic', 'FNP LNP'],
['firstname alternatename lastname', 'FN AN LN'],
];
}
/**
* Test sql_fullname_display method with various fullname formats
*
* @param string $fullnamedisplay
* @param string $expectedfullname
*
* @dataProvider get_sql_fullname_provider
*/
public function test_get_sql_fullname(string $fullnamedisplay, string $expectedfullname): void {
global $DB;
$this->resetAfterTest();
set_config('fullnamedisplay', $fullnamedisplay);
$user = $this->getDataGenerator()->create_user([
'firstname' => 'FN',
'lastname' => 'LN',
'firstnamephonetic' => 'FNP',
'lastnamephonetic' => 'LNP',
'middlename' => 'MN',
'alternatename' => 'AN',
]);
[$sqlfullname, $params] = fields::get_sql_fullname('u');
$fullname = $DB->get_field_sql("SELECT {$sqlfullname} FROM {user} u WHERE u.id = :id", $params + [
'id' => $user->id,
]);
$this->assertEquals($expectedfullname, $fullname);
}
/**
* Test sql_fullname_display when one of the configured name fields is null
*/
public function test_get_sql_fullname_null_field(): void {
global $DB;
$this->resetAfterTest();
set_config('fullnamedisplay', 'firstname lastname alternatename');
$user = $this->getDataGenerator()->create_user([
'firstname' => 'FN',
'lastname' => 'LN',
]);
// Set alternatename field to null, ensure we still get result in later assertion.
$user->alternatename = null;
user_update_user($user, false);
[$sqlfullname, $params] = fields::get_sql_fullname('u');
$fullname = $DB->get_field_sql("SELECT {$sqlfullname} FROM {user} u WHERE u.id = :id", $params + [
'id' => $user->id,
]);
$this->assertEquals('FN LN ', $fullname);
}
}
+73
View File
@@ -0,0 +1,73 @@
<?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/>.
/**
* Defines fixutres for unit testing of lib/classes/myprofile/.
*
* @package core_user
* @category test
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class phpunit_fixture_myprofile_category
*
* @package core_user
* @category test
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class phpunit_fixture_myprofile_category extends \core_user\output\myprofile\category {
/**
* Make protected method public for testing.
*
* @param node $node
* @return node Nodes after the specified node.
*/
public function find_nodes_after($node) {
return parent::find_nodes_after($node);
}
/**
* Make protected method public for testing.
*/
public function validate_after_order() {
parent::validate_after_order();
}
}
/**
* Class phpunit_fixture_myprofile_tree
*
* @package core_user
* @category test
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class phpunit_fixture_myprofile_tree extends \core_user\output\myprofile\tree {
/**
* Make protected method public for testing.
*
* @param category $cat Category object
* @return array An array of category objects.
*/
public function find_categories_after($cat) {
return parent::find_categories_after($cat);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
// This file is part of Moodle - https://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/>.
/**
* Provides {@link testable_user_selector} class.
*
* @package core_user
* @subpackage fixtures
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Testable subclass of the user selector base class.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_user_selector extends user_selector_base {
/**
* Basic implementation of the users finder.
*
* @param string $search
* @return array of (string)optgroupname => array of users
*/
public function find_users($search) {
global $DB;
list($wherecondition, $whereparams) = $this->search_sql($search, 'u');
list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
$params = array_merge($whereparams, $sortparams);
$fields = $this->required_fields_sql('u');
$sql = "SELECT $fields
FROM {user} u
WHERE $wherecondition
ORDER BY $sort";
$found = $DB->get_records_sql($sql, $params);
if (empty($found)) {
return [];
}
return [get_string('potusers', 'core_role') => $found];
}
}
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - https://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 core_user;
use group_non_members_selector;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/user/selector/lib.php');
/**
* Unit tests for {@link group_non_members_selector} class.
*
* @package core_user
* @category test
* @copyright 2019 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_non_members_selector_test extends \advanced_testcase {
/**
* Test find_users that only return group non members
*/
public function test_find_users_only_return_group_non_member(): void {
$this->resetAfterTest();
// Create course.
$course = $this->getDataGenerator()->create_course();
// Create users.
$user1 = $this->getDataGenerator()->create_user(['firstname' => 'User', 'lastname' => '1']);
$user2 = $this->getDataGenerator()->create_user(['firstname' => 'User', 'lastname' => '2']);
$user3 = $this->getDataGenerator()->create_user(['firstname' => 'User', 'lastname' => '3']);
$user4 = $this->getDataGenerator()->create_user(['firstname' => 'User', 'lastname' => '4']);
$user5 = $this->getDataGenerator()->create_user(['firstname' => 'User', 'lastname' => '5']);
// Create group.
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
// Enroll users to course. Except User5.
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
$this->getDataGenerator()->enrol_user($user4->id, $course->id);
// Assign User1 to Group.
$this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user1->id]);
// User1 and User5 will not exist in the result.
// User2, User3 and User4 will exist in the result.
$potentialmembersselector = new group_non_members_selector('addselect',
['groupid' => $group->id, 'courseid' => $course->id]);
foreach ($potentialmembersselector->find_users('User') as $found) {
$this->assertCount(3, $found);
$this->assertArrayNotHasKey($user5->id, $found);
$this->assertArrayNotHasKey($user1->id, $found);
$this->assertArrayHasKey($user2->id, $found);
$this->assertArrayHasKey($user3->id, $found);
$this->assertArrayHasKey($user4->id, $found);
}
// Assign User2 to Group.
$this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
// User1, User2 and User5 will not exist in the result.
// User3 and User4 will exist in the result.
$potentialmembersselector = new group_non_members_selector('addselect',
['groupid' => $group->id, 'courseid' => $course->id]);
foreach ($potentialmembersselector->find_users('User') as $found) {
$this->assertCount(2, $found);
$this->assertArrayNotHasKey($user5->id, $found);
$this->assertArrayNotHasKey($user1->id, $found);
$this->assertArrayNotHasKey($user2->id, $found);
$this->assertArrayHasKey($user3->id, $found);
$this->assertArrayHasKey($user4->id, $found);
}
// Assign User3 to Group.
$this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
// User1, User2, User3 and User5 will not exist in the result.
// Only User4 will exist in the result.
$potentialmembersselector = new group_non_members_selector('addselect',
['groupid' => $group->id, 'courseid' => $course->id]);
foreach ($potentialmembersselector->find_users('User') as $found) {
$this->assertCount(1, $found);
$this->assertArrayNotHasKey($user5->id, $found);
$this->assertArrayNotHasKey($user1->id, $found);
$this->assertArrayNotHasKey($user2->id, $found);
$this->assertArrayNotHasKey($user3->id, $found);
$this->assertArrayHasKey($user4->id, $found);
}
}
}
+371
View File
@@ -0,0 +1,371 @@
<?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 core_user;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . "/user/tests/fixtures/myprofile_fixtures.php");
/**
* Unit tests for core_user\output\myprofile
*
* @package core_user
* @category test
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
*/
class myprofile_test extends \advanced_testcase {
/**
* Test node::__construct().
*/
public function test_node__construct(): void {
$node = new \core_user\output\myprofile\node('parentcat', 'nodename',
'nodetitle', 'after', 'www.google.com', 'description', new \pix_icon('i/course', ''), 'class1 class2');
$this->assertSame('parentcat', $node->parentcat);
$this->assertSame('nodename', $node->name);
$this->assertSame('nodetitle', $node->title);
$this->assertSame('after', $node->after);
$url = new \moodle_url('www.google.com');
$this->assertEquals($url, $node->url);
$this->assertEquals(new \pix_icon('i/course', ''), $node->icon);
$this->assertSame('class1 class2', $node->classes);
}
/**
* Test category::node_add().
*/
public function test_add_node(): void {
$tree = new \core_user\output\myprofile\tree();
$category = new \core_user\output\myprofile\category('category', 'categorytitle');
$node = new \core_user\output\myprofile\node('category', 'nodename',
'nodetitle', null, 'www.iAmaZombie.com', 'description');
$category->add_node($node);
$this->assertCount(1, $category->nodes);
$node = new \core_user\output\myprofile\node('category', 'nodename2',
'nodetitle', null, 'www.WorldisGonnaEnd.com', 'description');
$category->add_node($node);
$this->assertCount(2, $category->nodes);
$node = new \core_user\output\myprofile\node('category', 'nodename3',
'nodetitle', null, 'www.TeamBeardsFTW.com', 'description');
$tree->add_node($node);
$tree->add_category($category);
$tree->sort_categories();
$category = $tree->categories['category'];
$this->assertCount(3, $category->nodes);
}
/**
* Test category::__construct().
*/
public function test_category__construct(): void {
$category = new \core_user\output\myprofile\category('categoryname', 'title', 'after', 'class1 class2');
$this->assertSame('categoryname', $category->name);
$this->assertSame('title', $category->title);
$this->assertSame('after', $category->after);
$this->assertSame('class1 class2', $category->classes);
}
public function test_validate_after_order1(): void {
$category = new \phpunit_fixture_myprofile_category('category', 'title', null);
// Create nodes.
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle', null, null, 'content');
$node2 = new \core_user\output\myprofile\node('category', 'node2', 'nodetitle', 'node1', null, 'content');
$node3 = new \core_user\output\myprofile\node('category', 'node3', 'nodetitle', 'node2', null, null);
$category->add_node($node3);
$category->add_node($node2);
$category->add_node($node1);
$this->expectException(\coding_exception::class);
$category->validate_after_order();
}
public function test_validate_after_order2(): void {
$category = new \phpunit_fixture_myprofile_category('category', 'title', null);
// Create nodes.
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle', null, null, null);
$node2 = new \core_user\output\myprofile\node('category', 'node2', 'nodetitle', 'node1', null, 'content');
$node3 = new \core_user\output\myprofile\node('category', 'node3', 'nodetitle', 'node2', null, null);
$category->add_node($node3);
$category->add_node($node2);
$category->add_node($node1);
$this->expectException(\coding_exception::class);
$category->validate_after_order();
}
/**
* Test category::find_nodes_after().
*/
public function test_find_nodes_after(): void {
$category = new \phpunit_fixture_myprofile_category('category', 'title', null);
// Create nodes.
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle', null);
$node2 = new \core_user\output\myprofile\node('category', 'node2', 'nodetitle', 'node1');
$node3 = new \core_user\output\myprofile\node('category', 'node3', 'nodetitle', 'node2');
$node4 = new \core_user\output\myprofile\node('category', 'node4', 'nodetitle', 'node3');
$node5 = new \core_user\output\myprofile\node('category', 'node5', 'nodetitle', 'node3');
$node6 = new \core_user\output\myprofile\node('category', 'node6', 'nodetitle', 'node1');
// Add the nodes in random order.
$category->add_node($node3);
$category->add_node($node2);
$category->add_node($node4);
$category->add_node($node1);
$category->add_node($node5);
$category->add_node($node6);
// After node 1 we should have node2 - node3 - node4 - node5 - node6.
$return = $category->find_nodes_after($node1);
$this->assertCount(5, $return);
$node = array_shift($return);
$this->assertEquals($node2, $node);
$node = array_shift($return);
$this->assertEquals($node3, $node);
$node = array_shift($return);
$this->assertEquals($node4, $node);
$node = array_shift($return);
$this->assertEquals($node5, $node);
$node = array_shift($return);
$this->assertEquals($node6, $node);
// Last check also verifies calls for all subsequent nodes, still do some random checking.
$return = $category->find_nodes_after($node6);
$this->assertCount(0, $return);
$return = $category->find_nodes_after($node3);
$this->assertCount(2, $return);
}
/**
* Test category::sort_nodes().
*/
public function test_sort_nodes1(): void {
$category = new \phpunit_fixture_myprofile_category('category', 'title', null);
// Create nodes.
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle', null);
$node2 = new \core_user\output\myprofile\node('category', 'node2', 'nodetitle', 'node1');
$node3 = new \core_user\output\myprofile\node('category', 'node3', 'nodetitle', 'node2');
$node4 = new \core_user\output\myprofile\node('category', 'node4', 'nodetitle', 'node3');
$node5 = new \core_user\output\myprofile\node('category', 'node5', 'nodetitle', 'node3');
$node6 = new \core_user\output\myprofile\node('category', 'node6', 'nodetitle', 'node1');
// Add the nodes in random order.
$category->add_node($node3);
$category->add_node($node2);
$category->add_node($node4);
$category->add_node($node1);
$category->add_node($node5);
$category->add_node($node6);
// After node 1 we should have node2 - node3 - node4 - node5 - node6.
$category->sort_nodes();
$nodes = $category->nodes;
$this->assertCount(6, $nodes);
$node = array_shift($nodes);
$this->assertEquals($node1, $node);
$node = array_shift($nodes);
$this->assertEquals($node2, $node);
$node = array_shift($nodes);
$this->assertEquals($node3, $node);
$node = array_shift($nodes);
$this->assertEquals($node4, $node);
$node = array_shift($nodes);
$this->assertEquals($node5, $node);
$node = array_shift($nodes);
$this->assertEquals($node6, $node);
// Last check also verifies calls for all subsequent nodes, still do some random checking.
$return = $category->find_nodes_after($node6);
$this->assertCount(0, $return);
$return = $category->find_nodes_after($node3);
$this->assertCount(2, $return);
// Add a node with invalid 'after' and make sure an exception is thrown.
$node7 = new \core_user\output\myprofile\node('category', 'node7', 'nodetitle', 'noderandom');
$category->add_node($node7);
$this->expectException(\coding_exception::class);
$category->sort_nodes();
}
/**
* Test category::sort_nodes() with a mix of content and non content nodes.
*/
public function test_sort_nodes2(): void {
$category = new \phpunit_fixture_myprofile_category('category', 'title', null);
// Create nodes.
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle', null, null, 'content');
$node2 = new \core_user\output\myprofile\node('category', 'node2', 'nodetitle', 'node1', null, 'content');
$node3 = new \core_user\output\myprofile\node('category', 'node3', 'nodetitle', null);
$node4 = new \core_user\output\myprofile\node('category', 'node4', 'nodetitle', 'node3');
$node5 = new \core_user\output\myprofile\node('category', 'node5', 'nodetitle', 'node3');
$node6 = new \core_user\output\myprofile\node('category', 'node6', 'nodetitle', 'node1', null, 'content');
// Add the nodes in random order.
$category->add_node($node3);
$category->add_node($node2);
$category->add_node($node4);
$category->add_node($node1);
$category->add_node($node5);
$category->add_node($node6);
// After node 1 we should have node2 - node6 - node3 - node4 - node5.
$category->sort_nodes();
$nodes = $category->nodes;
$this->assertCount(6, $nodes);
$node = array_shift($nodes);
$this->assertEquals($node1, $node);
$node = array_shift($nodes);
$this->assertEquals($node2, $node);
$node = array_shift($nodes);
$this->assertEquals($node6, $node);
$node = array_shift($nodes);
$this->assertEquals($node3, $node);
$node = array_shift($nodes);
$this->assertEquals($node4, $node);
$node = array_shift($nodes);
$this->assertEquals($node5, $node);
}
/**
* Test tree::add_node().
*/
public function test_tree_add_node(): void {
$tree = new \phpunit_fixture_myprofile_tree();
$node1 = new \core_user\output\myprofile\node('category', 'node1', 'nodetitle');
$tree->add_node($node1);
$nodes = $tree->nodes;
$node = array_shift($nodes);
$this->assertEquals($node1, $node);
// Can't add node with same name.
$this->expectException(\coding_exception::class);
$tree->add_node($node1);
}
/**
* Test tree::add_category().
*/
public function test_tree_add_category(): void {
$tree = new \phpunit_fixture_myprofile_tree();
$category1 = new \core_user\output\myprofile\category('category', 'title');
$tree->add_category($category1);
$categories = $tree->categories;
$category = array_shift($categories);
$this->assertEquals($category1, $category);
// Can't add node with same name.
$this->expectException(\coding_exception::class);
$tree->add_category($category1);
}
/**
* Test tree::find_categories_after().
*/
public function test_find_categories_after(): void {
$tree = new \phpunit_fixture_myprofile_tree('category', 'title', null);
// Create categories.
$category1 = new \core_user\output\myprofile\category('category1', 'category1', null);
$category2 = new \core_user\output\myprofile\category('category2', 'category2', 'category1');
$category3 = new \core_user\output\myprofile\category('category3', 'category3', 'category2');
$category4 = new \core_user\output\myprofile\category('category4', 'category4', 'category3');
$category5 = new \core_user\output\myprofile\category('category5', 'category5', 'category3');
$category6 = new \core_user\output\myprofile\category('category6', 'category6', 'category1');
// Add the categories in random order.
$tree->add_category($category3);
$tree->add_category($category2);
$tree->add_category($category4);
$tree->add_category($category1);
$tree->add_category($category5);
$tree->add_category($category6);
// After category 1 we should have category2 - category3 - category4 - category5 - category6.
$return = $tree->find_categories_after($category1);
$this->assertCount(5, $return);
$category = array_shift($return);
$this->assertEquals($category2, $category);
$category = array_shift($return);
$this->assertEquals($category3, $category);
$category = array_shift($return);
$this->assertEquals($category4, $category);
$category = array_shift($return);
$this->assertEquals($category5, $category);
$category = array_shift($return);
$this->assertEquals($category6, $category);
// Last check also verifies calls for all subsequent categories, still do some random checking.
$return = $tree->find_categories_after($category6);
$this->assertCount(0, $return);
$return = $tree->find_categories_after($category3);
$this->assertCount(2, $return);
}
/**
* Test tree::sort_categories().
*/
public function test_sort_categories(): void {
$tree = new \phpunit_fixture_myprofile_tree('category', 'title', null);
// Create categories.
$category1 = new \core_user\output\myprofile\category('category1', 'category1', null);
$category2 = new \core_user\output\myprofile\category('category2', 'category2', 'category1');
$category3 = new \core_user\output\myprofile\category('category3', 'category3', 'category2');
$category4 = new \core_user\output\myprofile\category('category4', 'category4', 'category3');
$category5 = new \core_user\output\myprofile\category('category5', 'category5', 'category3');
$category6 = new \core_user\output\myprofile\category('category6', 'category6', 'category1');
// Add the categories in random order.
$tree->add_category($category3);
$tree->add_category($category2);
$tree->add_category($category4);
$tree->add_category($category1);
$tree->add_category($category5);
$tree->add_category($category6);
// After category 1 we should have category2 - category3 - category4 - category5 - category6.
$tree->sort_categories();
$categories = $tree->categories;
$this->assertCount(6, $categories);
$category = array_shift($categories);
$this->assertEquals($category1, $category);
$category = array_shift($categories);
$this->assertEquals($category2, $category);
$category = array_shift($categories);
$this->assertEquals($category3, $category);
$category = array_shift($categories);
$this->assertEquals($category4, $category);
$category = array_shift($categories);
$this->assertEquals($category5, $category);
$category = array_shift($categories);
$this->assertEquals($category6, $category);
// Can't add category with same name.
$this->expectException(\coding_exception::class);
$tree->add_category($category1);
}
}
+504
View File
@@ -0,0 +1,504 @@
<?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/>.
/**
* Privacy tests for core_user.
*
* @package core_user
* @category test
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\tests\provider_testcase;
use \core_user\privacy\provider;
use \core_privacy\local\request\approved_userlist;
use \core_privacy\local\request\transform;
require_once($CFG->dirroot . "/user/lib.php");
/**
* Unit tests for core_user.
*
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
/**
* Check that context information is returned correctly.
*/
public function test_get_contexts_for_userid(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
// Create some other users as well.
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertSame($context, $contextlist->current());
}
/**
* Test that data is exported as expected for a user.
*/
public function test_export_user_data(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'firstaccess' => 1535760000,
'lastaccess' => 1541030400,
'currentlogin' => 1541030400,
]);
$course = $this->getDataGenerator()->create_course();
$context = \context_user::instance($user->id);
$this->create_data_for_user($user, $course);
$approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_user', [$context->id]);
$writer = \core_privacy\local\request\writer::with_context($context);
provider::export_user_data($approvedlist);
// Make sure that the password history only returns a count.
$history = $writer->get_data([get_string('privacy:passwordhistorypath', 'user')]);
$objectcount = new \ArrayObject($history);
// This object should only have one property.
$this->assertCount(1, $objectcount);
$this->assertEquals(1, $history->password_history_count);
// Password resets should have two fields - timerequested and timererequested.
$resetarray = (array) $writer->get_data([get_string('privacy:passwordresetpath', 'user')]);
$detail = array_shift($resetarray);
$this->assertTrue(array_key_exists('timerequested', $detail));
$this->assertTrue(array_key_exists('timererequested', $detail));
// Last access to course.
$lastcourseaccess = (array) $writer->get_data([get_string('privacy:lastaccesspath', 'user')]);
$entry = array_shift($lastcourseaccess);
$this->assertEquals($course->fullname, $entry['course_name']);
$this->assertTrue(array_key_exists('timeaccess', $entry));
// User devices.
$userdevices = (array) $writer->get_data([get_string('privacy:devicespath', 'user')]);
$entry = array_shift($userdevices);
$this->assertEquals('com.moodle.moodlemobile', $entry['appid']);
// Make sure these fields are not exported.
$this->assertFalse(array_key_exists('pushid', $entry));
$this->assertFalse(array_key_exists('uuid', $entry));
// Session data.
$sessiondata = (array) $writer->get_data([get_string('privacy:sessionpath', 'user')]);
$entry = array_shift($sessiondata);
// Make sure that the sid is not exported.
$this->assertFalse(array_key_exists('sid', $entry));
// Check that some of the other fields are present.
$this->assertTrue(array_key_exists('state', $entry));
$this->assertTrue(array_key_exists('sessdata', $entry));
$this->assertTrue(array_key_exists('timecreated', $entry));
// Course requests
$courserequestdata = (array) $writer->get_data([get_string('privacy:courserequestpath', 'user')]);
$entry = array_shift($courserequestdata);
// Make sure that the password is not exported.
$this->assertFalse(property_exists($entry, 'password'));
// Check that some of the other fields are present.
$this->assertTrue(property_exists($entry, 'fullname'));
$this->assertTrue(property_exists($entry, 'shortname'));
$this->assertTrue(property_exists($entry, 'summary'));
// User details.
$userdata = (array) $writer->get_data([]);
// Check that the password is not exported.
$this->assertFalse(array_key_exists('password', $userdata));
// Check that some critical fields exist.
$this->assertTrue(array_key_exists('firstname', $userdata));
$this->assertTrue(array_key_exists('lastname', $userdata));
$this->assertTrue(array_key_exists('email', $userdata));
// Check access times.
$this->assertEquals(transform::datetime($user->firstaccess), $userdata['firstaccess']);
$this->assertEquals(transform::datetime($user->lastaccess), $userdata['lastaccess']);
$this->assertNull($userdata['lastlogin']);
$this->assertEquals(transform::datetime($user->currentlogin), $userdata['currentlogin']);
}
/**
* Test that user data is deleted for one user.
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'idnumber' => 'A0023',
'emailstop' => 1,
'phone1' => '555 3257',
'institution' => 'test',
'department' => 'Science',
'city' => 'Perth',
'country' => 'AU'
]);
$user2 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->create_data_for_user($user, $course);
$this->create_data_for_user($user2, $course);
provider::delete_data_for_all_users_in_context(\context_user::instance($user->id));
// These tables should not have any user data for $user. Only for $user2.
$records = $DB->get_records('user_password_history');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_password_resets');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_lastaccess');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_devices');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
// Now check that there is still a record for the deleted user, but that non-critical information is removed.
$record = $DB->get_record('user', ['id' => $user->id]);
$this->assertEmpty($record->idnumber);
$this->assertEmpty($record->emailstop);
$this->assertEmpty($record->phone1);
$this->assertEmpty($record->institution);
$this->assertEmpty($record->department);
$this->assertEmpty($record->city);
$this->assertEmpty($record->country);
$this->assertEmpty($record->timezone);
$this->assertEmpty($record->timecreated);
$this->assertEmpty($record->timemodified);
$this->assertEmpty($record->firstnamephonetic);
// Check for critical fields.
// Deleted should now be 1.
$this->assertEquals(1, $record->deleted);
$this->assertEquals($user->id, $record->id);
$this->assertEquals($user->username, $record->username);
$this->assertEquals($user->password, $record->password);
$this->assertEquals($user->firstname, $record->firstname);
$this->assertEquals($user->lastname, $record->lastname);
$this->assertEquals($user->email, $record->email);
}
/**
* Test that user data is deleted for one user.
*/
public function test_delete_data_for_user(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'idnumber' => 'A0023',
'emailstop' => 1,
'phone1' => '555 3257',
'institution' => 'test',
'department' => 'Science',
'city' => 'Perth',
'country' => 'AU'
]);
$user2 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->create_data_for_user($user, $course);
$this->create_data_for_user($user2, $course);
// Provide multiple different context to check that only the correct user is deleted.
$contexts = [
\context_user::instance($user->id)->id,
\context_user::instance($user2->id)->id,
\context_system::instance()->id];
$approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_user', $contexts);
provider::delete_data_for_user($approvedlist);
// These tables should not have any user data for $user. Only for $user2.
$records = $DB->get_records('user_password_history');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_password_resets');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_lastaccess');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
$records = $DB->get_records('user_devices');
$this->assertCount(1, $records);
$data = array_shift($records);
$this->assertNotEquals($user->id, $data->userid);
$this->assertEquals($user2->id, $data->userid);
// Now check that there is still a record for the deleted user, but that non-critical information is removed.
$record = $DB->get_record('user', ['id' => $user->id]);
$this->assertEmpty($record->idnumber);
$this->assertEmpty($record->emailstop);
$this->assertEmpty($record->phone1);
$this->assertEmpty($record->institution);
$this->assertEmpty($record->department);
$this->assertEmpty($record->city);
$this->assertEmpty($record->country);
$this->assertEmpty($record->timezone);
$this->assertEmpty($record->timecreated);
$this->assertEmpty($record->timemodified);
$this->assertEmpty($record->firstnamephonetic);
// Check for critical fields.
// Deleted should now be 1.
$this->assertEquals(1, $record->deleted);
$this->assertEquals($user->id, $record->id);
$this->assertEquals($user->username, $record->username);
$this->assertEquals($user->password, $record->password);
$this->assertEquals($user->firstname, $record->firstname);
$this->assertEquals($user->lastname, $record->lastname);
$this->assertEquals($user->email, $record->email);
}
/**
* Test that only users with a user context are fetched.
*/
public function test_get_users_in_context(): void {
$this->resetAfterTest();
$component = 'core_user';
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$userlist = new \core_privacy\local\request\userlist($usercontext, $component);
// The list of users for user context should return the user.
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$user->id];
$actual = $userlist->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for system context should not return any users.
$systemcontext = \context_system::instance();
$userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
global $DB;
$this->resetAfterTest();
$component = 'core_user';
// Create user1.
$user1 = $this->getDataGenerator()->create_user([
'idnumber' => 'A0023',
'emailstop' => 1,
'phone1' => '555 3257',
'institution' => 'test',
'department' => 'Science',
'city' => 'Perth',
'country' => 'AU'
]);
$usercontext1 = \context_user::instance($user1->id);
$userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component);
// Create user2.
$user2 = $this->getDataGenerator()->create_user([
'idnumber' => 'A0024',
'emailstop' => 1,
'phone1' => '555 3258',
'institution' => 'test',
'department' => 'Science',
'city' => 'Perth',
'country' => 'AU'
]);
$usercontext2 = \context_user::instance($user2->id);
$userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component);
// The list of users for usercontext1 should return user1.
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
// The list of users for usercontext2 should return user2.
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// Add userlist1 to the approved user list.
$approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids());
// Delete using delete_data_for_users().
provider::delete_data_for_users($approvedlist);
// Now check that there is still a record for user1 (deleted user), but non-critical information is removed.
$record = $DB->get_record('user', ['id' => $user1->id]);
$this->assertEmpty($record->idnumber);
$this->assertEmpty($record->emailstop);
$this->assertEmpty($record->phone1);
$this->assertEmpty($record->institution);
$this->assertEmpty($record->department);
$this->assertEmpty($record->city);
$this->assertEmpty($record->country);
$this->assertEmpty($record->timezone);
$this->assertEmpty($record->timecreated);
$this->assertEmpty($record->timemodified);
$this->assertEmpty($record->firstnamephonetic);
// Check for critical fields.
// Deleted should now be 1.
$this->assertEquals(1, $record->deleted);
$this->assertEquals($user1->id, $record->id);
$this->assertEquals($user1->username, $record->username);
$this->assertEquals($user1->password, $record->password);
$this->assertEquals($user1->firstname, $record->firstname);
$this->assertEquals($user1->lastname, $record->lastname);
$this->assertEquals($user1->email, $record->email);
// Now check that the record and information for user2 is still present.
$record = $DB->get_record('user', ['id' => $user2->id]);
$this->assertNotEmpty($record->idnumber);
$this->assertNotEmpty($record->emailstop);
$this->assertNotEmpty($record->phone1);
$this->assertNotEmpty($record->institution);
$this->assertNotEmpty($record->department);
$this->assertNotEmpty($record->city);
$this->assertNotEmpty($record->country);
$this->assertNotEmpty($record->timezone);
$this->assertNotEmpty($record->timecreated);
$this->assertNotEmpty($record->timemodified);
$this->assertNotEmpty($record->firstnamephonetic);
$this->assertEquals(0, $record->deleted);
$this->assertEquals($user2->id, $record->id);
$this->assertEquals($user2->username, $record->username);
$this->assertEquals($user2->password, $record->password);
$this->assertEquals($user2->firstname, $record->firstname);
$this->assertEquals($user2->lastname, $record->lastname);
$this->assertEquals($user2->email, $record->email);
}
/**
* Create user data for a user.
*
* @param \stdClass $user A user object.
* @param \stdClass $course A course.
*/
protected function create_data_for_user($user, $course) {
global $DB;
$this->resetAfterTest();
// Last course access.
$lastaccess = (object) [
'userid' => $user->id,
'courseid' => $course->id,
'timeaccess' => time() - DAYSECS
];
$DB->insert_record('user_lastaccess', $lastaccess);
// Password history.
$history = (object) [
'userid' => $user->id,
'hash' => 'HID098djJUU',
'timecreated' => time()
];
$DB->insert_record('user_password_history', $history);
// Password resets.
$passwordreset = (object) [
'userid' => $user->id,
'timerequested' => time(),
'timererequested' => time(),
'token' => $this->generate_random_string()
];
$DB->insert_record('user_password_resets', $passwordreset);
// User mobile devices.
$userdevices = (object) [
'userid' => $user->id,
'appid' => 'com.moodle.moodlemobile',
'name' => 'occam',
'model' => 'Nexus 4',
'platform' => 'Android',
'version' => '4.2.2',
'pushid' => 'kishUhd',
'uuid' => 'KIhud7s',
'timecreated' => time(),
'timemodified' => time()
];
$DB->insert_record('user_devices', $userdevices);
// Course request.
$courserequest = (object) [
'fullname' => 'Test Course',
'shortname' => 'TC',
'summary' => 'Summary of course',
'summaryformat' => 1,
'category' => 1,
'reason' => 'Because it would be nice.',
'requester' => $user->id,
'password' => ''
];
$DB->insert_record('course_request', $courserequest);
// User session table data.
$usersessions = (object) [
'state' => 0,
'sid' => $this->generate_random_string(), // Needs a unique id.
'userid' => $user->id,
'sessdata' => 'Nothing',
'timecreated' => time(),
'timemodified' => time(),
'firstip' => '0.0.0.0',
'lastip' => '0.0.0.0'
];
$DB->insert_record('sessions', $usersessions);
}
/**
* Create a random string.
*
* @param integer $length length of the string to generate.
* @return string A random string.
*/
protected function generate_random_string($length = 6) {
$response = '';
$source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($length > 0) {
$response = '';
$source = str_split($source, 1);
for ($i = 1; $i <= $length; $i++) {
$num = mt_rand(1, count($source));
$response .= $source[$num - 1];
}
}
return $response;
}
}
+351
View File
@@ -0,0 +1,351 @@
<?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 core_user;
/**
* Unit tests for user/profile/lib.php.
*
* @package core_user
* @copyright 2014 The Open University
* @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class profilelib_test extends \advanced_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/user/profile/lib.php");
}
/**
* Tests profile_get_custom_fields function and checks it is consistent
* with profile_user_record.
*/
public function test_get_custom_fields(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
// Add a custom field of textarea type.
$id1 = $this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'frogdesc', 'name' => 'Description of frog',
'datatype' => 'textarea'])->id;
// Check the field is returned.
$result = profile_get_custom_fields();
$this->assertArrayHasKey($id1, $result);
$this->assertEquals('frogdesc', $result[$id1]->shortname);
// Textarea types are not included in user data though, so if we
// use the 'only in user data' parameter, there is still nothing.
$this->assertArrayNotHasKey($id1, profile_get_custom_fields(true));
// Check that profile_user_record returns same (no) fields.
$this->assertObjectNotHasProperty('frogdesc', profile_user_record($user->id));
// Check that profile_user_record returns all the fields when requested.
$this->assertObjectHasProperty('frogdesc', profile_user_record($user->id, false));
// Add another custom field, this time of normal text type.
$id2 = $this->getDataGenerator()->create_custom_profile_field(array(
'shortname' => 'frogname', 'name' => 'Name of frog',
'datatype' => 'text'))->id;
// Check both are returned using normal option.
$result = profile_get_custom_fields();
$this->assertArrayHasKey($id2, $result);
$this->assertEquals('frogname', $result[$id2]->shortname);
// And check that only the one is returned the other way.
$this->assertArrayHasKey($id2, profile_get_custom_fields(true));
// Check profile_user_record returns same field.
$this->assertObjectHasProperty('frogname', profile_user_record($user->id));
// Check that profile_user_record returns all the fields when requested.
$this->assertObjectHasProperty('frogname', profile_user_record($user->id, false));
}
/**
* Make sure that all profile fields can be initialised without arguments.
*/
public function test_default_constructor(): void {
global $DB, $CFG;
require_once($CFG->dirroot . '/user/profile/definelib.php');
$datatypes = profile_list_datatypes();
foreach ($datatypes as $datatype => $datatypename) {
require_once($CFG->dirroot . '/user/profile/field/' .
$datatype . '/field.class.php');
$newfield = 'profile_field_' . $datatype;
$formfield = new $newfield();
$this->assertNotNull($formfield);
}
}
/**
* Test profile_view function
*/
public function test_profile_view(): void {
global $USER;
$this->resetAfterTest();
// Course without sections.
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
// Redirect events to the sink, so we can recover them later.
$sink = $this->redirectEvents();
profile_view($user, $context, $course);
$events = $sink->get_events();
$event = reset($events);
// Check the event details are correct.
$this->assertInstanceOf('\core\event\user_profile_viewed', $event);
$this->assertEquals($context, $event->get_context());
$this->assertEquals($user->id, $event->relateduserid);
$this->assertEquals($course->id, $event->other['courseid']);
$this->assertEquals($course->shortname, $event->other['courseshortname']);
$this->assertEquals($course->fullname, $event->other['coursefullname']);
profile_view($user, $usercontext);
$events = $sink->get_events();
$event = array_pop($events);
$sink->close();
$this->assertInstanceOf('\core\event\user_profile_viewed', $event);
$this->assertEquals($usercontext, $event->get_context());
$this->assertEquals($user->id, $event->relateduserid);
}
/**
* Test that {@link user_not_fully_set_up()} takes required custom fields into account.
*/
public function test_profile_has_required_custom_fields_set(): void {
global $CFG;
require_once($CFG->dirroot.'/mnet/lib.php');
$this->resetAfterTest();
// Add a required, visible, unlocked custom field.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'house', 'name' => 'House', 'required' => 1,
'visible' => 1, 'locked' => 0, 'datatype' => 'text']);
// Add an optional, visible, unlocked custom field.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'pet', 'name' => 'Pet', 'required' => 0,
'visible' => 1, 'locked' => 0, 'datatype' => 'text']);
// Add required but invisible custom field.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'secretid', 'name' => 'Secret ID',
'required' => 1, 'visible' => 0, 'locked' => 0, 'datatype' => 'text']);
// Add required but locked custom field.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'muggleborn', 'name' => 'Muggle-born',
'required' => 1, 'visible' => 1, 'locked' => 1, 'datatype' => 'checkbox']);
// Create some student accounts.
$hermione = $this->getDataGenerator()->create_user();
$harry = $this->getDataGenerator()->create_user();
$ron = $this->getDataGenerator()->create_user();
$draco = $this->getDataGenerator()->create_user();
// Hermione has all available custom fields filled (of course she has).
profile_save_data((object)['id' => $hermione->id, 'profile_field_house' => 'Gryffindor']);
profile_save_data((object)['id' => $hermione->id, 'profile_field_pet' => 'Crookshanks']);
// Harry has only the optional field filled.
profile_save_data((object)['id' => $harry->id, 'profile_field_pet' => 'Hedwig']);
// Draco has only the required field filled.
profile_save_data((object)['id' => $draco->id, 'profile_field_house' => 'Slytherin']);
// Only students with required fields filled should be considered as fully set up in the default (strict) mode.
$this->assertFalse(user_not_fully_set_up($hermione));
$this->assertFalse(user_not_fully_set_up($draco));
$this->assertTrue(user_not_fully_set_up($harry));
$this->assertTrue(user_not_fully_set_up($ron));
// In the lax mode, students do not need to have required fields filled.
$this->assertFalse(user_not_fully_set_up($hermione, false));
$this->assertFalse(user_not_fully_set_up($draco, false));
$this->assertFalse(user_not_fully_set_up($harry, false));
$this->assertFalse(user_not_fully_set_up($ron, false));
// Lack of required core field is seen as a problem in either mode.
unset($hermione->email);
$this->assertTrue(user_not_fully_set_up($hermione, true));
$this->assertTrue(user_not_fully_set_up($hermione, false));
// When confirming remote MNet users, we do not have custom fields available.
$roamingharry = mnet_strip_user($harry, ['firstname', 'lastname', 'email']);
$roaminghermione = mnet_strip_user($hermione, ['firstname', 'lastname', 'email']);
$this->assertTrue(user_not_fully_set_up($roamingharry, true));
$this->assertFalse(user_not_fully_set_up($roamingharry, false));
$this->assertTrue(user_not_fully_set_up($roaminghermione, true));
$this->assertTrue(user_not_fully_set_up($roaminghermione, false));
}
/**
* Test that user generator sets the custom profile fields
*/
public function test_profile_fields_in_generator(): void {
global $CFG;
require_once($CFG->dirroot.'/mnet/lib.php');
$this->resetAfterTest();
// Add a required, visible, unlocked custom field.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'house', 'name' => 'House', 'required' => 1,
'visible' => 1, 'locked' => 0, 'datatype' => 'text', 'defaultdata' => null]);
// Create some student accounts.
$hermione = $this->getDataGenerator()->create_user(['profile_field_house' => 'Gryffindor']);
$harry = $this->getDataGenerator()->create_user();
// Only students with required fields filled should be considered as fully set up.
$this->assertFalse(user_not_fully_set_up($hermione));
$this->assertTrue(user_not_fully_set_up($harry));
// Test that the profile fields were actually set.
$profilefields1 = profile_user_record($hermione->id);
$this->assertEquals('Gryffindor', $profilefields1->house);
$profilefields2 = profile_user_record($harry->id);
$this->assertObjectHasProperty('house', $profilefields2);
$this->assertNull($profilefields2->house);
}
/**
* Tests the profile_get_custom_field_data_by_shortname function when working normally.
*/
public function test_profile_get_custom_field_data_by_shortname_normal(): void {
global $DB;
$this->resetAfterTest();
// Create 3 profile fields.
$generator = $this->getDataGenerator();
$field1 = $generator->create_custom_profile_field(['datatype' => 'text',
'shortname' => 'speciality', 'name' => 'Speciality',
'visible' => PROFILE_VISIBLE_ALL]);
$field2 = $generator->create_custom_profile_field(['datatype' => 'menu',
'shortname' => 'veggie', 'name' => 'Vegetarian',
'visible' => PROFILE_VISIBLE_PRIVATE]);
// Get the first field data and check it is correct.
$data = profile_get_custom_field_data_by_shortname('speciality');
$this->assertEquals('Speciality', $data->name);
$this->assertEquals(PROFILE_VISIBLE_ALL, $data->visible);
$this->assertEquals($field1->id, $data->id);
// Get the second field data, checking there is no database query this time.
$before = $DB->perf_get_queries();
$data = profile_get_custom_field_data_by_shortname('veggie');
$this->assertEquals($before, $DB->perf_get_queries());
$this->assertEquals('Vegetarian', $data->name);
$this->assertEquals(PROFILE_VISIBLE_PRIVATE, $data->visible);
$this->assertEquals($field2->id, $data->id);
}
/**
* Tests the profile_get_custom_field_data_by_shortname function with a field that doesn't exist.
*/
public function test_profile_get_custom_field_data_by_shortname_missing(): void {
$this->assertNull(profile_get_custom_field_data_by_shortname('speciality'));
}
/**
* Data provider for {@see test_profile_get_custom_field_data_by_shortname_case_sensitivity}
*
* @return array[]
*/
public function profile_get_custom_field_data_by_shortname_case_sensitivity_provider(): array {
return [
'Matching case, case-sensitive search' => ['hello', 'hello', true, true],
'Matching case, case-insensitive search' => ['hello', 'hello', false, true],
'Non-matching case, case-sensitive search' => ['hello', 'Hello', true, false],
'Non-matching case, case-insensitive search' => ['hello', 'Hello', false, true],
'Non-matching, case-sensitive search' => ['hello', 'hola', true, false],
'Non-matching, case-insensitive search' => ['hello', 'hola', false, false],
];
}
/**
* Test retrieving custom field by shortname, specifying case-sensitivity when matching
*
* @param string $shortname
* @param string $shortnamesearch
* @param bool $casesensitive
* @param bool $expectmatch
*
* @dataProvider profile_get_custom_field_data_by_shortname_case_sensitivity_provider
*/
public function test_profile_get_custom_field_data_by_shortname_case_sensitivity(
string $shortname,
string $shortnamesearch,
bool $casesensitive,
bool $expectmatch
): void {
$this->resetAfterTest();
$this->getDataGenerator()->create_custom_profile_field([
'datatype' => 'text',
'shortname' => $shortname,
'name' => 'My field',
]);
$customfield = profile_get_custom_field_data_by_shortname($shortnamesearch, $casesensitive);
if ($expectmatch) {
$this->assertInstanceOf(\stdClass::class, $customfield);
$this->assertEquals('text', $customfield->datatype);
$this->assertEquals($shortname, $customfield->shortname);
$this->assertEquals('My field', $customfield->name);
} else {
$this->assertNull($customfield);
}
}
/**
* Test profile field loading via profile_get_user_field helper
*
* @covers ::profile_get_user_field
*/
public function test_profile_get_user_field(): void {
$this->resetAfterTest();
$profilefield = $this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'fruit',
'name' => 'Fruit',
'datatype' => 'text',
]);
$user = $this->getDataGenerator()->create_user(['profile_field_fruit' => 'Apple']);
$fieldinstance = profile_get_user_field('text', $profilefield->id, $user->id);
$this->assertInstanceOf(\profile_field_text::class, $fieldinstance);
$this->assertEquals($profilefield->id, $fieldinstance->fieldid);
$this->assertEquals($user->id, $fieldinstance->userid);
$this->assertEquals('Apple', $fieldinstance->data);
}
}
@@ -0,0 +1,540 @@
<?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/>.
declare(strict_types=1);
namespace core_user\reportbuilder\datasource;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\tags;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\filters\user as user_filter;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for users datasource
*
* @package core_user
* @covers \core_user\reportbuilder\datasource\users
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class users_test extends core_reportbuilder_testcase {
/**
* Test default datasource
*/
public function test_datasource_default(): void {
$this->resetAfterTest();
$user2 = $this->getDataGenerator()->create_user(['firstname' => 'Charles']);
$user3 = $this->getDataGenerator()->create_user(['firstname' => 'Brian']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(3, $content);
// Default columns are fullname, username, email. Results are sorted by the fullname.
[$adminrow, $userrow1, $userrow2] = array_map('array_values', $content);
$this->assertEquals(['Admin User', 'admin', 'admin@example.com'], $adminrow);
$this->assertEquals([fullname($user3), $user3->username, $user3->email], $userrow1);
$this->assertEquals([fullname($user2), $user2->username, $user2->email], $userrow2);
}
/**
* Test datasource columns that aren't added by default
*/
public function test_datasource_non_default_columns(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'firstname' => 'Zoe',
'idnumber' => 'U0001',
'city' => 'London',
'country' => 'GB',
'theme' => 'boost',
'interests' => ['Horses'],
]);
$cohort = $this->getDataGenerator()->create_cohort(['name' => 'My cohort']);
cohort_add_member($cohort->id, $user->id);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// User.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithlink',
'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicture']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicturelink']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:picture']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:description']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstnamephonetic']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastnamephonetic']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:middlename']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:alternatename']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:idnumber']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:institution']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:department']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone1']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone2']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:address']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastaccess']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:confirmed']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:auth']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:moodlenetprofile']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:timecreated']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:timemodified']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastip']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:theme']);
// Tags.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']);
// Cohort.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:name']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(2, $content);
// Admin row.
[
$fullnamewithlink,
$fullnamewithpicture,
$fullnamewithpicturelink,
$picture,
$lastname,
$firstname,
] = array_values($content[0]);
$this->assertStringContainsString('Admin User', $fullnamewithlink);
$this->assertStringContainsString('Admin User', $fullnamewithpicture);
$this->assertStringContainsString('Admin User', $fullnamewithpicturelink);
$this->assertNotEmpty($picture);
$this->assertEquals('Admin', $lastname);
$this->assertEquals('User', $firstname);
// User row.
[
$fullnamewithlink,
$fullnamewithpicture,
$fullnamewithpicturelink,
$picture,
$firstname,
$lastname,
$city,
$country,
$description,
$firstnamephonetic,
$lastnamephonetic,
$middlename,
$alternatename,
$idnumber,
$institution,
$department,
$phone1,
$phone2,
$address,
$lastaccess,
$suspended,
$confirmed,
$auth,
$moodlenetprofile,
$timecreated,
$timemodified,
$lastip,
$theme,
$tag,
$cohortname,
] = array_values($content[1]);
$this->assertStringContainsString(fullname($user), $fullnamewithlink);
$this->assertStringContainsString(fullname($user), $fullnamewithpicture);
$this->assertStringContainsString(fullname($user), $fullnamewithpicturelink);
$this->assertNotEmpty($picture);
$this->assertEquals($user->firstname, $firstname);
$this->assertEquals($user->lastname, $lastname);
$this->assertEquals($user->city, $city);
$this->assertEquals('United Kingdom', $country);
$this->assertEquals($user->description, $description);
$this->assertEquals($user->firstnamephonetic, $firstnamephonetic);
$this->assertEquals($user->lastnamephonetic, $lastnamephonetic);
$this->assertEquals($user->middlename, $middlename);
$this->assertEquals($user->alternatename, $alternatename);
$this->assertEquals($user->idnumber, $idnumber);
$this->assertEquals($user->institution, $institution);
$this->assertEquals($user->department, $department);
$this->assertEquals($user->phone1, $phone1);
$this->assertEquals($user->phone2, $phone2);
$this->assertEquals($user->address, $address);
$this->assertEmpty($lastaccess);
$this->assertEquals('No', $suspended);
$this->assertEquals('Yes', $confirmed);
$this->assertEquals('Manual accounts', $auth);
$this->assertEquals($user->moodlenetprofile, $moodlenetprofile);
$this->assertNotEmpty($timecreated);
$this->assertNotEmpty($timemodified);
$this->assertEquals('0.0.0.0', $lastip);
$this->assertEquals('Boost', $theme);
$this->assertEquals('Horses', $tag);
$this->assertEquals($cohort->name, $cohortname);
}
/**
* Data provider for {@see test_datasource_filters}
*
* @return array[]
*/
public function datasource_filters_provider(): array {
return [
// User.
'Filter user' => ['user:userselect', [
'user:userselect_operator' => user_filter::USER_SELECT,
'user:userselect_value' => [-1],
], false],
'Filter fullname' => ['user:fullname', [
'user:fullname_operator' => text::CONTAINS,
'user:fullname_value' => 'Zoe',
], true],
'Filter fullname (no match)' => ['user:fullname', [
'user:fullname_operator' => text::CONTAINS,
'user:fullname_value' => 'Alfie',
], false],
'Filter firstname' => ['user:firstname', [
'user:firstname_operator' => text::IS_EQUAL_TO,
'user:firstname_value' => 'Zoe',
], true],
'Filter firstname (no match)' => ['user:firstname', [
'user:firstname_operator' => text::IS_EQUAL_TO,
'user:firstname_value' => 'Alfie',
], false],
'Filter middlename' => ['user:middlename', [
'user:middlename_operator' => text::IS_EQUAL_TO,
'user:middlename_value' => 'Zebediah',
], true],
'Filter middlename (no match)' => ['user:middlename', [
'user:middlename_operator' => text::IS_EQUAL_TO,
'user:middlename_value' => 'Aardvark',
], false],
'Filter lastname' => ['user:lastname', [
'user:lastname_operator' => text::IS_EQUAL_TO,
'user:lastname_value' => 'Zebra',
], true],
'Filter lastname (no match)' => ['user:lastname', [
'user:lastname_operator' => text::IS_EQUAL_TO,
'user:lastname_value' => 'Aardvark',
], false],
'Filter firstnamephonetic' => ['user:firstnamephonetic', [
'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
'user:firstnamephonetic_value' => 'Eoz',
], true],
'Filter firstnamephonetic (no match)' => ['user:firstnamephonetic', [
'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
'user:firstnamephonetic_value' => 'Alfie',
], false],
'Filter lastnamephonetic' => ['user:lastnamephonetic', [
'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
'user:lastnamephonetic_value' => 'Arbez',
], true],
'Filter lastnamephonetic (no match)' => ['user:lastnamephonetic', [
'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
'user:lastnamephonetic_value' => 'Aardvark',
], false],
'Filter alternatename' => ['user:alternatename', [
'user:alternatename_operator' => text::IS_EQUAL_TO,
'user:alternatename_value' => 'Zee',
], true],
'Filter alternatename (no match)' => ['user:alternatename', [
'user:alternatename_operator' => text::IS_EQUAL_TO,
'user:alternatename_value' => 'Aardvark',
], false],
'Filter email' => ['user:email', [
'user:email_operator' => text::CONTAINS,
'user:email_value' => 'zoe1',
], true],
'Filter email (no match)' => ['user:email', [
'user:email_operator' => text::CONTAINS,
'user:email_value' => 'alfie1',
], false],
'Filter phone1' => ['user:phone1', [
'user:phone1_operator' => text::IS_EQUAL_TO,
'user:phone1_value' => '111',
], true],
'Filter phone1 (no match)' => ['user:phone1', [
'user:phone1_operator' => text::IS_EQUAL_TO,
'user:phone1_value' => '119',
], false],
'Filter phone2' => ['user:phone2', [
'user:phone2_operator' => text::IS_EQUAL_TO,
'user:phone2_value' => '222',
], true],
'Filter phone2 (no match)' => ['user:phone2', [
'user:phone2_operator' => text::IS_EQUAL_TO,
'user:phone2_value' => '229',
], false],
'Filter address' => ['user:address', [
'user:address_operator' => text::IS_EQUAL_TO,
'user:address_value' => 'Big Farm',
], true],
'Filter address (no match)' => ['user:address', [
'user:address_operator' => text::IS_EQUAL_TO,
'user:address_value' => 'Small Farm',
], false],
'Filter city' => ['user:city', [
'user:city_operator' => text::IS_EQUAL_TO,
'user:city_value' => 'Barcelona',
], true],
'Filter city (no match)' => ['user:city', [
'user:city_operator' => text::IS_EQUAL_TO,
'user:city_value' => 'Perth',
], false],
'Filter country' => ['user:country', [
'user:country_operator' => select::EQUAL_TO,
'user:country_value' => 'ES',
], true],
'Filter country (no match)' => ['user:country', [
'user:country_operator' => select::EQUAL_TO,
'user:country_value' => 'AU',
], false],
'Filter theme' => ['user:theme', [
'user:theme_operator' => select::EQUAL_TO,
'user:theme_value' => 'boost',
], true],
'Filter theme (no match)' => ['user:theme', [
'user:theme_operator' => select::EQUAL_TO,
'user:theme_value' => 'classic',
], false],
'Filter description' => ['user:description', [
'user:description_operator' => text::CONTAINS,
'user:description_value' => 'Hello there',
], true],
'Filter description (no match)' => ['user:description', [
'user:description_operator' => text::CONTAINS,
'user:description_value' => 'Goodbye',
], false],
'Filter auth' => ['user:auth', [
'user:auth_operator' => select::EQUAL_TO,
'user:auth_value' => 'manual',
], true],
'Filter auth (no match)' => ['user:auth', [
'user:auth_operator' => select::EQUAL_TO,
'user:auth_value' => 'ldap',
], false],
'Filter username' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'zoe1',
], true],
'Filter username (no match)' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'alfie1',
], false],
'Filter idnumber' => ['user:idnumber', [
'user:idnumber_operator' => text::IS_EQUAL_TO,
'user:idnumber_value' => 'Z0001',
], true],
'Filter idnumber (no match)' => ['user:idnumber', [
'user:idnumber_operator' => text::IS_EQUAL_TO,
'user:idnumber_value' => 'A0001',
], false],
'Filter institution' => ['user:institution', [
'user:institution_operator' => text::IS_EQUAL_TO,
'user:institution_value' => 'Farm',
], true],
'Filter institution (no match)' => ['user:institution', [
'user:institution_operator' => text::IS_EQUAL_TO,
'user:institution_value' => 'University',
], false],
'Filter department' => ['user:department', [
'user:department_operator' => text::IS_EQUAL_TO,
'user:department_value' => 'Stable',
], true],
'Filter department (no match)' => ['user:department', [
'user:department_operator' => text::IS_EQUAL_TO,
'user:department_value' => 'Office',
], false],
'Filter moodlenetprofile' => ['user:moodlenetprofile', [
'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
'user:moodlenetprofile_value' => '@zoe1@example.com',
], true],
'Filter moodlenetprofile (no match)' => ['user:moodlenetprofile', [
'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
'user:moodlenetprofile_value' => '@alfie1@example.com',
], false],
'Filter suspended' => ['user:suspended', [
'user:suspended_operator' => boolean_select::NOT_CHECKED,
], true],
'Filter suspended (no match)' => ['user:suspended', [
'user:suspended_operator' => boolean_select::CHECKED,
], false],
'Filter confirmed' => ['user:confirmed', [
'user:confirmed_operator' => boolean_select::CHECKED,
], true],
'Filter confirmed (no match)' => ['user:confirmed', [
'user:confirmed_operator' => boolean_select::NOT_CHECKED,
], false],
'Filter timecreated' => ['user:timecreated', [
'user:timecreated_operator' => date::DATE_RANGE,
'user:timecreated_from' => 1622502000,
], true],
'Filter timecreated (no match)' => ['user:timecreated', [
'user:timecreated_operator' => date::DATE_RANGE,
'user:timecreated_from' => 1619823600,
'user:timecreated_to' => 1622502000,
], false],
'Filter timemodified' => ['user:timemodified', [
'user:timemodified_operator' => date::DATE_RANGE,
'user:timemodified_from' => 1622502000,
], true],
'Filter timemodified (no match)' => ['user:timemodified', [
'user:timemodified_operator' => date::DATE_RANGE,
'user:timemodified_to' => 1622502000,
], false],
'Filter lastaccess' => ['user:lastaccess', [
'user:lastaccess_operator' => date::DATE_EMPTY,
], true],
'Filter lastaccess (no match)' => ['user:lastaccess', [
'user:lastaccess_operator' => date::DATE_RANGE,
'user:lastaccess_from' => 1619823600,
'user:lastaccess_to' => 1622502000,
], false],
'Filter lastip' => ['user:lastip', [
'user:lastip_operator' => text::IS_EQUAL_TO,
'user:lastip_value' => '0.0.0.0',
], true],
'Filter lastip (no match)' => ['user:lastip', [
'user:lastip_operator' => text::IS_EQUAL_TO,
'user:lastip_value' => '1.2.3.4',
], false],
// Tags.
'Filter tag name' => ['tag:name', [
'tag:name_operator' => tags::EQUAL_TO,
'tag:name_value' => [-1],
], false],
'Filter tag name not empty' => ['tag:name', [
'tag:name_operator' => tags::NOT_EMPTY,
], true],
// Cohort.
'Filter cohort name' => ['cohort:name', [
'cohort:name_operator' => text::IS_EQUAL_TO,
'cohort:name_value' => 'My cohort',
], true],
'Filter cohort name (no match)' => ['cohort:name', [
'cohort:name_operator' => text::IS_EQUAL_TO,
'cohort:name_value' => 'Not my cohort',
], false],
];
}
/**
* Test datasource filters
*
* @param string $filtername
* @param array $filtervalues
* @param bool $expectmatch
*
* @dataProvider datasource_filters_provider
*/
public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'username' => 'zoe1',
'email' => 'zoe1@example.com',
'firstname' => 'Zoe',
'middlename' => 'Zebediah',
'lastname' => 'Zebra',
'firstnamephonetic' => 'Eoz',
'lastnamephonetic' => 'Arbez',
'alternatename' => 'Zee',
'idnumber' => 'Z0001',
'institution' => 'Farm',
'department' => 'Stable',
'phone1' => '111',
'phone2' => '222',
'address' => 'Big Farm',
'city' => 'Barcelona',
'country' => 'ES',
'theme' => 'boost',
'description' => 'Hello there',
'moodlenetprofile' => '@zoe1@example.com',
'interests' => ['Horses'],
'lastip' => '0.0.0.0',
]);
$cohort = $this->getDataGenerator()->create_cohort(['name' => 'My cohort']);
cohort_add_member($cohort->id, $user->id);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create report containing single column, and given filter.
$report = $generator->create_report(['name' => 'Tasks', 'source' => users::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
// Add filter, set it's values.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
if ($expectmatch) {
$this->assertNotEmpty($content);
// Merge report usernames into easily traversable array.
$usernames = array_merge(...array_map('array_values', $content));
$this->assertContains($user->username, $usernames);
} else {
$this->assertEmpty($content);
}
}
/**
* Stress test datasource
*
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
*/
public function test_stress_datasource(): void {
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->datasource_stress_test_columns(users::class);
$this->datasource_stress_test_columns_aggregation(users::class);
$this->datasource_stress_test_conditions(users::class, 'user:username');
}
}
+244
View File
@@ -0,0 +1,244 @@
<?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/>.
/**
* Course global search unit tests.
*
* @package core_user
* @copyright 2016 Devang Gaur {@link http://www.devanggaur.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\search;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
/**
* Provides the unit tests for course global search.
*
* @package core
* @copyright 2016 Devang Gaur {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_test extends \advanced_testcase {
/**
* @var string Area id
*/
protected $userareaid = null;
public function setUp(): void {
$this->resetAfterTest(true);
set_config('enableglobalsearch', true);
$this->userareaid = \core_search\manager::generate_areaid('core_user', 'user');
// Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
$search = \testable_core_search::instance();
}
/**
* Indexing users contents.
*
* @return void
*/
public function test_users_indexing(): void {
global $SITE;
// Returns the instance as long as the area is supported.
$searcharea = \core_search\manager::get_search_area($this->userareaid);
$this->assertInstanceOf('\core_user\search\user', $searcharea);
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
// All records.
// Recordset will produce 4 user records:
// Guest User, Admin User and two above generated users.
$recordset = $searcharea->get_recordset_by_timestamp(0);
$this->assertTrue($recordset->valid());
$nrecords = 0;
foreach ($recordset as $record) {
$this->assertInstanceOf('stdClass', $record);
$doc = $searcharea->get_document($record);
$this->assertInstanceOf('\core_search\document', $doc);
$nrecords++;
}
// If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
$recordset->close();
$this->assertEquals(4, $nrecords);
// The +2 is to prevent race conditions.
$recordset = $searcharea->get_recordset_by_timestamp(time() + 2);
// No new records.
$this->assertFalse($recordset->valid());
$recordset->close();
// Context support; first, try an unsupported context type.
$coursecontext = \context_course::instance($SITE->id);
$this->assertNull($searcharea->get_document_recordset(0, $coursecontext));
// Try a specific user, will only return 1 record (that user).
$rs = $searcharea->get_document_recordset(0, \context_user::instance($user1->id));
$this->assertEquals(1, iterator_count($rs));
$rs->close();
}
/**
* Document contents.
*
* @return void
*/
public function test_users_document(): void {
// Returns the instance as long as the area is supported.
$searcharea = \core_search\manager::get_search_area($this->userareaid);
$this->assertInstanceOf('\core_user\search\user', $searcharea);
$user = self::getDataGenerator()->create_user();
$doc = $searcharea->get_document($user);
$this->assertInstanceOf('\core_search\document', $doc);
$this->assertEquals($user->id, $doc->get('itemid'));
$this->assertEquals($this->userareaid . '-' . $user->id, $doc->get('id'));
$this->assertEquals(SITEID, $doc->get('courseid'));
$this->assertFalse($doc->is_set('userid'));
$this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid'));
$this->assertEquals(content_to_text(fullname($user), false), $searcharea->get_document_display_title($doc));
$this->assertEquals(content_to_text($user->description, $user->descriptionformat), $doc->get('content'));
}
/**
* Document accesses.
*
* @return void
*/
public function test_users_access(): void {
global $CFG;
// Returns the instance as long as the area is supported.
$searcharea = \core_search\manager::get_search_area($this->userareaid);
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
$user4 = self::getDataGenerator()->create_user();
$user5 = self::getDataGenerator()->create_user();
$user5->id = 0; // Visitor (not guest).
$deleteduser = self::getDataGenerator()->create_user(array('deleted' => 1));
$unconfirmeduser = self::getDataGenerator()->create_user(array('confirmed' => 0));
$suspendeduser = self::getDataGenerator()->create_user(array('suspended' => 1));
$course1 = self::getDataGenerator()->create_course();
$course2 = self::getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id));
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher');
$this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
$this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user4->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($suspendeduser->id, $course1->id, 'student');
$this->getDataGenerator()->create_group_member(array('userid' => $user2->id, 'groupid' => $group1->id));
$this->getDataGenerator()->create_group_member(array('userid' => $user3->id, 'groupid' => $group1->id));
$this->getDataGenerator()->create_group_member(array('userid' => $user4->id, 'groupid' => $group2->id));
$this->setAdminUser();
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user3->id));
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($deleteduser->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($unconfirmeduser->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($suspendeduser->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access(2));
$this->setUser($user1);
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user3->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user4->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access(1));// Guest user can't be accessed.
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access(2));// Admin user can't be accessed.
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($unconfirmeduser->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($suspendeduser->id));
$this->setUser($user2);
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user3->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user4->id));
$this->setUser($user3);
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user3->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($suspendeduser->id));
$this->setGuestUser();
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user3->id));
$CFG->forceloginforprofiles = 0;
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user3->id));
$this->setUser($user5);
$CFG->forceloginforprofiles = 1;
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($user3->id));
$CFG->forceloginforprofiles = 0;
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user2->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($user3->id));
}
/**
* Test document icon.
*/
public function test_get_doc_icon(): void {
$searcharea = \core_search\manager::get_search_area($this->userareaid);
$user = self::getDataGenerator()->create_user();
$doc = $searcharea->get_document($user);
$result = $searcharea->get_doc_icon($doc);
$this->assertEquals('i/user', $result->get_name());
$this->assertEquals('moodle', $result->get_component());
}
/**
* Test assigned search categories.
*/
public function test_get_category_names(): void {
$searcharea = \core_search\manager::get_search_area($this->userareaid);
$expected = ['core-users'];
$this->assertEquals($expected, $searcharea->get_category_names());
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
<?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 core_user;
/**
* Unit tests for user roles editable class.
*
* @package core_user
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userroleseditable_test extends \advanced_testcase {
/**
* Test user roles editable.
*/
public function test_update(): void {
global $DB;
$this->resetAfterTest();
// Create user and modify user profile.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course1->id);
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
role_assign($teacherrole->id, $user1->id, $coursecontext->id);
role_assign($teacherrole->id, $user2->id, $coursecontext->id);
$this->setAdminUser();
accesslib_clear_all_caches_for_unit_testing();
// Use the userroleseditable api to remove all roles from user1 and give user2 student and teacher.
$itemid = $course1->id . ':' . $user1->id;
$newvalue = json_encode([]);
$result = \core_user\output\user_roles_editable::update($itemid, $newvalue);
$this->assertTrue($result instanceof \core_user\output\user_roles_editable);
$currentroles = get_user_roles_in_course($user1->id, $course1->id);
$this->assertEmpty($currentroles);
$this->setAdminUser();
accesslib_clear_all_caches_for_unit_testing();
$itemid = $course1->id . ':' . $user2->id;
$newvalue = json_encode([$teacherrole->id, $studentrole->id]);
$result = \core_user\output\user_roles_editable::update($itemid, $newvalue);
$this->assertTrue($result instanceof \core_user\output\user_roles_editable);
$currentroles = get_user_roles_in_course($user2->id, $course1->id);
$this->assertStringContainsString('Non-editing teacher', $currentroles);
$this->assertStringContainsString('Student', $currentroles);
}
}
+263
View File
@@ -0,0 +1,263 @@
<?php
// This file is part of Moodle - https://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 core_user;
use testable_user_selector;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/user/selector/lib.php');
require_once($CFG->dirroot.'/user/tests/fixtures/testable_user_selector.php');
/**
* Tests for the implementation of {@link user_selector_base} class.
*
* @package core_user
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userselector_test extends \advanced_testcase {
/**
* Setup the environment for the tests.
*/
protected function setup_hidden_siteidentity() {
global $CFG, $DB;
$CFG->showuseridentity = 'idnumber,country,city';
$CFG->hiddenuserfields = 'country,city';
$env = new \stdClass();
$env->student = $this->getDataGenerator()->create_user();
$env->teacher = $this->getDataGenerator()->create_user();
$env->manager = $this->getDataGenerator()->create_user();
$env->course = $this->getDataGenerator()->create_course();
$env->coursecontext = \context_course::instance($env->course->id);
$env->teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$env->studentrole = $DB->get_record('role', array('shortname' => 'student'));
$env->managerrole = $DB->get_record('role', array('shortname' => 'manager'));
role_assign($env->studentrole->id, $env->student->id, $env->coursecontext->id);
role_assign($env->teacherrole->id, $env->teacher->id, $env->coursecontext->id);
role_assign($env->managerrole->id, $env->manager->id, SYSCONTEXTID);
return $env;
}
/**
* No identity fields are not shown to student user (no permission to view identity fields).
*/
public function test_hidden_siteidentity_fields_no_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->student);
$selector = new testable_user_selector('test');
foreach ($selector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectNotHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
}
/**
* Teacher can see students' identity fields only within the course.
*/
public function test_hidden_siteidentity_fields_course_only_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->teacher);
$systemselector = new testable_user_selector('test');
$courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
foreach ($systemselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectNotHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
foreach ($courseselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
}
}
}
/**
* Teacher can be prevented from seeing students' identity fields even within the course.
*/
public function test_hidden_siteidentity_fields_course_prevented_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->teacher);
assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->teacherrole->id, $env->coursecontext->id);
$courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
foreach ($courseselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
}
/**
* Manager can see students' identity fields anywhere.
*/
public function test_hidden_siteidentity_fields_anywhere_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->manager);
$systemselector = new testable_user_selector('test');
$courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
foreach ($systemselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
}
}
foreach ($courseselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
}
}
}
/**
* Manager can be prevented from seeing hidden fields outside the course.
*/
public function test_hidden_siteidentity_fields_schismatic_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->manager);
// Revoke the capability to see hidden user fields outside the course.
// Note that inside the course, the manager can still see the hidden identifiers as this is currently
// controlled by a separate capability for legacy reasons. This is counter-intuitive behaviour and is
// likely to be fixed in MDL-51630.
assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
$systemselector = new testable_user_selector('test');
$courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
foreach ($systemselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
foreach ($courseselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
}
}
}
/**
* Two capabilities must be currently set to prevent manager from seeing hidden fields.
*/
public function test_hidden_siteidentity_fields_hard_to_prevent_access(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->manager);
assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
$systemselector = new testable_user_selector('test');
$courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
foreach ($systemselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
foreach ($courseselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectNotHasProperty('country', $user);
$this->assertObjectNotHasProperty('city', $user);
}
}
}
/**
* For legacy reasons, user selectors supported ability to override $CFG->showuseridentity.
*
* However, this was found as violating the principle of respecting site privacy settings. So the feature has been
* dropped in Moodle 3.6.
*/
public function test_hidden_siteidentity_fields_explicit_extrafields(): void {
$this->resetAfterTest();
$env = $this->setup_hidden_siteidentity();
$this->setUser($env->manager);
$implicitselector = new testable_user_selector('test');
$explicitselector = new testable_user_selector('test', ['extrafields' => ['email', 'department']]);
$this->assertDebuggingCalled();
foreach ($implicitselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
$this->assertObjectNotHasProperty('email', $user);
$this->assertObjectNotHasProperty('department', $user);
}
}
foreach ($explicitselector->find_users('') as $found) {
foreach ($found as $user) {
$this->assertObjectHasProperty('idnumber', $user);
$this->assertObjectHasProperty('country', $user);
$this->assertObjectHasProperty('city', $user);
$this->assertObjectNotHasProperty('email', $user);
$this->assertObjectNotHasProperty('department', $user);
}
}
}
}