Proyectos de Subversion Moodle

Rev

Rev 11 | | 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
namespace core_availability;
18
 
19
/**
20
 * Unit tests for the condition tree class and related logic.
21
 *
22
 * @package core_availability
23
 * @copyright 2014 The Open University
24
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
1441 ariadna 26
final class tree_test extends \advanced_testcase {
1 efrain 27
    public function setUp(): void {
28
        // Load the mock classes so they can be used.
29
        require_once(__DIR__ . '/fixtures/mock_condition.php');
30
        require_once(__DIR__ . '/fixtures/mock_info.php');
1441 ariadna 31
        parent::setUp();
1 efrain 32
    }
33
 
34
    /**
35
     * Tests constructing a tree with errors.
36
     */
11 efrain 37
    public function test_construct_errors(): void {
1 efrain 38
        try {
39
            new tree('frog');
40
            $this->fail();
41
        } catch (\coding_exception $e) {
42
            $this->assertStringContainsString('not object', $e->getMessage());
43
        }
44
        try {
45
            new tree((object)array());
46
            $this->fail();
47
        } catch (\coding_exception $e) {
48
            $this->assertStringContainsString('missing ->op', $e->getMessage());
49
        }
50
        try {
51
            new tree((object)array('op' => '*'));
52
            $this->fail();
53
        } catch (\coding_exception $e) {
54
            $this->assertStringContainsString('unknown ->op', $e->getMessage());
55
        }
56
        try {
57
            new tree((object)array('op' => '|'));
58
            $this->fail();
59
        } catch (\coding_exception $e) {
60
            $this->assertStringContainsString('missing ->show', $e->getMessage());
61
        }
62
        try {
63
            new tree((object)array('op' => '|', 'show' => 0));
64
            $this->fail();
65
        } catch (\coding_exception $e) {
66
            $this->assertStringContainsString('->show not bool', $e->getMessage());
67
        }
68
        try {
69
            new tree((object)array('op' => '&'));
70
            $this->fail();
71
        } catch (\coding_exception $e) {
72
            $this->assertStringContainsString('missing ->showc', $e->getMessage());
73
        }
74
        try {
75
            new tree((object)array('op' => '&', 'showc' => 0));
76
            $this->fail();
77
        } catch (\coding_exception $e) {
78
            $this->assertStringContainsString('->showc not array', $e->getMessage());
79
        }
80
        try {
81
            new tree((object)array('op' => '&', 'showc' => array(0)));
82
            $this->fail();
83
        } catch (\coding_exception $e) {
84
            $this->assertStringContainsString('->showc value not bool', $e->getMessage());
85
        }
86
        try {
87
            new tree((object)array('op' => '|', 'show' => true));
88
            $this->fail();
89
        } catch (\coding_exception $e) {
90
            $this->assertStringContainsString('missing ->c', $e->getMessage());
91
        }
92
        try {
93
            new tree((object)array('op' => '|', 'show' => true,
94
                    'c' => 'side'));
95
            $this->fail();
96
        } catch (\coding_exception $e) {
97
            $this->assertStringContainsString('->c not array', $e->getMessage());
98
        }
99
        try {
100
            new tree((object)array('op' => '|', 'show' => true,
101
                    'c' => array(3)));
102
            $this->fail();
103
        } catch (\coding_exception $e) {
104
            $this->assertStringContainsString('child not object', $e->getMessage());
105
        }
106
        try {
107
            new tree((object)array('op' => '|', 'show' => true,
108
                    'c' => array((object)array('type' => 'doesnotexist'))));
109
            $this->fail();
110
        } catch (\coding_exception $e) {
111
            $this->assertStringContainsString('Unknown condition type: doesnotexist', $e->getMessage());
112
        }
113
        try {
114
            new tree((object)array('op' => '|', 'show' => true,
115
                    'c' => array((object)array())));
116
            $this->fail();
117
        } catch (\coding_exception $e) {
118
            $this->assertStringContainsString('missing ->op', $e->getMessage());
119
        }
120
        try {
121
            new tree((object)array('op' => '&',
122
                    'c' => array((object)array('op' => '&', 'c' => array())),
123
                    'showc' => array(true, true)
124
                    ));
125
            $this->fail();
126
        } catch (\coding_exception $e) {
127
            $this->assertStringContainsString('->c, ->showc mismatch', $e->getMessage());
128
        }
129
    }
