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_search;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once(__DIR__ . '/fixtures/testable_core_search.php');
22
require_once(__DIR__ . '/fixtures/mock_search_area.php');
23
 
24
/**
25
 * Unit tests for search manager.
26
 *
27
 * @package     core_search
28
 * @category    phpunit
29
 * @copyright   2015 David Monllao {@link http://www.davidmonllao.com}
30
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
1441 ariadna 32
final class manager_test extends \advanced_testcase {
1 efrain 33
 
34
    /**
35
     * Forum area id.
36
     *
37
     * @var string
38
     */
39
 
40
    protected $forumpostareaid = null;
41
 
42
    /**
43
     * Courses area id.
44
     *
45
     * @var string
46
     */
47
    protected $coursesareaid = null;
48
 
49
    public function setUp(): void {
1441 ariadna 50
        parent::setUp();
1 efrain 51
        $this->forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
52
        $this->coursesareaid = \core_search\manager::generate_areaid('core_course', 'course');
53
    }
54
 
55
    protected function tearDown(): void {
56
        // Stop it from faking time in the search manager (if set by test).
57
        \testable_core_search::fake_current_time();
58
        parent::tearDown();
59
    }
60
 
11 efrain 61
    public function test_search_enabled(): void {
1 efrain 62
 
63
        $this->resetAfterTest();
64
 
65
        // Disabled by default.
66
        $this->assertFalse(\core_search\manager::is_global_search_enabled());
67
 
68
        set_config('enableglobalsearch', true);
69
        $this->assertTrue(\core_search\manager::is_global_search_enabled());
70
 
71
        set_config('enableglobalsearch', false);
72
        $this->assertFalse(\core_search\manager::is_global_search_enabled());
73
    }
74
 
75
    /**
76
     * Tests the course search url is correct.
77
     *
78
     * @param bool|null $gsenabled Enable global search (null to leave as the default).
79
     * @param bool|null $allcourses Enable searching all courses (null to leave as the default).
80
     * @param bool|null $enablearea Enable the course search area (null to leave as the default).
81
     * @param string $expected The expected course search url.
82
     * @dataProvider data_course_search_url
83
     */
11 efrain 84
    public function test_course_search_url(?bool $gsenabled, ?bool $allcourses, ?bool $enablearea, string $expected): void {
1 efrain 85
        $this->resetAfterTest();
86
 
87
        if (!is_null($gsenabled)) {
88
            set_config('enableglobalsearch', $gsenabled);
89
        }
90
 
91
        if (!is_null($allcourses)) {
92
            set_config('searchincludeallcourses', $allcourses);
93
        }
94
 
95
        if (!is_null($enablearea)) {
96
            // Setup the course search area.
97
            $areaid = \core_search\manager::generate_areaid('core_course', 'course');
98
            $area = \core_search\manager::get_search_area($areaid);
99
            $area->set_enabled($enablearea);
100
        }
101
 
102
        $this->assertEquals(new \moodle_url($expected), \core_search\manager::get_course_search_url());
103
    }
104
 
105
    /**
106
     * Data for the test_course_search_url test.
107
     *
108
     * @return array[]
109
     */
1441 ariadna 110
    public static function data_course_search_url(): array {
1 efrain 111
        return [
112
            'defaults' => [null, null, null, '/course/search.php'],
113
            'enabled' => [true, true, true, '/search/index.php'],
114
            'no all courses, no search area' => [true, false, false, '/course/search.php'],
115
            'no search area' => [true, true, false, '/course/search.php'],
116
            'no all courses' => [true, false, true, '/course/search.php'],
117
            'disabled' => [false, false, false, '/course/search.php'],
118
            'no global search' => [false, true, false, '/course/search.php'],
119
            'no global search, no all courses' => [false, false, true, '/course/search.php'],
120
            'no global search, no search area' => [false, true, false, '/course/search.php'],
121
        ];
122
    }
123
 
124
    /**
125
     * Tests that we detect that global search can replace frontpage course search.
126
     *
127
     * @param bool|null $gsenabled Enable global search (null to leave as the default).
128
     * @param bool|null $allcourses Enable searching all courses (null to leave as the default).
129
     * @param bool|null $enablearea Enable the course search area (null to leave as the default).
130
     * @param bool $expected The expected result.
131
     * @dataProvider data_can_replace_course_search
132
     */
11 efrain 133
    public function test_can_replace_course_search(?bool $gsenabled, ?bool $allcourses, ?bool $enablearea, bool $expected): void {
1 efrain 134
        $this->resetAfterTest();
135
 
136
        if (!is_null($gsenabled)) {
137
            set_config('enableglobalsearch', $gsenabled);
138
        }
139
 
140
        if (!is_null($allcourses)) {
141
            set_config('searchincludeallcourses', $allcourses);
142
        }
143
 
144
        if (!is_null($enablearea)) {
145
            // Setup the course search area.
146
            $areaid = \core_search\manager::generate_areaid('core_course', 'course');
147
            $area = \core_search\manager::get_search_area($areaid);
148
            $area->set_enabled($enablearea);
149
        }
150
 
151
        $this->assertEquals($expected, \core_search\manager::can_replace_course_search());
152
    }
153
 
154
    /**
155
     * Data for the test_can_replace_course_search test.
156
     *
157
     * @return array[]
158
     */
1441 ariadna 159
    public static function data_can_replace_course_search(): array {
1 efrain 160
        return [
161
            'defaults' => [null, null, null, false],
162
            'enabled' => [true, true, true, true],
163
            'no all courses, no search area' => [true, false, false, false],
164
            'no search area' => [true, true, false, false],
165
            'no all courses' => [true, false, true, false],
166
            'disabled' => [false, false, false, false],
167
            'no global search' => [false, true, false, false],
168
            'no global search, no all courses' => [false, false, true, false],
169
            'no global search, no search area' => [false, true, false, false],
170
        ];
171
    }
172
 
11 efrain 173
    public function test_search_areas(): void {
1 efrain 174
        global $CFG;
175
 
176
        $this->resetAfterTest();
177
 
178
        set_config('enableglobalsearch', true);
179
 
180
        $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita');
181
 
182
        $searcharea = \core_search\manager::get_search_area($this->forumpostareaid);
183
        $this->assertInstanceOf('\core_search\base', $searcharea);
184
 
185
        $this->assertFalse(\core_search\manager::get_search_area($fakeareaid));
186
 
187
        $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list());
188
        $this->assertArrayNotHasKey($fakeareaid, \core_search\manager::get_search_areas_list());
189
 
190
        // Enabled by default once global search is enabled.
191
        $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
192
 
193
        list($componentname, $varname) = $searcharea->get_config_var_name();
194
        set_config($varname . '_enabled', 0, $componentname);
195
        \core_search\manager::clear_static();
196
 
197
        $this->assertArrayNotHasKey('mod_forum', \core_search\manager::get_search_areas_list(true));
198
 
199
        set_config($varname . '_enabled', 1, $componentname);
200
 
201
        // Although the result is wrong, we want to check that \core_search\manager::get_search_areas_list returns cached results.
202
        $this->assertArrayNotHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
203
 
204
        // Now we check the real result.
205
        \core_search\manager::clear_static();
206
        $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
207
    }
208
 
11 efrain 209
    public function test_search_config(): void {
1 efrain 210
 
211
        $this->resetAfterTest();
212
 
213
        $search = \testable_core_search::instance();
214
 
215
        // We should test both plugin types and core subsystems. No core subsystems available yet.
216
        $searcharea = $search->get_search_area($this->forumpostareaid);
217
 
218
        list($componentname, $varname) = $searcharea->get_config_var_name();
219
 
220
        // Just with a couple of vars should be enough.
221
        $start = time() - 100;
222
        $end = time();
223
        set_config($varname . '_indexingstart', $start, $componentname);
224
        set_config($varname . '_indexingend', $end, $componentname);
225
 
226
        $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
227
        $this->assertEquals($start, $configs[$this->forumpostareaid]->indexingstart);
228
        $this->assertEquals($end, $configs[$this->forumpostareaid]->indexingend);
229
        $this->assertEquals(false, $configs[$this->forumpostareaid]->partial);
230
 
231
        try {
232
            $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita');
233
            $search->reset_config($fakeareaid);
234
            $this->fail('An exception should be triggered if the provided search area does not exist.');
235
        } catch (\moodle_exception $ex) {
236
            $this->assertStringContainsString($fakeareaid . ' search area is not available.', $ex->getMessage());
237
        }
238
 
239
        // We clean it all but enabled components.
240
        $search->reset_config($this->forumpostareaid);
241
        $config = $searcharea->get_config();
242
        $this->assertEquals(1, $config[$varname . '_enabled']);
243
        $this->assertEquals(0, $config[$varname . '_indexingstart']);
244
        $this->assertEquals(0, $config[$varname . '_indexingend']);
245
        $this->assertEquals(0, $config[$varname . '_lastindexrun']);
246
        $this->assertEquals(0, $config[$varname . '_partial']);
247
        // No caching.
248
        $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
249
        $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart);
250
        $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend);
251
 
252
        set_config($varname . '_indexingstart', $start, $componentname);
253
        set_config($varname . '_indexingend', $end, $componentname);
254
 
255
        // All components config should be reset.
256
        $search->reset_config();
257
        $this->assertEquals(0, get_config($componentname, $varname . '_indexingstart'));
258
        $this->assertEquals(0, get_config($componentname, $varname . '_indexingend'));
