Differences Between: [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 declare(strict_types=1); 18 19 namespace core_files\reportbuilder\datasource; 20 21 use core\context\{course, coursecat, user}; 22 use core_reportbuilder_generator; 23 use core_reportbuilder_testcase; 24 use core_reportbuilder\local\filters\{boolean_select, date, number, select, text}; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php"); 30 31 /** 32 * Unit tests for files datasource 33 * 34 * @package core_files 35 * @covers \core_files\reportbuilder\datasource\files 36 * @copyright 2022 Paul Holden <paulh@moodle.com> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class files_test extends core_reportbuilder_testcase { 40 41 /** 42 * Test default datasource 43 */ 44 public function test_datasource_default(): void { 45 $this->resetAfterTest(); 46 47 $course = $this->getDataGenerator()->create_course(); 48 $coursecontext = course::instance($course->id); 49 50 $user = $this->getDataGenerator()->create_user(); 51 $usercontext = user::instance($user->id); 52 53 $this->setUser($user); 54 55 $this->generate_test_files($coursecontext); 56 57 /** @var core_reportbuilder_generator $generator */ 58 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 59 $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 1]); 60 61 $content = $this->get_custom_report_content($report->get('id')); 62 $content = $this->filter_custom_report_content($content, static function(array $row): bool { 63 return $row['c0_ctxid'] !== 'System'; 64 }); 65 66 $this->assertCount(2, $content); 67 68 // Default columns are context, user, name, type, size, time created. Sorted by context and time created. 69 [$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[0]); 70 $this->assertEquals($coursecontext->get_context_name(), $contextname); 71 $this->assertEquals(fullname($user), $userfullname); 72 $this->assertEquals('Hello.txt', $filename); 73 $this->assertEquals('Text file', $mimetype); 74 $this->assertEquals("5\xc2\xa0bytes", $filesize); 75 $this->assertNotEmpty($timecreated); 76 77 [$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[1]); 78 $this->assertEquals($usercontext->get_context_name(), $contextname); 79 $this->assertEquals(fullname($user), $userfullname); 80 $this->assertEquals('Hello.txt', $filename); 81 $this->assertEquals('Text file', $mimetype); 82 $this->assertEquals("5\xc2\xa0bytes", $filesize); 83 $this->assertNotEmpty($timecreated); 84 } 85 86 /** 87 * Test datasource columns that aren't added by default 88 */ 89 public function test_datasource_non_default_columns(): void { 90 $this->resetAfterTest(); 91 $this->setAdminUser(); 92 93 $category = $this->getDataGenerator()->create_category(); 94 $categorycontext = coursecat::instance($category->id); 95 96 $course = $this->getDataGenerator()->create_course(['category' => $category->id]); 97 $coursecontext = course::instance($course->id); 98 99 $user = $this->getDataGenerator()->create_user(); 100 $usercontext = user::instance($user->id); 101 102 $this->setUser($user); 103 104 $draftitemid = $this->generate_test_files($coursecontext); 105 106 /** @var core_reportbuilder_generator $generator */ 107 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 108 $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]); 109 110 // Consistent order, sorted by context and content hash. 111 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link', 112 'sortenabled' => 1, 'sortorder' => 1]); 113 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']); 114 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:level']); 115 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:path']); 116 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:parent']); 117 118 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:path']); 119 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:author']); 120 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:license']); 121 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:contenthash', 122 'sortenabled' => 1, 'sortorder' => 2]); 123 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:component']); 124 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:area']); 125 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:itemid']); 126 127 $content = $this->get_custom_report_content($report->get('id')); 128 $content = $this->filter_custom_report_content($content, static function(array $row): bool { 129 return stripos($row['c0_ctxid'], 'System') === false; 130 }); 131 132 // There should be two entries (directory & file) for each context. 133 $this->assertEquals([ 134 [ 135 "<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>", 136 $coursecontext->get_context_name(), 137 'Course', 138 $coursecontext->path, 139 $categorycontext->get_context_name(), 140 '/', 141 null, 142 '', 143 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 144 'course', 145 'summary', 146 0, 147 ], 148 [ 149 "<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>", 150 $coursecontext->get_context_name(), 151 'Course', 152 $coursecontext->path, 153 $categorycontext->get_context_name(), 154 '/', 155 null, 156 '', 157 'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0', 158 'course', 159 'summary', 160 0, 161 ], 162 [ 163 "<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>", 164 $usercontext->get_context_name(), 165 'User', 166 $usercontext->path, 167 'System', 168 '/', 169 null, 170 '', 171 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 172 'user', 173 'draft', 174 $draftitemid, 175 ], 176 [ 177 "<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>", 178 $usercontext->get_context_name(), 179 'User', 180 $usercontext->path, 181 'System', 182 '/', 183 null, 184 '', 185 'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0', 186 'user', 187 'draft', 188 $draftitemid, 189 ], 190 ], array_map('array_values', $content)); 191 } 192 193 /** 194 * Data provider for {@see test_datasource_filters} 195 * 196 * @return array[] 197 */ 198 public function datasource_filters_provider(): array { 199 return [ 200 // File. 201 'Filter directory' => ['file:directory', [ 202 'file:directory_operator' => boolean_select::CHECKED, 203 ], 2], 204 'Filter draft' => ['file:draft', [ 205 'file:draft_operator' => boolean_select::CHECKED, 206 ], 2], 207 'Filter name' => ['file:name', [ 208 'file:name_operator' => text::IS_EQUAL_TO, 209 'file:name_value' => 'Hello.txt', 210 ], 2], 211 'Filter size' => ['file:size', [ 212 'file:size_operator' => number::GREATER_THAN, 213 'file:size_value1' => 2, 214 ], 2], 215 'Filter license' => ['file:license', [ 216 'file:license_operator' => select::EQUAL_TO, 217 'file:license_value' => 'unknown', 218 ], 4], 219 'Filter license (non match)' => ['file:license', [ 220 'file:license_operator' => select::EQUAL_TO, 221 'file:license_value' => 'public', 222 ], 0], 223 'Filter content hash' => ['file:contenthash', [ 224 'file:contenthash_operator' => text::IS_EQUAL_TO, 225 'file:contenthash_value' => 'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0', 226 ], 2], 227 'Filter content hash (no match)' => ['file:contenthash', [ 228 'file:contenthash_operator' => text::IS_EQUAL_TO, 229 'file:contenthash_value' => 'f00f', 230 ], 0], 231 'Filter time created' => ['file:timecreated', [ 232 'file:timecreated_operator' => date::DATE_RANGE, 233 'file:timecreated_from' => 1622502000, 234 ], 4], 235 'Filter time created (non match)' => ['file:timecreated', [ 236 'file:timecreated_operator' => date::DATE_RANGE, 237 'file:timecreated_to' => 1622502000, 238 ], 0], 239 240 // Context. 241 'Context level' => ['context:level', [ 242 'context:level_operator' => select::EQUAL_TO, 243 'context:level_value' => CONTEXT_COURSE, 244 ], 2], 245 'Context level (no match)' => ['context:level', [ 246 'context:level_operator' => select::EQUAL_TO, 247 'context:level_value' => CONTEXT_BLOCK, 248 ], 0], 249 'Context path' => ['context:path', [ 250 'context:path_operator' => text::STARTS_WITH, 251 'context:path_value' => '/1/', 252 ], 4], 253 'Context path (no match)' => ['context:path', [ 254 'context:path_operator' => text::STARTS_WITH, 255 'context:path_value' => '/1/2/3/', 256 ], 0], 257 258 // User. 259 'Filter user' => ['user:username', [ 260 'user:username_operator' => text::IS_EQUAL_TO, 261 'user:username_value' => 'alfie', 262 ], 4], 263 'Filter user (no match)' => ['user:username', [ 264 'user:username_operator' => text::IS_EQUAL_TO, 265 'user:username_value' => 'lionel', 266 ], 0], 267 ]; 268 } 269 270 /** 271 * Test datasource filters 272 * 273 * @param string $filtername 274 * @param array $filtervalues 275 * @param int $expectmatchcount 276 * 277 * @dataProvider datasource_filters_provider 278 */ 279 public function test_datasource_filters( 280 string $filtername, 281 array $filtervalues, 282 int $expectmatchcount 283 ): void { 284 $this->resetAfterTest(); 285 $this->setAdminUser(); 286 287 $user = $this->getDataGenerator()->create_user(['username' => 'alfie']); 288 $this->setUser($user); 289 290 $course = $this->getDataGenerator()->create_course(); 291 $coursecontext = course::instance($course->id); 292 293 $this->generate_test_files($coursecontext); 294 295 /** @var core_reportbuilder_generator $generator */ 296 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 297 298 // Create report containing single column, and given filter. 299 $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]); 300 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']); 301 302 // Add filter, set it's values. 303 $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]); 304 $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues); 305 $content = $this->filter_custom_report_content($content, static function(array $row): bool { 306 return stripos($row['c0_ctxid'], 'System') === false; 307 }); 308 309 $this->assertCount($expectmatchcount, $content); 310 } 311 312 /** 313 * Stress test datasource 314 * 315 * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php 316 */ 317 public function test_stress_datasource(): void { 318 if (!PHPUNIT_LONGTEST) { 319 $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); 320 } 321 322 $this->resetAfterTest(); 323 $this->setAdminUser(); 324 325 $course = $this->getDataGenerator()->create_course(); 326 $coursecontext = course::instance($course->id); 327 328 $this->generate_test_files($coursecontext); 329 330 $this->datasource_stress_test_columns(files::class); 331 $this->datasource_stress_test_columns_aggregation(files::class); 332 $this->datasource_stress_test_conditions(files::class, 'file:path'); 333 } 334 335 /** 336 * Ensuring report content only includes files we have explicitly created within the test 337 * 338 * @param array $content 339 * @param callable $callback 340 * @return array 341 */ 342 protected function filter_custom_report_content(array $content, callable $callback): array { 343 $content = array_filter($content, $callback); 344 return array_values($content); 345 } 346 347 /** 348 * Helper method to generate some test files (a user draft and course summary file) for reporting on 349 * 350 * @param course $context 351 * @return int Draft item ID 352 */ 353 protected function generate_test_files(course $context): int { 354 global $USER; 355 356 $draftitemid = file_get_unused_draft_itemid(); 357 358 // Populate user draft. 359 get_file_storage()->create_file_from_string([ 360 'contextid' => user::instance($USER->id)->id, 361 'userid' => $USER->id, 362 'component' => 'user', 363 'filearea' => 'draft', 364 'itemid' => $draftitemid, 365 'filepath' => '/', 366 'filename' => 'Hello.txt', 367 ], 'Hello'); 368 369 // Save draft to course summary file area. 370 file_save_draft_area_files($draftitemid, $context->id, 'course', 'summary', 0); 371 372 return $draftitemid; 373 } 374 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body