MDL-55725 search: Index proper time modified with indexed files
[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  * - define('TEST_SEARCH_SOLR_SSLCERT', '');
29  * - define('TEST_SEARCH_SOLR_SSLKEY', '');
30  * - define('TEST_SEARCH_SOLR_KEYPASSWORD', '');
31  * - define('TEST_SEARCH_SOLR_CAINFOCERT', '');
32  *
33  * @package     core_search
34  * @category    phpunit
35  * @copyright   2015 David Monllao {@link http://www.davidmonllao.com}
36  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
39 defined('MOODLE_INTERNAL') || die();
41 global $CFG;
42 require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
43 require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
44 require_once($CFG->dirroot . '/search/engine/solr/tests/fixtures/testable_engine.php');
46 /**
47  * Solr search engine base unit tests.
48  *
49  * @package     core_search
50  * @category    phpunit
51  * @copyright   2015 David Monllao {@link http://www.davidmonllao.com}
52  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
53  */
54 class search_solr_engine_testcase extends advanced_testcase {
56     /**
57      * @var \core_search::manager
58      */
59     protected $search = null;
61     /**
62      * @var Instace of core_search_generator.
63      */
64     protected $generator = null;
66     /**
67      * @var Instace of testable_engine.
68      */
69     protected $engine = null;
71     public function setUp() {
72         $this->resetAfterTest();
73         set_config('enableglobalsearch', true);
75         if (!function_exists('solr_get_version')) {
76             $this->markTestSkipped('Solr extension is not loaded.');
77         }
79         if (!defined('TEST_SEARCH_SOLR_HOSTNAME') || !defined('TEST_SEARCH_SOLR_INDEXNAME') ||
80                 !defined('TEST_SEARCH_SOLR_PORT')) {
81             $this->markTestSkipped('Solr extension test server not set.');
82         }
84         set_config('server_hostname', TEST_SEARCH_SOLR_HOSTNAME, 'search_solr');
85         set_config('server_port', TEST_SEARCH_SOLR_PORT, 'search_solr');
86         set_config('indexname', TEST_SEARCH_SOLR_INDEXNAME, 'search_solr');
88         if (defined('TEST_SEARCH_SOLR_USERNAME')) {
89             set_config('server_username', TEST_SEARCH_SOLR_USERNAME, 'search_solr');
90         }
92         if (defined('TEST_SEARCH_SOLR_PASSWORD')) {
93             set_config('server_password', TEST_SEARCH_SOLR_PASSWORD, 'search_solr');
94         }
96         if (defined('TEST_SEARCH_SOLR_SSLCERT')) {
97             set_config('secure', true, 'search_solr');
98             set_config('ssl_cert', TEST_SEARCH_SOLR_SSLCERT, 'search_solr');
99         }
101         if (defined('TEST_SEARCH_SOLR_SSLKEY')) {
102             set_config('ssl_key', TEST_SEARCH_SOLR_SSLKEY, 'search_solr');
103         }
105         if (defined('TEST_SEARCH_SOLR_KEYPASSWORD')) {
106             set_config('ssl_keypassword', TEST_SEARCH_SOLR_KEYPASSWORD, 'search_solr');
107         }
109         if (defined('TEST_SEARCH_SOLR_CAINFOCERT')) {
110             set_config('ssl_cainfo', TEST_SEARCH_SOLR_CAINFOCERT, 'search_solr');
111         }
113         set_config('fileindexing', 1, 'search_solr');
115         // We are only test indexing small string files, so setting this as low as we can.
116         set_config('maxindexfilekb', 1, 'search_solr');
118         $this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
119         $this->generator->setup();
121         // Inject search solr engine into the testable core search as we need to add the mock
122         // search component to it.
123         $this->engine = new \search_solr\testable_engine();
124         $this->search = testable_core_search::instance($this->engine);
125         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
126         $this->search->add_search_area($areaid, new core_mocksearch\search\mock_search_area());
128         $this->setAdminUser();
130         // Cleanup before doing anything on it as the index it is out of this test control.
131         $this->search->delete_index();
133         // Add moodle fields if they don't exist.
134         $schema = new \search_solr\schema();
135         $schema->setup(false);
136     }
138     public function tearDown() {
139         // For unit tests before PHP 7, teardown is called even on skip. So only do our teardown if we did setup.
140         if ($this->generator) {
141             // Moodle DML freaks out if we don't teardown the temp table after each run.
142             $this->generator->teardown();
143             $this->generator = null;
144         }
145     }
147     /**
148      * Simple data provider to allow tests to be run with file indexing on and off.
149      */
150     public function file_indexing_provider() {
151         return array(
152             'file-indexing-on' => array(1),
153             'file-indexing-off' => array(0)
154         );
155     }
157     public function test_connection() {
158         $this->assertTrue($this->engine->is_server_ready());
159     }
161     /**
162      * @dataProvider file_indexing_provider
163      */
164     public function test_index($fileindexing) {
165         global $DB;
167         $this->engine->test_set_config('fileindexing', $fileindexing);
169         $record = new \stdClass();
170         $record->timemodified = time() - 1;
171         $this->generator->create_record($record);
173         // Data gets into the search engine.
174         $this->assertTrue($this->search->index());
176         // Not anymore as everything was already added.
177         sleep(1);
178         $this->assertFalse($this->search->index());
180         $this->generator->create_record();
182         // Indexing again once there is new data.
183         $this->assertTrue($this->search->index());
184     }
186     /**
187      * Better keep this not very strict about which or how many results are returned as may depend on solr engine config.
188      *
189      * @dataProvider file_indexing_provider
190      *
191      * @return void
192      */
193     public function test_search($fileindexing) {
194         global $USER, $DB;
196         $this->engine->test_set_config('fileindexing', $fileindexing);
198         $this->generator->create_record();
199         $record = new \stdClass();
200         $record->title = "Special title";
201         $this->generator->create_record($record);
203         $this->search->index();
205         $querydata = new stdClass();
206         $querydata->q = 'message';
207         $results = $this->search->search($querydata);
208         $this->assertCount(2, $results);
210         // Based on core_mocksearch\search\indexer.
211         $this->assertEquals($USER->id, $results[0]->get('userid'));
212         $this->assertEquals(\context_system::instance()->id, $results[0]->get('contextid'));
214         // Do a test to make sure we aren't searching non-query fields, like areaid.
215         $querydata->q = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
216         $this->assertCount(0, $this->search->search($querydata));
217         $querydata->q = 'message';
219         sleep(1);
220         $beforeadding = time();
221         sleep(1);
222         $this->generator->create_record();
223         $this->search->index();
225         // Timestart.
226         $querydata->timestart = $beforeadding;
227         $this->assertCount(1, $this->search->search($querydata));
229         // Timeend.
230         unset($querydata->timestart);
231         $querydata->timeend = $beforeadding;
232         $this->assertCount(2, $this->search->search($querydata));
234         // Title.
235         unset($querydata->timeend);
236         $querydata->title = 'Special title';
237         $this->assertCount(1, $this->search->search($querydata));
239         // Course IDs.
240         unset($querydata->title);
241         $querydata->courseids = array(SITEID + 1);
242         $this->assertCount(0, $this->search->search($querydata));
244         $querydata->courseids = array(SITEID);
245         $this->assertCount(3, $this->search->search($querydata));
247         // Now try some area-id combinations.
248         unset($querydata->courseids);
249         $forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
250         $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
252         $querydata->areaids = array($forumpostareaid);
253         $this->assertCount(0, $this->search->search($querydata));
255         $querydata->areaids = array($forumpostareaid, $mockareaid);
256         $this->assertCount(3, $this->search->search($querydata));
258         $querydata->areaids = array($mockareaid);
259         $this->assertCount(3, $this->search->search($querydata));
261         $querydata->areaids = array();
262         $this->assertCount(3, $this->search->search($querydata));
264         // Check that index contents get updated.
265         $this->generator->delete_all();
266         $this->search->index(true);
267         unset($querydata->title);
268         $querydata->q = '*';
269         $this->assertCount(0, $this->search->search($querydata));
270     }
272     /**
273      * @dataProvider file_indexing_provider
274      */
275     public function test_delete($fileindexing) {
276         $this->engine->test_set_config('fileindexing', $fileindexing);
278         $this->generator->create_record();
279         $this->generator->create_record();
280         $this->search->index();
282         $querydata = new stdClass();
283         $querydata->q = 'message';
285         $this->assertCount(2, $this->search->search($querydata));
287         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
288         $this->search->delete_index($areaid);
289         $this->assertCount(0, $this->search->search($querydata));
290     }
292     /**
293      * @dataProvider file_indexing_provider
294      */
295     public function test_alloweduserid($fileindexing) {
296         $this->engine->test_set_config('fileindexing', $fileindexing);
298         $area = new core_mocksearch\search\mock_search_area();
300         $record = $this->generator->create_record();
302         // Get the doc and insert the default doc.
303         $doc = $area->get_document($record);
304         $this->engine->add_document($doc);
306         $users = array();
307         $users[] = $this->getDataGenerator()->create_user();
308         $users[] = $this->getDataGenerator()->create_user();
309         $users[] = $this->getDataGenerator()->create_user();
311         // Add a record that only user 100 can see.
312         $originalid = $doc->get('id');
314         // Now add a custom doc for each user.
315         foreach ($users as $user) {
316             $doc = $area->get_document($record);
317             $doc->set('id', $originalid.'-'.$user->id);
318             $doc->set('owneruserid', $user->id);
319             $this->engine->add_document($doc);
320         }
322         $this->engine->area_index_complete($area->get_area_id());
324         $querydata = new stdClass();
325         $querydata->q = 'message';
326         $querydata->title = $doc->get('title');
328         // We are going to go through each user and see if they get the original and the owned doc.
329         foreach ($users as $user) {
330             $this->setUser($user);
332             $results = $this->search->search($querydata);
333             $this->assertCount(2, $results);
335             $owned = 0;
336             $notowned = 0;
338             // We don't know what order we will get the results in, so we are doing this.
339             foreach ($results as $result) {
340                 $owneruserid = $result->get('owneruserid');
341                 if (empty($owneruserid)) {
342                     $notowned++;
343                     $this->assertEquals(0, $owneruserid);
344                     $this->assertEquals($originalid, $result->get('id'));
345                 } else {
346                     $owned++;
347                     $this->assertEquals($user->id, $owneruserid);
348                     $this->assertEquals($originalid.'-'.$user->id, $result->get('id'));
349                 }
350             }
352             $this->assertEquals(1, $owned);
353             $this->assertEquals(1, $notowned);
354         }
356         // Now test a user with no owned results.
357         $otheruser = $this->getDataGenerator()->create_user();
358         $this->setUser($otheruser);
360         $results = $this->search->search($querydata);
361         $this->assertCount(1, $results);
363         $this->assertEquals(0, $results[0]->get('owneruserid'));
364         $this->assertEquals($originalid, $results[0]->get('id'));
365     }
367     /**
368      * @dataProvider file_indexing_provider
369      */
370     public function test_highlight($fileindexing) {
371         global $PAGE;
373         $this->engine->test_set_config('fileindexing', $fileindexing);
375         $this->generator->create_record();
376         $this->search->index();
378         $querydata = new stdClass();
379         $querydata->q = 'message';
381         $results = $this->search->search($querydata);
382         $this->assertCount(1, $results);
384         $result = reset($results);
386         $regex = '|'.\search_solr\engine::HIGHLIGHT_START.'message'.\search_solr\engine::HIGHLIGHT_END.'|';
387         $this->assertRegExp($regex, $result->get('content'));
389         $searchrenderer = $PAGE->get_renderer('core_search');
390         $exported = $result->export_for_template($searchrenderer);
392         $regex = '|<span class="highlight">message</span>|';
393         $this->assertRegExp($regex, $exported['content']);
394     }
396     public function test_export_file_for_engine() {
397         // Get area to work with.
398         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
399         $area = \core_search\manager::get_search_area($areaid);
401         $record = $this->generator->create_record();
403         $doc = $area->get_document($record);
404         $filerecord = new stdClass();
405         $filerecord->timemodified  = 978310800;
406         $file = $this->generator->create_file($filerecord);
407         $doc->add_stored_file($file);
409         $filearray = $doc->export_file_for_engine($file);
411         $this->assertEquals(\core_search\manager::TYPE_FILE, $filearray['type']);
412         $this->assertEquals($file->get_id(), $filearray['solr_fileid']);
413         $this->assertEquals($file->get_contenthash(), $filearray['solr_filecontenthash']);
414         $this->assertEquals(\search_solr\document::INDEXED_FILE_TRUE, $filearray['solr_fileindexstatus']);
415         $this->assertEquals($file->get_filename(), $filearray['title']);
416         $this->assertEquals(978310800, \search_solr\document::import_time_from_engine($filearray['modified']));
417     }
419     public function test_index_file() {
420         // Very simple test.
421         $file = $this->generator->create_file();
423         $record = new \stdClass();
424         $record->attachfileids = array($file->get_id());
425         $this->generator->create_record($record);
427         $this->search->index();
428         $querydata = new stdClass();
429         $querydata->q = '"File contents"';
431         $this->assertCount(1, $this->search->search($querydata));
432     }
434     public function test_reindexing_files() {
435         // Get area to work with.
436         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
437         $area = \core_search\manager::get_search_area($areaid);
439         $record = $this->generator->create_record();
441         $doc = $area->get_document($record);
443         // Now we are going to make some files.
444         $fs = get_file_storage();
445         $syscontext = \context_system::instance();
447         $files = array();
449         $filerecord = new \stdClass();
450         // We make enough so that we pass the 500 files threashold. That is the boundary when getting files.
451         $boundary = 500;
452         $top = (int)($boundary * 1.1);
453         for ($i = 0; $i < $top; $i++) {
454             $filerecord->filename  = 'searchfile'.$i;
455             $filerecord->content = 'Some FileContents'.$i;
456             $file = $this->generator->create_file($filerecord);
457             $doc->add_stored_file($file);
458             $files[] = $file;
459         }
461         // Add the doc with lots of files, then commit.
462         $this->engine->add_document($doc, true);
463         $this->engine->area_index_complete($area->get_area_id());
465         // Indexes we are going to check. 0 means we will delete, 1 means we will keep.
466         $checkfiles = array(
467             0 => 0,                        // Check the begining of the set.
468             1 => 1,
469             2 => 0,
470             ($top - 3) => 0,               // Check the end of the set.
471             ($top - 2) => 1,
472             ($top - 1) => 0,
473             ($boundary - 2) => 0,          // Check at the boundary between fetch groups.
474             ($boundary - 1) => 0,
475             $boundary => 0,
476             ($boundary + 1) => 0,
477             ((int)($boundary * 0.5)) => 1, // Make sure we keep some middle ones.
478             ((int)($boundary * 1.05)) => 1
479         );
481         $querydata = new stdClass();
483         // First, check that all the files are currently there.
484         foreach ($checkfiles as $key => $unused) {
485             $querydata->q = 'FileContents'.$key;
486             $this->assertCount(1, $this->search->search($querydata));
487             $querydata->q = 'searchfile'.$key;
488             $this->assertCount(1, $this->search->search($querydata));
489         }
491         // Remove the files we want removed from the files array.
492         foreach ($checkfiles as $key => $keep) {
493             if (!$keep) {
494                 unset($files[$key]);
495             }
496         }
498         // And make us a new file to add.
499         $filerecord->filename  = 'searchfileNew';
500         $filerecord->content  = 'Some FileContentsNew';
501         $files[] = $this->generator->create_file($filerecord);
502         $checkfiles['New'] = 1;
504         $doc = $area->get_document($record);
505         foreach($files as $file) {
506             $doc->add_stored_file($file);
507         }
509         // Reindex the document with the changed files.
510         $this->engine->add_document($doc, true);
511         $this->engine->area_index_complete($area->get_area_id());
513         // Go through our check array, and see if the file is there or not.
514         foreach ($checkfiles as $key => $keep) {
515             $querydata->q = 'FileContents'.$key;
516             $this->assertCount($keep, $this->search->search($querydata));
517             $querydata->q = 'searchfile'.$key;
518             $this->assertCount($keep, $this->search->search($querydata));
519         }
521         // Now check that we get one result when we search from something in all of them.
522         $querydata->q = 'Some';
523         $this->assertCount(1, $this->search->search($querydata));
524     }
526     /**
527      * Test indexing a file we don't consider indexable.
528      */
529     public function test_index_filtered_file() {
530         // Get area to work with.
531         $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
532         $area = \core_search\manager::get_search_area($areaid);
534         // Get a single record to make a doc from.
535         $record = $this->generator->create_record();
537         $doc = $area->get_document($record);
539         // Now we are going to make some files.
540         $fs = get_file_storage();
541         $syscontext = \context_system::instance();
543         // We need to make a file greater than 1kB in size, which is the lowest filter size.
544         $filerecord = new \stdClass();
545         $filerecord->filename = 'largefile';
546         $filerecord->content = 'Some LargeFindContent to find.';
547         for ($i = 0; $i < 200; $i++) {
548             $filerecord->content .= ' The quick brown fox jumps over the lazy dog.';
549         }
551         $this->assertGreaterThan(1024, strlen($filerecord->content));
553         $file = $this->generator->create_file($filerecord);
554         $doc->add_stored_file($file);
556         $filerecord->filename = 'smallfile';
557         $filerecord->content = 'Some SmallFindContent to find.';
558         $file = $this->generator->create_file($filerecord);
559         $doc->add_stored_file($file);
561         $this->engine->add_document($doc, true);
562         $this->engine->area_index_complete($area->get_area_id());
564         $querydata = new stdClass();
565         // We shouldn't be able to find the large file contents.
566         $querydata->q = 'LargeFindContent';
567         $this->assertCount(0, $this->search->search($querydata));
569         // But we should be able to find the filename.
570         $querydata->q = 'largefile';
571         $this->assertCount(1, $this->search->search($querydata));
573         // We should be able to find the small file contents.
574         $querydata->q = 'SmallFindContent';
575         $this->assertCount(1, $this->search->search($querydata));
577         // And we should be able to find the filename.
578         $querydata->q = 'smallfile';
579         $this->assertCount(1, $this->search->search($querydata));
580     }
582     public function test_delete_by_id() {
583         // First get files in the index.
584         $file = $this->generator->create_file();
585         $record = new \stdClass();
586         $record->attachfileids = array($file->get_id());
587         $this->generator->create_record($record);
588         $this->generator->create_record($record);
589         $this->search->index();
591         $querydata = new stdClass();
593         // Then search to make sure they are there.
594         $querydata->q = '"File contents"';
595         $results = $this->search->search($querydata);
596         $this->assertCount(2, $results);
598         $first = reset($results);
599         $deleteid = $first->get('id');
601         $this->engine->delete_by_id($deleteid);
603         // Check that we don't get a result for it anymore.
604         $results = $this->search->search($querydata);
605         $this->assertCount(1, $results);
606         $result = reset($results);
607         $this->assertNotEquals($deleteid, $result->get('id'));
608     }
610     /**
611      * Test that expected results are returned, even with low check_access success rate.
612      *
613      * @dataProvider file_indexing_provider
614      */
615     public function test_solr_filling($fileindexing) {
616         $this->engine->test_set_config('fileindexing', $fileindexing);
618         $user1 = self::getDataGenerator()->create_user();
619         $user2 = self::getDataGenerator()->create_user();
621         // We are going to create a bunch of records that user 1 can see with 2 keywords.
622         // Then we are going to create a bunch for user 2 with only 1 of the keywords.
623         // If user 2 searches for both keywords, solr will return all of the user 1 results, then the user 2 results.
624         // This is because the user 1 results will match 2 keywords, while the others will match only 1.
626         $record = new \stdClass();
628         // First create a bunch of records for user 1 to see.
629         $record->denyuserids = array($user2->id);
630         $record->content = 'Something1 Something2';
631         $maxresults = (int)(\core_search\manager::MAX_RESULTS * .75);
632         for ($i = 0; $i < $maxresults; $i++) {
633             $this->generator->create_record($record);
634         }
636         // Then create a bunch of records for user 2 to see.
637         $record->denyuserids = array($user1->id);
638         $record->content = 'Something1';
639         for ($i = 0; $i < $maxresults; $i++) {
640             $this->generator->create_record($record);
641         }
643         $this->search->index();
645         // Check that user 1 sees all their results.
646         $this->setUser($user1);
647         $querydata = new stdClass();
648         $querydata->q = 'Something1 Something2';
649         $results = $this->search->search($querydata);
650         $this->assertCount($maxresults, $results);
652         // Check that user 2 will see theirs, even though they may be crouded out.
653         $this->setUser($user2);
654         $results = $this->search->search($querydata);
655         $this->assertCount($maxresults, $results);
656     }
658     /**
659      * Create 40 docs, that will be return from Solr in 10 hidden, 10 visible, 10 hidden, 10 visible if you query for:
660      * Something1 Something2 Something3 Something4, with the specified user set.
661      */
662     protected function setup_user_hidden_docs($user) {
663         // These results will come first, and will not be visible by the user.
664         $record = new \stdClass();
665         $record->denyuserids = array($user->id);
666         $record->content = 'Something1 Something2 Something3 Something4';
667         for ($i = 0; $i < 10; $i++) {
668             $this->generator->create_record($record);
669         }
671         // These results will come second, and will  be visible by the user.
672         unset($record->denyuserids);
673         $record->content = 'Something1 Something2 Something3';
674         for ($i = 0; $i < 10; $i++) {
675             $this->generator->create_record($record);
676         }
678         // These results will come third, and will not be visible by the user.
679         $record->denyuserids = array($user->id);
680         $record->content = 'Something1 Something2';
681         for ($i = 0; $i < 10; $i++) {
682             $this->generator->create_record($record);
683         }
685         // These results will come fourth, and will be visible by the user.
686         unset($record->denyuserids);
687         $record->content = 'Something1 ';
688         for ($i = 0; $i < 10; $i++) {
689             $this->generator->create_record($record);
690         }
691     }
693     /**
694      * Test that counts are what we expect.
695      *
696      * @dataProvider file_indexing_provider
697      */
698     public function test_get_query_total_count($fileindexing) {
699         $this->engine->test_set_config('fileindexing', $fileindexing);
701         $user = self::getDataGenerator()->create_user();
702         $this->setup_user_hidden_docs($user);
703         $this->search->index();
705         $this->setUser($user);
706         $querydata = new stdClass();
707         $querydata->q = 'Something1 Something2 Something3 Something4';
709         // In this first set, it should have determined the first 10 of 40 are bad, so there could be up to 30 left.
710         $results = $this->engine->execute_query($querydata, true, 5);
711         $this->assertEquals(30, $this->engine->get_query_total_count());
712         $this->assertCount(5, $results);
714         // To get to 15, it has to process the first 10 that are bad, 10 that are good, 10 that are bad, then 5 that are good.
715         // So we now know 20 are bad out of 40.
716         $results = $this->engine->execute_query($querydata, true, 15);
717         $this->assertEquals(20, $this->engine->get_query_total_count());
718         $this->assertCount(15, $results);
720         // Try to get more then all, make sure we still see 20 count and 20 returned.
721         $results = $this->engine->execute_query($querydata, true, 30);
722         $this->assertEquals(20, $this->engine->get_query_total_count());
723         $this->assertCount(20, $results);
724     }
726     /**
727      * Test that paged results are what we expect.
728      *
729      * @dataProvider file_indexing_provider
730      */
731     public function test_manager_paged_search($fileindexing) {
732         $this->engine->test_set_config('fileindexing', $fileindexing);
734         $user = self::getDataGenerator()->create_user();
735         $this->setup_user_hidden_docs($user);
736         $this->search->index();
738         // Check that user 1 sees all their results.
739         $this->setUser($user);
740         $querydata = new stdClass();
741         $querydata->q = 'Something1 Something2 Something3 Something4';
743         // On this first page, it should have determined the first 10 of 40 are bad, so there could be up to 30 left.
744         $results = $this->search->paged_search($querydata, 0);
745         $this->assertEquals(30, $results->totalcount);
746         $this->assertCount(10, $results->results);
747         $this->assertEquals(0, $results->actualpage);
749         // On the second page, it should have found the next 10 bad ones, so we no know there are only 20 total.
750         $results = $this->search->paged_search($querydata, 1);
751         $this->assertEquals(20, $results->totalcount);
752         $this->assertCount(10, $results->results);
753         $this->assertEquals(1, $results->actualpage);
755         // Try to get an additional page - we should get back page 1 results, since that is the last page with valid results.
756         $results = $this->search->paged_search($querydata, 2);
757         $this->assertEquals(20, $results->totalcount);
758         $this->assertCount(10, $results->results);
759         $this->assertEquals(1, $results->actualpage);
760     }