Merge "Selenium: replace UserLoginPage with BlankPage where possible"
[lhc/web/wiklou.git] / tests / phpunit / includes / specials / SpecialSearchTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4
5 /**
6 * Test class for SpecialSearch class
7 * Copyright © 2012, Antoine Musso
8 *
9 * @author Antoine Musso
10 * @group Database
11 */
12 class SpecialSearchTest extends MediaWikiTestCase {
13
14 /**
15 * @covers SpecialSearch::load
16 * @dataProvider provideSearchOptionsTests
17 * @param array $requested Request parameters. For example:
18 * [ 'ns5' => true, 'ns6' => true ]. Null to use default options.
19 * @param array $userOptions User options to test with. For example:
20 * [ 'searchNs5' => 1 ];. Null to use default options.
21 * @param string $expectedProfile An expected search profile name
22 * @param array $expectedNS Expected namespaces
23 * @param string $message
24 */
25 public function testProfileAndNamespaceLoading( $requested, $userOptions,
26 $expectedProfile, $expectedNS, $message = 'Profile name and namespaces mismatches!'
27 ) {
28 $context = new RequestContext;
29 $context->setUser(
30 $this->newUserWithSearchNS( $userOptions )
31 );
32 /*
33 $context->setRequest( new FauxRequest( [
34 'ns5'=>true,
35 'ns6'=>true,
36 ] ));
37 */
38 $context->setRequest( new FauxRequest( $requested ) );
39 $search = new SpecialSearch();
40 $search->setContext( $context );
41 $search->load();
42
43 /**
44 * Verify profile name and namespace in the same assertion to make
45 * sure we will be able to fully compare the above code. PHPUnit stop
46 * after an assertion fail.
47 */
48 $this->assertEquals(
49 [ /** Expected: */
50 'ProfileName' => $expectedProfile,
51 'Namespaces' => $expectedNS,
52 ],
53 [ /** Actual: */
54 'ProfileName' => $search->getProfile(),
55 'Namespaces' => $search->getNamespaces(),
56 ],
57 $message
58 );
59 }
60
61 public static function provideSearchOptionsTests() {
62 $defaultNS = MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
63 $EMPTY_REQUEST = [];
64 $NO_USER_PREF = null;
65
66 return [
67 /**
68 * Parameters:
69 * <Web Request>, <User options>
70 * Followed by expected values:
71 * <ProfileName>, <NSList>
72 * Then an optional message.
73 */
74 [
75 $EMPTY_REQUEST, $NO_USER_PREF,
76 'default', $defaultNS,
77 'T35270: No request nor user preferences should give default profile'
78 ],
79 [
80 [ 'ns5' => 1 ], $NO_USER_PREF,
81 'advanced', [ 5 ],
82 'Web request with specific NS should override user preference'
83 ],
84 [
85 $EMPTY_REQUEST, [
86 'searchNs2' => 1,
87 'searchNs14' => 1,
88 ] + array_fill_keys( array_map( function ( $ns ) {
89 return "searchNs$ns";
90 }, $defaultNS ), 0 ),
91 'advanced', [ 2, 14 ],
92 'T35583: search with no option should honor User search preferences'
93 . ' and have all other namespace disabled'
94 ],
95 ];
96 }
97
98 /**
99 * Helper to create a new User object with given options
100 * User remains anonymous though
101 * @param array|null $opt
102 */
103 function newUserWithSearchNS( $opt = null ) {
104 $u = User::newFromId( 0 );
105 if ( $opt === null ) {
106 return $u;
107 }
108 foreach ( $opt as $name => $value ) {
109 $u->setOption( $name, $value );
110 }
111
112 return $u;
113 }
114
115 /**
116 * Verify we do not expand search term in <title> on search result page
117 * https://gerrit.wikimedia.org/r/4841
118 * @covers SpecialSearch::setupPage
119 */
120 public function testSearchTermIsNotExpanded() {
121 $this->setMwGlobals( [
122 'wgSearchType' => null,
123 ] );
124
125 # Initialize [[Special::Search]]
126 $ctx = new RequestContext();
127 $term = '{{SITENAME}}';
128 $ctx->setRequest( new FauxRequest( [ 'search' => $term, 'fulltext' => 1 ] ) );
129 $ctx->setTitle( Title::newFromText( 'Special:Search' ) );
130 $search = new SpecialSearch();
131 $search->setContext( $ctx );
132
133 # Simulate a user searching for a given term
134 $search->execute( '' );
135
136 # Lookup the HTML page title set for that page
137 $pageTitle = $search
138 ->getContext()
139 ->getOutput()
140 ->getHTMLTitle();
141
142 # Compare :-]
143 $this->assertRegExp(
144 '/' . preg_quote( $term, '/' ) . '/',
145 $pageTitle,
146 "Search term '{$term}' should not be expanded in Special:Search <title>"
147 );
148 }
149
150 public function provideRewriteQueryWithSuggestion() {
151 return [
152 [
153 'With suggestion and no rewritten query shows did you mean',
154 '/Did you mean: <a[^>]+>first suggestion/',
155 'first suggestion',
156 null,
157 [ Title::newMainPage() ]
158 ],
159
160 [
161 'With rewritten query informs user of change',
162 '/Showing results for <a[^>]+>first suggestion/',
163 'asdf',
164 'first suggestion',
165 [ Title::newMainPage() ]
166 ],
167
168 [
169 'When both queries have no results user gets no results',
170 '/There were no results matching the query/',
171 'first suggestion',
172 'first suggestion',
173 []
174 ],
175 ];
176 }
177
178 /**
179 * @dataProvider provideRewriteQueryWithSuggestion
180 * @covers SpecialSearch::showResults
181 */
182 public function testRewriteQueryWithSuggestion(
183 $message,
184 $expectRegex,
185 $suggestion,
186 $rewrittenQuery,
187 array $resultTitles
188 ) {
189 $results = array_map( function ( $title ) {
190 return SearchResult::newFromTitle( $title );
191 }, $resultTitles );
192
193 $searchResults = new SpecialSearchTestMockResultSet(
194 $suggestion,
195 $rewrittenQuery,
196 $results
197 );
198
199 $mockSearchEngine = $this->mockSearchEngine( $searchResults );
200 $search = $this->getMockBuilder( SpecialSearch::class )
201 ->setMethods( [ 'getSearchEngine' ] )
202 ->getMock();
203 $search->expects( $this->any() )
204 ->method( 'getSearchEngine' )
205 ->will( $this->returnValue( $mockSearchEngine ) );
206
207 $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) );
208 $search->getContext()->setLanguage( Language::factory( 'en' ) );
209 $search->load();
210 $search->showResults( 'this is a fake search' );
211
212 $html = $search->getContext()->getOutput()->getHTML();
213 foreach ( (array)$expectRegex as $regex ) {
214 $this->assertRegExp( $regex, $html, $message );
215 }
216 }
217
218 protected function mockSearchEngine( $results ) {
219 $mock = $this->getMockBuilder( SearchEngine::class )
220 ->setMethods( [ 'searchText', 'searchTitle' ] )
221 ->getMock();
222
223 $mock->expects( $this->any() )
224 ->method( 'searchText' )
225 ->will( $this->returnValue( $results ) );
226
227 return $mock;
228 }
229
230 /**
231 * @covers SpecialSearch::execute
232 */
233 public function testSubPageRedirect() {
234 $this->setMwGlobals( [
235 'wgScript' => '/w/index.php',
236 ] );
237
238 $ctx = new RequestContext;
239 $sp = Title::newFromText( 'Special:Search/foo_bar' );
240 MediaWikiServices::getInstance()->getSpecialPageFactory()->executePath( $sp, $ctx );
241 $url = $ctx->getOutput()->getRedirect();
242 // some older versions of hhvm have a bug that doesn't parse relative
243 // urls with a port, so help it out a little bit.
244 // https://github.com/facebook/hhvm/issues/7136
245 $url = wfExpandUrl( $url, PROTO_CURRENT );
246
247 $parts = parse_url( $url );
248 $this->assertEquals( '/w/index.php', $parts['path'] );
249 parse_str( $parts['query'], $query );
250 $this->assertEquals( 'Special:Search', $query['title'] );
251 $this->assertEquals( 'foo bar', $query['search'] );
252 }
253 }
254
255 class SpecialSearchTestMockResultSet extends SearchResultSet {
256 protected $results;
257 protected $suggestion;
258
259 public function __construct(
260 $suggestion = null,
261 $rewrittenQuery = null,
262 array $results = [],
263 $containedSyntax = false
264 ) {
265 $this->suggestion = $suggestion;
266 $this->rewrittenQuery = $rewrittenQuery;
267 $this->results = $results;
268 $this->containedSyntax = $containedSyntax;
269 }
270
271 public function expandResults() {
272 return $this->results;
273 }
274
275 public function getTotalHits() {
276 return $this->numRows();
277 }
278
279 public function hasSuggestion() {
280 return $this->suggestion !== null;
281 }
282
283 public function getSuggestionQuery() {
284 return $this->suggestion;
285 }
286
287 public function getSuggestionSnippet() {
288 return $this->suggestion;
289 }
290
291 public function hasRewrittenQuery() {
292 return $this->rewrittenQuery !== null;
293 }
294
295 public function getQueryAfterRewrite() {
296 return $this->rewrittenQuery;
297 }
298
299 public function getQueryAfterRewriteSnippet() {
300 return htmlspecialchars( $this->rewrittenQuery );
301 }
302 }