MDL-53508 search: Improve highlighting and move to text fields
[moodle.git] / search / engine / solr / 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  * Solr earch engine base unit tests.
19  *
20  * Required params:
21  * - define('TEST_SEARCH_SOLR_HOSTNAME', '127.0.0.1');
22  * - define('TEST_SEARCH_SOLR_PORT', '8983');
23  * - define('TEST_SEARCH_SOLR_INDEXNAME', 'unittest');
24  *
25  * Optional params:
26  * - define('TEST_SEARCH_SOLR_USERNAME', '');
27  * - define('TEST_SEARCH_SOLR_PASSWORD', '');
28  *
29  * @package     core_search
30  * @category    phpunit
31  * @copyright   2015 David Monllao {@link http://www.davidmonllao.com}
32  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
35 defined('MOODLE_INTERNAL') || die();
37 global $CFG;
38 require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
39 require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
41 /**
42  * Solr search engine base unit tests.
43  *
44  * @package     core_search
45  * @category    phpunit
46  * @copyright   2015 David Monllao {@link http://www.davidmonllao.com}
47  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48  */
49 class search_solr_engine_testcase extends advanced_testcase {
51     /**
52      * @var \core_search::manager
53      */
54     protected $search = null;
56     public function setUp() {
57         $this->resetAfterTest();
58         set_config('enableglobalsearch', true);
60         if (!function_exists('solr_get_version')) {
61             $this->markTestSkipped('Solr extension is not loaded.');
62         }
64         if (!defined('TEST_SEARCH_SOLR_HOSTNAME') || !defined('TEST_SEARCH_SOLR_INDEXNAME') ||
65                 !defined('TEST_SEARCH_SOLR_PORT')) {
66             $this->markTestSkipped('Solr extension test server not set.');
67         }
69         set_config('hostname', TEST_SEARCH_SOLR_HOSTNAME, 'search_solr');
70         set_config('port', TEST_SEARCH_SOLR_PORT, 'search_solr');
71         set_config('indexname', TEST_SEARCH_SOLR_INDEXNAME, 'search_solr');
73         if (defined('TEST_SEARCH_SOLR_USERNAME')) {
74             set_config('server_username', TEST_SEARCH_SOLR_USERNAME);
75         }
77         if (defined('TEST_SEARCH_SOLR_PASSWORD')) {
78             set_config('server_password', TEST_SEARCH_SOLR_PASSWORD);
79         }
81         // Inject search solr engine into the testable core search as we need to add the mock
82         // search component to it.
83         $searchengine = new \search_solr\engine();
84         $this->search = testable_core_search::instance($searchengine);
85         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
86         $this->search->add_search_area($areaid, new core_mocksearch\search\role_capabilities());
88         $this->setAdminUser();
90         // Cleanup before doing anything on it as the index it is out of this test control.
91         $this->search->delete_index();
93         // Add moodle fields if they don't exist.
94         $schema = new \search_solr\schema();
95         $schema->setup(false);
96     }
98     public function test_connection() {
99         $this->assertTrue($this->search->get_engine()->is_server_ready());
100     }
102     public function test_index() {
103         global $DB;
105         $noneditingteacherid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
107         // Data gets into the search engine.
108         $this->assertTrue($this->search->index());
110         // Not anymore as everything was already added.
111         sleep(1);
112         $this->assertFalse($this->search->index());
114         assign_capability('moodle/course:renameroles', CAP_ALLOW, $noneditingteacherid, context_system::instance()->id);
115         accesslib_clear_all_caches_for_unit_testing();
117         // Indexing again once there is new data.
118         $this->assertTrue($this->search->index());
119     }
121     /**
122      * Better keep this not very strict about which or how many results are returned as may depend on solr engine config.
123      *
124      * @return void
125      */
126     public function test_search() {
127         global $USER, $DB;
129         $noneditingteacherid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
131         $this->search->index();
133         $querydata = new stdClass();
134         $querydata->q = 'message';
135         $results = $this->search->search($querydata);
136         $this->assertCount(2, $results);
138         // Based on core_mocksearch\search\indexer.
139         $this->assertEquals($USER->id, $results[0]->get('userid'));
140         $this->assertEquals(\context_system::instance()->id, $results[0]->get('contextid'));
142         // Testing filters we don't purge cache in between assertions because cache key depends on the whole filters set
143         // and they are different.
144         sleep(1);
145         $beforeadding = time();
146         sleep(1);
147         assign_capability('moodle/course:renameroles', CAP_ALLOW, $noneditingteacherid, context_system::instance()->id);
148         accesslib_clear_all_caches_for_unit_testing();
149         $this->search->index();
151         // Timestart.
152         $querydata->timestart = $beforeadding;
153         $this->assertCount(1, $this->search->search($querydata));
155         // Timeend.
156         unset($querydata->timestart);
157         $querydata->timeend = $beforeadding;
158         $this->assertCount(2, $this->search->search($querydata));
160         // Title.
161         unset($querydata->timeend);
162         $querydata->title = 'moodle/course:renameroles roleid 1';
163         $this->assertCount(1, $this->search->search($querydata));
164     }
166     public function test_delete() {
167         $this->search->index();
169         $querydata = new stdClass();
170         $querydata->q = 'message';
172         $this->assertCount(2, $this->search->search($querydata));
174         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
175         $this->search->delete_index($areaid);
176         cache_helper::purge_by_definition('core', 'search_results');
177         $this->assertCount(0, $this->search->search($querydata));
178     }
180     public function test_alloweduserid() {
181         $engine = $this->search->get_engine();
182         $area = new core_mocksearch\search\role_capabilities();
184         // Get the first record for the recordset.
185         $recordset = $area->get_recordset_by_timestamp();
186         foreach ($recordset as $r) {
187             $record = $r;
188             break;
189         }
190         $recordset->close();
192         // Get the doc and insert the default doc.
193         $doc = $area->get_document($record);
194         $engine->add_document($doc->export_for_engine());
196         $users = array();
197         $users[] = $this->getDataGenerator()->create_user();
198         $users[] = $this->getDataGenerator()->create_user();
199         $users[] = $this->getDataGenerator()->create_user();
201         // Add a record that only user 100 can see.
202         $originalid = $doc->get('id');
204         // Now add a custom doc for each user.
205         foreach ($users as $user) {
206             $doc->set('id', $originalid.'-'.$user->id);
207             $doc->set('owneruserid', $user->id);
208             $engine->add_document($doc->export_for_engine());
209         }
211         $engine->area_index_complete($area->get_area_id());
213         $querydata = new stdClass();
214         $querydata->q = 'message';
215         $querydata->title = $doc->get('title');
217         // We are going to go through each user and see if they get the original and the owned doc.
218         foreach ($users as $user) {
219             $this->setUser($user);
221             $results = $this->search->search($querydata);
222             $this->assertCount(2, $results);
224             $owned = 0;
225             $notowned = 0;
227             // We don't know what order we will get the results in, so we are doing this.
228             foreach ($results as $result) {
229                 $owneruserid = $result->get('owneruserid');
230                 if (empty($owneruserid)) {
231                     $notowned++;
232                     $this->assertEquals(0, $owneruserid);
233                     $this->assertEquals($originalid, $result->get('id'));
234                 } else {
235                     $owned++;
236                     $this->assertEquals($user->id, $owneruserid);
237                     $this->assertEquals($originalid.'-'.$user->id, $result->get('id'));
238                 }
239             }
241             $this->assertEquals(1, $owned);
242             $this->assertEquals(1, $notowned);
243         }
245         // Now test a user with no owned results.
246         $otheruser = $this->getDataGenerator()->create_user();
247         $this->setUser($otheruser);
249         $results = $this->search->search($querydata);
250         $this->assertCount(1, $results);
252         $this->assertEquals(0, $results[0]->get('owneruserid'));
253         $this->assertEquals($originalid, $results[0]->get('id'));
254     }
256     public function test_highlight() {
257         global $PAGE;
259         $this->search->index();
261         $querydata = new stdClass();
262         $querydata->q = 'message';
264         $results = $this->search->search($querydata);
265         $this->assertCount(2, $results);
267         $result = reset($results);
269         $regex = '|'.\search_solr\engine::HIGHLIGHT_START.'message'.\search_solr\engine::HIGHLIGHT_END.'|';
270         $this->assertRegExp($regex, $result->get('content'));
272         $searchrenderer = $PAGE->get_renderer('core_search');
273         $exported = $result->export_for_template($searchrenderer);
275         $regex = '|<span class="highlight">message</span>|';
276         $this->assertRegExp($regex, $exported['content']);
277     }