Proyectos de Subversion Moodle

Rev

| 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\report;
20
 
21
use advanced_testcase;
22
use coding_exception;
23
use lang_string;
24
use stdClass;
25
use core_reportbuilder\local\helpers\database;
26
 
27
/**
28
 * Unit tests for a report column
29
 *
30
 * @package     core_reportbuilder
31
 * @covers      \core_reportbuilder\local\report\column
32
 * @copyright   2020 Paul Holden <paulh@moodle.com>
33
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 */
35
class column_test extends advanced_testcase {
36
 
37
    /**
38
     * Test column name getter/setter
39
     */
40
    public function test_name(): void {
41
        $column = $this->create_column('test');
42
        $this->assertEquals('test', $column->get_name());
43
 
44
        $this->assertEquals('another', $column
45
            ->set_name('another')
46
            ->get_name()
47
        );
48
    }
49
 
50
    /**
51
     * Test column title getter/setter
52
     */
53
    public function test_title(): void {
54
        $column = $this->create_column('test', new lang_string('show'));
55
        $this->assertEquals('Show', $column->get_title());
56
        $this->assertFalse($column->has_custom_title());
57
 
58
        $this->assertEquals('Hide', $column
59
            ->set_title(new lang_string('hide'))
60
            ->get_title()
61
        );
62
        $this->assertTrue($column->has_custom_title());
63
 
64
        // Column titles can also be empty.
65
        $this->assertEmpty($column
66
            ->set_title(null)
67
            ->get_title());
68
    }
69
 
70
    /**
71
     * Test entity name getter
72
     */
73
    public function test_get_entity_name(): void {
74
        $column = $this->create_column('test', null, 'entityname');
75
        $this->assertEquals('entityname', $column->get_entity_name());
76
    }
77
 
78
    /**
79
     * Test getting unique identifier
80
     */
81
    public function test_get_unique_identifier(): void {
82
        $column = $this->create_column('test', null, 'entityname');
83
        $this->assertEquals('entityname:test', $column->get_unique_identifier());
84
    }
85
 
86
    /**
87
     * Test column type getter/setter
88
     */
89
    public function test_type(): void {
90
        $column = $this->create_column('test');
91
        $this->assertEquals(column::TYPE_INTEGER, $column
92
            ->set_type(column::TYPE_INTEGER)
93
            ->get_type());
94
    }
95
 
96
    /**
97
     * Test column default type
98
     */
99
    public function test_type_default(): void {
100
        $column = $this->create_column('test');
101
        $this->assertEquals(column::TYPE_TEXT, $column->get_type());
102
    }
103
 
104
    /**
105
     * Test column type with invalid value
106
     */
107
    public function test_type_invalid(): void {
108
        $column = $this->create_column('test');
109
 
110
        $this->expectException(coding_exception::class);
111
        $this->expectExceptionMessage('Invalid column type');
112
        $column->set_type(-1);
113
    }
114
 
115
    /**
116
     * Test adding single join
117
     */
118
    public function test_add_join(): void {
119
        $column = $this->create_column('test');
120
        $this->assertEquals([], $column->get_joins());
121
 
122
        $column->add_join('JOIN {user} u ON u.id = table.userid');
123
        $this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $column->get_joins());
124
    }
125
 
126
    /**
127
     * Test adding multiple joins
128
     */
129
    public function test_add_joins(): void {
130
        $tablejoins = [
131
            "JOIN {course} c2 ON c2.id = c1.id",
132
            "JOIN {course} c3 ON c3.id = c1.id",
133
        ];
134
 
135
        $column = $this->create_column('test')
136
            ->add_joins($tablejoins);
137
 
138
        $this->assertEquals($tablejoins, $column->get_joins());
139
    }
140
 
141
    /**
142
     * Data provider for {@see test_add_field}
143
     *
144
     * @return array
145
     */
146
    public function add_field_provider(): array {
147
        return [
148
            ['foo', '', ['foo AS c1_foo']],
149
            ['foo', 'bar', ['foo AS c1_bar']],
150
            ['t.foo', '', ['t.foo AS c1_foo']],
151
            ['t.foo', 'bar', ['t.foo AS c1_bar']],
152
        ];
153
    }
154
 
155
    /**
156
     * Test adding single field, and retrieving it
157
     *
158
     * @param string $sql
159
     * @param string $alias
160
     * @param array $expectedselect
161
     *
162
     * @dataProvider add_field_provider
163
     */
