Proyectos de Subversion Moodle

Rev

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