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
@@ -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');
}
}