Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core_search;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once(__DIR__ . '/fixtures/testable_core_search.php');
23
require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
24
 
25
/**
26
 * Search engine base unit tests.
27
 *
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
31
 */
32
class base_activity_test extends \advanced_testcase {
33
    /**
34
     * @var \core_search::manager
35
     */
36
    protected $search = null;
37
 
38
    /**
39
     * @var \core_search_generator Instace of core_search_generator.
40
     */
41
    protected $generator = null;
42
 
43
    /**
44
     * @var Instace of testable_engine.
45
     */
46
    protected $engine = null;
47
 
48
    /** @var \context[] Array of test contexts */
49
    protected $contexts;
50
 
51
    /** @var \stdClass[] Array of test forum objects */
52
    protected $forums;
53
 
54
    public function setUp(): void {
55
        global $DB;
56
        $this->resetAfterTest();
57
        set_config('enableglobalsearch', true);
58
 
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();
61
 
62
        $this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
63
        $this->generator->setup();
64
 
65
        $this->setAdminUser();
66
 
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);
77
 
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);
92
 
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]);
96
        }
97
    }
98
 
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;
105
        }
106
    }
107
 
108
    /**
109
     * Test base activity get search fileareas
110
     */
111
    public function test_get_search_fileareas_base() {
112
 
113
        $builder = $this->getMockBuilder('\core_search\base_activity');
114
        $builder->disableOriginalConstructor();
115
        $stub = $builder->getMockForAbstractClass();
116
 
117
        $result = $stub->get_search_fileareas();
118
 
119
        $this->assertEquals(array('intro'), $result);
120
    }
121
 
122
    /**
123
     * Test base attach files
124
     */
125
    public function test_attach_files_base() {
126
        $filearea = 'intro';
127
        $component = 'mod_forum';
128
        $module = 'forum';
129
 
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;
134
 
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);
146
 
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);
152
 
153
        $document = $area->get_document($record);
154
        $document->set('itemid', $activity->id);
155
 
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);
164
 
165
        // Attach file to our test document.
166
        $stub->attach_files($document);
167
 
168
        // Verify file is attached.
169
        $files = $document->get_files();
170
        $file = array_values($files)[0];
171
 
172
        $this->assertEquals(1, count($files));
173
        $this->assertEquals($content, $file->get_content());
174
    }
175
 
176
    /**
177
     * Tests getting the recordset.
178
     */
179
    public function test_get_document_recordset() {
180
        global $USER, $DB;
181
 
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());
185
 
186
        // Should return all forums.
187
        $this->assertCount(4, $results);
188
 
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);
197
 
198
        $allids = self::records_to_ids($this->forums);
199
        $this->assertEquals($allids, self::records_to_ids($results));
200
 
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));
205
 
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));
210
 
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));
216
 
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));
221
 
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));
226
 
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));
232
 
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));
237
 
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));
243
 
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);
247
 
248
        // Block context (cannot return anything, so always null).
249
        $this->assertNull($area->get_document_recordset(0, $blockcontext));
250
 
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));
254
    }
255
 
256
    /**
257
     * Utility function to convert recordset to array for testing.
258
     *
259
     * @param \moodle_recordset $rs Recordset to convert
260
     * @return array Array indexed by number (0, 1, 2, ...)
261
     */
262
    protected static function recordset_to_indexed_array(\moodle_recordset $rs) {
263
        $results = [];
264
        foreach ($rs as $rec) {
265
            $results[] = $rec;
266
        }
267
        $rs->close();
268
        return $results;
269
    }
270
 
271
    /**
272
     * Utility function to convert records to array of IDs.
273
     *
274
     * @param array $recs Records which should have an 'id' field
275
     * @return array Array of ids
276
     */
277
    protected static function records_to_ids(array $recs) {
278
        $ids = [];
279
        foreach ($recs as $rec) {
280
            $ids[] = $rec->id;
281
        }
282
        return $ids;
283
    }
284
 
285
    /**
286
     * Tests the get_doc_url function.
287
     */
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());
291
 
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])));
296
        }
297
    }
298
 
299
    /**
300
     * Tests the check_access function.
301
     */
302
    public function test_check_access() {
303
        global $CFG;
304
        require_once($CFG->dirroot . '/course/lib.php');
305
 
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);
312
 
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);
316
 
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));
325
 
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.)
330
    }
331
 
332
    /**
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.
335
     */
336
    public function test_get_contexts_to_reindex() {
337
        global $DB;
338
 
339
        $this->resetAfterTest();
340
 
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']);
347
 
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]);
353
 
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);
359
 
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);
364
 
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);
369
    }
370
 
371
    /**
372
     * Test document icon.
373
     */
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();
379
 
380
        $baseactivity->method('get_module_name')->willReturn('test_activity');
381
 
382
        $document = $this->getMockBuilder('\core_search\document')
383
            ->disableOriginalConstructor()
384
            ->getMock();
385
 
386
        $result = $baseactivity->get_doc_icon($document);
387
 
388
        $this->assertEquals('monologo', $result->get_name());
389
        $this->assertEquals('test_activity', $result->get_component());
390
    }
391
}