130
 
131
    /**
132
     * Tests constructing a tree with plugin that does not exist (ignored).
133
     */
11 efrain 134
    public function test_construct_ignore_missing_plugin(): void {
1 efrain 135
        // Construct a tree with & combination of one condition that doesn't exist.
136
        $tree = new tree(tree::get_root_json(array(
137
                (object)array('type' => 'doesnotexist')), tree::OP_OR), true);
138
        // Expected result is an empty tree with | condition, shown.
139
        $this->assertEquals('+|()', (string)$tree);
140
    }
141
 
142
    /**
143
     * Tests constructing a tree with subtrees using all available operators.
144
     */
11 efrain 145
    public function test_construct_just_trees(): void {
1 efrain 146
        $structure = tree::get_root_json(array(
147
                tree::get_nested_json(array(), tree::OP_OR),
148
                tree::get_nested_json(array(
149
                    tree::get_nested_json(array(), tree::OP_NOT_OR)), tree::OP_NOT_AND)),
150
                tree::OP_AND, array(true, true));
151
        $tree = new tree($structure);
152
        $this->assertEquals('&(+|(),+!&(!|()))', (string)$tree);
153
    }
154
 
155
    /**
156
     * Tests constructing tree using the mock plugin.
157
     */
11 efrain 158
    public function test_construct_with_mock_plugin(): void {
1 efrain 159
        $structure = tree::get_root_json(array(
160
                self::mock(array('a' => true, 'm' => ''))), tree::OP_OR);
161
        $tree = new tree($structure);
162
        $this->assertEquals('+|({mock:y,})', (string)$tree);
163
    }
164
 
165
    /**
166
     * Tests the check_available and get_result_information functions.
167
     */