259
        $this->assertEquals(0, get_config($componentname, $varname . '_lastindexrun'));
260
        // No caching.
261
        $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
262
        $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart);
263
        $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend);
264
    }
265
 
266
    /**
267
     * Tests the get_last_indexing_duration method in the base area class.
268
     */
11 efrain 269
    public function test_get_last_indexing_duration(): void {
1 efrain 270
        $this->resetAfterTest();
271
 
272
        $search = \testable_core_search::instance();
273
 
274
        $searcharea = $search->get_search_area($this->forumpostareaid);
275
 
276
        // When never indexed, the duration is false.
277
        $this->assertSame(false, $searcharea->get_last_indexing_duration());
278
 
279
        // Set the start/end times.
280
        list($componentname, $varname) = $searcharea->get_config_var_name();
281
        $start = time() - 100;
282
        $end = time();
283
        set_config($varname . '_indexingstart', $start, $componentname);
284
        set_config($varname . '_indexingend', $end, $componentname);
285
 
286
        // The duration should now be 100.
287
        $this->assertSame(100, $searcharea->get_last_indexing_duration());
288
    }
289
 
290
    /**
291
     * Tests that partial indexing works correctly.
292
     */
11 efrain 293
    public function test_partial_indexing(): void {
1 efrain 294
        global $USER;
295
 
296
        $this->resetAfterTest();
297
        $this->setAdminUser();
298
 
299
        // Create a course and a forum.
300
        $generator = $this->getDataGenerator();
301
        $course = $generator->create_course();
302
        $forum = $generator->create_module('forum', ['course' => $course->id]);
303
 
304
        // Index everything up to current. Ensure the course is older than current second so it
305
        // definitely doesn't get indexed again next time.
306
        $this->waitForSecond();
307
        $search = \testable_core_search::instance();
308
        $search->index(false, 0);
309
 
310
        $searcharea = $search->get_search_area($this->forumpostareaid);
311
        list($componentname, $varname) = $searcharea->get_config_var_name();
312
        $this->assertFalse(get_config($componentname, $varname . '_partial'));
313
 
314
        // Add 3 discussions to the forum.
315
        $now = time();
316
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
317
                'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now,
318
                'name' => 'Frog']);
319
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
320
                'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 1,
321
                'name' => 'Toad']);
322
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
323
                'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
324
                'name' => 'Zombie']);
325
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
326
                'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
327
                'name' => 'Werewolf']);
328
        time_sleep_until($now + 3);
329
 
330
        // Clear the count of added documents.
331
        $search->get_engine()->get_and_clear_added_documents();
332
 
333
        // Make the search engine delay while indexing each document.
334
        $search->get_engine()->set_add_delay(1.2);
335
 
336
        // Use fake time, starting from now.
337
        \testable_core_search::fake_current_time(time());
338
 
339
        // Index with a limit of 2 seconds - it should index 2 of the documents (after the second
340
        // one, it will have taken 2.4 seconds so it will stop).
341
        $search->index(false, 2);
342
        $added = $search->get_engine()->get_and_clear_added_documents();
343
        $this->assertCount(2, $added);
344
        $this->assertEquals('Frog', $added[0]->get('title'));
345
        $this->assertEquals('Toad', $added[1]->get('title'));
346
        $this->assertEquals(1, get_config($componentname, $varname . '_partial'));
347
        // Whilst 2.4 seconds of "time" have elapsed, the indexing duration is
348
        // measured in seconds, so should be 2.
349
        $this->assertEquals(2, $searcharea->get_last_indexing_duration());
350
 
351
        // Add a label.
352
        $generator->create_module('label', ['course' => $course->id, 'intro' => 'Vampire']);
353
 
354
        // Wait to next second (so as to not reindex the label more than once, as it will now
355
        // be timed before the indexing run).
356
        $this->waitForSecond();
357
        \testable_core_search::fake_current_time(time());
358
 
359
        // Next index with 1 second limit should do the label and not the forum - the logic is,
360
        // if it spent ages indexing an area last time, do that one last on next run.
361
        $search->index(false, 1);
362
        $added = $search->get_engine()->get_and_clear_added_documents();
363
        $this->assertCount(1, $added);
364
        $this->assertEquals('Vampire', $added[0]->get('title'));
365
 
366
        // Index again with a 3 second limit - it will redo last post for safety (because of other
367
        // things possibly having the same time second), and then do the remaining one. (Note:
368
        // because it always does more than one second worth of items, it would actually index 2
369
        // posts even if the limit were less than 2, we are testing it does 3 posts to make sure
370
        // the time limiting is actually working with the specified time.)
371
        $search->index(false, 3);
372
        $added = $search->get_engine()->get_and_clear_added_documents();
373
        $this->assertCount(3, $added);
374
        $this->assertEquals('Toad', $added[0]->get('title'));
375
        $remainingtitles = [$added[1]->get('title'), $added[2]->get('title')];
376
        sort($remainingtitles);
377
        $this->assertEquals(['Werewolf', 'Zombie'], $remainingtitles);
378
        $this->assertFalse(get_config($componentname, $varname . '_partial'));
379
 
380
        // Index again - there should be nothing to index this time.
381
        $search->index(false, 2);
382
        $added = $search->get_engine()->get_and_clear_added_documents();
383
        $this->assertCount(0, $added);
384
        $this->assertFalse(get_config($componentname, $varname . '_partial'));
385
    }
386
 
387
    /**
388
     * Tests the progress display while indexing.
389
     *
390
     * This tests the different logic about displaying progress for slow/fast and
391
     * complete/incomplete processing.
392
     */
11 efrain 393
    public function test_index_progress(): void {
1 efrain 394
        $this->resetAfterTest();
395
        $generator = $this->getDataGenerator();
396
 
397
        // Set up the fake search area.
398
        $search = \testable_core_search::instance();
399
        $area = new \core_mocksearch\search\mock_search_area();
400
        $search->add_search_area('whatever', $area);
401
        $searchgenerator = $generator->get_plugin_generator('core_search');
402
        $searchgenerator->setUp();
403
 
404
        // Add records with specific time modified values.
405
        $time = strtotime('2017-11-01 01:00');
406
        for ($i = 0; $i < 8; $i ++) {
407
            $searchgenerator->create_record((object)['timemodified' => $time]);
408
            $time += 60;
409
        }
410
 
411
        // Simulate slow progress on indexing and initial query.
412
        $now = strtotime('2017-11-11 01:00');
413
        \testable_core_search::fake_current_time($now);
414
        $area->set_indexing_delay(10.123);
415
        $search->get_engine()->set_add_delay(15.789);
416
 
417
        // Run search indexing and check output.
418
        $progress = new \progress_trace_buffer(new \text_progress_trace(), false);
419
        $search->index(false, 75, $progress);
420
        $out = $progress->get_buffer();
421
        $progress->reset_buffer();
422
 
423
        // Check for the standard text.
424
        $this->assertStringContainsString('Processing area: Mock search area', $out);
425
        $this->assertStringContainsString('Stopping indexing due to time limit', $out);
426
 
427
        // Check for initial query performance indication.
428
        $this->assertStringContainsString('Initial query took 10.1 seconds.', $out);
429
 
430
        // Check for the two (approximately) every-30-seconds messages.
431
        $this->assertStringContainsString('01:00:41: Done to 1/11/17, 01:01', $out);
432
        $this->assertStringContainsString('01:01:13: Done to 1/11/17, 01:03', $out);
433
 
434
        // Check for the 'not complete' indicator showing when it was done until.
435
        $this->assertStringContainsString('Processed 5 records containing 5 documents, in 89.1 seconds ' .
436
                '(not complete; done to 1/11/17, 01:04)', $out);
437
 
438
        // Make the initial query delay less than 5 seconds, so it won't appear. Make the documents
439
        // quicker, so that the 30-second delay won't be needed.
440
        $area->set_indexing_delay(4.9);
441
        $search->get_engine()->set_add_delay(1);
442
 
443
        // Run search indexing (still partial) and check output.
444
        $progress = new \progress_trace_buffer(new \text_progress_trace(), false);
445
        $search->index(false, 5, $progress);
446
        $out = $progress->get_buffer();
447
        $progress->reset_buffer();
448
 
449
        $this->assertStringContainsString('Processing area: Mock search area', $out);
450
        $this->assertStringContainsString('Stopping indexing due to time limit', $out);
451
        $this->assertStringNotContainsString('Initial query took', $out);
452
        $this->assertStringNotContainsString(': Done to', $out);
453
        $this->assertStringContainsString('Processed 2 records containing 2 documents, in 6.9 seconds ' .
454
                '(not complete; done to 1/11/17, 01:05).', $out);
455
 
456
        // Run the remaining items to complete it.
457
        $progress = new \progress_trace_buffer(new \text_progress_trace(), false);
458
        $search->index(false, 100, $progress);
459
        $out = $progress->get_buffer();
460
        $progress->reset_buffer();
461
 
462
        $this->assertStringContainsString('Processing area: Mock search area', $out);
463
        $this->assertStringNotContainsString('Stopping indexing due to time limit', $out);
464
        $this->assertStringNotContainsString('Initial query took', $out);
465
        $this->assertStringNotContainsString(': Done to', $out);
466
        $this->assertStringContainsString('Processed 3 records containing 3 documents, in 7.9 seconds.', $out);
467
 
468
        $searchgenerator->tearDown();
469
    }
470
 
