Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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_reportbuilder\local\helpers;
20
 
21
use core_customfield_generator;
22
use core_reportbuilder_generator;
23
use core_reportbuilder\local\entities\course;
1441 ariadna 24
use core_reportbuilder\local\filters\{boolean_select, date, number, select, text};
25
use core_reportbuilder\local\report\{column, filter};
26
use core_reportbuilder\tests\core_reportbuilder_testcase;
1 efrain 27
use core_course\reportbuilder\datasource\{categories, courses};
28
 
29
/**
30
 * Unit tests for custom fields helper
31
 *
32
 * @package     core_reportbuilder
33
 * @covers      \core_reportbuilder\local\helpers\custom_fields
34
 * @copyright   2021 David Matamoros <davidmc@moodle.com>
35
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
1441 ariadna 37
final class custom_fields_test extends core_reportbuilder_testcase {
1 efrain 38
 
39
    /**
40
     * Generate custom fields, one of each type
41
     *
42
     * @return custom_fields
43
     */
44
    private function generate_customfields(): custom_fields {
45
 
46
        /** @var core_customfield_generator $generator */
47
        $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
48
        $category = $generator->create_category([
49
            'component' => 'core_course',
50
            'area' => 'course',
51
            'itemid' => 0,
52
            'contextid' => \context_system::instance()->id
53
        ]);
54
 
55
        $generator->create_field(
1441 ariadna 56
            ['categoryid' => $category->get('id'), 'type' => 'text', 'name' => 'Text', 'shortname' => 'text',
57
                'configdata' => ['defaultvalue' => 'default']]);
1 efrain 58
 
59
        $generator->create_field(
1441 ariadna 60
            ['categoryid' => $category->get('id'), 'type' => 'textarea', 'name' => 'Textarea', 'shortname' => 'textarea',
61
                'configdata' => ['defaultvalue' => 'Default']]);
1 efrain 62
 
1441 ariadna 63
        // This field is available only to course teachers.
1 efrain 64
        $generator->create_field(
1441 ariadna 65
            ['categoryid' => $category->get('id'), 'type' => 'checkbox', 'name' => 'Checkbox', 'shortname' => 'checkbox',
66
                'configdata' => ['checkbydefault' => 1, 'visibility' => 1]]);
1 efrain 67
 
68
        $generator->create_field(
69
            ['categoryid' => $category->get('id'), 'type' => 'date', 'name' => 'Date', 'shortname' => 'date']);
70
 
71
        $generator->create_field(
72
            ['categoryid' => $category->get('id'), 'type' => 'select', 'name' => 'Select', 'shortname' => 'select',
1441 ariadna 73
                'configdata' => ['options' => "Cat\nDog\nFish", 'defaultvalue' => 'Cat']]);
1 efrain 74
 
1441 ariadna 75
        $generator->create_field(
76
            ['categoryid' => $category->get('id'), 'type' => 'number', 'name' => 'Number', 'shortname' => 'number',
77
                'configdata' => ['defaultvalue' => 1]]);
78
 
1 efrain 79
        $courseentity = new course();
80
        $coursealias = $courseentity->get_table_alias('course');
81
 
82
        // Create an instance of the customfields helper.
1441 ariadna 83
        return new custom_fields("{$coursealias}.id", $courseentity->get_entity_name(), 'core_course', 'course');
1 efrain 84
    }
85
 
86
    /**
87
     * Test for get_columns
88
     */
89
    public function test_get_columns(): void {
90
        $this->resetAfterTest();
1441 ariadna 91
        $this->setAdminUser();
1 efrain 92
 
93
        $customfields = $this->generate_customfields();
1441 ariadna 94
 
1 efrain 95
        $columns = $customfields->get_columns();
1441 ariadna 96
        $this->assertCount(6, $columns);
1 efrain 97
        $this->assertContainsOnlyInstancesOf(column::class, $columns);
98
 
99
        // Column titles.
1441 ariadna 100
        $this->assertEquals([
101
            'Text',
102
            'Textarea',
103
            'Checkbox',
104
            'Date',
105
            'Select',
106
            'Number',
107
        ], array_map(
108
            fn(column $column) => $column->get_title(),
109
            $columns,
110
        ));
1 efrain 111
 
112
        // Column types.
1441 ariadna 113
        $this->assertEquals([
114
            column::TYPE_TEXT,
115
            column::TYPE_LONGTEXT,
116
            column::TYPE_BOOLEAN,
117
            column::TYPE_TIMESTAMP,
118
            column::TYPE_TEXT,
119
            column::TYPE_FLOAT,
120
        ], array_map(
121
            fn(column $column) => $column->get_type(),
122
            $columns,
123
        ));
1 efrain 124
 
125
        // Column sortable.
1441 ariadna 126
        $this->assertEquals([
127
            true,
128
            true,
129
            true,
130
            true,
131
            true,
132
            true,
133
        ], array_map(
134
            fn(column $column) => $column->get_is_sortable(),
135
            $columns,
136
        ));
137
 
138
        // Column available.
139
        $this->assertEquals([
140
            true,
141
            true,
142
            true,
143
            true,
144
            true,
145
            true,
146
        ], array_map(
147
            fn(column $column) => $column->get_is_available(),
148
            $columns,
149
        ));
150
 
151
        // Column available, for non-privileged user.
152
        $this->setUser(null);
153
        $this->assertEquals([
154
            true,
155
            true,
156
            false,
157
            true,
158
            true,
159
            true,
160
        ], array_map(
161
            fn(column $column) => $column->get_is_available(),
162
            $customfields->get_columns(),
163
        ));
1 efrain 164
    }