11 efrain 168
    public function test_check_available(): void {
1 efrain 169
        global $USER;
170
 
171
        // Setup.
172
        $this->resetAfterTest();
173
        $info = new \core_availability\mock_info();
174
        $this->setAdminUser();
175
        $information = '';
176
 
177
        // No conditions.
178
        $structure = tree::get_root_json(array(), tree::OP_OR);
179
        list ($available, $information) = $this->get_available_results(
180
                $structure, $info, $USER->id);
181
        $this->assertTrue($available);
182
 
183
        // One condition set to yes.
184
        $structure->c = array(
185
                self::mock(array('a' => true)));
186
        list ($available, $information) = $this->get_available_results(
187
                $structure, $info, $USER->id);
188
        $this->assertTrue($available);
189
 
190
        // One condition set to no.
191
        $structure->c = array(
192
                self::mock(array('a' => false, 'm' => 'no')));
193
        list ($available, $information) = $this->get_available_results(
194
                $structure, $info, $USER->id);
195
        $this->assertFalse($available);
196
        $this->assertEquals('SA: no', $information);
197
 
198
        // Two conditions, OR, resolving as true.
199
        $structure->c = array(
200
                self::mock(array('a' => false, 'm' => 'no')),
201
                self::mock(array('a' => true)));
202
        list ($available, $information) = $this->get_available_results(
203
                $structure, $info, $USER->id);
204
        $this->assertTrue($available);
205
        $this->assertEquals('', $information);
206
 
207
        // Two conditions, OR, resolving as false.
208
        $structure->c = array(
209
                self::mock(array('a' => false, 'm' => 'no')),
210
                self::mock(array('a' => false, 'm' => 'way')));
211
        list ($available, $information) = $this->get_available_results(
212
                $structure, $info, $USER->id);
213
        $this->assertFalse($available);
214
        $this->assertMatchesRegularExpression('~any of.*no.*way~', $information);
215
 
216
        // Two conditions, OR, resolving as false, no display.
217
        $structure->show = false;
218
        list ($available, $information) = $this->get_available_results(
219
                $structure, $info, $USER->id);
220
        $this->assertFalse($available);
221
        $this->assertEquals('', $information);
222
 
223
        // Two conditions, AND, resolving as true.
224
        $structure->op = '&';
225
        unset($structure->show);
226
        $structure->showc = array(true, true);
227
        $structure->c = array(
228
                self::mock(array('a' => true)),
229
                self::mock(array('a' => true)));
230
        list ($available, $information) = $this->get_available_results(
231
                $structure, $info, $USER->id);
232
        $this->assertTrue($available);
233
 
234
        // Two conditions, AND, one false.
235
        $structure->c = array(
236
                self::mock(array('a' => false, 'm' => 'wom')),
237
                self::mock(array('a' => true, 'm' => '')));
238
        list ($available, $information) = $this->get_available_results(
239
                $structure, $info, $USER->id);
240
        $this->assertFalse($available);
241
        $this->assertEquals('SA: wom', $information);
242
 
243
        // Two conditions, AND, both false.
244
        $structure->c = array(
245
                self::mock(array('a' => false, 'm' => 'wom')),
246
                self::mock(array('a' => false, 'm' => 'bat')));
247
        list ($available, $information) = $this->get_available_results(
248
                $structure, $info, $USER->id);
249
        $this->assertFalse($available);
250
        $this->assertMatchesRegularExpression('~wom.*bat~', $information);
251
 
252
        // Two conditions, AND, both false, show turned off for one. When
253
        // show is turned off, that means if you don't have that condition
254
        // you don't get to see anything at all.
255
        $structure->showc[0] = false;
256
        list ($available, $information) = $this->get_available_results(
257
                $structure, $info, $USER->id);
258
        $this->assertFalse($available);
259
        $this->assertEquals('', $information);
260
        $structure->showc[0] = true;
261
 
262
        // Two conditions, NOT OR, both false.
263
        $structure->op = '!|';
264
        list ($available, $information) = $this->get_available_results(
265
                $structure, $info, $USER->id);
266
        $this->assertTrue($available);
267
 
268
        // Two conditions, NOT OR, one true.
269
        $structure->c[0]->a = true;
270
        list ($available, $information) = $this->get_available_results(
271
                $structure, $info, $USER->id);
272
        $this->assertFalse($available);
273
        $this->assertEquals('SA: !wom', $information);
274
 
275
        // Two conditions, NOT OR, both true.
276
        $structure->c[1]->a = true;
277
        list ($available, $information) = $this->get_available_results(
278
                $structure, $info, $USER->id);
279
        $this->assertFalse($available);
280
        $this->assertMatchesRegularExpression('~!wom.*!bat~', $information);
281
 
282
        // Two conditions, NOT AND, both true.
283
        $structure->op = '!&';
284
        unset($structure->showc);
285
        $structure->show = true;
286
        list ($available, $information) = $this->get_available_results(
287
                $structure, $info, $USER->id);
288
        $this->assertFalse($available);
289
        $this->assertMatchesRegularExpression('~any of.*!wom.*!bat~', $information);
290
 
291
        // Two conditions, NOT AND, one true.
292
        $structure->c[1]->a = false;
293
        list ($available, $information) = $this->get_available_results(
294
                $structure, $info, $USER->id);
295
        $this->assertTrue($available);
296
 
297
        // Nested NOT conditions; true.
298
        $structure->c = array(
299
                tree::get_nested_json(array(
300
                    self::mock(array('a' => true, 'm' => 'no'))), tree::OP_NOT_AND));
301
        list ($available, $information) = $this->get_available_results(
302
                $structure, $info, $USER->id);
303
        $this->assertTrue($available);
304
 
305
        // Nested NOT conditions; false (note no ! in message).
306
        $structure->c[0]->c[0]->a = false;
307
        list ($available, $information) = $this->get_available_results(
308
                $structure, $info, $USER->id);
309
        $this->assertFalse($available);
310
        $this->assertEquals('SA: no', $information);
311
 
312
        // Nested condition groups, message test.
313
        $structure->op = '|';
314
        $structure->c = array(
315
                tree::get_nested_json(array(
316
                    self::mock(array('a' => false, 'm' => '1')),
317
                    self::mock(array('a' => false, 'm' => '2'))
318
                    ), tree::OP_AND),
319
                self::mock(array('a' => false, 'm' => 3)));
320
        list ($available, $information) = $this->get_available_results(
321
                $structure, $info, $USER->id);
322
        $this->assertFalse($available);
323
        $this->assertMatchesRegularExpression('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~', $information);
324
    }