471
    /**
472
     * Tests that documents with modified time in the future are NOT indexed (as this would cause
473
     * a problem by preventing it from indexing other documents modified between now and the future
474
     * date).
475
     */
11 efrain 476
    public function test_future_documents(): void {
1 efrain 477
        $this->resetAfterTest();
478
 
479
        // Create a course and a forum.
480
        $generator = $this->getDataGenerator();
481
        $course = $generator->create_course();
482
        $forum = $generator->create_module('forum', ['course' => $course->id]);
483
 
484
        // Index everything up to current. Ensure the course is older than current second so it
485
        // definitely doesn't get indexed again next time.
486
        $this->waitForSecond();
487
        $search = \testable_core_search::instance();
488
        $search->index(false, 0);
489
        $search->get_engine()->get_and_clear_added_documents();
490
 
491
        // Add 2 discussions to the forum, one of which happend just now, but the other is
492
        // incorrectly set to the future.
493
        $now = time();
494
        $userid = get_admin()->id;
495
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
496
                'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now,
497
                'name' => 'Frog']);
498
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
499
                'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now + 100,
500
                'name' => 'Toad']);
501
 
502
        // Wait for a second so we're not actually on the same second as the forum post (there's a
503
        // 1 second overlap between indexing; it would get indexed in both checks below otherwise).
504
        $this->waitForSecond();
505
 
506
        // Index.
507
        $search->index(false);
508
        $added = $search->get_engine()->get_and_clear_added_documents();
509
        $this->assertCount(1, $added);
510
        $this->assertEquals('Frog', $added[0]->get('title'));
511
 
512
        // Check latest time - it should be the same as $now, not the + 100.
513
        $searcharea = $search->get_search_area($this->forumpostareaid);
514
        list($componentname, $varname) = $searcharea->get_config_var_name();
515
        $this->assertEquals($now, get_config($componentname, $varname . '_lastindexrun'));
516
 
517
        // Index again - there should be nothing to index this time.
518
        $search->index(false);
519
        $added = $search->get_engine()->get_and_clear_added_documents();
520
        $this->assertCount(0, $added);
521
    }
522
 
523
    /**
524
     * Tests that indexing a specified context works correctly.
525
     */
11 efrain 526
    public function test_context_indexing(): void {
1 efrain 527
        global $USER;
528
 
529
        $this->resetAfterTest();
530
        $this->setAdminUser();
531
 
532
        // Create a course and two forums and a page.
533
        $generator = $this->getDataGenerator();
534
        $course = $generator->create_course();
535
        $now = time();
536
        $forum1 = $generator->create_module('forum', ['course' => $course->id]);
537
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
538
                'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now,
539
                'name' => 'Frog']);
540
        $this->waitForSecond();
541
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
542
                'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
543
                'name' => 'Zombie']);
544
        $forum2 = $generator->create_module('forum', ['course' => $course->id]);
545
        $this->waitForSecond();
546
        $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
547
                'forum' => $forum2->id, 'userid' => $USER->id, 'timemodified' => $now + 1,
548
                'name' => 'Toad']);
549
        $generator->create_module('page', ['course' => $course->id]);
550
        $generator->create_module('forum', ['course' => $course->id]);
551
 
552
        // Index forum 1 only.
553
        $search = \testable_core_search::instance();
554
        $buffer = new \progress_trace_buffer(new \text_progress_trace(), false);
555
        $result = $search->index_context(\context_module::instance($forum1->cmid), '', 0, $buffer);
556
        $this->assertTrue($result->complete);
557
        $log = $buffer->get_buffer();
558
        $buffer->reset_buffer();
559
 
560
        // Confirm that output only processed 1 forum activity and 2 posts.
561
        $this->assertNotFalse(strpos($log, "area: Forum - activity information\n  Processed 1 "));
562
        $this->assertNotFalse(strpos($log, "area: Forum - posts\n  Processed 2 "));
563
 
564
        // Confirm that some areas for different types of context were skipped.
565
        $this->assertNotFalse(strpos($log, "area: Users\n  Skipping"));
566
        $this->assertNotFalse(strpos($log, "area: Courses\n  Skipping"));
567
 
568
        // Confirm that another module area had no results.
569
        $this->assertNotFalse(strpos($log, "area: Page\n  No documents"));
570
 
571
        // Index whole course.
572
        $result = $search->index_context(\context_course::instance($course->id), '', 0, $buffer);
573
        $this->assertTrue($result->complete);
574
        $log = $buffer->get_buffer();
575
        $buffer->reset_buffer();
576
 
577
        // Confirm that output processed 3 forum activities and 3 posts.
578
        $this->assertNotFalse(strpos($log, "area: Forum - activity information\n  Processed 3 "));
579
        $this->assertNotFalse(strpos($log, "area: Forum - posts\n  Processed 3 "));
580
 
581
        // The course area was also included this time.
582
        $this->assertNotFalse(strpos($log, "area: Courses\n  Processed 1 "));
583
 
584
        // Confirm that another module area had results too.
585
        $this->assertNotFalse(strpos($log, "area: Page\n  Processed 1 "));
586
 
587
        // Index whole course, but only forum posts.
588
        $result = $search->index_context(\context_course::instance($course->id), 'mod_forum-post',
589
                0, $buffer);
590
        $this->assertTrue($result->complete);
591
        $log = $buffer->get_buffer();
592
        $buffer->reset_buffer();
593
 
594
        // Confirm that output processed 3 posts but not forum activities.
595
        $this->assertFalse(strpos($log, "area: Forum - activity information"));
596
        $this->assertNotFalse(strpos($log, "area: Forum - posts\n  Processed 3 "));
597
 
598
        // Set time limit and retry index of whole course, taking 3 tries to complete it.
599
        $search->get_engine()->set_add_delay(0.4);
600
        $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer);
601
        $log = $buffer->get_buffer();
602
        $buffer->reset_buffer();
603
        $this->assertFalse($result->complete);
604
        $this->assertNotFalse(strpos($log, "area: Forum - activity information\n  Processed 2 "));
605
 
606
        $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer,
607
                $result->startfromarea, $result->startfromtime);
608
        $log = $buffer->get_buffer();
609
        $buffer->reset_buffer();
610
        $this->assertNotFalse(strpos($log, "area: Forum - activity information\n  Processed 2 "));
611
        $this->assertNotFalse(strpos($log, "area: Forum - posts\n  Processed 2 "));
612
        $this->assertFalse($result->complete);
613
 
614
        $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer,
615
                $result->startfromarea, $result->startfromtime);
616
        $log = $buffer->get_buffer();
617
        $buffer->reset_buffer();
618
        $this->assertNotFalse(strpos($log, "area: Forum - posts\n  Processed 2 "));
619
        $this->assertTrue($result->complete);
620
    }
621
 
622
    /**
623
     * Adding this test here as get_areas_user_accesses process is the same, results just depend on the context level.
624
     *
625
     * @return void
626
     */
11 efrain 627
    public function test_search_user_accesses(): void {
1 efrain 628
        global $DB;
629
 
630
        $this->resetAfterTest();
631
 
632
        $frontpage = $DB->get_record('course', array('id' => SITEID));
633
        $frontpagectx = \context_course::instance($frontpage->id);
634
        $course1 = $this->getDataGenerator()->create_course();
635
        $course1ctx = \context_course::instance($course1->id);
636
        $course2 = $this->getDataGenerator()->create_course();
637
        $course2ctx = \context_course::instance($course2->id);
638
        $course3 = $this->getDataGenerator()->create_course();
639
        $course3ctx = \context_course::instance($course3->id);
640
        $teacher = $this->getDataGenerator()->create_user();
641
        $teacherctx = \context_user::instance($teacher->id);
642
        $student = $this->getDataGenerator()->create_user();
643
        $studentctx = \context_user::instance($student->id);
644
        $noaccess = $this->getDataGenerator()->create_user();
645
        $noaccessctx = \context_user::instance($noaccess->id);
646
        $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, 'teacher');
647
        $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student');
648
 
649
        $frontpageforum = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id));
650
        $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
651
        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
652
        $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
653
        $frontpageforumcontext = \context_module::instance($frontpageforum->cmid);
654
        $context1 = \context_module::instance($forum1->cmid);
655
        $context2 = \context_module::instance($forum2->cmid);
656
        $context3 = \context_module::instance($forum3->cmid);
657
        $forum4 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id));
658
        $context4 = \context_module::instance($forum4->cmid);
659
 
660
        $search = \testable_core_search::instance();
661
        $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
662
        $search->add_core_search_areas();
663
        $search->add_search_area($mockareaid, new \core_mocksearch\search\mock_search_area());
664
 
665
        $this->setAdminUser();
666
        $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
667
 
668
        $sitectx = \context_course::instance(SITEID);
669
 
670
        // Can access the frontpage ones.
671
        $this->setUser($noaccess);
672
        $contexts = $search->get_areas_user_accesses()->usercontexts;
673
        $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id), $contexts[$this->forumpostareaid]);
674
        $this->assertEquals(array($sitectx->id => $sitectx->id), $contexts[$this->coursesareaid]);
675
        $mockctxs = array($noaccessctx->id => $noaccessctx->id, $frontpagectx->id => $frontpagectx->id);
676
        $this->assertEquals($mockctxs, $contexts[$mockareaid]);
677
 
678
        $this->setUser($teacher);
679
        $contexts = $search->get_areas_user_accesses()->usercontexts;
680
        $frontpageandcourse1 = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id,
681
            $context2->id => $context2->id);
682
        $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]);
683
        $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id),
