3 use MediaWiki\Block\DatabaseBlock
;
12 class ApiMoveTest
extends ApiTestCase
{
14 * @param string $from Prefixed name of source
15 * @param string $to Prefixed name of destination
16 * @param string $id Page id of the page to move
17 * @param array|string|null $opts Options: 'noredirect' to expect no redirect
19 protected function assertMoved( $from, $to, $id, $opts = null ) {
23 $fromTitle = Title
::newFromText( $from );
24 $toTitle = Title
::newFromText( $to );
26 $this->assertTrue( $toTitle->exists(),
27 "Destination {$toTitle->getPrefixedText()} does not exist" );
29 if ( in_array( 'noredirect', $opts ) ) {
30 $this->assertFalse( $fromTitle->exists(),
31 "Source {$fromTitle->getPrefixedText()} exists" );
33 $this->assertTrue( $fromTitle->exists(),
34 "Source {$fromTitle->getPrefixedText()} does not exist" );
35 $this->assertTrue( $fromTitle->isRedirect(),
36 "Source {$fromTitle->getPrefixedText()} is not a redirect" );
38 $target = Revision
::newFromTitle( $fromTitle )->getContent()->getRedirectTarget();
39 $this->assertSame( $toTitle->getPrefixedText(), $target->getPrefixedText() );
42 $this->assertSame( $id, $toTitle->getArticleID() );
46 * Shortcut function to create a page and return its id.
48 * @param string $name Page to create
49 * @return int ID of created page
51 protected function createPage( $name ) {
52 return $this->editPage( $name, 'Content' )->value
['revision']->getPage();
55 public function testFromWithFromid() {
56 $this->setExpectedException( ApiUsageException
::class,
57 'The parameters "from" and "fromid" can not be used together.' );
59 $this->doApiRequestWithToken( [
61 'from' => 'Some page',
63 'to' => 'Some other page',
67 public function testMove() {
68 $name = ucfirst( __FUNCTION__
);
70 $id = $this->createPage( $name );
72 $res = $this->doApiRequestWithToken( [
78 $this->assertMoved( $name, "$name 2", $id );
79 $this->assertArrayNotHasKey( 'warnings', $res[0] );
82 public function testMoveById() {
83 $name = ucfirst( __FUNCTION__
);
85 $id = $this->createPage( $name );
87 $res = $this->doApiRequestWithToken( [
93 $this->assertMoved( $name, "$name 2", $id );
94 $this->assertArrayNotHasKey( 'warnings', $res[0] );
97 public function testMoveNonexistent() {
98 $this->setExpectedException( ApiUsageException
::class,
99 "The page you specified doesn't exist." );
101 $this->doApiRequestWithToken( [
103 'from' => 'Nonexistent page',
104 'to' => 'Different page'
108 public function testMoveNonexistentId() {
109 $this->setExpectedException( ApiUsageException
::class,
110 'There is no page with ID 2147483647.' );
112 $this->doApiRequestWithToken( [
114 'fromid' => pow( 2, 31 ) - 1,
115 'to' => 'Different page',
119 public function testMoveToInvalidPageName() {
120 $this->setExpectedException( ApiUsageException
::class, 'Bad title "[".' );
122 $name = ucfirst( __FUNCTION__
);
123 $id = $this->createPage( $name );
126 $this->doApiRequestWithToken( [
132 $this->assertSame( $id, Title
::newFromText( $name )->getArticleID() );
136 public function testMoveWhileBlocked() {
137 $this->assertNull( DatabaseBlock
::newFromTarget( '127.0.0.1' ), 'Sanity check' );
139 $block = new DatabaseBlock( [
140 'address' => self
::$users['sysop']->getUser()->getName(),
141 'by' => self
::$users['sysop']->getUser()->getId(),
142 'reason' => 'Capriciousness',
143 'timestamp' => '19370101000000',
144 'expiry' => 'infinity',
145 'enableAutoblock' => true,
149 $name = ucfirst( __FUNCTION__
);
150 $id = $this->createPage( $name );
153 $this->doApiRequestWithToken( [
158 $this->fail( 'Expected exception not thrown' );
159 } catch ( ApiUsageException
$ex ) {
160 $this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
161 $this->assertNotNull( DatabaseBlock
::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
164 self
::$users['sysop']->getUser()->clearInstanceCache();
165 $this->assertSame( $id, Title
::newFromText( $name )->getArticleID() );
171 public function testPingLimiter() {
172 $this->setExpectedException( ApiUsageException
::class,
173 "You've exceeded your rate limit. Please wait some time and try again." );
175 $name = ucfirst( __FUNCTION__
);
177 $this->setMwGlobals( 'wgMainCacheType', 'hash' );
179 $this->mergeMwGlobalArrayValue( 'wgRateLimits',
180 [ 'move' => [ '&can-bypass' => false, 'user' => [ 1, 60 ] ] ] );
182 $id = $this->createPage( $name );
184 $res = $this->doApiRequestWithToken( [
190 $this->assertMoved( $name, "$name 2", $id );
191 $this->assertArrayNotHasKey( 'warnings', $res[0] );
194 $this->doApiRequestWithToken( [
200 $this->assertSame( $id, Title
::newFromText( "$name 2" )->getArticleID() );
201 $this->assertFalse( Title
::newFromText( "$name 3" )->exists(),
202 "\"$name 3\" should not exist" );
206 public function testTagsNoPermission() {
207 $this->setExpectedException( ApiUsageException
::class,
208 'You do not have permission to apply change tags along with your changes.' );
210 $name = ucfirst( __FUNCTION__
);
212 ChangeTags
::defineTag( 'custom tag' );
214 $this->setGroupPermissions( 'user', 'applychangetags', false );
216 $id = $this->createPage( $name );
219 $this->doApiRequestWithToken( [
223 'tags' => 'custom tag',
226 $this->assertSame( $id, Title
::newFromText( $name )->getArticleID() );
227 $this->assertFalse( Title
::newFromText( "$name 2" )->exists(),
228 "\"$name 2\" should not exist" );
232 public function testSelfMove() {
233 $this->setExpectedException( ApiUsageException
::class,
234 'The title is the same; cannot move a page over itself.' );
236 $name = ucfirst( __FUNCTION__
);
237 $this->createPage( $name );
239 $this->doApiRequestWithToken( [
246 public function testMoveTalk() {
247 $name = ucfirst( __FUNCTION__
);
249 $id = $this->createPage( $name );
250 $talkId = $this->createPage( "Talk:$name" );
252 $res = $this->doApiRequestWithToken( [
259 $this->assertMoved( $name, "$name 2", $id );
260 $this->assertMoved( "Talk:$name", "Talk:$name 2", $talkId );
262 $this->assertArrayNotHasKey( 'warnings', $res[0] );
265 public function testMoveTalkFailed() {
266 $name = ucfirst( __FUNCTION__
);
268 $id = $this->createPage( $name );
269 $talkId = $this->createPage( "Talk:$name" );
270 $talkDestinationId = $this->createPage( "Talk:$name 2" );
272 $res = $this->doApiRequestWithToken( [
279 $this->assertMoved( $name, "$name 2", $id );
280 $this->assertSame( $talkId, Title
::newFromText( "Talk:$name" )->getArticleID() );
281 $this->assertSame( $talkDestinationId,
282 Title
::newFromText( "Talk:$name 2" )->getArticleID() );
283 $this->assertSame( [ [
284 'message' => 'articleexists',
286 'code' => 'articleexists',
288 ] ], $res[0]['move']['talkmove-errors'] );
290 $this->assertArrayNotHasKey( 'warnings', $res[0] );
293 public function testMoveSubpages() {
294 $name = ucfirst( __FUNCTION__
);
296 $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN
=> true ] );
297 $this->resetServices();
299 $pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
301 foreach ( array_merge( $pages, [ "$name/error", "$name 2/error" ] ) as $page ) {
302 $ids[$page] = $this->createPage( $page );
305 $res = $this->doApiRequestWithToken( [
310 'movesubpages' => '',
313 foreach ( $pages as $page ) {
314 $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
317 $this->assertSame( $ids["$name/error"],
318 Title
::newFromText( "$name/error" )->getArticleID() );
319 $this->assertSame( $ids["$name 2/error"],
320 Title
::newFromText( "$name 2/error" )->getArticleID() );
322 $results = array_merge( $res[0]['move']['subpages'], $res[0]['move']['subpages-talk'] );
323 foreach ( $results as $arr ) {
324 if ( $arr['from'] === "$name/error" ) {
325 $this->assertSame( [ [
326 'message' => 'articleexists',
328 'code' => 'articleexists',
330 ] ], $arr['errors'] );
332 $this->assertSame( str_replace( $name, "$name 2", $arr['from'] ), $arr['to'] );
334 $this->assertCount( 2, $arr );
337 $this->assertArrayNotHasKey( 'warnings', $res[0] );
340 public function testMoveNoPermission() {
341 $this->setExpectedException( ApiUsageException
::class,
342 'You must be a registered user and [[Special:UserLogin|logged in]] to move a page.' );
344 $name = ucfirst( __FUNCTION__
);
346 $id = $this->createPage( $name );
351 $this->doApiRequestWithToken( [
357 $this->assertSame( $id, Title
::newFromText( "$name" )->getArticleID() );
358 $this->assertFalse( Title
::newFromText( "$name 2" )->exists(),
359 "\"$name 2\" should not exist" );
363 public function testSuppressRedirect() {
364 $name = ucfirst( __FUNCTION__
);
366 $id = $this->createPage( $name );
368 $res = $this->doApiRequestWithToken( [
375 $this->assertMoved( $name, "$name 2", $id, 'noredirect' );
376 $this->assertArrayNotHasKey( 'warnings', $res[0] );
379 public function testSuppressRedirectNoPermission() {
380 $name = ucfirst( __FUNCTION__
);
382 $this->setGroupPermissions( 'sysop', 'suppressredirect', false );
383 $id = $this->createPage( $name );
385 $res = $this->doApiRequestWithToken( [
392 $this->assertMoved( $name, "$name 2", $id );
393 $this->assertArrayNotHasKey( 'warnings', $res[0] );
396 public function testMoveSubpagesError() {
397 $name = ucfirst( __FUNCTION__
);
399 // Subpages are allowed in talk but not main
400 $idBase = $this->createPage( "Talk:$name" );
401 $idSub = $this->createPage( "Talk:$name/1" );
403 $res = $this->doApiRequestWithToken( [
405 'from' => "Talk:$name",
407 'movesubpages' => '',
410 $this->assertMoved( "Talk:$name", $name, $idBase );
411 $this->assertSame( $idSub, Title
::newFromText( "Talk:$name/1" )->getArticleID() );
412 $this->assertFalse( Title
::newFromText( "$name/1" )->exists(),
413 "\"$name/1\" should not exist" );
415 $this->assertSame( [ 'errors' => [ [
416 'message' => 'namespace-nosubpages',
418 'code' => 'namespace-nosubpages',
420 ] ] ], $res[0]['move']['subpages'] );
422 $this->assertArrayNotHasKey( 'warnings', $res[0] );