Remove hard deprecation of PasswordPolicyChecks::checkPopularPasswordBlacklist
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiOptionsTest.php
1 <?php
2
3 /**
4 * @group API
5 * @group Database
6 * @group medium
7 *
8 * @covers ApiOptions
9 */
10 class ApiOptionsTest extends MediaWikiLangTestCase {
11
12 /** @var PHPUnit_Framework_MockObject_MockObject */
13 private $mUserMock;
14 /** @var ApiOptions */
15 private $mTested;
16 private $mSession;
17 /** @var DerivativeContext */
18 private $mContext;
19
20 private static $Success = [ 'options' => 'success' ];
21
22 protected function setUp() {
23 parent::setUp();
24
25 $this->mUserMock = $this->getMockBuilder( User::class )
26 ->disableOriginalConstructor()
27 ->getMock();
28
29 // Set up groups and rights
30 $this->mUserMock->expects( $this->any() )
31 ->method( 'getEffectiveGroups' )->will( $this->returnValue( [ '*', 'user' ] ) );
32
33 // Set up callback for User::getOptionKinds
34 $this->mUserMock->expects( $this->any() )
35 ->method( 'getOptionKinds' )->will( $this->returnCallback( [ $this, 'getOptionKinds' ] ) );
36
37 // No actual DB data
38 $this->mUserMock->expects( $this->any() )
39 ->method( 'getInstanceForUpdate' )->will( $this->returnValue( $this->mUserMock ) );
40
41 // Needs to return something
42 $this->mUserMock->method( 'getOptions' )
43 ->willReturn( [] );
44
45 // Create a new context
46 $this->mContext = new DerivativeContext( new RequestContext() );
47 $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) );
48 $this->mContext->setUser( $this->mUserMock );
49
50 $this->overrideUserPermissions( $this->mUserMock, [ 'editmyoptions' ] );
51 $main = new ApiMain( $this->mContext );
52
53 // Empty session
54 $this->mSession = [];
55
56 $this->mTested = new ApiOptions( $main, 'options' );
57
58 $this->mergeMwGlobalArrayValue( 'wgHooks', [
59 'GetPreferences' => [
60 [ $this, 'hookGetPreferences' ]
61 ]
62 ] );
63 $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', [
64 'testradio' => 'option1',
65 ] );
66 // Workaround for static caching in User::getDefaultOptions()
67 $this->setContentLang( Language::factory( 'qqq' ) );
68 }
69
70 public function hookGetPreferences( $user, &$preferences ) {
71 $preferences = [];
72
73 foreach ( [ 'name', 'willBeNull', 'willBeEmpty', 'willBeHappy' ] as $k ) {
74 $preferences[$k] = [
75 'type' => 'text',
76 'section' => 'test',
77 'label' => "\u{00A0}",
78 ];
79 }
80
81 $preferences['testmultiselect'] = [
82 'type' => 'multiselect',
83 'options' => [
84 'Test' => [
85 '<span dir="auto">Some HTML here for option 1</span>' => 'opt1',
86 '<span dir="auto">Some HTML here for option 2</span>' => 'opt2',
87 '<span dir="auto">Some HTML here for option 3</span>' => 'opt3',
88 '<span dir="auto">Some HTML here for option 4</span>' => 'opt4',
89 ],
90 ],
91 'section' => 'test',
92 'label' => "\u{00A0}",
93 'prefix' => 'testmultiselect-',
94 'default' => [],
95 ];
96
97 $preferences['testradio'] = [
98 'type' => 'radio',
99 'options' => [ 'Option 1' => 'option1', 'Option 2' => 'option2' ],
100 'section' => 'test',
101 ];
102 }
103
104 /**
105 * @param IContextSource $context
106 * @param array|null $options
107 *
108 * @return array
109 */
110 public function getOptionKinds( IContextSource $context, $options = null ) {
111 // Match with above.
112 $kinds = [
113 'name' => 'registered',
114 'willBeNull' => 'registered',
115 'willBeEmpty' => 'registered',
116 'willBeHappy' => 'registered',
117 'testradio' => 'registered',
118 'testmultiselect-opt1' => 'registered-multiselect',
119 'testmultiselect-opt2' => 'registered-multiselect',
120 'testmultiselect-opt3' => 'registered-multiselect',
121 'testmultiselect-opt4' => 'registered-multiselect',
122 'special' => 'special',
123 ];
124
125 if ( $options === null ) {
126 return $kinds;
127 }
128
129 $mapping = [];
130 foreach ( $options as $key => $value ) {
131 if ( isset( $kinds[$key] ) ) {
132 $mapping[$key] = $kinds[$key];
133 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
134 $mapping[$key] = 'userjs';
135 } else {
136 $mapping[$key] = 'unused';
137 }
138 }
139
140 return $mapping;
141 }
142
143 private function getSampleRequest( $custom = [] ) {
144 $request = [
145 'token' => '123ABC',
146 'change' => null,
147 'optionname' => null,
148 'optionvalue' => null,
149 ];
150
151 return array_merge( $request, $custom );
152 }
153
154 private function executeQuery( $request ) {
155 $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) );
156 $this->mTested->execute();
157
158 return $this->mTested->getResult()->getResultData( null, [ 'Strip' => 'all' ] );
159 }
160
161 /**
162 * @expectedException ApiUsageException
163 */
164 public function testNoToken() {
165 $request = $this->getSampleRequest( [ 'token' => null ] );
166
167 $this->executeQuery( $request );
168 }
169
170 public function testAnon() {
171 $this->mUserMock->expects( $this->once() )
172 ->method( 'isAnon' )
173 ->will( $this->returnValue( true ) );
174
175 try {
176 $request = $this->getSampleRequest();
177
178 $this->executeQuery( $request );
179 } catch ( ApiUsageException $e ) {
180 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'notloggedin' ) );
181 return;
182 }
183 $this->fail( "ApiUsageException was not thrown" );
184 }
185
186 public function testNoOptionname() {
187 try {
188 $request = $this->getSampleRequest( [ 'optionvalue' => '1' ] );
189
190 $this->executeQuery( $request );
191 } catch ( ApiUsageException $e ) {
192 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'nooptionname' ) );
193 return;
194 }
195 $this->fail( "ApiUsageException was not thrown" );
196 }
197
198 public function testNoChanges() {
199 $this->mUserMock->expects( $this->never() )
200 ->method( 'resetOptions' );
201
202 $this->mUserMock->expects( $this->never() )
203 ->method( 'setOption' );
204
205 $this->mUserMock->expects( $this->never() )
206 ->method( 'saveSettings' );
207
208 try {
209 $request = $this->getSampleRequest();
210
211 $this->executeQuery( $request );
212 } catch ( ApiUsageException $e ) {
213 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'nochanges' ) );
214 return;
215 }
216 $this->fail( "ApiUsageException was not thrown" );
217 }
218
219 public function testReset() {
220 $this->mUserMock->expects( $this->once() )
221 ->method( 'resetOptions' )
222 ->with( $this->equalTo( [ 'all' ] ) );
223
224 $this->mUserMock->expects( $this->never() )
225 ->method( 'setOption' );
226
227 $this->mUserMock->expects( $this->once() )
228 ->method( 'saveSettings' );
229
230 $request = $this->getSampleRequest( [ 'reset' => '' ] );
231
232 $response = $this->executeQuery( $request );
233
234 $this->assertEquals( self::$Success, $response );
235 }
236
237 public function testResetKinds() {
238 $this->mUserMock->expects( $this->once() )
239 ->method( 'resetOptions' )
240 ->with( $this->equalTo( [ 'registered' ] ) );
241
242 $this->mUserMock->expects( $this->never() )
243 ->method( 'setOption' );
244
245 $this->mUserMock->expects( $this->once() )
246 ->method( 'saveSettings' );
247
248 $request = $this->getSampleRequest( [ 'reset' => '', 'resetkinds' => 'registered' ] );
249
250 $response = $this->executeQuery( $request );
251
252 $this->assertEquals( self::$Success, $response );
253 }
254
255 public function testResetChangeOption() {
256 $this->mUserMock->expects( $this->once() )
257 ->method( 'resetOptions' );
258
259 $this->mUserMock->expects( $this->exactly( 2 ) )
260 ->method( 'setOption' )
261 ->withConsecutive(
262 [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ],
263 [ $this->equalTo( 'name' ), $this->equalTo( 'value' ) ]
264 );
265
266 $this->mUserMock->expects( $this->once() )
267 ->method( 'saveSettings' );
268
269 $args = [
270 'reset' => '',
271 'change' => 'willBeHappy=Happy',
272 'optionname' => 'name',
273 'optionvalue' => 'value'
274 ];
275
276 $response = $this->executeQuery( $this->getSampleRequest( $args ) );
277
278 $this->assertEquals( self::$Success, $response );
279 }
280
281 /**
282 * @dataProvider provideOptionManupulation
283 * @param array $params
284 * @param array $setOptions
285 * @param array|null $result
286 */
287 public function testOptionManupulation( array $params, array $setOptions, array $result = null,
288 $message = ''
289 ) {
290 $this->mUserMock->expects( $this->never() )
291 ->method( 'resetOptions' );
292
293 $this->mUserMock->expects( $this->exactly( count( $setOptions ) ) )
294 ->method( 'setOption' )
295 ->withConsecutive( ...$setOptions );
296
297 if ( $setOptions ) {
298 $this->mUserMock->expects( $this->once() )
299 ->method( 'saveSettings' );
300 } else {
301 $this->mUserMock->expects( $this->never() )
302 ->method( 'saveSettings' );
303 }
304
305 $request = $this->getSampleRequest( $params );
306 $response = $this->executeQuery( $request );
307
308 if ( !$result ) {
309 $result = self::$Success;
310 }
311 $this->assertEquals( $result, $response, $message );
312 }
313
314 public function provideOptionManupulation() {
315 return [
316 [
317 [ 'change' => 'userjs-option=1' ],
318 [ [ 'userjs-option', '1' ] ],
319 null,
320 'Setting userjs options',
321 ],
322 [
323 [ 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ],
324 [
325 [ 'willBeNull', null ],
326 [ 'willBeEmpty', '' ],
327 [ 'willBeHappy', 'Happy' ],
328 ],
329 null,
330 'Basic option setting',
331 ],
332 [
333 [ 'change' => 'testradio=option2' ],
334 [ [ 'testradio', 'option2' ] ],
335 null,
336 'Changing radio options',
337 ],
338 [
339 [ 'change' => 'testradio' ],
340 [ [ 'testradio', null ] ],
341 null,
342 'Resetting radio options',
343 ],
344 [
345 [ 'change' => 'unknownOption=1' ],
346 [],
347 [
348 'options' => 'success',
349 'warnings' => [
350 'options' => [
351 'warnings' => "Validation error for \"unknownOption\": not a valid preference."
352 ],
353 ],
354 ],
355 'Unrecognized options should be rejected',
356 ],
357 [
358 [ 'change' => 'special=1' ],
359 [],
360 [
361 'options' => 'success',
362 'warnings' => [
363 'options' => [
364 'warnings' => "Validation error for \"special\": cannot be set by this module."
365 ]
366 ]
367 ],
368 'Refuse setting special options',
369 ],
370 [
371 [
372 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
373 . 'testmultiselect-opt3=|testmultiselect-opt4=0'
374 ],
375 [
376 [ 'testmultiselect-opt1', true ],
377 [ 'testmultiselect-opt2', null ],
378 [ 'testmultiselect-opt3', false ],
379 [ 'testmultiselect-opt4', false ],
380 ],
381 null,
382 'Setting multiselect options',
383 ],
384 [
385 [ 'optionname' => 'name', 'optionvalue' => 'value' ],
386 [ [ 'name', 'value' ] ],
387 null,
388 'Setting options via optionname/optionvalue'
389 ],
390 [
391 [ 'optionname' => 'name' ],
392 [ [ 'name', null ] ],
393 null,
394 'Resetting options via optionname without optionvalue',
395 ],
396 ];
397 }
398 }