10 class ApiOptionsTest
extends MediaWikiLangTestCase
{
12 /** @var PHPUnit_Framework_MockObject_MockObject */
14 /** @var ApiOptions */
17 /** @var DerivativeContext */
20 private static $Success = [ 'options' => 'success' ];
22 protected function setUp() {
25 $this->mUserMock
= $this->getMockBuilder( User
::class )
26 ->disableOriginalConstructor()
29 // Set up groups and rights
30 $this->mUserMock
->expects( $this->any() )
31 ->method( 'getEffectiveGroups' )->will( $this->returnValue( [ '*', 'user' ] ) );
33 // Set up callback for User::getOptionKinds
34 $this->mUserMock
->expects( $this->any() )
35 ->method( 'getOptionKinds' )->will( $this->returnCallback( [ $this, 'getOptionKinds' ] ) );
38 $this->mUserMock
->expects( $this->any() )
39 ->method( 'getInstanceForUpdate' )->will( $this->returnValue( $this->mUserMock
) );
41 // Needs to return something
42 $this->mUserMock
->method( 'getOptions' )
45 // DefaultPreferencesFactory calls a ton of user methods, but we still want to list all of
46 // them in case bugs are caused by unexpected things returning null that shouldn't.
47 $this->mUserMock
->expects( $this->never() )->method( $this->anythingBut(
48 'getEffectiveGroups', 'getOptionKinds', 'getInstanceForUpdate', 'getOptions', 'getId',
49 'isAnon', 'getRequest', 'isLoggedIn', 'getName', 'getGroupMemberships', 'getEditCount',
50 'getRegistration', 'isAllowed', 'getRealName', 'getOption', 'getStubThreshold',
51 'getBoolOption', 'getEmail', 'getDatePreference', 'useRCPatrol', 'useNPPatrol',
52 'setOption', 'saveSettings', 'resetOptions', 'isRegistered'
55 // Create a new context
56 $this->mContext
= new DerivativeContext( new RequestContext() );
57 $this->mContext
->getContext()->setTitle( Title
::newFromText( 'Test' ) );
58 $this->mContext
->setUser( $this->mUserMock
);
60 $this->overrideUserPermissions( $this->mUserMock
, [ 'editmyoptions' ] );
61 $main = new ApiMain( $this->mContext
);
66 $this->mTested
= new ApiOptions( $main, 'options' );
68 $this->mergeMwGlobalArrayValue( 'wgHooks', [
70 [ $this, 'hookGetPreferences' ]
73 $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', [
74 'testradio' => 'option1',
76 // Workaround for static caching in User::getDefaultOptions()
77 $this->setContentLang( Language
::factory( 'qqq' ) );
80 public function hookGetPreferences( $user, &$preferences ) {
83 foreach ( [ 'name', 'willBeNull', 'willBeEmpty', 'willBeHappy' ] as $k ) {
87 'label' => "\u{00A0}",
91 $preferences['testmultiselect'] = [
92 'type' => 'multiselect',
95 '<span dir="auto">Some HTML here for option 1</span>' => 'opt1',
96 '<span dir="auto">Some HTML here for option 2</span>' => 'opt2',
97 '<span dir="auto">Some HTML here for option 3</span>' => 'opt3',
98 '<span dir="auto">Some HTML here for option 4</span>' => 'opt4',
102 'label' => "\u{00A0}",
103 'prefix' => 'testmultiselect-',
107 $preferences['testradio'] = [
109 'options' => [ 'Option 1' => 'option1', 'Option 2' => 'option2' ],
115 * @param IContextSource $context
116 * @param array|null $options
120 public function getOptionKinds( IContextSource
$context, $options = null ) {
123 'name' => 'registered',
124 'willBeNull' => 'registered',
125 'willBeEmpty' => 'registered',
126 'willBeHappy' => 'registered',
127 'testradio' => 'registered',
128 'testmultiselect-opt1' => 'registered-multiselect',
129 'testmultiselect-opt2' => 'registered-multiselect',
130 'testmultiselect-opt3' => 'registered-multiselect',
131 'testmultiselect-opt4' => 'registered-multiselect',
132 'special' => 'special',
135 if ( $options === null ) {
140 foreach ( $options as $key => $value ) {
141 if ( isset( $kinds[$key] ) ) {
142 $mapping[$key] = $kinds[$key];
143 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
144 $mapping[$key] = 'userjs';
146 $mapping[$key] = 'unused';
153 private function getSampleRequest( $custom = [] ) {
157 'optionname' => null,
158 'optionvalue' => null,
161 return array_merge( $request, $custom );
164 private function executeQuery( $request ) {
165 $this->mContext
->setRequest( new FauxRequest( $request, true, $this->mSession
) );
166 $this->mUserMock
->method( 'getRequest' )->willReturn( $this->mContext
->getRequest() );
168 $this->mTested
->execute();
170 return $this->mTested
->getResult()->getResultData( null, [ 'Strip' => 'all' ] );
174 * @expectedException ApiUsageException
176 public function testNoToken() {
177 $request = $this->getSampleRequest( [ 'token' => null ] );
179 $this->executeQuery( $request );
182 public function testAnon() {
183 $this->mUserMock
->expects( $this->once() )
185 ->will( $this->returnValue( true ) );
188 $request = $this->getSampleRequest();
190 $this->executeQuery( $request );
191 } catch ( ApiUsageException
$e ) {
192 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $e, 'notloggedin' ) );
195 $this->fail( "ApiUsageException was not thrown" );
198 public function testNoOptionname() {
200 $request = $this->getSampleRequest( [ 'optionvalue' => '1' ] );
202 $this->executeQuery( $request );
203 } catch ( ApiUsageException
$e ) {
204 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $e, 'nooptionname' ) );
207 $this->fail( "ApiUsageException was not thrown" );
210 public function testNoChanges() {
211 $this->mUserMock
->expects( $this->never() )
212 ->method( 'resetOptions' );
214 $this->mUserMock
->expects( $this->never() )
215 ->method( 'setOption' );
217 $this->mUserMock
->expects( $this->never() )
218 ->method( 'saveSettings' );
221 $request = $this->getSampleRequest();
223 $this->executeQuery( $request );
224 } catch ( ApiUsageException
$e ) {
225 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $e, 'nochanges' ) );
228 $this->fail( "ApiUsageException was not thrown" );
231 public function testReset() {
232 $this->mUserMock
->expects( $this->once() )
233 ->method( 'resetOptions' )
234 ->with( $this->equalTo( [ 'all' ] ) );
236 $this->mUserMock
->expects( $this->never() )
237 ->method( 'setOption' );
239 $this->mUserMock
->expects( $this->once() )
240 ->method( 'saveSettings' );
242 $request = $this->getSampleRequest( [ 'reset' => '' ] );
244 $response = $this->executeQuery( $request );
246 $this->assertEquals( self
::$Success, $response );
249 public function testResetKinds() {
250 $this->mUserMock
->expects( $this->once() )
251 ->method( 'resetOptions' )
252 ->with( $this->equalTo( [ 'registered' ] ) );
254 $this->mUserMock
->expects( $this->never() )
255 ->method( 'setOption' );
257 $this->mUserMock
->expects( $this->once() )
258 ->method( 'saveSettings' );
260 $request = $this->getSampleRequest( [ 'reset' => '', 'resetkinds' => 'registered' ] );
262 $response = $this->executeQuery( $request );
264 $this->assertEquals( self
::$Success, $response );
267 public function testResetChangeOption() {
268 $this->mUserMock
->expects( $this->once() )
269 ->method( 'resetOptions' );
271 $this->mUserMock
->expects( $this->exactly( 2 ) )
272 ->method( 'setOption' )
274 [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ],
275 [ $this->equalTo( 'name' ), $this->equalTo( 'value' ) ]
278 $this->mUserMock
->expects( $this->once() )
279 ->method( 'saveSettings' );
283 'change' => 'willBeHappy=Happy',
284 'optionname' => 'name',
285 'optionvalue' => 'value'
288 $response = $this->executeQuery( $this->getSampleRequest( $args ) );
290 $this->assertEquals( self
::$Success, $response );
294 * @dataProvider provideOptionManupulation
295 * @param array $params
296 * @param array $setOptions
297 * @param array|null $result
299 public function testOptionManupulation( array $params, array $setOptions, array $result = null,
302 $this->mUserMock
->expects( $this->never() )
303 ->method( 'resetOptions' );
305 $this->mUserMock
->expects( $this->exactly( count( $setOptions ) ) )
306 ->method( 'setOption' )
307 ->withConsecutive( ...$setOptions );
310 $this->mUserMock
->expects( $this->once() )
311 ->method( 'saveSettings' );
313 $this->mUserMock
->expects( $this->never() )
314 ->method( 'saveSettings' );
317 $request = $this->getSampleRequest( $params );
318 $response = $this->executeQuery( $request );
321 $result = self
::$Success;
323 $this->assertEquals( $result, $response, $message );
326 public function provideOptionManupulation() {
329 [ 'change' => 'userjs-option=1' ],
330 [ [ 'userjs-option', '1' ] ],
332 'Setting userjs options',
335 [ 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ],
337 [ 'willBeNull', null ],
338 [ 'willBeEmpty', '' ],
339 [ 'willBeHappy', 'Happy' ],
342 'Basic option setting',
345 [ 'change' => 'testradio=option2' ],
346 [ [ 'testradio', 'option2' ] ],
348 'Changing radio options',
351 [ 'change' => 'testradio' ],
352 [ [ 'testradio', null ] ],
354 'Resetting radio options',
357 [ 'change' => 'unknownOption=1' ],
360 'options' => 'success',
363 'warnings' => "Validation error for \"unknownOption\": not a valid preference."
367 'Unrecognized options should be rejected',
370 [ 'change' => 'special=1' ],
373 'options' => 'success',
376 'warnings' => "Validation error for \"special\": cannot be set by this module."
380 'Refuse setting special options',
384 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
385 . 'testmultiselect-opt3=|testmultiselect-opt4=0'
388 [ 'testmultiselect-opt1', true ],
389 [ 'testmultiselect-opt2', null ],
390 [ 'testmultiselect-opt3', false ],
391 [ 'testmultiselect-opt4', false ],
394 'Setting multiselect options',
397 [ 'optionname' => 'name', 'optionvalue' => 'value' ],
398 [ [ 'name', 'value' ] ],
400 'Setting options via optionname/optionvalue'
403 [ 'optionname' => 'name' ],
404 [ [ 'name', null ] ],
406 'Resetting options via optionname without optionvalue',