]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - tests/phpunit/includes/search/SearchEngineTest.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / tests / phpunit / includes / search / SearchEngineTest.php
1 <?php
2
3 /**
4  * @group Search
5  * @group Database
6  *
7  * @covers SearchEngine<extended>
8  * @note Coverage will only ever show one of on of the Search* classes
9  */
10 class SearchEngineTest extends MediaWikiLangTestCase {
11
12         /**
13          * @var SearchEngine
14          */
15         protected $search;
16
17         /**
18          * Checks for database type & version.
19          * Will skip current test if DB does not support search.
20          */
21         protected function setUp() {
22                 parent::setUp();
23
24                 // Search tests require MySQL or SQLite with FTS
25                 $dbType = $this->db->getType();
26                 $dbSupported = ( $dbType === 'mysql' )
27                         || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' );
28
29                 if ( !$dbSupported ) {
30                         $this->markTestSkipped( "MySQL or SQLite with FTS3 only" );
31                 }
32
33                 $searchType = SearchEngineFactory::getSearchEngineClass( $this->db );
34                 $this->setMwGlobals( [
35                         'wgSearchType' => $searchType
36                 ] );
37
38                 $this->search = new $searchType( $this->db );
39         }
40
41         protected function tearDown() {
42                 unset( $this->search );
43
44                 parent::tearDown();
45         }
46
47         public function addDBDataOnce() {
48                 if ( !$this->isWikitextNS( NS_MAIN ) ) {
49                         // @todo cover the case of non-wikitext content in the main namespace
50                         return;
51                 }
52
53                 // Reset the search type back to default - some extensions may have
54                 // overridden it.
55                 $this->setMwGlobals( [ 'wgSearchType' => null ] );
56
57                 $this->insertPage( 'Not_Main_Page', 'This is not a main page' );
58                 $this->insertPage(
59                         'Talk:Not_Main_Page',
60                         'This is not a talk page to the main page, see [[smithee]]'
61                 );
62                 $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]' );
63                 $this->insertPage( 'Talk:Smithee', 'This article sucks.' );
64                 $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.' );
65                 $this->insertPage( 'Another_page', 'This page also is unrelated.' );
66                 $this->insertPage( 'Help:Help', 'Help me!' );
67                 $this->insertPage( 'Thppt', 'Blah blah' );
68                 $this->insertPage( 'Alan_Smithee', 'yum' );
69                 $this->insertPage( 'Pages', 'are\'food' );
70                 $this->insertPage( 'HalfOneUp', 'AZ' );
71                 $this->insertPage( 'FullOneUp', 'AZ' );
72                 $this->insertPage( 'HalfTwoLow', 'az' );
73                 $this->insertPage( 'FullTwoLow', 'az' );
74                 $this->insertPage( 'HalfNumbers', '1234567890' );
75                 $this->insertPage( 'FullNumbers', '1234567890' );
76                 $this->insertPage( 'DomainName', 'example.com' );
77         }
78
79         protected function fetchIds( $results ) {
80                 if ( !$this->isWikitextNS( NS_MAIN ) ) {
81                         $this->markTestIncomplete( __CLASS__ . " does no yet support non-wikitext content "
82                                 . "in the main namespace" );
83                 }
84                 $this->assertTrue( is_object( $results ) );
85
86                 $matches = [];
87                 $row = $results->next();
88                 while ( $row ) {
89                         $matches[] = $row->getTitle()->getPrefixedText();
90                         $row = $results->next();
91                 }
92                 $results->free();
93                 # Search is not guaranteed to return results in a certain order;
94                 # sort them numerically so we will compare simply that we received
95                 # the expected matches.
96                 sort( $matches );
97
98                 return $matches;
99         }
100
101         public function testFullWidth() {
102                 $this->assertEquals(
103                         [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ],
104                         $this->fetchIds( $this->search->searchText( 'AZ' ) ),
105                         "Search for normalized from Half-width Upper" );
106                 $this->assertEquals(
107                         [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ],
108                         $this->fetchIds( $this->search->searchText( 'az' ) ),
109                         "Search for normalized from Half-width Lower" );
110                 $this->assertEquals(
111                         [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ],
112                         $this->fetchIds( $this->search->searchText( 'AZ' ) ),
113                         "Search for normalized from Full-width Upper" );
114                 $this->assertEquals(
115                         [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ],
116                         $this->fetchIds( $this->search->searchText( 'az' ) ),
117                         "Search for normalized from Full-width Lower" );
118         }
119
120         public function testTextSearch() {
121                 $this->assertEquals(
122                         [ 'Smithee' ],
123                         $this->fetchIds( $this->search->searchText( 'smithee' ) ),
124                         "Plain search" );
125         }
126
127         public function testWildcardSearch() {
128                 $res = $this->search->searchText( 'smith*' );
129                 $this->assertEquals(
130                         [ 'Smithee' ],
131                         $this->fetchIds( $res ),
132                         "Search with wildcards" );
133
134                 $res = $this->search->searchText( 'smithson*' );
135                 $this->assertEquals(
136                         [],
137                         $this->fetchIds( $res ),
138                         "Search with wildcards must not find unrelated articles" );
139
140                 $res = $this->search->searchText( 'smith* smithee' );
141                 $this->assertEquals(
142                         [ 'Smithee' ],
143                         $this->fetchIds( $res ),
144                         "Search with wildcards can be combined with simple terms" );
145
146                 $res = $this->search->searchText( 'smith* "one who smiths"' );
147                 $this->assertEquals(
148                         [ 'Smithee' ],
149                         $this->fetchIds( $res ),
150                         "Search with wildcards can be combined with phrase search" );
151         }
152
153         public function testPhraseSearch() {
154                 $res = $this->search->searchText( '"smithee is one who smiths"' );
155                 $this->assertEquals(
156                         [ 'Smithee' ],
157                         $this->fetchIds( $res ),
158                         "Search a phrase" );
159
160                 $res = $this->search->searchText( '"smithee is who smiths"' );
161                 $this->assertEquals(
162                         [],
163                         $this->fetchIds( $res ),
164                         "Phrase search is not sloppy, search terms must be adjacent" );
165
166                 $res = $this->search->searchText( '"is smithee one who smiths"' );
167                 $this->assertEquals(
168                         [],
169                         $this->fetchIds( $res ),
170                         "Phrase search is ordered" );
171         }
172
173         public function testPhraseSearchHighlight() {
174                 $phrase = "smithee is one who smiths";
175                 $res = $this->search->searchText( "\"$phrase\"" );
176                 $match = $res->next();
177                 $snippet = "A <span class='searchmatch'>" . $phrase . "</span>";
178                 $this->assertStringStartsWith( $snippet,
179                         $match->getTextSnippet( $res->termMatches() ),
180                         "Highlight a phrase search" );
181         }
182
183         public function testTextPowerSearch() {
184                 $this->search->setNamespaces( [ 0, 1, 4 ] );
185                 $this->assertEquals(
186                         [
187                                 'Smithee',
188                                 'Talk:Not Main Page',
189                         ],
190                         $this->fetchIds( $this->search->searchText( 'smithee' ) ),
191                         "Power search" );
192         }
193
194         public function testTitleSearch() {
195                 $this->assertEquals(
196                         [
197                                 'Alan Smithee',
198                                 'Smithee',
199                         ],
200                         $this->fetchIds( $this->search->searchTitle( 'smithee' ) ),
201                         "Title search" );
202         }
203
204         public function testTextTitlePowerSearch() {
205                 $this->search->setNamespaces( [ 0, 1, 4 ] );
206                 $this->assertEquals(
207                         [
208                                 'Alan Smithee',
209                                 'Smithee',
210                                 'Talk:Smithee',
211                         ],
212                         $this->fetchIds( $this->search->searchTitle( 'smithee' ) ),
213                         "Title power search" );
214         }
215
216         /**
217          * @covers SearchEngine::getSearchIndexFields
218          */
219         public function testSearchIndexFields() {
220                 /**
221                  * @var $mockEngine SearchEngine
222                  */
223                 $mockEngine = $this->getMockBuilder( 'SearchEngine' )
224                         ->setMethods( [ 'makeSearchFieldMapping' ] )->getMock();
225
226                 $mockFieldBuilder = function ( $name, $type ) {
227                         $mockField =
228                                 $this->getMockBuilder( 'SearchIndexFieldDefinition' )->setConstructorArgs( [
229                                         $name,
230                                         $type
231                                 ] )->getMock();
232
233                         $mockField->expects( $this->any() )->method( 'getMapping' )->willReturn( [
234                                 'testData' => 'test',
235                                 'name' => $name,
236                                 'type' => $type,
237                         ] );
238
239                         $mockField->expects( $this->any() )
240                                 ->method( 'merge' )
241                                 ->willReturn( $mockField );
242
243                         return $mockField;
244                 };
245
246                 $mockEngine->expects( $this->atLeastOnce() )
247                         ->method( 'makeSearchFieldMapping' )
248                         ->willReturnCallback( $mockFieldBuilder );
249
250                 // Not using mock since PHPUnit mocks do not work properly with references in params
251                 $this->setTemporaryHook( 'SearchIndexFields',
252                         function ( &$fields, SearchEngine $engine ) use ( $mockFieldBuilder ) {
253                                 $fields['testField'] =
254                                         $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT );
255                                 return true;
256                         } );
257
258                 $fields = $mockEngine->getSearchIndexFields();
259                 $this->assertArrayHasKey( 'language', $fields );
260                 $this->assertArrayHasKey( 'category', $fields );
261                 $this->assertInstanceOf( 'SearchIndexField', $fields['testField'] );
262
263                 $mapping = $fields['testField']->getMapping( $mockEngine );
264                 $this->assertArrayHasKey( 'testData', $mapping );
265                 $this->assertEquals( 'test', $mapping['testData'] );
266         }
267
268         public function hookSearchIndexFields( $mockFieldBuilder, &$fields, SearchEngine $engine ) {
269                 $fields['testField'] = $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT );
270                 return true;
271         }
272
273         public function testAugmentorSearch() {
274                 $this->search->setNamespaces( [ 0, 1, 4 ] );
275                 $resultSet = $this->search->searchText( 'smithee' );
276                 // Not using mock since PHPUnit mocks do not work properly with references in params
277                 $this->mergeMwGlobalArrayValue( 'wgHooks',
278                         [ 'SearchResultsAugment' => [ [ $this, 'addAugmentors' ] ] ] );
279                 $this->search->augmentSearchResults( $resultSet );
280                 for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
281                         $id = $result->getTitle()->getArticleID();
282                         $augmentData = "Result:$id:" . $result->getTitle()->getText();
283                         $augmentData2 = "Result2:$id:" . $result->getTitle()->getText();
284                         $this->assertEquals( [ 'testSet' => $augmentData, 'testRow' => $augmentData2 ],
285                                 $result->getExtensionData() );
286                 }
287         }
288
289         public function addAugmentors( &$setAugmentors, &$rowAugmentors ) {
290                 $setAugmentor = $this->createMock( 'ResultSetAugmentor' );
291                 $setAugmentor->expects( $this->once() )
292                         ->method( 'augmentAll' )
293                         ->willReturnCallback( function ( SearchResultSet $resultSet ) {
294                                 $data = [];
295                                 for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
296                                         $id = $result->getTitle()->getArticleID();
297                                         $data[$id] = "Result:$id:" . $result->getTitle()->getText();
298                                 }
299                                 $resultSet->rewind();
300                                 return $data;
301                         } );
302                 $setAugmentors['testSet'] = $setAugmentor;
303
304                 $rowAugmentor = $this->createMock( 'ResultAugmentor' );
305                 $rowAugmentor->expects( $this->exactly( 2 ) )
306                         ->method( 'augment' )
307                         ->willReturnCallback( function ( SearchResult $result ) {
308                                 $id = $result->getTitle()->getArticleID();
309                                 return "Result2:$id:" . $result->getTitle()->getText();
310                         } );
311                 $rowAugmentors['testRow'] = $rowAugmentor;
312         }
313 }