325
 
326
    /**
327
     * Shortcut function to check availability and also get information.
328
     *
329
     * @param \stdClass $structure Tree structure
330
     * @param \core_availability\info $info Location info
331
     * @param int $userid User id
332
     */
333
    protected function get_available_results($structure, \core_availability\info $info, $userid) {
334
        global $PAGE, $OUTPUT;
335
        $tree = new tree($structure);
336
        $result = $tree->check_available(false, $info, true, $userid);
337
        $information = $tree->get_result_information($info, $result);
338
        if (!is_string($information)) {
339
            $renderable = new \core_availability\output\availability_info($information);
340
            $information = str_replace(array("\r", "\n"), '', $OUTPUT->render($renderable));
341
        }
342
        return array($result->is_available(), $information);
343
    }
344
 
345
    /**
346
     * Shortcut function to render the full availability information.
347
     *
348
     * @param \stdClass $structure Tree structure
349
     * @param \core_availability\info $info Location info
350
     */
351
    protected function render_full_information($structure, \core_availability\info $info) {
352
        global $OUTPUT;
353
        $tree = new tree($structure);
354
        $information = $tree->get_full_information($info);
355
        $renderable = new \core_availability\output\availability_info($information);
356
        $html = $OUTPUT->render($renderable);
357
        return str_replace(array("\r", "\n"), '', $html);
358
    }
359
 
360
    /**
361
     * Tests the is_available_for_all() function.
362
     */
11 efrain 363
    public function test_is_available_for_all(): void {
1 efrain 364
        // Empty tree is always available.
365
        $structure = tree::get_root_json(array(), tree::OP_OR);
366
        $tree = new tree($structure);
367
        $this->assertTrue($tree->is_available_for_all());
368
 
369
        // Tree with normal item in it, not always available.
370
        $structure->c[0] = (object)array('type' => 'mock');
371
        $tree = new tree($structure);
372
        $this->assertFalse($tree->is_available_for_all());
373
 
374
        // OR tree with one always-available item.
375
        $structure->c[1] = self::mock(array('all' => true));
376
        $tree = new tree($structure);
377
        $this->assertTrue($tree->is_available_for_all());
378
 
379
        // AND tree with one always-available and one not.
380
        $structure->op = '&';
381
        $structure->showc = array(true, true);
382
        unset($structure->show);
383
        $tree = new tree($structure);
384
        $this->assertFalse($tree->is_available_for_all());
385
 
386
        // Test NOT conditions (items not always-available).
387
        $structure->op = '!&';
388
        $structure->show = true;
389
        unset($structure->showc);
390
        $tree = new tree($structure);
391
        $this->assertFalse($tree->is_available_for_all());
392
 
393
        // Test again with one item always-available for NOT mode.
394
        $structure->c[1]->allnot = true;
395
        $tree = new tree($structure);
396
        $this->assertTrue($tree->is_available_for_all());
397
    }
398
 
399
    /**
400
     * Tests the get_full_information() function.
401
     */
