MDL-53226 search_simpledb: Fix table name in query
[moodle.git] / search / engine / simpledb / tests / engine_test.php
CommitLineData
2ee2f530
DM
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 * Simple db search engine tests.
19 *
20 * @package search_simpledb
c2e97077 21 * @category test
2ee2f530
DM
22 * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
30require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
31
32/**
33 * Simple search engine base unit tests.
34 *
35 * @package search_simpledb
c2e97077 36 * @category test
2ee2f530
DM
37 * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class search_simpledb_engine_testcase extends advanced_testcase {
41
42 /**
43 * @var \core_search::manager
44 */
45 protected $search = null;
46
c2e97077
DM
47 /**
48 * @var \
49 */
50 protected $engine = null;
51
52 /**
53 * @var core_search_generator
54 */
55 protected $generator = null;
56
57 /**
58 * Initial stuff.
59 *
60 * @return void
61 */
2ee2f530
DM
62 public function setUp() {
63 $this->resetAfterTest();
c2e97077
DM
64
65 if ($this->requires_manual_index_update()) {
66 // We need to update fulltext index manually, which requires an alter table statement.
67 $this->preventResetByRollback();
68 }
69
2ee2f530
DM
70 set_config('enableglobalsearch', true);
71
72 // Inject search_simpledb engine into the testable core search as we need to add the mock
73 // search component to it.
c2e97077
DM
74
75 $this->engine = new \search_simpledb\engine();
76 $this->search = testable_core_search::instance($this->engine);
77 $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
78 $this->search->add_search_area($areaid, new core_mocksearch\search\mock_search_area());
79
80 $this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
81 $this->generator->setup();
82
83 $this->setAdminUser();
84 }
85
86 /**
87 * tearDown
88 *
89 * @return void
90 */
91 public function tearDown() {
92 // For unit tests before PHP 7, teardown is called even on skip. So only do our teardown if we did setup.
93 if ($this->generator) {
94 // Moodle DML freaks out if we don't teardown the temp table after each run.
95 $this->generator->teardown();
96 $this->generator = null;
97 }
2ee2f530
DM
98 }
99
c2e97077
DM
100 /**
101 * Test indexing process.
102 *
103 * @return void
104 */
2ee2f530
DM
105 public function test_index() {
106 global $DB;
107
c2e97077
DM
108 $record = new \stdClass();
109 $record->timemodified = time() - 1;
110 $this->generator->create_record($record);
2ee2f530
DM
111
112 // Data gets into the search engine.
113 $this->assertTrue($this->search->index());
114
115 // Not anymore as everything was already added.
116 sleep(1);
117 $this->assertFalse($this->search->index());
118
c2e97077 119 $this->generator->create_record();
2ee2f530
DM
120
121 // Indexing again once there is new data.
122 $this->assertTrue($this->search->index());
123 }
124
125 /**
126 * Test search filters.
127 *
128 * @return void
129 */
130 public function test_search() {
131 global $USER, $DB;
132
c2e97077
DM
133 $this->generator->create_record();
134 $record = new \stdClass();
135 $record->title = "Special title";
136 $this->generator->create_record($record);
2ee2f530
DM
137
138 $this->search->index();
c2e97077 139 $this->update_index();
2ee2f530
DM
140
141 $querydata = new stdClass();
142 $querydata->q = 'message';
143 $results = $this->search->search($querydata);
144 $this->assertCount(2, $results);
145
146 // Based on core_mocksearch\search\indexer.
147 $this->assertEquals($USER->id, $results[0]->get('userid'));
c2e97077 148 $this->assertEquals(\context_course::instance(SITEID)->id, $results[0]->get('contextid'));
2ee2f530
DM
149
150 // Do a test to make sure we aren't searching non-query fields, like areaid.
c2e97077 151 $querydata->q = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
2ee2f530
DM
152 $this->assertCount(0, $this->search->search($querydata));
153 $querydata->q = 'message';
154
155 sleep(1);
156 $beforeadding = time();
157 sleep(1);
c2e97077 158 $this->generator->create_record();
2ee2f530 159 $this->search->index();
c2e97077 160 $this->update_index();
2ee2f530
DM
161
162 // Timestart.
163 $querydata->timestart = $beforeadding;
c2e97077 164 $this->assertCount(1, $this->search->search($querydata));
2ee2f530
DM
165
166 // Timeend.
167 unset($querydata->timestart);
168 $querydata->timeend = $beforeadding;
c2e97077 169 $this->assertCount(2, $this->search->search($querydata));
2ee2f530
DM
170
171 // Title.
172 unset($querydata->timeend);
c2e97077 173 $querydata->title = 'Special title';
2ee2f530
DM
174 $this->assertCount(1, $this->search->search($querydata));
175
176 // Course IDs.
177 unset($querydata->title);
178 $querydata->courseids = array(SITEID + 1);
179 $this->assertCount(0, $this->search->search($querydata));
180
181 $querydata->courseids = array(SITEID);
182 $this->assertCount(3, $this->search->search($querydata));
183
c2e97077
DM
184 // Now try some area-id combinations.
185 unset($querydata->courseids);
186 $forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
187 $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
188
189 $querydata->areaids = array($forumpostareaid);
190 $this->assertCount(0, $this->search->search($querydata));
191
192 $querydata->areaids = array($forumpostareaid, $mockareaid);
193 $this->assertCount(3, $this->search->search($querydata));
194
195 $querydata->areaids = array($mockareaid);
196 $this->assertCount(3, $this->search->search($querydata));
197
198 $querydata->areaids = array();
199 $this->assertCount(3, $this->search->search($querydata));
200
2ee2f530 201 // Check that index contents get updated.
c2e97077 202 $this->generator->delete_all();
2ee2f530 203 $this->search->index(true);
c2e97077 204 $this->update_index();
2ee2f530 205 unset($querydata->title);
c2e97077 206 $querydata->q = '';
2ee2f530
DM
207 $this->assertCount(0, $this->search->search($querydata));
208 }
209
c2e97077
DM
210 /**
211 * Test delete function
212 *
213 * @return void
214 */
2ee2f530 215 public function test_delete() {
c2e97077
DM
216
217 $this->generator->create_record();
218 $this->generator->create_record();
2ee2f530 219 $this->search->index();
c2e97077 220 $this->update_index();
2ee2f530
DM
221
222 $querydata = new stdClass();
223 $querydata->q = 'message';
224
225 $this->assertCount(2, $this->search->search($querydata));
226
c2e97077 227 $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
2ee2f530 228 $this->search->delete_index($areaid);
c2e97077 229 $this->update_index();
2ee2f530
DM
230 $this->assertCount(0, $this->search->search($querydata));
231 }
232
c2e97077
DM
233 /**
234 * Test user is allowed.
235 *
236 * @return void
237 */
2ee2f530 238 public function test_alloweduserid() {
c2e97077
DM
239
240 $area = new core_mocksearch\search\mock_search_area();
241
242 $record = $this->generator->create_record();
2ee2f530
DM
243
244 // Get the doc and insert the default doc.
245 $doc = $area->get_document($record);
c2e97077 246 $this->engine->add_document($doc);
2ee2f530
DM
247
248 $users = array();
249 $users[] = $this->getDataGenerator()->create_user();
250 $users[] = $this->getDataGenerator()->create_user();
251 $users[] = $this->getDataGenerator()->create_user();
252
253 // Add a record that only user 100 can see.
254 $originalid = $doc->get('id');
255
256 // Now add a custom doc for each user.
257 foreach ($users as $user) {
258 $doc = $area->get_document($record);
259 $doc->set('id', $originalid.'-'.$user->id);
260 $doc->set('owneruserid', $user->id);
c2e97077 261 $this->engine->add_document($doc);
2ee2f530 262 }
c2e97077 263 $this->update_index();
2ee2f530 264
c2e97077 265 $this->engine->area_index_complete($area->get_area_id());
2ee2f530
DM
266
267 $querydata = new stdClass();
268 $querydata->q = 'message';
269 $querydata->title = $doc->get('title');
270
271 // We are going to go through each user and see if they get the original and the owned doc.
272 foreach ($users as $user) {
273 $this->setUser($user);
274
275 $results = $this->search->search($querydata);
276 $this->assertCount(2, $results);
277
278 $owned = 0;
279 $notowned = 0;
280
281 // We don't know what order we will get the results in, so we are doing this.
282 foreach ($results as $result) {
283 $owneruserid = $result->get('owneruserid');
284 if (empty($owneruserid)) {
285 $notowned++;
286 $this->assertEquals(0, $owneruserid);
287 $this->assertEquals($originalid, $result->get('id'));
288 } else {
289 $owned++;
290 $this->assertEquals($user->id, $owneruserid);
291 $this->assertEquals($originalid.'-'.$user->id, $result->get('id'));
292 }
293 }
294
295 $this->assertEquals(1, $owned);
296 $this->assertEquals(1, $notowned);
297 }
298
299 // Now test a user with no owned results.
300 $otheruser = $this->getDataGenerator()->create_user();
301 $this->setUser($otheruser);
302
303 $results = $this->search->search($querydata);
304 $this->assertCount(1, $results);
305
306 $this->assertEquals(0, $results[0]->get('owneruserid'));
307 $this->assertEquals($originalid, $results[0]->get('id'));
308 }
309
310 public function test_delete_by_id() {
c2e97077
DM
311
312 $this->generator->create_record();
313 $this->generator->create_record();
2ee2f530 314 $this->search->index();
c2e97077 315 $this->update_index();
2ee2f530
DM
316
317 $querydata = new stdClass();
318
319 // Then search to make sure they are there.
c2e97077 320 $querydata->q = 'message';
2ee2f530
DM
321 $results = $this->search->search($querydata);
322 $this->assertCount(2, $results);
323
324 $first = reset($results);
325 $deleteid = $first->get('id');
326
c2e97077
DM
327 $this->engine->delete_by_id($deleteid);
328 $this->update_index();
2ee2f530
DM
329
330 // Check that we don't get a result for it anymore.
331 $results = $this->search->search($querydata);
332 $this->assertCount(1, $results);
333 $result = reset($results);
334 $this->assertNotEquals($deleteid, $result->get('id'));
335 }
c2e97077
DM
336
337 /**
338 * Updates mssql fulltext index if necessary.
339 *
340 * @return bool
341 */
342 private function update_index() {
343 global $DB;
344
345 if (!$this->requires_manual_index_update()) {
346 return;
347 }
348
bc178fa0 349 $DB->execute("ALTER FULLTEXT INDEX ON {search_simpledb_index} START UPDATE POPULATION");
c2e97077
DM
350
351 $catalogname = $DB->get_prefix() . 'search_simpledb_catalog';
352 $retries = 0;
353 do {
354 // 0.2 seconds.
355 usleep(200000);
356
357 $record = $DB->get_record_sql("SELECT FULLTEXTCATALOGPROPERTY(cat.name, 'PopulateStatus') AS [PopulateStatus]
358 FROM sys.fulltext_catalogs AS cat
359 WHERE cat.name = ?", array($catalogname));
360 $retries++;
361
362 } while ($retries < 100 && $record->populatestatus != '0');
363
364 if ($retries === 100) {
365 // No update after 20 seconds...
366 $this->fail('Sorry, your SQL server fulltext search index is too slow.');
367 }
368 }
369
370 /**
371 * Mssql with fulltext support requires manual updates.
372 *
373 * @return bool
374 */
375 private function requires_manual_index_update() {
376 global $DB;
377 return ($DB->get_dbfamily() === 'mssql' && $DB->is_fulltext_search_supported());
378 }
2ee2f530 379}