on-demand release 4.4dev+
[moodle.git] / search / tests / base_activity_test.php
blobebd615e48d8efe32073207b810687fc81df10bcc
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 namespace core_search;
19 defined('MOODLE_INTERNAL') || die();
21 global $CFG;
22 require_once(__DIR__ . '/fixtures/testable_core_search.php');
23 require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
25 /**
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 {
33 /**
34 * @var \core_search::manager
36 protected $search = null;
38 /**
39 * @var \core_search_generator Instace of core_search_generator.
41 protected $generator = null;
43 /**
44 * @var Instace of testable_engine.
46 protected $engine = null;
48 /** @var \context[] Array of test contexts */
49 protected $contexts;
51 /** @var \stdClass[] Array of test forum objects */
52 protected $forums;
54 public function setUp(): void {
55 global $DB;
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() {
126 $filearea = 'intro';
127 $component = 'mod_forum';
128 $module = '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();
137 $filerecord = array(
138 'contextid' => $contextid,
139 'component' => $component,
140 'filearea' => $filearea,
141 'itemid' => 0,
142 'filepath' => '/',
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() {
180 global $USER, $DB;
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));
211 // Course 1 only.
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));
217 // Course 2 only.
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) {
263 $results = [];
264 foreach ($rs as $rec) {
265 $results[] = $rec;
267 $rs->close();
268 return $results;
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) {
278 $ids = [];
279 foreach ($recs as $rec) {
280 $ids[] = $rec->id;
282 return $ids;
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() {
303 global $CFG;
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() {
337 global $DB;
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.
349 $now = time();
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()
384 ->getMock();
386 $result = $baseactivity->get_doc_icon($document);
388 $this->assertEquals('monologo', $result->get_name());
389 $this->assertEquals('test_activity', $result->get_component());