11 efrain 402
    public function test_get_full_information(): void {
1 efrain 403
        global $PAGE;
404
        // Setup.
405
        $info = new \core_availability\mock_info();
406
 
407
        // No conditions.
408
        $structure = tree::get_root_json(array(), tree::OP_OR);
409
        $tree = new tree($structure);
410
        $this->assertEquals('', $tree->get_full_information($info));
411
 
412
        // Condition (normal and NOT).
413
        $structure->c = array(
414
                self::mock(array('m' => 'thing')));
415
        $tree = new tree($structure);
416
        $this->assertEquals('SA: [FULL]thing',
417
                $tree->get_full_information($info));
418
        $structure->op = '!&';
419
        $tree = new tree($structure);
420
        $this->assertEquals('SA: ![FULL]thing',
421
                $tree->get_full_information($info));
422
 
423
        // Complex structure.
424
        $structure->op = '|';
425
        $structure->c = array(
426
                tree::get_nested_json(array(
427
                    self::mock(array('m' => '1')),
428
                    self::mock(array('m' => '2'))), tree::OP_AND),
429
                self::mock(array('m' => 3)));
430
        $this->assertMatchesRegularExpression('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
431
                $this->render_full_information($structure, $info));
432
 
433
        // Test intro messages before list. First, OR message.
434
        $structure->c = array(
435
                self::mock(array('m' => '1')),
436
                self::mock(array('m' => '2'))
437
        );
438
        $this->assertMatchesRegularExpression('~Not available unless any of:.*<ul~',
439
                $this->render_full_information($structure, $info));
440
 
441
        // Now, OR message when not shown.
442
        $structure->show = false;
443
 
444
        $this->assertMatchesRegularExpression('~hidden.*<ul~',
445
                $this->render_full_information($structure, $info));
446
 
447
        // AND message.
448
        $structure->op = '&';
449
        unset($structure->show);
450
        $structure->showc = array(false, false);
451
        $this->assertMatchesRegularExpression('~Not available unless:.*<ul~',
452
                $this->render_full_information($structure, $info));
453
 
454
        // Hidden markers on items.
455
        $this->assertMatchesRegularExpression('~1.*hidden.*2.*hidden~',
456
                $this->render_full_information($structure, $info));
457
 
458
        // Hidden markers on child tree and items.
459
        $structure->c[1] = tree::get_nested_json(array(
460
                self::mock(array('m' => '2')),
461
                self::mock(array('m' => '3'))), tree::OP_AND);
462
        $this->assertMatchesRegularExpression('~1.*hidden.*All of \(hidden.*2.*3~',
463
                $this->render_full_information($structure, $info));
464
        $structure->c[1]->op = '|';
465
        $this->assertMatchesRegularExpression('~1.*hidden.*Any of \(hidden.*2.*3~',
466
                $this->render_full_information($structure, $info));
467
 
468
        // Hidden markers on single-item display, AND and OR.
469
        $structure->showc = array(false);
470
        $structure->c = array(
471
                self::mock(array('m' => '1'))
472
        );
473
        $tree = new tree($structure);
474
        $this->assertMatchesRegularExpression('~1.*hidden~',
475
                $tree->get_full_information($info));
476
 
477
        unset($structure->showc);
478
        $structure->show = false;
479
        $structure->op = '|';
480
        $tree = new tree($structure);
481
        $this->assertMatchesRegularExpression('~1.*hidden~',
482
                $tree->get_full_information($info));
483
 
484
        // Hidden marker if single item is tree.
485
        $structure->c[0] = tree::get_nested_json(array(
486
                self::mock(array('m' => '1')),
487
                self::mock(array('m' => '2'))), tree::OP_AND);
488
        $this->assertMatchesRegularExpression('~Not available \(hidden.*1.*2~',
489
                $this->render_full_information($structure, $info));
490
 
491
        // Single item tree containing single item.
492
        unset($structure->c[0]->c[1]);
493
        $tree = new tree($structure);
494
        $this->assertMatchesRegularExpression('~SA.*1.*hidden~',
495
                $tree->get_full_information($info));
496
    }
497
 
498
    /**
499
     * Tests the is_empty() function.
500
     */
11 efrain 501
    public function test_is_empty(): void {
1 efrain 502
        // Tree with nothing in should be empty.
503
        $structure = tree::get_root_json(array(), tree::OP_OR);
504
        $tree = new tree($structure);
505
        $this->assertTrue($tree->is_empty());
506
 
507
        // Tree with something in is not empty.
508
        $structure = tree::get_root_json(array(self::mock(array('m' => '1'))), tree::OP_OR);
509
        $tree = new tree($structure);
510
        $this->assertFalse($tree->is_empty());
511
    }
512
 
513
    /**
514
     * Tests the get_all_children() function.
515
     */
