Merge "Reset interwiki table between tests"
[lhc/web/wiklou.git] / tests / phpunit / includes / PrefixSearchTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @group Search
7 * @group Database
8 * @covers PrefixSearch
9 */
10 class PrefixSearchTest extends MediaWikiLangTestCase {
11 const NS_NONCAP = 12346;
12
13 private $originalHandlers;
14
15 public function addDBDataOnce() {
16 if ( !$this->isWikitextNS( NS_MAIN ) ) {
17 // tests are skipped if NS_MAIN is not wikitext
18 return;
19 }
20
21 $this->insertPage( 'Sandbox' );
22 $this->insertPage( 'Bar' );
23 $this->insertPage( 'Example' );
24 $this->insertPage( 'Example Bar' );
25 $this->insertPage( 'Example Foo' );
26 $this->insertPage( 'Example Foo/Bar' );
27 $this->insertPage( 'Example/Baz' );
28 $this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' );
29 $this->insertPage( 'Redirect Test' );
30 $this->insertPage( 'Redirect Test Worse Result' );
31 $this->insertPage( 'Redirect test2', '#REDIRECT [[Redirect Test2]]' );
32 $this->insertPage( 'Redirect TEST2', '#REDIRECT [[Redirect Test2]]' );
33 $this->insertPage( 'Redirect Test2' );
34 $this->insertPage( 'Redirect Test2 Worse Result' );
35
36 $this->insertPage( 'Talk:Sandbox' );
37 $this->insertPage( 'Talk:Example' );
38
39 $this->insertPage( 'User:Example' );
40
41 $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Bar' ) );
42 $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Upper' ) );
43 $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'sandbox' ) );
44 }
45
46 protected function setUp() {
47 parent::setUp();
48
49 if ( !$this->isWikitextNS( NS_MAIN ) ) {
50 $this->markTestSkipped( 'Main namespace does not support wikitext.' );
51 }
52
53 // Avoid special pages from extensions interferring with the tests
54 $this->setMwGlobals( [
55 'wgSpecialPages' => [],
56 'wgHooks' => [],
57 'wgExtraNamespaces' => [ self::NS_NONCAP => 'NonCap' ],
58 'wgCapitalLinkOverrides' => [ self::NS_NONCAP => false ],
59 ] );
60
61 $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
62 TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
63
64 // Clear caches so that our new namespace appears
65 MWNamespace::clearCaches();
66 Language::factory( 'en' )->resetNamespaces();
67
68 SpecialPageFactory::resetList();
69 }
70
71 public function tearDown() {
72 MWNamespace::clearCaches();
73 Language::factory( 'en' )->resetNamespaces();
74
75 parent::tearDown();
76
77 TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
78
79 SpecialPageFactory::resetList();
80 }
81
82 protected function searchProvision( array $results = null ) {
83 if ( $results === null ) {
84 $this->setMwGlobals( 'wgHooks', [] );
85 } else {
86 $this->setMwGlobals( 'wgHooks', [
87 'PrefixSearchBackend' => [
88 function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) {
89 $srchres = $results;
90 return false;
91 }
92 ],
93 ] );
94 }
95 }
96
97 public static function provideSearch() {
98 return [
99 [ [
100 'Empty string',
101 'query' => '',
102 'results' => [],
103 ] ],
104 [ [
105 'Main namespace with title prefix',
106 'query' => 'Ex',
107 'results' => [
108 'Example',
109 'Example/Baz',
110 'Example Bar',
111 ],
112 // Third result when testing offset
113 'offsetresult' => [
114 'Example Foo',
115 ],
116 ] ],
117 [ [
118 'Talk namespace prefix',
119 'query' => 'Talk:',
120 'results' => [
121 'Talk:Example',
122 'Talk:Sandbox',
123 ],
124 ] ],
125 [ [
126 'User namespace prefix',
127 'query' => 'User:',
128 'results' => [
129 'User:Example',
130 ],
131 ] ],
132 [ [
133 'Special namespace prefix',
134 'query' => 'Special:',
135 'results' => [
136 'Special:ActiveUsers',
137 'Special:AllMessages',
138 'Special:AllMyUploads',
139 ],
140 // Third result when testing offset
141 'offsetresult' => [
142 'Special:AllPages',
143 ],
144 ] ],
145 [ [
146 'Special namespace with prefix',
147 'query' => 'Special:Un',
148 'results' => [
149 'Special:Unblock',
150 'Special:UncategorizedCategories',
151 'Special:UncategorizedFiles',
152 ],
153 // Third result when testing offset
154 'offsetresult' => [
155 'Special:UncategorizedPages',
156 ],
157 ] ],
158 [ [
159 'Special page name',
160 'query' => 'Special:EditWatchlist',
161 'results' => [
162 'Special:EditWatchlist',
163 ],
164 ] ],
165 [ [
166 'Special page subpages',
167 'query' => 'Special:EditWatchlist/',
168 'results' => [
169 'Special:EditWatchlist/clear',
170 'Special:EditWatchlist/raw',
171 ],
172 ] ],
173 [ [
174 'Special page subpages with prefix',
175 'query' => 'Special:EditWatchlist/cl',
176 'results' => [
177 'Special:EditWatchlist/clear',
178 ],
179 ] ],
180 [ [
181 'Namespace with case sensitive first letter',
182 'query' => 'NonCap:upper',
183 'results' => []
184 ] ],
185 [ [
186 'Multinamespace search',
187 'query' => 'B',
188 'results' => [
189 'Bar',
190 'NonCap:Bar',
191 ],
192 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
193 ] ],
194 [ [
195 'Multinamespace search with lowercase first letter',
196 'query' => 'sand',
197 'results' => [
198 'Sandbox',
199 'NonCap:sandbox',
200 ],
201 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
202 ] ],
203 ];
204 }
205
206 /**
207 * @dataProvider provideSearch
208 * @covers PrefixSearch::search
209 * @covers PrefixSearch::searchBackend
210 */
211 public function testSearch( array $case ) {
212 // FIXME: fails under postgres
213 $this->markTestSkippedIfDbType( 'postgres' );
214 $this->searchProvision( null );
215
216 $namespaces = $case['namespaces'] ?? [];
217
218 if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
219 // Postgres will sort lexicographically on utf8 code units (" " before "/")
220 sort( $case['results'], SORT_STRING );
221 }
222
223 $searcher = new StringPrefixSearch;
224 $results = $searcher->search( $case['query'], 3, $namespaces );
225 $this->assertEquals(
226 $case['results'],
227 $results,
228 $case[0]
229 );
230 }
231
232 /**
233 * @dataProvider provideSearch
234 * @covers PrefixSearch::search
235 * @covers PrefixSearch::searchBackend
236 */
237 public function testSearchWithOffset( array $case ) {
238 // FIXME: fails under postgres
239 $this->markTestSkippedIfDbType( 'postgres' );
240 $this->searchProvision( null );
241
242 $namespaces = $case['namespaces'] ?? [];
243
244 $searcher = new StringPrefixSearch;
245 $results = $searcher->search( $case['query'], 3, $namespaces, 1 );
246
247 if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
248 // Postgres will sort lexicographically on utf8 code units (" " before "/")
249 sort( $case['results'], SORT_STRING );
250 }
251
252 // We don't expect the first result when offsetting
253 array_shift( $case['results'] );
254 // And sometimes we expect a different last result
255 $expected = isset( $case['offsetresult'] ) ?
256 array_merge( $case['results'], $case['offsetresult'] ) :
257 $case['results'];
258
259 $this->assertEquals(
260 $expected,
261 $results,
262 $case[0]
263 );
264 }
265
266 public static function provideSearchBackend() {
267 return [
268 [ [
269 'Simple case',
270 'provision' => [
271 'Bar',
272 'Barcelona',
273 'Barbara',
274 ],
275 'query' => 'Bar',
276 'results' => [
277 'Bar',
278 'Barcelona',
279 'Barbara',
280 ],
281 ] ],
282 [ [
283 'Exact match not on top (T72958)',
284 'provision' => [
285 'Barcelona',
286 'Bar',
287 'Barbara',
288 ],
289 'query' => 'Bar',
290 'results' => [
291 'Bar',
292 'Barcelona',
293 'Barbara',
294 ],
295 ] ],
296 [ [
297 'Exact match missing (T72958)',
298 'provision' => [
299 'Barcelona',
300 'Barbara',
301 'Bart',
302 ],
303 'query' => 'Bar',
304 'results' => [
305 'Bar',
306 'Barcelona',
307 'Barbara',
308 ],
309 ] ],
310 [ [
311 'Exact match missing and not existing',
312 'provision' => [
313 'Exile',
314 'Exist',
315 'External',
316 ],
317 'query' => 'Ex',
318 'results' => [
319 'Exile',
320 'Exist',
321 'External',
322 ],
323 ] ],
324 [ [
325 "Exact match shouldn't override already found match if " .
326 "exact is redirect and found isn't",
327 'provision' => [
328 // Target of the exact match is low in the list
329 'Redirect Test Worse Result',
330 'Redirect Test',
331 ],
332 'query' => 'redirect test',
333 'results' => [
334 // Redirect target is pulled up and exact match isn't added
335 'Redirect Test',
336 'Redirect Test Worse Result',
337 ],
338 ] ],
339 [ [
340 "Exact match shouldn't override already found match if " .
341 "both exact match and found match are redirect",
342 'provision' => [
343 // Another redirect to the same target as the exact match
344 // is low in the list
345 'Redirect Test2 Worse Result',
346 'Redirect test2',
347 ],
348 'query' => 'redirect TEST2',
349 'results' => [
350 // Found redirect is pulled to the top and exact match isn't
351 // added
352 'Redirect test2',
353 'Redirect Test2 Worse Result',
354 ],
355 ] ],
356 [ [
357 "Exact match should override any already found matches that " .
358 "are redirects to it",
359 'provision' => [
360 // Another redirect to the same target as the exact match
361 // is low in the list
362 'Redirect Test Worse Result',
363 'Redirect test',
364 ],
365 'query' => 'Redirect Test',
366 'results' => [
367 // Found redirect is pulled to the top and exact match isn't
368 // added
369 'Redirect Test',
370 'Redirect Test Worse Result',
371 ],
372 ] ],
373 ];
374 }
375
376 /**
377 * @dataProvider provideSearchBackend
378 * @covers PrefixSearch::searchBackend
379 */
380 public function testSearchBackend( array $case ) {
381 $this->searchProvision( $case['provision'] );
382 $searcher = new StringPrefixSearch;
383 $results = $searcher->search( $case['query'], 3 );
384 $this->assertEquals(
385 $case['results'],
386 $results,
387 $case[0]
388 );
389 }
390 }