2 // This file is part of Moodle - http://moodle.org/
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.
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 namespace core_search
;
19 defined('MOODLE_INTERNAL') ||
die();
22 require_once(__DIR__
. '/fixtures/testable_core_search.php');
23 require_once($CFG->dirroot
. '/search/tests/fixtures/mock_search_area.php');
26 * Search engine base unit tests.
28 * @package core_search
29 * @copyright 2017 Matt Porritt <mattp@catalyst-au.net>
30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 class base_activity_test
extends \advanced_testcase
{
34 * @var \core_search::manager
36 protected $search = null;
39 * @var \core_search_generator Instace of core_search_generator.
41 protected $generator = null;
44 * @var Instace of testable_engine.
46 protected $engine = null;
48 /** @var \context[] Array of test contexts */
51 /** @var \stdClass[] Array of test forum objects */
54 public function setUp(): void
{
56 $this->resetAfterTest();
57 set_config('enableglobalsearch', true);
59 // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
60 $search = \testable_core_search
::instance();
62 $this->generator
= self
::getDataGenerator()->get_plugin_generator('core_search');
63 $this->generator
->setup();
65 $this->setAdminUser();
67 // Create course and 2 forums.
68 $generator = $this->getDataGenerator();
69 $course = $generator->create_course();
70 $this->contexts
['c1'] = \context_course
::instance($course->id
);
71 $this->forums
[1] = $generator->create_module('forum', ['course' => $course->id
, 'name' => 'Forum 1',
72 'intro' => '<p>Intro 1</p>', 'introformat' => FORMAT_HTML
]);
73 $this->contexts
['f1'] = \context_module
::instance($this->forums
[1]->cmid
);
74 $this->forums
[2] = $generator->create_module('forum', ['course' => $course->id
, 'name' => 'Forum 2',
75 'intro' => '<p>Intro 2</p>', 'introformat' => FORMAT_HTML
]);
76 $this->contexts
['f2'] = \context_module
::instance($this->forums
[2]->cmid
);
78 // Create another 2 courses (in same category and in a new category) with one forum each.
79 $this->contexts
['cc1'] = \context_coursecat
::instance($course->category
);
80 $course2 = $generator->create_course();
81 $this->contexts
['c2'] = \context_course
::instance($course2->id
);
82 $this->forums
[3] = $generator->create_module('forum', ['course' => $course2->id
, 'name' => 'Forum 3',
83 'intro' => '<p>Intro 3</p>', 'introformat' => FORMAT_HTML
]);
84 $this->contexts
['f3'] = \context_module
::instance($this->forums
[3]->cmid
);
85 $cat2 = $generator->create_category();
86 $this->contexts
['cc2'] = \context_coursecat
::instance($cat2->id
);
87 $course3 = $generator->create_course(['category' => $cat2->id
]);
88 $this->contexts
['c3'] = \context_course
::instance($course3->id
);
89 $this->forums
[4] = $generator->create_module('forum', ['course' => $course3->id
, 'name' => 'Forum 4',
90 'intro' => '<p>Intro 4</p>', 'introformat' => FORMAT_HTML
]);
91 $this->contexts
['f4'] = \context_module
::instance($this->forums
[4]->cmid
);
93 // Hack about with the time modified values.
94 foreach ($this->forums
as $index => $forum) {
95 $DB->set_field('forum', 'timemodified', $index, ['id' => $forum->id
]);
99 public function tearDown(): void
{
100 // For unit tests before PHP 7, teardown is called even on skip. So only do our teardown if we did setup.
101 if ($this->generator
) {
102 // Moodle DML freaks out if we don't teardown the temp table after each run.
103 $this->generator
->teardown();
104 $this->generator
= null;
109 * Test base activity get search fileareas
111 public function test_get_search_fileareas_base() {
113 $builder = $this->getMockBuilder('\core_search\base_activity');
114 $builder->disableOriginalConstructor();
115 $stub = $builder->getMockForAbstractClass();
117 $result = $stub->get_search_fileareas();
119 $this->assertEquals(array('intro'), $result);
123 * Test base attach files
125 public function test_attach_files_base() {
127 $component = 'mod_forum';
130 $course = self
::getDataGenerator()->create_course();
131 $activity = self
::getDataGenerator()->create_module('forum', array('course' => $course->id
));
132 $context = \context_module
::instance($activity->cmid
);
133 $contextid = $context->id
;
135 // Create file to add.
136 $fs = get_file_storage();
138 'contextid' => $contextid,
139 'component' => $component,
140 'filearea' => $filearea,
143 'filename' => 'testfile.txt');
144 $content = 'All the news that\'s fit to print';
145 $file = $fs->create_file_from_string($filerecord, $content);
147 // Construct the search document.
148 $rec = new \
stdClass();
149 $rec->courseid
= $course->id
;
150 $area = new \core_mocksearch\search\
mock_search_area();
151 $record = $this->generator
->create_record($rec);
153 $document = $area->get_document($record);
154 $document->set('itemid', $activity->id
);
156 // Create a mock from the abstract class,
157 // with required methods stubbed.
158 $builder = $this->getMockBuilder('\core_search\base_activity');
159 $builder->disableOriginalConstructor();
160 $builder->onlyMethods(array('get_module_name', 'get_component_name'));
161 $stub = $builder->getMockForAbstractClass();
162 $stub->method('get_module_name')->willReturn($module);
163 $stub->method('get_component_name')->willReturn($component);
165 // Attach file to our test document.
166 $stub->attach_files($document);
168 // Verify file is attached.
169 $files = $document->get_files();
170 $file = array_values($files)[0];
172 $this->assertEquals(1, count($files));
173 $this->assertEquals($content, $file->get_content());
177 * Tests getting the recordset.
179 public function test_get_document_recordset() {
182 // Get all the forums to index (no restriction).
183 $area = new \mod_forum\search\activity
();
184 $results = self
::recordset_to_indexed_array($area->get_document_recordset());
186 // Should return all forums.
187 $this->assertCount(4, $results);
189 // Each result should basically have the contents of the forum table. We'll just check
190 // the key fields for the first one and then the other ones by id only.
191 $this->assertEquals($this->forums
[1]->id
, $results[0]->id
);
192 $this->assertEquals(1, $results[0]->timemodified
);
193 $this->assertEquals($this->forums
[1]->course
, $results[0]->course
);
194 $this->assertEquals('Forum 1', $results[0]->name
);
195 $this->assertEquals('<p>Intro 1</p>', $results[0]->intro
);
196 $this->assertEquals(FORMAT_HTML
, $results[0]->introformat
);
198 $allids = self
::records_to_ids($this->forums
);
199 $this->assertEquals($allids, self
::records_to_ids($results));
201 // Repeat with a time restriction.
202 $results = self
::recordset_to_indexed_array($area->get_document_recordset(3));
203 $this->assertEquals([$this->forums
[3]->id
, $this->forums
[4]->id
],
204 self
::records_to_ids($results));
206 // Now use context restrictions. First, the whole site (no change).
207 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
208 0, \context_system
::instance()));
209 $this->assertEquals($allids, self
::records_to_ids($results));
212 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
213 0, $this->contexts
['c1']));
214 $this->assertEquals([$this->forums
[1]->id
, $this->forums
[2]->id
],
215 self
::records_to_ids($results));
218 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
219 0, $this->contexts
['c2']));
220 $this->assertEquals([$this->forums
[3]->id
], self
::records_to_ids($results));
222 // Specific forum only.
223 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
224 0, $this->contexts
['f4']));
225 $this->assertEquals([$this->forums
[4]->id
], self
::records_to_ids($results));
227 // Category 1 context (courses 1 and 2).
228 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
229 0, $this->contexts
['cc1']));
230 $this->assertEquals([$this->forums
[1]->id
, $this->forums
[2]->id
, $this->forums
[3]->id
],
231 self
::records_to_ids($results));
233 // Category 2 context (course 3).
234 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
235 0, $this->contexts
['cc2']));
236 $this->assertEquals([$this->forums
[4]->id
], self
::records_to_ids($results));
238 // Combine context restriction (category 1) with timemodified.
239 $results = self
::recordset_to_indexed_array($area->get_document_recordset(
240 2, $this->contexts
['cc1']));
241 $this->assertEquals([$this->forums
[2]->id
, $this->forums
[3]->id
],
242 self
::records_to_ids($results));
244 // Find an arbitrary block on the system to get a block context.
245 $blockid = array_values($DB->get_records('block_instances', null, 'id', 'id', 0, 1))[0]->id
;
246 $blockcontext = \context_block
::instance($blockid);
248 // Block context (cannot return anything, so always null).
249 $this->assertNull($area->get_document_recordset(0, $blockcontext));
251 // User context (cannot return anything, so always null).
252 $usercontext = \context_user
::instance($USER->id
);
253 $this->assertNull($area->get_document_recordset(0, $usercontext));
257 * Utility function to convert recordset to array for testing.
259 * @param \moodle_recordset $rs Recordset to convert
260 * @return array Array indexed by number (0, 1, 2, ...)
262 protected static function recordset_to_indexed_array(\moodle_recordset
$rs) {
264 foreach ($rs as $rec) {
272 * Utility function to convert records to array of IDs.
274 * @param array $recs Records which should have an 'id' field
275 * @return array Array of ids
277 protected static function records_to_ids(array $recs) {
279 foreach ($recs as $rec) {
286 * Tests the get_doc_url function.
288 public function test_get_doc_url() {
289 $area = new \mod_forum\search\activity
();
290 $results = self
::recordset_to_indexed_array($area->get_document_recordset());
292 for ($i = 0; $i < 4; $i++
) {
293 $this->assertEquals(new \
moodle_url('/mod/forum/view.php',
294 ['id' => $this->forums
[$i +
1]->cmid
]),
295 $area->get_doc_url($area->get_document($results[$i])));
300 * Tests the check_access function.
302 public function test_check_access() {
304 require_once($CFG->dirroot
. '/course/lib.php');
306 // Create a test user who can access courses 1 and 2 (everything except forum 4).
307 $generator = $this->getDataGenerator();
308 $user = $generator->create_user();
309 $generator->enrol_user($user->id
, $this->forums
[1]->course
, 'student');
310 $generator->enrol_user($user->id
, $this->forums
[3]->course
, 'student');
311 $this->setUser($user);
313 // Delete forum 2 and set forum 3 hidden.
314 course_delete_module($this->forums
[2]->cmid
);
315 set_coursemodule_visible($this->forums
[3]->cmid
, 0);
317 // Call check access on all the first three.
318 $area = new \mod_forum\search\activity
();
319 $this->assertEquals(\core_search\manager
::ACCESS_GRANTED
, $area->check_access(
320 $this->forums
[1]->id
));
321 $this->assertEquals(\core_search\manager
::ACCESS_DELETED
, $area->check_access(
322 $this->forums
[2]->id
));
323 $this->assertEquals(\core_search\manager
::ACCESS_DENIED
, $area->check_access(
324 $this->forums
[3]->id
));
326 // Note: Do not check forum 4 which is in a course the user can't access; this will return
327 // ACCESS_GRANTED, but it does not matter because the search engine will not have included
328 // that context in the list to search. (This is because the $cm->uservisible access flag
329 // is only valid if the user is known to be able to access the course.)
333 * Tests the module version of get_contexts_to_reindex, which is supposed to return all the
334 * activity contexts in order of date added.
336 public function test_get_contexts_to_reindex() {
339 $this->resetAfterTest();
341 // Set up a course with two URLs and a Page.
342 $generator = $this->getDataGenerator();
343 $course = $generator->create_course(['fullname' => 'TCourse']);
344 $url1 = $generator->create_module('url', ['course' => $course->id
, 'name' => 'TURL1']);
345 $url2 = $generator->create_module('url', ['course' => $course->id
, 'name' => 'TURL2']);
346 $page = $generator->create_module('page', ['course' => $course->id
, 'name' => 'TPage1']);
348 // Hack the items so they have different added times.
350 $DB->set_field('course_modules', 'added', $now - 3, ['id' => $url2->cmid
]);
351 $DB->set_field('course_modules', 'added', $now - 2, ['id' => $url1->cmid
]);
352 $DB->set_field('course_modules', 'added', $now - 1, ['id' => $page->cmid
]);
354 // Check the URL contexts are in date order.
355 $urlarea = new \mod_url\search\activity
();
356 $contexts = iterator_to_array($urlarea->get_contexts_to_reindex(), false);
357 $this->assertEquals([\context_module
::instance($url1->cmid
),
358 \context_module
::instance($url2->cmid
)], $contexts);
360 // Check the Page contexts.
361 $pagearea = new \mod_page\search\activity
();
362 $contexts = iterator_to_array($pagearea->get_contexts_to_reindex(), false);
363 $this->assertEquals([\context_module
::instance($page->cmid
)], $contexts);
365 // Check another module area that has no instances.
366 $glossaryarea = new \mod_glossary\search\activity
();
367 $contexts = iterator_to_array($glossaryarea->get_contexts_to_reindex(), false);
368 $this->assertEquals([], $contexts);
372 * Test document icon.
374 public function test_get_doc_icon() {
375 $baseactivity = $this->getMockBuilder('\core_search\base_activity')
376 ->disableOriginalConstructor()
377 ->onlyMethods(array('get_module_name'))
378 ->getMockForAbstractClass();
380 $baseactivity->method('get_module_name')->willReturn('test_activity');
382 $document = $this->getMockBuilder('\core_search\document')
383 ->disableOriginalConstructor()
386 $result = $baseactivity->get_doc_icon($document);
388 $this->assertEquals('monologo', $result->get_name());
389 $this->assertEquals('test_activity', $result->get_component());