684
            $contexts[$this->coursesareaid]);
685
        $mockctxs = array($teacherctx->id => $teacherctx->id,
686
                $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id);
687
        $this->assertEquals($mockctxs, $contexts[$mockareaid]);
688
 
689
        $this->setUser($student);
690
        $contexts = $search->get_areas_user_accesses()->usercontexts;
691
        $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]);
692
        $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id),
693
            $contexts[$this->coursesareaid]);
694
        $mockctxs = array($studentctx->id => $studentctx->id,
695
                $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id);
696
        $this->assertEquals($mockctxs, $contexts[$mockareaid]);
697
 
698
        // Hide the activity.
699
        set_coursemodule_visible($forum2->cmid, 0);
700
        $contexts = $search->get_areas_user_accesses()->usercontexts;
701
        $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id),
702
            $contexts[$this->forumpostareaid]);
703
 
704
        // Now test course limited searches.
705
        set_coursemodule_visible($forum2->cmid, 1);
706
        $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student');
707
        $contexts = $search->get_areas_user_accesses()->usercontexts;
708
        $allcontexts = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id,
709
            $context2->id => $context2->id, $context3->id => $context3->id);
710
        $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
711
        $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id,
712
            $course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]);
713
 
714
        $contexts = $search->get_areas_user_accesses(array($course1->id, $course2->id))->usercontexts;
715
        $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id, $context3->id => $context3->id);
716
        $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
717
        $this->assertEquals(array($course1ctx->id => $course1ctx->id,
718
            $course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]);
719
 
720
        $contexts = $search->get_areas_user_accesses(array($course2->id))->usercontexts;
721
        $allcontexts = array($context3->id => $context3->id);
722
        $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
723
        $this->assertEquals(array($course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]);
724
 
725
        $contexts = $search->get_areas_user_accesses(array($course1->id))->usercontexts;
726
        $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id);
727
        $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
728
        $this->assertEquals(array($course1ctx->id => $course1ctx->id), $contexts[$this->coursesareaid]);
729
 
730
        // Test context limited search with no course limit.
731
        $contexts = $search->get_areas_user_accesses(false,
732
                [$frontpageforumcontext->id, $course2ctx->id])->usercontexts;
733
        $this->assertEquals([$frontpageforumcontext->id => $frontpageforumcontext->id],
734
                $contexts[$this->forumpostareaid]);
735
        $this->assertEquals([$course2ctx->id => $course2ctx->id],
736
                $contexts[$this->coursesareaid]);
737
 
738
        // Test context limited search with course limit.
739
        $contexts = $search->get_areas_user_accesses([$course1->id, $course2->id],
740
                [$frontpageforumcontext->id, $course2ctx->id])->usercontexts;
741
        $this->assertArrayNotHasKey($this->forumpostareaid, $contexts);
742
        $this->assertEquals([$course2ctx->id => $course2ctx->id],
743
                $contexts[$this->coursesareaid]);
744
 
745
        // Single context and course.
746
        $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts;
747
        $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]);
748
        $this->assertArrayNotHasKey($this->coursesareaid, $contexts);
749
 
750
        // Enable "Include all visible courses" feature.
751
        set_config('searchincludeallcourses', 1);
752
        $contexts = $search->get_areas_user_accesses()->usercontexts;
753
        $expected = [
754
            $sitectx->id => $sitectx->id,
755
            $course1ctx->id => $course1ctx->id,
756
            $course2ctx->id => $course2ctx->id,
757
            $course3ctx->id => $course3ctx->id
758
        ];
759
        // Check that a student has assess to all courses data when "searchincludeallcourses" is enabled.
760
        $this->assertEquals($expected, $contexts[$this->coursesareaid]);
761
        // But at the same time doesn't have access to activities in the courses that the student can't access.
762
        $this->assertFalse(key_exists($context4->id, $contexts[$this->forumpostareaid]));
763
 
764
        // For admins, this is still limited only if we specify the things, so it should be same.
765
        $this->setAdminUser();
766
        $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts;
767
        $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]);
768
        $this->assertArrayNotHasKey($this->coursesareaid, $contexts);
769
    }
770
 
771
    /**
772
     * Tests the block support in get_search_user_accesses.
773
     *
774
     * @return void
775
     */
11 efrain 776
    public function test_search_user_accesses_blocks(): void {
1 efrain 777
        global $DB;
778
 
779
        $this->resetAfterTest();
780
        $this->setAdminUser();
781
 
782
        // Create course and add HTML block.
783
        $generator = $this->getDataGenerator();
784
        $course1 = $generator->create_course();
785
        $context1 = \context_course::instance($course1->id);
786
        $page = new \moodle_page();
787
        $page->set_context($context1);
788
        $page->set_course($course1);
789
        $page->set_pagelayout('standard');
790
        $page->set_pagetype('course-view');
791
        $page->blocks->load_blocks();
792
        $page->blocks->add_block_at_end_of_default_region('html');
793
 
794
        // Create another course with HTML blocks only in some weird page or a module page (not
795
        // yet supported, so both these blocks will be ignored).
796
        $course2 = $generator->create_course();
797
        $context2 = \context_course::instance($course2->id);
798
        $page = new \moodle_page();
799
        $page->set_context($context2);
800
        $page->set_course($course2);
801
        $page->set_pagelayout('standard');
802
        $page->set_pagetype('bogus-page');
803
        $page->blocks->load_blocks();
804
        $page->blocks->add_block_at_end_of_default_region('html');
805
 
806
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
807
        $forumcontext = \context_module::instance($forum->cmid);
808
        $page = new \moodle_page();
809
        $page->set_context($forumcontext);
810
        $page->set_course($course2);
811
        $page->set_pagelayout('standard');
812
        $page->set_pagetype('mod-forum-view');
813
        $page->blocks->load_blocks();
814
        $page->blocks->add_block_at_end_of_default_region('html');
815
 
816
        // The third course has 2 HTML blocks.
817
        $course3 = $generator->create_course();
818
        $context3 = \context_course::instance($course3->id);
819
        $page = new \moodle_page();
820
        $page->set_context($context3);
821
        $page->set_course($course3);
822
        $page->set_pagelayout('standard');
823
        $page->set_pagetype('course-view');
824
        $page->blocks->load_blocks();
825
        $page->blocks->add_block_at_end_of_default_region('html');
826
        $page->blocks->add_block_at_end_of_default_region('html');
827
 
828
        // Student 1 belongs to all 3 courses.
829
        $student1 = $generator->create_user();
830
        $generator->enrol_user($student1->id, $course1->id, 'student');
831
        $generator->enrol_user($student1->id, $course2->id, 'student');
832
        $generator->enrol_user($student1->id, $course3->id, 'student');
833
 
834
        // Student 2 belongs only to course 2.
835
        $student2 = $generator->create_user();
836
        $generator->enrol_user($student2->id, $course2->id, 'student');
837
 
838
        // And the third student is only in course 3.
839
        $student3 = $generator->create_user();
840
        $generator->enrol_user($student3->id, $course3->id, 'student');
841
 
842
        $search = \testable_core_search::instance();
843
        $search->add_core_search_areas();
844
 
845
        // Admin gets 'true' result to function regardless of blocks.
846
        $this->setAdminUser();
847
        $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
848
 
849
        // Student 1 gets all 3 block contexts.
850
        $this->setUser($student1);
851
        $contexts = $search->get_areas_user_accesses()->usercontexts;
852
        $this->assertArrayHasKey('block_html-content', $contexts);
853
        $this->assertCount(3, $contexts['block_html-content']);
854
 
855
        // Student 2 does not get any blocks.
856
        $this->setUser($student2);
857
        $contexts = $search->get_areas_user_accesses()->usercontexts;
858
        $this->assertArrayNotHasKey('block_html-content', $contexts);
859
 
860
        // Student 3 gets only two of them.
861
        $this->setUser($student3);
862
        $contexts = $search->get_areas_user_accesses()->usercontexts;
863
        $this->assertArrayHasKey('block_html-content', $contexts);
864
        $this->assertCount(2, $contexts['block_html-content']);
865
 
866
        // A course limited search for student 1 is the same as the student 3 search.
867
        $this->setUser($student1);
868
        $limitedcontexts = $search->get_areas_user_accesses([$course3->id])->usercontexts;
869
        $this->assertEquals($contexts['block_html-content'], $limitedcontexts['block_html-content']);
870
 
871
        // Get block context ids for the blocks that appear.
872
        $blockcontextids = $DB->get_fieldset_sql('
873
            SELECT x.id
874
              FROM {block_instances} bi
875
              JOIN {context} x ON x.instanceid = bi.id AND x.contextlevel = ?
876
             WHERE (parentcontextid = ? OR parentcontextid = ?)
877
                   AND blockname = ?
878
          ORDER BY bi.id', [CONTEXT_BLOCK, $context1->id, $context3->id, 'html']);
879
 
880
        // Context limited search (no course).
881
        $contexts = $search->get_areas_user_accesses(false,
882
                [$blockcontextids[0], $blockcontextids[2]])->usercontexts;
883
        $this->assertCount(2, $contexts['block_html-content']);
884
 
885
        // Context limited search (with course 3).
886
        $contexts = $search->get_areas_user_accesses([$course2->id, $course3->id],
887
                [$blockcontextids[0], $blockcontextids[2]])->usercontexts;
888
        $this->assertCount(1, $contexts['block_html-content']);
889
    }
890
 
891
    /**
892
     * Tests retrieval of users search areas when limiting to a course the user is not enrolled in
893
     */
11 efrain 894
    public function test_search_users_accesses_limit_non_enrolled_course(): void {
1 efrain 895
        global $DB;
896
 
897
        $this->resetAfterTest();
898
 
899
        $user = $this->getDataGenerator()->create_user();
900
        $this->setUser($user);
901
 
902
        $search = \testable_core_search::instance();
903
        $search->add_core_search_areas();
904
 
905
        $course = $this->getDataGenerator()->create_course();
906
        $context = \context_course::instance($course->id);
907
 
908
        // Limit courses to search to only those the user is enrolled in.
909
        set_config('searchallavailablecourses', 0);
910
 
911
        $usercontexts = $search->get_areas_user_accesses([$course->id])->usercontexts;
912
        $this->assertNotEmpty($usercontexts);
913
        $this->assertArrayNotHasKey('core_course-course', $usercontexts);
914
 
915
        // This config ensures the search will also include courses the user can view.
916
        set_config('searchallavailablecourses', 1);
917
 
918
        // Allow "Authenticated user" role to view the course without being enrolled in it.
919
        $userrole = $DB->get_record('role', ['shortname' => 'user'], '*', MUST_EXIST);
920
        role_change_permission($userrole->id, $context, 'moodle/course:view', CAP_ALLOW);
921
 
922
        $usercontexts = $search->get_areas_user_accesses([$course->id])->usercontexts;
923
        $this->assertNotEmpty($usercontexts);
924
        $this->assertArrayHasKey('core_course-course', $usercontexts);
925
        $this->assertEquals($context->id, reset($usercontexts['core_course-course']));
926
    }
927
 
928
    /**
929
     * Test get_areas_user_accesses with regard to the 'all available courses' config option.
930
     *
931
     * @return void
932
     */
11 efrain 933
    public function test_search_user_accesses_allavailable(): void {
1 efrain 934
        global $DB, $CFG;
935
 
936
        $this->resetAfterTest();
937
 
938
        // Front page, including a forum.
939
        $frontpage = $DB->get_record('course', array('id' => SITEID));
940
        $forumfront = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id));