165
 
166
    /**
1441 ariadna 167
     * Test that joins added to the custom fields helper are present in its columns/filters
1 efrain 168
     */
169
    public function test_add_join(): void {
170
        $this->resetAfterTest();
171
 
172
        $customfields = $this->generate_customfields();
173
 
1441 ariadna 174
        // We always join on the customfield data table.
175
        $columnjoins = $customfields->get_columns()[0]->get_joins();
176
        $this->assertCount(1, $columnjoins);
177
        $this->assertStringStartsWith('LEFT JOIN {customfield_data}', $columnjoins[0]);
1 efrain 178
 
1441 ariadna 179
        $filterjoins = $customfields->get_filters()[0]->get_joins();
180
        $this->assertCount(1, $filterjoins);
181
        $this->assertStringStartsWith('LEFT JOIN {customfield_data}', $filterjoins[0]);
1 efrain 182
 
183
        // Add additional join.
184
        $customfields->add_join('JOIN {test} t ON t.id = id');
185
 
1441 ariadna 186
        $columnjoins = $customfields->get_columns()[0]->get_joins();
187
        $this->assertCount(2, $columnjoins);
188
        $this->assertEquals('JOIN {test} t ON t.id = id', $columnjoins[0]);
189
        $this->assertStringStartsWith('LEFT JOIN {customfield_data}', $columnjoins[1]);
1 efrain 190
 
1441 ariadna 191
        $filterjoins = $customfields->get_filters()[0]->get_joins();
192
        $this->assertCount(2, $filterjoins);
193
        $this->assertEquals('JOIN {test} t ON t.id = id', $filterjoins[0]);
194
        $this->assertStringStartsWith('LEFT JOIN {customfield_data}', $filterjoins[1]);
1 efrain 195
    }
196
 
197
    /**
1441 ariadna 198
     * Test for get_filters
1 efrain 199
     */
1441 ariadna 200
    public function test_get_filters(): void {
1 efrain 201
        $this->resetAfterTest();
1441 ariadna 202
        $this->setAdminUser();
1 efrain 203
 
204
        $customfields = $this->generate_customfields();
205
 
1441 ariadna 206
        $filters = $customfields->get_filters();
207
        $this->assertCount(6, $filters);
208
        $this->assertContainsOnlyInstancesOf(filter::class, $filters);
1 efrain 209
 
1441 ariadna 210
        // Filter headers.
211
        $this->assertEquals([
212
            'Text',
213
            'Textarea',
214
            'Checkbox',
215
            'Date',
216
            'Select',
217
            'Number',
218
        ], array_map(
219
            fn(filter $filter) => $filter->get_header(),
220
            $filters,
221
        ));
1 efrain 222
 
1441 ariadna 223
        // Filter types.
224
        $this->assertEquals([
225
            text::class,
226
            text::class,
227
            boolean_select::class,
228
            date::class,
229
            select::class,
230
            number::class,
231
        ], array_map(
232
            fn(filter $filter) => $filter->get_filter_class(),
233
            $filters,
234
        ));
1 efrain 235
 
1441 ariadna 236
        // Filter available.
237
        $this->assertEquals([
238
            true,
239
            true,
240
            true,
241
            true,
242
            true,
243
            true,
244
        ], array_map(
245
            fn(filter $filter) => $filter->get_is_available(),
246
            $filters,
247
        ));
1 efrain 248
 
1441 ariadna 249
        // Filter available, for non-privileged user.
250
        $this->setUser(null);
251
        $this->assertEquals([
252
            true,
253
            true,
254
            false,
255
            true,
256
            true,
257
            true,
258
        ], array_map(
259
            fn(filter $filter) => $filter->get_is_available(),
260
            $customfields->get_filters(),
261
        ));
1 efrain 262
    }
263
 
264
    /**
265
     * Test that adding custom field columns to a report returns expected values
266
     */