11 efrain 516
    public function test_get_all_children(): void {
1 efrain 517
        // Create a tree with nothing in.
518
        $structure = tree::get_root_json(array(), tree::OP_OR);
519
        $tree1 = new tree($structure);
520
 
521
        // Create second tree with complex structure.
522
        $structure->c = array(
523
                tree::get_nested_json(array(
524
                    self::mock(array('m' => '1')),
525
                    self::mock(array('m' => '2'))
526
                ), tree::OP_OR),
527
                self::mock(array('m' => 3)));
528
        $tree2 = new tree($structure);
529
 
530
        // Check list of conditions from both trees.
531
        $this->assertEquals(array(), $tree1->get_all_children('core_availability\condition'));
532
        $result = $tree2->get_all_children('core_availability\condition');
533
        $this->assertEquals(3, count($result));
534
        $this->assertEquals('{mock:n,1}', (string)$result[0]);
535
        $this->assertEquals('{mock:n,2}', (string)$result[1]);
536
        $this->assertEquals('{mock:n,3}', (string)$result[2]);
537
 
538
        // Check specific type, should give same results.
539
        $result2 = $tree2->get_all_children('availability_mock\condition');
540
        $this->assertEquals($result, $result2);
541
    }
542
 
543
    /**
544
     * Tests the update_dependency_id() function.
545
     */
11 efrain 546
    public function test_update_dependency_id(): void {
1 efrain 547
        // Create tree with structure of 3 mocks.
548
        $structure = tree::get_root_json(array(
549
                tree::get_nested_json(array(
550
                    self::mock(array('table' => 'frogs', 'id' => 9)),
551
                    self::mock(array('table' => 'zombies', 'id' => 9))
552
                )),
553
                self::mock(array('table' => 'frogs', 'id' => 9))));
554
 
555
        // Get 'before' value.
556
        $tree = new tree($structure);
557
        $before = $tree->save();
558
 
559
        // Try replacing a table or id that isn't used.
560
        $this->assertFalse($tree->update_dependency_id('toads', 9, 13));
561
        $this->assertFalse($tree->update_dependency_id('frogs', 7, 8));
562
        $this->assertEquals($before, $tree->save());
563
 
564
        // Replace the zombies one.
565
        $this->assertTrue($tree->update_dependency_id('zombies', 9, 666));
566
        $after = $tree->save();
567
        $this->assertEquals(666, $after->c[0]->c[1]->id);
568
 
569
        // And the frogs one.
570
        $this->assertTrue($tree->update_dependency_id('frogs', 9, 3));
571
        $after = $tree->save();
572
        $this->assertEquals(3, $after->c[0]->c[0]->id);
573
        $this->assertEquals(3, $after->c[1]->id);
574
    }
575
 
576
    /**
577
     * Tests the filter_users function.
578
     */
11 efrain 579
    public function test_filter_users(): void {
1 efrain 580
        $info = new \core_availability\mock_info();
581
        $checker = new capability_checker($info->get_context());
582
 
583
        // Don't need to create real users in database, just use these ids.
584
        $users = array(1 => null, 2 => null, 3 => null);
585
 
586
        // Test basic tree with one condition that doesn't filter.
587
        $structure = tree::get_root_json(array(self::mock(array())));
588
        $tree = new tree($structure);
589
        $result = $tree->filter_user_list($users, false, $info, $checker);
590
        ksort($result);
591
        $this->assertEquals(array(1, 2, 3), array_keys($result));
592
 
593
        // Now a tree with one condition that filters.
594
        $structure = tree::get_root_json(array(self::mock(array('filter' => array(2, 3)))));
595
        $tree = new tree($structure);
596
        $result = $tree->filter_user_list($users, false, $info, $checker);
597
        ksort($result);
598
        $this->assertEquals(array(2, 3), array_keys($result));
599
 
600
        // Tree with two conditions that both filter (|).
601
        $structure = tree::get_root_json(array(
602
                self::mock(array('filter' => array(3))),
603
                self::mock(array('filter' => array(1)))), tree::OP_OR);
604
        $tree = new tree($structure);
605
        $result = $tree->filter_user_list($users, false, $info, $checker);
606
        ksort($result);
607
        $this->assertEquals(array(1, 3), array_keys($result));
608
 
609
        // Tree with OR condition one of which doesn't filter.
610
        $structure = tree::get_root_json(array(
611
                self::mock(array('filter' => array(3))),
612
                self::mock(array())), tree::OP_OR);
613
        $tree = new tree($structure);
614
        $result = $tree->filter_user_list($users, false, $info, $checker);
615
        ksort($result);
616
        $this->assertEquals(array(1, 2, 3), array_keys($result));
617
 
618
        // Tree with two condition that both filter (&).
619
        $structure = tree::get_root_json(array(
620
                self::mock(array('filter' => array(2, 3))),
621
                self::mock(array('filter' => array(1, 2)))));
622
        $tree = new tree($structure);
623
        $result = $tree->filter_user_list($users, false, $info, $checker);
624
        ksort($result);
625
        $this->assertEquals(array(2), array_keys($result));
626
 
627
        // Tree with child tree with NOT condition.
628
        $structure = tree::get_root_json(array(
629
                tree::get_nested_json(array(
630
                    self::mock(array('filter' => array(1)))), tree::OP_NOT_AND)));
631
        $tree = new tree($structure);
632
        $result = $tree->filter_user_list($users, false, $info, $checker);
633
        ksort($result);
634
        $this->assertEquals(array(2, 3), array_keys($result));
635
    }
