MDL-56498 message: Ensure clicked notifications are marked read
[moodle.git] / search / tests / manager_test.php
blob124e04ab984123432c3548733a1f1146cc0caa96
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.
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/>.
17 /**
18 * Search manager unit tests.
20 * @package core_search
21 * @category phpunit
22 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
28 require_once(__DIR__ . '/fixtures/testable_core_search.php');
29 require_once(__DIR__ . '/fixtures/mock_search_area.php');
31 /**
32 * Unit tests for search manager.
34 * @package core_search
35 * @category phpunit
36 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 class search_manager_testcase extends advanced_testcase {
41 protected $forumpostareaid = null;
42 protected $mycoursesareaid = null;
44 public function setUp() {
45 $this->forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
46 $this->mycoursesareaid = \core_search\manager::generate_areaid('core_course', 'mycourse');
49 protected function tearDown() {
50 // Stop it from faking time in the search manager (if set by test).
51 testable_core_search::fake_current_time();
52 parent::tearDown();
55 public function test_search_enabled() {
57 $this->resetAfterTest();
59 // Disabled by default.
60 $this->assertFalse(\core_search\manager::is_global_search_enabled());
62 set_config('enableglobalsearch', true);
63 $this->assertTrue(\core_search\manager::is_global_search_enabled());
65 set_config('enableglobalsearch', false);
66 $this->assertFalse(\core_search\manager::is_global_search_enabled());
69 public function test_search_areas() {
70 global $CFG;
72 $this->resetAfterTest();
74 set_config('enableglobalsearch', true);
76 $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita');
78 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid);
79 $this->assertInstanceOf('\core_search\base', $searcharea);
81 $this->assertFalse(\core_search\manager::get_search_area($fakeareaid));
83 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list());
84 $this->assertArrayNotHasKey($fakeareaid, \core_search\manager::get_search_areas_list());
86 // Enabled by default once global search is enabled.
87 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
89 list($componentname, $varname) = $searcharea->get_config_var_name();
90 set_config($varname . '_enabled', 0, $componentname);
91 \core_search\manager::clear_static();
93 $this->assertArrayNotHasKey('mod_forum', \core_search\manager::get_search_areas_list(true));
95 set_config($varname . '_enabled', 1, $componentname);
97 // Although the result is wrong, we want to check that \core_search\manager::get_search_areas_list returns cached results.
98 $this->assertArrayNotHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
100 // Now we check the real result.
101 \core_search\manager::clear_static();
102 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true));
105 public function test_search_config() {
107 $this->resetAfterTest();
109 $search = testable_core_search::instance();
111 // We should test both plugin types and core subsystems. No core subsystems available yet.
112 $searcharea = $search->get_search_area($this->forumpostareaid);
114 list($componentname, $varname) = $searcharea->get_config_var_name();
116 // Just with a couple of vars should be enough.
117 $start = time() - 100;
118 $end = time();
119 set_config($varname . '_indexingstart', $start, $componentname);
120 set_config($varname . '_indexingend', $end, $componentname);
122 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
123 $this->assertEquals($start, $configs[$this->forumpostareaid]->indexingstart);
124 $this->assertEquals($end, $configs[$this->forumpostareaid]->indexingend);
125 $this->assertEquals(false, $configs[$this->forumpostareaid]->partial);
127 try {
128 $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita');
129 $search->reset_config($fakeareaid);
130 $this->fail('An exception should be triggered if the provided search area does not exist.');
131 } catch (moodle_exception $ex) {
132 $this->assertContains($fakeareaid . ' search area is not available.', $ex->getMessage());
135 // We clean it all but enabled components.
136 $search->reset_config($this->forumpostareaid);
137 $config = $searcharea->get_config();
138 $this->assertEquals(1, $config[$varname . '_enabled']);
139 $this->assertEquals(0, $config[$varname . '_indexingstart']);
140 $this->assertEquals(0, $config[$varname . '_indexingend']);
141 $this->assertEquals(0, $config[$varname . '_lastindexrun']);
142 $this->assertEquals(0, $config[$varname . '_partial']);
143 // No caching.
144 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
145 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart);
146 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend);
148 set_config($varname . '_indexingstart', $start, $componentname);
149 set_config($varname . '_indexingend', $end, $componentname);
151 // All components config should be reset.
152 $search->reset_config();
153 $this->assertEquals(0, get_config($componentname, $varname . '_indexingstart'));
154 $this->assertEquals(0, get_config($componentname, $varname . '_indexingend'));
155 $this->assertEquals(0, get_config($componentname, $varname . '_lastindexrun'));
156 // No caching.
157 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea));
158 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart);
159 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend);
163 * Tests the get_last_indexing_duration method in the base area class.
165 public function test_get_last_indexing_duration() {
166 $this->resetAfterTest();
168 $search = testable_core_search::instance();
170 $searcharea = $search->get_search_area($this->forumpostareaid);
172 // When never indexed, the duration is false.
173 $this->assertSame(false, $searcharea->get_last_indexing_duration());
175 // Set the start/end times.
176 list($componentname, $varname) = $searcharea->get_config_var_name();
177 $start = time() - 100;
178 $end = time();
179 set_config($varname . '_indexingstart', $start, $componentname);
180 set_config($varname . '_indexingend', $end, $componentname);
182 // The duration should now be 100.
183 $this->assertSame(100, $searcharea->get_last_indexing_duration());
187 * Tests that partial indexing works correctly.
189 public function test_partial_indexing() {
190 global $USER;
192 $this->resetAfterTest();
193 $this->setAdminUser();
195 // Create a course and a forum.
196 $generator = $this->getDataGenerator();
197 $course = $generator->create_course();
198 $forum = $generator->create_module('forum', ['course' => $course->id]);
200 // Index everything up to current. Ensure the course is older than current second so it
201 // definitely doesn't get indexed again next time.
202 $this->waitForSecond();
203 $search = testable_core_search::instance();
204 $search->index(false, 0);
206 $searcharea = $search->get_search_area($this->forumpostareaid);
207 list($componentname, $varname) = $searcharea->get_config_var_name();
208 $this->assertFalse(get_config($componentname, $varname . '_partial'));
210 // Add 3 discussions to the forum.
211 $now = time();
212 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
213 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now,
214 'name' => 'Frog']);
215 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
216 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 1,
217 'name' => 'Toad']);
218 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
219 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
220 'name' => 'Zombie']);
221 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
222 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
223 'name' => 'Werewolf']);
224 time_sleep_until($now + 3);
226 // Clear the count of added documents.
227 $search->get_engine()->get_and_clear_added_documents();
229 // Make the search engine delay while indexing each document.
230 $search->get_engine()->set_add_delay(1.2);
232 // Use fake time, starting from now.
233 testable_core_search::fake_current_time(time());
235 // Index with a limit of 2 seconds - it should index 2 of the documents (after the second
236 // one, it will have taken 2.4 seconds so it will stop).
237 $search->index(false, 2);
238 $added = $search->get_engine()->get_and_clear_added_documents();
239 $this->assertCount(2, $added);
240 $this->assertEquals('Frog', $added[0]->get('title'));
241 $this->assertEquals('Toad', $added[1]->get('title'));
242 $this->assertEquals(1, get_config($componentname, $varname . '_partial'));
243 // Whilst 2.4 seconds of "time" have elapsed, the indexing duration is
244 // measured in seconds, so should be 2.
245 $this->assertEquals(2, $searcharea->get_last_indexing_duration());
247 // Add a label.
248 $generator->create_module('label', ['course' => $course->id, 'intro' => 'Vampire']);
250 // Wait to next second (so as to not reindex the label more than once, as it will now
251 // be timed before the indexing run).
252 $this->waitForSecond();
253 testable_core_search::fake_current_time(time());
255 // Next index with 1 second limit should do the label and not the forum - the logic is,
256 // if it spent ages indexing an area last time, do that one last on next run.
257 $search->index(false, 1);
258 $added = $search->get_engine()->get_and_clear_added_documents();
259 $this->assertCount(1, $added);
260 $this->assertEquals('Vampire', $added[0]->get('title'));
262 // Index again with a 3 second limit - it will redo last post for safety (because of other
263 // things possibly having the same time second), and then do the remaining one. (Note:
264 // because it always does more than one second worth of items, it would actually index 2
265 // posts even if the limit were less than 2, we are testing it does 3 posts to make sure
266 // the time limiting is actually working with the specified time.)
267 $search->index(false, 3);
268 $added = $search->get_engine()->get_and_clear_added_documents();
269 $this->assertCount(3, $added);
270 $this->assertEquals('Toad', $added[0]->get('title'));
271 $remainingtitles = [$added[1]->get('title'), $added[2]->get('title')];
272 sort($remainingtitles);
273 $this->assertEquals(['Werewolf', 'Zombie'], $remainingtitles);
274 $this->assertFalse(get_config($componentname, $varname . '_partial'));
276 // Index again - there should be nothing to index this time.
277 $search->index(false, 2);
278 $added = $search->get_engine()->get_and_clear_added_documents();
279 $this->assertCount(0, $added);
280 $this->assertFalse(get_config($componentname, $varname . '_partial'));
284 * Tests the progress display while indexing.
286 * This tests the different logic about displaying progress for slow/fast and
287 * complete/incomplete processing.
289 public function test_index_progress() {
290 $this->resetAfterTest();
291 $generator = $this->getDataGenerator();
293 // Set up the fake search area.
294 $search = testable_core_search::instance();
295 $area = new \core_mocksearch\search\mock_search_area();
296 $search->add_search_area('whatever', $area);
297 $searchgenerator = $generator->get_plugin_generator('core_search');
298 $searchgenerator->setUp();
300 // Add records with specific time modified values.
301 $time = strtotime('2017-11-01 01:00');
302 for ($i = 0; $i < 8; $i ++) {
303 $searchgenerator->create_record((object)['timemodified' => $time]);
304 $time += 60;
307 // Simulate slow progress on indexing and initial query.
308 $now = strtotime('2017-11-11 01:00');
309 \testable_core_search::fake_current_time($now);
310 $area->set_indexing_delay(10.123);
311 $search->get_engine()->set_add_delay(15.789);
313 // Run search indexing and check output.
314 $progress = new progress_trace_buffer(new text_progress_trace(), false);
315 $search->index(false, 75, $progress);
316 $out = $progress->get_buffer();
317 $progress->reset_buffer();
319 // Check for the standard text.
320 $this->assertContains('Processing area: Mock search area', $out);
321 $this->assertContains('Stopping indexing due to time limit', $out);
323 // Check for initial query performance indication.
324 $this->assertContains('Initial query took 10.1 seconds.', $out);
326 // Check for the two (approximately) every-30-seconds messages.
327 $this->assertContains('01:00:41: Done to 1/11/17, 01:01', $out);
328 $this->assertContains('01:01:13: Done to 1/11/17, 01:03', $out);
330 // Check for the 'not complete' indicator showing when it was done until.
331 $this->assertContains('Processed 5 records containing 5 documents, in 89.1 seconds ' .
332 '(not complete; done to 1/11/17, 01:04)', $out);
334 // Make the initial query delay less than 5 seconds, so it won't appear. Make the documents
335 // quicker, so that the 30-second delay won't be needed.
336 $area->set_indexing_delay(4.9);
337 $search->get_engine()->set_add_delay(1);
339 // Run search indexing (still partial) and check output.
340 $progress = new progress_trace_buffer(new text_progress_trace(), false);
341 $search->index(false, 5, $progress);
342 $out = $progress->get_buffer();
343 $progress->reset_buffer();
345 $this->assertContains('Processing area: Mock search area', $out);
346 $this->assertContains('Stopping indexing due to time limit', $out);
347 $this->assertNotContains('Initial query took', $out);
348 $this->assertNotContains(': Done to', $out);
349 $this->assertContains('Processed 2 records containing 2 documents, in 6.9 seconds ' .
350 '(not complete; done to 1/11/17, 01:05).', $out);
352 // Run the remaining items to complete it.
353 $progress = new progress_trace_buffer(new text_progress_trace(), false);
354 $search->index(false, 100, $progress);
355 $out = $progress->get_buffer();
356 $progress->reset_buffer();
358 $this->assertContains('Processing area: Mock search area', $out);
359 $this->assertNotContains('Stopping indexing due to time limit', $out);
360 $this->assertNotContains('Initial query took', $out);
361 $this->assertNotContains(': Done to', $out);
362 $this->assertContains('Processed 3 records containing 3 documents, in 7.9 seconds.', $out);
364 $searchgenerator->tearDown();
368 * Tests that documents with modified time in the future are NOT indexed (as this would cause
369 * a problem by preventing it from indexing other documents modified between now and the future
370 * date).
372 public function test_future_documents() {
373 $this->resetAfterTest();
375 // Create a course and a forum.
376 $generator = $this->getDataGenerator();
377 $course = $generator->create_course();
378 $forum = $generator->create_module('forum', ['course' => $course->id]);
380 // Index everything up to current. Ensure the course is older than current second so it
381 // definitely doesn't get indexed again next time.
382 $this->waitForSecond();
383 $search = testable_core_search::instance();
384 $search->index(false, 0);
385 $search->get_engine()->get_and_clear_added_documents();
387 // Add 2 discussions to the forum, one of which happend just now, but the other is
388 // incorrectly set to the future.
389 $now = time();
390 $userid = get_admin()->id;
391 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
392 'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now,
393 'name' => 'Frog']);
394 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
395 'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now + 100,
396 'name' => 'Toad']);
398 // Wait for a second so we're not actually on the same second as the forum post (there's a
399 // 1 second overlap between indexing; it would get indexed in both checks below otherwise).
400 $this->waitForSecond();
402 // Index.
403 $search->index(false);
404 $added = $search->get_engine()->get_and_clear_added_documents();
405 $this->assertCount(1, $added);
406 $this->assertEquals('Frog', $added[0]->get('title'));
408 // Check latest time - it should be the same as $now, not the + 100.
409 $searcharea = $search->get_search_area($this->forumpostareaid);
410 list($componentname, $varname) = $searcharea->get_config_var_name();
411 $this->assertEquals($now, get_config($componentname, $varname . '_lastindexrun'));
413 // Index again - there should be nothing to index this time.
414 $search->index(false);
415 $added = $search->get_engine()->get_and_clear_added_documents();
416 $this->assertCount(0, $added);
420 * Tests that indexing a specified context works correctly.
422 public function test_context_indexing() {
423 global $USER;
425 $this->resetAfterTest();
426 $this->setAdminUser();
428 // Create a course and two forums and a page.
429 $generator = $this->getDataGenerator();
430 $course = $generator->create_course();
431 $now = time();
432 $forum1 = $generator->create_module('forum', ['course' => $course->id]);
433 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
434 'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now,
435 'name' => 'Frog']);
436 $this->waitForSecond();
437 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
438 'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now + 2,
439 'name' => 'Zombie']);
440 $forum2 = $generator->create_module('forum', ['course' => $course->id]);
441 $this->waitForSecond();
442 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id,
443 'forum' => $forum2->id, 'userid' => $USER->id, 'timemodified' => $now + 1,
444 'name' => 'Toad']);
445 $generator->create_module('page', ['course' => $course->id]);
446 $generator->create_module('forum', ['course' => $course->id]);
448 // Index forum 1 only.
449 $search = testable_core_search::instance();
450 $buffer = new progress_trace_buffer(new text_progress_trace(), false);
451 $result = $search->index_context(\context_module::instance($forum1->cmid), '', 0, $buffer);
452 $this->assertTrue($result->complete);
453 $log = $buffer->get_buffer();
454 $buffer->reset_buffer();
456 // Confirm that output only processed 1 forum activity and 2 posts.
457 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 1 "));
458 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 "));
460 // Confirm that some areas for different types of context were skipped.
461 $this->assertNotFalse(strpos($log, "area: Users\n Skipping"));
462 $this->assertNotFalse(strpos($log, "area: My courses\n Skipping"));
464 // Confirm that another module area had no results.
465 $this->assertNotFalse(strpos($log, "area: Page\n No documents"));
467 // Index whole course.
468 $result = $search->index_context(\context_course::instance($course->id), '', 0, $buffer);
469 $this->assertTrue($result->complete);
470 $log = $buffer->get_buffer();
471 $buffer->reset_buffer();
473 // Confirm that output processed 3 forum activities and 3 posts.
474 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 3 "));
475 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 3 "));
477 // The course area was also included this time.
478 $this->assertNotFalse(strpos($log, "area: My courses\n Processed 1 "));
480 // Confirm that another module area had results too.
481 $this->assertNotFalse(strpos($log, "area: Page\n Processed 1 "));
483 // Index whole course, but only forum posts.
484 $result = $search->index_context(\context_course::instance($course->id), 'mod_forum-post',
485 0, $buffer);
486 $this->assertTrue($result->complete);
487 $log = $buffer->get_buffer();
488 $buffer->reset_buffer();
490 // Confirm that output processed 3 posts but not forum activities.
491 $this->assertFalse(strpos($log, "area: Forum - activity information"));
492 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 3 "));
494 // Set time limit and retry index of whole course, taking 3 tries to complete it.
495 $search->get_engine()->set_add_delay(0.4);
496 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer);
497 $log = $buffer->get_buffer();
498 $buffer->reset_buffer();
499 $this->assertFalse($result->complete);
500 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 2 "));
502 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer,
503 $result->startfromarea, $result->startfromtime);
504 $log = $buffer->get_buffer();
505 $buffer->reset_buffer();
506 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 2 "));
507 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 "));
508 $this->assertFalse($result->complete);
510 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer,
511 $result->startfromarea, $result->startfromtime);
512 $log = $buffer->get_buffer();
513 $buffer->reset_buffer();
514 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 "));
515 $this->assertTrue($result->complete);
519 * Adding this test here as get_areas_user_accesses process is the same, results just depend on the context level.
521 * @return void
523 public function test_search_user_accesses() {
524 global $DB;
526 $this->resetAfterTest();
528 $frontpage = $DB->get_record('course', array('id' => SITEID));
529 $frontpagectx = context_course::instance($frontpage->id);
530 $course1 = $this->getDataGenerator()->create_course();
531 $course1ctx = context_course::instance($course1->id);
532 $course2 = $this->getDataGenerator()->create_course();
533 $course2ctx = context_course::instance($course2->id);
534 $teacher = $this->getDataGenerator()->create_user();
535 $teacherctx = context_user::instance($teacher->id);
536 $student = $this->getDataGenerator()->create_user();
537 $studentctx = context_user::instance($student->id);
538 $noaccess = $this->getDataGenerator()->create_user();
539 $noaccessctx = context_user::instance($noaccess->id);
540 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, 'teacher');
541 $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student');
543 $frontpageforum = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id));
544 $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
545 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
546 $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
547 $frontpageforumcontext = context_module::instance($frontpageforum->cmid);
548 $context1 = context_module::instance($forum1->cmid);
549 $context2 = context_module::instance($forum2->cmid);
550 $context3 = context_module::instance($forum3->cmid);
552 $search = testable_core_search::instance();
553 $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
554 $search->add_core_search_areas();
555 $search->add_search_area($mockareaid, new core_mocksearch\search\mock_search_area());
557 $this->setAdminUser();
558 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
560 $sitectx = \context_course::instance(SITEID);
562 // Can access the frontpage ones.
563 $this->setUser($noaccess);
564 $contexts = $search->get_areas_user_accesses()->usercontexts;
565 $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id), $contexts[$this->forumpostareaid]);
566 $this->assertEquals(array($sitectx->id => $sitectx->id), $contexts[$this->mycoursesareaid]);
567 $mockctxs = array($noaccessctx->id => $noaccessctx->id, $frontpagectx->id => $frontpagectx->id);
568 $this->assertEquals($mockctxs, $contexts[$mockareaid]);
570 $this->setUser($teacher);
571 $contexts = $search->get_areas_user_accesses()->usercontexts;
572 $frontpageandcourse1 = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id,
573 $context2->id => $context2->id);
574 $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]);
575 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id),
576 $contexts[$this->mycoursesareaid]);
577 $mockctxs = array($teacherctx->id => $teacherctx->id,
578 $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id);
579 $this->assertEquals($mockctxs, $contexts[$mockareaid]);
581 $this->setUser($student);
582 $contexts = $search->get_areas_user_accesses()->usercontexts;
583 $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]);
584 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id),
585 $contexts[$this->mycoursesareaid]);
586 $mockctxs = array($studentctx->id => $studentctx->id,
587 $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id);
588 $this->assertEquals($mockctxs, $contexts[$mockareaid]);
590 // Hide the activity.
591 set_coursemodule_visible($forum2->cmid, 0);
592 $contexts = $search->get_areas_user_accesses()->usercontexts;
593 $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id),
594 $contexts[$this->forumpostareaid]);
596 // Now test course limited searches.
597 set_coursemodule_visible($forum2->cmid, 1);
598 $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student');
599 $contexts = $search->get_areas_user_accesses()->usercontexts;
600 $allcontexts = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id,
601 $context2->id => $context2->id, $context3->id => $context3->id);
602 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
603 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id,
604 $course2ctx->id => $course2ctx->id), $contexts[$this->mycoursesareaid]);
606 $contexts = $search->get_areas_user_accesses(array($course1->id, $course2->id))->usercontexts;
607 $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id, $context3->id => $context3->id);
608 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
609 $this->assertEquals(array($course1ctx->id => $course1ctx->id,
610 $course2ctx->id => $course2ctx->id), $contexts[$this->mycoursesareaid]);
612 $contexts = $search->get_areas_user_accesses(array($course2->id))->usercontexts;
613 $allcontexts = array($context3->id => $context3->id);
614 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
615 $this->assertEquals(array($course2ctx->id => $course2ctx->id), $contexts[$this->mycoursesareaid]);
617 $contexts = $search->get_areas_user_accesses(array($course1->id))->usercontexts;
618 $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id);
619 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
620 $this->assertEquals(array($course1ctx->id => $course1ctx->id), $contexts[$this->mycoursesareaid]);
622 // Test context limited search with no course limit.
623 $contexts = $search->get_areas_user_accesses(false,
624 [$frontpageforumcontext->id, $course2ctx->id])->usercontexts;
625 $this->assertEquals([$frontpageforumcontext->id => $frontpageforumcontext->id],
626 $contexts[$this->forumpostareaid]);
627 $this->assertEquals([$course2ctx->id => $course2ctx->id],
628 $contexts[$this->mycoursesareaid]);
630 // Test context limited search with course limit.
631 $contexts = $search->get_areas_user_accesses([$course1->id, $course2->id],
632 [$frontpageforumcontext->id, $course2ctx->id])->usercontexts;
633 $this->assertArrayNotHasKey($this->forumpostareaid, $contexts);
634 $this->assertEquals([$course2ctx->id => $course2ctx->id],
635 $contexts[$this->mycoursesareaid]);
637 // Single context and course.
638 $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts;
639 $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]);
640 $this->assertArrayNotHasKey($this->mycoursesareaid, $contexts);
642 // For admins, this is still limited only if we specify the things, so it should be same.
643 $this->setAdminUser();
644 $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts;
645 $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]);
646 $this->assertArrayNotHasKey($this->mycoursesareaid, $contexts);
650 * Tests the block support in get_search_user_accesses.
652 * @return void
654 public function test_search_user_accesses_blocks() {
655 global $DB;
657 $this->resetAfterTest();
658 $this->setAdminUser();
660 // Create course and add HTML block.
661 $generator = $this->getDataGenerator();
662 $course1 = $generator->create_course();
663 $context1 = \context_course::instance($course1->id);
664 $page = new \moodle_page();
665 $page->set_context($context1);
666 $page->set_course($course1);
667 $page->set_pagelayout('standard');
668 $page->set_pagetype('course-view');
669 $page->blocks->load_blocks();
670 $page->blocks->add_block_at_end_of_default_region('html');
672 // Create another course with HTML blocks only in some weird page or a module page (not
673 // yet supported, so both these blocks will be ignored).
674 $course2 = $generator->create_course();
675 $context2 = \context_course::instance($course2->id);
676 $page = new \moodle_page();
677 $page->set_context($context2);
678 $page->set_course($course2);
679 $page->set_pagelayout('standard');
680 $page->set_pagetype('bogus-page');
681 $page->blocks->load_blocks();
682 $page->blocks->add_block_at_end_of_default_region('html');
684 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
685 $forumcontext = context_module::instance($forum->cmid);
686 $page = new \moodle_page();
687 $page->set_context($forumcontext);
688 $page->set_course($course2);
689 $page->set_pagelayout('standard');
690 $page->set_pagetype('mod-forum-view');
691 $page->blocks->load_blocks();
692 $page->blocks->add_block_at_end_of_default_region('html');
694 // The third course has 2 HTML blocks.
695 $course3 = $generator->create_course();
696 $context3 = \context_course::instance($course3->id);
697 $page = new \moodle_page();
698 $page->set_context($context3);
699 $page->set_course($course3);
700 $page->set_pagelayout('standard');
701 $page->set_pagetype('course-view');
702 $page->blocks->load_blocks();
703 $page->blocks->add_block_at_end_of_default_region('html');
704 $page->blocks->add_block_at_end_of_default_region('html');
706 // Student 1 belongs to all 3 courses.
707 $student1 = $generator->create_user();
708 $generator->enrol_user($student1->id, $course1->id, 'student');
709 $generator->enrol_user($student1->id, $course2->id, 'student');
710 $generator->enrol_user($student1->id, $course3->id, 'student');
712 // Student 2 belongs only to course 2.
713 $student2 = $generator->create_user();
714 $generator->enrol_user($student2->id, $course2->id, 'student');
716 // And the third student is only in course 3.
717 $student3 = $generator->create_user();
718 $generator->enrol_user($student3->id, $course3->id, 'student');
720 $search = testable_core_search::instance();
721 $search->add_core_search_areas();
723 // Admin gets 'true' result to function regardless of blocks.
724 $this->setAdminUser();
725 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
727 // Student 1 gets all 3 block contexts.
728 $this->setUser($student1);
729 $contexts = $search->get_areas_user_accesses()->usercontexts;
730 $this->assertArrayHasKey('block_html-content', $contexts);
731 $this->assertCount(3, $contexts['block_html-content']);
733 // Student 2 does not get any blocks.
734 $this->setUser($student2);
735 $contexts = $search->get_areas_user_accesses()->usercontexts;
736 $this->assertArrayNotHasKey('block_html-content', $contexts);
738 // Student 3 gets only two of them.
739 $this->setUser($student3);
740 $contexts = $search->get_areas_user_accesses()->usercontexts;
741 $this->assertArrayHasKey('block_html-content', $contexts);
742 $this->assertCount(2, $contexts['block_html-content']);
744 // A course limited search for student 1 is the same as the student 3 search.
745 $this->setUser($student1);
746 $limitedcontexts = $search->get_areas_user_accesses([$course3->id])->usercontexts;
747 $this->assertEquals($contexts['block_html-content'], $limitedcontexts['block_html-content']);
749 // Get block context ids for the blocks that appear.
750 global $DB;
751 $blockcontextids = $DB->get_fieldset_sql('
752 SELECT x.id
753 FROM {block_instances} bi
754 JOIN {context} x ON x.instanceid = bi.id AND x.contextlevel = ?
755 WHERE (parentcontextid = ? OR parentcontextid = ?)
756 AND blockname = ?
757 ORDER BY bi.id', [CONTEXT_BLOCK, $context1->id, $context3->id, 'html']);
759 // Context limited search (no course).
760 $contexts = $search->get_areas_user_accesses(false,
761 [$blockcontextids[0], $blockcontextids[2]])->usercontexts;
762 $this->assertCount(2, $contexts['block_html-content']);
764 // Context limited search (with course 3).
765 $contexts = $search->get_areas_user_accesses([$course2->id, $course3->id],
766 [$blockcontextids[0], $blockcontextids[2]])->usercontexts;
767 $this->assertCount(1, $contexts['block_html-content']);
771 * Test get_areas_user_accesses with regard to the 'all available courses' config option.
773 * @return void
775 public function test_search_user_accesses_allavailable() {
776 global $DB, $CFG;
778 $this->resetAfterTest();
780 // Front page, including a forum.
781 $frontpage = $DB->get_record('course', array('id' => SITEID));
782 $forumfront = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id));
783 $forumfrontctx = context_module::instance($forumfront->cmid);
785 // Course 1 does not allow guest access.
786 $course1 = $this->getDataGenerator()->create_course((object)array(
787 'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED,
788 'enrol_guest_password_0' => ''));
789 $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id));
790 $forum1ctx = context_module::instance($forum1->cmid);
792 // Course 2 does not allow guest but is accessible by all users.
793 $course2 = $this->getDataGenerator()->create_course((object)array(
794 'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED,
795 'enrol_guest_password_0' => ''));
796 $course2ctx = context_course::instance($course2->id);
797 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
798 $forum2ctx = context_module::instance($forum2->cmid);
799 assign_capability('moodle/course:view', CAP_ALLOW, $CFG->defaultuserroleid, $course2ctx->id);
801 // Course 3 allows guest access without password.
802 $course3 = $this->getDataGenerator()->create_course((object)array(
803 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
804 'enrol_guest_password_0' => ''));
805 $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
806 $forum3ctx = context_module::instance($forum3->cmid);
808 // Student user is enrolled in course 1.
809 $student = $this->getDataGenerator()->create_user();
810 $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student');
812 // No access user is just a user with no permissions.
813 $noaccess = $this->getDataGenerator()->create_user();
815 // First test without the all available option.
816 $search = testable_core_search::instance();
818 // Admin user can access everything.
819 $this->setAdminUser();
820 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
822 // No-access user can access only the front page forum.
823 $this->setUser($noaccess);
824 $contexts = $search->get_areas_user_accesses()->usercontexts;
825 $this->assertEquals([$forumfrontctx->id], array_keys($contexts[$this->forumpostareaid]));
827 // Student can access the front page forum plus the enrolled one.
828 $this->setUser($student);
829 $contexts = $search->get_areas_user_accesses()->usercontexts;
830 $this->assertEquals([$forum1ctx->id, $forumfrontctx->id],
831 array_keys($contexts[$this->forumpostareaid]));
833 // Now turn on the all available option.
834 set_config('searchallavailablecourses', 1);
836 // Admin user can access everything.
837 $this->setAdminUser();
838 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses());
840 // No-access user can access the front page forum and course 2, 3.
841 $this->setUser($noaccess);
842 $contexts = $search->get_areas_user_accesses()->usercontexts;
843 $this->assertEquals([$forum2ctx->id, $forum3ctx->id, $forumfrontctx->id],
844 array_keys($contexts[$this->forumpostareaid]));
846 // Student can access the front page forum plus the enrolled one plus courses 2, 3.
847 $this->setUser($student);
848 $contexts = $search->get_areas_user_accesses()->usercontexts;
849 $this->assertEquals([$forum1ctx->id, $forum2ctx->id, $forum3ctx->id, $forumfrontctx->id],
850 array_keys($contexts[$this->forumpostareaid]));
854 * Tests group-related aspects of the get_areas_user_accesses function.
856 public function test_search_user_accesses_groups() {
857 global $DB;
859 $this->resetAfterTest();
860 $this->setAdminUser();
862 // Create 2 courses each with 2 groups and 2 forums (separate/visible groups).
863 $generator = $this->getDataGenerator();
864 $course1 = $generator->create_course();
865 $course2 = $generator->create_course();
866 $group1 = $generator->create_group(['courseid' => $course1->id]);
867 $group2 = $generator->create_group(['courseid' => $course1->id]);
868 $group3 = $generator->create_group(['courseid' => $course2->id]);
869 $group4 = $generator->create_group(['courseid' => $course2->id]);
870 $forum1s = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => SEPARATEGROUPS]);
871 $id1s = context_module::instance($forum1s->cmid)->id;
872 $forum1v = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => VISIBLEGROUPS]);
873 $id1v = context_module::instance($forum1v->cmid)->id;
874 $forum2s = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => SEPARATEGROUPS]);
875 $id2s = context_module::instance($forum2s->cmid)->id;
876 $forum2n = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => NOGROUPS]);
877 $id2n = context_module::instance($forum2n->cmid)->id;
879 // Get search instance.
880 $search = testable_core_search::instance();
881 $search->add_core_search_areas();
883 // User 1 is a manager in one course and a student in the other one. They belong to
884 // all of the groups 1, 2, 3, and 4.
885 $user1 = $generator->create_user();
886 $generator->enrol_user($user1->id, $course1->id, 'manager');
887 $generator->enrol_user($user1->id, $course2->id, 'student');
888 groups_add_member($group1, $user1);
889 groups_add_member($group2, $user1);
890 groups_add_member($group3, $user1);
891 groups_add_member($group4, $user1);
893 $this->setUser($user1);
894 $accessinfo = $search->get_areas_user_accesses();
895 $contexts = $accessinfo->usercontexts;
897 // Double-check all the forum contexts.
898 $postcontexts = $contexts['mod_forum-post'];
899 sort($postcontexts);
900 $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts);
902 // Only the context in the second course (no accessallgroups) is restricted.
903 $restrictedcontexts = $accessinfo->separategroupscontexts;
904 sort($restrictedcontexts);
905 $this->assertEquals([$id2s], $restrictedcontexts);
907 // Only the groups from the second course (no accessallgroups) are included.
908 $groupids = $accessinfo->usergroups;
909 sort($groupids);
910 $this->assertEquals([$group3->id, $group4->id], $groupids);
912 // User 2 is a student in each course and belongs to groups 2 and 4.
913 $user2 = $generator->create_user();
914 $generator->enrol_user($user2->id, $course1->id, 'student');
915 $generator->enrol_user($user2->id, $course2->id, 'student');
916 groups_add_member($group2, $user2);
917 groups_add_member($group4, $user2);
919 $this->setUser($user2);
920 $accessinfo = $search->get_areas_user_accesses();
921 $contexts = $accessinfo->usercontexts;
923 // Double-check all the forum contexts.
924 $postcontexts = $contexts['mod_forum-post'];
925 sort($postcontexts);
926 $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts);
928 // Both separate groups forums are restricted.
929 $restrictedcontexts = $accessinfo->separategroupscontexts;
930 sort($restrictedcontexts);
931 $this->assertEquals([$id1s, $id2s], $restrictedcontexts);
933 // Groups from both courses are included.
934 $groupids = $accessinfo->usergroups;
935 sort($groupids);
936 $this->assertEquals([$group2->id, $group4->id], $groupids);
938 // User 3 is a manager at system level.
939 $user3 = $generator->create_user();
940 role_assign($DB->get_field('role', 'id', ['shortname' => 'manager'], MUST_EXIST), $user3->id,
941 \context_system::instance());
943 $this->setUser($user3);
944 $accessinfo = $search->get_areas_user_accesses();
946 // Nothing is restricted and no groups are relevant.
947 $this->assertEquals([], $accessinfo->separategroupscontexts);
948 $this->assertEquals([], $accessinfo->usergroups);
952 * test_is_search_area
954 * @return void
956 public function test_is_search_area() {
958 $this->assertFalse(testable_core_search::is_search_area('\asd\asd'));
959 $this->assertFalse(testable_core_search::is_search_area('\mod_forum\search\posta'));
960 $this->assertFalse(testable_core_search::is_search_area('\core_search\base_mod'));
961 $this->assertTrue(testable_core_search::is_search_area('\mod_forum\search\post'));
962 $this->assertTrue(testable_core_search::is_search_area('\\mod_forum\\search\\post'));
963 $this->assertTrue(testable_core_search::is_search_area('mod_forum\\search\\post'));
967 * Tests the request_index function used for reindexing certain contexts. This only tests
968 * adding things to the request list, it doesn't test that they are actually indexed by the
969 * scheduled task.
971 public function test_request_index() {
972 global $DB;
974 $this->resetAfterTest();
976 $course1 = $this->getDataGenerator()->create_course();
977 $course1ctx = context_course::instance($course1->id);
978 $course2 = $this->getDataGenerator()->create_course();
979 $course2ctx = context_course::instance($course2->id);
980 $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
981 $forum1ctx = context_module::instance($forum1->cmid);
982 $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
983 $forum2ctx = context_module::instance($forum2->cmid);
985 // Initially no requests.
986 $this->assertEquals(0, $DB->count_records('search_index_requests'));
988 // Request update for course 1, all areas.
989 \core_search\manager::request_index($course1ctx);
991 // Check all details of entry.
992 $results = array_values($DB->get_records('search_index_requests'));
993 $this->assertCount(1, $results);
994 $this->assertEquals($course1ctx->id, $results[0]->contextid);
995 $this->assertEquals('', $results[0]->searcharea);
996 $now = time();
997 $this->assertLessThanOrEqual($now, $results[0]->timerequested);
998 $this->assertGreaterThan($now - 10, $results[0]->timerequested);
999 $this->assertEquals('', $results[0]->partialarea);
1000 $this->assertEquals(0, $results[0]->partialtime);
1002 // Request forum 1, all areas; not added as covered by course 1.
1003 \core_search\manager::request_index($forum1ctx);
1004 $this->assertEquals(1, $DB->count_records('search_index_requests'));
1006 // Request forum 1, specific area; not added as covered by course 1 all areas.
1007 \core_search\manager::request_index($forum1ctx, 'forum-post');
1008 $this->assertEquals(1, $DB->count_records('search_index_requests'));
1010 // Request course 1 again, specific area; not added as covered by all areas.
1011 \core_search\manager::request_index($course1ctx, 'forum-post');
1012 $this->assertEquals(1, $DB->count_records('search_index_requests'));
1014 // Request course 1 again, all areas; not needed as covered already.
1015 \core_search\manager::request_index($course1ctx);
1016 $this->assertEquals(1, $DB->count_records('search_index_requests'));
1018 // Request course 2, specific area.
1019 \core_search\manager::request_index($course2ctx, 'label-activity');
1020 // Note: I'm ordering by ID for convenience - this is dangerous in real code (see MDL-43447)
1021 // but in a unit test it shouldn't matter as nobody is using clustered databases for unit
1022 // test.
1023 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1024 $this->assertCount(2, $results);
1025 $this->assertEquals($course1ctx->id, $results[0]->contextid);
1026 $this->assertEquals($course2ctx->id, $results[1]->contextid);
1027 $this->assertEquals('label-activity', $results[1]->searcharea);
1029 // Request forum 2, same specific area; not added.
1030 \core_search\manager::request_index($forum2ctx, 'label-activity');
1031 $this->assertEquals(2, $DB->count_records('search_index_requests'));
1033 // Request forum 2, different specific area; added.
1034 \core_search\manager::request_index($forum2ctx, 'forum-post');
1035 $this->assertEquals(3, $DB->count_records('search_index_requests'));
1037 // Request forum 2, all areas; also added. (Note: This could obviously remove the previous
1038 // one, but for simplicity, I didn't make it do that; also it could perhaps cause problems
1039 // if we had already begun processing the previous entry.)
1040 \core_search\manager::request_index($forum2ctx);
1041 $this->assertEquals(4, $DB->count_records('search_index_requests'));
1043 // Clear queue and do tests relating to priority.
1044 $DB->delete_records('search_index_requests');
1046 // Request forum 1, specific area, priority 100.
1047 \core_search\manager::request_index($forum1ctx, 'forum-post', 100);
1048 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1049 $this->assertCount(1, $results);
1050 $this->assertEquals(100, $results[0]->indexpriority);
1052 // Request forum 1, same area, lower priority; no change.
1053 \core_search\manager::request_index($forum1ctx, 'forum-post', 99);
1054 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1055 $this->assertCount(1, $results);
1056 $this->assertEquals(100, $results[0]->indexpriority);
1058 // Request forum 1, same area, higher priority; priority stored changes.
1059 \core_search\manager::request_index($forum1ctx, 'forum-post', 101);
1060 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1061 $this->assertCount(1, $results);
1062 $this->assertEquals(101, $results[0]->indexpriority);
1064 // Request forum 1, all areas, lower priority; adds second entry.
1065 \core_search\manager::request_index($forum1ctx, '', 100);
1066 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1067 $this->assertCount(2, $results);
1068 $this->assertEquals(100, $results[1]->indexpriority);
1070 // Request course 1, all areas, lower priority; adds third entry.
1071 \core_search\manager::request_index($course1ctx, '', 99);
1072 $results = array_values($DB->get_records('search_index_requests', null, 'id'));
1073 $this->assertCount(3, $results);
1074 $this->assertEquals(99, $results[2]->indexpriority);
1078 * Tests the process_index_requests function.
1080 public function test_process_index_requests() {
1081 global $DB;
1083 $this->resetAfterTest();
1085 $search = testable_core_search::instance();
1087 // When there are no index requests, nothing gets logged.
1088 $progress = new progress_trace_buffer(new text_progress_trace(), false);
1089 $search->process_index_requests(0.0, $progress);
1090 $out = $progress->get_buffer();
1091 $progress->reset_buffer();
1092 $this->assertEquals('', $out);
1094 // Set up the course with 3 forums.
1095 $generator = $this->getDataGenerator();
1096 $course = $generator->create_course(['fullname' => 'TCourse']);
1097 $forum1 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum1']);
1098 $forum2 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum2']);
1099 $forum3 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum3']);
1101 // Hack the forums so they have different creation times.
1102 $now = time();
1103 $DB->set_field('forum', 'timemodified', $now - 30, ['id' => $forum1->id]);
1104 $DB->set_field('forum', 'timemodified', $now - 20, ['id' => $forum2->id]);
1105 $DB->set_field('forum', 'timemodified', $now - 10, ['id' => $forum3->id]);
1106 $forum2time = $now - 20;
1108 // Make 2 index requests.
1109 testable_core_search::fake_current_time($now - 3);
1110 $search::request_index(context_course::instance($course->id), 'mod_label-activity');
1111 testable_core_search::fake_current_time($now - 2);
1112 $search::request_index(context_module::instance($forum1->cmid));
1114 // Run with no time limit.
1115 $search->process_index_requests(0.0, $progress);
1116 $out = $progress->get_buffer();
1117 $progress->reset_buffer();
1119 // Check that it's done both areas.
1120 $this->assertContains(
1121 'Indexing requested context: Course: TCourse (search area: mod_label-activity)',
1122 $out);
1123 $this->assertContains(
1124 'Completed requested context: Course: TCourse (search area: mod_label-activity)',
1125 $out);
1126 $this->assertContains('Indexing requested context: Forum: TForum1', $out);
1127 $this->assertContains('Completed requested context: Forum: TForum1', $out);
1129 // Check the requests database table is now empty.
1130 $this->assertEquals(0, $DB->count_records('search_index_requests'));
1132 // Request indexing the course a couple of times.
1133 testable_core_search::fake_current_time($now - 3);
1134 $search::request_index(context_course::instance($course->id), 'mod_forum-activity');
1135 testable_core_search::fake_current_time($now - 2);
1136 $search::request_index(context_course::instance($course->id), 'mod_forum-post');
1138 // Do the processing again with a time limit and indexing delay. The time limit is too
1139 // small; because of the way the logic works, this means it will index 2 activities.
1140 $search->get_engine()->set_add_delay(0.2);
1141 $search->process_index_requests(0.1, $progress);
1142 $out = $progress->get_buffer();
1143 $progress->reset_buffer();
1145 // Confirm the right wrapper information was logged.
1146 $this->assertContains(
1147 'Indexing requested context: Course: TCourse (search area: mod_forum-activity)',
1148 $out);
1149 $this->assertContains('Stopping indexing due to time limit', $out);
1150 $this->assertContains(
1151 'Ending requested context: Course: TCourse (search area: mod_forum-activity)',
1152 $out);
1154 // Check the database table has been updated with progress.
1155 $records = array_values($DB->get_records('search_index_requests', null, 'searcharea'));
1156 $this->assertEquals('mod_forum-activity', $records[0]->partialarea);
1157 $this->assertEquals($forum2time, $records[0]->partialtime);
1159 // Run again and confirm it now finishes.
1160 $search->process_index_requests(2.0, $progress);
1161 $out = $progress->get_buffer();
1162 $progress->reset_buffer();
1163 $this->assertContains(
1164 'Completed requested context: Course: TCourse (search area: mod_forum-activity)',
1165 $out);
1166 $this->assertContains(
1167 'Completed requested context: Course: TCourse (search area: mod_forum-post)',
1168 $out);
1170 // Confirm table is now empty.
1171 $this->assertEquals(0, $DB->count_records('search_index_requests'));
1173 // Make 2 requests - first one is low priority.
1174 testable_core_search::fake_current_time($now - 3);
1175 $search::request_index(context_module::instance($forum1->cmid), 'mod_forum-activity',
1176 \core_search\manager::INDEX_PRIORITY_REINDEXING);
1177 testable_core_search::fake_current_time($now - 2);
1178 $search::request_index(context_module::instance($forum2->cmid), 'mod_forum-activity');
1180 // Process with short time limit and confirm it does the second one first.
1181 $search->process_index_requests(0.1, $progress);
1182 $out = $progress->get_buffer();
1183 $progress->reset_buffer();
1184 $this->assertContains(
1185 'Completed requested context: Forum: TForum2 (search area: mod_forum-activity)',
1186 $out);
1187 $search->process_index_requests(0.1, $progress);
1188 $out = $progress->get_buffer();
1189 $progress->reset_buffer();
1190 $this->assertContains(
1191 'Completed requested context: Forum: TForum1 (search area: mod_forum-activity)',
1192 $out);
1194 // Make a request for a course context...
1195 $course = $generator->create_course();
1196 $context = context_course::instance($course->id);
1197 $search::request_index($context);
1199 // ...but then delete it (note: delete_course spews output, so we throw it away).
1200 ob_start();
1201 delete_course($course);
1202 ob_end_clean();
1204 // Process requests - it should only note the deleted context.
1205 $search->process_index_requests(10, $progress);
1206 $out = $progress->get_buffer();
1207 $progress->reset_buffer();
1208 $this->assertContains('Skipped deleted context: ' . $context->id, $out);
1210 // Confirm request table is now empty.
1211 $this->assertEquals(0, $DB->count_records('search_index_requests'));