267
    public function test_custom_report_content(): void {
268
        $this->resetAfterTest();
1441 ariadna 269
        $this->setAdminUser();
1 efrain 270
 
1441 ariadna 271
        $category = $this->getDataGenerator()->create_category(['name' => 'Zebras']);
272
        $courseone = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'C1']);
273
 
274
        // Second course will populate each custom field.
1 efrain 275
        $this->generate_customfields();
1441 ariadna 276
        $coursetwo = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'C2', 'customfields' => [
1 efrain 277
            ['shortname' => 'text', 'value' => 'Hello'],
278
            ['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
1441 ariadna 279
            ['shortname' => 'checkbox', 'value' => 0],
1 efrain 280
            ['shortname' => 'date', 'value' => 1669852800],
281
            ['shortname' => 'select', 'value' => 2],
1441 ariadna 282
            ['shortname' => 'number', 'value' => 42],
1 efrain 283
        ]]);
284
 
285
        /** @var core_reportbuilder_generator $generator */
286
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
1441 ariadna 287
        $report = $generator->create_report(['name' => 'Categories', 'source' => categories::class, 'default' => 0]);
1 efrain 288
 
289
        // Add custom field columns to the report.
1441 ariadna 290
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:name',
291
            'sortenabled' => 1]);
292
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname',
293
            'sortenabled' => 1]);
1 efrain 294
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_text']);
295
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_textarea']);
296
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_checkbox']);
297
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_date']);
298
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_select']);
1441 ariadna 299
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:customfield_number']);
1 efrain 300
 
301
        $content = $this->get_custom_report_content($report->get('id'));
302
        $this->assertEquals([
1441 ariadna 303
            [
304
                'Category 1',
305
                '',
306
                '',
307
                '',
308
                '',
309
                '',
310
                '',
311
                '',
312
            ],
313
            [
314
                $category->name,
315
                $courseone->fullname,
316
                'default',
317
                format_text('Default'),
318
                'Yes',
319
                '',
320
                'Cat',
321
                1,
322
            ],
323
            [
324
                $category->name,
325
                $coursetwo->fullname,
326
                'Hello',
327
                format_text('Goodbye'),
328
                'No',
329
                userdate(1669852800),
330
                'Dog',
331
                42,
332
            ],
1 efrain 333
        ], array_map('array_values', $content));
334
    }
335
 
336
    /**
337
     * Data provider for {@see test_custom_report_filter}
338
     *
339
     * @return array[]
340
     */
1441 ariadna 341
    public static function custom_report_filter_provider(): array {
1 efrain 342
        return [
343
            'Filter by text custom field' => ['course:customfield_text', [
344
                'course:customfield_text_operator' => text::IS_EQUAL_TO,
345
                'course:customfield_text_value' => 'Hello',
1441 ariadna 346
            ], 'C2'],
347
            'Filter by text custom field (default)' => ['course:customfield_text', [
348
                'course:customfield_text_operator' => text::IS_EQUAL_TO,
349
                'course:customfield_text_value' => 'default',
350
            ], 'C1'],
1 efrain 351
            'Filter by text custom field (no match)' => ['course:customfield_text', [
352
                'course:customfield_text_operator' => text::IS_EQUAL_TO,
353
                'course:customfield_text_value' => 'Goodbye',
1441 ariadna 354
            ]],
1 efrain 355
            'Filter by textarea custom field' => ['course:customfield_textarea', [
356
                'course:customfield_textarea_operator' => text::IS_EQUAL_TO,
357
                'course:customfield_textarea_value' => 'Goodbye',
1441 ariadna 358
            ], 'C2'],
359
            'Filter by textarea custom field (default)' => ['course:customfield_textarea', [
360
                'course:customfield_textarea_operator' => text::IS_EQUAL_TO,
361
                'course:customfield_textarea_value' => 'Default',
362
            ], 'C1'],
1 efrain 363
            'Filter by textarea custom field (no match)' => ['course:customfield_textarea', [
364
                'course:customfield_textarea_operator' => text::IS_EQUAL_TO,
365
                'course:customfield_textarea_value' => 'Hello',
1441 ariadna 366
            ]],
1 efrain 367
            'Filter by checkbox custom field' => ['course:customfield_checkbox', [
1441 ariadna 368
                'course:customfield_checkbox_operator' => boolean_select::NOT_CHECKED,
369
            ], 'C2'],
370
            'Filter by checkbox custom field (default)' => ['course:customfield_checkbox', [
1 efrain 371
                'course:customfield_checkbox_operator' => boolean_select::CHECKED,
1441 ariadna 372
            ], 'C1'],
1 efrain 373
            'Filter by date custom field' => ['course:customfield_date', [
374
                'course:customfield_date_operator' => date::DATE_RANGE,
375
                'course:customfield_date_from' => 1622502000,
1441 ariadna 376
            ], 'C2'],
1 efrain 377
            'Filter by date custom field (no match)' => ['course:customfield_date', [
378
                'course:customfield_date_operator' => date::DATE_RANGE,
1441 ariadna 379
                'course:customfield_date_from' => 1672531200,
380
            ]],
1 efrain 381
            'Filter by select custom field' => ['course:customfield_select', [
382
                'course:customfield_select_operator' => select::EQUAL_TO,
383
                'course:customfield_select_value' => 2,
1441 ariadna 384
            ], 'C2'],
385
            'Filter by select custom field (default)' => ['course:customfield_select', [
386
                'course:customfield_select_operator' => select::EQUAL_TO,
387
                'course:customfield_select_value' => 1,
388
            ], 'C1'],
1 efrain 389
            'Filter by select custom field (no match)' => ['course:customfield_select', [
390
                'course:customfield_select_operator' => select::EQUAL_TO,
1441 ariadna 391
                'course:customfield_select_value' => 3,
392
            ]],
393
            'Filter by number custom field' => ['course:customfield_number', [
394
                'course:customfield_number_operator' => number::EQUAL_TO,
395
                'course:customfield_number_value1' => 42,
396
            ], 'C2'],
397
            'Filter by number custom field (default)' => ['course:customfield_number', [
398
                'course:customfield_number_operator' => number::EQUAL_TO,
399
                'course:customfield_number_value1' => 1,
400
            ], 'C1'],
401
            'Filter by number custom field (no match)' => ['course:customfield_number', [
402
                'course:customfield_number_operator' => number::EQUAL_TO,
403
                'course:customfield_number_value1' => 3,
404
            ]],
1 efrain 405
        ];
406
    }