164
    public function test_add_field(string $sql, string $alias, array $expectedselect): void {
165
        $column = $this->create_column('test')
166
            ->set_index(1)
167
            ->add_field($sql, $alias);
168
 
169
        $this->assertEquals($expectedselect, $column->get_fields());
170
    }
171
 
172
    /**
173
     * Test adding params to field, and retrieving them
174
     */
175
    public function test_add_field_with_params(): void {
176
        [$param0, $param1] = database::generate_param_names(2);
177
 
178
        $column = $this->create_column('test')
179
            ->set_index(1)
180
            ->add_field(":{$param0}", 'foo', [$param0 => 'foo'])
181
            ->add_field(":{$param1}", 'bar', [$param1 => 'bar']);
182
 
183
        // Select will look like the following: "p<index>_rbparam<counter>", where index is the column index and counter is
184
        // a static value of the report helper class.
185
        $fields = $column->get_fields();
186
        $this->assertCount(2, $fields);
187
 
188
        preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_foo/', $fields[0], $matches);
189
        $this->assertArrayHasKey('paramname', $matches);
190
        $fieldparam0 = $matches['paramname'];
191
 
192
        preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_bar/', $fields[1], $matches);
193
        $this->assertArrayHasKey('paramname', $matches);
194
        $fieldparam1 = $matches['paramname'];
195
 
196
        // Ensure column parameters have been renamed appropriately.
197
        $this->assertEquals([
198
            $fieldparam0 => 'foo',
199
            $fieldparam1 => 'bar',
200
        ], $column->get_params());
201
    }
202
 
203
    /**
204
     * Test adding field with alias as part of SQL throws an exception
205
     */
206
    public function test_add_field_alias_in_sql(): void {
207
        $column = $this->create_column('test')
208
            ->set_index(1);
209
 
210
        $this->expectException(coding_exception::class);
211
        $this->expectExceptionMessage('Column alias must be passed as a separate argument');
212
        $column->add_field('foo AS bar');
213
    }
214
 
215
    /**
216
     * Test adding field with complex SQL without an alias throws an exception
217
     */
218
    public function test_add_field_complex_without_alias(): void {
219
        global $DB;
220
 
221
        $column = $this->create_column('test')
222
            ->set_index(1);
223
 
224
        $this->expectException(coding_exception::class);
225
        $this->expectExceptionMessage('Complex columns must have an alias');
226
        $column->add_field($DB->sql_concat('foo', 'bar'));
227
    }
228
 
229
    /**
230
     * Data provider for {@see test_add_fields}
231
     *
232
     * @return array
233
     */
234
    public function add_fields_provider(): array {
235
        return [
236
            ['t.foo', ['t.foo AS c1_foo']],
237
            ['t.foo bar', ['t.foo AS c1_bar']],
238
            ['t.foo AS bar', ['t.foo AS c1_bar']],
239
            ['t.foo1, t.foo2 bar, t.foo3 AS baz', ['t.foo1 AS c1_foo1', 't.foo2 AS c1_bar', 't.foo3 AS c1_baz']],
240
        ];
241
    }
242
 
243
    /**
244
     * Test adding fields to a column, and retrieving them
245
     *
246
     * @param string $sql
247
     * @param array $expectedselect
248
     *
249
     * @dataProvider add_fields_provider
250
     */
251
    public function test_add_fields(string $sql, array $expectedselect): void {
252
        $column = $this->create_column('test')
253
            ->set_index(1)
254
            ->add_fields($sql);
255
 
256
        $this->assertEquals($expectedselect, $column->get_fields());
257
    }
258
 
259
    /**
260
     * Test column alias
261
     */
262
    public function test_column_alias(): void {
263
        $column = $this->create_column('test')
264
            ->set_index(1)
265
            ->add_fields('t.foo, t.bar');
266
 
267
        $this->assertEquals('c1_foo', $column->get_column_alias());
268
    }
269
 
270
    /**
271
     * Test column alias with a field containing an alias
272
     */
273
    public function test_column_alias_with_field_alias(): void {
274
        $column = $this->create_column('test')
275
            ->set_index(1)
276
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel');
277
 
278
        $this->assertEquals('c1_lionel', $column->get_column_alias());
279
    }
280
 
281
    /**
282
     * Test alias of column without any fields throws exception
283
     */
284
    public function test_column_alias_no_fields(): void {
285
        $column = $this->create_column('test');
286
 
287
        $this->expectException(coding_exception::class);
288
        $this->expectExceptionMessage('Column ' . $column->get_unique_identifier() . ' contains no fields');
289
        $column->add_field($column->get_column_alias());
290
    }