941
        $forumfrontctx = \context_module::instance($forumfront->cmid);
942
 
943
        // Course 1 does not allow guest access.
944
        $course1 = $this->getDataGenerator()->create_course((object)array(
945
                'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED,
946
                'enrol_guest_password_0' => ''));
947
        $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
948
        $forum1ctx = \context_module::instance($forum1->cmid);
949
 
950
        // Course 2 does not allow guest but is accessible by all users.
951
        $course2 = $this->getDataGenerator()->create_course((object)array(
952
                'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED,
953
                'enrol_guest_password_0' => ''));
954
        $course2ctx = \context_course::instance($course2->id);
955
        $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
956
        $forum2ctx = \context_module::instance($forum2->cmid);
957
        assign_capability('moodle/course:view', CAP_ALLOW, $CFG->defaultuserroleid, $course2ctx->id);
958
 
959
        // Course 3 allows guest access without password.
960
        $course3 = $this->getDataGenerator()->create_course((object)array(
961
                'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
962
                'enrol_guest_password_0' => ''));
963
        $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
964
        $forum3ctx = \context_module::instance($forum3->cmid);
965
 
966
        // Student user is enrolled in course 1.
967
        $student = $this->getDataGenerator()->create_user();
968
        $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student');
969
 
970
        // No access user is just a user with no permissions.
971
        $noaccess = $this->getDataGenerator()->create_user();
972
 
973
        // First test without the all available option.
974
        $search = \testable_core_search::instance();
975
 
976
        // Admin user can access everything.
977
        $this->setAdminUser();
978
        $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
979
 
980
        // No-access user can access only the front page forum.
981
        $this->setUser($noaccess);
982
        $contexts = $search->get_areas_user_accesses()->usercontexts;
983
        $this->assertEquals([$forumfrontctx->id], array_keys($contexts[$this->forumpostareaid]));
984
 
985
        // Student can access the front page forum plus the enrolled one.
986
        $this->setUser($student);
987
        $contexts = $search->get_areas_user_accesses()->usercontexts;
988
        $this->assertEquals([$forum1ctx->id, $forumfrontctx->id],
989
                array_keys($contexts[$this->forumpostareaid]));
990
 
991
        // Now turn on the all available option.
992
        set_config('searchallavailablecourses', 1);
993
 
994
        // Admin user can access everything.
995
        $this->setAdminUser();
996
        $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
997
 
998
        // No-access user can access the front page forum and course 2, 3.
999
        $this->setUser($noaccess);
1000
        $contexts = $search->get_areas_user_accesses()->usercontexts;
1001
        $this->assertEquals([$forum2ctx->id, $forum3ctx->id, $forumfrontctx->id],
1002
                array_keys($contexts[$this->forumpostareaid]));
1003
 
1004
        // Student can access the front page forum plus the enrolled one plus courses 2, 3.
1005
        $this->setUser($student);
1006
        $contexts = $search->get_areas_user_accesses()->usercontexts;
1007
        $this->assertEquals([$forum1ctx->id, $forum2ctx->id, $forum3ctx->id, $forumfrontctx->id],
1008
                array_keys($contexts[$this->forumpostareaid]));
1009
    }
1010
 
1011
    /**
1012
     * Tests group-related aspects of the get_areas_user_accesses function.
1013
     */
11 efrain 1014
    public function test_search_user_accesses_groups(): void {
1 efrain 1015
        global $DB;
1016
 
1017
        $this->resetAfterTest();
1018
        $this->setAdminUser();
1019
 
1020
        // Create 2 courses each with 2 groups and 2 forums (separate/visible groups).
1021
        $generator = $this->getDataGenerator();
1022
        $course1 = $generator->create_course();
1023
        $course2 = $generator->create_course();
1024
        $group1 = $generator->create_group(['courseid' => $course1->id]);
1025
        $group2 = $generator->create_group(['courseid' => $course1->id]);
1026
        $group3 = $generator->create_group(['courseid' => $course2->id]);
1027
        $group4 = $generator->create_group(['courseid' => $course2->id]);
1028
        $forum1s = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => SEPARATEGROUPS]);
1029
        $id1s = \context_module::instance($forum1s->cmid)->id;
1030
        $forum1v = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => VISIBLEGROUPS]);
1031
        $id1v = \context_module::instance($forum1v->cmid)->id;
1032
        $forum2s = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => SEPARATEGROUPS]);
1033
        $id2s = \context_module::instance($forum2s->cmid)->id;
1034
        $forum2n = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => NOGROUPS]);
1035
        $id2n = \context_module::instance($forum2n->cmid)->id;
1036
 
1037
        // Get search instance.
1038
        $search = \testable_core_search::instance();
1039
        $search->add_core_search_areas();
1040
 
1041
        // User 1 is a manager in one course and a student in the other one. They belong to
1042
        // all of the groups 1, 2, 3, and 4.
1043
        $user1 = $generator->create_user();
1044
        $generator->enrol_user($user1->id, $course1->id, 'manager');
1045
        $generator->enrol_user($user1->id, $course2->id, 'student');
1046
        groups_add_member($group1, $user1);
1047
        groups_add_member($group2, $user1);
1048
        groups_add_member($group3, $user1);
1049
        groups_add_member($group4, $user1);
1050
 
1051
        $this->setUser($user1);
1052
        $accessinfo = $search->get_areas_user_accesses();
1053
        $contexts = $accessinfo->usercontexts;
1054
 
1055
        // Double-check all the forum contexts.
1056
        $postcontexts = $contexts['mod_forum-post'];
1057
        sort($postcontexts);
1058
        $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts);
1059
 
1060
        // Only the context in the second course (no accessallgroups) is restricted.
1061
        $restrictedcontexts = $accessinfo->separategroupscontexts;
1062
        sort($restrictedcontexts);
1063
        $this->assertEquals([$id2s], $restrictedcontexts);
1064
 
1065
        // Only the groups from the second course (no accessallgroups) are included.
1066
        $groupids = $accessinfo->usergroups;
1067
        sort($groupids);
1068
        $this->assertEquals([$group3->id, $group4->id], $groupids);
1069
 
1070
        // User 2 is a student in each course and belongs to groups 2 and 4.
1071
        $user2 = $generator->create_user();
1072
        $generator->enrol_user($user2->id, $course1->id, 'student');
1073
        $generator->enrol_user($user2->id, $course2->id, 'student');
1074
        groups_add_member($group2, $user2);
1075
        groups_add_member($group4, $user2);
1076
 
1077
        $this->setUser($user2);
1078
        $accessinfo = $search->get_areas_user_accesses();
1079
        $contexts = $accessinfo->usercontexts;
1080
 
1081
        // Double-check all the forum contexts.
1082
        $postcontexts = $contexts['mod_forum-post'];
1083
        sort($postcontexts);
1084
        $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts);
1085
 
1086
        // Both separate groups forums are restricted.
1087
        $restrictedcontexts = $accessinfo->separategroupscontexts;
1088
        sort($restrictedcontexts);
1089
        $this->assertEquals([$id1s, $id2s], $restrictedcontexts);
1090
 
1091
        // Groups from both courses are included.
1092
        $groupids = $accessinfo->usergroups;
1093
        sort($groupids);
1094
        $this->assertEquals([$group2->id, $group4->id], $groupids);
1095
 
1096
        // User 3 is a manager at system level.
1097
        $user3 = $generator->create_user();