636
 
637
    /**
638
     * Tests the get_json methods in tree (which are mainly for use in testing
639
     * but might be used elsewhere).
640
     */
11 efrain 641
    public function test_get_json(): void {
1 efrain 642
        // Create a simple child object (fake).
643
        $child = (object)array('type' => 'fake');
644
        $childstr = json_encode($child);
645
 
646
        // Minimal case.
647
        $this->assertEquals(
648
                (object)array('op' => '&', 'c' => array()),
649
                tree::get_nested_json(array()));
650
        // Children and different operator.
651
        $this->assertEquals(
652
                (object)array('op' => '|', 'c' => array($child, $child)),
653
                tree::get_nested_json(array($child, $child), tree::OP_OR));
654
 
655
        // Root empty.
656
        $this->assertEquals('{"op":"&","c":[],"showc":[]}',
657
                json_encode(tree::get_root_json(array(), tree::OP_AND)));
658
        // Root with children (multi-show operator).
659
        $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
660
                    '],"showc":[true,true]}',
661
                json_encode(tree::get_root_json(array($child, $child), tree::OP_AND)));
662
        // Root with children (single-show operator).
663
        $this->assertEquals('{"op":"|","c":[' . $childstr . ',' . $childstr .
664
                    '],"show":true}',
665
                json_encode(tree::get_root_json(array($child, $child), tree::OP_OR)));
666
        // Root with children (specified show boolean).
667
        $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
668
                    '],"showc":[false,false]}',
669
                json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, false)));
670
        // Root with children (specified show array).
671
        $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
672
                    '],"showc":[true,false]}',
673
                json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, array(true, false))));
674
    }
675
 
676
    /**
677
     * Tests the behaviour of the counter in unique_sql_parameter().
678
     *
679
     * There was a problem with static counters used to implement a sequence of
680
     * parameter placeholders (MDL-53481). As always with static variables, it
681
     * is a bit tricky to unit test the behaviour reliably as it depends on the
682
     * actual tests executed and also their order.
683
     *
684
     * To minimise risk of false expected behaviour, this test method should be
685
     * first one where {@link core_availability\tree::get_user_list_sql()} is
686
     * used. We also use higher number of condition instances to increase the
687
     * risk of the counter collision, should there remain a problem.
688
     */
11 efrain 689
    public function test_unique_sql_parameter_behaviour(): void {
1 efrain 690
        global $DB;
691
        $this->resetAfterTest();
692
        $generator = $this->getDataGenerator();
693
 
694
        // Create a test course with multiple groupings and groups and a student in each of them.
695
        $course = $generator->create_course();
696
        $user = $generator->create_user();
697
        $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
698
        $generator->enrol_user($user->id, $course->id, $studentroleid);
699
        // The total number of groupings and groups must not be greater than 61.
700
        // There is a limit in MySQL on the max number of joined tables.
701
        $groups = [];
702
        for ($i = 0; $i < 25; $i++) {
703
            $group = $generator->create_group(array('courseid' => $course->id));
704
            groups_add_member($group, $user);
705
            $groups[] = $group;
706
        }
707
        $groupings = [];
708
        for ($i = 0; $i < 25; $i++) {
709
            $groupings[] = $generator->create_grouping(array('courseid' => $course->id));
710
        }
711
        foreach ($groupings as $grouping) {
712
            foreach ($groups as $group) {
713
                groups_assign_grouping($grouping->id, $group->id);
714
            }
715
        }
716
        $info = new \core_availability\mock_info($course);
717
 
718
        // Make a huge tree with 'AND' of all groups and groupings conditions.
719
        $conditions = [];
720
        foreach ($groups as $group) {
721
            $conditions[] = \availability_group\condition::get_json($group->id);
722
        }
723
        foreach ($groupings as $groupingid) {
724
            $conditions[] = \availability_grouping\condition::get_json($grouping->id);
725
        }
726
        shuffle($conditions);
727
        $tree = new tree(tree::get_root_json($conditions));
728
        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
729
        // This must not throw exception.
730
        $DB->fix_sql_params($sql, $params);
731
    }
