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\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
 */
1441 ariadna 35
final class column_test extends advanced_testcase {
1 efrain 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
     * Data provider for {@see test_add_field}
117
     *
118
     * @return array
119
     */
1441 ariadna 120
    public static function add_field_provider(): array {
1 efrain 121
        return [
122
            ['foo', '', ['foo AS c1_foo']],
123
            ['foo', 'bar', ['foo AS c1_bar']],
124
            ['t.foo', '', ['t.foo AS c1_foo']],
125
            ['t.foo', 'bar', ['t.foo AS c1_bar']],
126
        ];
127
    }
128
 
129
    /**
130
     * Test adding single field, and retrieving it
131
     *
132
     * @param string $sql
133
     * @param string $alias
134
     * @param array $expectedselect
135
     *
136
     * @dataProvider add_field_provider
137
     */
138
    public function test_add_field(string $sql, string $alias, array $expectedselect): void {
139
        $column = $this->create_column('test')
140
            ->set_index(1)
141
            ->add_field($sql, $alias);
142
 
143
        $this->assertEquals($expectedselect, $column->get_fields());
144
    }
145
 
146
    /**
147
     * Test adding params to field, and retrieving them
148
     */
149
    public function test_add_field_with_params(): void {
150
        [$param0, $param1] = database::generate_param_names(2);
151
 
152
        $column = $this->create_column('test')
153
            ->set_index(1)
154
            ->add_field(":{$param0}", 'foo', [$param0 => 'foo'])
155
            ->add_field(":{$param1}", 'bar', [$param1 => 'bar']);
156
 
157
        // Select will look like the following: "p<index>_rbparam<counter>", where index is the column index and counter is
158
        // a static value of the report helper class.
159
        $fields = $column->get_fields();
160
        $this->assertCount(2, $fields);
161
 
162
        preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_foo/', $fields[0], $matches);
163
        $this->assertArrayHasKey('paramname', $matches);
164
        $fieldparam0 = $matches['paramname'];
165
 
166
        preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_bar/', $fields[1], $matches);
167
        $this->assertArrayHasKey('paramname', $matches);
168
        $fieldparam1 = $matches['paramname'];
169
 
170
        // Ensure column parameters have been renamed appropriately.
171
        $this->assertEquals([
172
            $fieldparam0 => 'foo',
173
            $fieldparam1 => 'bar',
174
        ], $column->get_params());
175
    }
176
 
177
    /**
178
     * Test adding field with alias as part of SQL throws an exception
179
     */
180
    public function test_add_field_alias_in_sql(): void {
181
        $column = $this->create_column('test')
182
            ->set_index(1);
183
 
184
        $this->expectException(coding_exception::class);
185
        $this->expectExceptionMessage('Column alias must be passed as a separate argument');
186
        $column->add_field('foo AS bar');
187
    }
188
 
189
    /**
190
     * Test adding field with complex SQL without an alias throws an exception
191
     */
192
    public function test_add_field_complex_without_alias(): void {
193
        global $DB;
194
 
195
        $column = $this->create_column('test')
196
            ->set_index(1);
197
 
198
        $this->expectException(coding_exception::class);
199
        $this->expectExceptionMessage('Complex columns must have an alias');
200
        $column->add_field($DB->sql_concat('foo', 'bar'));
201
    }
202
 
203
    /**
204
     * Data provider for {@see test_add_fields}
205
     *
206
     * @return array
207
     */
1441 ariadna 208
    public static function add_fields_provider(): array {
1 efrain 209
        return [
210
            ['t.foo', ['t.foo AS c1_foo']],
211
            ['t.foo bar', ['t.foo AS c1_bar']],
212
            ['t.foo AS bar', ['t.foo AS c1_bar']],
213
            ['t.foo1, t.foo2 bar, t.foo3 AS baz', ['t.foo1 AS c1_foo1', 't.foo2 AS c1_bar', 't.foo3 AS c1_baz']],
214
        ];
215
    }
216
 
217
    /**
218
     * Test adding fields to a column, and retrieving them
219
     *
220
     * @param string $sql
221
     * @param array $expectedselect
222
     *
223
     * @dataProvider add_fields_provider
224
     */
225
    public function test_add_fields(string $sql, array $expectedselect): void {
226
        $column = $this->create_column('test')
227
            ->set_index(1)
228
            ->add_fields($sql);
229
 
230
        $this->assertEquals($expectedselect, $column->get_fields());
231
    }
232
 
233
    /**
234
     * Test column alias
235
     */
236
    public function test_column_alias(): void {
237
        $column = $this->create_column('test')
238
            ->set_index(1)
239
            ->add_fields('t.foo, t.bar');
240
 
241
        $this->assertEquals('c1_foo', $column->get_column_alias());
242
    }
243
 
244
    /**
245
     * Test column alias with a field containing an alias
246
     */
247
    public function test_column_alias_with_field_alias(): void {
248
        $column = $this->create_column('test')
249
            ->set_index(1)
250
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel');
251
 
252
        $this->assertEquals('c1_lionel', $column->get_column_alias());
253
    }
254
 
255
    /**
256
     * Test alias of column without any fields throws exception
257
     */
258
    public function test_column_alias_no_fields(): void {
259
        $column = $this->create_column('test');
260
 
261
        $this->expectException(coding_exception::class);
262
        $this->expectExceptionMessage('Column ' . $column->get_unique_identifier() . ' contains no fields');
263
        $column->add_field($column->get_column_alias());
264
    }
265
 
266
    /**
267
     * Test setting column group by SQL
268
     */
269
    public function test_set_groupby_sql(): void {
270
        $column = $this->create_column('test')
271
            ->set_index(1)
272
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel')
273
            ->set_groupby_sql('t.id');
274
 
275
        $this->assertEquals(['t.id'], $column->get_groupby_sql());
276
    }
277
 
278
    /**
279
     * Test getting default column group by SQL
280
     */
281
    public function test_get_groupby_sql(): void {
282
        global $DB;
283
 
284
        $column = $this->create_column('test')
285
            ->set_index(1)
286
            ->add_fields('t.foo, t.bar');
287
 
288
        // The behaviour of this method differs due to DB limitations.
289
        $usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
290
        if ($usealias) {
291
            $expected = ['c1_foo', 'c1_bar'];
292
        } else {
293
            $expected = ['t.foo', 't.bar'];
294
        }
295
 
296
        $this->assertEquals($expected, $column->get_groupby_sql());
297
    }
298
 
299
    /**
300
     * Data provider for {@see test_get_default_value} and {@see test_format_value}
301
     *
302
     * @return array[]
303
     */
1441 ariadna 304
    public static function column_type_provider(): array {
1 efrain 305
        return [
306
            [column::TYPE_INTEGER, 42],
307
            [column::TYPE_TEXT, 'Hello'],
308
            [column::TYPE_TIMESTAMP, HOURSECS],
309
            [column::TYPE_BOOLEAN, 1, true],
310
            [column::TYPE_FLOAT, 1.23],
311
            [column::TYPE_LONGTEXT, 'Amigos'],
312
        ];
313
    }
314
 
315
    /**
316
     * Test default value is returned from selected values, with correct type
317
     *
318
     * @param int $columntype
319
     * @param mixed $value
320
     * @param mixed|null $expected Expected value, or null to indicate it should be identical to value
321
     *
322
     * @dataProvider column_type_provider
323
     */
324
    public function test_get_default_value(int $columntype, $value, $expected = null): void {
325
        $defaultvalue = column::get_default_value([
326
            'value' => $value,
327
            'foo' => 'bar',
328
        ], $columntype);
329
 
330
        $this->assertSame($expected ?? $value, $defaultvalue);
331
    }
332
 
333
    /**
334
     * Test that column value is returned correctly, with correct type
335
     *
336
     * @param int $columntype
337
     * @param mixed $value
338
     * @param mixed|null $expected Expected value, or null to indicate it should be identical to value
339
     *
340
     * @dataProvider column_type_provider
341
     */
342
    public function test_format_value(int $columntype, $value, $expected = null): void {
343
        $column = $this->create_column('test')
344
            ->set_index(1)
345
            ->set_type($columntype)
346
            ->add_field('t.foo');
347
 
348
        $this->assertSame($expected ?? $value, $column->format_value([
349
            'c1_foo' => $value,
350
        ]));
351
    }
352
 
353
    /**
354
     * Test that column value with callback is returned
355
     */
356
    public function test_format_value_callback(): void {
357
        $column = $this->create_column('test')
358
            ->set_index(1)
359
            ->add_field('t.foo')
360
            ->set_type(column::TYPE_INTEGER)
361
            ->add_callback(static function(int $value, stdClass $values) {
362
                return $value * 2;
363
            });
364
 
365
        $this->assertEquals(84, $column->format_value([
366
            'c1_bar' => 10,
367
            'c1_foo' => 42,
368
        ]));
369
    }
370
 
371
    /**
372
     * Test that column value with callback (using all fields) is returned
373
     */
374
    public function test_format_value_callback_fields(): void {
375
        $column = $this->create_column('test')
376
            ->set_index(1)
377
            ->add_fields('t.foo, t.baz')
378
            ->set_type(column::TYPE_INTEGER)
379
            ->add_callback(static function(int $value, stdClass $values) {
380
                return $values->foo + $values->baz;
381
            });
382
 
383
        $this->assertEquals(60, $column->format_value([
384
            'c1_bar' => 10,
385
            'c1_foo' => 42,
386
            'c1_baz' => 18,
387
        ]));
388
    }
389
 
390
    /**
391
     * Test that column value with callback (using arguments) is returned
392
     */
393
    public function test_format_value_callback_arguments(): void {
394
        $column = $this->create_column('test')
395
            ->set_index(1)
396
            ->add_field('t.foo')
397
            ->set_type(column::TYPE_INTEGER)
398
            ->add_callback(static function(int $value, stdClass $values, int $argument) {
399
                return $value - $argument;
400
            }, 10);
401
 
402
        $this->assertEquals(32, $column->format_value([
403
            'c1_bar' => 10,
404
            'c1_foo' => 42,
405
        ]));
406
    }
407
 
408
    /**
409
     * Test that column value with callback (where aggregation is not set) is returned
410
     */
411
    public function test_format_value_callback_aggregation(): void {
412
        $column = $this->create_column('test')
413
            ->set_index(1)
414
            ->add_field('t.foo')
415
            ->set_type(column::TYPE_INTEGER)
416
            ->add_callback(static function(int $value, stdClass $values, $argument, ?string $aggregation): string {
417
                // Simple callback to return the given value, and append type of aggregation parameter.
418
                return "{$value} " . gettype($aggregation);
419
            });
420
 
421
        $this->assertEquals("42 NULL", $column->format_value(['c1_foo' => 42]));
422
    }
423
 
424
    /**
425
     * Test adding multiple callbacks to a column
426
     */
427
    public function test_add_multiple_callback(): void {
428
        $column = $this->create_column('test')
429
            ->set_index(1)
430
            ->add_field('t.foo')
431
            ->set_type(column::TYPE_TEXT)
432
            ->add_callback(static function(string $value): string {
433
                return strrev($value);
434
            })
435
            ->add_callback(static function(string $value): string {
436
                return strtoupper($value);
437
            });
438
 
439
        $this->assertEquals('LIONEL', $column->format_value([
440
            'c1_foo' => 'lenoil',
441
        ]));
442
    }
443
 
444
    /**
445
     * Test that setting column callback overwrites previous callbacks
446
     */
447
    public function test_set_callback(): void {
448
        $column = $this->create_column('test')
449
            ->set_index(1)
450
            ->add_field('t.foo')
451
            ->set_type(column::TYPE_TEXT)
452
            ->add_callback(static function(string $value): string {
453
                return strrev($value);
454
            })
455
            ->set_callback(static function(string $value): string {
456
                return strtoupper($value);
457
            });
458
 
459
        $this->assertEquals('LENOIL', $column->format_value([
460
            'c1_foo' => 'lenoil',
461
        ]));
462
    }
463
 
464
    /**
465
     * Test is sortable
466
     */
467
    public function test_is_sortable(): void {
468
        $column = $this->create_column('test');
469
        $this->assertFalse($column->get_is_sortable());
470
 
471
        $column->set_is_sortable(true);
472
        $this->assertTrue($column->get_is_sortable());
473
    }
474
 
475
    /**
476
     * Test retrieving sort fields
477
     */
1441 ariadna 478
    public function test_get_sort_fields(): void {
1 efrain 479
        $column = $this->create_column('test')
480
            ->set_index(1)
481
            ->add_fields('t.foo, t.bar, t.baz')
482
            ->set_is_sortable(true, ['t.baz', 't.bar']);
483
 
484
        $this->assertEquals(['c1_baz', 'c1_bar'], $column->get_sort_fields());
485
    }
486
 
487
    /**
488
     * Test retrieving sort fields when an aliased field is set as sortable
489
     */
1441 ariadna 490
    public function test_get_sort_fields_with_field_alias(): void {
1 efrain 491
        $column = $this->create_column('test')
492
            ->set_index(1)
493
            ->add_field('t.foo')
494
            ->add_field('COALESCE(t.foo, t.bar)', 'lionel')
495
            ->set_is_sortable(true, ['lionel']);
496
 
497
        $this->assertEquals(['c1_lionel'], $column->get_sort_fields());
498
    }
499
 
500
    /**
1441 ariadna 501
     * Test retrieving sort fields that contain references to fields within complex snippet
502
     */
503
    public function test_get_sort_fields_complex(): void {
504
        $column = $this->create_column('test')
505
            ->set_index(1)
506
            ->add_fields('t.foo, t.bar')
507
            ->set_is_sortable(true, ['CASE WHEN 1=1 THEN t.foo ELSE t.bar END']);
508
 
509
        $this->assertEquals(['CASE WHEN 1=1 THEN c1_foo ELSE c1_bar END'], $column->get_sort_fields());
510
    }
511
 
512
    /**
1 efrain 513
     * Test retrieving sort fields when an unknown field is set as sortable
514
     */
1441 ariadna 515
    public function test_get_sort_fields_unknown_field(): void {
1 efrain 516
        $column = $this->create_column('test')
517
            ->set_index(1)
518
            ->add_fields('t.foo')
519
            ->set_is_sortable(true, ['t.baz']);
520
 
521
        $this->assertEquals(['t.baz'], $column->get_sort_fields());
522
    }
523
 
524
    /**
525
     * Test is available
526
     */
527
    public function test_is_available(): void {
528
        $column = $this->create_column('test');
529
        $this->assertTrue($column->get_is_available());
530
 
531
        $column->set_is_available(true);
532
        $this->assertTrue($column->get_is_available());
533
    }
534
 
535
    /**
536
     * Helper method to create a column instance
537
     *
538
     * @param string $name
539
     * @param lang_string|null $title
540
     * @param string $entityname
541
     * @return column
542
     */
543
    private function create_column(string $name, ?lang_string $title = null, string $entityname = 'column_testcase'): column {
544
        return new column($name, $title, $entityname);
545
    }
546
}