1098
        role_assign($DB->get_field('role', 'id', ['shortname' => 'manager'], MUST_EXIST), $user3->id,
1099
                \context_system::instance());
1100
 
1101
        $this->setUser($user3);
1102
        $accessinfo = $search->get_areas_user_accesses();
1103
 
1104
        // Nothing is restricted and no groups are relevant.
1105
        $this->assertEquals([], $accessinfo->separategroupscontexts);
1106
        $this->assertEquals([], $accessinfo->usergroups);
1107
    }
1108
 
1109
    /**
1110
     * test_is_search_area
1111
     *
1112
     * @return void
1113
     */
11 efrain 1114
    public function test_is_search_area(): void {
1 efrain 1115
 
1116
        $this->assertFalse(\testable_core_search::is_search_area('\asd\asd'));
1117
        $this->assertFalse(\testable_core_search::is_search_area('\mod_forum\search\posta'));
1118
        $this->assertFalse(\testable_core_search::is_search_area('\core_search\base_mod'));
1119
        $this->assertTrue(\testable_core_search::is_search_area('\mod_forum\search\post'));
1120
        $this->assertTrue(\testable_core_search::is_search_area('\\mod_forum\\search\\post'));
1121
        $this->assertTrue(\testable_core_search::is_search_area('mod_forum\\search\\post'));
1122
    }
1123
 
1124
    /**
1125
     * Tests the request_index function used for reindexing certain contexts. This only tests
1126
     * adding things to the request list, it doesn't test that they are actually indexed by the
1127
     * scheduled task.
1128
     */
11 efrain 1129
    public function test_request_index(): void {
1 efrain 1130
        global $DB;
1131
 
1132
        $this->resetAfterTest();
1133
 
1134
        $course1 = $this->getDataGenerator()->create_course();
1135
        $course1ctx = \context_course::instance($course1->id);
1136
        $course2 = $this->getDataGenerator()->create_course();
1137
        $course2ctx = \context_course::instance($course2->id);
1138
        $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
1139
        $forum1ctx = \context_module::instance($forum1->cmid);
1140
        $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
1141
        $forum2ctx = \context_module::instance($forum2->cmid);
1142
 
1143
        // Initially no requests.
1144
        $this->assertEquals(0, $DB->count_records('search_index_requests'));
1145
 
1146
        // Request update for course 1, all areas.
1147
        \core_search\manager::request_index($course1ctx);
1148
 
1149
        // Check all details of entry.
1150
        $results = array_values($DB->get_records('search_index_requests'));
1151
        $this->assertCount(1, $results);
1152
        $this->assertEquals($course1ctx->id, $results[0]->contextid);
1153
        $this->assertEquals('', $results[0]->searcharea);
1154
        $now = time();
1155
        $this->assertLessThanOrEqual($now, $results[0]->timerequested);
1156
        $this->assertGreaterThan($now - 10, $results[0]->timerequested);
1157
        $this->assertEquals('', $results[0]->partialarea);
1158
        $this->assertEquals(0, $results[0]->partialtime);
1159
 
1160
        // Request forum 1, all areas; not added as covered by course 1.
1161
        \core_search\manager::request_index($forum1ctx);
1162
        $this->assertEquals(1, $DB->count_records('search_index_requests'));
1163
 
1164
        // Request forum 1, specific area; not added as covered by course 1 all areas.
1165
        \core_search\manager::request_index($forum1ctx, 'forum-post');
1166
        $this->assertEquals(1, $DB->count_records('search_index_requests'));
1167
 
1168
        // Request course 1 again, specific area; not added as covered by all areas.
1169
        \core_search\manager::request_index($course1ctx, 'forum-post');
1170
        $this->assertEquals(1, $DB->count_records('search_index_requests'));
1171
 
1172
        // Request course 1 again, all areas; not needed as covered already.
1173
        \core_search\manager::request_index($course1ctx);
1174
        $this->assertEquals(1, $DB->count_records('search_index_requests'));
1175
 
1176
        // Request course 2, specific area.
1177
        \core_search\manager::request_index($course2ctx, 'label-activity');
1178
        // Note: I'm ordering by ID for convenience - this is dangerous in real code (see MDL-43447)
1179
        // but in a unit test it shouldn't matter as nobody is using clustered databases for unit
1180
        // test.
1181
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1182
        $this->assertCount(2, $results);
1183
        $this->assertEquals($course1ctx->id, $results[0]->contextid);
1184
        $this->assertEquals($course2ctx->id, $results[1]->contextid);
1185
        $this->assertEquals('label-activity', $results[1]->searcharea);
1186
 
1187
        // Request forum 2, same specific area; not added.
1188
        \core_search\manager::request_index($forum2ctx, 'label-activity');
1189
        $this->assertEquals(2, $DB->count_records('search_index_requests'));
1190
 
1191
        // Request forum 2, different specific area; added.
1192
        \core_search\manager::request_index($forum2ctx, 'forum-post');
1193
        $this->assertEquals(3, $DB->count_records('search_index_requests'));
1194
 
1195
        // Request forum 2, all areas; also added. (Note: This could obviously remove the previous
1196
        // one, but for simplicity, I didn't make it do that; also it could perhaps cause problems
1197
        // if we had already begun processing the previous entry.)
1198
        \core_search\manager::request_index($forum2ctx);
1199
        $this->assertEquals(4, $DB->count_records('search_index_requests'));
1200
 
1201
        // Clear queue and do tests relating to priority.
1202
        $DB->delete_records('search_index_requests');
1203
 
1204
        // Request forum 1, specific area, priority 100.
1205
        \core_search\manager::request_index($forum1ctx, 'forum-post', 100);
1206
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1207
        $this->assertCount(1, $results);
1208
        $this->assertEquals(100, $results[0]->indexpriority);
1209
 
1210
        // Request forum 1, same area, lower priority; no change.
1211
        \core_search\manager::request_index($forum1ctx, 'forum-post', 99);
1212
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1213
        $this->assertCount(1, $results);
1214
        $this->assertEquals(100, $results[0]->indexpriority);
1215
 
1216
        // Request forum 1, same area, higher priority; priority stored changes.
1217
        \core_search\manager::request_index($forum1ctx, 'forum-post', 101);
1218
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1219
        $this->assertCount(1, $results);
1220
        $this->assertEquals(101, $results[0]->indexpriority);
1221
 
1222
        // Request forum 1, all areas, lower priority; adds second entry.
1223
        \core_search\manager::request_index($forum1ctx, '', 100);
1224
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1225
        $this->assertCount(2, $results);
1226
        $this->assertEquals(100, $results[1]->indexpriority);
1227
 
1228
        // Request course 1, all areas, lower priority; adds third entry.
1229
        \core_search\manager::request_index($course1ctx, '', 99);
1230
        $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1231
        $this->assertCount(3, $results);
1232
        $this->assertEquals(99, $results[2]->indexpriority);
1233
    }
1234
 
1235
    /**
1236
     * Tests the process_index_requests function.
1237
     */
