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