732
 
733
    /**
734
     * Tests get_user_list_sql.
735
     */
11 efrain 736
    public function test_get_user_list_sql(): void {
1 efrain 737
        global $DB;
738
        $this->resetAfterTest();
739
        $generator = $this->getDataGenerator();
740
 
741
        // Create a test course with 2 groups and users in each combination of them.
742
        $course = $generator->create_course();
743
        $group1 = $generator->create_group(array('courseid' => $course->id));
744
        $group2 = $generator->create_group(array('courseid' => $course->id));
745
        $userin1 = $generator->create_user();
746
        $userin2 = $generator->create_user();
747
        $userinboth = $generator->create_user();
748
        $userinneither = $generator->create_user();
749
        $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
750
        foreach (array($userin1, $userin2, $userinboth, $userinneither) as $user) {
751
            $generator->enrol_user($user->id, $course->id, $studentroleid);
752
        }
753
        groups_add_member($group1, $userin1);
754
        groups_add_member($group2, $userin2);
755
        groups_add_member($group1, $userinboth);
756
        groups_add_member($group2, $userinboth);
757
        $info = new \core_availability\mock_info($course);
758
 
759
        // Tree with single group condition.
760
        $tree = new tree(tree::get_root_json(array(
761
            \availability_group\condition::get_json($group1->id)
762
            )));
763
        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
764
        $result = $DB->get_fieldset_sql($sql, $params);
765
        sort($result);
766
        $this->assertEquals(array($userin1->id, $userinboth->id), $result);
767
 
768
        // Tree with 'AND' of both group conditions.
769
        $tree = new tree(tree::get_root_json(array(
770
            \availability_group\condition::get_json($group1->id),
771
            \availability_group\condition::get_json($group2->id)
772
        )));
773
        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
774
        $result = $DB->get_fieldset_sql($sql, $params);
775
        sort($result);
776
        $this->assertEquals(array($userinboth->id), $result);
777
 
778
        // Tree with 'AND' of both group conditions.
779
        $tree = new tree(tree::get_root_json(array(
780
            \availability_group\condition::get_json($group1->id),
781
            \availability_group\condition::get_json($group2->id)
782
        ), tree::OP_OR));
783
        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
784
        $result = $DB->get_fieldset_sql($sql, $params);
785
        sort($result);
786
        $this->assertEquals(array($userin1->id, $userin2->id, $userinboth->id), $result);
787
 
788
        // Check with flipped logic (NOT above level of tree).
789
        list($sql, $params) = $tree->get_user_list_sql(true, $info, false);
790
        $result = $DB->get_fieldset_sql($sql, $params);
791
        sort($result);
792
        $this->assertEquals(array($userinneither->id), $result);
793
 
794
        // Tree with 'OR' of group conditions and a non-filtering condition.
795
        // The non-filtering condition should mean that ALL users are included.
796
        $tree = new tree(tree::get_root_json(array(
797
            \availability_group\condition::get_json($group1->id),
798
            \availability_date\condition::get_json(\availability_date\condition::DIRECTION_UNTIL, 3)
799
        ), tree::OP_OR));
800
        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
801
        $this->assertEquals('', $sql);
802
        $this->assertEquals(array(), $params);
803
    }
804
 
805
    /**
806
     * Utility function to build the PHP structure representing a mock condition.
807
     *
808
     * @param array $params Mock parameters
809
     * @return \stdClass Structure object
810
     */
811
    protected static function mock(array $params) {
812
        $params['type'] = 'mock';
813
        return (object)$params;
814
    }
815
}