11 efrain 1238
    public function test_process_index_requests(): void {
1 efrain 1239
        global $DB;
1240
 
1241
        $this->resetAfterTest();
1242
 
1243
        $search = \testable_core_search::instance();
1244
 
1245
        // When there are no index requests, nothing gets logged.
1246
        $progress = new \progress_trace_buffer(new \text_progress_trace(), false);
1247
        $search->process_index_requests(0.0, $progress);
1248
        $out = $progress->get_buffer();
1249
        $progress->reset_buffer();
1250
        $this->assertEquals('', $out);
1251
 
1252
        // Set up the course with 3 forums.
1253
        $generator = $this->getDataGenerator();
1254
        $course = $generator->create_course(['fullname' => 'TCourse']);
1255
        $forum1 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum1']);
1256
        $forum2 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum2']);
1257
        $forum3 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum3']);
1258
 
1259
        // Hack the forums so they have different creation times.
1260
        $now = time();
1261
        $DB->set_field('forum', 'timemodified', $now - 30, ['id' => $forum1->id]);
1262
        $DB->set_field('forum', 'timemodified', $now - 20, ['id' => $forum2->id]);
1263
        $DB->set_field('forum', 'timemodified', $now - 10, ['id' => $forum3->id]);
1264
        $forum2time = $now - 20;
1265
 
1266
        // Make 2 index requests.
1267
        \testable_core_search::fake_current_time($now - 3);
1268
        $search::request_index(\context_course::instance($course->id), 'mod_label-activity');
1269
        \testable_core_search::fake_current_time($now - 2);
1270
        $search::request_index(\context_module::instance($forum1->cmid));
1271
 
1272
        // Run with no time limit.
1273
        $search->process_index_requests(0.0, $progress);
1274
        $out = $progress->get_buffer();
1275
        $progress->reset_buffer();
1276
 
1277
        // Check that it's done both areas.
1278
        $this->assertStringContainsString(
1279
                'Indexing requested context: Course: TCourse (search area: mod_label-activity)',
1280
                $out);
1281
        $this->assertStringContainsString(
1282
                'Completed requested context: Course: TCourse (search area: mod_label-activity)',
1283
                $out);
1284
        $this->assertStringContainsString('Indexing requested context: Forum: TForum1', $out);
1285
        $this->assertStringContainsString('Completed requested context: Forum: TForum1', $out);
1286
 
1287
        // Check the requests database table is now empty.
1288
        $this->assertEquals(0, $DB->count_records('search_index_requests'));
1289
 
1290
        // Request indexing the course a couple of times.
1291
        \testable_core_search::fake_current_time($now - 3);
1292
        $search::request_index(\context_course::instance($course->id), 'mod_forum-activity');
1293
        \testable_core_search::fake_current_time($now - 2);
1294
        $search::request_index(\context_course::instance($course->id), 'mod_forum-post');
1295
 
1296
        // Do the processing again with a time limit and indexing delay. The time limit is too
1297
        // small; because of the way the logic works, this means it will index 2 activities.
1298
        $search->get_engine()->set_add_delay(0.2);
1299
        $search->process_index_requests(0.1, $progress);
1300
        $out = $progress->get_buffer();
1301
        $progress->reset_buffer();
1302
 
1303
        // Confirm the right wrapper information was logged.
1304
        $this->assertStringContainsString(
1305
                'Indexing requested context: Course: TCourse (search area: mod_forum-activity)',
1306
                $out);
1307
        $this->assertStringContainsString('Stopping indexing due to time limit', $out);
1308
        $this->assertStringContainsString(
1309
                'Ending requested context: Course: TCourse (search area: mod_forum-activity)',
1310
                $out);
1311
 
1312
        // Check the database table has been updated with progress.
1313
        $records = array_values($DB->get_records('search_index_requests', null, 'searcharea'));
1314
        $this->assertEquals('mod_forum-activity', $records[0]->partialarea);
1315
        $this->assertEquals($forum2time, $records[0]->partialtime);
1316
 
1317
        // Run again and confirm it now finishes.
1318
        $search->process_index_requests(2.0, $progress);
1319
        $out = $progress->get_buffer();
1320
        $progress->reset_buffer();
1321
        $this->assertStringContainsString(
1322
                'Completed requested context: Course: TCourse (search area: mod_forum-activity)',
1323
                $out);
1324
        $this->assertStringContainsString(
1325
                'Completed requested context: Course: TCourse (search area: mod_forum-post)',
1326
                $out);
1327
 
1328
        // Confirm table is now empty.
1329
        $this->assertEquals(0, $DB->count_records('search_index_requests'));
1330
 
1331
        // Make 2 requests - first one is low priority.
1332
        \testable_core_search::fake_current_time($now - 3);
1333
        $search::request_index(\context_module::instance($forum1->cmid), 'mod_forum-activity',
1334
                \core_search\manager::INDEX_PRIORITY_REINDEXING);
1335
        \testable_core_search::fake_current_time($now - 2);
1336
        $search::request_index(\context_module::instance($forum2->cmid), 'mod_forum-activity');
1337
 
1338
        // Process with short time limit and confirm it does the second one first.
1339
        $search->process_index_requests(0.1, $progress);
1340
        $out = $progress->get_buffer();
1341
        $progress->reset_buffer();
1342
        $this->assertStringContainsString(
1343
                'Completed requested context: Forum: TForum2 (search area: mod_forum-activity)',
1344
                $out);
1345
        $search->process_index_requests(0.1, $progress);
1346
        $out = $progress->get_buffer();
1347
        $progress->reset_buffer();
1348
        $this->assertStringContainsString(
1349
                'Completed requested context: Forum: TForum1 (search area: mod_forum-activity)',
1350
                $out);
1351
 
1352
        // Make a request for a course context...
1353
        $course = $generator->create_course();
1354
        $context = \context_course::instance($course->id);
1355
        $search::request_index($context);
1356
 
1357
        // ...but then delete it (note: delete_course spews output, so we throw it away).
1358
        ob_start();
1359
        delete_course($course);
1360
        ob_end_clean();
1361
 
1362
        // Process requests - it should only note the deleted context.
1363
        $search->process_index_requests(10, $progress);
1364
        $out = $progress->get_buffer();
1365
        $progress->reset_buffer();
1366
        $this->assertStringContainsString('Skipped deleted context: ' . $context->id, $out);
1367
 
1368
        // Confirm request table is now empty.
1369
        $this->assertEquals(0, $DB->count_records('search_index_requests'));
1370
    }
1371
 
1372
    /**
1373
     * Test search area categories.
1374
     */
11 efrain 1375
    public function test_get_search_area_categories(): void {
1 efrain 1376
        $categories = \core_search\manager::get_search_area_categories();
1377
 
1378
        $this->assertTrue(is_array($categories));
1379
        $this->assertTrue(count($categories) >= 4); // We always should have 4 core categories.
1380
        $this->assertArrayHasKey('core-all', $categories);
1381
        $this->assertArrayHasKey('core-course-content', $categories);
1382
        $this->assertArrayHasKey('core-courses', $categories);
1383
        $this->assertArrayHasKey('core-users', $categories);
1384
 
1385
        foreach ($categories as $category) {
1386
            $this->assertInstanceOf('\core_search\area_category', $category);
1387
        }
1388
    }
1389
 
1390
    /**
1391
     * Test that we can find out if search area categories functionality is enabled.
1392
     */
11 efrain 1393
    public function test_is_search_area_categories_enabled(): void {
1 efrain 1394
        $this->resetAfterTest();
1395
 
1396
        $this->assertFalse(\core_search\manager::is_search_area_categories_enabled());
1397
        set_config('searchenablecategories', 1);
1398
        $this->assertTrue(\core_search\manager::is_search_area_categories_enabled());
1399
        set_config('searchenablecategories', 0);
1400
        $this->assertFalse(\core_search\manager::is_search_area_categories_enabled());
1401
    }
1402
 
1403
    /**
1404
     * Test that we can find out if hiding all results category is enabled.
1405
     */
11 efrain 1406
    public function test_should_hide_all_results_category(): void {
1 efrain 1407
        $this->resetAfterTest();
1408
 
1409
        $this->assertEquals(0, \core_search\manager::should_hide_all_results_category());
1410
        set_config('searchhideallcategory', 1);
1411
        $this->assertEquals(1, \core_search\manager::should_hide_all_results_category());
1412
        set_config('searchhideallcategory', 0);
1413
        $this->assertEquals(0, \core_search\manager::should_hide_all_results_category());
1414
    }
1415
 
1416
    /**
1417
     * Test that we can get default search category name.
1418
     */
11 efrain 1419
    public function test_get_default_area_category_name(): void {
1 efrain 1420
        $this->resetAfterTest();
1421
 
1422
        $expected = 'core-all';
1423
        $this->assertEquals($expected, \core_search\manager::get_default_area_category_name());
1424
 
1425
        set_config('searchhideallcategory', 1);
1426
        $expected = 'core-course-content';
1427
        $this->assertEquals($expected, \core_search\manager::get_default_area_category_name());
1428
 
1429
        set_config('searchhideallcategory', 0);
1430
        $expected = 'core-all';
1431
        $this->assertEquals($expected, \core_search\manager::get_default_area_category_name());
1432
    }
1433
 
1434
    /**
1435
     * Test that we can get correct search area category by its name.
1436
     */
11 efrain 1437
    public function test_get_search_area_category_by_name(): void {
1 efrain 1438
        $this->resetAfterTest();
1439
 
1440
        $testcategory = \core_search\manager::get_search_area_category_by_name('test_random_name');
1441
        $this->assertEquals('core-all', $testcategory->get_name());
1442
 
1443
        $testcategory = \core_search\manager::get_search_area_category_by_name('core-courses');
1444
        $this->assertEquals('core-courses', $testcategory->get_name());
1445
 
1446
        set_config('searchhideallcategory', 1);
1447
        $testcategory = \core_search\manager::get_search_area_category_by_name('test_random_name');
1448
        $this->assertEquals('core-course-content', $testcategory->get_name());
1449
    }
1450
 
1451
    /**
1452
     * Test that we can check that "Include all visible courses" feature is enabled.
1453
     */
11 efrain 1454
    public function test_include_all_courses_enabled(): void {
1 efrain 1455
        $this->resetAfterTest();
1456
        $this->assertFalse(\core_search\manager::include_all_courses());
1457
        set_config('searchincludeallcourses', 1);
1458
        $this->assertTrue(\core_search\manager::include_all_courses());
1459
    }
1460
 
1461
    /**
1462
     * Test that we can correctly build a list of courses for a course filter for the search results.
1463
     */
11 efrain 1464
    public function test_build_limitcourseids(): void {
1 efrain 1465
        global $USER;
1466
 
1467
        $this->resetAfterTest();
1468
        $this->setAdminUser();
1469
 
1470
        $course1 = $this->getDataGenerator()->create_course();
1471
        $course2 = $this->getDataGenerator()->create_course();
1472
        $course3 = $this->getDataGenerator()->create_course();
1473
        $course4 = $this->getDataGenerator()->create_course();
1474
 
1475
        $this->getDataGenerator()->enrol_user($USER->id, $course1->id);
1476
        $this->getDataGenerator()->enrol_user($USER->id, $course3->id);
1477
 
1478
        $search = \testable_core_search::instance();
1479
 
1480
        $formdata = new \stdClass();
1481
        $formdata->courseids = [];
1482
        $formdata->mycoursesonly = false;
1483
        $limitcourseids = $search->build_limitcourseids($formdata);
1484
        $this->assertEquals(false, $limitcourseids);
1485
 
1486
        $formdata->courseids = [];
1487
        $formdata->mycoursesonly = true;
1488
        $limitcourseids = $search->build_limitcourseids($formdata);
1489
        $this->assertEquals([$course1->id, $course3->id], $limitcourseids);
1490
 
1491
        $formdata->courseids = [$course1->id, $course2->id, $course4->id];
1492
        $formdata->mycoursesonly = false;
1493
        $limitcourseids = $search->build_limitcourseids($formdata);
1494
        $this->assertEquals([$course1->id, $course2->id, $course4->id], $limitcourseids);
1495
 
1496
        $formdata->courseids = [$course1->id, $course2->id, $course4->id];
1497
        $formdata->mycoursesonly = true;
1498
        $limitcourseids = $search->build_limitcourseids($formdata);
1499
        $this->assertEquals([$course1->id], $limitcourseids);
1500
    }