291
 
292
    /**
293
     * Test setting column group by SQL
294
     */
295
    public function test_set_groupby_sql(): void {
296
        $column = $this->create_column('test')
297
            ->set_index(1)
298
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel')
299
            ->set_groupby_sql('t.id');
300
 
301
        $this->assertEquals(['t.id'], $column->get_groupby_sql());
302
    }
303
 
304
    /**
305
     * Test getting default column group by SQL
306
     */
307
    public function test_get_groupby_sql(): void {
308
        global $DB;
309
 
310
        $column = $this->create_column('test')
311
            ->set_index(1)
312
            ->add_fields('t.foo, t.bar');
313
 
314
        // The behaviour of this method differs due to DB limitations.
315
        $usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
316
        if ($usealias) {
317
            $expected = ['c1_foo', 'c1_bar'];
318
        } else {
319
            $expected = ['t.foo', 't.bar'];
320
        }
321
 
322
        $this->assertEquals($expected, $column->get_groupby_sql());
323
    }
324
 
325
    /**
326
     * Data provider for {@see test_get_default_value} and {@see test_format_value}
327
     *
328
     * @return array[]
329
     */
330
    public function column_type_provider(): array {
331
        return [
332
            [column::TYPE_INTEGER, 42],
333
            [column::TYPE_TEXT, 'Hello'],
334
            [column::TYPE_TIMESTAMP, HOURSECS],
335
            [column::TYPE_BOOLEAN, 1, true],
336
            [column::TYPE_FLOAT, 1.23],
337
            [column::TYPE_LONGTEXT, 'Amigos'],
338
        ];
339
    }
340
 
341
    /**
342
     * Test default value is returned from selected values, with correct type
343
     *
344
     * @param int $columntype
345
     * @param mixed $value
346
     * @param mixed|null $expected Expected value, or null to indicate it should be identical to value
347
     *
348
     * @dataProvider column_type_provider
349
     */
350
    public function test_get_default_value(int $columntype, $value, $expected = null): void {
351
        $defaultvalue = column::get_default_value([
352
            'value' => $value,
353
            'foo' => 'bar',
354
        ], $columntype);
355
 
356
        $this->assertSame($expected ?? $value, $defaultvalue);
357
    }
358
 
359
    /**
360
     * Test that column value is returned correctly, with correct type
361
     *
362
     * @param int $columntype
363
     * @param mixed $value
364
     * @param mixed|null $expected Expected value, or null to indicate it should be identical to value
365
     *
366
     * @dataProvider column_type_provider
367
     */
368
    public function test_format_value(int $columntype, $value, $expected = null): void {
369
        $column = $this->create_column('test')
370
            ->set_index(1)
371
            ->set_type($columntype)
372
            ->add_field('t.foo');
373
 
374
        $this->assertSame($expected ?? $value, $column->format_value([
375
            'c1_foo' => $value,
376
        ]));
377
    }
378
 
379
    /**
380
     * Test that column value with callback is returned
381
     */
382
    public function test_format_value_callback(): void {
383
        $column = $this->create_column('test')
384
            ->set_index(1)
385
            ->add_field('t.foo')
386
            ->set_type(column::TYPE_INTEGER)
387
            ->add_callback(static function(int $value, stdClass $values) {
388
                return $value * 2;
389
            });
390
 
391
        $this->assertEquals(84, $column->format_value([
392
            'c1_bar' => 10,
393
            'c1_foo' => 42,
394
        ]));
395
    }
396
 
397
    /**
398
     * Test that column value with callback (using all fields) is returned
399
     */
400
    public function test_format_value_callback_fields(): void {
401
        $column = $this->create_column('test')
402
            ->set_index(1)
403
            ->add_fields('t.foo, t.baz')
404
            ->set_type(column::TYPE_INTEGER)
405
            ->add_callback(static function(int $value, stdClass $values) {
406
                return $values->foo + $values->baz;
407
            });
408
 
409
        $this->assertEquals(60, $column->format_value([
410
            'c1_bar' => 10,
411
            'c1_foo' => 42,
412
            'c1_baz' => 18,
413
        ]));
414
    }
415
 
416
    /**
417
     * Test that column value with callback (using arguments) is returned
418
     */
419
    public function test_format_value_callback_arguments(): void {
420
        $column = $this->create_column('test')
421
            ->set_index(1)
422
            ->add_field('t.foo')
423
            ->set_type(column::TYPE_INTEGER)
424
            ->add_callback(static function(int $value, stdClass $values, int $argument) {
425
                return $value - $argument;
426
            }, 10);
427
 
428
        $this->assertEquals(32, $column->format_value([
429
            'c1_bar' => 10,
430
            'c1_foo' => 42,
431
        ]));
432
    }
