wiki.techinc.nl/tests/phpunit/includes/api/ApiMoveTest.php
daniel c6c62e2c8f PHPUnit: enable main object cache in tests
This sets MainCacheType to 'hash', so the main cache is functional
within test cases. Between tests we are already calling ObjectCache::clear,
so the cache will not leak data into other test cases.

It turned out that the way we have been overriding configuration
settings in MediaWikiIntegrationTestCase is insufficient, since the
overrides would only apply in the main Config object, not in global
variables. This means they could not be overwritten with setMwGlobals.
So this patch also changes how we apply config overrides for test setup.

Config overrides that happen during tests work as before.

Depends-On: Ic36e92b73f22fd64e2d0b680d8e900f598158460
Change-Id: Ia60cb0bc18b235f23d9778da8241f65accfc0d35
2022-07-07 16:26:03 +10:00

498 lines
14 KiB
PHP

<?php
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Revision\SlotRecord;
/**
* @group API
* @group Database
* @group medium
*
* @covers ApiMove
*/
class ApiMoveTest extends ApiTestCase {
protected function setUp(): void {
parent::setUp();
$this->tablesUsed = array_merge(
$this->tablesUsed,
[ 'watchlist', 'watchlist_expiry' ]
);
$this->setMwGlobals( [
'wgWatchlistExpiry' => true,
] );
}
/**
* @param string $from Prefixed name of source
* @param string $to Prefixed name of destination
* @param string $id Page id of the page to move
* @param array|string|null $opts Options: 'noredirect' to expect no redirect
*/
protected function assertMoved( $from, $to, $id, $opts = null ) {
$opts = (array)$opts;
Title::clearCaches();
$fromTitle = Title::newFromText( $from );
$toTitle = Title::newFromText( $to );
$this->assertTrue( $toTitle->exists(),
"Destination {$toTitle->getPrefixedText()} does not exist" );
if ( in_array( 'noredirect', $opts ) ) {
$this->assertFalse( $fromTitle->exists(),
"Source {$fromTitle->getPrefixedText()} exists" );
} else {
$this->assertTrue( $fromTitle->exists(),
"Source {$fromTitle->getPrefixedText()} does not exist" );
$this->assertTrue( $fromTitle->isRedirect(),
"Source {$fromTitle->getPrefixedText()} is not a redirect" );
$target = $this->getServiceContainer()
->getRevisionLookup()
->getRevisionByTitle( $fromTitle )
->getContent( SlotRecord::MAIN )
->getRedirectTarget();
$this->assertSame( $toTitle->getPrefixedText(), $target->getPrefixedText() );
}
$this->assertSame( $id, $toTitle->getArticleID() );
}
/**
* Shortcut function to create a page and return its id.
*
* @param string $name Page to create
* @return int ID of created page
*/
protected function createPage( $name ) {
return $this->editPage( $name, 'Content' )->value['revision-record']->getPageId();
}
public function testFromWithFromid() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'The parameters "from" and "fromid" can not be used together.' );
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => 'Some page',
'fromid' => 123,
'to' => 'Some other page',
] );
}
public function testMove() {
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveById() {
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'fromid' => $id,
'to' => "$name 2",
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveAndWatch(): void {
$name = ucfirst( __FUNCTION__ );
$this->createPage( $name );
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'watchlist' => 'watch',
'watchlistexpiry' => '99990123000000',
] );
$title = Title::newFromText( $name );
$title2 = Title::newFromText( "$name 2" );
$user = $this->getTestSysop()->getUser();
$watchlistManager = $this->getServiceContainer()->getWatchlistManager();
$this->assertTrue( $watchlistManager->isTempWatched( $user, $title ) );
$this->assertTrue( $watchlistManager->isTempWatched( $user, $title2 ) );
}
public function testMoveWithWatchUnchanged(): void {
$name = ucfirst( __FUNCTION__ );
$this->createPage( $name );
$title = Title::newFromText( $name );
$title2 = Title::newFromText( "$name 2" );
$user = $this->getTestSysop()->getUser();
// Temporarily watch the page.
$this->doApiRequestWithToken( [
'action' => 'watch',
'titles' => $name,
'expiry' => '99990123000000',
] );
// Fetched stored expiry (maximum duration may override '99990123000000').
$store = $this->getServiceContainer()->getWatchedItemStore();
$expiry = $store->getWatchedItem( $user, $title )->getExpiry();
// Move to new location, without changing the watched state.
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $title->getDBkey(),
'to' => $title2->getDBkey(),
] );
// New page should have the same expiry.
$expiry2 = $store->getWatchedItem( $user, $title2 )->getExpiry();
$this->assertSame( wfTimestamp( TS_MW, $expiry ), $expiry2 );
}
public function testMoveNonexistent() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( "The page you specified doesn't exist." );
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => 'Nonexistent page',
'to' => 'Different page'
] );
}
public function testMoveNonexistentId() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'There is no page with ID 2147483647.' );
$this->doApiRequestWithToken( [
'action' => 'move',
'fromid' => pow( 2, 31 ) - 1,
'to' => 'Different page',
] );
}
public function testMoveToInvalidPageName() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'Bad title "[".' );
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
try {
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => '[',
] );
} finally {
$this->assertSame( $id, Title::newFromText( $name )->getArticleID() );
}
}
public function testMoveWhileBlocked() {
$this->assertNull( DatabaseBlock::newFromTarget( '127.0.0.1' ) );
$blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
$block = new DatabaseBlock( [
'address' => self::$users['sysop']->getUser()->getName(),
'by' => self::$users['sysop']->getUser(),
'reason' => 'Capriciousness',
'timestamp' => '19370101000000',
'expiry' => 'infinity',
'enableAutoblock' => true,
] );
$blockStore->insertBlock( $block );
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
try {
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
] );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
$this->assertNotNull( DatabaseBlock::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
} finally {
$blockStore->deleteBlock( $block );
self::$users['sysop']->getUser()->clearInstanceCache();
$this->assertSame( $id, Title::newFromText( $name )->getArticleID() );
}
}
// @todo File moving
public function testPingLimiter() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage(
"You've exceeded your rate limit. Please wait some time and try again."
);
$name = ucfirst( __FUNCTION__ );
$this->overrideConfigValue( 'RateLimits',
[ 'move' => [ '&can-bypass' => false, 'user' => [ 1, 60 ] ] ] );
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
try {
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => "$name 2",
'to' => "$name 3",
] );
} finally {
$this->assertSame( $id, Title::newFromText( "$name 2" )->getArticleID() );
$this->assertFalse( Title::newFromText( "$name 3" )->exists(),
"\"$name 3\" should not exist" );
}
}
public function testTagsNoPermission() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage(
'You do not have permission to apply change tags along with your changes.'
);
$name = ucfirst( __FUNCTION__ );
ChangeTags::defineTag( 'custom tag' );
$this->setGroupPermissions( 'user', 'applychangetags', false );
$id = $this->createPage( $name );
try {
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'tags' => 'custom tag',
] );
} finally {
$this->assertSame( $id, Title::newFromText( $name )->getArticleID() );
$this->assertFalse( Title::newFromText( "$name 2" )->exists(),
"\"$name 2\" should not exist" );
}
}
public function testSelfMove() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'The title is the same; cannot move a page over itself.' );
$name = ucfirst( __FUNCTION__ );
$this->createPage( $name );
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => $name,
] );
}
public function testMoveTalk() {
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$talkId = $this->createPage( "Talk:$name" );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'movetalk' => '',
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertMoved( "Talk:$name", "Talk:$name 2", $talkId );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveTalkFailed() {
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$talkId = $this->createPage( "Talk:$name" );
$talkDestinationId = $this->createPage( "Talk:$name 2" );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'movetalk' => '',
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertSame( $talkId, Title::newFromText( "Talk:$name" )->getArticleID() );
$this->assertSame( $talkDestinationId,
Title::newFromText( "Talk:$name 2" )->getArticleID() );
$this->assertSame( [ [
'message' => 'articleexists',
'params' => [ "Talk:$name 2" ],
'code' => 'articleexists',
'type' => 'error',
] ], $res[0]['move']['talkmove-errors'] );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveSubpages() {
$name = ucfirst( __FUNCTION__ );
$this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] );
$pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
$ids = [];
foreach ( array_merge( $pages, [ "$name/error", "$name 2/error" ] ) as $page ) {
$ids[$page] = $this->createPage( $page );
}
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'movetalk' => '',
'movesubpages' => '',
] );
foreach ( $pages as $page ) {
$this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
}
$this->assertSame( $ids["$name/error"],
Title::newFromText( "$name/error" )->getArticleID() );
$this->assertSame( $ids["$name 2/error"],
Title::newFromText( "$name 2/error" )->getArticleID() );
$results = array_merge( $res[0]['move']['subpages'], $res[0]['move']['subpages-talk'] );
foreach ( $results as $arr ) {
if ( $arr['from'] === "$name/error" ) {
$this->assertSame( [ [
'message' => 'articleexists',
'params' => [ "$name 2/error" ],
'code' => 'articleexists',
'type' => 'error'
] ], $arr['errors'] );
} else {
$this->assertSame( str_replace( $name, "$name 2", $arr['from'] ), $arr['to'] );
}
$this->assertCount( 2, $arr );
}
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveNoPermission() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage(
'You must be a registered user and [[Special:UserLogin|logged in]] to move a page.'
);
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$user = new User();
try {
$this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
], null, $user );
} finally {
$this->assertSame( $id, Title::newFromText( "$name" )->getArticleID() );
$this->assertFalse( Title::newFromText( "$name 2" )->exists(),
"\"$name 2\" should not exist" );
}
}
public function testSuppressRedirect() {
$name = ucfirst( __FUNCTION__ );
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'noredirect' => '',
] );
$this->assertMoved( $name, "$name 2", $id, 'noredirect' );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testSuppressRedirectNoPermission() {
$name = ucfirst( __FUNCTION__ );
$this->setGroupPermissions( 'sysop', 'suppressredirect', false );
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => $name,
'to' => "$name 2",
'noredirect' => '',
] );
$this->assertMoved( $name, "$name 2", $id );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
public function testMoveSubpagesError() {
$name = ucfirst( __FUNCTION__ );
// Subpages are allowed in talk but not main
$idBase = $this->createPage( "Talk:$name" );
$idSub = $this->createPage( "Talk:$name/1" );
$res = $this->doApiRequestWithToken( [
'action' => 'move',
'from' => "Talk:$name",
'to' => $name,
'movesubpages' => '',
] );
$this->assertMoved( "Talk:$name", $name, $idBase );
$this->assertSame( $idSub, Title::newFromText( "Talk:$name/1" )->getArticleID() );
$this->assertFalse( Title::newFromText( "$name/1" )->exists(),
"\"$name/1\" should not exist" );
$this->assertSame( [ 'errors' => [ [
'message' => 'namespace-nosubpages',
'params' => [ '' ],
'code' => 'namespace-nosubpages',
'type' => 'error',
] ] ], $res[0]['move']['subpages'] );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
}
}