1501
 
1502
    /**
1503
     * Test data for test_parse_areaid test fucntion.
1504
     *
1505
     * @return array
1506
     */
1441 ariadna 1507
    public static function parse_search_area_id_data_provider(): array {
1 efrain 1508
        return [
1509
            ['mod_book-chapter', ['mod_book', 'search_chapter']],
1510
            ['mod_customcert-activity', ['mod_customcert', 'search_activity']],
1511
            ['core_course-mycourse', ['core_search', 'core_course_mycourse']],
1512
        ];
1513
    }
1514
 
1515
    /**
1516
     * Test that manager class can parse area id correctly.
1517
     * @dataProvider parse_search_area_id_data_provider
1518
     *
1519
     * @param string $areaid Area id to parse.
1520
     * @param array $expected Expected result of parsing.
1521
     */
11 efrain 1522
    public function test_parse_search_area_id($areaid, $expected): void {
1 efrain 1523
        $this->assertEquals($expected, \core_search\manager::parse_areaid($areaid));
1524
    }
1525
 
1526
    /**
1527
     * Test that manager class will throw an exception when parsing an invalid area id.
1528
     */
11 efrain 1529
    public function test_parse_invalid_search_area_id(): void {
1 efrain 1530
        $this->expectException('coding_exception');
1531
        $this->expectExceptionMessage('Trying to parse invalid search area id invalid_area');
1532
        \core_search\manager::parse_areaid('invalid_area');
1533
    }
1534
 
1535
    /**
1536
     * Test getting a coding exception when trying to lean up existing search area.
1537
     */
11 efrain 1538
    public function test_cleaning_up_existing_search_area(): void {
1 efrain 1539
        $expectedmessage = "Area mod_assign-activity exists. Please use appropriate search area class to manipulate the data.";
1540
 
1541
        $this->expectException('coding_exception');
1542
        $this->expectExceptionMessage($expectedmessage);
1543
 
1544
        \core_search\manager::clean_up_non_existing_area('mod_assign-activity');
1545
    }
1546
 
1547
    /**
1548
     * Test clean up of non existing search area.
1549
     */
11 efrain 1550
    public function test_clean_up_non_existing_search_area(): void {
1 efrain 1551
        global $DB;
1552
 
1553
        $this->resetAfterTest();
1554
 
1555
        $areaid = 'core_course-mycourse';
1556
        $plugin = 'core_search';
1557
 
1558
        // Get all settings to DB and make sure they are there.
1559
        foreach (\core_search\base::get_settingnames() as $settingname) {
1560
            $record = new \stdClass();
1561
            $record->plugin = $plugin;
1562
            $record->name = 'core_course_mycourse'. $settingname;
1563
            $record->value = 'test';
1564
 
1565
            $DB->insert_record('config_plugins', $record);
1566
            $this->assertTrue($DB->record_exists('config_plugins', ['plugin' => $plugin, 'name' => $record->name]));
1567
        }
1568
 
1569
        // Clean up the search area.
1570
        \core_search\manager::clean_up_non_existing_area($areaid);
1571
 
1572
        // Check that records are not in DB after we ran clean up.
1573
        foreach (\core_search\base::get_settingnames() as $settingname) {
1574
            $plugin = 'core_search';
1575
            $name = 'core_course_mycourse'. $settingname;
1576
            $this->assertFalse($DB->record_exists('config_plugins', ['plugin' => $plugin, 'name' => $name]));
1577
        }
1578
    }
1579
 
1580
    /**
1581
     * Tests the context_deleted, course_deleting_start, and course_deleting_finish methods.
1582
     */
11 efrain 1583
    public function test_context_deletion(): void {
1 efrain 1584
        $this->resetAfterTest();
1585
 
1586
        // Create one course with 4 activities, and another with one.
1587
        $generator = $this->getDataGenerator();
1588
        $course1 = $generator->create_course();
1589
        $page1 = $generator->create_module('page', ['course' => $course1]);
1590
        $context1 = \context_module::instance($page1->cmid);
1591
        $page2 = $generator->create_module('page', ['course' => $course1]);
1592
        $page3 = $generator->create_module('page', ['course' => $course1]);
1593
        $context3 = \context_module::instance($page3->cmid);
1594
        $page4 = $generator->create_module('page', ['course' => $course1]);
1595
        $course2 = $generator->create_course();
1596
        $page5 = $generator->create_module('page', ['course' => $course2]);
1597
        $context5 = \context_module::instance($page5->cmid);
1598
 
1599
        // Also create a user.
1600
        $user = $generator->create_user();
1601
        $usercontext = \context_user::instance($user->id);
1602
 
1603
        $search = \testable_core_search::instance();
1604
 
1605
        // Delete two of the pages individually.
1606
        course_delete_module($page1->cmid);
1607
        course_delete_module($page3->cmid);
1608
 
1609
        // Delete the course with another two.
1610
        delete_course($course1->id, false);
1611
 
1612
        // Delete the user.
1613
        delete_user($user);
1614
 
1615
        // Delete the page from the other course.
1616
        course_delete_module($page5->cmid);
1617
 
1618
        // It should have deleted the contexts and the course, but not the contexts in the course.
1619
        $expected = [
1620
            ['context', $context1->id],
1621
            ['context', $context3->id],
1622
            ['course', $course1->id],
1623
            ['context', $usercontext->id],
1624
            ['context', $context5->id]
1625
        ];
1626
        $this->assertEquals($expected, $search->get_engine()->get_and_clear_deletes());
1627
    }
1441 ariadna 1628
 
1629
    /**
1630
     * Tests the indexing delay (used to avoid race conditions) in {@see manager::index()}.
1631
     *
1632
     * @covers \core_search\manager::index
1633
     */
1634
    public function test_indexing_delay(): void {
1635
        global $USER, $CFG;
1636
 
1637
        $this->resetAfterTest();
1638
 
1639
        // Normally the indexing delay is turned off for test scripts because we don't want to have
1640
        // to wait 5 seconds after creating anything to index it and it's not like there will be a
1641
        // race condition (indexing doesn't run at same time as adding). This turns it on.
1642
        $CFG->searchindexingdelayfortestscript = true;
1643
 
1644
        $this->setAdminUser();
1645
 
1646
        // Create a course and a forum.
1647
        $generator = $this->getDataGenerator();
1648
        $course = $generator->create_course();
1649
        $forum = $generator->create_module('forum', ['course' => $course->id]);
1650
 
1651
        // Skip ahead 5 seconds so everything gets indexed.
1652
        $now = time();
1653
        $now += manager::INDEXING_DELAY;
1654
        $search = \testable_core_search::instance();
1655
        $search->fake_current_time($now);
1656
        $search->index();
1657
        $search->get_engine()->get_and_clear_added_documents();
1658
 
1659
        // Basic discussion data.
1660
        $basicdata = [
1661
            'course' => $course->id,
1662
            'forum' => $forum->id,
1663
            'userid' => $USER->id,
1664
        ];
1665
        // Discussion so old it's prior to indexing delay (not realistic).
1666
        $generator->get_plugin_generator('mod_forum')->create_discussion(array_merge($basicdata,
1667
            ['timemodified' => $now - manager::INDEXING_DELAY, 'name' => 'Frog']));
1668
        // Discussion just within indexing delay (simulates if it took a while to add to database).
1669
        $generator->get_plugin_generator('mod_forum')->create_discussion(array_merge($basicdata,
1670
            ['timemodified' => $now - (manager::INDEXING_DELAY - 1), 'name' => 'Toad']));
1671
        // Move time along a bit.
1672
        $now += 100;
1673
        $search->fake_current_time($now);
1674
        // Discussion that happened 5 seconds before the new now.
1675
        $generator->get_plugin_generator('mod_forum')->create_discussion(array_merge($basicdata,
1676
            ['timemodified' => $now - (manager::INDEXING_DELAY), 'name' => 'Zombie']));
1677
        // This one only happened 4 seconds before so it shouldn't be indexed yet.
1678
        $generator->get_plugin_generator('mod_forum')->create_discussion(array_merge($basicdata,
1679
            ['timemodified' => $now - (manager::INDEXING_DELAY - 1), 'name' => 'Werewolf']));
1680
 
1681
        // Reindex and check that it added the middle two discussions.
1682
        $search->index();
1683
        $added = $search->get_engine()->get_and_clear_added_documents();
1684
        $this->assertCount(2, $added);
1685
        $this->assertEquals('Toad', $added[0]->get('title'));
1686
        $this->assertEquals('Zombie', $added[1]->get('title'));
1687
 
1688
        // Move time forwards a couple of seconds and now the last one will get indexed.
1689
        $now += 2;
1690
        $search->fake_current_time($now);
1691
        $search->index();
1692
        $added = $search->get_engine()->get_and_clear_added_documents();
1693
        $this->assertCount(1, $added);
1694
        $this->assertEquals('Werewolf', $added[0]->get('title'));
1695
    }
1 efrain 1696
}