407
 
408
    /**
409
     * Test filtering report by custom fields
410
     *
411
     * @param string $filtername
412
     * @param array $filtervalues
1441 ariadna 413
     * @param string|null $expectmatch
1 efrain 414
     *
415
     * @dataProvider custom_report_filter_provider
416
     */
1441 ariadna 417
    public function test_custom_report_filter(string $filtername, array $filtervalues, ?string $expectmatch = null): void {
1 efrain 418
        $this->resetAfterTest();
1441 ariadna 419
        $this->setAdminUser();
1 efrain 420
 
1441 ariadna 421
        $this->getDataGenerator()->create_course(['fullname' => 'C1']);
422
 
423
        // Second course will populate each custom field.
1 efrain 424
        $this->generate_customfields();
1441 ariadna 425
        $this->getDataGenerator()->create_course(['fullname' => 'C2', 'customfields' => [
1 efrain 426
            ['shortname' => 'text', 'value' => 'Hello'],
427
            ['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
1441 ariadna 428
            ['shortname' => 'checkbox', 'value' => 0],
1 efrain 429
            ['shortname' => 'date', 'value' => 1669852800],
430
            ['shortname' => 'select', 'value' => 2],
1441 ariadna 431
            ['shortname' => 'number', 'value' => 42],
1 efrain 432
        ]]);
433
 
434
        /** @var core_reportbuilder_generator $generator */
435
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
436
 
437
        // Create report containing single column, and given filter.
438
        $report = $generator->create_report(['name' => 'Users', 'source' => courses::class, 'default' => 0]);
439
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
440
 
441
        // Add filter, set it's values.
442
        $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
443
        $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
444
 
1441 ariadna 445
        if ($expectmatch !== null) {
1 efrain 446
            $this->assertCount(1, $content);
1441 ariadna 447
            $this->assertEquals($expectmatch, reset($content[0]));
1 efrain 448
        } else {
449
            $this->assertEmpty($content);
450
        }
451
    }
452
 
453
    /**
454
     * Stress test course datasource using custom fields
455
     *
456
     * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
457
     */
458
    public function test_stress_datasource(): void {
459
        if (!PHPUNIT_LONGTEST) {
460
            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
461
        }
462
 
463
        $this->resetAfterTest();
464
 
465
        $this->generate_customfields();
1441 ariadna 466
        $this->getDataGenerator()->create_course(['customfields' => [
1 efrain 467
            ['shortname' => 'text', 'value' => 'Hello'],
468
            ['shortname' => 'textarea_editor', 'value' => ['text' => 'Goodbye', 'format' => FORMAT_MOODLE]],
469
            ['shortname' => 'checkbox', 'value' => true],
470
            ['shortname' => 'date', 'value' => 1669852800],
471
            ['shortname' => 'select', 'value' => 2],
472
        ]]);
473
 
474
        $this->datasource_stress_test_columns(courses::class);
475
        $this->datasource_stress_test_columns_aggregation(courses::class);
476
        $this->datasource_stress_test_conditions(courses::class, 'course:idnumber');
477
    }
478
}
479