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
+316
View File
@@ -0,0 +1,316 @@
@core_reportbuilder @javascript
Feature: Configure access to reports based on intended audience
As an admin
I want to restrict which users have access to a report
Background:
Given the following "custom profile fields" exist:
| datatype | shortname | name |
| text | fruit | Fruit |
And the following "users" exist:
| username | firstname | middlename | lastname | email | profile_field_fruit |
| user1 | User | One | 1 | user1@example.com | Apple |
| user2 | User | Two | 2 | user2@example.com | Banana |
| user3 | User | Three | 3 | user3@example.com | Banana |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 1 |
Scenario: Configure report audience with manually added users audience type
When I log in as "user1"
And I follow "Reports" in the user menu
And I should see "Nothing to display"
And I log out
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Access" dynamic tab
And I should see "Nothing to display"
And I click on the "Audience" dynamic tab
And I should see "There are no audiences for this report"
And I click on "Add audience 'Manually added users'" "link"
And I should see "Added audience 'Manually added users'"
And I should see "Audience not saved" in the "Manually added users" "core_reportbuilder > Audience"
And I set the following fields in the "Manually added users" "core_reportbuilder > Audience" to these values:
| Add users manually | User 1,User 3 |
And I press "Save changes"
And I should see "Audience saved"
And I should not see "Audience not saved" in the "Manually added users" "core_reportbuilder > Audience"
And I should see "User 1, User 3" in the "Manually added users" "core_reportbuilder > Audience"
And I should not see "There are no audiences for this report"
And I click on the "Access" dynamic tab
And I should see "User 1" in the "reportbuilder-table" "table"
And I should not see "User 2" in the "reportbuilder-table" "table"
And I should see "User 3" in the "reportbuilder-table" "table"
And I log out
And I log in as "user1"
And I follow "Reports" in the user menu
Then I should see "My report" in the "reportbuilder-table" "table"
And I click on "My report" "link" in the "My report" "table_row"
And I should see "User 1" in the "reportbuilder-table" "table"
Scenario: Configure report audience with administrator audience type
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
When I click on "Add audience 'Site administrators'" "link"
And I press "Save changes"
Then I should see "Audience saved"
And I click on the "Access" dynamic tab
And I should see "Admin User" in the "reportbuilder-table" "table"
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should not see "User 2" in the "reportbuilder-table" "table"
And I should not see "User 3" in the "reportbuilder-table" "table"
Scenario: Configure report audience with has system role audience type
Given the following "role assigns" exist:
| user | role | contextlevel | reference |
| user2 | manager | System | |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
When I click on "Add audience 'Assigned system role'" "link"
And I should see "Added audience 'Assigned system role'"
And I set the following fields in the "Assigned system role" "core_reportbuilder > Audience" to these values:
| Select a role | Manager |
And I press "Save changes"
Then I should see "Audience saved"
And I should see "Manager" in the "Assigned system role" "core_reportbuilder > Audience"
And I click on the "Access" dynamic tab
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should see "User 2" in the "reportbuilder-table" "table"
And I should not see "User 3" in the "reportbuilder-table" "table"
And I log out
And I am on the "My report" "reportbuilder > View" page logged in as "user2"
And I should see "User 1" in the "reportbuilder-table" "table"
Scenario: Configure report audience with Member of cohort audience type
Given the following "cohorts" exist:
| name | idnumber |
| Cohort1 | cohort1 |
And the following "cohort members" exist:
| cohort | user |
| cohort1 | user3 |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
When I click on "Add audience 'Member of cohort'" "link"
And I should see "Added audience 'Member of cohort'"
And I set the following fields in the "Member of cohort" "core_reportbuilder > Audience" to these values:
| Select members from cohort | Cohort1 |
And I press "Save changes"
Then I should see "Audience saved"
And I should see "Cohort1" in the "Member of cohort" "core_reportbuilder > Audience"
And I click on the "Access" dynamic tab
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should not see "User 2" in the "reportbuilder-table" "table"
And I should see "User 3" in the "reportbuilder-table" "table"
And I log out
And I am on the "My report" "reportbuilder > View" page logged in as "user3"
And I should see "User 1" in the "reportbuilder-table" "table"
Scenario: Configure report audience with Member of cohort audience type with no cohorts available
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on the "Audience" dynamic tab
Then "Add audience 'All users'" "link" should exist
# This audience type should be disabled because there are no cohorts available.
And "Add audience 'Member of cohort'" "link" should not exist
And the "title" attribute of "//div[@data-region='sidebar-menu']/descendant::div[normalize-space(.)='Member of cohort']" "xpath_element" should contain "Not available"
Scenario: Configure report audience as user who cannot use specific audience
Given the following "users" exist:
| username | firstname | lastname |
| manager1 | Manager | 1 |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager1 | manager | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Allow | manager | System | |
| moodle/cohort:view | Prohibit | manager | System | |
And I am on the "My report" "reportbuilder > Editor" page logged in as "manager1"
When I click on the "Audience" dynamic tab
Then I should not see "Member of cohort" in the "[data-region='sidebar-menu']" "css_element"
Scenario: Search for and add audience to report
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
When I set the field "Search" in the "[data-region=sidebar-menu]" "css_element" to "All users"
Then I should see "All users" in the "[data-region=sidebar-menu]" "css_element"
And I should not see "Member of cohort" in the "[data-region=sidebar-menu]" "css_element"
And I click on "Add audience 'All users'" "link"
And I should see "Added audience 'All users'"
And I press "Save changes"
And I should see "Audience saved"
Scenario: Rename report audience
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
And I click on "Add audience 'All users'" "link"
And I press "Save changes"
When I set the field "Rename audience 'All users'" to "All my lovely users"
And I reload the page
Then I should see "All my lovely users" in the "[data-region='audience-card']" "css_element"
Scenario: Rename report audience using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
And I click on "Add audience 'All users'" "link"
And I press "Save changes"
When I set the field "Rename audience 'All users'" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
And I reload the page
Then I should see "English" in the "[data-region='audience-card']" "css_element"
And I should not see "Spanish" in the "[data-region='audience-card']" "css_element"
Scenario: Delete report audience
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
And I click on "Add audience 'All users'" "link"
And I press "Save changes"
When I click on "Delete audience 'All users'" "button"
And I should see "Are you sure you want to delete the audience 'All users'?" in the "Delete audience 'All users'" "dialogue"
And I click on "Delete" "button" in the "Delete audience 'All users'" "dialogue"
Then I should see "Deleted audience 'All users'"
And "All users" "core_reportbuilder > Audience" should not exist
And I should see "There are no audiences for this report"
Scenario: Delete report audience used in schedule
Given the following "core_reportbuilder > Audiences" exist:
| report | configdata |
| My report | |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
And I press "New schedule"
And I set the following fields in the "New schedule" "dialogue" to these values:
| Name | My schedule |
| Starting from | ##tomorrow 11:00## |
| All users | 1 |
| Subject | Cause you know just what to say |
| Body | And you know just what to do |
And I click on "Save" "button" in the "New schedule" "dialogue"
When I click on the "Audience" dynamic tab
Then I should see "This audience is used in a schedule for this report" in the "All users" "core_reportbuilder > Audience"
And I click on "Delete audience 'All users'" "button"
And I should see "This audience is used in a schedule for this report" in the "Delete audience 'All users'" "dialogue"
And I click on "Delete" "button" in the "Delete audience 'All users'" "dialogue"
And I should see "Deleted audience 'All users'"
And "All users" "core_reportbuilder > Audience" should not exist
And I should see "There are no audiences for this report"
Scenario: Edit report audience with manually added users audience type
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Access" dynamic tab
And I should see "Nothing to display"
And I click on the "Audience" dynamic tab
And I click on "Add audience 'Manually added users'" "link"
And I set the following fields in the "Manually added users" "core_reportbuilder > Audience" to these values:
| Add users manually | User 1,User 3 |
And I press "Save changes"
When I press "Edit audience 'Manually added users'"
And "User 1" "autocomplete_selection" in the "Manually added users" "core_reportbuilder > Audience" should be visible
And "User 3" "autocomplete_selection" in the "Manually added users" "core_reportbuilder > Audience" should be visible
And I set the following fields in the "Manually added users" "core_reportbuilder > Audience" to these values:
| Add users manually | User 2 |
And I press "Save changes"
Then I should see "Audience saved"
And I should see "User 2" in the "Manually added users" "core_reportbuilder > Audience"
And I click on the "Access" dynamic tab
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should see "User 2" in the "reportbuilder-table" "table"
And I should not see "User 3" in the "reportbuilder-table" "table"
Scenario: View configured user additional names on the access tab
Given the following config values are set as admin:
| alternativefullnameformat | firstname middlename lastname |
And the following "core_reportbuilder > Audiences" exist:
| report | configdata |
| My report | |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on the "Access" dynamic tab
Then I should see "User One 1" in the "reportbuilder-table" "table"
And I should see "User Two 2" in the "reportbuilder-table" "table"
And I should see "User Three 3" in the "reportbuilder-table" "table"
# Now sort each of them.
And I click on "First name" "link" in the "reportbuilder-table" "table"
And "Admin User" "table_row" should appear before "User One 1" "table_row"
And I click on "First name" "link" in the "reportbuilder-table" "table"
And "User One 1" "table_row" should appear before "Admin User" "table_row"
And I click on "Middle name" "link" in the "reportbuilder-table" "table"
And "User Two 2" "table_row" should appear before "User One 1" "table_row"
And I click on "Middle name" "link" in the "reportbuilder-table" "table"
And "User One 1" "table_row" should appear before "User Two 2" "table_row"
And I click on "Last name" "link" in the "reportbuilder-table" "table"
And "User One 1" "table_row" should appear before "User Two 2" "table_row"
And I click on "Last name" "link" in the "reportbuilder-table" "table"
And "User Two 2" "table_row" should appear before "User One 1" "table_row"
Scenario: View configured user identity fields on the access tab
Given the following config values are set as admin:
| showuseridentity | email,profile_field_fruit |
And the following "core_reportbuilder > Audiences" exist:
| report | configdata |
| My report | |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on the "Access" dynamic tab
Then the following should exist in the "reportbuilder-table" table:
| -1- | Email address | Fruit |
| User 1 | user1@example.com | Apple |
| User 2 | user2@example.com | Banana |
| User 3 | user3@example.com | Banana |
# Now let's filter them.
And I click on "Filters" "button"
And I set the following fields in the "Fruit" "core_reportbuilder > Filter" to these values:
| Fruit operator | Is equal to |
| Fruit value | Banana |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should see "User 2" in the "reportbuilder-table" "table"
And I should see "User 3" in the "reportbuilder-table" "table"
And I set the following fields in the "Email address" "core_reportbuilder > Filter" to these values:
| Email address operator | Contains |
| Email address value | user2 |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I should not see "User 1" in the "reportbuilder-table" "table"
And I should see "User 2" in the "reportbuilder-table" "table"
And I should not see "User 3" in the "reportbuilder-table" "table"
Scenario: View report as a user with edit capability
Given the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:edit | Allow | viewreportsrole | System | |
| moodle/reportbuilder:view | Prohibit | viewreportsrole | System | |
When I log in as "user1"
And I follow "Reports" in the user menu
And I should see "Nothing to display"
And I click on "New report" "button"
And I set the following fields in the "New report" "dialogue" to these values:
| Name | My user1 report |
| Report source | Users |
| Include default setup | 1 |
And I click on "Save" "button" in the "New report" "dialogue"
And I click on "Close 'My user1 report' editor" "button"
And I should see "My user1 report" in the "reportbuilder-table" "table"
And I press "View report" action in the "My user1 report" report row
And I should see "User 1" in the "reportbuilder-table" "table"
Scenario: View report as a user with editall capability
Given the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Allow | viewreportsrole | System | |
| moodle/reportbuilder:edit | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:view | Prohibit | viewreportsrole | System | |
When I log in as "user1"
And I follow "Reports" in the user menu
Then I should see "My report" in the "reportbuilder-table" "table"
And I press "View report" action in the "My report" report row
And I should see "User 1" in the "reportbuilder-table" "table"
@@ -0,0 +1,122 @@
<?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);
// 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 core_reportbuilder\local\aggregation\groupconcatdistinct;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\report\column;
/**
* Behat step definitions for Report builder
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_reportbuilder extends behat_base {
/**
* Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
*
* Recognised page names are:
* | type | identifier | description |
* | Editor | Report name | Custom report editor |
* | View | Report name | Custom report view |
*
* @param string $type
* @param string $identifier
* @return moodle_url
* @throws Exception for unrecognised report or page type
*/
protected function resolve_page_instance_url(string $type, string $identifier): moodle_url {
if (!$report = report::get_record(['name' => $identifier])) {
throw new Exception("Unknown report '{$identifier}'");
}
switch ($type) {
case 'Editor':
return new moodle_url('/reportbuilder/edit.php', ['id' => $report->get('id')]);
case 'View':
return new moodle_url('/reportbuilder/view.php', ['id' => $report->get('id')]);
default:
throw new Exception("Unrecognised reportbuilder page type '{$type}'");
}
}
/**
* Return the list of partial named selectors
*
* @return behat_component_named_selector[]
*/
public static function get_partial_named_selectors(): array {
return [
new behat_component_named_selector('Filter', [
".//*[@data-region='filters-form']//*[@data-filter-for=%locator%]",
]),
new behat_component_named_selector('Condition', [
".//*[@data-region='conditions-form']//*[@data-condition-name=%locator%]",
]),
new behat_component_named_selector('Audience', [
".//*[@data-region='audiences']//*[@data-audience-title=%locator%]",
]),
];
}
/**
* Set aggregation for given column in report editor (proxied so we can skip if aggregation type not available)
*
* @When I set the :column column aggregation to :aggregation
*
* @param string $column
* @param string $aggregation
*
* @throws \Moodle\BehatExtension\Exception\SkippedException
*/
public function i_set_the_column_aggregation_to(string $column, string $aggregation): void {
// Skip if aggregation type unavailable.
$aggregationgroupconcatdistinct = (string) groupconcatdistinct::get_name();
if ($aggregation === $aggregationgroupconcatdistinct && !groupconcatdistinct::compatible(column::TYPE_TEXT)) {
throw new \Moodle\BehatExtension\Exception\SkippedException("{$aggregationgroupconcatdistinct} not available");
}
$editlabel = get_string('aggregatecolumn', 'core_reportbuilder', $column);
$this->execute('behat_forms::i_set_the_field_to', [$this->escape($editlabel), $this->escape($aggregation)]);
}
/**
* Press a given action from the action menu in a given report row
*
* @When I press :action action in the :row report row
*
* @param string $action
* @param string $row
*/
public function i_press_action_in_the_report_row(string $action, string $row): void {
$this->execute('behat_action_menu::i_choose_in_the_named_menu_in_container', [
$this->escape($action),
get_string('actions', 'core_reportbuilder'),
$this->escape($row),
'table_row',
]);
}
}
+110
View File
@@ -0,0 +1,110 @@
@core_reportbuilder @javascript
Feature: Manage card view settings in the report editor
In order to manage a report card view settings
As an admin
I need to be able to edit and save the form
Background:
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
| My report | user:city |
And the following "users" exist:
| username | firstname | lastname | email | city |
| l.smith | Lionel | Smith | lionel@example.com | Bilbao |
Scenario: Edit card view settings form
When I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
Then I change window size to "large"
And I click on "Show/hide 'Card view'" "button"
# Check default values.
And the following fields match these values:
| Columns visible | 1 |
| First column title | No |
And I set the following fields to these values:
| Columns visible | 3 |
| First column title | Yes |
And I press "Save changes"
And I should see "Card view settings saved"
# Let's check that after switching to preview mode card view form gets rendered again.
And I click on "Switch to preview mode" "button"
And I click on "Switch to edit mode" "button"
And I click on "Show/hide 'Card view'" "button"
And the following fields match these values:
| Columns visible | 3 |
| First column title | Yes |
And I click on "Delete column 'Full name'" "button"
And I click on "Delete" "button" in the "Delete column 'Full name'" "dialogue"
# Check that 'Columns visible' select updates taking into account report maximum columns.
And the field "visiblecolumns" matches value "2"
And the "visiblecolumns" select box should not contain "3"
Scenario: Show Card view
When I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
And I press "Switch to preview mode"
And I change window size to "530x812"
# Card view should just show user fullname while collapsed with default settings.
And I should see "Lionel Smith" in the "reportbuilder-table" "table"
And I should not see "lionel@example.com" in the "reportbuilder-table" "table"
And I should not see "Bilbao" in the "reportbuilder-table" "table"
And I click on "Show/hide 'Lionel Smith'" "button" in the "reportbuilder-table" "table"
And I should see "Lionel Smith" in the "reportbuilder-table" "table"
And I should see "lionel@example.com" in the "reportbuilder-table" "table"
And I should see "Bilbao" in the "reportbuilder-table" "table"
# Card view do not show first column title with default settings.
And "[data-cardtitle=\"Full name\"]" "css_element" should not exist in the "reportbuilder-table" "table"
And "[data-cardtitle=\"Email address\"]" "css_element" should exist in the "reportbuilder-table" "table"
And "[data-cardtitle=\"City/town\"]" "css_element" should exist in the "reportbuilder-table" "table"
# Change 'Columns visible' to 3 and 'First column title' to yes.
And I change window size to "large"
And I press "Switch to edit mode"
And I click on "Show/hide 'Card view'" "button"
And I set the following fields to these values:
| Columns visible | 3 |
| First column title | Yes |
And I press "Save changes"
# Check now all the columns are shown in the card and there is no toggle button.
And I press "Switch to preview mode"
And I change window size to "530x812"
And I should see "Lionel Smith" in the "reportbuilder-table" "table"
And I should see "lionel@example.com" in the "reportbuilder-table" "table"
And I should see "Bilbao" in the "reportbuilder-table" "table"
And "[data-cardtitle=\"Full name\"]" "css_element" should exist in the "reportbuilder-table" "table"
And "[data-cardtitle=\"Email address\"]" "css_element" should exist in the "reportbuilder-table" "table"
And "[data-cardtitle=\"City/town\"]" "css_element" should exist in the "reportbuilder-table" "table"
And "Show/hide 'Lionel Smith'" "button" should not exist
Scenario Outline: Toggle card view according to content of first column
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| New report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| New report | <firstcolumn> |
| New report | user:email |
And the following "core_reportbuilder > Conditions" exist:
| report | uniqueidentifier |
| New report | user:username |
When I am on the "New report" "reportbuilder > Editor" page logged in as "admin"
And I click on "Show/hide 'Conditions'" "button"
# Make sure we're only viewing our test user in the report.
And I set the following fields in the "Username" "core_reportbuilder > Condition" to these values:
| Username operator | Is equal to |
| Username value | l.smith |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
# Now use the card show/hide toggle.
And I change window size to "530x812"
And I press "Switch to preview mode"
And I should not see "lionel@example.com" in the "reportbuilder-table" "table"
And I click on "<togglebutton>" "button" in the "reportbuilder-table" "table"
Then I should see "lionel@example.com" in the "reportbuilder-table" "table"
Examples:
| firstcolumn | togglebutton |
| user:fullnamewithlink | Show/hide 'Lionel Smith' |
| user:firstname | Show/hide 'Lionel' |
| user:idnumber | Show/hide card |
@@ -0,0 +1,161 @@
@core_reportbuilder @javascript
Feature: Manage custom report columns aggregation
In order to manage the aggregation for columns of custom reports
As an admin
I need to select an aggregation for columns
Background:
Given the following "users" exist:
| username | firstname | lastname | email | confirmed | lastaccess |
| user01 | Bill | Richie | user01@example.com | 1 | ##2 days ago## |
| user02 | Ben | Richie | user02@example.com | 1 | ##3 days ago## |
| user03 | Bill | Richie | user03@example.com | 0 | ##3 days ago## |
Scenario Outline: Aggregate a text column
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:firstname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "First name" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'First name'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Comma separated distinct values | Ben, Bill |
| Comma separated values | Ben, Bill, Bill |
| Count | 3 |
| Count distinct | 2 |
Scenario Outline: Aggregate a text column containing multiple fields
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:fullname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "Full name" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'Full name'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Comma separated distinct values | Ben Richie, Bill Richie |
| Comma separated values | Ben Richie, Bill Richie, Bill Richie |
| Count | 3 |
| Count distinct | 2 |
Scenario Outline: Aggregate a time column
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:lastaccess |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "Last access" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'Last access'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Count | 3 |
| Count distinct | 2 |
| Maximum | ##2 days ago##%A, %d %B %Y## |
| Minimum | ##3 days ago##%A, %d %B %Y## |
Scenario Outline: Aggregate a boolean column
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:confirmed |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "Confirmed" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'Confirmed'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Comma separated distinct values | No, Yes |
| Comma separated values | No, Yes, Yes |
| Count | 3 |
| Count distinct | 2 |
| Maximum | Yes |
| Minimum | No |
| Average | 0.7 |
| Percentage | 66.7% |
| Sum | 2 |
Scenario Outline: Aggregated columns display localised floats
Given the following "language customisations" exist:
| component | stringid | value |
| core_langconfig | decsep | , |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:confirmed |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "Confirmed" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'Confirmed'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Average | 0,7 |
| Percentage | 66,7% |
Scenario Outline: Aggregated columns display localised list separators
Given the following "language customisations" exist:
| component | stringid | value |
| core_langconfig | listsep | ; |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
| My report | user:firstname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I set the "First name" column aggregation to "<aggregation>"
Then I should see "Aggregated column 'First name'"
And I should see "<output>" in the "Richie" "table_row"
Examples:
| aggregation | output |
| Comma separated distinct values | Ben; Bill |
| Comma separated values | Ben; Bill; Bill |
Scenario: Show unique report rows
Given the following "core_reportbuilder > Reports" exist:
| name | source | default | uniquerows |
| My report | core_user\reportbuilder\datasource\users | 0 | 1 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:firstname |
| My report | user:lastname |
When I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
Then the following should exist in the "reportbuilder-table" table:
| -1- | -2- |
| Admin | User |
| Ben | Richie |
| Bill | Richie |
# Assert there is no 4th row (duplicate Bill Richie) because we're showing unique rows.
And "//table[@data-region='reportbuilder-table']/tbody/tr[not(@class = 'emptyrow')][4]" "xpath_element" should not exist
And I set the "First name" column aggregation to "Comma separated values"
And the following should exist in the "reportbuilder-table" table:
| -1- | -2- |
| Admin | User |
| Ben, Bill, Bill | Richie |
@@ -0,0 +1,83 @@
@core_reportbuilder @javascript
Feature: Manage custom report columns
In order to manage the columns of custom reports
As an admin
I need to add, edit and delete columns in a report
Scenario: Add column to report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Add column 'Full name'" "link"
Then I should see "Added column 'Full name'"
And I should see "Full name" in the "reportbuilder-table" "table"
Scenario: Search for and add column to report
Given the following "core_reportbuilder > Report" exists:
| name | My report |
| source | core_user\reportbuilder\datasource\users |
| default | 0 |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I set the field "Search" in the "[data-region=sidebar-menu]" "css_element" to "Last name"
Then I should see "Last name" in the "[data-region=sidebar-menu]" "css_element"
And I should not see "Email address" in the "[data-region=sidebar-menu]" "css_element"
And I click on "Add column 'Last name'" "link"
And I should see "Added column 'Last name'"
And I should see "Last name" in the "reportbuilder-table" "table"
Scenario: Rename column in report
Given the following "core_reportbuilder > Report" exists:
| name | My report |
| source | core_user\reportbuilder\datasource\users |
| default | 0 |
And the following "core_reportbuilder > Column" exists:
| report | My report |
| uniqueidentifier | user:fullname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I set the field "Rename column 'Full name'" to "My renamed column"
And I reload the page
Then I should see "My renamed column" in the "reportbuilder-table" "table"
Scenario: Rename column in report using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I set the field "Rename column 'Full name'" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
And I reload the page
Then I should see "English" in the "reportbuilder-table" "table"
And I should not see "Spanish" in the "reportbuilder-table" "table"
Scenario: Move column in report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
| My report | user:lastaccess |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Move column 'Last access'" "button"
And I click on "After \"Full name\"" "link" in the "Move column 'Last access'" "dialogue"
Then I should see "Moved column 'Last access'"
And "Last access" "text" should appear before "Email address" "text"
Scenario: Delete column from report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Delete column 'Full name'" "button"
And I click on "Delete" "button" in the "Delete column 'Full name'" "dialogue"
Then I should see "Deleted column 'Full name'"
And I should see "Nothing to display"
@@ -0,0 +1,98 @@
@core_reportbuilder @javascript
Feature: Manage custom report columns sorting
In order to manage the sorting for columns of custom reports
As an admin
I need to be able to enable/disable, change sort direction and reorder sorting for columns
Background:
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:username |
| My report | user:lastname |
| My report | user:firstname |
And the following "users" exist:
| username | firstname | lastname | email |
| user01 | Alice | Zebra | user01@example.com |
| user02 | Zoe | Aardvark | user02@example.com |
| user03 | Alice | Badger | user03@example.com |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
Scenario: Toggle column sorting in report
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
# This will be the fallback sort after toggling lastname sorting.
And I click on "Enable initial sorting for column 'First name'" "checkbox"
When I click on "Enable initial sorting for column 'Last name'" "checkbox"
Then I should see "Updated sorting for column 'Last name'"
And "user02" "table_row" should appear before "user01" "table_row"
And I click on "Disable initial sorting for column 'Last name'" "checkbox"
And I should see "Updated sorting for column 'Last name'"
And "user01" "table_row" should appear before "user02" "table_row"
Scenario: Change column sort direction in report
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
When I click on "Enable initial sorting for column 'Last name'" "checkbox"
And I click on "Sort column 'Last name' descending" "button"
Then I should see "Updated sorting for column 'Last name'"
And "user01" "table_row" should appear before "user02" "table_row"
And I click on "Sort column 'Last name' ascending" "button"
And I should see "Updated sorting for column 'Last name'"
And "user02" "table_row" should appear before "user01" "table_row"
Scenario: Change column sort order in report
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
When I click on "Enable initial sorting for column 'Last name'" "checkbox"
And I click on "Enable initial sorting for column 'First name'" "checkbox"
And I click on "Move sorting for column 'First name'" "button"
And I click on "To the top of the list" "link" in the "Move sorting for column 'First name'" "dialogue"
Then I should see "Updated sorting for column 'First name'"
And "First name" "text" should appear before "Last name" "text" in the "#settingssorting" "css_element"
And "user01" "table_row" should appear before "user02" "table_row"
Scenario: Change column sorting for column sorted by multiple fields
Given I change window size to "large"
And I click on "Add column 'Full name'" "link"
And I click on "Show/hide 'Sorting'" "button"
When I click on "Enable initial sorting for column 'Full name'" "checkbox"
Then I should see "Updated sorting for column 'Full name'"
# User1 = Alice Zebra; User2=Zoe Aardvark; User3 = Alice Badger.
And "user03" "table_row" should appear before "user01" "table_row"
And "user01" "table_row" should appear before "user02" "table_row"
And I click on "Sort column 'Full name' descending" "button"
And I should see "Updated sorting for column 'Full name'"
And "user02" "table_row" should appear before "user01" "table_row"
And "user01" "table_row" should appear before "user03" "table_row"
Scenario: Configured report sorting is always applied when editing
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
# Sort by last name descending.
When I click on "Enable initial sorting for column 'Last name'" "checkbox"
Then "user02" "table_row" should appear before "user01" "table_row"
# Switching to preview mode should observe report config.
And I click on "Switch to preview mode" "button"
And "user02" "table_row" should appear before "user01" "table_row"
# Custom sorting for the user.
And I click on "Sort by First name Ascending" "link"
And "user01" "table_row" should appear before "user02" "table_row"
# Switching back to edit mode should observe report config.
And I click on "Switch to edit mode" "button"
And "user02" "table_row" should appear before "user01" "table_row"
Scenario: Sortable columns are updated when column is added to report
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
When I click on "Add column 'Full name'" "link"
Then I should see "Full name" in the "#settingssorting" "css_element"
Scenario: Sortable columns are updated when column is deleted from report
Given I change window size to "large"
And I click on "Show/hide 'Sorting'" "button"
When I click on "Delete column 'Username'" "button"
And I click on "Delete" "button" in the "Delete column 'Username'" "dialogue"
Then I should not see "Username" in the "#settingssorting" "css_element"
@@ -0,0 +1,100 @@
@core_reportbuilder @javascript
Feature: Manage custom report conditions
In order to manage the conditions of custom reports
As an admin
I need to add, edit and delete conditions in a report
Background:
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
And the following "users" exist:
| username | firstname | lastname | email | interests |
| user01 | User | One | user01@example.com | lionel, dancing |
| user02 | User | Two | user02@example.com | |
Scenario: Add condition to report
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I click on "Show/hide 'Conditions'" "button"
Then I should see "There are no conditions selected" in the "[data-region='settings-conditions']" "css_element"
And I set the field "Select a condition" to "Email address"
And I should see "Added condition 'Email address'"
And I should not see "There are no conditions selected" in the "[data-region='settings-conditions']" "css_element"
And I set the following fields in the "Email address" "core_reportbuilder > Condition" to these values:
| Email address operator | Does not contain |
| Email address value | user02 |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
And I should see "Conditions applied"
And I should see "User One" in the "reportbuilder-table" "table"
And I should not see "User Two" in the "reportbuilder-table" "table"
Scenario: Add tags condition to report
Given the following "core_reportbuilder > Condition" exists:
| report | My report |
| uniqueidentifier | tag:name |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Show/hide 'Conditions'" "button"
And I set the following fields in the "Tag name" "core_reportbuilder > Condition" to these values:
| Tag name operator | Is equal to |
| Tag name value | dancing |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
Then I should see "Conditions applied"
And I should see "User One" in the "reportbuilder-table" "table"
And I should not see "User Two" in the "reportbuilder-table" "table"
Scenario: Move condition in report
Given the following "core_reportbuilder > Conditions" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
| My report | user:country |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Show/hide 'Conditions'" "button"
And I click on "Move condition 'Country'" "button"
And I click on "After \"Full name\"" "link" in the "Move condition 'Country'" "dialogue"
Then I should see "Moved condition 'Country'"
And "Country" "text" should appear before "Email address" "text"
Scenario: Delete condition from report
Given the following "core_reportbuilder > Conditions" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I click on "Show/hide 'Conditions'" "button"
And I set the following fields in the "Email address" "core_reportbuilder > Condition" to these values:
| Email address operator | Does not contain |
| Email address value | user02 |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
And I click on "Delete condition 'Email address'" "button"
And I click on "Delete" "button" in the "Delete condition 'Email address'" "dialogue"
Then I should see "Deleted condition 'Email address'"
And I should see "There are no conditions selected" in the "[data-region='settings-conditions']" "css_element"
And "[data-region='active-conditions']" "css_element" should not exist
And I should see "User One" in the "reportbuilder-table" "table"
And I should see "User Two" in the "reportbuilder-table" "table"
Scenario: Reset conditions in report
Given the following "core_reportbuilder > Conditions" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I click on "Show/hide 'Conditions'" "button"
And I set the following fields in the "Email address" "core_reportbuilder > Condition" to these values:
| Email address operator | Does not contain |
| Email address value | example.com |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
And I should see "Nothing to display"
And I click on "Reset all" "button" in the "[data-region='settings-conditions']" "css_element"
And I click on "Reset all" "button" in the "Reset conditions" "dialogue"
Then I should see "Conditions reset"
And the following fields in the "Email address" "core_reportbuilder > Condition" match these values:
| Email address operator | Is any value |
And I should see "User One" in the "reportbuilder-table" "table"
And I should see "User Two" in the "reportbuilder-table" "table"
@@ -0,0 +1,321 @@
@core_reportbuilder @javascript
Feature: Manage custom reports
In order to manage custom reports
As an admin
I need to create new and edit existing reports
Scenario: Create custom report with default setup
Given the following "users" exist:
| username | firstname | lastname | suspended |
| user1 | User | 1 | 1 |
| user2 | User | 2 | 0 |
And I log in as "admin"
And I change window size to "large"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "New report" "button"
And I set the following fields in the "New report" "dialogue" to these values:
| Name | My report |
| Report source | Users |
| Include default setup | 1 |
And I click on "Save" "button" in the "New report" "dialogue"
Then I should see "My report"
# Confirm we see the default columns in the report.
And I should see "Full name" in the "Users" "table"
And I should see "Username" in the "Users" "table"
And I should see "Email address" in the "Users" "table"
# Confirm we see the default sorting in the report
And "Admin User" "table_row" should appear before "User 2" "table_row"
And I click on "Show/hide 'Sorting'" "button"
And "Disable initial sorting for column 'Full name'" "checkbox" should exist in the "#settingssorting" "css_element"
And I click on "Show/hide 'Sorting'" "button"
# Confirm we only see not suspended users in the report.
And I should see "Admin User" in the "Users" "table"
And I should see "User 2" in the "Users" "table"
And I should not see "User 1" in the "Users" "table"
# Confirm we see the default conditions in the report.
And I click on "Show/hide 'Conditions'" "button"
Then I should see "Full name" in the "[data-region='settings-conditions']" "css_element"
Then I should see "Username" in the "[data-region='settings-conditions']" "css_element"
Then I should see "Email address" in the "[data-region='settings-conditions']" "css_element"
Then I should see "Suspended" in the "[data-region='settings-conditions']" "css_element"
And the following fields in the "Suspended" "core_reportbuilder > Condition" match these values:
| Suspended operator | No |
# Confirm we see the default filters in the report.
And I click on "Switch to preview mode" "button"
And I click on "Filters" "button" in the "[data-region='core_reportbuilder/report-header']" "css_element"
Then I should see "Full name" in the "[data-region='report-filters']" "css_element"
Then I should see "Username" in the "[data-region='report-filters']" "css_element"
Then I should see "Email address" in the "[data-region='report-filters']" "css_element"
And I click on "Close 'My report' editor" "button"
And the following should exist in the "Reports list" table:
| Name | Report source | Modified by |
| My report | Users | Admin User |
Scenario: Create custom report without default setup
Given I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "New report" "button"
And I set the following fields in the "New report" "dialogue" to these values:
| Report source | Users |
| Include default setup | 0 |
# Try to set the report name to some blank spaces.
And I set the field "Name" in the "New report" "dialogue" to " "
And I click on "Save" "button" in the "New report" "dialogue"
And I should see "Required"
And I set the field "Name" in the "New report" "dialogue" to "My report"
And I click on "Save" "button" in the "New report" "dialogue"
Then I should see "My report"
And I should see "Nothing to display"
And I click on "Close 'My report' editor" "button"
And the following should exist in the "Reports list" table:
| Name | Report source |
| My report | Users |
Scenario: Create custom report as a manager
# Create a report that our manager can access, but not edit.
Given the following "core_reportbuilder > Report" exists:
| name | My report |
| source | core_user\reportbuilder\datasource\users |
And the following "core_reportbuilder > Audience" exists:
| report | My report |
| classname | core_reportbuilder\reportbuilder\audience\allusers |
| configdata | |
And the following "users" exist:
| username | firstname | lastname |
| manager1 | Manager | One |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager1 | manager | System | |
And I log in as "manager1"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "New report" "button"
And I set the following fields in the "New report" "dialogue" to these values:
| Name | Manager report |
| Report source | Users |
| Tags | Cat, Dog |
And I click on "Save" "button" in the "New report" "dialogue"
And I click on "Close 'Manager report' editor" "button"
And the following should exist in the "Reports list" table:
| Name | Tags | Report source |
| Manager report | Cat, Dog | Users |
# Manager can edit their own report, but not those of other users.
And I set the field "Edit report name" in the "Manager report" "table_row" to "Manager report (renamed)"
Then the "Edit report content" item should exist in the "Actions" action menu of the "Manager report (renamed)" "table_row"
And "Edit report name" "link" should not exist in the "My report" "table_row"
And "Actions" "actionmenu" should not exist in the "My report" "table_row"
Scenario: Rename custom report
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
# Try to set the report name to some blank spaces. It should simply ignore the change.
And I set the field "Edit report name" in the "My report" "table_row" to " "
And I set the field "Edit report name" in the "My report" "table_row" to "My renamed report"
And I reload the page
Then the following should exist in the "Reports list" table:
| Name | Report source |
| My renamed report | Users |
Scenario: Rename custom report using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I set the field "Edit report name" in the "My report" "table_row" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
And I reload the page
Then I should see "English" in the "Reports list" "table"
And I should not see "Spanish" in the "Reports list" "table"
# Confirm report name is correctly shown in action.
And I press "Delete report" action in the "English" report row
And I should see "Are you sure you want to delete the report 'English' and all associated data?" in the "Delete report" "dialogue"
And I click on "Cancel" "button" in the "Delete report" "dialogue"
Scenario: Edit custom report from the custom reports page
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
When I press "Edit report details" action in the "My report" report row
And I set the following fields in the "Edit report details" "dialogue" to these values:
| Name | My renamed report |
| Tags | Cat, Dog |
And I click on "Save" "button" in the "Edit report details" "dialogue"
Then I should see "Report updated"
And the following should exist in the "Reports list" table:
| Name | Tags | Report source |
| My renamed report | Cat, Dog | Users |
Scenario Outline: Filter custom reports
Given the following "core_reportbuilder > Reports" exist:
| name | source | tags |
| My users | core_user\reportbuilder\datasource\users | Cat, Dog |
| My courses | core_course\reportbuilder\datasource\courses | |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "Filters" "button"
And I set the following fields in the "<filter>" "core_reportbuilder > Filter" to these values:
| <filter> operator | Is equal to |
| <filter> value | <value> |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then I should see "Filters applied"
And the following should exist in the "Reports list" table:
| Name | Tags | Report source |
| My users | Cat, Dog | Users |
And I should not see "My courses" in the "Reports list" "table"
Examples:
| filter | value |
| Name | My users |
| Report source | Users |
| Tags | Cat |
Scenario: Reset filters in system report
Given the following "core_reportbuilder > Report" exists:
| name | My report |
| source | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "Filters" "button"
And I set the following fields in the "Name" "core_reportbuilder > Filter" to these values:
| Name operator | Contains |
| Name value | Lionel |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Nothing to display"
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
And I should not see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Filters" in the "#dropdownFiltersButton" "css_element"
And "[data-region='report-filters']" "css_element" should be visible
Then I should see "Filters reset"
And the following fields in the "Name" "core_reportbuilder > Filter" match these values:
| Name operator | Is any value |
And I should see "My report" in the "Reports list" "table"
Scenario: Custom report tags are not displayed if tagging is disabled
Given the following config values are set as admin:
| usetags | 0 |
And the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
Then the following should exist in the "Reports list" table:
| Name | Report source |
| My report | Users |
And "Tags" "link" should not exist in the "Reports list" "table"
And I click on "Filters" "button"
And "Tags" "core_reportbuilder > Filter" should not exist
Scenario: Delete custom report
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I log in as "admin"
When I navigate to "Reports > Report builder > Custom reports" in site administration
And I press "Delete report" action in the "My report" report row
And I click on "Delete" "button" in the "Delete report" "dialogue"
Then I should see "Report deleted"
And I should see "Nothing to display"
Scenario: Switch between Preview and Edit mode
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
Then I should see "Preview" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should not see "Edit" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should not see "Filters" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should see "Full name" in the ".reportbuilder-sidebar-menu" "css_element"
Then I click on "Switch to preview mode" "button"
Then I should not see "Preview" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should see "Edit" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should see "Filters" in the "[data-region='core_reportbuilder/report-header']" "css_element"
Then I click on "Switch to edit mode" "button"
Then I should see "Preview" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I should not see "Edit" in the "[data-region='core_reportbuilder/report-header']" "css_element"
Scenario: Access report clicking on the report name
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
When I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "My report" "link" in the "My report" "table_row"
Then I should see "Preview" in the "[data-region='core_reportbuilder/report']" "css_element"
And I should not see "Edit" in the "[data-region='core_reportbuilder/report']" "css_element"
And "button[title='Filters']" "css_element" should not exist in the "[data-region='core_reportbuilder/report']" "css_element"
Scenario: Access report clicking on the view icon
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
When I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I press "View" action in the "My report" report row
Then I should not see "Preview" in the "[data-region='core_reportbuilder/report']" "css_element"
And I should not see "Edit" in the "[data-region='core_reportbuilder/report']" "css_element"
And "button[title='Filters']" "css_element" should exist in the "[data-region='core_reportbuilder/report']" "css_element"
Scenario: Special characters in report name are shown correctly
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My fish & chips report | core_user\reportbuilder\datasource\users |
When I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I press "Edit report content" action in the "My fish & chips report" report row
Then I should see "My fish & chips report" in the "#region-main .navbar" "css_element"
Scenario: Site report limit is observed when creating new reports
Given the following config values are set as admin:
| customreportslimit | 0 |
And the following "core_reportbuilder > Reports" exist:
| name | source |
| Report users | core_user\reportbuilder\datasource\users |
| Report courses | core_course\reportbuilder\datasource\courses |
When I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
Then "New report" "button" should exist
And the following config values are set as admin:
| customreportslimit | 2 |
And I reload the page
And "New report" "button" should not exist
Scenario: Disable live editing of custom reports
Given the following config values are set as admin:
| customreportsliveediting | 0 |
And the following "core_reportbuilder > Reports" exist:
| name | source |
| Report users | core_user\reportbuilder\datasource\users |
When I am on the "Report users" "reportbuilder > Editor" page logged in as "admin"
Then I should see "Viewing of report data while editing is disabled by the site administrator. Switch to preview mode to view the report." in the "[data-region='core_table/dynamic']" "css_element"
And I click on "Switch to preview mode" "button"
And I should see "admin" in the "Users" "table"
And I click on "Close 'Report users' editor" "button"
And I press "View" action in the "Report users" report row
And I should see "admin" in the "Users" "table"
Scenario Outline: Download custom report in different formats
Given the following "users" exist:
| username | firstname | lastname |
| user1 | User | 1 |
| user2 | User | 2 |
And the following "core_reportbuilder > Reports" exist:
| name | source |
| Report users | core_user\reportbuilder\datasource\users |
When I am on the "Report users" "reportbuilder > Editor" page logged in as "admin"
And I click on "Switch to preview mode" "button"
Then I set the field "Download table data as" to "<format>"
And I press "Download"
Examples:
| format |
| Comma separated values (.csv) |
| Microsoft Excel (.xlsx) |
| HTML table |
| Javascript Object Notation (.json) |
| OpenDocument (.ods) |
| Portable Document Format (.pdf) |
@@ -0,0 +1,203 @@
@core_reportbuilder @javascript
Feature: Manage custom report filters
In order to manage the filters of custom reports
As an admin
I need to add, edit and delete filters in a report
Scenario: Add filter to report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Show/hide 'Filters'" "button"
Then I should see "There are no filters selected" in the "[data-region='active-filters']" "css_element"
And I set the field "Select a filter" to "Email address"
And I should see "Added filter 'Email address'"
And I should not see "There are no filters selected" in the "[data-region='active-filters']" "css_element"
And I should see "Email address" in the "[data-region='active-filters']" "css_element"
Scenario: Rename filter in report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on "Show/hide 'Filters'" "button"
When I set the field "Rename filter 'Email address'" to "My Email filter"
And I reload the page
And I click on "Show/hide 'Filters'" "button"
Then I should see "My Email filter" in the "[data-region='active-filters']" "css_element"
Scenario: Rename filter in report using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on "Show/hide 'Filters'" "button"
When I set the field "Rename filter 'Email address'" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
And I reload the page
And I click on "Show/hide 'Filters'" "button"
Then I should see "English" in the "[data-region='active-filters']" "css_element"
And I should not see "Spanish" in the "[data-region='active-filters']" "css_element"
Scenario: Rename filter in report using special characters
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on "Show/hide 'Filters'" "button"
When I set the field "Rename filter 'Email address'" to "Fish & Chips"
And I click on "Switch to preview mode" "button"
And I click on "Filters" "button"
Then I should see "Fish & Chips" in the "[data-region='report-filters']" "css_element"
Scenario: Move filter in report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
| My report | user:country |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
When I click on "Show/hide 'Filters'" "button"
And I click on "Move filter 'Country'" "button"
And I click on "After \"Full name\"" "link" in the "Move filter 'Country'" "dialogue"
Then I should see "Moved filter 'Country'"
And "Country" "text" should appear before "Email address" "text"
Scenario: Delete filter from report
Given the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on "Show/hide 'Filters'" "button"
And I click on "Delete filter 'Email address'" "button"
And I click on "Delete" "button" in the "Delete filter 'Email address'" "dialogue"
Then I should see "Deleted filter 'Email address'"
And I should see "There are no filters selected" in the "[data-region='active-filters']" "css_element"
And I should not see "Email address" in the "[data-region='active-filters']" "css_element"
Scenario: Reset filters in custom report
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
And I am on the "My report" "reportbuilder > View" page logged in as "admin"
When I click on "Filters" "button"
And I set the following fields in the "Full name" "core_reportbuilder > Filter" to these values:
| Full name operator | Contains |
| Full name value | Lionel |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Nothing to display"
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
And I should not see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Filters" in the "#dropdownFiltersButton" "css_element"
And "[data-region='report-filters']" "css_element" should be visible
Then I should see "Filters reset"
And the following fields in the "Full name" "core_reportbuilder > Filter" match these values:
| Full name operator | Is any value |
And I should see "Admin User" in the "Users" "table"
Scenario: Use report filters when previewing report
Given the following "users" exist:
| username | firstname | lastname |
| user1 | User | 1 |
| user2 | User | 2 |
| user3 | User | 3 |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
And the following "core_reportbuilder > Filters" exist:
| report | uniqueidentifier |
| My report | user:fullname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
And I should see "user1@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user2@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user3@example.com" in the ".reportbuilder-table" "css_element"
When I click on "Switch to preview mode" "button"
And I click on "Filters" "button"
And I set the following fields in the "Full name" "core_reportbuilder > Filter" to these values:
| Full name operator | Does not contain |
| Full name value | User 2 |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then I should see "Filters applied"
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And the following should exist in the "reportbuilder-table" table:
| Full name | Email address |
| User 1 | user1@example.com |
| User 3 | user3@example.com |
And the following should not exist in the "reportbuilder-table" table:
| Full name | Email address |
| User 2 | user2@example.com |
# Switching back to edit mode should not apply filters.
And I click on "Switch to edit mode" "button"
And I should see "user1@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user2@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user3@example.com" in the ".reportbuilder-table" "css_element"
Scenario: Use report filters when previewing report that contains same condition
Given the following "users" exist:
| username | firstname | lastname |
| user1 | User | 1 |
| user2 | User | 2 |
| user3 | User | 3 |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:fullname |
| My report | user:email |
And the following "core_reportbuilder > Condition" exists:
| report | My report |
| uniqueidentifier | user:email |
And the following "core_reportbuilder > Filter" exists:
| report | My report |
| uniqueidentifier | user:email |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
And I should see "user1@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user2@example.com" in the ".reportbuilder-table" "css_element"
And I should see "user3@example.com" in the ".reportbuilder-table" "css_element"
# Set a condition to the report.
When I click on "Show/hide 'Conditions'" "button"
And I set the following fields in the "Email address" "core_reportbuilder > Condition" to these values:
| Email address operator | Is not equal to |
| Email address value | user3@example.com |
And I click on "Apply" "button" in the "#settingsconditions" "css_element"
And I click on "Switch to preview mode" "button"
And I click on "Filters" "button"
And I set the following fields in the "Email address" "core_reportbuilder > Filter" to these values:
| Email address operator | Is not equal to |
| Email address value | user2@example.com |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then I should see "Filters applied"
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
# Assert we haven't overridden the condition and user3 is still not showing in the report.
And the following should exist in the "reportbuilder-table" table:
| Full name | Email address |
| User 1 | user1@example.com |
And the following should not exist in the "reportbuilder-table" table:
| Full name | Email address |
| User 2 | user2@example.com |
| User 3 | user3@example.com |
+135
View File
@@ -0,0 +1,135 @@
@core_reportbuilder @javascript
Feature: Manage custom report schedules
In order ot manage custom report schedules
As an admin
I need to create new and edit existing report schedules
Background:
Given the following "users" exist:
| username | firstname | lastname |
| user1 | User | One |
| user2 | User | Two |
| user3 | User | Three |
And the following "core_reportbuilder > Report" exists:
| name | My report |
| source | core_user\reportbuilder\datasource\users |
| default | 1 |
And the following "core_reportbuilder > Audience" exists:
| report | My report |
| classname | core_reportbuilder\reportbuilder\audience\allusers |
| configdata | |
Scenario: Create report schedule
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
# Rename the existing audience.
And I set the field "Rename audience 'All users'" to "All my lovely users"
# Add a second audience.
And I click on "Add audience 'Manually added users'" "link"
And I set the field "Add users manually" to "User One, User Two"
And I press "Save changes"
When I click on the "Schedules" dynamic tab
And I press "New schedule"
And I set the following fields in the "New schedule" "dialogue" to these values:
| Name | My schedule |
| Starting from | ##tomorrow 11:00## |
| Subject | You're all I've ever wanted |
| Body | And my arms are open wide |
# Confirm each audience is present in the form, select only the manually added users.
And I should see "All my lovely users" in the "New schedule" "dialogue"
And I set the field "Manually added users: User One, User Two" to "1"
And I click on "Save" "button" in the "New schedule" "dialogue"
Then I should see "Schedule created"
And the following should exist in the "Report schedules" table:
| Name | Starting from | Time last sent | Modified by |
| My schedule | ##tomorrow 11:00##%A, %d %B %Y, %H:%M## | Never | Admin User |
Scenario: Create report schedule for audience renamed using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
And I set the field "Rename audience 'All users'" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
When I click on the "Schedules" dynamic tab
And I press "New schedule"
Then I should see "English" in the "New schedule" "dialogue"
And I should not see "Spanish" in the "New schedule" "dialogue"
And I click on "Cancel" "button" in the "New schedule" "dialogue"
Scenario: Rename report schedule
Given the following "core_reportbuilder > Schedule" exists:
| report | My report |
| name | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I set the field "Edit schedule name" in the "My schedule" "table_row" to "My renamed schedule"
And I reload the page
Then I should see "My renamed schedule" in the "Report schedules" "table"
Scenario: Rename report schedule using filters
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And the following "core_reportbuilder > Schedule" exists:
| report | My report |
| name | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I set the field "Edit schedule name" in the "My schedule" "table_row" to "<span class=\"multilang\" lang=\"en\">English</span><span class=\"multilang\" lang=\"es\">Spanish</span>"
And I reload the page
Then I should see "English" in the "Report schedules" "table"
And I should not see "Spanish" in the "Report schedules" "table"
# Confirm schedule name is correctly shown in actions.
And I press "Send schedule" action in the "English" report row
And I should see "Are you sure you want to queue the schedule 'English' for sending immediately?" in the "Send schedule" "dialogue"
And I click on "Cancel" "button" in the "Send schedule" "dialogue"
And I press "Delete schedule" action in the "English" report row
And I should see "Are you sure you want to delete the schedule 'English'?" in the "Delete schedule" "dialogue"
And I click on "Cancel" "button" in the "Delete schedule" "dialogue"
Scenario: Toggle report schedule
Given the following "core_reportbuilder > Schedules" exist:
| report | name |
| My report | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I click on "Disable schedule" "field" in the "My schedule" "table_row"
Then the "class" attribute of "My schedule" "table_row" should contain "text-muted"
And I click on "Enable schedule" "field" in the "My schedule" "table_row"
Scenario: Edit report schedule
Given the following "core_reportbuilder > Schedules" exist:
| report | name |
| My report | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I press "Edit schedule details" action in the "My schedule" report row
And I set the following fields in the "Edit schedule details" "dialogue" to these values:
| Name | My updated schedule |
| Starting from | ##tomorrow 11:00## |
| All users: All site users | 1 |
And I click on "Save" "button" in the "Edit schedule details" "dialogue"
Then I should see "Schedule updated"
And the following should exist in the "Report schedules" table:
| Name | Starting from |
| My updated schedule | ##tomorrow 11:00##%A, %d %B %Y, %H:%M## |
Scenario: Send report schedule
Given the following "core_reportbuilder > Schedules" exist:
| report | name |
| My report | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I press "Send schedule" action in the "My schedule" report row
And I click on "Confirm" "button" in the "Send schedule" "dialogue"
Then I should see "Schedule sent"
Scenario: Delete report schedule
Given the following "core_reportbuilder > Schedules" exist:
| report | name |
| My report | My schedule |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Schedules" dynamic tab
When I press "Delete schedule" action in the "My schedule" report row
And I click on "Delete" "button" in the "Delete schedule" "dialogue"
Then I should see "Schedule deleted"
And I should see "Nothing to display"
+321
View File
@@ -0,0 +1,321 @@
<?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/>.
/**
* Unit tests for base datasource
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace core_reportbuilder;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\report\{column, filter};
use lang_string;
use ReflectionClass;
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for base datasource
*
* @package core_reportbuilder
* @coversDefaultClass \core_reportbuilder\datasource
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class datasource_test extends advanced_testcase {
/**
* Data provider for {@see test_add_columns_from_entity}
*
* @return array[]
*/
public static function add_columns_from_entity_provider(): array {
return [
'All columns' => [
[],
[],
4,
],
'Include columns (first, extra1, extra2)' => [
['first', 'extra*'],
[],
3,
],
'Exclude columns (first, extra1, extra2)' => [
[],
['first', 'extra*'],
1,
],
];
}
/**
* Test adding columns from entity
*
* @param string[] $include
* @param string[] $exclude
* @param int $expectedcount
*
* @covers ::add_columns_from_entity
*
* @dataProvider add_columns_from_entity_provider
*/
public function test_add_columns_from_entity(
array $include,
array $exclude,
int $expectedcount,
): void {
$instance = $this->get_datasource_test_source();
$method = (new ReflectionClass($instance))->getMethod('add_columns_from_entity');
$method->invoke($instance, 'datasource_test_entity', $include, $exclude);
// Get all our entity columns.
$columns = array_filter(
$instance->get_columns(),
fn(string $columnname) => strpos($columnname, 'datasource_test_entity:') === 0,
ARRAY_FILTER_USE_KEY,
);
$this->assertCount($expectedcount, $columns);
}
/**
* Data provider for {@see test_add_filters_from_entity}
*
* @return array[]
*/
public static function add_filters_from_entity_provider(): array {
return [
'All filters' => [
[],
[],
4,
],
'Include filters (first, extra1, extra2)' => [
['first', 'extra*'],
[],
3,
],
'Exclude filters (first, extra1, extra2)' => [
[],
['first', 'extra*'],
1,
],
];
}
/**
* Test adding filters from entity
*
* @param string[] $include
* @param string[] $exclude
* @param int $expectedcount
*
* @covers ::add_filters_from_entity
*
* @dataProvider add_filters_from_entity_provider
*/
public function test_add_filters_from_entity(
array $include,
array $exclude,
int $expectedcount,
): void {
$instance = $this->get_datasource_test_source();
$method = (new ReflectionClass($instance))->getMethod('add_filters_from_entity');
$method->invoke($instance, 'datasource_test_entity', $include, $exclude);
// Get all our entity filters.
$filters = array_filter(
$instance->get_filters(),
fn(string $filtername) => strpos($filtername, 'datasource_test_entity:') === 0,
ARRAY_FILTER_USE_KEY,
);
$this->assertCount($expectedcount, $filters);
}
/**
* Data provider for {@see test_add_conditions_from_entity}
*
* @return array[]
*/
public static function add_conditions_from_entity_provider(): array {
return [
'All conditions' => [
[],
[],
4,
],
'Include conditions (first, extra1, extra2)' => [
['first', 'extra*'],
[],
3,
],
'Exclude conditions (first, extra1, extra2)' => [
[],
['first', 'extra*'],
1,
],
];
}
/**
* Test adding conditions from entity
*
* @param string[] $include
* @param string[] $exclude
* @param int $expectedcount
*
* @covers ::add_conditions_from_entity
*
* @dataProvider add_conditions_from_entity_provider
*/
public function test_add_conditions_from_entity(
array $include,
array $exclude,
int $expectedcount,
): void {
$instance = $this->get_datasource_test_source();
$method = (new ReflectionClass($instance))->getMethod('add_conditions_from_entity');
$method->invoke($instance, 'datasource_test_entity', $include, $exclude);
// Get all our entity conditions.
$conditions = array_filter(
$instance->get_conditions(),
fn(string $conditionname) => strpos($conditionname, 'datasource_test_entity:') === 0,
ARRAY_FILTER_USE_KEY,
);
$this->assertCount($expectedcount, $conditions);
}
/**
* Test adding all from entity
*
* @covers ::add_all_from_entity
*/
public function test_add_all_from_entity(): void {
$instance = $this->get_datasource_test_source();
$method = (new ReflectionClass($instance))->getMethod('add_all_from_entity');
$method->invoke($instance, 'datasource_test_entity', ['first'], ['second'], ['extra1']);
// Assert the column we added (plus one we didn't).
$this->assertInstanceOf(column::class, $instance->get_column('datasource_test_entity:first'));
$this->assertNull($instance->get_column('datasource_test_entity:second'));
// Assert the filter we added (plus one we didn't).
$this->assertInstanceOf(filter::class, $instance->get_filter('datasource_test_entity:second'));
$this->assertNull($instance->get_filter('datasource_test_entity:first'));
// Assert the condition we added (plus one we didn't).
$this->assertInstanceOf(filter::class, $instance->get_condition('datasource_test_entity:extra1'));
$this->assertNull($instance->get_condition('datasource_test_entity:extra2'));
}
/**
* Create and return our test datasource instance
*
* @return datasource_test_source
*/
protected function get_datasource_test_source(): datasource_test_source {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Test', 'source' => datasource_test_source::class, 'default' => 0]);
/** @var datasource_test_source $instance */
$instance = manager::get_report_from_persistent($report);
return $instance;
}
}
/**
* Simple implementation of the base datasource
*/
class datasource_test_source extends datasource {
protected function initialise(): void {
$this->set_main_table('user', 'u');
$this->annotate_entity('dummy', new lang_string('yes'));
$this->add_column(new column('test', null, 'dummy'));
// This is the entity from which we'll add our report elements.
$this->add_entity(new datasource_test_entity());
}
public static function get_name(): string {
return self::class;
}
public function get_default_columns(): array {
return [];
}
public function get_default_filters(): array {
return [];
}
public function get_default_conditions(): array {
return [];
}
}
/**
* Simple implementation of the base entity
*/
class datasource_test_entity extends base {
protected function get_default_tables(): array {
return ['course'];
}
protected function get_default_entity_title(): lang_string {
return new lang_string('course');
}
/**
* We're going to add multiple columns/filters/conditions, each named as following:
*
* [first, second, extra1, extra2]
*
* @return base
*/
public function initialise(): base {
foreach (['first', 'second', 'extra1', 'extra2'] as $field) {
$name = new lang_string('customfieldcolumn', 'core_reportbuilder', $field);
$this->add_column(new column($field, $name, $this->get_entity_name()));
$this->add_filter(new filter(text::class, $field, $name, $this->get_entity_name()));
$this->add_condition(new filter(text::class, $field, $name, $this->get_entity_name()));
}
return $this;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?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_reportbuilder\external\audiences;
use context_system;
use core_reportbuilder\local\models\audience;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting report audiences
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\audiences\delete
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$audience1 = $generator->create_audience([
'reportid' => $report->get('id'),
'configdata' => [],
]);
$audience2 = $generator->create_audience([
'reportid' => $report->get('id'),
'configdata' => [],
]);
$audiences = audience::get_records(['reportid' => $report->get('id')]);
$this->assertCount(2, $audiences);
// Delete the first audience.
$result = delete::execute($report->get('id'), $audience1->get_persistent()->get('id'));
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result);
$audiences = audience::get_records(['reportid' => $report->get('id')]);
$this->assertCount(1, $audiences);
$audience = reset($audiences);
$this->assertEquals($audience2->get_persistent()->get('id'), $audience->get('id'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audience1 = $generator->create_audience([
'reportid' => $report->get('id'),
'configdata' => [],
]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'), $audience1->get_persistent()->get('id'));
}
}
+98
View File
@@ -0,0 +1,98 @@
<?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_reportbuilder\external\columns;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\column;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for adding report columns
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\add
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add column.
$result = add::execute($report->get('id'), 'user:fullname');
$result = external_api::clean_returnvalue(add::execute_returns(), $result);
$this->assertTrue($result['hassortablecolumns']);
$this->assertCount(1, $result['sortablecolumns']);
$sortablecolumn = reset($result['sortablecolumns']);
$this->assertEquals('Full name', $sortablecolumn['title']);
$this->assertEquals(SORT_ASC, $sortablecolumn['sortdirection']);
$this->assertEquals(0, $sortablecolumn['sortenabled']);
$this->assertEquals(1, $sortablecolumn['sortorder']);
$this->assertEquals('t/uplong', $sortablecolumn['sorticon']['key']);
$this->assertEquals('moodle', $sortablecolumn['sorticon']['component']);
$str = get_string('columnsortdirectiondesc', 'core_reportbuilder', 'Full name');
$this->assertEquals($str, $sortablecolumn['sorticon']['title']);
// Assert report columns.
$columns = column::get_records(['reportid' => $report->get('id')]);
$this->assertCount(1, $columns);
$this->assertEquals('user:fullname', reset($columns)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
add::execute($report->get('id'), 'user:fullname');
}
}
+104
View File
@@ -0,0 +1,104 @@
<?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_reportbuilder\external\columns;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\column;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting report columns
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\delete
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two columns.
$columnfullname = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first column.
$result = delete::execute($report->get('id'), $columnfullname->get('id'));
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result['hassortablecolumns']);
$this->assertCount(1, $result['sortablecolumns']);
$sortablecolumn = reset($result['sortablecolumns']);
$this->assertEquals('Email address', $sortablecolumn['title']);
$this->assertEquals(SORT_ASC, $sortablecolumn['sortdirection']);
$this->assertEquals(0, $sortablecolumn['sortenabled']);
$this->assertEquals(2, $sortablecolumn['sortorder']);
$this->assertEquals('t/uplong', $sortablecolumn['sorticon']['key']);
$this->assertEquals('moodle', $sortablecolumn['sorticon']['component']);
$str = get_string('columnsortdirectiondesc', 'core_reportbuilder', 'Email address');
$this->assertEquals($str, $sortablecolumn['sorticon']['title']);
// Assert report columns.
$columns = column::get_records(['reportid' => $report->get('id')]);
$this->assertCount(1, $columns);
$this->assertEquals('user:email', reset($columns)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'), $column->get('id'));
}
}
+104
View File
@@ -0,0 +1,104 @@
<?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_reportbuilder\external\columns;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\column;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for re-ordering report columns
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\reorder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add four columns.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city column to second position.
$result = reorder::execute($report->get('id'), $columncity->get('id'), 2);
$result = external_api::clean_returnvalue(reorder::execute_returns(), $result);
$this->assertTrue($result);
// Assert report columns order.
$columns = column::get_records(['reportid' => $report->get('id')], 'columnorder');
$columnidentifiers = array_map(static function(column $column): string {
return $column->get('uniqueidentifier');
}, $columns);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $columnidentifiers);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
reorder::execute($report->get('id'), $column->get('id'), 1);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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_reportbuilder\external\columns\sort;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for retrieving report column sorting
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\sort\get
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add some columns (note 'user:picture' is not a sortable column).
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:picture']);
$result = get::execute($report->get('id'));
$result = external_api::clean_returnvalue(get::execute_returns(), $result);
$this->assertTrue($result['hassortablecolumns']);
$sortablecolumntitles = array_column($result['sortablecolumns'], 'title');
$this->assertEquals([
'Full name',
'Email address',
], $sortablecolumntitles);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
get::execute($report->get('id'));
}
}
@@ -0,0 +1,114 @@
<?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_reportbuilder\external\columns\sort;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\column;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for re-ordering report column sorting
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\sort\reorder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add four columns.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city column sort order to second position.
$result = reorder::execute($report->get('id'), $columncity->get('id'), 2);
$result = external_api::clean_returnvalue(reorder::execute_returns(), $result);
$this->assertTrue($result['hassortablecolumns']);
$this->assertCount(4, $result['sortablecolumns']);
$columnid = $columncity->get('id');
$sortablecolumn = array_filter($result['sortablecolumns'], function(array $column) use($columnid) {
return $column['id'] == $columnid;
});
$sortablecolumn = reset($sortablecolumn);
$this->assertEquals($columnid, $sortablecolumn['id']);
$this->assertEquals('City/town', $sortablecolumn['title']);
$this->assertEquals(SORT_ASC, $sortablecolumn['sortdirection']);
$this->assertEquals(0, $sortablecolumn['sortenabled']);
$this->assertEquals(2, $sortablecolumn['sortorder']);
$this->assertEquals('t/uplong', $sortablecolumn['sorticon']['key']);
$this->assertEquals('moodle', $sortablecolumn['sorticon']['component']);
$str = get_string('columnsortdirectiondesc', 'core_reportbuilder', 'City/town');
$this->assertEquals($str, $sortablecolumn['sorticon']['title']);
// Assert report column sort order.
$columns = column::get_records(['reportid' => $report->get('id')], 'sortorder');
$columnidentifiers = array_map(static function(column $column): string {
return $column->get('uniqueidentifier');
}, $columns);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $columnidentifiers);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
reorder::execute($report->get('id'), $column->get('id'), 2);
}
}
@@ -0,0 +1,100 @@
<?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_reportbuilder\external\columns\sort;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\column;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for toggling report column sorting
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\columns\sort\toggle
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toggle_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Toggle sort descending.
$result = toggle::execute($report->get('id'), $column->get('id'), true, SORT_DESC);
$result = external_api::clean_returnvalue(toggle::execute_returns(), $result);
$this->assertTrue($result['hassortablecolumns']);
$this->assertCount(4, $result['sortablecolumns']);
$columnid = $column->get('id');
$sortablecolumn = array_filter($result['sortablecolumns'], function(array $column) use($columnid) {
return $column['id'] == $columnid;
});
$sortablecolumn = reset($sortablecolumn);
$this->assertEquals($columnid, $sortablecolumn['id']);
$this->assertEquals('Email address', $sortablecolumn['title']);
$this->assertEquals(SORT_DESC, $sortablecolumn['sortdirection']);
$this->assertEquals(1, $sortablecolumn['sortenabled']);
$this->assertEquals(4, $sortablecolumn['sortorder']);
$this->assertEquals('t/downlong', $sortablecolumn['sorticon']['key']);
$this->assertEquals('moodle', $sortablecolumn['sorticon']['component']);
$str = get_string('columnsortdirectionasc', 'core_reportbuilder', 'Email address');
$this->assertEquals($str, $sortablecolumn['sorticon']['title']);
// Confirm column was updated.
$columnupdated = new column($column->get('id'));
$this->assertTrue($columnupdated->get('sortenabled'));
$this->assertEquals(SORT_DESC, $columnupdated->get('sortdirection'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
toggle::execute($report->get('id'), $column->get('id'), true, SORT_DESC);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?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_reportbuilder\external\conditions;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for adding report conditions
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\conditions\add
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add condition.
$result = add::execute($report->get('id'), 'user:fullname');
$result = external_api::clean_returnvalue(add::execute_returns(), $result);
$this->assertTrue($result['hasactiveconditions']);
$this->assertNotEmpty($result['activeconditionsform']);
// Assert report conditions.
$conditions = filter::get_condition_records($report->get('id'));
$this->assertCount(1, $conditions);
$this->assertEquals('user:fullname', reset($conditions)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
add::execute($report->get('id'), 'user:fullname');
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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_reportbuilder\external\conditions;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting report conditions
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\conditions\delete
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two conditions.
$conditionfullname = $generator->create_condition([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullname',
]);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first condition.
$result = delete::execute($report->get('id'), $conditionfullname->get('id'));
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result['hasactiveconditions']);
$this->assertNotEmpty($result['activeconditionsform']);
// Assert report conditions.
$conditions = filter::get_condition_records($report->get('id'));
$this->assertCount(1, $conditions);
$this->assertEquals('user:email', reset($conditions)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$condition = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'), $condition->get('id'));
}
}
+105
View File
@@ -0,0 +1,105 @@
<?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_reportbuilder\external\conditions;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for re-ordering report conditions
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\conditions\reorder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add four conditions.
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$conditioncity = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city condition to second position.
$result = reorder::execute($report->get('id'), $conditioncity->get('id'), 2);
$result = external_api::clean_returnvalue(reorder::execute_returns(), $result);
$this->assertTrue($result['hasactiveconditions']);
$this->assertNotEmpty($result['activeconditionsform']);
// Assert report conditions order.
$conditions = filter::get_condition_records($report->get('id'), 'filterorder');
$conditionidentifiers = array_map(static function(filter $condition): string {
return $condition->get('uniqueidentifier');
}, $conditions);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $conditionidentifiers);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$condition = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
reorder::execute($report->get('id'), $condition->get('id'), 1);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?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_reportbuilder\external\conditions;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\manager;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for resetting report conditions
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\conditions\reset
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$instance = manager::get_report_from_persistent($report);
$instance->set_condition_values(['foo' => 'bar']);
$result = reset::execute($report->get('id'));
$result = external_api::clean_returnvalue(reset::execute_returns(), $result);
$this->assertTrue($result['hasactiveconditions']);
$this->assertNotEmpty($result['activeconditionsform']);
// Re-create the report instance, we should get an empty array back.
$secondinstance = manager::get_report_from_persistent($report->read());
$this->assertEquals([], $secondinstance->get_condition_values());
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
reset::execute($report->get('id'));
}
}
@@ -0,0 +1,121 @@
<?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_reportbuilder\external;
use advanced_testcase;
use context_system;
/**
* Unit tests for custom report audience cards exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_audience_cards_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_audience_cards_exporter_test extends advanced_testcase {
/**
* Test exported data/structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
$exporter = new custom_report_audience_cards_exporter(null);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertNotEmpty($export->menucards);
// Test only the site audiences, so tests are unaffected by audiences within components.
$menucardsite = array_filter($export->menucards, static function(array $menucard): bool {
return $menucard['name'] === get_string('site');
});
$this->assertCount(1, $menucardsite);
$menucardsite = reset($menucardsite);
$this->assertNotEmpty($menucardsite['key']);
$this->assertGreaterThanOrEqual(4, count($menucardsite['items']));
// Test the structure of the first menu card item.
$menucarditem = reset($menucardsite['items']);
$this->assertEquals([
'name' => 'All users',
'identifier' => \core_reportbuilder\reportbuilder\audience\allusers::class,
'title' => 'Add audience \'All users\'',
'action' => 'add-audience',
'disabled' => false,
], $menucarditem);
}
/**
* Test exported data when user cannot add some audience types
*/
public function test_export_audience_user_can_add(): void {
global $DB, $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// This capability controls access to the all/manual users audiences.
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user']);
assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $userrole, context_system::instance());
$exporter = new custom_report_audience_cards_exporter(null);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertCount(1, $export->menucards);
$this->assertEquals('Site', $export->menucards[0]['name']);
$this->assertEquals([
'All users',
'Manually added users',
], array_column($export->menucards[0]['items'], 'name'));
}
/**
* Test exported data when user can add an audience type, but it isn't available
*/
public function test_export_audience_is_available(): void {
global $DB, $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user']);
assign_capability('moodle/cohort:view', CAP_ALLOW, $userrole, context_system::instance());
$exporter = new custom_report_audience_cards_exporter(null);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertCount(1, $export->menucards);
$this->assertEquals('Site', $export->menucards[0]['name']);
// Cohort audience should be present, but disabled.
$this->assertCount(1, $export->menucards[0]['items']);
$this->assertEquals('Member of cohort', $export->menucards[0]['items'][0]['name']);
$this->assertTrue($export->menucards[0]['items'][0]['disabled']);
}
}
@@ -0,0 +1,58 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for custom report card view exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_card_view_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_card_view_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_card_view_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
// The exporter just returns two large pieces of HTML, assert they are non empty.
$this->assertNotEmpty($export->form);
$this->assertNotEmpty($export->helpicon);
}
}
@@ -0,0 +1,113 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_course\reportbuilder\datasource\courses;
/**
* Unit tests for custom report column cards exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_column_cards_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_column_cards_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => courses::class]);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_column_cards_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
// The root of the menu cards property should contain each entity.
$this->assertCount(4, $export->menucards);
[$menucardcategory, $menucardcourse, $menucardtag, $menucardfile] = $export->menucards;
// Course category entity menu card.
$this->assertEquals('Course category', $menucardcategory['name']);
$this->assertEquals('course_category', $menucardcategory['key']);
$this->assertNotEmpty($menucardcategory['items']);
// Test the structure of the first menu card item.
$menucarditem = reset($menucardcategory['items']);
$this->assertEquals([
'name' => 'Category name',
'identifier' => 'course_category:name',
'title' => 'Add column \'Category name\'',
'action' => 'report-add-column',
], $menucarditem);
// Course entity menu card.
$this->assertEquals('Course', $menucardcourse['name']);
$this->assertEquals('course', $menucardcourse['key']);
$this->assertNotEmpty($menucardcourse['items']);
// Test the structure of the first menu card item.
$menucarditem = reset($menucardcourse['items']);
$this->assertEquals([
'name' => 'Course full name with link',
'identifier' => 'course:coursefullnamewithlink',
'title' => 'Add column \'Course full name with link\'',
'action' => 'report-add-column',
], $menucarditem);
// Tag entity menu card.
$this->assertEquals('Tag', $menucardtag['name']);
$this->assertEquals('tag', $menucardtag['key']);
$this->assertNotEmpty($menucardtag['items']);
// Test the structure of the first menu card item.
$menucarditem = reset($menucardtag['items']);
$this->assertEquals([
'name' => 'Tag name',
'identifier' => 'tag:name',
'title' => 'Add column \'Tag name\'',
'action' => 'report-add-column',
], $menucarditem);
// File entity menu card.
$this->assertEquals('Course image', $menucardfile['name']);
$this->assertEquals('file', $menucardfile['key']);
$this->assertNotEmpty($menucardfile['items']);
// Test the structure of the first menu card item.
$menucarditem = reset($menucardfile['items']);
$this->assertEquals([
'name' => 'Filename',
'identifier' => 'file:name',
'title' => 'Add column \'Filename\'',
'action' => 'report-add-column',
], $menucarditem);
}
}
@@ -0,0 +1,129 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\report;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for custom report column sorting exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_columns_sorting_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_columns_sorting_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add a couple of columns.
$columnfullname = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$columnemail = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Enable sorting on the email column, move it to first place.
report::toggle_report_column_sorting($report->get('id'), $columnemail->get('id'), true, SORT_DESC);
report::reorder_report_column_sorting($report->get('id'), $columnemail->get('id'), 1);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_columns_sorting_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertTrue($export->hassortablecolumns);
$this->assertCount(2, $export->sortablecolumns);
[$sortcolumnemail, $sortcolumnfullname] = $export->sortablecolumns;
// Email column.
$this->assertEquals($columnemail->get('id'), $sortcolumnemail['id']);
$this->assertEquals('Email address', $sortcolumnemail['heading']);
$this->assertTrue($sortcolumnemail['sortenabled']);
$this->assertEquals(1, $sortcolumnemail['sortorder']);
$this->assertEquals(SORT_DESC, $sortcolumnemail['sortdirection']);
$this->assertEquals('Disable initial sorting for column \'Email address\'', $sortcolumnemail['sortenabledtitle']);
$this->assertEquals('Sort column \'Email address\' ascending', $sortcolumnemail['sorticon']['title']);
// Fullname column.
$this->assertEquals($columnfullname->get('id'), $sortcolumnfullname['id']);
$this->assertEquals('Full name', $sortcolumnfullname['heading']);
$this->assertFalse($sortcolumnfullname['sortenabled']);
$this->assertEquals(2, $sortcolumnfullname['sortorder']);
$this->assertEquals(SORT_ASC, $sortcolumnfullname['sortdirection']);
$this->assertEquals('Enable initial sorting for column \'Full name\'', $sortcolumnfullname['sortenabledtitle']);
$this->assertEquals('Sort column \'Full name\' descending', $sortcolumnfullname['sorticon']['title']);
$this->assertNotEmpty($export->helpicon);
}
/**
* Test exported data structure for report with columns, but they aren't sortable
*/
public function test_export_no_sortable_columns(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:picture']);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_columns_sorting_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertFalse($export->hassortablecolumns);
$this->assertEmpty($export->sortablecolumns);
}
/**
* Test exported data structure for report with no columns
*/
public function test_export_no_columns(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_columns_sorting_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertFalse($export->hassortablecolumns);
$this->assertEmpty($export->sortablecolumns);
}
}
@@ -0,0 +1,121 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_course\reportbuilder\datasource\courses;
/**
* Unit tests for custom report conditions exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_conditions_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_conditions_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => courses::class, 'default' => false]);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:shortname']);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_conditions_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertTrue($export->hasavailableconditions);
// The root of the available conditions property should contain each entity.
$this->assertCount(4, $export->availableconditions);
[$conditionscategory, $conditionscourse, $conditionstag, $conditionsfile] = $export->availableconditions;
// Course category conditions, assert structure of first item.
$this->assertEquals('Course category', $conditionscategory['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($conditionscategory['optiongroup']['values']));
$this->assertEquals([
'value' => 'course_category:name',
'visiblename' => 'Select category',
], $conditionscategory['optiongroup']['values'][0]);
// Course conditions, assert structure of first item.
$this->assertEquals('Course', $conditionscourse['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($conditionscourse['optiongroup']['values']));
$this->assertEquals([
'value' => 'course:fullname',
'visiblename' => 'Course full name',
], $conditionscourse['optiongroup']['values'][0]);
// Make sure the active condition we added, isn't present in available conditions.
$this->assertNotContains('course:shortname', array_column($conditionscourse['optiongroup']['values'], 'value'));
// Tag conditions, assert structure of first item.
$this->assertEquals('Tag', $conditionstag['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($conditionstag['optiongroup']['values']));
$this->assertEquals([
'value' => 'tag:name',
'visiblename' => 'Tag name',
], $conditionstag['optiongroup']['values'][0]);
// File conditions, assert structure of first item.
$this->assertEquals('Course image', $conditionsfile['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($conditionsfile['optiongroup']['values']));
$this->assertEquals([
'value' => 'file:name',
'visiblename' => 'Filename',
], $conditionsfile['optiongroup']['values'][0]);
// The active conditions are contained inside form HTML, just assert there's something present.
$this->assertTrue($export->hasactiveconditions);
$this->assertNotEmpty($export->activeconditionsform);
$this->assertNotEmpty($export->helpicon);
}
/**
* Test exported data structure for report with no conditions
*/
public function test_export_no_conditions(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => courses::class, 'default' => false]);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_conditions_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertFalse($export->hasactiveconditions);
$this->assertEmpty($export->activeconditionsform);
}
}
@@ -0,0 +1,76 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for custom report data exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_data_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_data_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
$this->getDataGenerator()->create_user(['firstname' => 'Zoe', 'lastname' => 'Zebra', 'email' => 'u1@example.com']);
$this->getDataGenerator()->create_user(['firstname' => 'Charlie', 'lastname' => 'Carrot', 'email' => 'u2@example.com']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullname',
'heading' => 'Lovely user',
'sortenabled' => 1,
]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_data_exporter(null, ['report' => $reportinstance, 'page' => 0, 'perpage' => 2]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
// There are three users (admin plus the two previouly created), but we're paging the first two only.
$this->assertEquals(['Lovely user', 'Email address'], $export->headers);
$this->assertEquals([
[
'columns' => ['Admin User', 'admin@example.com'],
],
[
'columns' => ['Charlie Carrot', 'u2@example.com'],
],
], $export->rows);
$this->assertEquals(3, $export->totalrowcount);
}
}
@@ -0,0 +1,64 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for custom report details exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_details_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_details_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$exporter = new custom_report_details_exporter($report);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
// The exporter outputs the persistent details, plus two other properties.
$this->assertEquals($report->get('name'), $export->name);
$this->assertEquals($report->get('source'), $export->source);
// Source name should be the name of the source.
$this->assertEquals(users::get_name(), $export->sourcename);
// We use the user exporter for the modifier of the report.
$this->assertObjectHasProperty('modifiedby', $export);
$this->assertEquals(fullname($user), $export->modifiedby->fullname);
}
}
@@ -0,0 +1,160 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder\manager;
use core_reportbuilder_generator;
use moodle_url;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\local\filters\text;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for custom report exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_exporter_test extends advanced_testcase {
/**
* Test exported data structure when editing a report
*/
public function test_export_editing(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
manager::get_report_from_persistent($report)->add_attributes(['data-foo' => 'bar', 'data-another' => '1']);
$PAGE->set_url(new moodle_url('/'));
$exporter = new custom_report_exporter($report, [], true);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertNotEmpty($export->table);
$this->assertEquals(0, $export->filtersapplied);
$this->assertFalse($export->filterspresent);
$this->assertEmpty($export->filtersform);
$this->assertTrue($export->editmode);
$this->assertEmpty($export->attributes);
// The following are all generated by additional exporters.
$this->assertNotEmpty($export->sidebarmenucards);
$this->assertNotEmpty($export->conditions);
$this->assertNotEmpty($export->filters);
$this->assertNotEmpty($export->sorting);
$this->assertNotEmpty($export->cardview);
}
/**
* Test exported data structure when viewing a report
*/
public function test_export_viewing(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
manager::get_report_from_persistent($report)->add_attributes(['data-foo' => 'bar', 'data-another' => '1']);
$PAGE->set_url(new moodle_url('/'));
$exporter = new custom_report_exporter($report, ['pagesize' => 10], false);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertNotEmpty($export->table);
$this->assertEquals(0, $export->filtersapplied);
$this->assertFalse($export->filterspresent);
$this->assertEmpty($export->filtersform);
$this->assertFalse($export->editmode);
$this->assertEquals([
['name' => 'data-foo', 'value' => 'bar'],
['name' => 'data-another', 'value' => '1']
], $export->attributes);
// The following are all generated by additional exporters, and should not be present when not editing.
$this->assertObjectNotHasProperty('sidebarmenucards', $export);
$this->assertObjectNotHasProperty('conditions', $export);
$this->assertObjectNotHasProperty('filters', $export);
$this->assertObjectNotHasProperty('sorting', $export);
$this->assertObjectNotHasProperty('cardview', $export);
}
/**
* Test exported data structure when filters are present
*/
public function test_export_filters_present(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$PAGE->set_url(new moodle_url('/'));
$exporter = new custom_report_exporter($report, ['pagesize' => 10], false);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertTrue($export->filterspresent);
$this->assertNotEmpty($export->filtersform);
$this->assertEquals(0, $export->filtersapplied);
}
/**
* Test exported data structure when filters are applied
*/
public function test_export_filters_applied(): void {
global $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Apply filter.
user_filter_manager::set($report->get('id'), ['user:email_operator' => text::IS_NOT_EMPTY]);
$PAGE->set_url(new moodle_url('/'));
$exporter = new custom_report_exporter($report, ['pagesize' => 10], false);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertTrue($export->filterspresent);
$this->assertNotEmpty($export->filtersform);
$this->assertEquals(1, $export->filtersapplied);
}
}
@@ -0,0 +1,149 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\report;
use core_course\reportbuilder\datasource\courses;
/**
* Unit tests for custom report filters exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\custom_report_filters_exporter
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_filters_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => courses::class, 'default' => false]);
// Add a couple of filters.
$filtercategoryname = $generator->create_filter([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'course_category:name',
]);
$filtercourseidnumber = $generator->create_filter([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'course:idnumber',
]);
// Move course ID number filter to first place.
report::reorder_report_filter($report->get('id'), $filtercourseidnumber->get('id'), 1);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_filters_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertTrue($export->hasavailablefilters);
// The root of the available filters property should contain each entity.
$this->assertCount(4, $export->availablefilters);
[$filterscategory, $filterscourse, $filterstag, $filtersfile] = $export->availablefilters;
// Course category filters, assert structure of first item.
$this->assertEquals('Course category', $filterscategory['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($filterscategory['optiongroup']['values']));
$this->assertEquals([
'value' => 'course_category:text',
'visiblename' => 'Category name',
], $filterscategory['optiongroup']['values'][0]);
// Course filters, assert structure of first item.
$this->assertEquals('Course', $filterscourse['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($filterscourse['optiongroup']['values']));
$this->assertEquals([
'value' => 'course:fullname',
'visiblename' => 'Course full name',
], $filterscourse['optiongroup']['values'][0]);
// Make sure the active filters we added, aren't present in available filters.
$filterscourseavailable = array_column($filterscourse['optiongroup']['values'], 'value');
$this->assertNotContains('course_category:name', $filterscourseavailable);
$this->assertNotContains('course:idnumber', $filterscourseavailable);
// Tag filters, assert structure of first item.
$this->assertEquals('Tag', $filterstag['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($filterstag['optiongroup']['values']));
$this->assertEquals([
'value' => 'tag:name',
'visiblename' => 'Tag name',
], $filterstag['optiongroup']['values'][0]);
// File filters, assert structure of first item.
$this->assertEquals('Course image', $filtersfile['optiongroup']['text']);
$this->assertGreaterThanOrEqual(1, count($filtersfile['optiongroup']['values']));
$this->assertEquals([
'value' => 'file:name',
'visiblename' => 'Filename',
], $filtersfile['optiongroup']['values'][0]);
$this->assertTrue($export->hasactivefilters);
$this->assertCount(2, $export->activefilters);
[$activefiltercourseidnumber, $activefiltercategoryname] = $export->activefilters;
// Course ID number filter.
$this->assertEquals($filtercourseidnumber->get('id'), $activefiltercourseidnumber['id']);
$this->assertEquals('Course', $activefiltercourseidnumber['entityname']);
$this->assertEquals('Course ID number', $activefiltercourseidnumber['heading']);
$this->assertEquals(1, $activefiltercourseidnumber['sortorder']);
// Course category filter.
$this->assertEquals($filtercategoryname->get('id'), $activefiltercategoryname['id']);
$this->assertEquals('Course category', $activefiltercategoryname['entityname']);
$this->assertEquals('Select category', $activefiltercategoryname['heading']);
$this->assertEquals(2, $activefiltercategoryname['sortorder']);
$this->assertNotEmpty($export->helpicon);
}
/**
* Test exported data structure for report with no filters
*/
public function test_export_no_filters(): void {
global $PAGE;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => courses::class, 'default' => false]);
$reportinstance = manager::get_report_from_persistent($report);
$exporter = new custom_report_filters_exporter(null, ['report' => $reportinstance]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertFalse($export->hasactivefilters);
$this->assertEmpty($export->activefilters);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?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_reportbuilder\external\filters;
use core_reportbuilder\manager;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for adding report filters
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\filters\add
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add filter.
$result = add::execute($report->get('id'), 'user:fullname');
$result = external_api::clean_returnvalue(add::execute_returns(), $result);
$this->assertTrue($result['hasavailablefilters']);
$this->assertEquals('User', $result['availablefilters'][0]['optiongroup']['text']);
$this->assertNotEmpty($result['availablefilters'][0]['optiongroup']['values']);
$this->assertTrue($result['hasactivefilters']);
$this->assertCount(1, $result['activefilters']);
$this->assertEquals('Full name', $result['activefilters'][0]['heading']);
// Assert report filters.
$filters = filter::get_filter_records($report->get('id'));
$this->assertCount(1, $filters);
$this->assertEquals('user:fullname', reset($filters)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
add::execute($report->get('id'), 'user:fullname');
}
}
+98
View File
@@ -0,0 +1,98 @@
<?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_reportbuilder\external\filters;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting report filters
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\filters\delete
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two filters.
$filterfullname = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first filter.
$result = delete::execute($report->get('id'), $filterfullname->get('id'));
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result['hasavailablefilters']);
$this->assertEquals('User', $result['availablefilters'][0]['optiongroup']['text']);
$this->assertNotEmpty($result['availablefilters'][0]['optiongroup']['values']);
$this->assertTrue($result['hasactivefilters']);
$this->assertCount(1, $result['activefilters']);
$this->assertEquals('Email address', $result['activefilters'][0]['heading']);
// Assert report filters.
$filters = filter::get_filter_records($report->get('id'));
$this->assertCount(1, $filters);
$this->assertEquals('user:email', reset($filters)->get('uniqueidentifier'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'), $filter->get('id'));
}
}
+108
View File
@@ -0,0 +1,108 @@
<?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_reportbuilder\external\filters;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for re-ordering report filters
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\filters\reorder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add four filters.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$filtercity = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city filter to second position.
$result = reorder::execute($report->get('id'), $filtercity->get('id'), 2);
$result = external_api::clean_returnvalue(reorder::execute_returns(), $result);
$this->assertTrue($result['hasavailablefilters']);
$this->assertEquals('User', $result['availablefilters'][0]['optiongroup']['text']);
$this->assertNotEmpty($result['availablefilters'][0]['optiongroup']['values']);
$this->assertTrue($result['hasactivefilters']);
$this->assertCount(4, $result['activefilters']);
// Assert report filters order.
$filters = filter::get_filter_records($report->get('id'), 'filterorder');
$filteridentifiers = array_map(static function(filter $filter): string {
return $filter->get('uniqueidentifier');
}, $filters);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $filteridentifiers);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
reorder::execute($report->get('id'), $filter->get('id'), 1);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?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_reportbuilder\external\filters;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\report_access_exception;
use core_external\external_api;
use externallib_advanced_testcase;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests external filters reset class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\filters\reset
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$instance = manager::get_report_from_persistent($report);
$instance->set_filter_values([
'entity:filter_operator' => 'something',
'entity:filter_value' => 42,
]);
$result = reset::execute($report->get('id'));
$result = external_api::clean_returnvalue(reset::execute_returns(), $result);
$this->assertTrue($result);
// We should get an empty array back.
$this->assertEquals([], $instance->get_filter_values());
}
/**
* Test execute method for a user without permission to view the report
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
reset::execute($report->get('id'));
}
}
+86
View File
@@ -0,0 +1,86 @@
<?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_reportbuilder\external\filters;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\report_access_exception;
use core_external\external_api;
use externallib_advanced_testcase;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests external filters set class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\filters\set
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$values = [
'entity:filter_operator' => 'something',
'entity:filter_value' => 42,
];
$result = set::execute($report->get('id'), '', json_encode($values));
$result = external_api::clean_returnvalue(set::execute_returns(), $result);
$this->assertTrue($result);
// We should get our original filter values back.
$instance = manager::get_report_from_persistent($report);
$this->assertEquals($values, $instance->get_filter_values());
}
/**
* Test execute method for a user without permission to view the report
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
set::execute($report->get('id'), '', json_encode(['foo' => 1]));
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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_reportbuilder\external\reports;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\report;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting reports
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\reports\delete
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Sanity test.
$this->assertCount(1, report::get_records());
$result = delete::execute($report->get('id'));
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result);
// Retrieve reports.
$this->assertEmpty(report::get_records());
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'));
}
}
+168
View File
@@ -0,0 +1,168 @@
<?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_reportbuilder\external\reports;
use context_system;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for getting reports
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\reports\get
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_test extends externallib_advanced_testcase {
/**
* Text execute method for edit mode
*/
public function test_execute_editmode(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two filters.
$filterfullname = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$filteremail = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$result = get::execute($report->get('id'), true);
$result = external_api::clean_returnvalue(get::execute_returns(), $result);
$this->assertEquals($result['id'], $report->get('id'));
$this->assertEquals($result['name'], 'My report');
$this->assertEquals($result['source'], users::class);
$this->assertNotEmpty($result['table']);
$this->assertNotEmpty($result['javascript']);
$this->assertFalse($result['filterspresent']);
$this->assertEmpty($result['filtersform']);
$this->assertTrue($result['editmode']);
// Confirm editor-specific data is returned.
$this->assertNotEmpty($result['sidebarmenucards']);
$this->assertNotEmpty($result['conditions']);
$this->assertNotEmpty($result['filters']);
$this->assertTrue($result['filters']['hasavailablefilters']);
$this->assertNotEmpty($result['filters']['availablefilters']);
$this->assertTrue($result['filters']['hasactivefilters']);
$this->assertEquals($filterfullname->get('id'), $result['filters']['activefilters'][0]['id']);
$this->assertEquals($filteremail->get('id'), $result['filters']['activefilters'][1]['id']);
$this->assertNotEmpty($result['sorting']);
$this->assertNotEmpty($result['cardview']);
}
/**
* Text execute method for preview mode
*/
public function test_execute_previewmode(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two filters.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$result = get::execute($report->get('id'), false);
$result = external_api::clean_returnvalue(get::execute_returns(), $result);
$this->assertEquals($result['id'], $report->get('id'));
$this->assertEquals($result['name'], 'My report');
$this->assertEquals($result['source'], users::class);
$this->assertNotEmpty($result['table']);
$this->assertNotEmpty($result['javascript']);
$this->assertTrue($result['filterspresent']);
$this->assertNotEmpty($result['filtersform']);
$this->assertFalse($result['editmode']);
// Confirm editor-specific data is not returned.
$this->assertArrayNotHasKey('sidebarmenucards', $result);
$this->assertArrayNotHasKey('conditions', $result);
$this->assertArrayNotHasKey('filters', $result);
$this->assertArrayNotHasKey('sorting', $result);
$this->assertArrayNotHasKey('cardview', $result);
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
get::execute($report->get('id'), true);
}
/**
* Test execute method for a user without permission to view reports
*/
public function test_execute_view_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$contextid = context_system::instance()->id;
$roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
assign_capability('moodle/reportbuilder:view', CAP_PROHIBIT, $roleid, $contextid);
role_assign($roleid, $user->id, $contextid);
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
get::execute($report->get('id'), false);
}
}
+92
View File
@@ -0,0 +1,92 @@
<?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_reportbuilder\external\reports;
use context_system;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\report;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for listing reports
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\reports\listing
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class listing_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create three reports.
$reportone = $generator->create_report(['name' => 'Report one', 'source' => users::class]);
$reporttwo = $generator->create_report(['name' => 'Report two', 'source' => users::class]);
$reportthree = $generator->create_report(['name' => 'Report three', 'source' => users::class]);
// Create second user, with audience of both report one and two.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$generator->create_audience(['reportid' => $reportone->get('id'), 'configdata' => []]);
$generator->create_audience(['reportid' => $reporttwo->get('id'), 'configdata' => []]);
// Switch to second user, get their report listing.
$result = listing::execute();
$result = external_api::clean_returnvalue(listing::execute_returns(), $result);
$this->assertEquals(['Report one', 'Report two'], array_column($result['reports'], 'name'));
$this->assertEmpty($result['warnings']);
}
/**
* Test execute method for a user without permission to view reports
*/
public function test_execute_access_exception(): void {
global $DB;
$this->resetAfterTest();
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user'], MUST_EXIST);
assign_capability('moodle/reportbuilder:view', CAP_PROHIBIT, $userrole, context_system::instance(), true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
listing::execute();
}
}
+115
View File
@@ -0,0 +1,115 @@
<?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_reportbuilder\external\reports;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for retrieving custom report content
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\reports\retrieve
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class retrieve_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
$this->getDataGenerator()->create_user(['firstname' => 'Zoe', 'lastname' => 'Zebra', 'email' => 'u1@example.com']);
$this->getDataGenerator()->create_user(['firstname' => 'Charlie', 'lastname' => 'Carrot', 'email' => 'u2@example.com']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname', 'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// There are three users (admin plus the two previouly created), but we're paging the first two only.
$result = retrieve::execute($report->get('id'), 0, 2);
$result = external_api::clean_returnvalue(retrieve::execute_returns(), $result);
// All data is generated by exporters, just assert relevant sample of each.
$this->assertArrayHasKey('details', $result);
$this->assertEquals('My report', $result['details']['name']);
$this->assertArrayHasKey('data', $result);
$this->assertEquals(['Full name', 'Email address'], $result['data']['headers']);
$this->assertEquals([
[
'columns' => ['Admin User', 'admin@example.com'],
],
[
'columns' => ['Charlie Carrot', 'u2@example.com'],
],
], $result['data']['rows']);
$this->assertEquals(3, $result['data']['totalrowcount']);
$this->assertEmpty($result['warnings']);
// Retrieve the second set of pages results.
$result = retrieve::execute($report->get('id'), 1, 2);
$result = external_api::clean_returnvalue(retrieve::execute_returns(), $result);
$this->assertArrayHasKey('details', $result);
$this->assertEquals('My report', $result['details']['name']);
$this->assertArrayHasKey('data', $result);
$this->assertEquals(['Full name', 'Email address'], $result['data']['headers']);
$this->assertEquals([
[
'columns' => ['Zoe Zebra', 'u1@example.com'],
],
], $result['data']['rows']);
$this->assertEquals(3, $result['data']['totalrowcount']);
$this->assertEmpty($result['warnings']);
}
/**
* Test execute method for a user without permission to view report
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
retrieve::execute($report->get('id'));
}
}
+98
View File
@@ -0,0 +1,98 @@
<?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_reportbuilder\external\reports;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\event\report_viewed;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\models\report;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for viewing reports
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\reports\view
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class view_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
// Catch the events.
$sink = $this->redirectEvents();
$result = view::execute($report->get('id'));
$result = external_api::clean_returnvalue(view::execute_returns(), $result);
$events = $sink->get_events();
$this->assertCount(1, $events);
$sink->close();
$this->assertValidKeys($result, ['status', 'warnings']);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(report_viewed::class, $event);
$this->assertEquals(report::TABLE, $event->objecttable);
$this->assertEquals($report->get('id'), $event->objectid);
$this->assertEquals($report->get('name'), $event->other['name']);
$this->assertEquals($report->get('source'), $event->other['source']);
$this->assertEquals($report->get_context()->id, $event->contextid);
}
/**
* Test execute method for a user without permission to view reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
view::execute($report->get('id'));
}
}
+83
View File
@@ -0,0 +1,83 @@
<?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_reportbuilder\external\schedules;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for deleting report schedules
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\schedules\delete
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$scheduleid = $schedule->get('id');
$result = delete::execute($report->get('id'), $scheduleid);
$result = external_api::clean_returnvalue(delete::execute_returns(), $result);
$this->assertTrue($result);
$this->assertFalse($schedule::record_exists($scheduleid));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
delete::execute($report->get('id'), $schedule->get('id'));
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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_reportbuilder\external\schedules;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\task\send_schedule;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for sending report schedules
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\schedules\send
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$result = send::execute($report->get('id'), $schedule->get('id'));
$result = external_api::clean_returnvalue(send::execute_returns(), $result);
$this->assertTrue($result);
// Assert our adhoc-task was created.
$tasks = \core\task\manager::get_adhoc_tasks(send_schedule::class);
$this->assertCount(1, $tasks);
$this->assertEquals((object) [
'reportid' => $report->get('id'),
'scheduleid' => $schedule->get('id'),
], reset($tasks)->get_custom_data());
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
send::execute($report->get('id'), $schedule->get('id'));
}
}
+86
View File
@@ -0,0 +1,86 @@
<?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_reportbuilder\external\schedules;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for toggling report schedules
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\schedules\toggle
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toggle_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'enabled' => true]);
// Confirm initial state.
$this->assertTrue($schedule->get('enabled'));
$result = toggle::execute($report->get('id'), $schedule->get('id'), false);
$result = external_api::clean_returnvalue(toggle::execute_returns(), $result);
$this->assertTrue($result);
// Check enabled state was toggled.
$schedule = $schedule->read();
$this->assertFalse($schedule->get('enabled'));
}
/**
* Test execute method for a user without permission to edit reports
*/
public function test_execute_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
toggle::execute($report->get('id'), $schedule->get('id'), false);
}
}
@@ -0,0 +1,81 @@
<?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_reportbuilder\external;
use advanced_testcase;
use core\context\system;
use core_reportbuilder_generator;
use core_reportbuilder\system_report_factory;
use core_reportbuilder\local\systemreports\reports_list;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for system report data exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\system_report_data_exporter
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_data_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Two reports, created one second apart to ensure consistent ordering by time created.
$generator->create_report(['name' => 'My first report', 'source' => users::class]);
$this->waitForSecond();
$generator->create_report(['name' => 'My second report', 'source' => users::class, 'tags' => ['cat', 'dog']]);
$reportinstance = system_report_factory::create(reports_list::class, system::instance());
$exporter = new system_report_data_exporter(null, ['report' => $reportinstance, 'page' => 0, 'perpage' => 1]);
$export = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertEquals([
'Name',
'Report source',
'Tags',
'Time created',
'Time modified',
'Modified by',
], $export->headers);
$this->assertCount(1, $export->rows);
[$name, $source, $tags, $timecreated, $timemodified, $modifiedby] = $export->rows[0]['columns'];
$this->assertStringContainsString('My second report', $name);
$this->assertEquals(users::get_name(), $source);
$this->assertEquals('cat, dog', $tags);
$this->assertNotEmpty($timecreated);
$this->assertNotEmpty($timemodified);
$this->assertEquals('Admin User', $modifiedby);
$this->assertEquals(2, $export->totalrowcount);
}
}
@@ -0,0 +1,99 @@
<?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_reportbuilder\external;
use advanced_testcase;
use context_system;
use moodle_url;
use core_reportbuilder\system_report_available;
use core_reportbuilder\system_report_factory;
/**
* Unit tests for system report exporter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\system_report_exporter
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_exporter_test extends advanced_testcase {
/**
* Load test fixture
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
}
/**
* Data provider for {@see test_export}
*
* @return array[]
*/
public function export_provider(): array {
return [
['With filters' => true],
['Without filters' => false],
];
}
/**
* Text execute method
*
* @param bool $withfilters
*
* @dataProvider export_provider
*/
public function test_export(bool $withfilters): void {
global $PAGE;
$this->resetAfterTest();
// Prevent debug warnings from flexible_table.
$PAGE->set_url(new moodle_url('/'));
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(), '', '', 0,
['withfilters' => $withfilters])->add_attributes(['data-foo' => 'bar', 'data-another' => '1']);
$exporter = new system_report_exporter($systemreport->get_report_persistent(), [
'source' => $systemreport,
'parameters' => json_encode($systemreport->get_parameters()),
]);
$data = $exporter->export($PAGE->get_renderer('core_reportbuilder'));
$this->assertNotEmpty($data->table);
if ($withfilters) {
$this->assertEquals('{"withfilters":true}', $data->parameters);
$this->assertTrue($data->filterspresent);
$this->assertNotEmpty($data->filtersform);
} else {
$this->assertEquals('{"withfilters":false}', $data->parameters);
$this->assertFalse($data->filterspresent);
$this->assertEmpty($data->filtersform);
}
$this->assertEquals([
['name' => 'data-foo', 'value' => 'bar'],
['name' => 'data-another', 'value' => '1']
], $data->attributes);
}
}
@@ -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/>.
declare(strict_types=1);
namespace core_reportbuilder\external\systemreports;
use core\context\system;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\local\systemreports\reports_list;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for validating access to a system report
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\systemreports\can_view
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class can_view_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
$result = can_view::execute(reports_list::class, ['contextid' => system::instance()->id], '', '', 0, []);
$result = external_api::clean_returnvalue(can_view::execute_returns(), $result);
$this->assertTrue($result);
}
/**
* Test execute method for a user without permission to view report
*/
public function test_execute_access_none(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user']);
unassign_capability('moodle/reportbuilder:view', $userrole, system::instance());
$result = can_view::execute(reports_list::class, ['contextid' => system::instance()->id], '', '', 0, []);
$result = external_api::clean_returnvalue(can_view::execute_returns(), $result);
$this->assertFalse($result);
}
}
@@ -0,0 +1,106 @@
<?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_reportbuilder\external\systemreports;
use core\context\system;
use core_reportbuilder_generator;
use core_external\external_api;
use externallib_advanced_testcase;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\local\systemreports\reports_list;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/webservice/tests/helpers.php");
/**
* Unit tests of external class for retrieving system report content
*
* @package core_reportbuilder
* @covers \core_reportbuilder\external\systemreports\retrieve
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class retrieve_test extends externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Two reports, created one second apart to ensure consistent ordering by time created.
$generator->create_report(['name' => 'My first report', 'source' => users::class]);
$this->waitForSecond();
$generator->create_report(['name' => 'My second report', 'source' => users::class, 'tags' => ['cat', 'dog']]);
// Retrieve paged results.
$result = retrieve::execute(reports_list::class, ['contextid' => system::instance()->id], '', '', 0, [], 0, 1);
$result = external_api::clean_returnvalue(retrieve::execute_returns(), $result);
$this->assertArrayHasKey('data', $result);
$this->assertEquals([
'Name',
'Report source',
'Tags',
'Time created',
'Time modified',
'Modified by',
], $result['data']['headers']);
$this->assertCount(1, $result['data']['rows']);
[$name, $source, $tags, $timecreated, $timemodified, $modifiedby] = $result['data']['rows'][0]['columns'];
$this->assertStringContainsString('My second report', $name);
$this->assertEquals(users::get_name(), $source);
$this->assertEquals('cat, dog', $tags);
$this->assertNotEmpty($timecreated);
$this->assertNotEmpty($timemodified);
$this->assertEquals('Admin User', $modifiedby);
$this->assertEquals(2, $result['data']['totalrowcount']);
$this->assertEmpty($result['warnings']);
}
/**
* Test execute method for a user without permission to view report
*/
public function test_execute_access_exception(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user']);
unassign_capability('moodle/reportbuilder:view', $userrole, system::instance());
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot view this report');
retrieve::execute(reports_list::class, ['contextid' => system::instance()->id]);
}
}
@@ -0,0 +1,95 @@
<?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_reportbuilder;
use core_reportbuilder\local\report\action;
use lang_string;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use moodle_url;
use pix_icon;
/**
* Testable system report fixture
*
* @package core_reportbuilder
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_available extends system_report {
/**
* Initialise the report
*/
protected function initialise(): void {
$this->set_main_table('user', 'u');
$this->annotate_entity('user', new lang_string('user'));
$this->add_column((new column(
'username',
new lang_string('username'),
'user'
))
->add_joins($this->get_joins())
->add_field('u.firstname')
);
$withfilters = $this->get_parameter('withfilters', false, PARAM_BOOL);
if ($withfilters) {
$this->add_filter((new filter(
text::class,
'username',
new lang_string('username'),
'user',
'u.username'
))
->add_joins($this->get_joins())
);
}
$withactions = $this->get_parameter('withactions', false, PARAM_BOOL);
if ($withactions) {
$this->add_action(new action(
new moodle_url('/user/profile.php', ['id' => ':id']),
new pix_icon('e/search', get_string('view')),
[],
true,
));
}
}
/**
* Ensure we can view the report
*
* @return bool
*/
protected function can_view(): bool {
return true;
}
/**
* Explicitly set availability of report
*
* @return bool
*/
public static function is_available(): bool {
return true;
}
}
@@ -0,0 +1,42 @@
<?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_reportbuilder;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/system_report_available.php');
/**
* Testable unavailable system report fixture
*
* @package core_reportbuilder
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_unavailable extends system_report_available {
/**
* Explicitly set availability of report to false
*
* @return bool
*/
public static function is_available(): bool {
return false;
}
}
@@ -0,0 +1,75 @@
<?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_reportbuilder;
use stdClass;
use core_reportbuilder\table\system_report_table;
/**
* Testable system report table for getting report data
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_system_report_table extends system_report_table {
/**
* Format each row of returned data, replacing aliased column names with the original
*
* @param array|stdClass $row
* @return array
*/
public function format_row($row): array {
$record = parent::format_row($row);
$result = [];
$columns = $this->report->get_columns();
foreach ($columns as $column) {
$result[$column->get_name()] = $record[$column->get_column_alias()];
}
return $result;
}
/**
* Return all table rows
*
* @return array
*/
public function get_table_rows(): array {
global $PAGE;
$PAGE->set_url('/');
$result = [];
$this->guess_base_url();
$this->setup();
$this->query_db(0, false);
foreach ($this->rawdata as $record) {
$result[] = $this->format_row($record);
}
$this->close_recordset();
return $result;
}
}
@@ -0,0 +1,126 @@
<?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);
use core_reportbuilder\local\models\report;
/**
* Behat data generator for Report builder
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_reportbuilder_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created for this component
*
* @return array[]
*/
protected function get_creatable_entities(): array {
return [
'Reports' => [
'singular' => 'Report',
'datagenerator' => 'report',
'required' => [
'name',
'source',
],
],
'Columns' => [
'singular' => 'Column',
'datagenerator' => 'column',
'required' => [
'report',
'uniqueidentifier',
],
'switchids' => [
'report' => 'reportid',
],
],
'Conditions' => [
'singular' => 'Condition',
'datagenerator' => 'condition',
'required' => [
'report',
'uniqueidentifier',
],
'switchids' => [
'report' => 'reportid',
],
],
'Filters' => [
'singular' => 'Filter',
'datagenerator' => 'filter',
'required' => [
'report',
'uniqueidentifier',
],
'switchids' => [
'report' => 'reportid',
],
],
'Audiences' => [
'singular' => 'Audience',
'datagenerator' => 'audience',
'required' => [
'report',
'configdata',
],
'switchids' => [
'report' => 'reportid',
],
],
'Schedules' => [
'singular' => 'Schedule',
'datagenerator' => 'schedule',
'required' => [
'report',
'name',
],
'switchids' => [
'report' => 'reportid',
],
],
];
}
/**
* Look up report ID from given name
*
* @param string $name
* @return int
*/
protected function get_report_id(string $name): int {
global $DB;
return (int) $DB->get_field(report::TABLE, 'id', ['name' => $name], MUST_EXIST);
}
/**
* Pre-process audience entity, generate correct config structure
*
* @param array $audience
* @return array
*/
protected function preprocess_audience(array $audience): array {
$audience['configdata'] = (array) json_decode($audience['configdata']);
return $audience;
}
}
+219
View File
@@ -0,0 +1,219 @@
<?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);
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\report as helper;
use core_reportbuilder\local\helpers\schedule as schedule_helper;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\models\schedule;
use core_reportbuilder\local\audiences\base as audience_base;
/**
* Report builder test generator
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_reportbuilder_generator extends component_generator_base {
/**
* Create report
*
* @param array|stdClass $record
* @return report
* @throws coding_exception
*/
public function create_report($record): report {
$record = (array) $record;
if (!array_key_exists('name', $record)) {
throw new coding_exception('Record must contain \'name\' property');
}
if (!array_key_exists('source', $record)) {
throw new coding_exception('Record must contain \'source\' property');
}
// Report tags.
$tags = $record['tags'] ?? '';
if (!is_array($tags)) {
$record['tags'] = preg_split('/\s*,\s*/', $tags, -1, PREG_SPLIT_NO_EMPTY);
}
// Include default setup unless specifically disabled in passed record.
$default = (bool) ($record['default'] ?? true);
// If setting up default report, purge caches to ensure any default attributes are always loaded in tests.
$report = helper::create_report((object) $record, $default);
if ($default) {
manager::reset_caches();
}
return $report;
}
/**
* Create report column
*
* @param array|stdClass $record
* @return column
* @throws coding_exception
*/
public function create_column($record): column {
$record = (array) $record;
if (!array_key_exists('reportid', $record)) {
throw new coding_exception('Record must contain \'reportid\' property');
}
if (!array_key_exists('uniqueidentifier', $record)) {
throw new coding_exception('Record must contain \'uniqueidentifier\' property');
}
$column = helper::add_report_column($record['reportid'], $record['uniqueidentifier']);
// Update additional record properties.
unset($record['reportid'], $record['uniqueidentifier']);
if ($properties = column::properties_filter((object) $record)) {
$column->set_many($properties)->update();
}
return $column;
}
/**
* Create report filter
*
* @param array|stdClass $record
* @return filter
* @throws coding_exception
*/
public function create_filter($record): filter {
$record = (array) $record;
if (!array_key_exists('reportid', $record)) {
throw new coding_exception('Record must contain \'reportid\' property');
}
if (!array_key_exists('uniqueidentifier', $record)) {
throw new coding_exception('Record must contain \'uniqueidentifier\' property');
}
$filter = helper::add_report_filter($record['reportid'], $record['uniqueidentifier']);
// Update additional record properties.
unset($record['reportid'], $record['uniqueidentifier']);
if ($properties = filter::properties_filter((object) $record)) {
$filter->set_many($properties)->update();
}
return $filter;
}
/**
* Create report condition
*
* @param array|stdClass $record
* @return filter
* @throws coding_exception
*/
public function create_condition($record): filter {
$record = (array) $record;
if (!array_key_exists('reportid', $record)) {
throw new coding_exception('Record must contain \'reportid\' property');
}
if (!array_key_exists('uniqueidentifier', $record)) {
throw new coding_exception('Record must contain \'uniqueidentifier\' property');
}
$condition = helper::add_report_condition($record['reportid'], $record['uniqueidentifier']);
// Update additional record properties.
unset($record['reportid'], $record['uniqueidentifier']);
if ($properties = filter::properties_filter((object) $record)) {
$condition->set_many($properties)->update();
}
return $condition;
}
/**
* Create report audience
*
* @param array|stdClass $record
* @return audience_base
* @throws coding_exception
*/
public function create_audience($record): audience_base {
$record = (array) $record;
// Required properties.
if (!array_key_exists('reportid', $record)) {
throw new coding_exception('Record must contain \'reportid\' property');
}
if (!array_key_exists('configdata', $record)) {
throw new coding_exception('Record must contain \'configdata\' property');
}
// Default to all users if not specified, for convenience.
/** @var audience_base $classname */
$classname = $record['classname'] ??
\core_reportbuilder\reportbuilder\audience\allusers::class;
return ($classname)::create($record['reportid'], $record['configdata']);
}
/**
* Create report schedule
*
* @param array|stdClass $record
* @return schedule
* @throws coding_exception
*/
public function create_schedule($record): schedule {
$record = (array) $record;
// Required properties.
if (!array_key_exists('reportid', $record)) {
throw new coding_exception('Record must contain \'reportid\' property');
}
if (!array_key_exists('name', $record)) {
throw new coding_exception('Record must contain \'name\' property');
}
// Optional properties.
if (!array_key_exists('format', $record)) {
$record['format'] = 'csv';
}
if (!array_key_exists('subject', $record)) {
$record['subject'] = $record['name'] . ' subject';
}
if (!array_key_exists('message', $record)) {
$record['message'] = $record['name'] . ' message';
}
if (!array_key_exists('timescheduled', $record)) {
$record['timescheduled'] = usergetmidnight(time() + DAYSECS);
}
// Time to use as comparison against current date (null means current time).
$timenow = $record['timenow'] ?? null;
return schedule_helper::create_schedule((object) $record, $timenow);
}
}
+188
View File
@@ -0,0 +1,188 @@
<?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_reportbuilder;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\local\models\{audience, column, filter, report, schedule};
use core_tag_tag;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the test data generator
*
* Note that assertions of created data content is performed in other testcases of the relevant classes, in the majority of cases
* here we just want to assert that the thing we created actually exists
*
* @package core_reportbuilder
* @covers \core_reportbuilder_generator
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends advanced_testcase {
/**
* Test creating a report
*/
public function test_create_report(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'tags' => ['cat', 'dog']]);
$this->assertTrue(report::record_exists($report->get('id')));
$this->assertEqualsCanonicalizing(['cat', 'dog'],
core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report', $report->get('id')));
}
/**
* Test creating a column
*/
public function test_create_column(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$this->assertTrue(column::record_exists($column->get('id')));
}
/**
* Test creating a column, specifying additional properties
*/
public function test_create_column_additional_properties(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => 0]);
$column = $generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'heading' => 'My pants',
'sortenabled' => 1,
]);
$this->assertEquals('My pants', $column->get('heading'));
$this->assertTrue($column->get('sortenabled'));
}
/**
* Test creating a filter
*/
public function test_create_filter(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$this->assertTrue(filter::record_exists($filter->get('id')));
}
/**
* Test creating a filter, specifying additional properties
*/
public function test_create_filter_additional_properties(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => 0]);
$filter = $generator->create_filter([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'heading' => 'My pants',
]);
$this->assertEquals('My pants', $filter->get('heading'));
}
/**
* Test creating a condition
*/
public function test_create_condition(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$condition = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$this->assertTrue(filter::record_exists($condition->get('id')));
}
/**
* Test creating a condition, specifying additional properties
*/
public function test_create_condition_additional_properties(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => 0]);
$condition = $generator->create_condition([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'heading' => 'My pants',
]);
$this->assertEquals('My pants', $condition->get('heading'));
}
/**
* Test creating an audience
*/
public function test_create_audience(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$this->assertTrue(audience::record_exists($audience->get_persistent()->get('id')));
}
/**
* Test creating a schedule
*/
public function test_create_schedule(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$this->assertTrue(schedule::record_exists($schedule->get('id')));
}
}
+201
View File
@@ -0,0 +1,201 @@
<?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);
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\aggregation;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\table\custom_report_table_view;
/**
* Helper base class for reportbuilder unit tests
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class core_reportbuilder_testcase extends advanced_testcase {
/**
* Retrieve content for given report as array of report data
*
* @param int $reportid
* @param int $pagesize
* @param array $filtervalues
* @return array[]
*/
protected function get_custom_report_content(int $reportid, int $pagesize = 30, array $filtervalues = []): array {
$records = [];
// Apply filter values.
user_filter_manager::set($reportid, $filtervalues);
// Create table instance.
$table = custom_report_table_view::create($reportid);
$table->setup();
$table->query_db($pagesize, false);
// Extract raw data.
foreach ($table->rawdata as $record) {
$records[] = $table->format_row($record);
}
$table->close_recordset();
return $records;
}
/**
* Stress test a report source by iterating over all it's columns, enabling sorting where possible and asserting we can
* create a report for each
*
* @param string $source
*/
protected function datasource_stress_test_columns(string $source): void {
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Stress columns', 'source' => $source, 'default' => 0]);
$instance = manager::get_report_from_persistent($report);
// Iterate over each available column, ensure each works correctly independent of any others.
foreach ($instance->get_columns() as $columnidentifier => $columninstance) {
$column = report::add_report_column($report->get('id'), $columnidentifier);
// Enable sorting of the column where possible.
if ($columninstance->get_is_sortable()) {
report::toggle_report_column_sorting($report->get('id'), $column->get('id'), true, SORT_DESC);
}
// We are only asserting the report returns content without errors, not the content itself.
try {
$content = $this->get_custom_report_content($report->get('id'));
$this->assertNotEmpty($content);
// Ensure appropriate debugging was triggered for deprecated column.
if ($columninstance->get_is_deprecated()) {
$this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
}
} catch (Throwable $exception) {
$this->fail("Error for column '{$columnidentifier}': " . $exception->getMessage());
}
report::delete_report_column($report->get('id'), $column->get('id'));
}
}
/**
* Stress test a report source by iterating over all columns and asserting we can create a report while aggregating each
*
* @param string $source
*/
protected function datasource_stress_test_columns_aggregation(string $source): void {
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Stress aggregation', 'source' => $source, 'default' => 0]);
$instance = manager::get_report_from_persistent($report);
// Add every column.
$columndeprecatedcount = 0;
foreach ($instance->get_columns() as $columnidentifier => $column) {
$columndeprecatedcount += (int) $column->get_is_deprecated();
report::add_report_column($report->get('id'), $columnidentifier);
}
// Now iterate over each column, and apply all suitable aggregation types.
$columns = $instance->get_active_columns();
$this->assertDebuggingCalledCount($columndeprecatedcount, null,
array_fill(0, $columndeprecatedcount, DEBUG_DEVELOPER));
foreach ($columns as $column) {
$aggregations = aggregation::get_column_aggregations($column->get_type(), $column->get_disabled_aggregation());
foreach (array_keys($aggregations) as $aggregation) {
$column->get_persistent()->set('aggregation', $aggregation)->update();
// We are only asserting the report returns content without errors, not the content itself.
try {
$content = $this->get_custom_report_content($report->get('id'));
$this->assertNotEmpty($content);
// Ensure appropriate debugging was triggered for deprecated columns.
$this->assertDebuggingCalledCount($columndeprecatedcount, null,
array_fill(0, $columndeprecatedcount, DEBUG_DEVELOPER));
} catch (Throwable $exception) {
$this->fail("Error for column '{$column->get_unique_identifier()}' with aggregation '{$aggregation}': " .
$exception->getMessage());
}
}
// Reset the column aggregation.
$column->get_persistent()->set('aggregation', null)->update();
}
}
/**
* Stress test a report source by iterating over all it's conditions and asserting we can create a report using each
*
* @param string $source
* @param string $columnidentifier Should be a simple column, with as few fields and joins as possible, ideally selected
* from the base table itself
*/
protected function datasource_stress_test_conditions(string $source, string $columnidentifier): void {
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Stress conditions', 'source' => $source, 'default' => 0]);
$instance = manager::get_report_from_persistent($report);
// Add single column only (to ensure no conditions have reliance on any columns).
report::add_report_column($report->get('id'), $columnidentifier);
// Iterate over each available condition, ensure each works correctly independent of any others.
$conditionidentifiers = array_keys($instance->get_conditions());
foreach ($conditionidentifiers as $conditionidentifier) {
$condition = report::add_report_condition($report->get('id'), $conditionidentifier);
$conditioninstance = $instance->get_condition($condition->get('uniqueidentifier'));
/** @var \core_reportbuilder\local\filters\base $conditionclass */
$conditionclass = $conditioninstance->get_filter_class();
// Set report condition values in order to activate it.
$conditionvalues = $conditionclass::create($conditioninstance)->get_sample_values();
if (empty($conditionvalues)) {
debugging("Missing sample values from filter '{$conditionclass}'", DEBUG_DEVELOPER);
}
$instance->set_condition_values($conditionvalues);
// We are only asserting the report returns content without errors, not the content itself.
try {
$content = $this->get_custom_report_content($report->get('id'));
$this->assertIsArray($content);
// Ensure appropriate debugging was triggered for deprecated condition.
if ($conditioninstance->get_is_deprecated()) {
$this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
}
} catch (Throwable $exception) {
$this->fail("Error for condition '{$conditionidentifier}': " . $exception->getMessage());
}
report::delete_report_condition($report->get('id'), $condition->get('id'));
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?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_reportbuilder;
use advanced_testcase;
use core_reportbuilder_generator;
use core_tag_tag;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/lib.php");
/**
* Unit tests for the component callbacks
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends advanced_testcase {
/**
* Test getting tagged reports
*
* @covers ::core_reportbuilder_get_tagged_reports
*/
public function test_core_reportbuilder_get_tagged_reports(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create three tagged reports.
$reportone = $generator->create_report(['name' => 'Report 1', 'source' => users::class, 'tags' => ['cat']]);
$reporttwo = $generator->create_report(['name' => 'Report 2', 'source' => users::class, 'tags' => ['dog']]);
$reportthree = $generator->create_report(['name' => 'Report 3', 'source' => users::class, 'tags' => ['cat']]);
// Add all users audience to report one and two.
$generator->create_audience(['reportid' => $reportone->get('id'), 'configdata' => []]);
$generator->create_audience(['reportid' => $reporttwo->get('id'), 'configdata' => []]);
$tag = core_tag_tag::get_by_name(0, 'cat');
// Current user can only access report one with "cat" tag.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$tagindex = core_reportbuilder_get_tagged_reports($tag);
$this->assertStringContainsString($reportone->get_formatted_name(), $tagindex->content);
$this->assertStringNotContainsString($reporttwo->get_formatted_name(), $tagindex->content);
$this->assertStringNotContainsString($reportthree->get_formatted_name(), $tagindex->content);
// Admin can access both reports with "cat" tag.
$this->setAdminUser();
$tagindex = core_reportbuilder_get_tagged_reports($tag);
$this->assertStringContainsString($reportone->get_formatted_name(), $tagindex->content);
$this->assertStringNotContainsString($reporttwo->get_formatted_name(), $tagindex->content);
$this->assertStringContainsString($reportthree->get_formatted_name(), $tagindex->content);
}
}
@@ -0,0 +1,117 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\local\report\column;
use core_user\reportbuilder\datasource\users;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for avg aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\avg
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class avg_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user suspended.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:suspended',
'aggregation' => avg::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', '0.5'],
['Admin', '0.0'],
], array_map('array_values', $content));
}
/**
* Test aggregation when applied to column with callback
*/
public function test_column_aggregation_with_callback(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
// This is the column we'll aggregate.
$generator->create_column(
['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended', 'aggregation' => avg::get_class_name()]
);
// Set callback to format the column (hack column definition to ensure callbacks are executed).
$instance = manager::get_report_from_persistent($report);
$instance->get_column('user:suspended')
->set_type(column::TYPE_FLOAT)
->set_callback(static function(float $value, stdClass $row, $arguments, ?string $aggregation): string {
// Simple callback to return the given value, and append aggregation type.
return number_format($value, 1) . " ({$aggregation})";
});
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => '0.0 (avg)',
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => '0.5 (avg)',
],
], $content);
}
}
@@ -0,0 +1,72 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for count aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\count
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class count_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user lastname.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'aggregation' => count::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 3],
['Admin', 1],
], array_map('array_values', $content));
}
}
@@ -0,0 +1,100 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for count distinct aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\countdistinct
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class countdistinct_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user lastname.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'aggregation' => countdistinct::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 2],
['Admin', 1],
], array_map('array_values', $content));
}
/**
* Test aggregation when applied to column with multiple fields
*/
public function test_column_aggregation_multiple_fields(): void {
$this->resetAfterTest();
// Create a user with the same firstname as existing admin.
$this->getDataGenerator()->create_user(['firstname' => 'Admin', 'lastname' => 'Test']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullname',
'aggregation' => countdistinct::get_class_name(),
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
// There are two distinct fullnames ("Admin User" & "Admin Test").
$countdistinct = reset($content[0]);
$this->assertEquals(2, $countdistinct);
}
}
@@ -0,0 +1,201 @@
<?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_reportbuilder\local\aggregation;
use core_badges_generator;
use core_badges\reportbuilder\datasource\badges;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_user\reportbuilder\datasource\users;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for group concatenation aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\groupconcat
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcat_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user lastname.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'aggregation' => groupconcat::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_ASC,
]);
// Assert lastname column was aggregated, and itself also sorted predictably.
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 'Apple, Banana, Banana'],
['Admin', 'User'],
], array_map('array_values', $content));
}
/**
* Test aggregation when applied to column with multiple fields
*/
public function test_column_aggregation_multiple_fields(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user(['firstname' => 'Adam', 'lastname' => 'Apple']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullnamewithlink',
'aggregation' => groupconcat::get_class_name(),
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
// Ensure users are sorted predictably (Adam -> Admin).
[$userone, $usertwo] = explode(', ', reset($content[0]));
$this->assertStringContainsString(fullname($user, true), $userone);
$this->assertStringContainsString(fullname(get_admin(), true), $usertwo);
}
/**
* Test aggregation when applied to column with callback
*/
public function test_column_aggregation_with_callback(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 0]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:confirmed',
'aggregation' => groupconcat::get_class_name(),
]);
// Add callback to format the column.
$instance = manager::get_report_from_persistent($report);
$instance->get_column('user:confirmed')
->add_callback(static function(string $value, stdClass $row, $arguments, ?string $aggregation): string {
// Simple callback to return the given value, and append aggregation type.
return "{$value} ({$aggregation})";
});
// Assert confirmed column was aggregated, and sorted predictably with callback applied.
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_confirmed' => 'Yes (groupconcat)',
],
[
'c0_firstname' => 'Bob',
'c1_confirmed' => 'No (groupconcat), Yes (groupconcat), Yes (groupconcat)',
],
], $content);
}
/**
* Test aggregation when applied to column with callback that expects/handles null values
*/
public function test_datasource_aggregate_column_callback_with_null(): void {
$this->resetAfterTest();
$this->setAdminUser();
$userone = $this->getDataGenerator()->create_user(['description' => 'First user']);
$usertwo = $this->getDataGenerator()->create_user(['description' => 'Second user']);
/** @var core_badges_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_badges');
// Create course badge, issue to both users.
$badgeone = $generator->create_badge(['name' => 'First badge']);
$badgeone->issue($userone->id, true);
$badgeone->issue($usertwo->id, true);
// Create second badge, without issuing to anyone.
$badgetwo = $generator->create_badge(['name' => 'Second badge']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Badges', 'source' => badges::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:name', 'sortenabled' => 1]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:description',
'aggregation' => groupconcat::get_class_name(),
]);
// Assert description column was aggregated, with callbacks accounting for null values.
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_name' => $badgeone->name,
'c1_description' => "{$userone->description}, {$usertwo->description}",
],
[
'c0_name' => $badgetwo->name,
'c1_description' => '',
],
], $content);
}
}
@@ -0,0 +1,162 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\local\report\column;
use core_user\reportbuilder\datasource\users;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for group concatenation distinct aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\groupconcatdistinct
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcatdistinct_test extends core_reportbuilder_testcase {
/**
* Test setup, we need to skip these tests on non-supported databases
*/
public function setUp(): void {
global $DB;
if (!groupconcatdistinct::compatible(column::TYPE_TEXT)) {
$this->markTestSkipped('Distinct group concatenation not supported in ' . $DB->get_dbfamily());
}
}
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user lastname.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:lastname',
'aggregation' => groupconcatdistinct::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_ASC,
]);
// Assert lastname column was aggregated, and itself also sorted predictably.
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 'Apple, Banana'],
['Admin', 'User'],
], array_map('array_values', $content));
}
/**
* Test aggregation when applied to column with multiple fields
*/
public function test_column_aggregation_multiple_fields(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user(['firstname' => 'Adam', 'lastname' => 'Apple']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullnamewithlink',
'aggregation' => groupconcatdistinct::get_class_name(),
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
// Ensure users are sorted predictably (Adam -> Admin).
[$userone, $usertwo] = explode(', ', reset($content[0]));
$this->assertStringContainsString(fullname($user, true), $userone);
$this->assertStringContainsString(fullname(get_admin(), true), $usertwo);
}
/**
* Test aggregation when applied to column with callback
*/
public function test_column_aggregation_with_callback(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 0]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
// This is the column we'll aggregate.
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:confirmed',
'aggregation' => groupconcatdistinct::get_class_name(),
]);
// Add callback to format the column.
$instance = manager::get_report_from_persistent($report);
$instance->get_column('user:confirmed')
->add_callback(static function(string $value, stdClass $row, $arguments, ?string $aggregation): string {
// Simple callback to return the given value, and append aggregation type.
return "{$value} ({$aggregation})";
});
// Assert confirmed column was aggregated, and sorted predictably with callback applied.
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_confirmed' => 'Yes (groupconcatdistinct)',
],
[
'c0_firstname' => 'Bob',
'c1_confirmed' => 'No (groupconcatdistinct), Yes (groupconcatdistinct)',
],
], $content);
}
}
@@ -0,0 +1,71 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for max aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\max
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class max_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user suspended.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:suspended',
'aggregation' => max::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 'Yes'],
['Admin', 'No'],
], array_map('array_values', $content));
}
}
@@ -0,0 +1,71 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for min aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\min
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class min_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Admin', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user suspended.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:suspended',
'aggregation' => min::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 'Yes'],
['Admin', 'No'],
], array_map('array_values', $content));
}
}
@@ -0,0 +1,71 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for sum aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\percent
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class percent_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user suspended.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:suspended',
'aggregation' => percent::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', '50.0%'],
['Admin', '0.0%'],
], array_map('array_values', $content));
}
}
@@ -0,0 +1,118 @@
<?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_reportbuilder\local\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\manager;
use core_reportbuilder\local\report\column;
use core_user\reportbuilder\datasource\users;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for sum aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\sum
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sum_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Report columns, aggregated/sorted by user suspended.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:suspended',
'aggregation' => sum::get_class_name(),
'sortenabled' => 1,
'sortdirection' => SORT_DESC,
]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Bob', 2],
['Admin', 0],
], array_map('array_values', $content));
}
/**
* Test aggregation when applied to column with callback
*/
public function test_column_aggregation_with_callback(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
// This is the column we'll aggregate.
$generator->create_column(
['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended', 'aggregation' => sum::get_class_name()]
);
// Set callback to format the column (hack column definition to ensure callbacks are executed).
$instance = manager::get_report_from_persistent($report);
$instance->get_column('user:suspended')
->set_type(column::TYPE_INTEGER)
->set_callback(static function(int $value, stdClass $row, $arguments, ?string $aggregation): string {
// Simple callback to return the given value, and append aggregation type.
return "{$value} ({$aggregation})";
});
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => '0 (sum)',
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => '2 (sum)',
],
], $content);
}
}
@@ -0,0 +1,391 @@
<?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/>.
/**
* Unit tests for base entity
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace core_reportbuilder\local\entities;
use advanced_testcase;
use coding_exception;
use lang_string;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for base entity
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\entities\base
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_test extends advanced_testcase {
/**
* Test entity table alias
*/
public function test_get_table_alias(): void {
$entity = new base_test_entity();
$mytablealias = $entity->get_table_alias('mytable');
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $mytablealias);
$myothertablealias = $entity->get_table_alias('myothertable');
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $myothertablealias);
// They must differ.
$this->assertNotEquals($mytablealias, $myothertablealias);
// Re-request both, ensure they are identical to what we previously received.
$this->assertEquals($mytablealias, $entity->get_table_alias('mytable'));
$this->assertEquals($myothertablealias, $entity->get_table_alias('myothertable'));
}
/**
* Test for invalid get table alias
*/
public function test_get_table_alias_invalid(): void {
$entity = new base_test_entity();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
'Invalid table name (nonexistingalias)');
$entity->get_table_alias('nonexistingalias');
}
/**
* Test getting all table aliases
*/
public function test_get_table_aliases(): void {
$entity = new base_test_entity();
[
'mytable' => $mytablealias,
'myothertable' => $myothertablealias,
] = $entity->get_table_aliases();
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $mytablealias);
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $myothertablealias);
// They must differ.
$this->assertNotEquals($mytablealias, $myothertablealias);
}
/**
* Test setting table alias
*/
public function test_set_table_alias(): void {
$entity = new base_test_entity();
$entity->set_table_alias('mytable', 'newalias');
$this->assertEquals('newalias', $entity->get_table_alias('mytable'));
}
/**
* Test invalid entity set table alias
*/
public function test_set_table_alias_invalid(): void {
$entity = new base_test_entity();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: Invalid table name (nonexistent)');
$entity->set_table_alias('nonexistent', 'newalias');
}
/**
* Test setting multiple table aliases
*/
public function test_set_table_aliases(): void {
$entity = new base_test_entity();
$entity->set_table_aliases([
'mytable' => 'newalias',
'myothertable' => 'newalias2',
]);
$this->assertEquals([
'mytable' => 'newalias',
'myothertable' => 'newalias2',
], $entity->get_table_aliases());
}
/**
* Test setting multiple table aliases, containing an invalid table
*/
public function test_set_table_aliases_invalid(): void {
$entity = new base_test_entity();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: Invalid table name (nonexistent)');
$entity->set_table_aliases([
'mytable' => 'newalias',
'nonexistent' => 'newalias2',
]);
}
/**
* Test setting table join alias
*/
public function test_set_table_join_alias(): void {
$entity = new base_test_entity();
$entity->set_table_join_alias('mytable', 'newalias');
$this->assertTrue($entity->has_table_join_alias('mytable'));
$this->assertEquals('newalias', $entity->get_table_alias('mytable'));
}
/**
* Test that entity doesn't have table join alias by default
*
* {@see test_set_table_join_alias} for assertion where it does
*/
public function test_has_table_join_alias(): void {
$entity = new base_test_entity();
$this->assertFalse($entity->has_table_join_alias('mytable'));
}
/**
* Test entity name
*/
public function test_set_entity_name(): void {
$entity = new base_test_entity();
$this->assertEquals('base_test_entity', $entity->get_entity_name());
$entity->set_entity_name('newentityname');
$this->assertEquals('newentityname', $entity->get_entity_name());
}
/**
* Test entity title
*/
public function test_set_entity_title(): void {
$entity = new base_test_entity();
$this->assertEquals(new lang_string('yes'), $entity->get_entity_title());
$newtitle = new lang_string('fullname');
$entity->set_entity_title($newtitle);
$this->assertEquals($newtitle, $entity->get_entity_title());
}
/**
* Test adding single join
*/
public function test_add_join(): void {
$entity = new base_test_entity();
$tablejoin = "JOIN {course} c2 ON c2.id = c1.id";
$entity->add_join($tablejoin);
$this->assertEquals([$tablejoin], $entity->get_joins());
}
/**
* Test adding multiple joins
*/
public function test_add_joins(): void {
$entity = new base_test_entity();
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$entity->add_joins($tablejoins);
$this->assertEquals($tablejoins, $entity->get_joins());
}
/**
* Test adding duplicate joins
*/
public function test_add_duplicate_joins(): void {
$entity = new base_test_entity();
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$entity
->add_joins($tablejoins)
->add_joins($tablejoins);
$this->assertEquals($tablejoins, $entity->get_joins());
}
/**
* Test getting column
*/
public function test_get_column(): void {
$entity = (new base_test_entity())->initialise();
$column = $entity->get_column('test');
$this->assertEquals('base_test_entity:test', $column->get_unique_identifier());
}
/**
* Test for invalid get column
*/
public function test_get_column_invalid(): void {
$entity = (new base_test_entity())->initialise();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
'Invalid column name (nonexistingcolumn)');
$entity->get_column('nonexistingcolumn');
}
/**
* Test getting columns
*/
public function test_get_columns(): void {
$entity = (new base_test_entity())->initialise();
$columns = $entity->get_columns();
$this->assertCount(1, $columns);
$this->assertContainsOnlyInstancesOf(column::class, $columns);
}
/**
* Test getting filter
*/
public function test_get_filter(): void {
$entity = (new base_test_entity())->initialise();
$filter = $entity->get_filter('test');
$this->assertEquals('base_test_entity:test', $filter->get_unique_identifier());
}
/**
* Test for invalid get filter
*/
public function test_get_filter_invalid(): void {
$entity = (new base_test_entity())->initialise();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
'Invalid filter name (nonexistingfilter)');
$entity->get_filter('nonexistingfilter');
}
/**
* Test getting filters
*/
public function test_get_filters(): void {
$entity = (new base_test_entity())->initialise();
$filters = $entity->get_filters();
$this->assertCount(1, $filters);
$this->assertContainsOnlyInstancesOf(filter::class, $filters);
}
/**
* Test getting condition
*/
public function test_get_condition(): void {
$entity = (new base_test_entity())->initialise();
$condition = $entity->get_condition('test');
$this->assertEquals('base_test_entity:test', $condition->get_unique_identifier());
}
/**
* Test for invalid get condition
*/
public function test_get_condition_invalid(): void {
$entity = (new base_test_entity())->initialise();
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Coding error detected, it must be fixed by a programmer: ' .
'Invalid condition name (nonexistingcondition)');
$entity->get_condition('nonexistingcondition');
}
/**
* Test getting conditions
*/
public function test_get_conditions(): void {
$entity = (new base_test_entity())->initialise();
$conditions = $entity->get_conditions();
$this->assertCount(1, $conditions);
$this->assertContainsOnlyInstancesOf(filter::class, $conditions);
}
}
/**
* Simple implementation of the base entity
*/
class base_test_entity extends base {
/**
* Table aliases
*
* @return array
*/
protected function get_default_tables(): array {
return [
'mytable',
'myothertable',
];
}
/**
* Entity title
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('yes');
}
/**
* Initialise entity
*
* @return base
*/
public function initialise(): base {
$column = (new column(
'test',
new lang_string('no'),
$this->get_entity_name()
))
->add_field('no');
$filter = (new filter(
text::class,
'test',
new lang_string('no'),
$this->get_entity_name(),
))
->set_field_sql('no');
return $this
->add_column($column)
->add_filter($filter)
->add_condition($filter);
}
}
@@ -0,0 +1,168 @@
<?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_reportbuilder\local\entities;
use advanced_testcase;
use core\context\system;
/**
* Unit tests for user entity
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\entities\user
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_test extends advanced_testcase {
/**
* Test getting user identity column
*/
public function test_get_identity_column(): void {
$this->resetAfterTest();
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']);
$user = new user();
$user->initialise();
$columnusername = $user->get_identity_column('username');
$this->assertEquals('user:username', $columnusername->get_unique_identifier());
$columnprofilefield = $user->get_identity_column('profile_field_hi');
$this->assertEquals('user:profilefield_hi', $columnprofilefield->get_unique_identifier());
}
/**
* Test getting all user identity columns
*/
public function test_get_identity_columns(): void {
$this->resetAfterTest();
$this->setAdminUser();
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']);
set_config('showuseridentity', 'username,profilefield_hi');
$context = system::instance();
$user = new user();
$user->initialise();
// All columns.
$this->assertEqualsCanonicalizing([
'user:username',
'user:profilefield_hi',
], array_map(
fn($column) => $column->get_unique_identifier(),
$user->get_identity_columns($context),
));
// Exclude username.
$columns = $user->get_identity_columns($context, ['username']);
$this->assertCount(1, $columns);
$this->assertEquals('user:profilefield_hi', reset($columns)->get_unique_identifier());
}
/**
* Test getting user identity filter
*/
public function test_get_identity_filter(): void {
$this->resetAfterTest();
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']);
$user = new user();
$user->initialise();
$filterusername = $user->get_identity_filter('username');
$this->assertEquals('user:username', $filterusername->get_unique_identifier());
$filterprofilefield = $user->get_identity_filter('profile_field_hi');
$this->assertEquals('user:profilefield_hi', $filterprofilefield->get_unique_identifier());
}
/**
* Test getting all user identity filters
*/
public function test_get_identity_filters(): void {
$this->resetAfterTest();
$this->setAdminUser();
$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']);
set_config('showuseridentity', 'username,profilefield_hi');
$context = system::instance();
$user = new user();
$user->initialise();
// All filters.
$this->assertEqualsCanonicalizing([
'user:username',
'user:profilefield_hi',
], array_map(
fn($filter) => $filter->get_unique_identifier(),
$user->get_identity_columns($context),
));
// Exclude username.
$filters = $user->get_identity_filters($context, ['username']);
$this->assertCount(1, $filters);
$this->assertEquals('user:profilefield_hi', reset($filters)->get_unique_identifier());
}
/**
* Data provider for {@see test_get_name_fields_select}
*
* @return array
*/
public function get_name_fields_select_provider(): array {
return [
['firstname', ['firstname']],
['firstname lastname', ['firstname', 'lastname']],
['firstname middlename lastname', ['firstname', 'middlename', 'lastname']],
['alternatename lastname firstname', ['alternatename', 'lastname', 'firstname']],
];
}
/**
* Tests the helper method for selecting all of a users' name fields
*
* @param string $fullnamedisplay
* @param string[] $expecteduserfields
*
* @dataProvider get_name_fields_select_provider
*/
public function test_get_name_fields_select(string $fullnamedisplay, array $expecteduserfields): void {
global $DB;
$this->resetAfterTest(true);
set_config('alternativefullnameformat', $fullnamedisplay);
// As a user without permission to view all fields we always get the standard ones.
$fields = user::get_name_fields_select('u');
$user = $DB->get_record_sql("SELECT {$fields} FROM {user} u WHERE username = :username", ['username' => 'admin']);
$this->assertEquals(['firstname', 'lastname'], array_keys((array) $user));
// As the admin we get all name fields from alternativefullnameformat.
$this->setAdminUser();
$fields = user::get_name_fields_select('u');
$user = $DB->get_record_sql("SELECT {$fields} FROM {user} u WHERE username = :username", ['username' => 'admin']);
$this->assertEquals($expecteduserfields, array_keys((array) $user));
}
}
@@ -0,0 +1,96 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for course selector filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\autocomplete
* @copyright 2022 Nathan Nguyen <nathannguyen@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class autocomplete_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public function get_sql_filter_provider(): array {
return [
[[], ["Course 1 full name", "Course 2 full name", "Course 3 full name", "PHPUnit test site"]],
[["course1", "course3"], ["Course 1 full name", "Course 3 full name"]],
[["course1"], ["Course 1 full name"]],
];
}
/**
* Test getting filter SQL
*
* @param array $shortnames list of course short name
* @param array $expected list of course full name
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(array $shortnames, array $expected): void {
global $DB;
$this->resetAfterTest();
// Create courses as values for autocompletion.
$course1 = $this->getDataGenerator()->create_course([
'fullname' => "Course 1 full name",
'shortname' => 'course1',
]);
$course2 = $this->getDataGenerator()->create_course([
'fullname' => "Course 2 full name",
'shortname' => 'course2',
]);
$course3 = $this->getDataGenerator()->create_course([
'fullname' => "Course 3 full name",
'shortname' => 'course3',
]);
$filter = (new filter(
autocomplete::class,
'test',
new \lang_string('course'),
'testentity',
'shortname'
))->set_options([
$course1->shortname => $course1->fullname,
$course2->shortname => $course2->fullname,
$course3->shortname => $course3->fullname,
]);
[$select, $params] = text::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_values' => $shortnames,
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
$this->assertEqualsCanonicalizing($expected, $fullnames);
}
}
@@ -0,0 +1,86 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for boolean report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\boolean_select
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class boolean_select_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter_simple}
*
* @return array
*/
public function get_sql_filter_simple_provider(): array {
return [
[boolean_select::ANY_VALUE, true],
[boolean_select::CHECKED, true],
[boolean_select::NOT_CHECKED, false],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param bool $expectuser
*
* @dataProvider get_sql_filter_simple_provider
*/
public function test_get_sql_filter_simple(int $operator, bool $expectuser): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'suspended' => 1,
]);
$filter = new filter(
boolean_select::class,
'test',
new lang_string('yes'),
'testentity',
'suspended'
);
// Create instance of our filter, passing given operator.
[$select, $params] = boolean_select::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
if ($expectuser) {
$this->assertContains($user->username, $usernames);
} else {
$this->assertNotContains($user->username, $usernames);
}
}
}
@@ -0,0 +1,148 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for course category report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\category
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public function get_sql_filter_provider(): array {
return [
// Equal to.
['One', category::EQUAL_TO, false, ['One']],
['One', category::EQUAL_TO, true, ['One', 'Two', 'Three']],
['Two', category::EQUAL_TO, true, ['Two', 'Three']],
['Three', category::EQUAL_TO, true, ['Three']],
// Not equal to.
['One', category::NOT_EQUAL_TO, false, ['Category 1', 'Two', 'Three', 'Four', 'Five', 'Six']],
['One', category::NOT_EQUAL_TO, true, ['Category 1', 'Four', 'Five', 'Six']],
['Two', category::NOT_EQUAL_TO, true, ['Category 1', 'One', 'Four', 'Five', 'Six']],
['Three', category::NOT_EQUAL_TO, true, ['Category 1', 'One', 'Two', 'Four', 'Five', 'Six']],
// Default/empty state.
[null, category::EQUAL_TO, false, ['Category 1', 'One', 'Two', 'Three', 'Four', 'Five', 'Six']],
];
}
/**
* Test getting filter SQL
*
* @param string|null $categoryname
* @param int $operator
* @param bool $subcategories
* @param string[] $expectedcategories
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(
?string $categoryname,
int $operator,
bool $subcategories,
array $expectedcategories,
): void {
global $DB;
$this->resetAfterTest();
// Create category tree "One -> Two -> Three".
$category1 = $this->getDataGenerator()->create_category(['name' => 'One']);
$category2 = $this->getDataGenerator()->create_category(['name' => 'Two', 'parent' => $category1->id]);
$category3 = $this->getDataGenerator()->create_category(['name' => 'Three', 'parent' => $category2->id]);
// Second category tree "Four -> Five -> Six".
$category4 = $this->getDataGenerator()->create_category(['name' => 'Four']);
$category5 = $this->getDataGenerator()->create_category(['name' => 'Five', 'parent' => $category4->id]);
$category6 = $this->getDataGenerator()->create_category(['name' => 'Six', 'parent' => $category5->id]);
if ($categoryname !== null) {
$categoryid = $DB->get_field('course_categories', 'id', ['name' => $categoryname], MUST_EXIST);
} else {
$categoryid = null;
}
$filter = new filter(
category::class,
'test',
new lang_string('yes'),
'testentity',
'id'
);
// Create instance of our filter, passing given operator.
[$select, $params] = category::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $categoryid,
$filter->get_unique_identifier() . '_subcategories' => $subcategories,
]);
$categories = $DB->get_fieldset_select('course_categories', 'name', $select, $params);
$this->assertEqualsCanonicalizing($expectedcategories, $categories);
}
/**
* Test getting filter SQL with parameters
*/
public function test_get_sql_filter_parameters(): void {
global $DB;
$this->resetAfterTest();
$category1 = $this->getDataGenerator()->create_category(['name' => 'One']);
$category2 = $this->getDataGenerator()->create_category(['name' => 'Two', 'parent' => $category1->id]);
$category3 = $this->getDataGenerator()->create_category(['name' => 'Three']);
// Rather convoluted filter SQL, but enough to demonstrate usage of a parameter that gets used twice in the query.
$paramzero = database::generate_param_name();
$filter = new filter(
category::class,
'test',
new lang_string('yes'),
'testentity',
"id + :{$paramzero}",
[$paramzero => 0]
);
// When including sub-categories, the filter SQL is included twice (for the category itself, plus to find descendents).
[$select, $params] = category::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_value' => $category1->id,
$filter->get_unique_identifier() . '_subcategories' => true,
]);
$categories = $DB->get_fieldset_select('course_categories', 'id', $select, $params);
$this->assertEqualsCanonicalizing([$category1->id, $category2->id], $categories);
}
}
@@ -0,0 +1,103 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for cohort report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\cohort
* @copyright 2024 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array[]
*/
public static function get_sql_filter_provider(): array {
return [
'Empty' => [
[],
['C1', 'C2', 'C3'],
],
'Non-existing' => [
[-1],
[],
],
'Single cohort' => [
['C1'],
['C1'],
],
'Multiple cohorts' => [
['C1', 'C2'],
['C1', 'C2'],
],
];
}
/**
* Test getting filter SQL
*
* @param int[]|string[] $values
* @param string[] $expectcohorts
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(array $values, array $expectcohorts): void {
global $DB;
$this->resetAfterTest();
$this->getDataGenerator()->create_cohort(['name' => 'C1']);
$this->getDataGenerator()->create_cohort(['name' => 'C2']);
$this->getDataGenerator()->create_cohort(['name' => 'C3']);
// Create cohort lookup for convenience, transform values that refer to cohorts by name, to their ID.
$cohortmap = $DB->get_records_menu(table: 'cohort', fields: 'name, id');
$values = array_map(static function(int|string $value) use ($cohortmap): int {
if (is_numeric($value)) {
return $value;
}
return (int) $cohortmap[$value];
}, $values);
$filter = new filter(
cohort::class,
'test',
new lang_string('yes'),
'testentity',
'id'
);
// Create instance of our filter, passing given values.
[$select, $params] = cohort::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_values' => $values,
]);
$cohorts = $DB->get_fieldset_select('cohort', 'name', $select, $params);
$this->assertEqualsCanonicalizing($expectcohorts, $cohorts);
}
}
@@ -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/>.
declare(strict_types=1);
namespace core_reportbuilder\local\filters;
use advanced_testcase;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for course selector filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\course_selector
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_selector_test extends advanced_testcase {
/**
* Test getting filter SQL
*/
public function test_get_sql_filter(): void {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course([
'fullname' => "Time travel",
]);
$course2 = $this->getDataGenerator()->create_course([
'fullname' => "Quantum computing",
]);
$course3 = $this->getDataGenerator()->create_course([
'fullname' => "Space travel",
]);
$filter = new filter(
course_selector::class,
'test',
new \lang_string('course'),
'testentity',
'id'
);
// Create instance of our filter, passing given courses ids.
[$select, $params] = text::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_values' => [$course1->id, $course3->id],
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
$this->assertEqualsCanonicalizing(['Time travel', 'Space travel'], $fullnames);
// Test without passing any course id.
[$select, $params] = text::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_values' => [],
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
$this->assertEqualsCanonicalizing(['Time travel', 'Quantum computing', 'Space travel', 'PHPUnit test site'], $fullnames);
}
}
@@ -0,0 +1,314 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for date report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\date
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class date_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter_simple}
*
* @return array
*/
public function get_sql_filter_simple_provider(): array {
return [
[date::DATE_ANY, true],
[date::DATE_NOT_EMPTY, true],
[date::DATE_EMPTY, false],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param bool $expectuser
*
* @dataProvider get_sql_filter_simple_provider
*/
public function test_get_sql_filter_simple(int $operator, bool $expectuser): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user([
'timecreated' => 12345,
]);
$filter = new filter(
date::class,
'test',
new lang_string('yes'),
'testentity',
'timecreated'
);
// Create instance of our filter, passing given operator.
[$select, $params] = date::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
if ($expectuser) {
$this->assertContains($user->username, $usernames);
} else {
$this->assertNotContains($user->username, $usernames);
}
}
/**
* Test getting filter SQL while specifying a date range
*/
public function test_get_sql_filter_date_range(): void {
global $DB;
$this->resetAfterTest();
$userone = $this->getDataGenerator()->create_user(['timecreated' => 50]);
$usertwo = $this->getDataGenerator()->create_user(['timecreated' => 100]);
$filter = new filter(
date::class,
'test',
new lang_string('yes'),
'testentity',
'timecreated'
);
// Create instance of our date range filter.
[$select, $params] = date::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => date::DATE_RANGE,
$filter->get_unique_identifier() . '_from' => 80,
$filter->get_unique_identifier() . '_to' => 120,
]);
// The only matching user should be our first test user.
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertEquals([$usertwo->username], $usernames);
}
/**
* Data provider for {@see test_get_sql_filter_current_week}
*
* @return array
*/
public function get_sql_filter_current_week_provider(): array {
return array_map(static function(int $day): array {
return [$day];
}, range(0, 6));
}
/**
* Test getting filter SQL for the current week. Note that relative dates are hard to test, here we are asserting that
* the current time is always within the current week regardless of calendar configuration/preferences
*
* @param int $startweekday
*
* @dataProvider get_sql_filter_current_week_provider
*/
public function test_get_sql_filter_current_week(int $startweekday): void {
global $DB;
$this->resetAfterTest();
set_config('calendar_startwday', $startweekday);
$user = $this->getDataGenerator()->create_user(['timecreated' => time()]);
$filter = new filter(
date::class,
'test',
new lang_string('yes'),
'testentity',
'timecreated'
);
[$select, $params] = date::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => date::DATE_CURRENT,
$filter->get_unique_identifier() . '_unit' => date::DATE_UNIT_WEEK,
]);
$matchingusers = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertContains($user->username, $matchingusers);
}
/**
* Data provider for {@see test_get_sql_filter_current_week_no_match}
*
* @return array
*/
public function get_sql_filter_current_week_no_match_provider(): array {
$data = [];
// For each day, create provider data for -/+ 8 days.
foreach (range(0, 6) as $day) {
$data = array_merge($data, [
[$day, '-8 day'],
[$day, '+8 day'],
]);
}
return $data;
}
/**
* Test getting filter SQL for the current week excludes dates that don't match (outside week time range)
*
* @param int $startweekday
* @param string $timecreated Relative time suitable for passing to {@see strtotime}
*
* @dataProvider get_sql_filter_current_week_no_match_provider
*/
public function test_get_sql_filter_current_week_no_match(int $startweekday, string $timecreated): void {
global $DB;
$this->resetAfterTest();
set_config('calendar_startwday', $startweekday);
$usertimecreated = strtotime($timecreated);
$user = $this->getDataGenerator()->create_user(['timecreated' => $usertimecreated]);
$filter = new filter(
date::class,
'test',
new lang_string('yes'),
'testentity',
'timecreated'
);
[$select, $params] = date::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => date::DATE_CURRENT,
$filter->get_unique_identifier() . '_unit' => date::DATE_UNIT_WEEK,
]);
$matchingusers = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertNotContains($user->username, $matchingusers);
}
/**
* Data provider for {@see test_get_sql_filter_relative}
*
* @return array
*/
public function get_sql_filter_relative_provider(): array {
return [
'Before hour' => [date::DATE_BEFORE, 1, date::DATE_UNIT_HOUR, '-90 minute'],
'Before day' => [date::DATE_BEFORE, 1, date::DATE_UNIT_DAY, '-25 hour'],
'Before week' => [date::DATE_BEFORE, 1, date::DATE_UNIT_WEEK, '-10 day'],
'Before month' => [date::DATE_BEFORE, 1, date::DATE_UNIT_MONTH, '-7 week'],
'Before year' => [date::DATE_BEFORE, 1, date::DATE_UNIT_YEAR, '-15 month'],
'Before two hours' => [date::DATE_BEFORE, 2, date::DATE_UNIT_HOUR, '-150 minute'],
'Before two days' => [date::DATE_BEFORE, 2, date::DATE_UNIT_DAY, '-50 hour'],
'Before two weeks' => [date::DATE_BEFORE, 2, date::DATE_UNIT_WEEK, '-20 day'],
'Before two months' => [date::DATE_BEFORE, 2, date::DATE_UNIT_MONTH, '-15 week'],
'Before two years' => [date::DATE_BEFORE, 2, date::DATE_UNIT_YEAR, '-30 month'],
'After hour' => [date::DATE_AFTER, 1, date::DATE_UNIT_HOUR, '+90 minute'],
'After day' => [date::DATE_AFTER, 1, date::DATE_UNIT_DAY, '+25 hour'],
'After week' => [date::DATE_AFTER, 1, date::DATE_UNIT_WEEK, '+10 day'],
'After month' => [date::DATE_AFTER, 1, date::DATE_UNIT_MONTH, '+7 week'],
'After year' => [date::DATE_AFTER, 1, date::DATE_UNIT_YEAR, '+15 month'],
'After two hours' => [date::DATE_AFTER, 2, date::DATE_UNIT_HOUR, '+150 minute'],
'After two days' => [date::DATE_AFTER, 2, date::DATE_UNIT_DAY, '+50 hour'],
'After two weeks' => [date::DATE_AFTER, 2, date::DATE_UNIT_WEEK, '+20 day'],
'After two months' => [date::DATE_AFTER, 2, date::DATE_UNIT_MONTH, '+15 week'],
'After two years' => [date::DATE_AFTER, 2, date::DATE_UNIT_YEAR, '+30 month'],
'Last hour' => [date::DATE_LAST, 1, date::DATE_UNIT_HOUR, '-30 minute'],
'Last day' => [date::DATE_LAST, 1, date::DATE_UNIT_DAY, '-6 hour'],
'Last week' => [date::DATE_LAST, 1, date::DATE_UNIT_WEEK, '-3 day'],
'Last month' => [date::DATE_LAST, 1, date::DATE_UNIT_MONTH, '-3 week'],
'Last year' => [date::DATE_LAST, 1, date::DATE_UNIT_YEAR, '-6 month'],
'Last two hours' => [date::DATE_LAST, 2, date::DATE_UNIT_HOUR, '-90 minute'],
'Last two days' => [date::DATE_LAST, 2, date::DATE_UNIT_DAY, '-25 hour'],
'Last two weeks' => [date::DATE_LAST, 2, date::DATE_UNIT_WEEK, '-10 day'],
'Last two months' => [date::DATE_LAST, 2, date::DATE_UNIT_MONTH, '-7 week'],
'Last two years' => [date::DATE_LAST, 2, date::DATE_UNIT_YEAR, '-15 month'],
// Current week is tested separately.
'Current hour' => [date::DATE_CURRENT, null, date::DATE_UNIT_HOUR],
'Current day' => [date::DATE_CURRENT, null, date::DATE_UNIT_DAY],
'Current month' => [date::DATE_CURRENT, null, date::DATE_UNIT_MONTH],
'Current year' => [date::DATE_CURRENT, null, date::DATE_UNIT_YEAR],
'Next hour' => [date::DATE_NEXT, 1, date::DATE_UNIT_HOUR, '+30 minute'],
'Next day' => [date::DATE_NEXT, 1, date::DATE_UNIT_DAY, '+6 hour'],
'Next week' => [date::DATE_NEXT, 1, date::DATE_UNIT_WEEK, '+3 day'],
'Next month' => [date::DATE_NEXT, 1, date::DATE_UNIT_MONTH, '+3 week'],
'Next year' => [date::DATE_NEXT, 1, date::DATE_UNIT_YEAR, '+6 month'],
'Next two hours' => [date::DATE_NEXT, 2, date::DATE_UNIT_HOUR, '+90 minute'],
'Next two days' => [date::DATE_NEXT, 2, date::DATE_UNIT_DAY, '+25 hour'],
'Next two weeks' => [date::DATE_NEXT, 2, date::DATE_UNIT_WEEK, '+10 day'],
'Next two months' => [date::DATE_NEXT, 2, date::DATE_UNIT_MONTH, '+7 week'],
'Next two years' => [date::DATE_NEXT, 2, date::DATE_UNIT_YEAR, '+15 month'],
'In the past' => [date::DATE_PAST, null, null, '-3 hour'],
'In the future' => [date::DATE_FUTURE, null, null, '+3 hour'],
];
}
/**
* Unit tests for filtering relative dates
*
* @param int $operator
* @param int|null $unitvalue
* @param int|null $unit
* @param string|null $timecreated Relative time suitable for passing to {@see strtotime} (or null for current time)
*
* @dataProvider get_sql_filter_relative_provider
*/
public function test_get_sql_filter_relative(int $operator, ?int $unitvalue, ?int $unit, ?string $timecreated = null): void {
global $DB;
$this->resetAfterTest();
$usertimecreated = ($timecreated !== null ? strtotime($timecreated) : time());
$user = $this->getDataGenerator()->create_user(['timecreated' => $usertimecreated]);
$filter = new filter(
date::class,
'test',
new lang_string('yes'),
'testentity',
'timecreated'
);
[$select, $params] = date::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $unitvalue,
$filter->get_unique_identifier() . '_unit' => $unit,
]);
$matchingusers = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertContains($user->username, $matchingusers);
}
}
@@ -0,0 +1,128 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for duration report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\duration
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class duration_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public function get_sql_filter_provider(): array {
return [
'Any duration' =>
[duration::DURATION_ANY, true],
// Maximum operator.
'Maximum seconds non-match' =>
[duration::DURATION_MAXIMUM, false, HOURSECS, 1],
'Maximum seconds match' =>
[duration::DURATION_MAXIMUM, true, HOURSECS * 3, 1],
'Maximum minutes non-match' =>
[duration::DURATION_MAXIMUM, false, 60, MINSECS],
'Maximum minutes match' =>
[duration::DURATION_MAXIMUM, true, 150, MINSECS],
'Maximum hours non-match (float)' =>
[duration::DURATION_MAXIMUM, false, 0.5, HOURSECS],
'Maximum hours non-match' =>
[duration::DURATION_MAXIMUM, false, 1, HOURSECS],
'Maximum hours match (float)' =>
[duration::DURATION_MAXIMUM, true, 2.5, HOURSECS],
'Maximum hours match' =>
[duration::DURATION_MAXIMUM, true, 3, HOURSECS],
// Minimum operator.
'Minimum seconds match' =>
[duration::DURATION_MINIMUM, true, HOURSECS, 1],
'Minimum seconds non-match' =>
[duration::DURATION_MINIMUM, false, HOURSECS * 3, 1],
'Minimum minutes match' =>
[duration::DURATION_MINIMUM, true, 60, MINSECS],
'Minimum minutes non-match' =>
[duration::DURATION_MINIMUM, false, 150, MINSECS],
'Minimum hours match (float)' =>
[duration::DURATION_MINIMUM, true, 0.5, HOURSECS],
'Minimum hours match' =>
[duration::DURATION_MINIMUM, true, 1, HOURSECS],
'Minimum hours non-match (float)' =>
[duration::DURATION_MINIMUM, false, 2.5, HOURSECS],
'Minimum hours non-match' =>
[duration::DURATION_MINIMUM, false, 3, HOURSECS],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param bool $expectuser
* @param float $value
* @param int $unit
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(int $operator, bool $expectuser, float $value = 0, int $unit = MINSECS): void {
global $DB;
$this->resetAfterTest();
// We are going to enrol our student from now, with a duration of two hours (timeend is two hours later).
$timestart = time();
$timeend = $timestart + (HOURSECS * 2);
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestart, $timeend);
$filter = new filter(
duration::class,
'test',
new lang_string('yes'),
'testentity',
'timeend - timestart'
);
// Create instance of our filter, passing given values.
[$select, $params] = duration::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
$filter->get_unique_identifier() . '_unit' => $unit,
]);
$useridfield = $DB->get_field_select('user_enrolments', 'userid', $select, $params);
if ($expectuser) {
$this->assertEquals($useridfield, $user->id);
} else {
$this->assertFalse($useridfield);
}
}
}
@@ -0,0 +1,111 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use core\context\user;
use core_reportbuilder\local\report\filter;
use lang_string;
/**
* Unit tests for filesize report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\filesize
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filesize_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public static function get_sql_filter_provider(): array {
return [
[filesize::ANY_VALUE, true],
[filesize::LESS_THAN, false, 10, filesize::SIZE_UNIT_BYTE],
[filesize::LESS_THAN, false, 10, filesize::SIZE_UNIT_KILOBYTE],
[filesize::LESS_THAN, true, 10, filesize::SIZE_UNIT_MEGABYTE],
[filesize::LESS_THAN, true, 10, filesize::SIZE_UNIT_GIGABYTE],
[filesize::GREATER_THAN, true, 10, filesize::SIZE_UNIT_BYTE],
[filesize::GREATER_THAN, true, 10, filesize::SIZE_UNIT_KILOBYTE],
[filesize::GREATER_THAN, false, 10, filesize::SIZE_UNIT_MEGABYTE],
[filesize::GREATER_THAN, false, 10, filesize::SIZE_UNIT_GIGABYTE],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param bool $expected
* @param float $value
* @param int $unit
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(
int $operator,
bool $expected,
float $value = 1,
int $unit = filesize::SIZE_UNIT_BYTE,
): void {
global $DB, $USER;
$this->resetAfterTest();
$this->setAdminUser();
// Create a sample 2MB file.
$file = get_file_storage()->create_file_from_string([
'contextid' => user::instance($USER->id)->id,
'userid' => $USER->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => file_get_unused_draft_itemid(),
'filepath' => '/',
'filename' => 'Hello.txt',
], str_repeat('A', 2 * filesize::SIZE_UNIT_MEGABYTE));
$filter = new filter(
filesize::class,
'test',
new lang_string('yes'),
'testentity',
'filesize'
);
// Create instance of our filter, passing given values.
[$select, $params] = filesize::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value1' => $value,
$filter->get_unique_identifier() . '_unit' => $unit,
]);
$fileids = $DB->get_fieldset_select('files', 'id', $select, $params);
if ($expected) {
$this->assertContains((string) $file->get_id(), $fileids);
} else {
$this->assertNotContains((string) $file->get_id(), $fileids);
}
}
}
@@ -0,0 +1,148 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for number report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\number
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class number_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter_simple}
*
* @return array[]
*/
public function get_sql_filter_simple_provider(): array {
return [
[number::ANY_VALUE, null, null, true],
[number::IS_NOT_EMPTY, null, null, true],
[number::IS_EMPTY, null, null, false],
[number::LESS_THAN, 1, null, false],
[number::LESS_THAN, 123, null, false],
[number::LESS_THAN, 124, null, true],
[number::GREATER_THAN, 1, null, true],
[number::GREATER_THAN, 123, null, false],
[number::GREATER_THAN, 124, null, false],
[number::EQUAL_TO, 123, null, true],
[number::EQUAL_TO, 124, null, false],
[number::EQUAL_OR_LESS_THAN, 124, null, true],
[number::EQUAL_OR_LESS_THAN, 123, null, true],
[number::EQUAL_OR_LESS_THAN, 122, null, false],
[number::EQUAL_OR_GREATER_THAN, 122, null, true],
[number::EQUAL_OR_GREATER_THAN, 123, null, true],
[number::EQUAL_OR_GREATER_THAN, 124, null, false],
[number::RANGE, 122, 124, true],
[number::RANGE, 124, 125, false],
[number::RANGE, 122, 123, true],
[number::RANGE, 123, 124, true],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param int|null $value1
* @param int|null $value2
* @param bool $expectmatch
*
* @dataProvider get_sql_filter_simple_provider
*/
public function test_get_sql_filter_simple(int $operator, ?int $value1, ?int $value2, bool $expectmatch): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course([
'timecreated' => 123,
]);
$filter = new filter(
number::class,
'test',
new lang_string('course'),
'testentity',
'timecreated'
);
// Create instance of our filter, passing given operator.
[$select, $params] = number::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_value1' => $value1,
$filter->get_unique_identifier() . '_value2' => $value2,
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
if ($expectmatch) {
$this->assertContains($course->fullname, $fullnames);
} else {
$this->assertNotContains($course->fullname, $fullnames);
}
}
/**
* Data provider for {@see test_get_sql_filter_invalid}
*
* @return array[]
*/
public function get_sql_filter_invalid_provider(): array {
return [
[number::LESS_THAN],
[number::GREATER_THAN],
[number::EQUAL_TO],
[number::EQUAL_OR_LESS_THAN],
[number::EQUAL_OR_GREATER_THAN],
[number::RANGE],
];
}
/**
* Test getting filter SQL for operators that require values
*
* @param int $operator
*
* @dataProvider get_sql_filter_invalid_provider
*/
public function test_get_sql_filter_invalid(int $operator): void {
$filter = new filter(
number::class,
'test',
new lang_string('course'),
'testentity',
'timecreated'
);
[$select, $params] = number::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$this->assertEquals('', $select);
$this->assertEquals([], $params);
}
}
@@ -0,0 +1,98 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for select report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\select
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class select_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter_simple}
*
* @return array
*/
public function get_sql_filter_simple_provider(): array {
return [
[select::ANY_VALUE, null, true],
[select::EQUAL_TO, 'starwars', true],
[select::EQUAL_TO, 'mandalorian', false],
[select::NOT_EQUAL_TO, 'starwars', false],
[select::NOT_EQUAL_TO, 'mandalorian', true],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param string|null $value
* @param bool $expectmatch
*
* @dataProvider get_sql_filter_simple_provider
*/
public function test_get_sql_filter_simple(int $operator, ?string $value, bool $expectmatch): void {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course([
'fullname' => "May the course be with you",
'shortname' => 'starwars',
]);
$course2 = $this->getDataGenerator()->create_course([
'fullname' => "This is the course",
'shortname' => 'mandalorian',
]);
$filter = (new filter(
select::class,
'test',
new lang_string('course'),
'testentity',
'shortname'
))->set_options([
$course1->shortname => $course1->fullname,
$course2->shortname => $course2->fullname,
]);
// Create instance of our filter, passing given operator.
[$select, $params] = select::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
if ($expectmatch) {
$this->assertContains($course1->fullname, $fullnames);
} else {
$this->assertNotContains($course1->fullname, $fullnames);
}
}
}
@@ -0,0 +1,181 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder_generator;
use core_reportbuilder\local\report\filter;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for tags report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\tags
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tags_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array[]
*/
public static function get_sql_filter_provider(): array {
return [
'Any value' => [tags::ANY_VALUE, null, ['course01', 'course01', 'course02', 'course03']],
'Not empty' => [tags::NOT_EMPTY, null, ['course01', 'course01', 'course02']],
'Empty' => [tags::EMPTY, null, ['course03']],
'Equal to unselected' => [tags::EQUAL_TO, null, ['course01', 'course01', 'course02', 'course03']],
'Equal to selected tag' => [tags::EQUAL_TO, 'cat', ['course01']],
'Not equal to unselected' => [tags::NOT_EQUAL_TO, null, ['course01', 'course01', 'course02', 'course03']],
'Not equal to selected tag' => [tags::NOT_EQUAL_TO, 'fish', ['course01', 'course01', 'course03']],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param string|null $tagname
* @param array $expectedcoursenames
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(int $operator, ?string $tagname, array $expectedcoursenames): void {
global $DB;
$this->resetAfterTest();
$this->getDataGenerator()->create_course(['fullname' => 'course01', 'tags' => ['cat', 'dog']]);
$this->getDataGenerator()->create_course(['fullname' => 'course02', 'tags' => ['fish']]);
$this->getDataGenerator()->create_course(['fullname' => 'course03']);
$filter = (new filter(
tags::class,
'tags',
new lang_string('tags'),
'testentity',
't.id'
));
// Create instance of our filter, passing ID of the tag if specified.
if ($tagname !== null) {
$tagid = $DB->get_field('tag', 'id', ['name' => $tagname], MUST_EXIST);
$value = [$tagid];
} else {
$value = null;
}
[$select, $params] = tags::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
]);
$sql = 'SELECT c.fullname
FROM {course} c
LEFT JOIN {tag_instance} ti ON ti.itemid = c.id
LEFT JOIN {tag} t ON t.id = ti.tagid
WHERE c.id != ' . SITEID;
if ($select) {
$sql .= " AND {$select}";
}
$courses = $DB->get_fieldset_sql($sql, $params);
$this->assertEqualsCanonicalizing($expectedcoursenames, $courses);
}
/**
* Data provider for {@see test_get_sql_filter_component}
*
* @return array[]
*/
public static function get_sql_filter_component_provider(): array {
return [
'Any value' => [tags::ANY_VALUE, null, ['report01', 'report02']],
'Not empty' => [tags::NOT_EMPTY, null, ['report01']],
'Empty' => [tags::EMPTY, null, ['report02']],
'Equal to unselected' => [tags::EQUAL_TO, null, ['report01', 'report02']],
'Equal to selected tag' => [tags::EQUAL_TO, 'fish', ['report01']],
'Equal to selected tag (different component)' => [tags::EQUAL_TO, 'cat', []],
'Not equal to unselected' => [tags::NOT_EQUAL_TO, null, ['report01', 'report02']],
'Not equal to selected tag' => [tags::NOT_EQUAL_TO, 'fish', ['report02']],
'Not Equal to selected tag (different component)' => [tags::NOT_EQUAL_TO, 'cat', ['report01', 'report02']],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param string|null $tagname
* @param array $expectedreportnames
*
* @dataProvider get_sql_filter_component_provider
*/
public function test_get_sql_filter_component(int $operator, ?string $tagname, array $expectedreportnames): void {
global $DB;
$this->resetAfterTest();
// Create a course with tags, we shouldn't ever get this data back when specifying another component.
$this->getDataGenerator()->create_course(['tags' => ['cat', 'dog']]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$generator->create_report(['name' => 'report01', 'source' => users::class, 'tags' => ['fish']]);
$generator->create_report(['name' => 'report02', 'source' => users::class]);
$filter = (new filter(
tags::class,
'tags',
new lang_string('tags'),
'testentity',
'r.id'
))->set_options([
'component' => 'core_reportbuilder',
'itemtype' => 'reportbuilder_report',
]);
// Create instance of our filter, passing ID of the tag if specified.
if ($tagname !== null) {
$tagid = $DB->get_field('tag', 'id', ['name' => $tagname], MUST_EXIST);
$value = [$tagid];
} else {
$value = null;
}
[$select, $params] = tags::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
]);
$sql = 'SELECT r.name FROM {reportbuilder_report} r';
if ($select) {
$sql .= " WHERE {$select}";
}
$reports = $DB->get_fieldset_sql($sql, $params);
$this->assertEqualsCanonicalizing($expectedreportnames, $reports);
}
}
@@ -0,0 +1,160 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for text report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\text
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter_simple}
*
* @return array
*/
public function get_sql_filter_simple_provider(): array {
return [
[text::ANY_VALUE, null, true],
[text::CONTAINS, 'looking', true],
[text::CONTAINS, 'sky', false],
[text::DOES_NOT_CONTAIN, 'sky', true],
[text::DOES_NOT_CONTAIN, 'looking', false],
[text::IS_EQUAL_TO, "Hello, is it me you're looking for?", true],
[text::IS_EQUAL_TO, 'I can see it in your eyes', false],
[text::IS_NOT_EQUAL_TO, "Hello, is it me you're looking for?", false],
[text::IS_NOT_EQUAL_TO, 'I can see it in your eyes', true],
[text::STARTS_WITH, 'Hello', true],
[text::STARTS_WITH, 'sunlight', false],
[text::ENDS_WITH, 'looking for?', true],
[text::ENDS_WITH, 'your heart', false],
// Ensure whitespace is trimmed.
[text::CONTAINS, ' looking for ', true],
[text::IS_EQUAL_TO, ' Hello, is it me you\'re looking for? ', true],
[text::STARTS_WITH, ' Hello, is it me ', true],
[text::ENDS_WITH, ' you\'re looking for? ', true],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param string|null $value
* @param bool $expectmatch
*
* @dataProvider get_sql_filter_simple_provider
*/
public function test_get_sql_filter_simple(int $operator, ?string $value, bool $expectmatch): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course([
'fullname' => "Hello, is it me you're looking for?",
]);
$filter = new filter(
text::class,
'test',
new lang_string('course'),
'testentity',
'fullname'
);
// Create instance of our filter, passing given operator.
[$select, $params] = text::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
]);
$fullnames = $DB->get_fieldset_select('course', 'fullname', $select, $params);
if ($expectmatch) {
$this->assertContains($course->fullname, $fullnames);
} else {
$this->assertNotContains($course->fullname, $fullnames);
}
}
/**
* Data provider for {@see test_get_sql_filter_empty}
*
* @return array
*/
public function get_sql_filter_empty_provider(): array {
return [
[text::IS_EMPTY, null, true],
[text::IS_EMPTY, '', true],
[text::IS_EMPTY, 'hola', false],
[text::IS_NOT_EMPTY, null, false],
[text::IS_NOT_EMPTY, '', false],
[text::IS_NOT_EMPTY, 'hola', true],
];
}
/**
* Test getting filter SQL using the {@see text::IS_EMPTY} and {@see text::IS_NOT_EMPTY} operators
*
* @param int $operator
* @param string|null $profilefieldvalue
* @param bool $expectmatch
*
* @dataProvider get_sql_filter_empty_provider
*/
public function test_get_sql_filter_empty(int $operator, ?string $profilefieldvalue, bool $expectmatch): void {
global $DB;
$this->resetAfterTest();
// We are using the user.moodlenetprofile field because it is nullable.
$user = $this->getDataGenerator()->create_user([
'moodlenetprofile' => $profilefieldvalue,
]);
$filter = new filter(
text::class,
'test',
new lang_string('user'),
'testentity',
'moodlenetprofile'
);
// Create instance of our filter, passing given operator.
[$select, $params] = text::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
if ($expectmatch) {
$this->assertContains($user->username, $usernames);
} else {
$this->assertNotContains($user->username, $usernames);
}
}
}
@@ -0,0 +1,111 @@
<?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_reportbuilder\local\filters;
use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;
/**
* Unit tests for user report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\user
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_test extends advanced_testcase {
/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public function get_sql_filter_simple(): array {
return [
[user::USER_ANY, ['admin', 'guest', 'user01', 'user02']],
[user::USER_CURRENT, ['user01']],
];
}
/**
* Test getting filter SQL
*
* @param int $operator
* @param string[] $expectedusernames
*
* @dataProvider get_sql_filter_simple
*/
public function test_get_sql_filter(int $operator, array $expectedusernames): void {
global $DB;
$this->resetAfterTest();
$user01 = $this->getDataGenerator()->create_user(['username' => 'user01']);
$user02 = $this->getDataGenerator()->create_user(['username' => 'user02']);
$this->setUser($user01);
$filter = new filter(
user::class,
'test',
new lang_string('yes'),
'testentity',
'id'
);
// Create instance of our filter, passing given operator.
[$select, $params] = user::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
]);
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertEqualsCanonicalizing($expectedusernames, $usernames);
}
/**
* Test getting filter SQL using specific user selection operator/value
*/
public function test_get_sql_filter_select_user(): void {
global $DB;
$this->resetAfterTest();
$user01 = $this->getDataGenerator()->create_user(['username' => 'user01']);
$user02 = $this->getDataGenerator()->create_user(['username' => 'user02']);
$filter = new filter(
user::class,
'test',
new lang_string('yes'),
'testentity',
'id'
);
// Create instance of our filter, passing given operator/value matching second user.
[$select, $params] = user::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => user::USER_SELECT,
$filter->get_unique_identifier() . '_value' => [$user02->id],
]);
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
$this->assertEquals([$user02->username], $usernames);
}
}
@@ -0,0 +1,365 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
use context_system;
use core_reportbuilder_generator;
use core_reportbuilder\reportbuilder\audience\manual;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for audience helper
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\audience
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience_test extends advanced_testcase {
/**
* Test reports list is empty for a normal user without any audience records configured
*/
public function test_reports_list_no_access(): void {
$this->resetAfterTest();
$reports = audience::user_reports_list();
$this->assertEmpty($reports);
}
/**
* Test get_base_records()
*/
public function test_get_base_records(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Report with no audiences.
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$baserecords = audience::get_base_records($report->get('id'));
$this->assertEmpty($baserecords);
// Create a couple of manual audience types.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$audience1 = $generator->create_audience([
'reportid' => $report->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user1->id, $user2->id]],
]);
$user3 = $this->getDataGenerator()->create_user();
$audience2 = $generator->create_audience([
'reportid' => $report->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user3->id]],
]);
$baserecords = audience::get_base_records($report->get('id'));
$this->assertCount(2, $baserecords);
$this->assertContainsOnlyInstancesOf(manual::class, $baserecords);
// Set invalid classname of first audience, should be excluded in subsequent request.
$audience1->get_persistent()->set('classname', '\invalid')->save();
$baserecords = audience::get_base_records($report->get('id'));
$this->assertCount(1, $baserecords);
$baserecord = reset($baserecords);
$this->assertInstanceOf(manual::class, $baserecord);
$this->assertEquals($audience2->get_persistent()->get('id'), $baserecord->get_persistent()->get('id'));
}
/**
* Test get_allowed_reports()
*/
public function test_get_allowed_reports(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
self::setUser($user1);
// No reports.
$reports = audience::get_allowed_reports();
$this->assertEmpty($reports);
$report1 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$report2 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$report3 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Reports with no audiences set.
$reports = audience::get_allowed_reports();
$this->assertEmpty($reports);
$generator->create_audience([
'reportid' => $report1->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user1->id, $user2->id]],
]);
$generator->create_audience([
'reportid' => $report2->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user2->id]],
]);
$generator->create_audience([
'reportid' => $report3->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user1->id]],
]);
// Purge cache, to ensure allowed reports are re-calculated.
audience::purge_caches();
$reports = audience::get_allowed_reports();
$this->assertEqualsCanonicalizing([$report1->get('id'), $report3->get('id')], $reports);
// User2 can access report1 and report2.
$reports = audience::get_allowed_reports((int) $user2->id);
$this->assertEqualsCanonicalizing([$report1->get('id'), $report2->get('id')], $reports);
// Purge cache, to ensure allowed reports are re-calculated.
audience::purge_caches();
// Now delete one of our users, ensure they no longer have any allowed reports.
delete_user($user2);
$reports = audience::get_allowed_reports((int) $user2->id);
$this->assertEmpty($reports);
}
/**
* Test user_reports_list()
*/
public function test_user_reports_list(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
self::setUser($user1);
$reports = audience::user_reports_list();
$this->assertEmpty($reports);
$report1 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$report2 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$report3 = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$generator->create_audience([
'reportid' => $report1->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user1->id, $user2->id]],
]);
$generator->create_audience([
'reportid' => $report2->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user2->id]],
]);
$generator->create_audience([
'reportid' => $report3->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$user1->id]],
]);
// Purge cache, to ensure allowed reports are re-calculated.
audience::purge_caches();
// User1 can access report1 and report3.
$reports = audience::user_reports_list();
$this->assertEqualsCanonicalizing([$report1->get('id'), $report3->get('id')], $reports);
// User2 can access report1 and report2.
$reports = audience::user_reports_list((int) $user2->id);
$this->assertEqualsCanonicalizing([$report1->get('id'), $report2->get('id')], $reports);
// User3 can not access any report.
$reports = audience::user_reports_list((int) $user3->id);
$this->assertEmpty($reports);
}
/**
* Test retrieving full list of reports that user can access
*/
public function test_user_reports_list_access_sql(): void {
global $DB;
$this->resetAfterTest();
$userone = $this->getDataGenerator()->create_user();
$usertwo = $this->getDataGenerator()->create_user();
$userthree = $this->getDataGenerator()->create_user();
$userfour = $this->getDataGenerator()->create_user();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Manager role gives users one and two capability to create own reports.
$managerrole = $DB->get_field('role', 'id', ['shortname' => 'manager']);
role_assign($managerrole, $userone->id, context_system::instance());
role_assign($managerrole, $usertwo->id, context_system::instance());
// Admin creates a report, no audience.
$this->setAdminUser();
$useradminreport = $generator->create_report(['name' => 'Admin report', 'source' => users::class]);
// User one creates a report, adds users two and three to audience.
$this->setUser($userone);
$useronereport = $generator->create_report(['name' => 'User one report', 'source' => users::class]);
$generator->create_audience(['reportid' => $useronereport->get('id'), 'classname' => manual::class, 'configdata' => [
'users' => [$usertwo->id, $userthree->id],
]]);
// User two creates a report, no audience.
$this->setUser($usertwo);
$usertworeport = $generator->create_report(['name' => 'User two report', 'source' => users::class]);
// User one sees only the report they created.
[$where, $params] = audience::user_reports_list_access_sql('r', (int) $userone->id);
$reports = $DB->get_fieldset_sql("SELECT r.id FROM {reportbuilder_report} r WHERE {$where}", $params);
$this->assertEquals([$useronereport->get('id')], $reports);
// User two see the report they created and the one they are in the audience of.
[$where, $params] = audience::user_reports_list_access_sql('r', (int) $usertwo->id);
$reports = $DB->get_fieldset_sql("SELECT r.id FROM {reportbuilder_report} r WHERE {$where}", $params);
$this->assertEqualsCanonicalizing([$useronereport->get('id'), $usertworeport->get('id')], $reports);
// User three sees the report they are in the audience of.
[$where, $params] = audience::user_reports_list_access_sql('r', (int) $userthree->id);
$reports = $DB->get_fieldset_sql("SELECT r.id FROM {reportbuilder_report} r WHERE {$where}", $params);
$this->assertEquals([$useronereport->get('id')], $reports);
// User four sees no reports.
[$where, $params] = audience::user_reports_list_access_sql('r', (int) $userfour->id);
$reports = $DB->get_fieldset_sql("SELECT r.id FROM {reportbuilder_report} r WHERE {$where}", $params);
$this->assertEmpty($reports);
}
/**
* Data provider for {@see test_user_reports_list_access_sql_with_capability}
*
* @return array[]
*/
public static function user_reports_list_access_sql_with_capability_provider(): array {
return [
['moodle/reportbuilder:editall'],
['moodle/reportbuilder:viewall'],
];
}
/**
* Test retrieving list of reports that user can access observes capability to view all reports
*
* @param string $capability
*
* @dataProvider user_reports_list_access_sql_with_capability_provider
*/
public function test_user_reports_list_access_sql_with_capability(string $capability): void {
global $DB;
$this->resetAfterTest();
// Admin creates a report, no audience.
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Admin report', 'source' => users::class]);
// Switch to new user, assign capability.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$userrole = $DB->get_field('role', 'id', ['shortname' => 'user']);
assign_capability($capability, CAP_ALLOW, $userrole, context_system::instance());
[$where, $params] = audience::user_reports_list_access_sql('r');
$reports = $DB->get_fieldset_sql("SELECT r.id FROM {reportbuilder_report} r WHERE {$where}", $params);
$this->assertEquals([$report->get('id')], $reports);
}
/**
* Test getting list of audiences in use within schedules for a report
*/
public function test_get_audiences_for_report_schedules(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audienceone = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$audiencetwo = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$audiencethree = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
// The first schedule contains audience one and two.
$generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'Schedule one', 'audiences' =>
json_encode([$audienceone->get_persistent()->get('id'), $audiencetwo->get_persistent()->get('id')])
]);
// Second schedule contains only audience one.
$generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'Schedule two', 'audiences' =>
json_encode([$audienceone->get_persistent()->get('id')])
]);
// The first two audiences should be returned, the third omitted.
$audiences = audience::get_audiences_for_report_schedules($report->get('id'));
$this->assertEqualsCanonicalizing([
$audienceone->get_persistent()->get('id'),
$audiencetwo->get_persistent()->get('id'),
], $audiences);
}
}
@@ -0,0 +1,367 @@
<?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_reportbuilder\local\helpers;
use core_customfield_generator;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\entities\course;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use core_course\reportbuilder\datasource\{categories, courses};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for custom fields helper
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\custom_fields
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_fields_test extends core_reportbuilder_testcase {
/**
* Generate custom fields, one of each type
*
* @return custom_fields
*/
private function generate_customfields(): custom_fields {
/** @var core_customfield_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
$category = $generator->create_category([
'component' => 'core_course',
'area' => 'course',
'itemid' => 0,
'contextid' => \context_system::instance()->id
]);
$generator->create_field(
['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Text', 'shortname' => 'text']);
$generator->create_field(
['categoryid' => $category->get('id'), 'type' => 'textarea', 'name' => 'Textarea', 'shortname' => 'textarea']);
$generator->create_field(
['categoryid' => $category->get('id'), 'type' => 'checkbox', 'name' => 'Checkbox', 'shortname' => 'checkbox']);
$generator->create_field(
['categoryid' => $category->get('id'), 'type' => 'date', 'name' => 'Date', 'shortname' => 'date']);
$generator->create_field(
['categoryid' => $category->get('id'), 'type' => 'select', 'name' => 'Select', 'shortname' => 'select',
'configdata' => ['options' => "Cat\nDog", 'defaultvalue' => 'Cat']]);
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
// Create an instance of the customfields helper.
return new custom_fields($coursealias . '.id', $courseentity->get_entity_name(),
'core_course', 'course');
}
/**
* Test for get_columns
*/
public function test_get_columns(): void {
$this->resetAfterTest();
$customfields = $this->generate_customfields();
$columns = $customfields->get_columns();
$this->assertCount(5, $columns);
$this->assertContainsOnlyInstancesOf(column::class, $columns);
// Column titles.
$this->assertEquals(
['Text', 'Textarea', 'Checkbox', 'Date', 'Select'],
array_map(fn(column $column) => $column->get_title(), $columns)
);
// Column types.
$this->assertEquals(
[column::TYPE_TEXT, column::TYPE_LONGTEXT, column::TYPE_BOOLEAN, column::TYPE_TIMESTAMP, column::TYPE_TEXT],
array_map(fn(column $column) => $column->get_type(), $columns)
);
// Column sortable.
$this->assertEquals(
[true, false, true, true, true],
array_map(fn(column $column) => $column->get_is_sortable(), $columns)
);
}
/**
* Test for add_join
*/
public function test_add_join(): void {
$this->resetAfterTest();
$customfields = $this->generate_customfields();
// By default, we always join on the customfield data table.
$columns = $customfields->get_columns();
$joins = $columns[0]->get_joins();
$this->assertCount(1, $joins);
$this->assertStringStartsWith('LEFT JOIN {customfield_data}', $joins[0]);
// Add additional join.
$customfields->add_join('JOIN {test} t ON t.id = id');
$columns = $customfields->get_columns();
$joins = $columns[0]->get_joins();
$this->assertCount(2, $joins);
$this->assertEquals('JOIN {test} t ON t.id = id', $joins[0]);
$this->assertStringStartsWith('LEFT JOIN {customfield_data}', $joins[1]);
}
/**
* Test for add_joins
*/
public function test_add_joins(): void {
$this->resetAfterTest();
$customfields = $this->generate_customfields();
// Add additional joins.
$customfields->add_joins(['JOIN {test} t ON t.id = id', 'JOIN {test2} t2 ON t2.id = id']);
$columns = $customfields->get_columns();
$joins = $columns[0]->get_joins();
$this->assertCount(3, $joins);
$this->assertEquals('JOIN {test} t ON t.id = id', $joins[0]);
$this->assertEquals('JOIN {test2} t2 ON t2.id = id', $joins[1]);
$this->assertStringStartsWith('LEFT JOIN {customfield_data}', $joins[2]);
}
/**
* Test for get_filters
*/
public function test_get_filters(): void {
$this->resetAfterTest();
$customfields = $this->generate_customfields();
$filters = $customfields->get_filters();
$this->assertCount(5, $filters);
$this->assertContainsOnlyInstancesOf(filter::class, $filters);
// Filter titles.
$this->assertEquals(
['Text', 'Textarea', 'Checkbox', 'Date', 'Select'],
array_map(fn(filter $filter) => $filter->get_header(), $filters)
);
}
/**
* Test that adding custom field columns to a report returns expected values
*/
public function test_custom_report_content(): void {
$this->resetAfterTest();
$this->generate_customfields();
$course = $this->getDataGenerator()->create_course(['customfields' => [
['shortname' => 'text', 'value' => 'Hello'],
['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
['shortname' => 'checkbox', 'value' => true],
['shortname' => 'date', 'value' => 1669852800],
['shortname' => 'select', 'value' => 2],
]]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Courses', 'source' => courses::class, 'default' => 0]);
// Add custom field columns to the report.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_text']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_textarea']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_checkbox']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_date']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_select']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
$course->fullname,
'Hello',
'<div class="text_to_html">Goodbye</div>',
'Yes',
userdate(1669852800),
'Dog'
], array_values($content[0]));
}
/**
* Test that adding custom field columns to report returns expected default values for fields
*/
public function test_custom_report_content_column_defaults(): void {
$this->resetAfterTest();
$this->generate_customfields();
$category = $this->getDataGenerator()->create_category(['name' => 'Zebras']);
$course = $this->getDataGenerator()->create_course(['category' => $category->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Categories', 'source' => categories::class, 'default' => 0]);
// Add custom field columns to the report.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:name',
'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_select']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['Category 1', '', ''],
[$category->name, $course->fullname, 'Cat'],
], array_map('array_values', $content));
}
/**
* Data provider for {@see test_custom_report_filter}
*
* @return array[]
*/
public function custom_report_filter_provider(): array {
return [
'Filter by text custom field' => ['course:customfield_text', [
'course:customfield_text_operator' => text::IS_EQUAL_TO,
'course:customfield_text_value' => 'Hello',
], true],
'Filter by text custom field (no match)' => ['course:customfield_text', [
'course:customfield_text_operator' => text::IS_EQUAL_TO,
'course:customfield_text_value' => 'Goodbye',
], false],
'Filter by textarea custom field' => ['course:customfield_textarea', [
'course:customfield_textarea_operator' => text::IS_EQUAL_TO,
'course:customfield_textarea_value' => 'Goodbye',
], true],
'Filter by textarea custom field (no match)' => ['course:customfield_textarea', [
'course:customfield_textarea_operator' => text::IS_EQUAL_TO,
'course:customfield_textarea_value' => 'Hello',
], false],
'Filter by checkbox custom field' => ['course:customfield_checkbox', [
'course:customfield_checkbox_operator' => boolean_select::CHECKED,
], true],
'Filter by checkbox custom field (no match)' => ['course:customfield_checkbox', [
'course:customfield_checkbox_operator' => boolean_select::NOT_CHECKED,
], false],
'Filter by date custom field' => ['course:customfield_date', [
'course:customfield_date_operator' => date::DATE_RANGE,
'course:customfield_date_from' => 1622502000,
], true],
'Filter by date custom field (no match)' => ['course:customfield_date', [
'course:customfield_date_operator' => date::DATE_RANGE,
'course:customfield_date_to' => 1622502000,
], false],
'Filter by select custom field' => ['course:customfield_select', [
'course:customfield_select_operator' => select::EQUAL_TO,
'course:customfield_select_value' => 2,
], true],
'Filter by select custom field (no match)' => ['course:customfield_select', [
'course:customfield_select_operator' => select::EQUAL_TO,
'course:customfield_select_value' => 1,
], false],
];
}
/**
* Test filtering report by custom fields
*
* @param string $filtername
* @param array $filtervalues
* @param bool $expectmatch
*
* @dataProvider custom_report_filter_provider
*/
public function test_custom_report_filter(string $filtername, array $filtervalues, bool $expectmatch): void {
$this->resetAfterTest();
$this->generate_customfields();
$course = $this->getDataGenerator()->create_course(['customfields' => [
['shortname' => 'text', 'value' => 'Hello'],
['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
['shortname' => 'checkbox', 'value' => true],
['shortname' => 'date', 'value' => 1669852800],
['shortname' => 'select', 'value' => 2],
]]);
/** @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' => 'Users', 'source' => courses::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
// 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->assertCount(1, $content);
$this->assertEquals($course->fullname, reset($content[0]));
} else {
$this->assertEmpty($content);
}
}
/**
* Stress test course datasource using custom fields
*
* 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();
$this->generate_customfields();
$course = $this->getDataGenerator()->create_course(['customfields' => [
['shortname' => 'text', 'value' => 'Hello'],
['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
['shortname' => 'checkbox', 'value' => true],
['shortname' => 'date', 'value' => 1669852800],
['shortname' => 'select', 'value' => 2],
]]);
$this->datasource_stress_test_columns(courses::class);
$this->datasource_stress_test_columns_aggregation(courses::class);
$this->datasource_stress_test_conditions(courses::class, 'course:idnumber');
}
}
@@ -0,0 +1,222 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
use coding_exception;
use core_user;
/**
* Unit tests for the database helper class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\database
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class database_test extends advanced_testcase {
/**
* Test generating alias
*/
public function test_generate_alias(): void {
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', database::generate_alias());
// Specify a suffix.
$this->assertMatchesRegularExpression('/^rbalias(\d+)_$/', database::generate_alias('_'));
}
/**
* Test generating multiple aliases
*/
public function test_generate_aliases(): void {
$aliases = database::generate_aliases(3);
$this->assertCount(3, $aliases);
[$aliasone, $aliastwo, $aliasthree] = $aliases;
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $aliasone);
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $aliastwo);
$this->assertMatchesRegularExpression('/^rbalias(\d+)$/', $aliasthree);
// Ensure they are different.
$this->assertNotEquals($aliasone, $aliastwo);
$this->assertNotEquals($aliasone, $aliasthree);
$this->assertNotEquals($aliastwo, $aliasthree);
// Specify a suffix.
[$aliasfour, $aliasfive] = database::generate_aliases(2, '_');
$this->assertNotEquals($aliasfour, $aliasfive);
$this->assertMatchesRegularExpression('/^rbalias(\d+)_$/', $aliasfour);
$this->assertMatchesRegularExpression('/^rbalias(\d+)_$/', $aliasfive);
}
/**
* Test generating parameter name
*/
public function test_generate_param_name(): void {
$this->assertMatchesRegularExpression('/^rbparam(\d+)$/', database::generate_param_name());
// Specify a suffix.
$this->assertMatchesRegularExpression('/^rbparam(\d+)_$/', database::generate_param_name('_'));
}
/**
* Test generating multiple parameter names
*/
public function test_generate_param_names(): void {
$params = database::generate_param_names(3);
$this->assertCount(3, $params);
[$paramone, $paramtwo, $paramthree] = $params;
$this->assertMatchesRegularExpression('/^rbparam(\d+)$/', $paramone);
$this->assertMatchesRegularExpression('/^rbparam(\d+)$/', $paramtwo);
$this->assertMatchesRegularExpression('/^rbparam(\d+)$/', $paramthree);
// Ensure they are different.
$this->assertNotEquals($paramone, $paramtwo);
$this->assertNotEquals($paramone, $paramthree);
$this->assertNotEquals($paramtwo, $paramthree);
// Specify a suffix.
[$paramfour, $paramfive] = database::generate_param_names(2, '_');
$this->assertNotEquals($paramfour, $paramfive);
$this->assertMatchesRegularExpression('/^rbparam(\d+)_$/', $paramfour);
$this->assertMatchesRegularExpression('/^rbparam(\d+)_$/', $paramfive);
}
/**
* Test parameter validation
*/
public function test_validate_params(): void {
[$paramone, $paramtwo] = database::generate_param_names(2);
$params = [
$paramone => 1,
$paramtwo => 2,
];
$this->assertTrue(database::validate_params($params));
}
/**
* Test parameter validation for invalid parameters
*/
public function test_validate_params_invalid(): void {
$params = [
database::generate_param_name() => 1,
'invalidfoo' => 2,
'invalidbar' => 4,
];
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Invalid parameter names (invalidfoo, invalidbar)');
database::validate_params($params);
}
/**
* Generate aliases and parameters and confirm they can be used within a query
*/
public function test_generated_data_in_query(): void {
global $DB;
// Unique aliases.
[
$usertablealias,
$userfieldalias,
] = database::generate_aliases(2);
// Unique parameters.
[
$paramuserid,
$paramuserdeleted,
] = database::generate_param_names(2);
// Simple query to retrieve the admin user.
$sql = "SELECT {$usertablealias}.id AS {$userfieldalias}
FROM {user} {$usertablealias}
WHERE {$usertablealias}.id = :{$paramuserid}
AND {$usertablealias}.deleted = :{$paramuserdeleted}";
$admin = core_user::get_user_by_username('admin');
$params = [
$paramuserid => $admin->id,
$paramuserdeleted => 0,
];
$record = $DB->get_record_sql($sql, $params);
$this->assertEquals($admin->id, $record->{$userfieldalias});
}
/**
* Test replacement of parameter names within SQL statements
*/
public function test_sql_replace_parameter_names(): void {
global $DB;
// Predefine parameter names, to ensure they don't overwrite each other.
[$param0, $param1, $param10] = ['rbparam0', 'rbparam1', 'rbparam10'];
$sql = "SELECT :{$param0} AS field0, :{$param1} AS field1, :{$param10} AS field10" . $DB->sql_null_from_clause();
$sql = database::sql_replace_parameter_names(
$sql,
[$param0, $param1, $param10],
fn(string $param) => "prefix_{$param}",
);
$record = $DB->get_record_sql($sql, [
"prefix_{$param0}" => 'Zero',
"prefix_{$param1}" => 'One',
"prefix_{$param10}" => 'Ten',
]);
$this->assertEquals((object) [
'field0' => 'Zero',
'field1' => 'One',
'field10' => 'Ten',
], $record);
}
/**
* Test replacement of parameter names within query, returning both modified query and parameters
*/
public function test_sql_replace_parameters(): void {
global $DB;
// Predefine parameter names, to ensure they don't overwrite each other.
[$param0, $param1, $param10] = ['rbparam0', 'rbparam1', 'rbparam10'];
$sql = "SELECT :{$param0} AS field0, :{$param1} AS field1, :{$param10} AS field10" . $DB->sql_null_from_clause();
[$sql, $params] = database::sql_replace_parameters(
$sql,
[$param0 => 'Zero', $param1 => 'One', $param10 => 'Ten'],
fn(string $param) => "prefix_{$param}",
);
$record = $DB->get_record_sql($sql, $params);
$this->assertEquals((object) [
'field0' => 'Zero',
'field1' => 'One',
'field10' => 'Ten',
], $record);
}
}
@@ -0,0 +1,74 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
use stdClass;
/**
* Unit tests for the format helper
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\format
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format_test extends advanced_testcase {
/**
* Test userdate method
*/
public function test_userdate(): void {
$now = time();
$userdate = format::userdate($now, new stdClass());
$this->assertEquals(userdate($now), $userdate);
}
/**
* Data provider for {@see test_boolean_as_text}
*
* @return array
*/
public function boolean_as_text_provider(): array {
return [
[false, get_string('no')],
[true, get_string('yes')],
];
}
/**
* Test boolean as text
*
* @param bool $value
* @param string $expected
*
* @dataProvider boolean_as_text_provider
*/
public function test_boolean_as_text(bool $value, string $expected): void {
$this->assertEquals($expected, format::boolean_as_text($value));
}
/**
* Test percentage formatting of a float
*/
public function test_percent(): void {
$this->assertEquals('33.3%', format::percent(1 / 3 * 100));
}
}
@@ -0,0 +1,746 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
use core_reportbuilder_generator;
use invalid_parameter_exception;
use core_reportbuilder\datasource;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
use core_tag_tag;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the report helper class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\report
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_test extends advanced_testcase {
/**
* Test creation report
*/
public function test_create_report(): void {
$this->resetAfterTest();
$this->setAdminUser();
$report = report::create_report((object) [
'name' => 'My report with tags',
'source' => users::class,
'tags' => ['cat', 'dog'],
]);
$this->assertEquals('My report with tags', $report->get('name'));
$this->assertEquals(datasource::TYPE_CUSTOM_REPORT, $report->get('type'));
$this->assertEqualsCanonicalizing(['cat', 'dog'],
core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report', $report->get('id')));
$report = report::create_report((object) [
'name' => 'My report without tags',
'source' => users::class,
]);
$this->assertEquals('My report without tags', $report->get('name'));
$this->assertEquals(datasource::TYPE_CUSTOM_REPORT, $report->get('type'));
$this->assertEmpty(core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report',
$report->get('id')));
}
/**
* Test updating report
*/
public function test_update_report(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'uniquerows' => 0]);
$reportupdated = report::update_report((object) [
'id' => $report->get('id'),
'name' => 'My renamed report without add tags',
'uniquerows' => 1,
]);
$this->assertEquals('My renamed report without add tags', $reportupdated->get('name'));
$this->assertTrue($reportupdated->get('uniquerows'));
$this->assertEmpty(core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report',
$reportupdated->get('id')));
$reportupdated = report::update_report((object) [
'id' => $report->get('id'),
'name' => 'My renamed report adding tags',
'uniquerows' => 1,
'tags' => ['cat', 'dog'],
]);
$this->assertEquals('My renamed report adding tags', $reportupdated->get('name'));
$this->assertTrue($reportupdated->get('uniquerows'));
$this->assertEqualsCanonicalizing(['cat', 'dog'],
core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report', $reportupdated->get('id')));
}
/**
* Test deleting report
*/
public function test_delete_report(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create Report1 and add some elements.
$report1 = $generator->create_report(['name' => 'My report 1', 'source' => users::class, 'default' => false,
'tags' => ['cat', 'dog']]);
$column1 = $generator->create_column(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']);
$filter1 = $generator->create_filter(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']);
$condition1 = $generator->create_condition(['reportid' => $report1->get('id'), 'uniqueidentifier' => 'user:email']);
// Create Report2 and add some elements.
$report2 = $generator->create_report(['name' => 'My report 2', 'source' => users::class, 'default' => false]);
$column2 = $generator->create_column(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']);
$filter2 = $generator->create_filter(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']);
$condition2 = $generator->create_condition(['reportid' => $report2->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete Report1.
$result = report::delete_report($report1->get('id'));
$this->assertTrue($result);
// Make sure Report1, and all it's elements are deleted.
$this->assertFalse($report1::record_exists($report1->get('id')));
$this->assertFalse($column1::record_exists($column1->get('id')));
$this->assertFalse($filter1::record_exists($filter1->get('id')));
$this->assertFalse($condition1::record_exists($condition1->get('id')));
$this->assertEmpty(core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report', $report1->get('id')));
// Make sure Report2, and all it's elements still exist.
$this->assertTrue($report2::record_exists($report2->get('id')));
$this->assertTrue($column2::record_exists($column2->get('id')));
$this->assertTrue($filter2::record_exists($filter2->get('id')));
$this->assertTrue($condition2::record_exists($condition2->get('id')));
}
/**
* Testing adding report column
*/
public function test_add_report_column(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add first column.
$columnfullname = report::add_report_column($report->get('id'), 'user:fullname');
$this->assertTrue(column::record_exists($columnfullname->get('id')));
$this->assertEquals($report->get('id'), $columnfullname->get('reportid'));
$this->assertEquals('user:fullname', $columnfullname->get('uniqueidentifier'));
$this->assertEquals(1, $columnfullname->get('columnorder'));
$this->assertEquals(1, $columnfullname->get('sortorder'));
// Add second column.
$columnemail = report::add_report_column($report->get('id'), 'user:email');
$this->assertTrue(column::record_exists($columnemail->get('id')));
$this->assertEquals($report->get('id'), $columnemail->get('reportid'));
$this->assertEquals('user:email', $columnemail->get('uniqueidentifier'));
$this->assertEquals(2, $columnemail->get('columnorder'));
$this->assertEquals(2, $columnemail->get('sortorder'));
}
/**
* Test adding invalid report column
*/
public function test_add_report_column_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid column');
report::add_report_column($report->get('id'), 'user:invalid');
}
/**
* Testing deleting report column
*/
public function test_delete_report_column(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add two columns.
$columnfullname = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first column.
$result = report::delete_report_column($report->get('id'), $columnfullname->get('id'));
$this->assertTrue($result);
// Assert report columns.
$columns = column::get_records(['reportid' => $report->get('id')]);
$this->assertCount(1, $columns);
$column = reset($columns);
$this->assertEquals('user:email', $column->get('uniqueidentifier'));
$this->assertEquals(1, $column->get('columnorder'));
}
/**
* Testing deleting invalid report column
*/
public function test_delete_report_column_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid column');
report::delete_report_column($report->get('id'), 42);
}
/**
* Testing re-ordering report column
*/
public function test_reorder_report_column(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Add four columns.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city column to second position.
$result = report::reorder_report_column($report->get('id'), $columncity->get('id'), 2);
$this->assertTrue($result);
// Assert report columns order.
$columns = column::get_records(['reportid' => $report->get('id')], 'columnorder');
$columnidentifiers = array_map(static function(column $column): string {
return $column->get('uniqueidentifier');
}, $columns);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $columnidentifiers);
}
/**
* Testing re-ordering invalid report column
*/
public function test_reorder_report_column_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid column');
report::reorder_report_column($report->get('id'), 42, 1);
}
/**
* Testing re-ordering report column sorting
*/
public function test_reorder_report_column_sorting(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add four columns.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$columncity = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city column to second position.
$result = report::reorder_report_column_sorting($report->get('id'), $columncity->get('id'), 2);
$this->assertTrue($result);
// Assert report columns order.
$columns = column::get_records(['reportid' => $report->get('id')], 'sortorder');
$columnidentifiers = array_map(static function(column $column): string {
return $column->get('uniqueidentifier');
}, $columns);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $columnidentifiers);
}
/**
* Testing re-ordering invalid report column sorting
*/
public function test_reorder_report_column_sorting_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid column');
report::reorder_report_column_sorting($report->get('id'), 42, 1);
}
/**
* Test toggling of report column sorting
*/
public function test_toggle_report_column_sorting(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Toggle sort descending.
$result = report::toggle_report_column_sorting($report->get('id'), $column->get('id'), true, SORT_DESC);
$this->assertTrue($result);
// Confirm column was updated.
$columnupdated = new column($column->get('id'));
$this->assertTrue($columnupdated->get('sortenabled'));
$this->assertEquals(SORT_DESC, $columnupdated->get('sortdirection'));
}
/**
* Test toggling of report column sorting with invalid column
*/
public function test_toggle_report_column_sorting_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid column');
report::toggle_report_column_sorting($report->get('id'), 42, false);
}
/**
* Test adding report condition
*/
public function test_add_report_condition(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add first condition.
$conditionfullname = report::add_report_condition($report->get('id'), 'user:fullname');
$this->assertTrue(filter::record_exists_select('id = :id AND iscondition = 1',
['id' => $conditionfullname->get('id')]));
$this->assertEquals($report->get('id'), $conditionfullname->get('reportid'));
$this->assertEquals('user:fullname', $conditionfullname->get('uniqueidentifier'));
$this->assertEquals(1, $conditionfullname->get('filterorder'));
// Add second condition.
$conditionemail = report::add_report_condition($report->get('id'), 'user:email');
$this->assertTrue(filter::record_exists_select('id = :id AND iscondition = 1',
['id' => $conditionemail->get('id')]));
$this->assertEquals($report->get('id'), $conditionemail->get('reportid'));
$this->assertEquals('user:email', $conditionemail->get('uniqueidentifier'));
$this->assertEquals(2, $conditionemail->get('filterorder'));
}
/**
* Test adding invalid report condition
*/
public function test_add_report_condition_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid condition');
report::add_report_condition($report->get('id'), 'user:invalid');
}
/**
* Test adding duplicate report condition
*/
public function test_add_report_condition_duplicate(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// First one is fine.
report::add_report_condition($report->get('id'), 'user:email');
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Duplicate condition');
report::add_report_condition($report->get('id'), 'user:email');
}
/**
* Test deleting report condition
*/
public function test_delete_report_condition(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add two conditions.
$conditionfullname = $generator->create_condition([
'reportid' => $report->get('id'),
'uniqueidentifier' => 'user:fullname',
]);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first condition.
$result = report::delete_report_condition($report->get('id'), $conditionfullname->get('id'));
$this->assertTrue($result);
// Assert report conditions.
$conditions = filter::get_condition_records($report->get('id'));
$this->assertCount(1, $conditions);
$condition = reset($conditions);
$this->assertEquals('user:email', $condition->get('uniqueidentifier'));
$this->assertEquals(1, $condition->get('filterorder'));
}
/**
* Test deleting invalid report condition
*/
public function test_delete_report_condition_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid condition');
report::delete_report_condition($report->get('id'), 42);
}
/**
* Test re-ordering report condition
*/
public function test_reorder_report_condition(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add four conditions.
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$conditioncity = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city condition to second position.
$result = report::reorder_report_condition($report->get('id'), $conditioncity->get('id'), 2);
$this->assertTrue($result);
// Assert report conditions order.
$conditions = filter::get_condition_records($report->get('id'), 'filterorder');
$conditionidentifiers = array_map(static function(filter $condition): string {
return $condition->get('uniqueidentifier');
}, $conditions);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $conditionidentifiers);
}
/**
* Test re-ordering invalid report condition
*/
public function test_reorder_report_condition_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid condition');
report::reorder_report_condition($report->get('id'), 42, 1);
}
/**
* Test adding report filter
*/
public function test_add_report_filter(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add first filter.
$filterfullname = report::add_report_filter($report->get('id'), 'user:fullname');
$this->assertTrue(filter::record_exists_select('id = :id AND iscondition = 0',
['id' => $filterfullname->get('id')]));
$this->assertEquals($report->get('id'), $filterfullname->get('reportid'));
$this->assertEquals('user:fullname', $filterfullname->get('uniqueidentifier'));
$this->assertEquals(1, $filterfullname->get('filterorder'));
// Add second filter.
$filteremail = report::add_report_filter($report->get('id'), 'user:email');
$this->assertTrue(filter::record_exists_select('id = :id AND iscondition = 0',
['id' => $filteremail->get('id')]));
$this->assertEquals($report->get('id'), $filteremail->get('reportid'));
$this->assertEquals('user:email', $filteremail->get('uniqueidentifier'));
$this->assertEquals(2, $filteremail->get('filterorder'));
}
/**
* Test adding invalid report filter
*/
public function test_add_report_filter_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid filter');
report::add_report_filter($report->get('id'), 'user:invalid');
}
/**
* Test adding duplicate report filter
*/
public function test_add_report_filter_duplicate(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// First one is fine.
report::add_report_filter($report->get('id'), 'user:email');
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Duplicate filter');
report::add_report_filter($report->get('id'), 'user:email');
}
/**
* Test deleting report filter
*/
public function test_delete_report_filter(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add two filters.
$filterfullname = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
// Delete the first filter.
$result = report::delete_report_filter($report->get('id'), $filterfullname->get('id'));
$this->assertTrue($result);
// Assert report filters.
$filters = filter::get_filter_records($report->get('id'));
$this->assertCount(1, $filters);
$filter = reset($filters);
$this->assertEquals('user:email', $filter->get('uniqueidentifier'));
$this->assertEquals(1, $filter->get('filterorder'));
}
/**
* Test deleting invalid report filter
*/
public function test_delete_report_filter_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid filter');
report::delete_report_filter($report->get('id'), 42);
}
/**
* Test re-ordering report filter
*/
public function test_reorder_report_filter(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class, 'default' => false]);
// Add four filters.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:email']);
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
$filtercity = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
// Move the city filter to second position.
$result = report::reorder_report_filter($report->get('id'), $filtercity->get('id'), 2);
$this->assertTrue($result);
// Assert report filters order.
$filters = filter::get_filter_records($report->get('id'), 'filterorder');
$filteridentifiers = array_map(static function(filter $filter): string {
return $filter->get('uniqueidentifier');
}, $filters);
$this->assertEquals([
'user:fullname',
'user:city',
'user:email',
'user:country',
], $filteridentifiers);
}
/**
* Test re-ordering invalid report filter
*/
public function test_reorder_report_filter_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid filter');
report::reorder_report_filter($report->get('id'), 42, 1);
}
}
@@ -0,0 +1,421 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
use invalid_parameter_exception;
use core_cohort\reportbuilder\audience\cohortmember;
use core_reportbuilder_generator;
use core_reportbuilder\local\models\schedule as model;
use core_reportbuilder\reportbuilder\audience\manual;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the schedule helper class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\schedule
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class schedule_test extends advanced_testcase {
/**
* Test create schedule
*/
public function test_create_schedule(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$timescheduled = time() + DAYSECS;
$schedule = schedule::create_schedule((object) [
'name' => 'My schedule',
'reportid' => $report->get('id'),
'format' => 'csv',
'subject' => 'Hello',
'message' => 'Hola',
'timescheduled' => $timescheduled,
]);
$this->assertEquals('My schedule', $schedule->get('name'));
$this->assertEquals($report->get('id'), $schedule->get('reportid'));
$this->assertEquals('csv', $schedule->get('format'));
$this->assertEquals('Hello', $schedule->get('subject'));
$this->assertEquals('Hola', $schedule->get('message'));
$this->assertEquals($timescheduled, $schedule->get('timescheduled'));
$this->assertEquals($timescheduled, $schedule->get('timenextsend'));
}
/**
* Test update schedule
*/
public function test_update_schedule(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
// Update some record properties.
$record = $schedule->to_record();
$record->name = 'My updated schedule';
$record->timescheduled = 1861340400; // 25/12/2028 07:00 UTC.
$schedule = schedule::update_schedule($record);
$this->assertEquals($record->name, $schedule->get('name'));
$this->assertEquals($record->timescheduled, $schedule->get('timescheduled'));
}
/**
* Test update invalid schedule
*/
public function test_update_schedule_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid schedule');
schedule::update_schedule((object) ['id' => 42, 'reportid' => $report->get('id')]);
}
/**
* Test toggle schedule
*/
public function test_toggle_schedule(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
// Disable the schedule.
schedule::toggle_schedule($report->get('id'), $schedule->get('id'), false);
$schedule = $schedule->read();
$this->assertFalse($schedule->get('enabled'));
// Enable the schedule.
schedule::toggle_schedule($report->get('id'), $schedule->get('id'), true);
$schedule = $schedule->read();
$this->assertTrue($schedule->get('enabled'));
}
/**
* Test toggle invalid schedule
*/
public function test_toggle_schedule_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid schedule');
schedule::toggle_schedule($report->get('id'), 42, true);
}
/**
* Test delete schedule
*/
public function test_delete_schedule(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$scheduleid = $schedule->get('id');
schedule::delete_schedule($report->get('id'), $scheduleid);
$this->assertFalse($schedule::record_exists($scheduleid));
}
/**
* Test delete invalid schedule
*/
public function test_delete_schedule_invalid(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$this->expectException(invalid_parameter_exception::class);
$this->expectExceptionMessage('Invalid schedule');
schedule::delete_schedule($report->get('id'), 42);
}
/**
* Test getting schedule report users (those in matching audience)
*/
public function test_get_schedule_report_users(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
// Create cohort, with some members.
$cohort = $this->getDataGenerator()->create_cohort();
$cohortuserone = $this->getDataGenerator()->create_user(['firstname' => 'Zoe', 'lastname' => 'Zebra']);
cohort_add_member($cohort->id, $cohortuserone->id);
$cohortusertwo = $this->getDataGenerator()->create_user(['firstname' => 'Henrietta', 'lastname' => 'Hamster']);
cohort_add_member($cohort->id, $cohortusertwo->id);
// Create a third user, to be added manually.
$manualuserone = $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Badger']);
$audiencecohort = $generator->create_audience([
'reportid' => $report->get('id'),
'classname' => cohortmember::class,
'configdata' => ['cohorts' => [$cohort->id]],
]);
$audiencemanual = $generator->create_audience([
'reportid' => $report->get('id'),
'classname' => manual::class,
'configdata' => ['users' => [$manualuserone->id]],
]);
// Now create our schedule.
$schedule = $generator->create_schedule([
'reportid' => $report->get('id'),
'name' => 'My schedule',
'audiences' => json_encode([
$audiencecohort->get_persistent()->get('id'),
$audiencemanual->get_persistent()->get('id'),
]),
]);
$users = schedule::get_schedule_report_users($schedule);
$this->assertEquals([
'Bob',
'Henrietta',
'Zoe',
], array_column($users, 'firstname'));
// Now delete one of our users, ensure they are no longer returned.
delete_user($manualuserone);
$users = schedule::get_schedule_report_users($schedule);
$this->assertEquals([
'Henrietta',
'Zoe',
], array_column($users, 'firstname'));
}
/**
* Test getting schedule report row count
*/
public function test_get_schedule_report_count(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
// There is only one row in the report (the only user on the site).
$count = schedule::get_schedule_report_count($schedule);
$this->assertEquals(1, $count);
}
/**
* Data provider for {@see test_get_schedule_report_file}
*
* @return string[]
*/
public function get_schedule_report_file_format(): array {
return [
['csv'],
['excel'],
['html'],
['json'],
['ods'],
['pdf'],
];
}
/**
* Test getting schedule report exported file, in each supported format
*
* @param string $format
*
* @dataProvider get_schedule_report_file_format
*/
public function test_get_schedule_report_file(string $format): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'format' => $format]);
// There is only one row in the report (the only user on the site).
$file = schedule::get_schedule_report_file($schedule);
$this->assertGreaterThan(64, $file->get_filesize());
}
/**
* Data provider for {@see test_should_send_schedule}
*
* @return array[]
*/
public function should_send_schedule_provider(): array {
$time = time();
// We just need large offsets for dates in the past/future.
$yesterday = $time - DAYSECS;
$tomorrow = $time + DAYSECS;
return [
'Disabled' => [[
'enabled' => false,
], false],
'Time scheduled in the past' => [[
'recurrence' => model::RECURRENCE_NONE,
'timescheduled' => $yesterday,
], true],
'Time scheduled in the past, already sent prior to schedule' => [[
'recurrence' => model::RECURRENCE_NONE,
'timescheduled' => $yesterday,
'timelastsent' => $yesterday - HOURSECS,
], true],
'Time scheduled in the past, already sent on schedule' => [[
'recurrence' => model::RECURRENCE_NONE,
'timescheduled' => $yesterday,
'timelastsent' => $yesterday,
], false],
'Time scheduled in the future' => [[
'recurrence' => model::RECURRENCE_NONE,
'timescheduled' => $tomorrow,
], false],
'Time scheduled in the future, already sent prior to schedule' => [[
'recurrence' => model::RECURRENCE_NONE,
'timelastsent' => $yesterday,
'timescheduled' => $tomorrow,
], false],
'Next send in the past' => [[
'recurrence' => model::RECURRENCE_DAILY,
'timescheduled' => $yesterday,
'timenextsend' => $yesterday,
], true],
'Next send in the future' => [[
'recurrence' => model::RECURRENCE_DAILY,
'timescheduled' => $yesterday,
'timenextsend' => $tomorrow,
], false],
];
}
/**
* Test for whether a schedule should be sent
*
* @param array $properties
* @param bool $expected
*
* @dataProvider should_send_schedule_provider
*/
public function test_should_send_schedule(array $properties, bool $expected): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule'] + $properties);
// If "Time next send" is specified, then override calculated value.
if (array_key_exists('timenextsend', $properties)) {
$schedule->set('timenextsend', $properties['timenextsend']);
}
$this->assertEquals($expected, schedule::should_send_schedule($schedule));
}
/**
* Data provider for {@see test_calculate_next_send_time}
*
* @return array[]
*/
public function calculate_next_send_time_provider(): array {
$timescheduled = 1635865200; // Tue Nov 02 2021 15:00:00 GMT+0000.
$timenow = 1639846800; // Sat Dec 18 2021 17:00:00 GMT+0000.
return [
'No recurrence' => [model::RECURRENCE_NONE, $timescheduled, $timenow, $timescheduled],
'Recurrence, time scheduled in future' => [model::RECURRENCE_DAILY, $timenow + DAYSECS, $timenow, $timenow + DAYSECS],
// Sun Dec 19 2021 15:00:00 GMT+0000.
'Daily recurrence' => [model::RECURRENCE_DAILY, $timescheduled, $timenow, 1639926000],
// Mon Dec 20 2021 15:00:00 GMT+0000.
'Weekday recurrence' => [model::RECURRENCE_WEEKDAYS, $timescheduled, $timenow, 1640012400],
// Tue Dec 21 2021 15:00:00 GMT+0000.
'Weekly recurrence' => [model::RECURRENCE_WEEKLY, $timescheduled, $timenow, 1640098800],
// Sun Jan 02 2022 15:00:00 GMT+0000.
'Monthy recurrence' => [model::RECURRENCE_MONTHLY, $timescheduled, $timenow, 1641135600],
// Wed Nov 02 2022 15:00:00 GMT+0000.
'Annual recurrence' => [model::RECURRENCE_ANNUALLY, $timescheduled, $timenow, 1667401200],
];
}
/**
* Test for calculating next schedule send time
*
* @param int $recurrence
* @param int $timescheduled
* @param int $timenow
* @param int $expected
*
* @dataProvider calculate_next_send_time_provider
*/
public function test_calculate_next_send_time(int $recurrence, int $timescheduled, int $timenow, int $expected): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$schedule = $generator->create_schedule([
'reportid' => $report->get('id'),
'name' => 'My schedule',
'recurrence' => $recurrence,
'timescheduled' => $timescheduled,
'timenow' => $timenow,
]);
$this->assertEquals($expected, schedule::calculate_next_send_time($schedule, $timenow));
}
}
@@ -0,0 +1,233 @@
<?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_reportbuilder\local\helpers;
use advanced_testcase;
/**
* Unit tests for the user filter helper
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\user_filter_manager
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_filter_manager_test extends advanced_testcase {
/**
* Helper method to return all user preferences for filters - based on the current storage backend using the same
*
* @return array
*/
private function get_filter_preferences(): array {
return array_filter(get_user_preferences(), static function(string $key): bool {
return strpos($key, 'reportbuilder-report-') === 0;
}, ARRAY_FILTER_USE_KEY);
}
/**
* Data provider for {@see test_get}
*
* @return array
*/
public function get_provider(): array {
return [
'Small value' => ['foo'],
'Large value' => [str_repeat('A', 4000)],
'Empty value' => [''],
];
}
/**
* Test getting filter values
*
* @param string $value
*
* @dataProvider get_provider
*/
public function test_get(string $value): void {
$this->resetAfterTest();
$values = [
'entity:filter_name' => $value,
];
user_filter_manager::set(5, $values);
// Make sure we get the same value back.
$this->assertEquals($values, user_filter_manager::get(5));
}
/**
* Test getting filter values that once spanned multiple chunks
*/
public function test_get_large_to_small(): void {
$this->resetAfterTest();
// Set a large initial filter value.
user_filter_manager::set(5, [
'longvalue' => str_repeat('ABCD', 1000),
]);
// Sanity check, there should be 4 (because 4000 characters plus some JSON encoding requires that many chunks).
$preferences = $this->get_filter_preferences();
$this->assertCount(4, $preferences);
$values = [
'longvalue' => 'ABCD',
];
user_filter_manager::set(5, $values);
// Make sure we get the same value back.
$this->assertEquals($values, user_filter_manager::get(5));
// Everything should now fit in a single filter preference.
$preferences = $this->get_filter_preferences();
$this->assertCount(1, $preferences);
}
/**
* Test getting filter values that haven't been set
*/
public function test_get_empty(): void {
$this->assertEquals([], user_filter_manager::get(5));
}
/**
* Data provider for {@see test_reset_all}
*
* @return array
*/
public function reset_all_provider(): array {
return [
'Small value' => ['foo'],
'Large value' => [str_repeat('A', 4000)],
'Empty value' => [''],
];
}
/**
* Test resetting all filter values
*
* @param string $value
*
* @dataProvider reset_all_provider
*/
public function test_reset_all(string $value): void {
$this->resetAfterTest();
user_filter_manager::set(5, [
'entity:filter_name' => $value
]);
$reset = user_filter_manager::reset_all(5);
$this->assertTrue($reset);
// We should get an empty array back.
$this->assertEquals([], user_filter_manager::get(5));
// All filter preferences should be removed.
$this->assertEmpty($this->get_filter_preferences());
}
/**
* Test resetting single filter values
*/
public function test_reset_single(): void {
$this->resetAfterTest();
user_filter_manager::set(5, [
'entity:filter_name' => 'foo',
'entity:filter_value' => 'bar',
'entity:other_name' => 'baz',
'entity:other_value' => 'bax',
]);
$reset = user_filter_manager::reset_single(5, 'entity:other');
$this->assertTrue($reset);
$this->assertEquals([
'entity:filter_name' => 'foo',
'entity:filter_value' => 'bar',
], user_filter_manager::get(5));
}
/**
* Test merging filter values
*/
public function test_merge(): void {
$this->resetAfterTest();
$values = [
'entity:filter_name' => 'foo',
'entity:filter_value' => 'bar',
'entity:filter2_name' => 'tree',
'entity:filter2_value' => 'house',
];
// Make sure we get the same value back.
user_filter_manager::set(5, $values);
$this->assertEqualsCanonicalizing($values, user_filter_manager::get(5));
user_filter_manager::merge(5, [
'entity:filter_name' => 'twotimesfoo',
'entity:filter_value' => 'twotimesbar',
]);
// Make sure that both values have been changed and the other values have not been modified.
$expected = [
'entity:filter_name' => 'twotimesfoo',
'entity:filter_value' => 'twotimesbar',
'entity:filter2_name' => 'tree',
'entity:filter2_value' => 'house',
];
$this->assertEqualsCanonicalizing($expected, user_filter_manager::get(5));
}
/**
* Test to get all filters from a given user
*/
public function test_get_all_for_user(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$filtervalues1 = [
'entity:filter_name' => 'foo',
'entity:filter_value' => 'bar',
'entity:other_name' => 'baz',
'entity:other_value' => 'bax',
];
user_filter_manager::set(5, $filtervalues1);
$filtervalues2 = [
'entity:filter_name' => 'blue',
'entity:filter_value' => 'red',
];
user_filter_manager::set(9, $filtervalues2);
$this->setAdminUser();
$values = user_filter_manager::get_all_for_user((int)$user->id);
$this->assertEqualsCanonicalizing([$filtervalues1, $filtervalues2], [reset($values), end($values)]);
// Check for a user with no filters.
$user2 = $this->getDataGenerator()->create_user();
$values = user_filter_manager::get_all_for_user((int)$user2->id);
$this->assertEmpty($values);
}
}
@@ -0,0 +1,380 @@
<?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_reportbuilder\local\helpers;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for user profile fields helper
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\helpers\user_profile_fields
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_profile_fields_test extends core_reportbuilder_testcase {
/**
* Generate custom profile fields, one of each type
*
* @return user_profile_fields
*/
private function generate_userprofilefields(): user_profile_fields {
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'checkbox', 'name' => 'Checkbox field', 'datatype' => 'checkbox']);
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'datetime', 'name' => 'Date field', 'datatype' => 'datetime', 'param2' => 2022, 'param3' => 0]);
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'menu', 'name' => 'Menu field', 'datatype' => 'menu', 'param1' => "Cat\nDog"]);
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'Social', 'name' => 'msn', 'datatype' => 'social', 'param1' => 'msn']);
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'text', 'name' => 'Text field', 'datatype' => 'text']);
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'textarea', 'name' => 'Textarea field', 'datatype' => 'textarea']);
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
// Create an instance of the userprofilefield helper.
return new user_profile_fields("$useralias.id", $userentity->get_entity_name());
}
/**
* Test for get_columns
*/
public function test_get_columns(): void {
$this->resetAfterTest();
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
// Get pre-existing user profile fields.
$initialuserprofilefields = new user_profile_fields("$useralias.id", $userentity->get_entity_name());
$initialcolumns = $initialuserprofilefields->get_columns();
$initialcolumntitles = array_map(static function(column $column): string {
return $column->get_title();
}, $initialcolumns);
$initialcolumntypes = array_map(static function(column $column): int {
return $column->get_type();
}, $initialcolumns);
// Add new custom profile fields.
$userprofilefields = $this->generate_userprofilefields();
$columns = $userprofilefields->get_columns();
// Columns count should be equal to start + 6.
$this->assertCount(count($initialcolumns) + 6, $columns);
$this->assertContainsOnlyInstancesOf(column::class, $columns);
// Assert column titles.
$columntitles = array_map(static function(column $column): string {
return $column->get_title();
}, $columns);
$expectedcolumntitles = array_merge($initialcolumntitles, [
'Checkbox field',
'Date field',
'Menu field',
'MSN ID',
'Text field',
'Textarea field',
]);
$this->assertEquals($expectedcolumntitles, $columntitles);
// Assert column types.
$columntypes = array_map(static function(column $column): int {
return $column->get_type();
}, $columns);
$expectedcolumntypes = array_merge($initialcolumntypes, [
column::TYPE_BOOLEAN,
column::TYPE_TIMESTAMP,
column::TYPE_TEXT,
column::TYPE_TEXT,
column::TYPE_TEXT,
column::TYPE_LONGTEXT,
]);
$this->assertEquals($expectedcolumntypes, $columntypes);
}
/**
* Test for add_join
*/
public function test_add_join(): void {
$this->resetAfterTest();
$userprofilefields = $this->generate_userprofilefields();
$columns = $userprofilefields->get_columns();
$this->assertCount(1, ($columns[0])->get_joins());
$userprofilefields->add_join('JOIN {test} t ON t.id = id');
$columns = $userprofilefields->get_columns();
$this->assertCount(2, ($columns[0])->get_joins());
}
/**
* Test for add_joins
*/
public function test_add_joins(): void {
$this->resetAfterTest();
$userprofilefields = $this->generate_userprofilefields();
$columns = $userprofilefields->get_columns();
$this->assertCount(1, ($columns[0])->get_joins());
$userprofilefields->add_joins(['JOIN {test} t ON t.id = id', 'JOIN {test2} t2 ON t2.id = id']);
$columns = $userprofilefields->get_columns();
$this->assertCount(3, ($columns[0])->get_joins());
}
/**
* Test for get_filters
*/
public function test_get_filters(): void {
$this->resetAfterTest();
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
// Get pre-existing user profile fields.
$initialuserprofilefields = new user_profile_fields("$useralias.id", $userentity->get_entity_name());
$initialfilters = $initialuserprofilefields->get_filters();
$initialfilterheaders = array_map(static function(filter $filter): string {
return $filter->get_header();
}, $initialfilters);
// Add new custom profile fields.
$userprofilefields = $this->generate_userprofilefields();
$filters = $userprofilefields->get_filters();
// Filters count should be equal to start + 6.
$this->assertCount(count($initialfilters) + 6, $filters);
$this->assertContainsOnlyInstancesOf(filter::class, $filters);
// Assert filter headers.
$filterheaders = array_map(static function(filter $filter): string {
return $filter->get_header();
}, $filters);
$expectedfilterheaders = array_merge($initialfilterheaders, [
'Checkbox field',
'Date field',
'Menu field',
'MSN ID',
'Text field',
'Textarea field',
]);
$this->assertEquals($expectedfilterheaders, $filterheaders);
}
/**
* Test that adding user profile field columns to a report returns expected values
*/
public function test_custom_report_content(): void {
$this->resetAfterTest();
$userprofilefields = $this->generate_userprofilefields();
// Create test subject with user profile fields content.
$user = $this->getDataGenerator()->create_user([
'firstname' => 'Zebedee',
'profile_field_checkbox' => true,
'profile_field_datetime' => '2021-12-09',
'profile_field_menu' => 'Cat',
'profile_field_Social' => 12345,
'profile_field_text' => 'Hello',
'profile_field_textarea' => 'Goodbye',
]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// Add user profile field columns to the report.
$firstname = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_checkbox']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_datetime']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_menu']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_social']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_text']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_textarea']);
// Sort the report, Admin -> Zebedee for consistency.
report::toggle_report_column_sorting($report->get('id'), $firstname->get('id'), true);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_data' => '',
'c2_data' => '',
'c3_data' => '',
'c4_data' => '',
'c5_data' => '',
'c6_data' => '',
], [
'c0_firstname' => 'Zebedee',
'c1_data' => 'Yes',
'c2_data' => '9 December 2021',
'c3_data' => 'Cat',
'c4_data' => '12345',
'c5_data' => 'Hello',
'c6_data' => '<div class="no-overflow">Goodbye</div>',
],
], $content);
}
/**
* Data provider for {@see test_custom_report_filter}
*
* @return array[]
*/
public function custom_report_filter_provider(): array {
return [
'Filter by checkbox profile field' => ['user:profilefield_checkbox', [
'user:profilefield_checkbox_operator' => boolean_select::CHECKED,
], 'testuser'],
'Filter by checkbox profile field (empty)' => ['user:profilefield_checkbox', [
'user:profilefield_checkbox_operator' => boolean_select::NOT_CHECKED,
], 'admin'],
'Filter by datetime profile field' => ['user:profilefield_datetime', [
'user:profilefield_datetime_operator' => date::DATE_RANGE,
'user:profilefield_datetime_from' => 1622502000,
], 'testuser'],
'Filter by datetime profile field (empty)' => ['user:profilefield_datetime', [
'user:profilefield_datetime_operator' => date::DATE_EMPTY,
], 'admin'],
'Filter by menu profile field' => ['user:profilefield_menu', [
'user:profilefield_menu_operator' => select::EQUAL_TO,
'user:profilefield_menu_value' => 'Dog',
], 'testuser'],
'Filter by menu profile field (empty)' => ['user:profilefield_menu', [
'user:profilefield_menu_operator' => select::NOT_EQUAL_TO,
'user:profilefield_menu_value' => 'Dog',
], 'admin'],
'Filter by social profile field' => ['user:profilefield_social', [
'user:profilefield_social_operator' => text::IS_EQUAL_TO,
'user:profilefield_social_value' => '12345',
], 'testuser'],
'Filter by social profile field (empty)' => ['user:profilefield_social', [
'user:profilefield_social_operator' => text::IS_EMPTY,
], 'admin'],
'Filter by text profile field' => ['user:profilefield_text', [
'user:profilefield_text_operator' => text::IS_EQUAL_TO,
'user:profilefield_text_value' => 'Hello',
], 'testuser'],
'Filter by text profile field (empty)' => ['user:profilefield_text', [
'user:profilefield_text_operator' => text::IS_NOT_EQUAL_TO,
'user:profilefield_text_value' => 'Hello',
], 'admin'],
'Filter by textarea profile field' => ['user:profilefield_textarea', [
'user:profilefield_textarea_operator' => text::IS_EQUAL_TO,
'user:profilefield_textarea_value' => 'Goodbye',
], 'testuser'],
'Filter by textarea profile field (empty)' => ['user:profilefield_textarea', [
'user:profilefield_textarea_operator' => text::DOES_NOT_CONTAIN,
'user:profilefield_textarea_value' => 'Goodbye',
], 'admin'],
];
}
/**
* Test filtering report by custom profile fields
*
* @param string $filtername
* @param array $filtervalues
* @param string $expectmatchuser
*
* @dataProvider custom_report_filter_provider
*/
public function test_custom_report_filter(string $filtername, array $filtervalues, string $expectmatchuser): void {
$this->resetAfterTest();
$userprofilefields = $this->generate_userprofilefields();
// Create test subject with user profile fields content.
$user = $this->getDataGenerator()->create_user([
'username' => 'testuser',
'profile_field_checkbox' => true,
'profile_field_datetime' => '2021-12-09',
'profile_field_menu' => 'Dog',
'profile_field_Social' => '12345',
'profile_field_text' => 'Hello',
'profile_field_textarea' => 'Goodbye',
]);
/** @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' => 'Users', '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);
$this->assertCount(1, $content);
$this->assertEquals($expectmatchuser, reset($content[0]));
}
/**
* Stress test user datasource using profile fields
*
* 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();
$userprofilefields = $this->generate_userprofilefields();
$user = $this->getDataGenerator()->create_user([
'profile_field_checkbox' => true,
'profile_field_datetime' => '2021-12-09',
'profile_field_menu' => 'Dog',
'profile_field_Social' => '12345',
'profile_field_text' => 'Hello',
'profile_field_textarea' => 'Goodbye',
]);
$this->datasource_stress_test_columns(users::class);
$this->datasource_stress_test_columns_aggregation(users::class);
$this->datasource_stress_test_conditions(users::class, 'user:idnumber');
}
}
@@ -0,0 +1,155 @@
<?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_reportbuilder\local\models;
use advanced_testcase;
use core\persistent;
use core_reportbuilder\event\audience_created;
use core_reportbuilder\event\audience_deleted;
use core_reportbuilder\event\audience_updated;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the audience model
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\models\audience
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience_test extends advanced_testcase {
/**
* Tests for audience_created event
*
* @return persistent[]
*
* @covers \core_reportbuilder\event\audience_created
*/
public function test_audience_created_event(): array {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Catch the events.
$sink = $this->redirectEvents();
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []])
->get_persistent();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(audience_created::class, $event);
$this->assertEquals(audience::TABLE, $event->objecttable);
$this->assertEquals($audience->get('id'), $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return [$report, $audience];
}
/**
* Tests for audience_updated event
*
* @param persistent[] $persistents
* @return persistent[]
*
* @depends test_audience_created_event
* @covers \core_reportbuilder\event\audience_updated
*/
public function test_audience_updated_event(array $persistents): array {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistents.
[$report, $audience] = $persistents;
$report = new report($DB->insert_record(report::TABLE, $report->to_record()));
$audience = new audience($DB->insert_record(audience::TABLE, $audience->to_record()));
// Catch the events.
$sink = $this->redirectEvents();
$audience->set('heading', 'Hello')->update();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(audience_updated::class, $event);
$this->assertEquals(audience::TABLE, $event->objecttable);
$this->assertEquals($audience->get('id'), $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return [$report, $audience];
}
/**
* Tests for audience_deleted event
*
* @param persistent[] $persistents
*
* @depends test_audience_updated_event
* @covers \core_reportbuilder\event\audience_deleted
*/
public function test_audience_deleted_event(array $persistents): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistents (remembering audience ID which is removed from persistent upon deletion).
[$report, $audience] = $persistents;
$report = new report($DB->insert_record(report::TABLE, $report->to_record()));
$audienceid = $DB->insert_record(audience::TABLE, $audience->to_record());
$audience = new audience($audienceid);
// Catch the events.
$sink = $this->redirectEvents();
$audience->delete();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(audience_deleted::class, $event);
$this->assertEquals(audience::TABLE, $event->objecttable);
$this->assertEquals($audienceid, $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
}
}
@@ -0,0 +1,149 @@
<?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_reportbuilder\local\models;
use advanced_testcase;
use core_reportbuilder\event\report_created;
use core_reportbuilder\event\report_deleted;
use core_reportbuilder\event\report_updated;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the report model
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\models\report
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_test extends advanced_testcase {
/**
* Tests for report_created event
*
* @return report
*
* @covers \core_reportbuilder\event\report_created
*/
public function test_report_created_event(): report {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Catch the events.
$sink = $this->redirectEvents();
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(report_created::class, $event);
$this->assertEquals(report::TABLE, $event->objecttable);
$this->assertEquals($report->get('id'), $event->objectid);
$this->assertEquals($report->get('name'), $event->other['name']);
$this->assertEquals($report->get('source'), $event->other['source']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return $report;
}
/**
* Tests for report_updated event
*
* @param report $report
* @return report
*
* @depends test_report_created_event
* @covers \core_reportbuilder\event\report_updated
*/
public function test_report_updated_event(report $report): report {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistent.
$report = new report($DB->insert_record(report::TABLE, $report->to_record()));
// Catch the events.
$sink = $this->redirectEvents();
$report->set('name', 'New report name')->update();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(report_updated::class, $event);
$this->assertEquals(report::TABLE, $event->objecttable);
$this->assertEquals($report->get('id'), $event->objectid);
$this->assertEquals('New report name', $event->other['name']);
$this->assertEquals($report->get('source'), $event->other['source']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return $report;
}
/**
* Tests for report_deleted event
*
* @param report $report
*
* @depends test_report_updated_event
* @covers \core_reportbuilder\event\report_deleted
*/
public function test_report_deleted_event(report $report): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistent (remembering report ID which is removed from persistent upon deletion).
$reportid = $DB->insert_record(report::TABLE, $report->to_record());
$report = new report($reportid);
// Catch the events.
$sink = $this->redirectEvents();
$report->delete();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(report_deleted::class, $event);
$this->assertEquals(report::TABLE, $event->objecttable);
$this->assertEquals($reportid, $event->objectid);
$this->assertEquals($report->get('name'), $event->other['name']);
$this->assertEquals($report->get('source'), $event->other['source']);
$this->assertEquals($report->get_context()->id, $event->contextid);
}
}
@@ -0,0 +1,154 @@
<?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_reportbuilder\local\models;
use advanced_testcase;
use core\persistent;
use core_reportbuilder\event\schedule_created;
use core_reportbuilder\event\schedule_deleted;
use core_reportbuilder\event\schedule_updated;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the schedule model
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\models\schedule
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class schedule_test extends advanced_testcase {
/**
* Tests for schedule_created event
*
* @return persistent[]
*
* @covers \core_reportbuilder\event\schedule_created
*/
public function test_schedule_created_event(): array {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Catch the events.
$sink = $this->redirectEvents();
$schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(schedule_created::class, $event);
$this->assertEquals(schedule::TABLE, $event->objecttable);
$this->assertEquals($schedule->get('id'), $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return [$report, $schedule];
}
/**
* Tests for schedule_updated event
*
* @param persistent[] $persistents
* @return persistent[]
*
* @depends test_schedule_created_event
* @covers \core_reportbuilder\event\schedule_updated
*/
public function test_schedule_updated_event(array $persistents): array {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistents.
[$report, $schedule] = $persistents;
$report = new report($DB->insert_record(report::TABLE, $report->to_record()));
$schedule = new schedule($DB->insert_record(schedule::TABLE, $schedule->to_record()));
// Catch the events.
$sink = $this->redirectEvents();
$schedule->set('name', 'My new schedule')->update();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(schedule_updated::class, $event);
$this->assertEquals(schedule::TABLE, $event->objecttable);
$this->assertEquals($schedule->get('id'), $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
return [$report, $schedule];
}
/**
* Tests for schedule_deleted event
*
* @param persistent[] $persistents
*
* @depends test_schedule_updated_event
* @covers \core_reportbuilder\event\schedule_deleted
*/
public function test_schedule_deleted_event(array $persistents): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Re-create the persistents (remembering schedule ID which is removed from persistent upon deletion).
[$report, $schedule] = $persistents;
$report = new report($DB->insert_record(report::TABLE, $report->to_record()));
$scheduleid = $DB->insert_record(schedule::TABLE, $schedule->to_record());
$schedule = new schedule($scheduleid);
// Catch the events.
$sink = $this->redirectEvents();
$schedule->delete();
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf(schedule_deleted::class, $event);
$this->assertEquals(schedule::TABLE, $event->objecttable);
$this->assertEquals($scheduleid, $event->objectid);
$this->assertEquals($report->get('id'), $event->other['reportid']);
$this->assertEquals($report->get_context()->id, $event->contextid);
}
}
@@ -0,0 +1,149 @@
<?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_reportbuilder\local\report;
use advanced_testcase;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
/**
* Unit tests for a report action
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\report\action
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_test extends advanced_testcase {
/**
* Test adding a callback that returns true
*/
public function test_add_callback_true(): void {
$action = $this->create_action()
->add_callback(static function(stdClass $row): bool {
return true;
});
$this->assertNotNull($action->get_action_link(new stdClass()));
}
/**
* Test adding a callback that returns false
*/
public function test_add_callback_false(): void {
$action = $this->create_action()
->add_callback(static function(stdClass $row): bool {
return false;
});
$this->assertNull($action->get_action_link(new stdClass()));
}
/**
* Data provider for {@see test_action_title}
*
* @return array[]
*/
public function action_title_provider(): array {
$title = new lang_string('yes');
return [
'Specified via constructor' => ['', [], $title],
'Specified via pix icon' => [(string) $title],
'Specified via attributes' => ['', ['title' => $title]],
'Specified via attributes placeholder' => ['', ['title' => ':title'], null, ['title' => $title]],
];
}
/**
* Test action title is correct
*
* @param string $pixiconalt
* @param array $attributes
* @param lang_string|null $title
* @param array $row
*
* @dataProvider action_title_provider
*/
public function test_action_title(
string $pixiconalt,
array $attributes = [],
?lang_string $title = null,
array $row = []
): void {
$action = new action(
new moodle_url('#'),
new pix_icon('t/edit', $pixiconalt),
$attributes,
false,
$title
);
// Assert correct title appears inside action link, after the icon.
$actionlink = $action->get_action_link((object) $row);
$this->assertEquals('Yes', $actionlink->text);
}
/**
* Test that action link URL parameters have placeholders replaced
*/
public function test_get_action_link_url_parameters(): void {
$action = $this->create_action(['id' => ':id', 'action' => 'edit']);
$actionlink = $action->get_action_link((object) ['id' => 42]);
// This is the action URL we expect.
$expectedactionurl = (new moodle_url('/', ['id' => 42, 'action' => 'edit']))->out(false);
$this->assertEquals($expectedactionurl, $actionlink->url->out(false));
}
/**
* Test that action link attributes have placeholders replaced
*/
public function test_get_action_link_attributes(): void {
$action = $this->create_action([], ['data-id' => ':id', 'data-action' => 'edit']);
$actionlink = $action->get_action_link((object) ['id' => 42]);
// We expect each of these attributes to exist.
$expectedattributes = [
'data-id' => 42,
'data-action' => 'edit',
];
foreach ($expectedattributes as $key => $value) {
$this->assertEquals($value, $actionlink->attributes[$key]);
}
}
/**
* Helper method to create an action instance
*
* @param array $urlparams
* @param array $attributes
* @return action
*/
private function create_action(array $urlparams = [], array $attributes = []): action {
return new action(
new moodle_url('/', $urlparams),
new pix_icon('t/edit', get_string('edit')),
$attributes
);
}
}
@@ -0,0 +1,278 @@
<?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_reportbuilder\local\report;
use advanced_testcase;
use coding_exception;
use context_system;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\system_report_available;
use core_reportbuilder\system_report_factory;
use lang_string;
use ReflectionClass;
/**
* Unit tests for report base class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\report\base
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_test extends advanced_testcase {
/**
* Load required class
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
}
/**
* Test for add_base_condition_simple
*/
public function test_add_base_condition_simple(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->add_base_condition_simple('username', 'admin');
[$where, $params] = $systemreport->get_base_condition();
$this->assertStringMatchesFormat('username = :%a', $where);
$this->assertEqualsCanonicalizing(['admin'], $params);
}
/**
* Test for add_base_condition_simple null
*/
public function test_add_base_condition_simple_null(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->add_base_condition_simple('username', null);
[$where, $params] = $systemreport->get_base_condition();
$this->assertEquals('username IS NULL', $where);
$this->assertEmpty($params);
}
/**
* Test for adding SQL base condition to a report
*/
public function test_add_base_condition_sql(): void {
$this->resetAfterTest();
$parameter = database::generate_param_name();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->add_base_condition_sql("username = :{$parameter}", [$parameter => 'admin']);
[$where, $params] = $systemreport->get_base_condition();
$this->assertEquals("username = :{$parameter}", $where);
$this->assertEquals([$parameter => 'admin'], $params);
}
/**
* Test for adding multiple SQL base condition to a report
*/
public function test_add_base_condition_sql_multiple(): void {
$this->resetAfterTest();
[$paramusername, $paramemail] = database::generate_param_names(2);
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->add_base_condition_sql("username = :{$paramusername}", [$paramusername => 'admin']);
$systemreport->add_base_condition_sql("email = :{$paramemail}", [$paramemail => 'admin@example.com']);
[$where, $params] = $systemreport->get_base_condition();
$this->assertEquals("username = :{$paramusername} AND email = :{$paramemail}", $where);
$this->assertEquals([$paramusername => 'admin', $paramemail => 'admin@example.com'], $params);
}
/**
* Test for adding empty SQL base condition to a report
*/
public function test_add_base_condition_sql_empty_clause(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->add_base_condition_sql('username IS NOT NULL');
$systemreport->add_base_condition_sql('');
[$where, $params] = $systemreport->get_base_condition();
$this->assertEquals("username IS NOT NULL", $where);
$this->assertEmpty($params);
}
/**
* Test for adding SQL base condition to a report with invalid parameter
*/
public function test_add_base_condition_sql_invalid_parameter(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Invalid parameter names');
$systemreport->add_base_condition_sql("username = :param", ['param' => 'admin']);
}
/**
* Test getting report base conditions, where none have been set
*/
public function test_get_base_condition_default(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
[$where, $params] = $systemreport->get_base_condition();
$this->assertEmpty($where);
$this->assertEmpty($params);
}
/**
* Test for get_filter_instances
*/
public function test_get_filter_instances(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
'', '', 0, ['withfilters' => true]);
$filters = $systemreport->get_filter_instances();
$this->assertCount(1, $filters);
$this->assertInstanceOf(\core_reportbuilder\local\filters\text::class, reset($filters));
}
/**
* Test for set_downloadable
*/
public function test_set_downloadable(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$systemreport->set_downloadable(true, 'testfilename');
$this->assertTrue($systemreport->is_downloadable());
$this->assertEquals('testfilename', $systemreport->get_downloadfilename());
$systemreport->set_downloadable(false, 'anothertestfilename');
$this->assertFalse($systemreport->is_downloadable());
$this->assertEquals('anothertestfilename', $systemreport->get_downloadfilename());
}
/**
* Test for get_context
*/
public function test_get_context(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$this->assertEquals(context_system::instance(), $systemreport->get_context());
$course = $this->getDataGenerator()->create_course();
$contextcourse = \context_course::instance($course->id);
$systemreport2 = system_report_factory::create(system_report_available::class, $contextcourse);
$this->assertEquals($contextcourse, $systemreport2->get_context());
}
/**
* Test entity annotation
*/
public function test_annotate_entity(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$method = (new ReflectionClass($systemreport))->getMethod('annotate_entity');
$method->invoke($systemreport, 'test', new lang_string('yes'));
$this->assertEquals(new lang_string('yes'), $systemreport->get_entity_title('test'));
}
/**
* Test entity annotation for invalid entity name
*/
public function test_annotate_entity_invalid(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$method = (new ReflectionClass($systemreport))->getMethod('annotate_entity');
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Entity name must be comprised of alphanumeric character, underscore or dash');
$method->invoke($systemreport, '', new lang_string('yes'));
}
/**
* Test entity annotation for duplicated entity name
*/
public function test_annotate_entity_duplicate(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$method = (new ReflectionClass($systemreport))->getMethod('annotate_entity');
$method->invoke($systemreport, 'test', new lang_string('yes'));
// Adding a second time with the same name should trigger exception.
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Duplicate entity name (test)');
$method->invoke($systemreport, 'test', new lang_string('no'));
}
/**
* Test for get_column
*/
public function test_get_column(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$column = $systemreport->get_column('user:username');
$this->assertInstanceOf(column::class, $column);
$column = $systemreport->get_column('user:nonexistingcolumn');
$this->assertNull($column);
}
/**
* Test for get_filter
*/
public function test_get_filter(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance(),
'', '', 0, ['withfilters' => true]);
$filter = $systemreport->get_filter('user:username');
$this->assertInstanceOf(filter::class, $filter);
$filter = $systemreport->get_filter('user:nonexistingfilter');
$this->assertNull($filter);
}
/**
* Test for get_report_persistent
*/
public function test_get_report_persistent(): void {
$this->resetAfterTest();
$systemreport = system_report_factory::create(system_report_available::class, context_system::instance());
$persistent = $systemreport->get_report_persistent();
$this->assertEquals(system_report_available::class, $persistent->get('source'));
}
}
@@ -0,0 +1,560 @@
<?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_reportbuilder\local\report;
use advanced_testcase;
use coding_exception;
use lang_string;
use stdClass;
use core_reportbuilder\local\helpers\database;
/**
* Unit tests for a report column
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\report\column
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_test extends advanced_testcase {
/**
* Test column name getter/setter
*/
public function test_name(): void {
$column = $this->create_column('test');
$this->assertEquals('test', $column->get_name());
$this->assertEquals('another', $column
->set_name('another')
->get_name()
);
}
/**
* Test column title getter/setter
*/
public function test_title(): void {
$column = $this->create_column('test', new lang_string('show'));
$this->assertEquals('Show', $column->get_title());
$this->assertFalse($column->has_custom_title());
$this->assertEquals('Hide', $column
->set_title(new lang_string('hide'))
->get_title()
);
$this->assertTrue($column->has_custom_title());
// Column titles can also be empty.
$this->assertEmpty($column
->set_title(null)
->get_title());
}
/**
* Test entity name getter
*/
public function test_get_entity_name(): void {
$column = $this->create_column('test', null, 'entityname');
$this->assertEquals('entityname', $column->get_entity_name());
}
/**
* Test getting unique identifier
*/
public function test_get_unique_identifier(): void {
$column = $this->create_column('test', null, 'entityname');
$this->assertEquals('entityname:test', $column->get_unique_identifier());
}
/**
* Test column type getter/setter
*/
public function test_type(): void {
$column = $this->create_column('test');
$this->assertEquals(column::TYPE_INTEGER, $column
->set_type(column::TYPE_INTEGER)
->get_type());
}
/**
* Test column default type
*/
public function test_type_default(): void {
$column = $this->create_column('test');
$this->assertEquals(column::TYPE_TEXT, $column->get_type());
}
/**
* Test column type with invalid value
*/
public function test_type_invalid(): void {
$column = $this->create_column('test');
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Invalid column type');
$column->set_type(-1);
}
/**
* Test adding single join
*/
public function test_add_join(): void {
$column = $this->create_column('test');
$this->assertEquals([], $column->get_joins());
$column->add_join('JOIN {user} u ON u.id = table.userid');
$this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $column->get_joins());
}
/**
* Test adding multiple joins
*/
public function test_add_joins(): void {
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$column = $this->create_column('test')
->add_joins($tablejoins);
$this->assertEquals($tablejoins, $column->get_joins());
}
/**
* Data provider for {@see test_add_field}
*
* @return array
*/
public function add_field_provider(): array {
return [
['foo', '', ['foo AS c1_foo']],
['foo', 'bar', ['foo AS c1_bar']],
['t.foo', '', ['t.foo AS c1_foo']],
['t.foo', 'bar', ['t.foo AS c1_bar']],
];
}
/**
* Test adding single field, and retrieving it
*
* @param string $sql
* @param string $alias
* @param array $expectedselect
*
* @dataProvider add_field_provider
*/
public function test_add_field(string $sql, string $alias, array $expectedselect): void {
$column = $this->create_column('test')
->set_index(1)
->add_field($sql, $alias);
$this->assertEquals($expectedselect, $column->get_fields());
}
/**
* Test adding params to field, and retrieving them
*/
public function test_add_field_with_params(): void {
[$param0, $param1] = database::generate_param_names(2);
$column = $this->create_column('test')
->set_index(1)
->add_field(":{$param0}", 'foo', [$param0 => 'foo'])
->add_field(":{$param1}", 'bar', [$param1 => 'bar']);
// Select will look like the following: "p<index>_rbparam<counter>", where index is the column index and counter is
// a static value of the report helper class.
$fields = $column->get_fields();
$this->assertCount(2, $fields);
preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_foo/', $fields[0], $matches);
$this->assertArrayHasKey('paramname', $matches);
$fieldparam0 = $matches['paramname'];
preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_bar/', $fields[1], $matches);
$this->assertArrayHasKey('paramname', $matches);
$fieldparam1 = $matches['paramname'];
// Ensure column parameters have been renamed appropriately.
$this->assertEquals([
$fieldparam0 => 'foo',
$fieldparam1 => 'bar',
], $column->get_params());
}
/**
* Test adding field with alias as part of SQL throws an exception
*/
public function test_add_field_alias_in_sql(): void {
$column = $this->create_column('test')
->set_index(1);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Column alias must be passed as a separate argument');
$column->add_field('foo AS bar');
}
/**
* Test adding field with complex SQL without an alias throws an exception
*/
public function test_add_field_complex_without_alias(): void {
global $DB;
$column = $this->create_column('test')
->set_index(1);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Complex columns must have an alias');
$column->add_field($DB->sql_concat('foo', 'bar'));
}
/**
* Data provider for {@see test_add_fields}
*
* @return array
*/
public function add_fields_provider(): array {
return [
['t.foo', ['t.foo AS c1_foo']],
['t.foo bar', ['t.foo AS c1_bar']],
['t.foo AS bar', ['t.foo AS c1_bar']],
['t.foo1, t.foo2 bar, t.foo3 AS baz', ['t.foo1 AS c1_foo1', 't.foo2 AS c1_bar', 't.foo3 AS c1_baz']],
];
}
/**
* Test adding fields to a column, and retrieving them
*
* @param string $sql
* @param array $expectedselect
*
* @dataProvider add_fields_provider
*/
public function test_add_fields(string $sql, array $expectedselect): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields($sql);
$this->assertEquals($expectedselect, $column->get_fields());
}
/**
* Test column alias
*/
public function test_column_alias(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.bar');
$this->assertEquals('c1_foo', $column->get_column_alias());
}
/**
* Test column alias with a field containing an alias
*/
public function test_column_alias_with_field_alias(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('COALESCE(t.foo, t.bar)', 'lionel');
$this->assertEquals('c1_lionel', $column->get_column_alias());
}
/**
* Test alias of column without any fields throws exception
*/
public function test_column_alias_no_fields(): void {
$column = $this->create_column('test');
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Column ' . $column->get_unique_identifier() . ' contains no fields');
$column->add_field($column->get_column_alias());
}
/**
* Test setting column group by SQL
*/
public function test_set_groupby_sql(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('COALESCE(t.foo, t.bar)', 'lionel')
->set_groupby_sql('t.id');
$this->assertEquals(['t.id'], $column->get_groupby_sql());
}
/**
* Test getting default column group by SQL
*/
public function test_get_groupby_sql(): void {
global $DB;
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.bar');
// The behaviour of this method differs due to DB limitations.
$usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
if ($usealias) {
$expected = ['c1_foo', 'c1_bar'];
} else {
$expected = ['t.foo', 't.bar'];
}
$this->assertEquals($expected, $column->get_groupby_sql());
}
/**
* Data provider for {@see test_get_default_value} and {@see test_format_value}
*
* @return array[]
*/
public function column_type_provider(): array {
return [
[column::TYPE_INTEGER, 42],
[column::TYPE_TEXT, 'Hello'],
[column::TYPE_TIMESTAMP, HOURSECS],
[column::TYPE_BOOLEAN, 1, true],
[column::TYPE_FLOAT, 1.23],
[column::TYPE_LONGTEXT, 'Amigos'],
];
}
/**
* Test default value is returned from selected values, with correct type
*
* @param int $columntype
* @param mixed $value
* @param mixed|null $expected Expected value, or null to indicate it should be identical to value
*
* @dataProvider column_type_provider
*/
public function test_get_default_value(int $columntype, $value, $expected = null): void {
$defaultvalue = column::get_default_value([
'value' => $value,
'foo' => 'bar',
], $columntype);
$this->assertSame($expected ?? $value, $defaultvalue);
}
/**
* Test that column value is returned correctly, with correct type
*
* @param int $columntype
* @param mixed $value
* @param mixed|null $expected Expected value, or null to indicate it should be identical to value
*
* @dataProvider column_type_provider
*/
public function test_format_value(int $columntype, $value, $expected = null): void {
$column = $this->create_column('test')
->set_index(1)
->set_type($columntype)
->add_field('t.foo');
$this->assertSame($expected ?? $value, $column->format_value([
'c1_foo' => $value,
]));
}
/**
* Test that column value with callback is returned
*/
public function test_format_value_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values) {
return $value * 2;
});
$this->assertEquals(84, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
]));
}
/**
* Test that column value with callback (using all fields) is returned
*/
public function test_format_value_callback_fields(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.baz')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values) {
return $values->foo + $values->baz;
});
$this->assertEquals(60, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
'c1_baz' => 18,
]));
}
/**
* Test that column value with callback (using arguments) is returned
*/
public function test_format_value_callback_arguments(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values, int $argument) {
return $value - $argument;
}, 10);
$this->assertEquals(32, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
]));
}
/**
* Test that column value with callback (where aggregation is not set) is returned
*/
public function test_format_value_callback_aggregation(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values, $argument, ?string $aggregation): string {
// Simple callback to return the given value, and append type of aggregation parameter.
return "{$value} " . gettype($aggregation);
});
$this->assertEquals("42 NULL", $column->format_value(['c1_foo' => 42]));
}
/**
* Test adding multiple callbacks to a column
*/
public function test_add_multiple_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_TEXT)
->add_callback(static function(string $value): string {
return strrev($value);
})
->add_callback(static function(string $value): string {
return strtoupper($value);
});
$this->assertEquals('LIONEL', $column->format_value([
'c1_foo' => 'lenoil',
]));
}
/**
* Test that setting column callback overwrites previous callbacks
*/
public function test_set_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_TEXT)
->add_callback(static function(string $value): string {
return strrev($value);
})
->set_callback(static function(string $value): string {
return strtoupper($value);
});
$this->assertEquals('LENOIL', $column->format_value([
'c1_foo' => 'lenoil',
]));
}
/**
* Test is sortable
*/
public function test_is_sortable(): void {
$column = $this->create_column('test');
$this->assertFalse($column->get_is_sortable());
$column->set_is_sortable(true);
$this->assertTrue($column->get_is_sortable());
}
/**
* Test retrieving sort fields
*/
public function test_get_sortfields(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.bar, t.baz')
->set_is_sortable(true, ['t.baz', 't.bar']);
$this->assertEquals(['c1_baz', 'c1_bar'], $column->get_sort_fields());
}
/**
* Test retrieving sort fields when an aliased field is set as sortable
*/
public function test_get_sortfields_with_field_alias(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->add_field('COALESCE(t.foo, t.bar)', 'lionel')
->set_is_sortable(true, ['lionel']);
$this->assertEquals(['c1_lionel'], $column->get_sort_fields());
}
/**
* Test retrieving sort fields when an unknown field is set as sortable
*/
public function test_get_sortfields_unknown_field(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo')
->set_is_sortable(true, ['t.baz']);
$this->assertEquals(['t.baz'], $column->get_sort_fields());
}
/**
* Test is available
*/
public function test_is_available(): void {
$column = $this->create_column('test');
$this->assertTrue($column->get_is_available());
$column->set_is_available(true);
$this->assertTrue($column->get_is_available());
}
/**
* Helper method to create a column instance
*
* @param string $name
* @param lang_string|null $title
* @param string $entityname
* @return column
*/
private function create_column(string $name, ?lang_string $title = null, string $entityname = 'column_testcase'): column {
return new column($name, $title, $entityname);
}
}
@@ -0,0 +1,231 @@
<?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_reportbuilder\local\report;
use advanced_testcase;
use lang_string;
use moodle_exception;
use core_reportbuilder\local\filters\text;
/**
* Unit tests for a report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\report\filter
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter_test extends advanced_testcase {
/**
* Test getting filter class
*/
public function test_get_filter_class(): void {
$filter = $this->create_filter('username');
$this->assertEquals(text::class, $filter->get_filter_class());
}
/**
* Test specifying invalid filter class
*/
public function test_invalid_filter_class(): void {
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage('Invalid filter (sillyclass)');
new filter('sillyclass', 'username', new lang_string('username'), 'filter_testcase');
}
/**
* Test getting name
*/
public function test_get_name(): void {
$filter = $this->create_filter('username');
$this->assertEquals('username', $filter->get_name());
}
/**
* Test getting header
*/
public function test_get_header(): void {
$filter = $this->create_filter('username');
$this->assertEquals('Username', $filter->get_header());
}
/**
* Test setting header
*/
public function test_set_header(): void {
$filter = $this->create_filter('username')
->set_header(new lang_string('firstname'));
$this->assertEquals('First name', $filter->get_header());
}
/**
* Test getting entity name
*/
public function test_get_entity_name(): void {
$filter = $this->create_filter('username');
$this->assertEquals('filter_testcase', $filter->get_entity_name());
}
/**
* Test getting unique identifier
*/
public function test_get_unique_identifier(): void {
$filter = $this->create_filter('username');
$this->assertEquals('filter_testcase:username', $filter->get_unique_identifier());
}
/**
* Test getting field SQL
*/
public function test_get_field_sql(): void {
$filter = $this->create_filter('username', 'u.username');
$this->assertEquals('u.username', $filter->get_field_sql());
}
/**
* Test getting field params
*/
public function test_get_field_params(): void {
$filter = $this->create_filter('username', 'u.username = :foo', ['foo' => 'bar']);
$this->assertEquals(['foo' => 'bar'], $filter->get_field_params());
}
/**
* Test getting field SQL and params, while providing index for uniqueness
*/
public function test_get_field_sql_and_params(): void {
$filter = $this->create_filter('username', 'u.username = :username AND u.idnumber = :idnumber',
['username' => 'test', 'idnumber' => 'bar']);
[$sql, $params] = $filter->get_field_sql_and_params(1);
$this->assertEquals('u.username = :username_1 AND u.idnumber = :idnumber_1', $sql);
$this->assertEquals(['username_1' => 'test', 'idnumber_1' => 'bar'], $params);
}
/**
* Test adding single join
*/
public function test_add_join(): void {
$filter = $this->create_filter('username', 'u.username');
$this->assertEquals([], $filter->get_joins());
$filter->add_join('JOIN {user} u ON u.id = table.userid');
$this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $filter->get_joins());
}
/**
* Test adding multiple joins
*/
public function test_add_joins(): void {
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$filter = $this->create_filter('username', 'u.username')
->add_joins($tablejoins);
$this->assertEquals($tablejoins, $filter->get_joins());
}
/**
* Test is available
*/
public function test_is_available(): void {
$filter = $this->create_filter('username', 'u.username');
$this->assertTrue($filter->get_is_available());
$filter->set_is_available(true);
$this->assertTrue($filter->get_is_available());
}
/**
* Test setting filter options
*/
public function test_set_options(): void {
$filter = $this->create_filter('username', 'u.username')
->set_options([1, 2, 3]);
$this->assertEquals([1, 2, 3], $filter->get_options());
}
/**
* Test setting filter options via callback
*/
public function test_set_options_callback(): void {
$filter = $this->create_filter('username', 'u.username')
->set_options_callback(static function() {
return 10 * 5;
});
$this->assertEquals(50, $filter->get_options());
}
/**
* Test restricting filter operators
*/
public function test_limited_operators(): void {
$filter = $this->create_filter('username', 'u.username')
->set_limited_operators([
text::IS_EQUAL_TO,
text::IS_NOT_EQUAL_TO,
]);
$limitedoperators = $filter->restrict_limited_operators([
text::CONTAINS => 'Contains',
text::DOES_NOT_CONTAIN => 'Does not contain',
text::IS_EQUAL_TO => 'Is equal to',
text::IS_NOT_EQUAL_TO => 'Is not equal to',
]);
$this->assertEquals([
text::IS_EQUAL_TO => 'Is equal to',
text::IS_NOT_EQUAL_TO => 'Is not equal to',
], $limitedoperators);
}
/**
* Test not restricting filter operators
*/
public function test_unlimited_operators(): void {
$filter = $this->create_filter('username', 'u.username');
$operators = [
text::CONTAINS => 'Contains',
text::DOES_NOT_CONTAIN => 'Does not contain',
];
// If no operator limit has been set for the filter, then all available operators should be present.
$this->assertEquals($operators, $filter->restrict_limited_operators($operators));
}
/**
* Helper method to create a filter instance
*
* @param string $name
* @param string $fieldsql
* @param array $fieldparams
* @return filter
*/
private function create_filter(string $name, string $fieldsql = '', array $fieldparams = []): filter {
return new filter(text::class, $name, new lang_string($name), 'filter_testcase', $fieldsql, $fieldparams);
}
}
+215
View File
@@ -0,0 +1,215 @@
<?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_reportbuilder;
use context_system;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_user\reportbuilder\datasource\users;
use stdClass;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\report\base;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for the report manager class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\manager
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager_test extends core_reportbuilder_testcase {
/**
* Test creating a report instance from persistent
*/
public function test_get_report_from_persistent(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
$this->resetAfterTest();
$report = manager::create_report_persistent((object) [
'type' => base::TYPE_SYSTEM_REPORT,
'source' => system_report_available::class,
]);
$systemreport = manager::get_report_from_persistent($report);
$this->assertInstanceOf(system_report::class, $systemreport);
}
/**
* Test creating a report instance from persistent differs per-user, using a report source whose own initialization is
* dependent on the current user (the users report source, loading available user profile fields)
*
* Note: internally the {@see get_custom_report_content} test helper calls {@see manager::get_report_from_persistent}
*/
public function test_get_report_from_persistent_per_user(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Custom profile field, visible only to the admin.
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'text', 'name' => 'Text field', 'datatype' => 'text', 'visible' => 0]);
$user = $this->getDataGenerator()->create_user(['username' => 'usertwo', 'profile_field_text' => 'Hello']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Hidden profile field', 'source' => users::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username', 'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_text']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['admin', ''],
['usertwo', 'Hello'],
], array_map('array_values', $content));
// Now switch to second, non-admin, user.
$this->setUser($user);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
['admin'],
['usertwo'],
], array_map('array_values', $content));
}
/**
* Test creating a report instance from persistent with an invalid source
*/
public function test_get_report_from_persistent_invalid(): void {
$this->resetAfterTest();
$report = manager::create_report_persistent((object) [
'type' => base::TYPE_SYSTEM_REPORT,
'source' => stdClass::class,
]);
$this->expectException(source_invalid_exception::class);
manager::get_report_from_persistent($report);
}
/**
* Test creating a report instance from persistent with an unavailable source
*/
public function test_get_report_from_persistent_unavailable(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_unavailable.php");
$this->resetAfterTest();
$report = manager::create_report_persistent((object) [
'type' => base::TYPE_SYSTEM_REPORT,
'source' => system_report_unavailable::class,
]);
$this->expectException(source_unavailable_exception::class);
manager::get_report_from_persistent($report);
}
/**
* Test report source exists
*/
public function test_report_source_exists(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
$this->assertTrue(manager::report_source_exists(system_report_available::class));
$this->assertFalse(manager::report_source_exists(stdClass::class));
}
/**
* Test report source available
*/
public function test_report_source_available(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
$this->assertTrue(manager::report_source_available(system_report_available::class));
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_unavailable.php");
$this->assertFalse(manager::report_source_available(system_report_unavailable::class));
}
/**
* Test creating a report persistent model
*/
public function test_create_report_persistent(): void {
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/fixtures/system_report_available.php");
$this->resetAfterTest();
$report = manager::create_report_persistent((object) [
'type' => base::TYPE_SYSTEM_REPORT,
'source' => \core_reportbuilder\system_report_available::class,
]);
$this->assertInstanceOf(report::class, $report);
$this->assertEquals(base::TYPE_SYSTEM_REPORT, $report->get('type'));
$this->assertEquals(system_report_available::class, $report->get('source'));
$this->assertInstanceOf(context_system::class, $report->get_context());
}
/**
* Data provider for {@see test_report_limit_reached}
*
* @return array
*/
public function report_limit_reached_provider(): array {
return [
[0, 1, false],
[1, 1, true],
[2, 1, false],
[1, 2, true],
];
}
/**
* Test test_report_limit_reached method to check site custom reports limit
*
* @param int $customreportslimit
* @param int $existingreports
* @param bool $expected
* @dataProvider report_limit_reached_provider
*/
public function test_report_limit_reached(int $customreportslimit, int $existingreports, bool $expected): void {
global $CFG;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
for ($i = 1; $i <= $existingreports; $i++) {
$generator->create_report(['name' => 'Limited report '.$i, 'source' => users::class]);
}
// Set current custom report limit, and check whether the limit has been reached.
$CFG->customreportslimit = $customreportslimit;
$this->assertEquals($expected, manager::report_limit_reached());
}
}
@@ -0,0 +1,105 @@
<?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_reportbuilder\output;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the audience heading editable class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\output\audience_heading_editable
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience_heading_editable_test extends advanced_testcase {
/**
* Test update method
*/
public function test_update(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$persistent = $audience->get_persistent();
$editable = audience_heading_editable::update($persistent->get('id'), 'New name');
$result = $editable->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('New name', $result['value']);
// Reload persistent, assert update.
$this->assertEquals('New name', $persistent->read()->get('heading'));
}
/**
* Test update method for a user without permission to edit reports
*/
public function test_update_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
audience_heading_editable::update($audience->get_persistent()->get('id'), 'New name');
}
/**
* Test update method via component callback
*
* @covers ::core_reportbuilder_inplace_editable
*/
public function test_update_callback(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]);
$persistent = $audience->get_persistent();
$params = ['audienceheading', $persistent->get('id'), 'New name'];
$editable = component_callback('core_reportbuilder', 'inplace_editable', $params);
$this->assertInstanceOf(audience_heading_editable::class, $editable);
// Reload persistent, assert update.
$this->assertEquals('New name', $persistent->read()->get('heading'));
}
}
@@ -0,0 +1,101 @@
<?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_reportbuilder\output;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the column aggregation editable class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\output\column_aggregation_editable
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_aggregation_editable_test extends advanced_testcase {
/**
* Test update method
*/
public function test_update(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$editable = column_aggregation_editable::update($column->get('id'), 'count');
$result = $editable->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('count', $result['value']);
// Reload persistent, assert update.
$this->assertEquals('count', $column->read()->get('aggregation'));
}
/**
* Test update method for a user without permission to edit reports
*/
public function test_update_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
column_aggregation_editable::update($column->get('id'), 'New name');
}
/**
* Test update method via component callback
*
* @covers ::core_reportbuilder_inplace_editable
*/
public function test_update_callback(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$params = ['columnaggregation', $column->get('id'), 'count'];
$editable = component_callback('core_reportbuilder', 'inplace_editable', $params);
$this->assertInstanceOf(column_aggregation_editable::class, $editable);
// Reload persistent, assert update.
$this->assertEquals('count', $column->read()->get('aggregation'));
}
}
@@ -0,0 +1,101 @@
<?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_reportbuilder\output;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the column heading editable class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\output\column_heading_editable
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_heading_editable_test extends advanced_testcase {
/**
* Test update method
*/
public function test_update(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$editable = column_heading_editable::update($column->get('id'), 'New name');
$result = $editable->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('New name', $result['value']);
// Reload persistent, assert update.
$this->assertEquals('New name', $column->read()->get('heading'));
}
/**
* Test update method for a user without permission to edit reports
*/
public function test_update_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
column_heading_editable::update($column->get('id'), 'New name');
}
/**
* Test update method via component callback
*
* @covers ::core_reportbuilder_inplace_editable
*/
public function test_update_callback(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$params = ['columnheading', $column->get('id'), 'New name'];
$editable = component_callback('core_reportbuilder', 'inplace_editable', $params);
$this->assertInstanceOf(column_heading_editable::class, $editable);
// Reload persistent, assert update.
$this->assertEquals('New name', $column->read()->get('heading'));
}
}
@@ -0,0 +1,101 @@
<?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_reportbuilder\output;
use advanced_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\report_access_exception;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for the filter heading editable class
*
* @package core_reportbuilder
* @covers \core_reportbuilder\output\filter_heading_editable
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter_heading_editable_test extends advanced_testcase {
/**
* Test update method
*/
public function test_update(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$editable = filter_heading_editable::update($filter->get('id'), 'New name');
$result = $editable->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('New name', $result['value']);
// Reload persistent, assert update.
$this->assertEquals('New name', $filter->read()->get('heading'));
}
/**
* Test update method for a user without permission to edit reports
*/
public function test_update_access_exception(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(report_access_exception::class);
$this->expectExceptionMessage('You cannot edit this report');
filter_heading_editable::update($filter->get('id'), 'New name');
}
/**
* Test update method via component callback
*
* @covers ::core_reportbuilder_inplace_editable
*/
public function test_update_callback(): void {
$this->resetAfterTest();
$this->setAdminUser();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
$filter = $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
$params = ['filterheading', $filter->get('id'), 'New name'];
$editable = component_callback('core_reportbuilder', 'inplace_editable', $params);
$this->assertInstanceOf(filter_heading_editable::class, $editable);
// Reload persistent, assert update.
$this->assertEquals('New name', $filter->read()->get('heading'));
}
}

Some files were not shown because too many files have changed in this diff Show More