46dd4a6cb2d9812dd2a8bbce5898b25e0f533649
[moodle.git] / search / engine / simpledb / tests / engine_test.php
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/>.
17 /**
18  * Simple db search engine tests.
19  *
20  * @package     search_simpledb
21  * @category    test
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  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
30 require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
32 /**
33  * Simple search engine base unit tests.
34  *
35  * @package     search_simpledb
36  * @category    test
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  */
40 class search_simpledb_engine_testcase extends advanced_testcase {
42     /**
43      * @var \core_search::manager
44      */
45     protected $search = null;
47     /**
48      * @var \
49      */
50     protected $engine = null;
52     /**
53      * @var core_search_generator
54      */
55     protected $generator = null;
57     /**
58      * Initial stuff.
59      *
60      * @return void
61      */
62     public function setUp() {
63         $this->resetAfterTest();
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         }
70         set_config('enableglobalsearch', true);
72         // Inject search_simpledb engine into the testable core search as we need to add the mock
73         // search component to it.
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());
80         $this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
81         $this->generator->setup();
83         $this->setAdminUser();
84     }
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         }
98     }
100     /**
101      * Test indexing process.
102      *
103      * @return void
104      */
105     public function test_index() {
106         global $DB;
108         $record = new \stdClass();
109         $record->timemodified = time() - 1;
110         $this->generator->create_record($record);
112         // Data gets into the search engine.
113         $this->assertTrue($this->search->index());
115         // Not anymore as everything was already added.
116         sleep(1);
117         $this->assertFalse($this->search->index());
119         $this->generator->create_record();
121         // Indexing again once there is new data.
122         $this->assertTrue($this->search->index());
123     }
125     /**
126      * Test search filters.
127      *
128      * @return void
129      */
130     public function test_search() {
131         global $USER, $DB;
133         $this->generator->create_record();
134         $record = new \stdClass();
135         $record->title = "Special title";
136         $this->generator->create_record($record);
138         $this->search->index();
139         $this->update_index();
141         $querydata = new stdClass();
142         $querydata->q = 'message';
143         $results = $this->search->search($querydata);
144         $this->assertCount(2, $results);
146         // Based on core_mocksearch\search\indexer.
147         $this->assertEquals($USER->id, $results[0]->get('userid'));
148         $this->assertEquals(\context_course::instance(SITEID)->id, $results[0]->get('contextid'));
150         // Do a test to make sure we aren't searching non-query fields, like areaid.
151         $querydata->q = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
152         $this->assertCount(0, $this->search->search($querydata));
153         $querydata->q = 'message';
155         sleep(1);
156         $beforeadding = time();
157         sleep(1);
158         $this->generator->create_record();
159         $this->search->index();
160         $this->update_index();
162         // Timestart.
163         $querydata->timestart = $beforeadding;
164         $this->assertCount(1, $this->search->search($querydata));
166         // Timeend.
167         unset($querydata->timestart);
168         $querydata->timeend = $beforeadding;
169         $this->assertCount(2, $this->search->search($querydata));
171         // Title.
172         unset($querydata->timeend);
173         $querydata->title = 'Special title';
174         $this->assertCount(1, $this->search->search($querydata));
176         // Course IDs.
177         unset($querydata->title);
178         $querydata->courseids = array(SITEID + 1);
179         $this->assertCount(0, $this->search->search($querydata));
181         $querydata->courseids = array(SITEID);
182         $this->assertCount(3, $this->search->search($querydata));
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');
189         $querydata->areaids = array($forumpostareaid);
190         $this->assertCount(0, $this->search->search($querydata));
192         $querydata->areaids = array($forumpostareaid, $mockareaid);
193         $this->assertCount(3, $this->search->search($querydata));
195         $querydata->areaids = array($mockareaid);
196         $this->assertCount(3, $this->search->search($querydata));
198         $querydata->areaids = array();
199         $this->assertCount(3, $this->search->search($querydata));
201         // Check that index contents get updated.
202         $this->generator->delete_all();
203         $this->search->index(true);
204         $this->update_index();
205         unset($querydata->title);
206         $querydata->q = '';
207         $this->assertCount(0, $this->search->search($querydata));
208     }
210     /**
211      * Test delete function
212      *
213      * @return void
214      */
215     public function test_delete() {
217         $this->generator->create_record();
218         $this->generator->create_record();
219         $this->search->index();
220         $this->update_index();
222         $querydata = new stdClass();
223         $querydata->q = 'message';
225         $this->assertCount(2, $this->search->search($querydata));
227         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
228         $this->search->delete_index($areaid);
229         $this->update_index();
230         $this->assertCount(0, $this->search->search($querydata));
231     }
233     /**
234      * Test user is allowed.
235      *
236      * @return void
237      */
238     public function test_alloweduserid() {
240         $area = new core_mocksearch\search\mock_search_area();
242         $record = $this->generator->create_record();
244         // Get the doc and insert the default doc.
245         $doc = $area->get_document($record);
246         $this->engine->add_document($doc);
248         $users = array();
249         $users[] = $this->getDataGenerator()->create_user();
250         $users[] = $this->getDataGenerator()->create_user();
251         $users[] = $this->getDataGenerator()->create_user();
253         // Add a record that only user 100 can see.
254         $originalid = $doc->get('id');
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);
261             $this->engine->add_document($doc);
262         }
263         $this->update_index();
265         $this->engine->area_index_complete($area->get_area_id());
267         $querydata = new stdClass();
268         $querydata->q = 'message';
269         $querydata->title = $doc->get('title');
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);
275             $results = $this->search->search($querydata);
276             $this->assertCount(2, $results);
278             $owned = 0;
279             $notowned = 0;
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             }
295             $this->assertEquals(1, $owned);
296             $this->assertEquals(1, $notowned);
297         }
299         // Now test a user with no owned results.
300         $otheruser = $this->getDataGenerator()->create_user();
301         $this->setUser($otheruser);
303         $results = $this->search->search($querydata);
304         $this->assertCount(1, $results);
306         $this->assertEquals(0, $results[0]->get('owneruserid'));
307         $this->assertEquals($originalid, $results[0]->get('id'));
308     }
310     public function test_delete_by_id() {
312         $this->generator->create_record();
313         $this->generator->create_record();
314         $this->search->index();
315         $this->update_index();
317         $querydata = new stdClass();
319         // Then search to make sure they are there.
320         $querydata->q = 'message';
321         $results = $this->search->search($querydata);
322         $this->assertCount(2, $results);
324         $first = reset($results);
325         $deleteid = $first->get('id');
327         $this->engine->delete_by_id($deleteid);
328         $this->update_index();
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     }
337     /**
338      * Updates mssql fulltext index if necessary.
339      *
340      * @return bool
341      */
342     private function update_index() {
343         global $DB;
345         if (!$this->requires_manual_index_update()) {
346             return;
347         }
349         $DB->execute("ALTER FULLTEXT INDEX ON t_search_simpledb_index START UPDATE POPULATION");
351         $catalogname = $DB->get_prefix() . 'search_simpledb_catalog';
352         $retries = 0;
353         do {
354             // 0.2 seconds.
355             usleep(200000);
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++;
362         } while ($retries < 100 && $record->populatestatus != '0');
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     }
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     }