433
 
434
    /**
435
     * Test that column value with callback (where aggregation is not set) is returned
436
     */
437
    public function test_format_value_callback_aggregation(): void {
438
        $column = $this->create_column('test')
439
            ->set_index(1)
440
            ->add_field('t.foo')
441
            ->set_type(column::TYPE_INTEGER)
442
            ->add_callback(static function(int $value, stdClass $values, $argument, ?string $aggregation): string {
443
                // Simple callback to return the given value, and append type of aggregation parameter.
444
                return "{$value} " . gettype($aggregation);
445
            });
446
 
447
        $this->assertEquals("42 NULL", $column->format_value(['c1_foo' => 42]));
448
    }
449
 
450
    /**
451
     * Test adding multiple callbacks to a column
452
     */
453
    public function test_add_multiple_callback(): void {
454
        $column = $this->create_column('test')
455
            ->set_index(1)
456
            ->add_field('t.foo')
457
            ->set_type(column::TYPE_TEXT)
458
            ->add_callback(static function(string $value): string {
459
                return strrev($value);
460
            })
461
            ->add_callback(static function(string $value): string {
462
                return strtoupper($value);
463
            });
464
 
465
        $this->assertEquals('LIONEL', $column->format_value([
466
            'c1_foo' => 'lenoil',
467
        ]));
468
    }
469
 
470
    /**
471
     * Test that setting column callback overwrites previous callbacks
472
     */
473
    public function test_set_callback(): void {
474
        $column = $this->create_column('test')
475
            ->set_index(1)
476
            ->add_field('t.foo')
477
            ->set_type(column::TYPE_TEXT)
478
            ->add_callback(static function(string $value): string {
479
                return strrev($value);
480
            })
481
            ->set_callback(static function(string $value): string {
482
                return strtoupper($value);
483
            });
484
 
485
        $this->assertEquals('LENOIL', $column->format_value([
486
            'c1_foo' => 'lenoil',
487
        ]));
488
    }
489
 
490
    /**
491
     * Test is sortable
492
     */
493
    public function test_is_sortable(): void {
494
        $column = $this->create_column('test');
495
        $this->assertFalse($column->get_is_sortable());
496
 
497
        $column->set_is_sortable(true);
498
        $this->assertTrue($column->get_is_sortable());
499
    }
500
 
501
    /**
502
     * Test retrieving sort fields
503
     */
504
    public function test_get_sortfields(): void {
505
        $column = $this->create_column('test')
506
            ->set_index(1)
507
            ->add_fields('t.foo, t.bar, t.baz')
508
            ->set_is_sortable(true, ['t.baz', 't.bar']);
509
 
510
        $this->assertEquals(['c1_baz', 'c1_bar'], $column->get_sort_fields());
511
    }
512
 
513
    /**
514
     * Test retrieving sort fields when an aliased field is set as sortable
515
     */
516
    public function test_get_sortfields_with_field_alias(): void {
517
        $column = $this->create_column('test')
518
            ->set_index(1)
519
            ->add_field('t.foo')
520
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel')
521
            ->set_is_sortable(true, ['lionel']);
522
 
523
        $this->assertEquals(['c1_lionel'], $column->get_sort_fields());
524
    }
525
 
526
    /**
527
     * Test retrieving sort fields when an unknown field is set as sortable
528
     */
529
    public function test_get_sortfields_unknown_field(): void {
530
        $column = $this->create_column('test')
531
            ->set_index(1)
532
            ->add_fields('t.foo')
533
            ->set_is_sortable(true, ['t.baz']);
534
 
535
        $this->assertEquals(['t.baz'], $column->get_sort_fields());
536
    }
537
 
538
    /**
539
     * Test is available
540
     */
541
    public function test_is_available(): void {
542
        $column = $this->create_column('test');
543
        $this->assertTrue($column->get_is_available());
544
 
545
        $column->set_is_available(true);
546
        $this->assertTrue($column->get_is_available());
547
    }
548
 
549
    /**
550
     * Helper method to create a column instance
551
     *
552
     * @param string $name
553
     * @param lang_string|null $title
554
     * @param string $entityname
555
     * @return column
556
     */
557
    private function create_column(string $name, ?lang_string $title = null, string $entityname = 'column_testcase'): column {
558
        return new column($name, $title, $entityname);
559
    }
560
}