Merge branch 'MDL-70422-310' of https://github.com/paulholden/moodle into MOODLE_310_...
[moodle.git] / search / tests / base_activity_test.php
CommitLineData
4e921569
MP
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/**
18 * Search engine base unit tests.
19 *
20 * @package core_search
21 * @copyright 2017 Matt Porritt <mattp@catalyst-au.net>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27global $CFG;
28require_once(__DIR__ . '/fixtures/testable_core_search.php');
29require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
30
31/**
32 * Search engine base unit tests.
33 *
34 * @package core_search
35 * @copyright 2017 Matt Porritt <mattp@catalyst-au.net>
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 */
38class search_base_activity_testcase extends advanced_testcase {
39 /**
40 * @var \core_search::manager
41 */
42 protected $search = null;
43
44 /**
45 * @var Instace of core_search_generator.
46 */
47 protected $generator = null;
48
49 /**
50 * @var Instace of testable_engine.
51 */
52 protected $engine = null;
53
427b7563 54 /** @var context[] Array of test contexts */
55 protected $contexts;
56
57 /** @var stdClass[] Array of test forum objects */
58 protected $forums;
59
d81a9480 60 public function setUp(): void {
427b7563 61 global $DB;
4e921569
MP
62 $this->resetAfterTest();
63 set_config('enableglobalsearch', true);
64
65 // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
66 $search = testable_core_search::instance();
67
68 $this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
69 $this->generator->setup();
427b7563 70
71 $this->setAdminUser();
72
73 // Create course and 2 forums.
74 $generator = $this->getDataGenerator();
75 $course = $generator->create_course();
76 $this->contexts['c1'] = \context_course::instance($course->id);
77 $this->forums[1] = $generator->create_module('forum', ['course' => $course->id, 'name' => 'Forum 1',
78 'intro' => '<p>Intro 1</p>', 'introformat' => FORMAT_HTML]);
79 $this->contexts['f1'] = \context_module::instance($this->forums[1]->cmid);
80 $this->forums[2] = $generator->create_module('forum', ['course' => $course->id, 'name' => 'Forum 2',
81 'intro' => '<p>Intro 2</p>', 'introformat' => FORMAT_HTML]);
82 $this->contexts['f2'] = \context_module::instance($this->forums[2]->cmid);
83
84 // Create another 2 courses (in same category and in a new category) with one forum each.
85 $this->contexts['cc1'] = \context_coursecat::instance($course->category);
86 $course2 = $generator->create_course();
87 $this->contexts['c2'] = \context_course::instance($course2->id);
88 $this->forums[3] = $generator->create_module('forum', ['course' => $course2->id, 'name' => 'Forum 3',
89 'intro' => '<p>Intro 3</p>', 'introformat' => FORMAT_HTML]);
90 $this->contexts['f3'] = \context_module::instance($this->forums[3]->cmid);
91 $cat2 = $generator->create_category();
92 $this->contexts['cc2'] = \context_coursecat::instance($cat2->id);
93 $course3 = $generator->create_course(['category' => $cat2->id]);
94 $this->contexts['c3'] = \context_course::instance($course3->id);
95 $this->forums[4] = $generator->create_module('forum', ['course' => $course3->id, 'name' => 'Forum 4',
96 'intro' => '<p>Intro 4</p>', 'introformat' => FORMAT_HTML]);
97 $this->contexts['f4'] = \context_module::instance($this->forums[4]->cmid);
98
99 // Hack about with the time modified values.
100 foreach ($this->forums as $index => $forum) {
101 $DB->set_field('forum', 'timemodified', $index, ['id' => $forum->id]);
102 }
4e921569
MP
103 }
104
d81a9480 105 public function tearDown(): void {
4e921569
MP
106 // For unit tests before PHP 7, teardown is called even on skip. So only do our teardown if we did setup.
107 if ($this->generator) {
108 // Moodle DML freaks out if we don't teardown the temp table after each run.
109 $this->generator->teardown();
110 $this->generator = null;
111 }
112 }
113
114 /**
115 * Test base activity get search fileareas
116 */
117 public function test_get_search_fileareas_base() {
118
119 $builder = $this->getMockBuilder('\core_search\base_activity');
120 $builder->disableOriginalConstructor();
121 $stub = $builder->getMockForAbstractClass();
122
123 $result = $stub->get_search_fileareas();
124
125 $this->assertEquals(array('intro'), $result);
126 }
127
128 /**
129 * Test base attach files
130 */
131 public function test_attach_files_base() {
132 $filearea = 'intro';
133 $component = 'mod_forum';
134 $module = 'forum';
135
136 $course = self::getDataGenerator()->create_course();
137 $activity = self::getDataGenerator()->create_module('forum', array('course' => $course->id));
138 $context = \context_module::instance($activity->cmid);
139 $contextid = $context->id;
140
141 // Create file to add.
142 $fs = get_file_storage();
143 $filerecord = array(
144 'contextid' => $contextid,
145 'component' => $component,
146 'filearea' => $filearea,
147 'itemid' => 0,
148 'filepath' => '/',
149 'filename' => 'testfile.txt');
150 $content = 'All the news that\'s fit to print';
151 $file = $fs->create_file_from_string($filerecord, $content);
152
153 // Construct the search document.
154 $rec = new \stdClass();
155 $rec->courseid = $course->id;
156 $area = new core_mocksearch\search\mock_search_area();
157 $record = $this->generator->create_record($rec);
158
159 $document = $area->get_document($record);
160 $document->set('itemid', $activity->id);
161
162 // Create a mock from the abstract class,
163 // with required methods stubbed.
164 $builder = $this->getMockBuilder('\core_search\base_activity');
165 $builder->disableOriginalConstructor();
166 $builder->setMethods(array('get_module_name', 'get_component_name'));
167 $stub = $builder->getMockForAbstractClass();
168 $stub->method('get_module_name')->willReturn($module);
169 $stub->method('get_component_name')->willReturn($component);
170
171 // Attach file to our test document.
172 $stub->attach_files($document);
173
174 // Verify file is attached.
175 $files = $document->get_files();
176 $file = array_values($files)[0];
177
178 $this->assertEquals(1, count($files));
179 $this->assertEquals($content, $file->get_content());
180 }
427b7563 181
182 /**
183 * Tests getting the recordset.
184 */
185 public function test_get_document_recordset() {
186 global $USER, $DB;
187
188 // Get all the forums to index (no restriction).
189 $area = new mod_forum\search\activity();
190 $results = self::recordset_to_indexed_array($area->get_document_recordset());
191
192 // Should return all forums.
193 $this->assertCount(4, $results);
194
195 // Each result should basically have the contents of the forum table. We'll just check
196 // the key fields for the first one and then the other ones by id only.
197 $this->assertEquals($this->forums[1]->id, $results[0]->id);
198 $this->assertEquals(1, $results[0]->timemodified);
199 $this->assertEquals($this->forums[1]->course, $results[0]->course);
200 $this->assertEquals('Forum 1', $results[0]->name);
201 $this->assertEquals('<p>Intro 1</p>', $results[0]->intro);
202 $this->assertEquals(FORMAT_HTML, $results[0]->introformat);
203
204 $allids = self::records_to_ids($this->forums);
205 $this->assertEquals($allids, self::records_to_ids($results));
206
207 // Repeat with a time restriction.
208 $results = self::recordset_to_indexed_array($area->get_document_recordset(3));
209 $this->assertEquals([$this->forums[3]->id, $this->forums[4]->id],
210 self::records_to_ids($results));
211
212 // Now use context restrictions. First, the whole site (no change).
213 $results = self::recordset_to_indexed_array($area->get_document_recordset(
214 0, context_system::instance()));
215 $this->assertEquals($allids, self::records_to_ids($results));
216
217 // Course 1 only.
218 $results = self::recordset_to_indexed_array($area->get_document_recordset(
219 0, $this->contexts['c1']));
220 $this->assertEquals([$this->forums[1]->id, $this->forums[2]->id],
221 self::records_to_ids($results));
222
223 // Course 2 only.
224 $results = self::recordset_to_indexed_array($area->get_document_recordset(
225 0, $this->contexts['c2']));
226 $this->assertEquals([$this->forums[3]->id], self::records_to_ids($results));
227
228 // Specific forum only.
229 $results = self::recordset_to_indexed_array($area->get_document_recordset(
230 0, $this->contexts['f4']));
231 $this->assertEquals([$this->forums[4]->id], self::records_to_ids($results));
232
233 // Category 1 context (courses 1 and 2).
234 $results = self::recordset_to_indexed_array($area->get_document_recordset(
235 0, $this->contexts['cc1']));
236 $this->assertEquals([$this->forums[1]->id, $this->forums[2]->id, $this->forums[3]->id],
237 self::records_to_ids($results));
238
239 // Category 2 context (course 3).
240 $results = self::recordset_to_indexed_array($area->get_document_recordset(
241 0, $this->contexts['cc2']));
242 $this->assertEquals([$this->forums[4]->id], self::records_to_ids($results));
243
244 // Combine context restriction (category 1) with timemodified.
245 $results = self::recordset_to_indexed_array($area->get_document_recordset(
246 2, $this->contexts['cc1']));
247 $this->assertEquals([$this->forums[2]->id, $this->forums[3]->id],
248 self::records_to_ids($results));
249
250 // Find an arbitrary block on the system to get a block context.
251 $blockid = array_values($DB->get_records('block_instances', null, 'id', 'id', 0, 1))[0]->id;
252 $blockcontext = context_block::instance($blockid);
253
254 // Block context (cannot return anything, so always null).
255 $this->assertNull($area->get_document_recordset(0, $blockcontext));
256
257 // User context (cannot return anything, so always null).
258 $usercontext = context_user::instance($USER->id);
259 $this->assertNull($area->get_document_recordset(0, $usercontext));
260 }
261
262 /**
263 * Utility function to convert recordset to array for testing.
264 *
265 * @param moodle_recordset $rs Recordset to convert
266 * @return array Array indexed by number (0, 1, 2, ...)
267 */
268 protected static function recordset_to_indexed_array(moodle_recordset $rs) {
269 $results = [];
270 foreach ($rs as $rec) {
271 $results[] = $rec;
272 }
273 $rs->close();
274 return $results;
275 }
276
277 /**
278 * Utility function to convert records to array of IDs.
279 *
280 * @param array $recs Records which should have an 'id' field
281 * @return array Array of ids
282 */
283 protected static function records_to_ids(array $recs) {
284 $ids = [];
285 foreach ($recs as $rec) {
286 $ids[] = $rec->id;
287 }
288 return $ids;
289 }
290
291 /**
292 * Tests the get_doc_url function.
293 */
294 public function test_get_doc_url() {
295 $area = new mod_forum\search\activity();
296 $results = self::recordset_to_indexed_array($area->get_document_recordset());
297
298 for ($i = 0; $i < 4; $i++) {
299 $this->assertEquals(new moodle_url('/mod/forum/view.php',
300 ['id' => $this->forums[$i + 1]->cmid]),
301 $area->get_doc_url($area->get_document($results[$i])));
302 }
303 }
304
305 /**
306 * Tests the check_access function.
307 */
308 public function test_check_access() {
309 global $CFG;
310 require_once($CFG->dirroot . '/course/lib.php');
311
312 // Create a test user who can access courses 1 and 2 (everything except forum 4).
313 $generator = $this->getDataGenerator();
314 $user = $generator->create_user();
315 $generator->enrol_user($user->id, $this->forums[1]->course, 'student');
316 $generator->enrol_user($user->id, $this->forums[3]->course, 'student');
317 $this->setUser($user);
318
319 // Delete forum 2 and set forum 3 hidden.
320 course_delete_module($this->forums[2]->cmid);
321 set_coursemodule_visible($this->forums[3]->cmid, 0);
322
323 // Call check access on all the first three.
324 $area = new mod_forum\search\activity();
325 $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access(
326 $this->forums[1]->id));
327 $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access(
328 $this->forums[2]->id));
329 $this->assertEquals(\core_search\manager::ACCESS_DENIED, $area->check_access(
330 $this->forums[3]->id));
331
332 // Note: Do not check forum 4 which is in a course the user can't access; this will return
333 // ACCESS_GRANTED, but it does not matter because the search engine will not have included
334 // that context in the list to search. (This is because the $cm->uservisible access flag
335 // is only valid if the user is known to be able to access the course.)
336 }
25564a78 337
338 /**
339 * Tests the module version of get_contexts_to_reindex, which is supposed to return all the
340 * activity contexts in order of date added.
341 */
342 public function test_get_contexts_to_reindex() {
343 global $DB;
344
345 $this->resetAfterTest();
346
347 // Set up a course with two URLs and a Page.
348 $generator = $this->getDataGenerator();
349 $course = $generator->create_course(['fullname' => 'TCourse']);
350 $url1 = $generator->create_module('url', ['course' => $course->id, 'name' => 'TURL1']);
351 $url2 = $generator->create_module('url', ['course' => $course->id, 'name' => 'TURL2']);
352 $page = $generator->create_module('page', ['course' => $course->id, 'name' => 'TPage1']);
353
354 // Hack the items so they have different added times.
355 $now = time();
356 $DB->set_field('course_modules', 'added', $now - 3, ['id' => $url2->cmid]);
357 $DB->set_field('course_modules', 'added', $now - 2, ['id' => $url1->cmid]);
358 $DB->set_field('course_modules', 'added', $now - 1, ['id' => $page->cmid]);
359
360 // Check the URL contexts are in date order.
361 $urlarea = new \mod_url\search\activity();
362 $contexts = iterator_to_array($urlarea->get_contexts_to_reindex(), false);
363 $this->assertEquals([\context_module::instance($url1->cmid),
364 \context_module::instance($url2->cmid)], $contexts);
365
366 // Check the Page contexts.
367 $pagearea = new \mod_page\search\activity();
368 $contexts = iterator_to_array($pagearea->get_contexts_to_reindex(), false);
369 $this->assertEquals([\context_module::instance($page->cmid)], $contexts);
370
371 // Check another module area that has no instances.
372 $glossaryarea = new \mod_glossary\search\activity();
373 $contexts = iterator_to_array($glossaryarea->get_contexts_to_reindex(), false);
374 $this->assertEquals([], $contexts);
375 }
66f145ef
DM
376
377 /**
378 * Test document icon.
379 */
380 public function test_get_doc_icon() {
381 $baseactivity = $this->getMockBuilder('\core_search\base_activity')
382 ->disableOriginalConstructor()
383 ->setMethods(array('get_module_name'))
384 ->getMockForAbstractClass();
385
386 $baseactivity->method('get_module_name')->willReturn('test_activity');
387
388 $document = $this->getMockBuilder('\core_search\document')
389 ->disableOriginalConstructor()
390 ->getMock();
391
392 $result = $baseactivity->get_doc_icon($document);
393
394 $this->assertEquals('icon', $result->get_name());
395 $this->assertEquals('test_activity', $result->get_component());
396 }
4e921569 397}