Merge "Action::checkCanExecute should only block an Action if the user is sitewide...
[lhc/web/wiklou.git] / tests / phpunit / includes / actions / ActionTest.php
1 <?php
2
3 use MediaWiki\Block\Restriction\PageRestriction;
4
5 /**
6 * @covers Action
7 *
8 * @group Action
9 * @group Database
10 *
11 * @license GPL-2.0-or-later
12 * @author Thiemo Kreuz
13 */
14 class ActionTest extends MediaWikiTestCase {
15
16 protected function setUp() {
17 parent::setUp();
18
19 $context = $this->getContext();
20 $this->setMwGlobals( 'wgActions', [
21 'null' => null,
22 'disabled' => false,
23 'view' => true,
24 'edit' => true,
25 'revisiondelete' => SpecialPageAction::class,
26 'dummy' => true,
27 'access' => 'ControlledAccessDummyAction',
28 'unblock' => 'RequiresUnblockDummyAction',
29 'string' => 'NamedDummyAction',
30 'declared' => 'NonExistingClassName',
31 'callable' => [ $this, 'dummyActionCallback' ],
32 'object' => new InstantiatedDummyAction( $context->getWikiPage(), $context ),
33 ] );
34 }
35
36 private function getPage() {
37 return WikiPage::factory( Title::makeTitle( 0, 'Title' ) );
38 }
39
40 private function getContext( $requestedAction = null ) {
41 $request = new FauxRequest( [ 'action' => $requestedAction ] );
42
43 $context = new DerivativeContext( RequestContext::getMain() );
44 $context->setRequest( $request );
45 $context->setWikiPage( $this->getPage() );
46
47 return $context;
48 }
49
50 public function actionProvider() {
51 return [
52 [ 'dummy', 'DummyAction' ],
53 [ 'string', 'NamedDummyAction' ],
54 [ 'callable', 'CalledDummyAction' ],
55 [ 'object', 'InstantiatedDummyAction' ],
56
57 // Capitalization is ignored
58 [ 'DUMMY', 'DummyAction' ],
59 [ 'STRING', 'NamedDummyAction' ],
60
61 // Null and non-existing values
62 [ 'null', null ],
63 [ 'undeclared', null ],
64 [ '', null ],
65 [ false, null ],
66 ];
67 }
68
69 /**
70 * @dataProvider actionProvider
71 * @param string $requestedAction
72 * @param string|null $expected
73 */
74 public function testActionExists( $requestedAction, $expected ) {
75 $exists = Action::exists( $requestedAction );
76
77 $this->assertSame( $expected !== null, $exists );
78 }
79
80 public function testActionExists_doesNotRequireInstantiation() {
81 // The method is not supposed to check if the action can be instantiated.
82 $exists = Action::exists( 'declared' );
83
84 $this->assertTrue( $exists );
85 }
86
87 /**
88 * @dataProvider actionProvider
89 * @param string $requestedAction
90 * @param string|null $expected
91 */
92 public function testGetActionName( $requestedAction, $expected ) {
93 $context = $this->getContext( $requestedAction );
94 $actionName = Action::getActionName( $context );
95
96 $this->assertEquals( $expected ?: 'nosuchaction', $actionName );
97 }
98
99 public function testGetActionName_editredlinkWorkaround() {
100 // See https://phabricator.wikimedia.org/T22966
101 $context = $this->getContext( 'editredlink' );
102 $actionName = Action::getActionName( $context );
103
104 $this->assertEquals( 'edit', $actionName );
105 }
106
107 public function testGetActionName_historysubmitWorkaround() {
108 // See https://phabricator.wikimedia.org/T22966
109 $context = $this->getContext( 'historysubmit' );
110 $actionName = Action::getActionName( $context );
111
112 $this->assertEquals( 'view', $actionName );
113 }
114
115 public function testGetActionName_revisiondeleteWorkaround() {
116 // See https://phabricator.wikimedia.org/T22966
117 $context = $this->getContext( 'historysubmit' );
118 $context->getRequest()->setVal( 'revisiondelete', true );
119 $actionName = Action::getActionName( $context );
120
121 $this->assertEquals( 'revisiondelete', $actionName );
122 }
123
124 public function testGetActionName_whenCanNotUseWikiPage_defaultsToView() {
125 $request = new FauxRequest( [ 'action' => 'edit' ] );
126 $context = new DerivativeContext( RequestContext::getMain() );
127 $context->setRequest( $request );
128 $actionName = Action::getActionName( $context );
129
130 $this->assertEquals( 'view', $actionName );
131 }
132
133 /**
134 * @dataProvider actionProvider
135 * @param string $requestedAction
136 * @param string|null $expected
137 */
138 public function testActionFactory( $requestedAction, $expected ) {
139 $context = $this->getContext();
140 $action = Action::factory( $requestedAction, $context->getWikiPage(), $context );
141
142 $this->assertType( $expected ?: 'null', $action );
143 }
144
145 public function testNull_doesNotExist() {
146 $exists = Action::exists( null );
147
148 $this->assertFalse( $exists );
149 }
150
151 public function testNull_defaultsToView() {
152 $context = $this->getContext( null );
153 $actionName = Action::getActionName( $context );
154
155 $this->assertEquals( 'view', $actionName );
156 }
157
158 public function testNull_canNotBeInstantiated() {
159 $page = $this->getPage();
160 $action = Action::factory( null, $page );
161
162 $this->assertNull( $action );
163 }
164
165 public function testDisabledAction_exists() {
166 $exists = Action::exists( 'disabled' );
167
168 $this->assertTrue( $exists );
169 }
170
171 public function testDisabledAction_isNotResolved() {
172 $context = $this->getContext( 'disabled' );
173 $actionName = Action::getActionName( $context );
174
175 $this->assertEquals( 'nosuchaction', $actionName );
176 }
177
178 public function testDisabledAction_factoryReturnsFalse() {
179 $page = $this->getPage();
180 $action = Action::factory( 'disabled', $page );
181
182 $this->assertFalse( $action );
183 }
184
185 public function dummyActionCallback() {
186 $context = $this->getContext();
187 return new CalledDummyAction( $context->getWikiPage(), $context );
188 }
189
190 public function testCanExecute() {
191 $user = $this->getTestUser()->getUser();
192 $user->mRights = [ 'access' ];
193 $action = Action::factory( 'access', $this->getPage(), $this->getContext() );
194 $this->assertNull( $action->canExecute( $user ) );
195 }
196
197 public function testCanExecuteNoRight() {
198 $user = $this->getTestUser()->getUser();
199 $user->mRights = [];
200 $action = Action::factory( 'access', $this->getPage(), $this->getContext() );
201
202 try {
203 $action->canExecute( $user );
204 } catch ( Exception $e ) {
205 $this->assertInstanceOf( PermissionsError::class, $e );
206 }
207 }
208
209 public function testCanExecuteRequiresUnblock() {
210 $user = $this->getTestUser()->getUser();
211 $user->mRights = [];
212
213 $page = $this->getExistingTestPage();
214 $action = Action::factory( 'unblock', $page, $this->getContext() );
215
216 $block = new Block( [
217 'address' => $user,
218 'by' => $this->getTestSysop()->getUser()->getId(),
219 'expiry' => 'infinity',
220 'sitewide' => false,
221 ] );
222 $block->setRestrictions( [
223 new PageRestriction( 0, $page->getTitle()->getArticleID() ),
224 ] );
225
226 $block->insert();
227
228 try {
229 $action->canExecute( $user );
230 } catch ( Exception $e ) {
231 $this->assertInstanceOf( UserBlockedError::class, $e );
232 }
233
234 $block->delete();
235 }
236
237 }
238
239 class DummyAction extends Action {
240
241 public function getName() {
242 return static::class;
243 }
244
245 public function show() {
246 }
247
248 public function execute() {
249 }
250
251 public function canExecute( User $user ) {
252 return $this->checkCanExecute( $user );
253 }
254 }
255
256 class NamedDummyAction extends DummyAction {
257 }
258
259 class CalledDummyAction extends DummyAction {
260 }
261
262 class InstantiatedDummyAction extends DummyAction {
263 }
264
265 class ControlledAccessDummyAction extends DummyAction {
266 public function getRestriction() {
267 return 'access';
268 }
269 }
270
271 class RequiresUnblockDummyAction extends DummyAction {
272 public function requiresUnblock() {
273 return true;
274 }
275 }