Merge "output: Narrow Title type hint to LinkTarget"
[lhc/web/wiklou.git] / tests / phpunit / includes / preferences / DefaultPreferencesFactoryTest.php
1 <?php
2
3 use MediaWiki\Auth\AuthManager;
4 use MediaWiki\MediaWikiServices;
5 use MediaWiki\Permissions\PermissionManager;
6 use MediaWiki\Preferences\DefaultPreferencesFactory;
7 use Wikimedia\TestingAccessWrapper;
8
9 /**
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * http://www.gnu.org/copyleft/gpl.html
24 *
25 * @file
26 */
27
28 /**
29 * @group Preferences
30 */
31 class DefaultPreferencesFactoryTest extends \MediaWikiTestCase {
32 use TestAllServiceOptionsUsed;
33
34 /** @var IContextSource */
35 protected $context;
36
37 /** @var Config */
38 protected $config;
39
40 public function setUp() {
41 parent::setUp();
42 $this->context = new RequestContext();
43 $this->context->setTitle( Title::newFromText( self::class ) );
44
45 $services = MediaWikiServices::getInstance();
46
47 $this->setMwGlobals( 'wgParser', $services->getParserFactory()->create() );
48 $this->config = $services->getMainConfig();
49 }
50
51 /**
52 * Get a basic PreferencesFactory for testing with.
53 * @param PermissionManager|null $manager
54 * @return DefaultPreferencesFactory
55 */
56 protected function getPreferencesFactory( PermissionManager $manager = null ) {
57 $mockNsInfo = $this->createMock( NamespaceInfo::class );
58 $mockNsInfo->method( 'getValidNamespaces' )->willReturn( [
59 NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK
60 ] );
61 $mockNsInfo->expects( $this->never() )
62 ->method( $this->anythingBut( 'getValidNamespaces', '__destruct' ) );
63
64 $mockPermissionManager = $manager ?? $this->createMock( PermissionManager::class );
65
66 return new DefaultPreferencesFactory(
67 new LoggedServiceOptions( self::$serviceOptionsAccessLog,
68 DefaultPreferencesFactory::$constructorOptions, $this->config ),
69 new Language(),
70 AuthManager::singleton(),
71 MediaWikiServices::getInstance()->getLinkRenderer(),
72 $mockNsInfo,
73 $mockPermissionManager
74 );
75 }
76
77 /**
78 * @covers MediaWiki\Preferences\DefaultPreferencesFactory::getForm()
79 */
80 public function testGetForm() {
81 $this->setTemporaryHook( 'GetPreferences', null );
82
83 $testUser = $this->getTestUser();
84 $form = $this->getPreferencesFactory()->getForm( $testUser->getUser(), $this->context );
85 $this->assertInstanceOf( PreferencesFormOOUI::class, $form );
86 $this->assertCount( 5, $form->getPreferenceSections() );
87 }
88
89 /**
90 * CSS classes for emailauthentication preference field when there's no email.
91 * @see https://phabricator.wikimedia.org/T36302
92 * @covers MediaWiki\Preferences\DefaultPreferencesFactory::profilePreferences()
93 * @dataProvider emailAuthenticationProvider
94 */
95 public function testEmailAuthentication( $user, $cssClass ) {
96 $pm = $this->createMock( PermissionManager::class );
97 $pm->method( 'userHasRight' )->willReturn( true );
98 $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $user, $this->context );
99 $this->assertArrayHasKey( 'cssclass', $prefs['emailauthentication'] );
100 $this->assertEquals( $cssClass, $prefs['emailauthentication']['cssclass'] );
101 }
102
103 /**
104 * @covers MediaWiki\Preferences\DefaultPreferencesFactory::renderingPreferences()
105 */
106 public function testShowRollbackConfIsHiddenForUsersWithoutRollbackRights() {
107 $userMock = $this->getMockBuilder( User::class )
108 ->disableOriginalConstructor()
109 ->getMock();
110 $userMock->method( 'getEffectiveGroups' )
111 ->willReturn( [] );
112 $userMock->method( 'getGroupMemberships' )
113 ->willReturn( [] );
114 $userMock->method( 'getOptions' )
115 ->willReturn( [ 'test' => 'yes' ] );
116 $pm = $this->createMock( PermissionManager::class );
117 $pm->method( 'userHasRight' )
118 ->will( $this->returnValueMap( [
119 [ $userMock, 'editmyoptions', true ]
120 ] ) );
121 $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $userMock, $this->context );
122 $this->assertArrayNotHasKey( 'showrollbackconfirmation', $prefs );
123 }
124
125 /**
126 * @covers MediaWiki\Preferences\DefaultPreferencesFactory::renderingPreferences()
127 */
128 public function testShowRollbackConfIsShownForUsersWithRollbackRights() {
129 $userMock = $this->getMockBuilder( User::class )
130 ->disableOriginalConstructor()
131 ->getMock();
132 $userMock->method( 'getEffectiveGroups' )
133 ->willReturn( [] );
134 $userMock->method( 'getGroupMemberships' )
135 ->willReturn( [] );
136 $userMock->method( 'getOptions' )
137 ->willReturn( [ 'test' => 'yes' ] );
138 $pm = $this->createMock( PermissionManager::class );
139 $pm->method( 'userHasRight' )
140 ->will( $this->returnValueMap( [
141 [ $userMock, 'editmyoptions', true ],
142 [ $userMock, 'rollback', true ]
143 ] ) );
144 $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $userMock, $this->context );
145 $this->assertArrayHasKey( 'showrollbackconfirmation', $prefs );
146 $this->assertEquals(
147 'rendering/advancedrendering',
148 $prefs['showrollbackconfirmation']['section']
149 );
150 }
151
152 public function emailAuthenticationProvider() {
153 $userNoEmail = new User;
154 $userEmailUnauthed = new User;
155 $userEmailUnauthed->setEmail( 'noauth@example.org' );
156 $userEmailAuthed = new User;
157 $userEmailAuthed->setEmail( 'noauth@example.org' );
158 $userEmailAuthed->setEmailAuthenticationTimestamp( wfTimestamp() );
159 return [
160 [ $userNoEmail, 'mw-email-none' ],
161 [ $userEmailUnauthed, 'mw-email-not-authenticated' ],
162 [ $userEmailAuthed, 'mw-email-authenticated' ],
163 ];
164 }
165
166 /**
167 * Test that PreferencesFormPreSave hook has correct data:
168 * - user Object is passed
169 * - oldUserOptions contains previous user options (before save)
170 * - formData and User object have set up new properties
171 *
172 * @see https://phabricator.wikimedia.org/T169365
173 * @covers MediaWiki\Preferences\DefaultPreferencesFactory::submitForm()
174 */
175 public function testPreferencesFormPreSaveHookHasCorrectData() {
176 $oldOptions = [
177 'test' => 'abc',
178 'option' => 'old'
179 ];
180 $newOptions = [
181 'test' => 'abc',
182 'option' => 'new'
183 ];
184 $configMock = new HashConfig( [
185 'HiddenPrefs' => []
186 ] );
187 $form = $this->getMockBuilder( PreferencesFormOOUI::class )
188 ->disableOriginalConstructor()
189 ->getMock();
190
191 $userMock = $this->getMockBuilder( User::class )
192 ->disableOriginalConstructor()
193 ->getMock();
194 $userMock->method( 'getOptions' )
195 ->willReturn( $oldOptions );
196
197 $userMock->expects( $this->exactly( 2 ) )
198 ->method( 'setOption' )
199 ->withConsecutive(
200 [ $this->equalTo( 'test' ), $this->equalTo( $newOptions[ 'test' ] ) ],
201 [ $this->equalTo( 'option' ), $this->equalTo( $newOptions[ 'option' ] ) ]
202 );
203
204 $form->method( 'getModifiedUser' )
205 ->willReturn( $userMock );
206
207 $form->method( 'getContext' )
208 ->willReturn( $this->context );
209
210 $form->method( 'getConfig' )
211 ->willReturn( $configMock );
212
213 $pm = $this->createMock( PermissionManager::class );
214 $pm->method( 'userHasAnyRight' )
215 ->will( $this->returnValueMap( [
216 [ $userMock, 'editmyprivateinfo', 'editmyoptions', true ]
217 ] ) );
218 $pm->method( 'userHasRight' )
219 ->will( $this->returnValueMap( [
220 [ $userMock, 'editmyoptions', true ]
221 ] ) );
222
223 $this->setTemporaryHook( 'PreferencesFormPreSave',
224 function ( $formData, $form, $user, &$result, $oldUserOptions )
225 use ( $newOptions, $oldOptions, $userMock ) {
226 $this->assertSame( $userMock, $user );
227 foreach ( $newOptions as $option => $value ) {
228 $this->assertSame( $value, $formData[ $option ] );
229 }
230 foreach ( $oldOptions as $option => $value ) {
231 $this->assertSame( $value, $oldUserOptions[ $option ] );
232 }
233 $this->assertEquals( true, $result );
234 }
235 );
236
237 /** @var DefaultPreferencesFactory $factory */
238 $factory = TestingAccessWrapper::newFromObject( $this->getPreferencesFactory( $pm ) );
239 $factory->saveFormData( $newOptions, $form, [] );
240 }
241
242 /**
243 * The rclimit preference should accept non-integer input and filter it to become an integer.
244 *
245 * @covers \MediaWiki\Preferences\DefaultPreferencesFactory::saveFormData
246 */
247 public function testIntvalFilter() {
248 // Test a string with leading zeros (i.e. not octal) and spaces.
249 $this->context->getRequest()->setVal( 'wprclimit', ' 0012 ' );
250 $user = new User;
251 $pm = $this->createMock( PermissionManager::class );
252 $pm->method( 'userHasAnyRight' )
253 ->willReturn( true );
254 $pm->method( 'userHasRight' )
255 ->will( $this->returnValueMap( [
256 [ $user, 'editmyoptions', true ]
257 ] ) );
258 $form = $this->getPreferencesFactory( $pm )->getForm( $user, $this->context );
259 $form->show();
260 $form->trySubmit();
261 $this->assertEquals( 12, $user->getOption( 'rclimit' ) );
262 }
263
264 /**
265 * @coversNothing
266 */
267 public function testAllServiceOptionsUsed() {
268 $this->assertAllServiceOptionsUsed( [ 'EnotifMinorEdits', 'EnotifRevealEditorAddress' ] );
269 }
270 }