setMwGlobals( 'wgUseRCPatrol', true ); $this->tablesUsed = array_merge( $this->tablesUsed, [ 'page', 'recentchanges', 'logging', ] ); } public function provideAuthorize() { yield 'Allowed' => [ 'authority' => $this->mockRegisteredUltimateAuthority(), 'expect' => true, ]; yield 'No edit' => [ 'authority' => $this->mockRegisteredAuthorityWithoutPermissions( [ 'edit' ] ), 'expect' => true, ]; yield 'No rollback' => [ 'authority' => $this->mockRegisteredAuthorityWithoutPermissions( [ 'rollback' ] ), 'expect' => true, ]; } /** * @covers ::authorizeRollback * @dataProvider provideAuthorize */ public function testAuthorize( Authority $authority, bool $expect ) { $this->assertSame( $expect, $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( new PageIdentityValue( 10, NS_MAIN, 'Test', PageIdentity::LOCAL ), $authority, new UserIdentityValue( 0, '127.0.0.1' ) ) ->authorizeRollback() ->isGood() ); } public function testAuthorizeReadOnly() { $mockReadOnly = $this->createMock( ReadOnlyMode::class ); $mockReadOnly->method( 'isReadOnly' )->willReturn( true ); $rollback = $this->newServiceInstance( RollbackPage::class, [ 'readOnlyMode' => $mockReadOnly, 'performer' => $this->mockRegisteredUltimateAuthority() ] ); $this->assertFalse( $rollback->authorizeRollback()->isGood() ); } /** * @covers ::authorizeRollback */ public function testAuthorizePingLimiter() { $performer = $this->mockRegisteredUltimateAuthority(); $userMock = $this->createMock( User::class ); $userMock->method( 'pingLimiter' ) ->withConsecutive( [ 'rollback', 1 ], [ 'edit', 1 ] ) ->willReturnOnConsecutiveCalls( false, false ); $userFactoryMock = $this->createMock( UserFactory::class ); $userFactoryMock->method( 'newFromAuthority' ) ->with( $performer ) ->willReturn( $userMock ); $rollbackPage = $this->newServiceInstance( RollbackPage::class, [ 'performer' => $performer, 'userFactory' => $userFactoryMock ] ); $this->assertTrue( $rollbackPage->authorizeRollback()->isGood() ); } public function testRollbackNotAllowed() { $this->assertFalse( $this->newServiceInstance( RollbackPage::class, [ 'performer' => $this->mockRegisteredNullAuthority() ] )->rollbackIfAllowed()->isGood() ); } public function testRollback() { $admin = $this->getTestSysop()->getUser(); $user1 = $this->getTestUser()->getUser(); // Use the confirmed group for user2 to make sure the user is different $user2 = $this->getTestUser( [ 'confirmed' ] )->getUser(); $page = new WikiPage( Title::newFromText( __METHOD__ ) ); // Make some edits $text = "one"; $status1 = $this->editPage( $page, $text, "section one", NS_MAIN, $admin ); $this->assertTrue( $status1->isGood(), 'Sanity: edit 1 success' ); $text .= "\n\ntwo"; $status2 = $this->editPage( $page, $text, "adding section two", NS_MAIN, $user1 ); $this->assertTrue( $status2->isGood(), 'Sanity: edit 2 success' ); $text .= "\n\nthree"; $status3 = $this->editPage( $page, $text, "adding section three", NS_MAIN, $user2 ); $this->assertTrue( $status3->isGood(), 'Sanity: edit 3 success' ); /** @var RevisionRecord $rev1 */ /** @var RevisionRecord $rev2 */ /** @var RevisionRecord $rev3 */ $rev1 = $status1->getValue()['revision-record']; $rev2 = $status2->getValue()['revision-record']; $rev3 = $status3->getValue()['revision-record']; /** * We are having issues with doRollback spuriously failing. Apparently * the last revision somehow goes missing or not committed under some * circumstances. So, make sure the revisions have the correct usernames. */ $this->assertEquals( 3, $this->getServiceContainer() ->getRevisionStore() ->countRevisionsByPageId( $this->db, $page->getId() ) ); $this->assertEquals( $admin->getName(), $rev1->getUser()->getName() ); $this->assertEquals( $user1->getName(), $rev2->getUser()->getName() ); $this->assertEquals( $user2->getName(), $rev3->getUser()->getName() ); // Now, try the actual rollback $rollbackStatus = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user2 ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackStatus->isGood() ); $this->assertEquals( $rev2->getSha1(), $page->getRevisionRecord()->getSha1(), "rollback did not revert to the correct revision" ); $this->assertEquals( "one\n\ntwo", $page->getContent()->getText() ); $rc = $this->getServiceContainer()->getRevisionStore()->getRecentChange( $page->getRevisionRecord() ); $this->assertNotNull( $rc, 'RecentChanges entry' ); $this->assertEquals( RecentChange::PRC_AUTOPATROLLED, $rc->getAttribute( 'rc_patrolled' ), 'rc_patrolled' ); $mainSlot = $page->getRevisionRecord()->getSlot( SlotRecord::MAIN ); $this->assertTrue( $mainSlot->isInherited(), 'isInherited' ); $this->assertSame( $rev2->getId(), $mainSlot->getOrigin(), 'getOrigin' ); } public function testRollbackFailSameContent() { $admin = $this->getTestSysop()->getUser(); $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $text = "one"; $status1 = $this->editPage( $page, $text, "section one", NS_MAIN, $admin ); $this->assertTrue( $status1->isGood(), 'Sanity: edit 1 success' ); $rev1 = $page->getRevisionRecord(); $user1 = $this->getTestUser( [ 'sysop' ] )->getUser(); $text .= "\n\ntwo"; $status1 = $this->editPage( $page, $text, "adding section two", NS_MAIN, $user1 ); $this->assertTrue( $status1->isGood(), 'Sanity: edit 2 success' ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); # now, try the rollback again $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->rollback(); $this->assertFalse( $rollbackResult->isGood() ); $this->assertTrue( $rollbackResult->hasMessage( 'alreadyrolled' ) ); $this->assertEquals( $rev1->getSha1(), $page->getRevisionRecord()->getSha1(), "rollback did not revert to the correct revision" ); $this->assertEquals( "one", $page->getContent()->getText() ); } public function testRollbackFailNotExisting() { $rollbackStatus = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( new PageIdentityValue( 0, NS_MAIN, __METHOD__, PageIdentityValue::LOCAL ), $this->mockRegisteredUltimateAuthority(), new UserIdentityValue( 0, '127.0.0.1' ) ) ->rollback(); $this->assertFalse( $rollbackStatus->isGood() ); $this->assertTrue( $rollbackStatus->hasMessage( 'notanarticle' ) ); } /** * @param Authority $user1 * @param Authority $user2 * @param WikiPage $page * @return array with info about created page: * 'revision-one' => RevisionRecord * 'revision-two' => RevisionRecord */ private function prepareForRollback( Authority $user1, Authority $user2, WikiPage $page ): array { $result = []; $text = "one"; $status = $this->editPage( $page, $text, "section one", NS_MAIN, $user1 ); $this->assertTrue( $status->isGood(), 'Sanity: edit 1 success' ); $result['revision-one'] = $status->getValue()['revision-record']; $text .= "\n\ntwo"; $status = $this->editPage( $page, $text, "adding section two", NS_MAIN, $user2 ); $this->assertTrue( $status->isGood(), 'Sanity: edit 2 success' ); $result['revision-two'] = $status->getValue()['revision-record']; return $result; } public function testRollbackTagging() { if ( !in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) { $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.' ); } $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $admin = $this->getTestSysop()->getUser(); $user1 = $this->getTestUser()->getUser(); $this->prepareForRollback( $admin, $user1, $page ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->setChangeTags( [ 'tag' ] ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); $this->assertContains( 'mw-rollback', $rollbackResult->getValue()['tags'] ); $this->assertContains( 'tag', $rollbackResult->getValue()['tags'] ); } public function testRollbackBot() { $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $admin = $this->getTestSysop()->getUser(); $user1 = $this->getTestUser()->getUser(); $this->prepareForRollback( $admin, $user1, $page ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->markAsBot( true ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); $rc = $this->getServiceContainer()->getRevisionStore()->getRecentChange( $page->getRevisionRecord() ); $this->assertNotNull( $rc ); $this->assertSame( '1', $rc->getAttribute( 'rc_bot' ) ); } public function testRollbackBotNotAllowed() { $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $admin = $this->mockUserAuthorityWithoutPermissions( $this->getTestSysop()->getUser(), [ 'markbotedits', 'bot' ] ); $user1 = $this->getTestUser()->getUser(); $this->prepareForRollback( $admin, $user1, $page ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->markAsBot( true ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); $rc = $this->getServiceContainer()->getRevisionStore()->getRecentChange( $page->getRevisionRecord() ); $this->assertNotNull( $rc ); $this->assertSame( '0', $rc->getAttribute( 'rc_bot' ) ); } public function testRollbackCustomSummary() { $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $admin = $this->getTestSysop()->getUser(); $user1 = $this->getTestUser()->getUser(); $revisions = $this->prepareForRollback( $admin, $user1, $page ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->setSummary( 'TEST! $1 $2 $3 $4 $5 $6' ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); $targetTimestamp = $this->getServiceContainer() ->getContentLanguage() ->timeanddate( $revisions['revision-one']->getTimestamp() ); $currentTimestamp = $this->getServiceContainer() ->getContentLanguage() ->timeanddate( $revisions['revision-two']->getTimestamp() ); $expectedSummary = "TEST! {$admin->getName()} {$user1->getName()}" . " {$revisions['revision-one']->getId()}" . " {$targetTimestamp}" . " {$revisions['revision-two']->getId()}" . " {$currentTimestamp}"; $this->assertSame( $expectedSummary, $page->getRevisionRecord()->getComment()->text ); $rc = $this->getServiceContainer()->getRevisionStore()->getRecentChange( $page->getRevisionRecord() ); $this->assertNotNull( $rc ); $this->assertSame( $expectedSummary, $rc->getAttribute( 'rc_comment' ) ); } public function testRollbackChangesContentModel() { $page = new WikiPage( Title::newFromText( __METHOD__ ) ); $admin = $this->getTestSysop()->getUser(); $user1 = $this->getTestUser()->getUser(); $status1 = $this->editPage( $page, new JsonContent( '{}' ), "it's json", NS_MAIN, $admin ); $this->assertTrue( $status1->isGood(), 'Sanity: edit 1 success' ); $status1 = $this->editPage( $page, new WikitextContent( 'bla' ), "no, it's wikitext", NS_MAIN, $user1 ); $this->assertTrue( $status1->isGood(), 'Sanity: edit 2 success' ); $rollbackResult = $this->getServiceContainer() ->getRollbackPageFactory() ->newRollbackPage( $page, $admin, $user1 ) ->setSummary( 'TESTING' ) ->rollbackIfAllowed(); $this->assertTrue( $rollbackResult->isGood() ); $logQuery = DatabaseLogEntry::getSelectQueryData(); $logRow = $this->db->selectRow( $logQuery['tables'], $logQuery['fields'], [ 'log_namespace' => NS_MAIN, 'log_title' => __METHOD__, 'log_type' => 'contentmodel' ], __METHOD__, [], $logQuery['join_conds'] ); $this->assertNotNull( $logRow ); $this->assertSame( $admin->getUser()->getName(), $logRow->user_name ); $this->assertSame( 'TESTING', $logRow->log_comment_text ); } }