From 6b7f5a8ce3fc90b7024be74b36f8dce26f184698 Mon Sep 17 00:00:00 2001 From: Leszek Manicki Date: Thu, 31 Mar 2016 16:01:53 +0200 Subject: [PATCH] Add tests for ApiQueryWatchlist Note: This change does not add tests for deleted revision/log data (comment, action, user). These should be added in a follow up. Change-Id: Ia76c39134564c477aa46ccb7b6595173410b0251 --- .../api/ApiQueryWatchlistIntegrationTest.php | 1592 +++++++++++++++++ 1 file changed, 1592 insertions(+) create mode 100644 tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php diff --git a/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php b/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php new file mode 100644 index 0000000000..f1f9295ef3 --- /dev/null +++ b/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php @@ -0,0 +1,1592 @@ +tablesUsed = array_unique( + array_merge( $this->tablesUsed, [ 'watchlist', 'recentchanges', 'page' ] ) + ); + } + + protected function setUp() { + parent::setUp(); + self::$users['ApiQueryWatchlistIntegrationTestUser'] + = new TestUser( 'ApiQueryWatchlistIntegrationTestUser' ); + self::$users['ApiQueryWatchlistIntegrationTestUser2'] + = new TestUser( 'ApiQueryWatchlistIntegrationTestUser2' ); + $this->doLogin( 'ApiQueryWatchlistIntegrationTestUser' ); + } + + private function getTestUser() { + return self::$users['ApiQueryWatchlistIntegrationTestUser']->getUser(); + } + + private function getNonLoggedInTestUser() { + return self::$users['ApiQueryWatchlistIntegrationTestUser2']->getUser(); + } + + private function getSysopTestUser() { + return self::$users['sysop']->getUser(); + } + + private function doPageEdit( User $user, LinkTarget $target, $content, $summary ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $page->doEditContent( + ContentHandler::makeContent( $content, $title ), + $summary, + 0, + false, + $user + ); + } + + private function doMinorPageEdit( User $user, LinkTarget $target, $content, $summary ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $page->doEditContent( + ContentHandler::makeContent( $content, $title ), + $summary, + EDIT_MINOR, + false, + $user + ); + } + + private function doBotPageEdit( User $user, LinkTarget $target, $content, $summary ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $page->doEditContent( + ContentHandler::makeContent( $content, $title ), + $summary, + EDIT_FORCE_BOT, + false, + $user + ); + } + + private function doAnonPageEdit( LinkTarget $target, $content, $summary ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $page->doEditContent( + ContentHandler::makeContent( $content, $title ), + $summary, + 0, + false, + User::newFromId( 0 ) + ); + } + + private function doPatrolledPageEdit( + User $user, + LinkTarget $target, + $content, + $summary, + User $patrollingUser + ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $status = $page->doEditContent( + ContentHandler::makeContent( $content, $title ), + $summary, + 0, + false, + $user + ); + /** @var Revision $rev */ + $rev = $status->value['revision']; + $rc = $rev->getRecentChange(); + $rc->doMarkPatrolled( $patrollingUser, false, [] ); + } + + private function deletePage( LinkTarget $target, $reason ) { + $title = Title::newFromLinkTarget( $target ); + $page = WikiPage::factory( $title ); + $page->doDeleteArticleReal( $reason ); + } + + /** + * Performs a batch of page edits as a specified user + * @param User $user + * @param array $editData associative array, keys: + * - target => LinkTarget page to edit + * - content => string new content + * - summary => string edit summary + * - minorEdit => bool mark as minor edit if true (defaults to false) + * - botEdit => bool mark as bot edit if true (defaults to false) + */ + private function doPageEdits( User $user, array $editData ) { + foreach ( $editData as $singleEditData ) { + if ( array_key_exists( 'minorEdit', $singleEditData ) && $singleEditData['minorEdit'] ) { + $this->doMinorPageEdit( + $user, + $singleEditData['target'], + $singleEditData['content'], + $singleEditData['summary'] + ); + continue; + } + if ( array_key_exists( 'botEdit', $singleEditData ) && $singleEditData['botEdit'] ) { + $this->doBotPageEdit( + $user, + $singleEditData['target'], + $singleEditData['content'], + $singleEditData['summary'] + ); + continue; + } + $this->doPageEdit( + $user, + $singleEditData['target'], + $singleEditData['content'], + $singleEditData['summary'] + ); + } + } + + private function getWatchedItemStore() { + return MediaWikiServices::getInstance()->getWatchedItemStore(); + } + + /** + * @param User $user + * @param LinkTarget[] $targets + */ + private function watchPages( User $user, array $targets ) { + $store = $this->getWatchedItemStore(); + $store->addWatchBatchForUser( $user, $targets ); + } + + private function doListWatchlistRequest( array $params = [], $user = null ) { + return $this->doApiRequest( + array_merge( + [ 'action' => 'query', 'list' => 'watchlist' ], + $params + ), null, false, $user + ); + } + + private function doGeneratorWatchlistRequest( array $params = [] ) { + return $this->doApiRequest( + array_merge( + [ 'action' => 'query', 'generator' => 'watchlist' ], + $params + ) + ); + } + + private function getItemsFromApiResponse( array $response ) { + return $response[0]['query']['watchlist']; + } + + /** + * Convenience method to assert that actual items array fetched from API is equal to the expected + * array, Unlike assertEquals this only checks if values of specified keys are equal in both + * arrays. This could be used e.g. not to compare IDs that could change between test run + * but only stable keys. + * Optionally this also checks that specified keys are present in the actual item without + * performing any checks on the related values. + * + * @param array $actualItems array of actual items (associative arrays) + * @param array $expectedItems array of expected items (associative arrays), + * those items have less keys than actual items + * @param array $keysUsedInValueComparison list of keys of the actual item that will be used + * in the comparison of values + * @param array $requiredKeys optional, list of keys that must be present in the + * actual items. Values of those keys are not checked. + */ + private function assertArraySubsetsEqual( + array $actualItems, + array $expectedItems, + array $keysUsedInValueComparison, + array $requiredKeys = [] + ) { + $this->assertCount( count( $expectedItems ), $actualItems ); + + // not checking values of all keys of the actual item, so removing unwanted keys from comparison + $actualItemsOnlyComparedValues = array_map( + function( array $item ) use ( $keysUsedInValueComparison ) { + return array_intersect_key( $item, array_flip( $keysUsedInValueComparison ) ); + }, + $actualItems + ); + + $this->assertEquals( + $expectedItems, + $actualItemsOnlyComparedValues + ); + + // Check that each item in $actualItems contains all of keys specified in $requiredKeys + $actualItemsKeysOnly = array_map( 'array_keys', $actualItems ); + foreach ( $actualItemsKeysOnly as $keysOfTheItem ) { + $this->assertEmpty( array_diff( $requiredKeys, $keysOfTheItem ) ); + } + } + + private function getTitleFormatter() { + return new MediaWikiTitleCodec( Language::factory( 'en' ), GenderCache::singleton() ); + } + + private function getPrefixedText( LinkTarget $target ) { + $formatter = $this->getTitleFormatter(); + return $formatter->getPrefixedText( $target ); + } + + private function cleanTestUsersWatchlist() { + $user = $this->getTestUser(); + $store = $this->getWatchedItemStore(); + $items = $store->getWatchedItemsForUser( $user ); + foreach ( $items as $item ) { + $store->removeWatch( $user, $item->getLinkTarget() ); + } + } + + public function testListWatchlist_returnsWatchedItemsWithRCInfo() { + // Clean up after previous tests that might have added something to the watchlist of + // the user with the same user ID as user used here as the test user + $this->cleanTestUsersWatchlist(); + + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest(); + + $this->assertArrayHasKey( 'query', $result[0] ); + $this->assertArrayHasKey( 'watchlist', $result[0]['query'] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $result ), + [ + [ + 'type' => 'new', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + 'bot' => false, + 'new' => true, + 'minor' => false, + ] + ], + [ 'type', 'ns', 'title', 'bot', 'new', 'minor' ], + [ 'pageid', 'revid', 'old_revid' ] + ); + } + + public function testIdsPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'ids', ] ); + $items = $this->getItemsFromApiResponse( $result ); + + $this->assertCount( 1, $items ); + $this->assertArrayHasKey( 'pageid', $items[0] ); + $this->assertArrayHasKey( 'revid', $items[0] ); + $this->assertArrayHasKey( 'old_revid', $items[0] ); + $this->assertEquals( 'new', $items[0]['type'] ); + } + + public function testTitlePropParameter() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $subjectTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $talkTarget, + 'content' => 'Some Talk Page Content', + 'summary' => 'Create Talk page', + ], + ] + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ), + ], + [ + 'type' => 'new', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testFlagsPropParameter() { + $user = $this->getTestUser(); + $normalEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $minorEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageM' ); + $botEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageB' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $normalEditTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $minorEditTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $minorEditTarget, + 'content' => 'Slightly Better Content', + 'summary' => 'Change content', + 'minorEdit' => true, + ], + [ + 'target' => $botEditTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page with a bot', + 'botEdit' => true, + ], + ] + ); + $this->watchPages( $user, [ $normalEditTarget, $minorEditTarget, $botEditTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'flags', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'new' => true, + 'minor' => false, + 'bot' => true, + ], + [ + 'type' => 'edit', + 'new' => false, + 'minor' => true, + 'bot' => false, + ], + [ + 'type' => 'new', + 'new' => true, + 'minor' => false, + 'bot' => false, + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testUserPropParameter() { + $user = $this->getTestUser(); + $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' ); + $this->doPageEdit( + $user, + $userEditTarget, + 'Some Content', + 'Create the page' + ); + $this->doAnonPageEdit( + $anonEditTarget, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'user', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'anon' => true, + 'user' => User::newFromId( 0 )->getName(), + ], + [ + 'type' => 'new', + 'user' => $user->getName(), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testUserIdPropParameter() { + $user = $this->getTestUser(); + $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' ); + $this->doPageEdit( + $user, + $userEditTarget, + 'Some Content', + 'Create the page' + ); + $this->doAnonPageEdit( + $anonEditTarget, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'userid', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'anon' => true, + 'user' => 0, + 'userid' => 0, + ], + [ + 'type' => 'new', + 'user' => $user->getId(), + 'userid' => $user->getId(), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testCommentPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'comment', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'comment' => 'Create the page', + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testParsedCommentPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'parsedcomment', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'parsedcomment' => 'Create the <b>page</b>', + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testTimestampPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'timestamp', ] ); + $items = $this->getItemsFromApiResponse( $result ); + + $this->assertCount( 1, $items ); + $this->assertArrayHasKey( 'timestamp', $items[0] ); + $this->assertInternalType( 'string', $items[0]['timestamp'] ); + } + + public function testSizesPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'sizes', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'oldlen' => 0, + 'newlen' => 12, + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testNotificationTimestampPropParameter() { + $otherUser = $this->getNonLoggedInTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $otherUser, + $target, + 'Some Content', + 'Create the page' + ); + $store = $this->getWatchedItemStore(); + $store->addWatch( $this->getTestUser(), $target ); + $store->updateNotificationTimestamp( + $otherUser, + $target, + '20151212010101' + ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'notificationtimestamp', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'notificationtimestamp' => '2015-12-12T01:01:01Z', + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + private function setupPatrolledSpecificFixtures( User $user ) { + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + + $this->doPatrolledPageEdit( + $user, + $target, + 'Some Content', + 'Create the page (this gets patrolled)', + $user + ); + + $this->watchPages( $user, [ $target ] ); + } + + public function testPatrolPropParameter() { + $user = $this->getSysopTestUser(); + $this->setupPatrolledSpecificFixtures( $user ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'patrol', ], $user ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'patrolled' => true, + 'unpatrolled' => false, + ] + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + private function createPageAndDeleteIt( LinkTarget $target ) { + $this->doPageEdit( + $this->getTestUser(), + $target, + 'Some Content', + 'Create the page that will be deleted' + ); + $this->deletePage( $target, 'Important Reason' ); + } + + public function testLoginfoPropParameter() { + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->createPageAndDeleteIt( $target ); + + $this->watchPages( $this->getTestUser(), [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'loginfo', ] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $result ), + [ + [ + 'type' => 'log', + 'logtype' => 'delete', + 'logaction' => 'delete', + 'logparams' => [], + ], + ], + [ 'type', 'logtype', 'logaction', 'logparams' ], + [ 'logid' ] + ); + } + + public function testEmptyPropParameter() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => '', ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + ] + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testNamespaceParam() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $subjectTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $talkTarget, + 'content' => 'Some Content', + 'summary' => 'Create the talk page', + ], + ] + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlnamespace' => '0', ] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $result ), + [ + [ + 'ns' => 0, + 'title' => $this->getPrefixedText( $subjectTarget ), + ], + ], + [ 'ns', 'title' ] + ); + } + + public function testUserParam() { + $user = $this->getTestUser(); + $otherUser = $this->getNonLoggedInTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $subjectTarget, + 'Some Content', + 'Create the page' + ); + $this->doPageEdit( + $otherUser, + $talkTarget, + 'What is this page about?', + 'Create the talk page' + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ + 'wlprop' => 'user|title', + 'wluser' => $otherUser->getName(), + ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ), + 'user' => $otherUser->getName(), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testExcludeUserParam() { + $user = $this->getTestUser(); + $otherUser = $this->getNonLoggedInTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $subjectTarget, + 'Some Content', + 'Create the page' + ); + $this->doPageEdit( + $otherUser, + $talkTarget, + 'What is this page about?', + 'Create the talk page' + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ + 'wlprop' => 'user|title', + 'wlexcludeuser' => $otherUser->getName(), + ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ), + 'user' => $user->getName(), + ] + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testShowMinorParams() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $target, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $target, + 'content' => 'Slightly Better Content', + 'summary' => 'Change content', + 'minorEdit' => true, + ], + ] + ); + $this->watchPages( $user, [ $target ] ); + + $resultMinor = $this->doListWatchlistRequest( [ 'wlshow' => 'minor', 'wlprop' => 'flags' ] ); + $resultNotMinor = $this->doListWatchlistRequest( [ 'wlshow' => '!minor', 'wlprop' => 'flags' ] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $resultMinor ), + [ + [ 'minor' => true, ] + ], + [ 'minor' ] + ); + $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotMinor ) ); + } + + public function testShowBotParams() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doBotPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $resultBot = $this->doListWatchlistRequest( [ 'wlshow' => 'bot' ] ); + $resultNotBot = $this->doListWatchlistRequest( [ 'wlshow' => '!bot' ] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $resultBot ), + [ + [ 'bot' => true ], + ], + [ 'bot' ] + ); + $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotBot ) ); + } + + public function testShowAnonParams() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doAnonPageEdit( + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $resultAnon = $this->doListWatchlistRequest( [ + 'wlprop' => 'user', + 'wlshow' => 'anon' + ] ); + $resultNotAnon = $this->doListWatchlistRequest( [ + 'wlprop' => 'user', + 'wlshow' => '!anon' + ] ); + + $this->assertArraySubsetsEqual( + $this->getItemsFromApiResponse( $resultAnon ), + [ + [ 'anon' => true ], + ], + [ 'anon' ] + ); + $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotAnon ) ); + } + + public function testShowUnreadParams() { + $user = $this->getTestUser(); + $otherUser = $this->getNonLoggedInTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $subjectTarget, + 'Some Content', + 'Create the page' + ); + $this->doPageEdit( + $otherUser, + $talkTarget, + 'Some Content', + 'Create the talk page' + ); + $store = $this->getWatchedItemStore(); + $store->addWatchBatchForUser( $user, [ $subjectTarget, $talkTarget ] ); + $store->updateNotificationTimestamp( + $otherUser, + $talkTarget, + '20151212010101' + ); + + $resultUnread = $this->doListWatchlistRequest( [ + 'wlprop' => 'notificationtimestamp|title', + 'wlshow' => 'unread' + ] ); + $resultNotUnread = $this->doListWatchlistRequest( [ + 'wlprop' => 'notificationtimestamp|title', + 'wlshow' => '!unread' + ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'notificationtimestamp' => '2015-12-12T01:01:01Z', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ) + ] + ], + $this->getItemsFromApiResponse( $resultUnread ) + ); + $this->assertEquals( + [ + [ + 'type' => 'new', + 'notificationtimestamp' => '', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ) + ] + ], + $this->getItemsFromApiResponse( $resultNotUnread ) + ); + } + + public function testShowPatrolledParams() { + $user = $this->getSysopTestUser(); + $this->setupPatrolledSpecificFixtures( $user ); + + $resultPatrolled = $this->doListWatchlistRequest( [ + 'wlprop' => 'patrol', + 'wlshow' => 'patrolled' + ], $user ); + $resultNotPatrolled = $this->doListWatchlistRequest( [ + 'wlprop' => 'patrol', + 'wlshow' => '!patrolled' + ], $user ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'patrolled' => true, + 'unpatrolled' => false, + ] + ], + $this->getItemsFromApiResponse( $resultPatrolled ) + ); + $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotPatrolled ) ); + } + + public function testNewAndEditTypeParameters() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $subjectTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $subjectTarget, + 'content' => 'Some Other Content', + 'summary' => 'Change the content', + ], + [ + 'target' => $talkTarget, + 'content' => 'Some Talk Page Content', + 'summary' => 'Create Talk page', + ], + ] + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $resultNew = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'new' ] ); + $resultEdit = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'edit' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ), + ], + ], + $this->getItemsFromApiResponse( $resultNew ) + ); + $this->assertEquals( + [ + [ + 'type' => 'edit', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ), + ], + ], + $this->getItemsFromApiResponse( $resultEdit ) + ); + } + + public function testLogTypeParameters() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->createPageAndDeleteIt( $subjectTarget ); + $this->doPageEdit( + $user, + $talkTarget, + 'Some Talk Page Content', + 'Create Talk page' + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'log' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'log', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + private function getExternalRC( LinkTarget $target ) { + $title = Title::newFromLinkTarget( $target ); + + $rc = new RecentChange; + $rc->mTitle = $title; + $rc->mAttribs = [ + 'rc_timestamp' => wfTimestamp( TS_MW ), + 'rc_namespace' => $title->getNamespace(), + 'rc_title' => $title->getDBkey(), + 'rc_type' => RC_EXTERNAL, + 'rc_source' => 'foo', + 'rc_minor' => 0, + 'rc_cur_id' => $title->getArticleID(), + 'rc_user' => 0, + 'rc_user_text' => 'External User', + 'rc_comment' => '', + 'rc_this_oldid' => $title->getLatestRevID(), + 'rc_last_oldid' => $title->getLatestRevID(), + 'rc_bot' => 0, + 'rc_ip' => '', + 'rc_patrolled' => 0, + 'rc_new' => 0, + 'rc_old_len' => $title->getLength(), + 'rc_new_len' => $title->getLength(), + 'rc_deleted' => 0, + 'rc_logid' => 0, + 'rc_log_type' => null, + 'rc_log_action' => '', + 'rc_params' => '', + ]; + $rc->mExtra = [ + 'prefixedDBkey' => $title->getPrefixedDBkey(), + 'lastTimestamp' => 0, + 'oldSize' => $title->getLength(), + 'newSize' => $title->getLength(), + 'pageStatus' => 'changed' + ]; + + return $rc; + } + + public function testExternalTypeParameters() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $subjectTarget, + 'Some Content', + 'Create the page' + ); + $this->doPageEdit( + $user, + $talkTarget, + 'Some Talk Page Content', + 'Create Talk page' + ); + + $rc = $this->getExternalRC( $subjectTarget ); + $rc->save(); + + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'external' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'external', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testCategorizeTypeParameter() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $categoryTarget = new TitleValue( NS_CATEGORY, 'ApiQueryWatchlistIntegrationTestCategory' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $categoryTarget, + 'content' => 'Some Content', + 'summary' => 'Create the category', + ], + [ + 'target' => $subjectTarget, + 'content' => 'Some Content [[Category:ApiQueryWatchlistIntegrationTestCategory]]t', + 'summary' => 'Create the page and add it to the category', + ], + ] + ); + $title = Title::newFromLinkTarget( $subjectTarget ); + $revision = Revision::newFromTitle( $title ); + + $rc = RecentChange::newForCategorization( + $revision->getTimestamp(), + Title::newFromLinkTarget( $categoryTarget ), + $user, + $revision->getComment(), + $title, + 0, + $revision->getId(), + null, + false + ); + $rc->save(); + + $this->watchPages( $user, [ $subjectTarget, $categoryTarget ] ); + + $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'categorize' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'categorize', + 'ns' => $categoryTarget->getNamespace(), + 'title' => $this->getPrefixedText( $categoryTarget ), + ], + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testLimitParam() { + $user = $this->getTestUser(); + $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $target1, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $target2, + 'content' => 'Some Talk Page Content', + 'summary' => 'Create Talk page', + ], + [ + 'target' => $target3, + 'content' => 'Some Other Content', + 'summary' => 'Create the page', + ], + ] + ); + $this->watchPages( $user, [ $target1, $target2, $target3 ] ); + + $resultWithoutLimit = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] ); + $resultWithLimit = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target3->getNamespace(), + 'title' => $this->getPrefixedText( $target3 ) + ], + [ + 'type' => 'new', + 'ns' => $target2->getNamespace(), + 'title' => $this->getPrefixedText( $target2 ) + ], + [ + 'type' => 'new', + 'ns' => $target1->getNamespace(), + 'title' => $this->getPrefixedText( $target1 ) + ], + ], + $this->getItemsFromApiResponse( $resultWithoutLimit ) + ); + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target3->getNamespace(), + 'title' => $this->getPrefixedText( $target3 ) + ], + [ + 'type' => 'new', + 'ns' => $target2->getNamespace(), + 'title' => $this->getPrefixedText( $target2 ) + ], + ], + $this->getItemsFromApiResponse( $resultWithLimit ) + ); + $this->assertArrayHasKey( 'continue', $resultWithLimit[0] ); + $this->assertArrayHasKey( 'wlcontinue', $resultWithLimit[0]['continue'] ); + } + + public function testAllRevParam() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $target, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $target, + 'content' => 'Some Other Content', + 'summary' => 'Change the content', + ], + ] + ); + $this->watchPages( $user, [ $target ] ); + + $resultAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wlallrev' => '', ] ); + $resultNoAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'edit', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + ], + ], + $this->getItemsFromApiResponse( $resultNoAllRev ) + ); + $this->assertEquals( + [ + [ + 'type' => 'edit', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + ], + [ + 'type' => 'new', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + ], + ], + $this->getItemsFromApiResponse( $resultAllRev ) + ); + } + + public function testDirParams() { + $user = $this->getTestUser(); + $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $subjectTarget, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $talkTarget, + 'content' => 'Some Talk Page Content', + 'summary' => 'Create Talk page', + ], + ] + ); + $this->watchPages( $user, [ $subjectTarget, $talkTarget ] ); + + $resultDirOlder = $this->doListWatchlistRequest( [ 'wldir' => 'older', 'wlprop' => 'title' ] ); + $resultDirNewer = $this->doListWatchlistRequest( [ 'wldir' => 'newer', 'wlprop' => 'title' ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ) + ], + [ + 'type' => 'new', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ) + ], + ], + $this->getItemsFromApiResponse( $resultDirOlder ) + ); + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $subjectTarget->getNamespace(), + 'title' => $this->getPrefixedText( $subjectTarget ) + ], + [ + 'type' => 'new', + 'ns' => $talkTarget->getNamespace(), + 'title' => $this->getPrefixedText( $talkTarget ) + ], + ], + $this->getItemsFromApiResponse( $resultDirNewer ) + ); + } + + public function testStartEndParams() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $resultStart = $this->doListWatchlistRequest( [ + 'wlstart' => '20010115000000', + 'wldir' => 'newer', + 'wlprop' => 'title', + ] ); + $resultEnd = $this->doListWatchlistRequest( [ + 'wlend' => '20010115000000', + 'wldir' => 'newer', + 'wlprop' => 'title', + ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + ] + ], + $this->getItemsFromApiResponse( $resultStart ) + ); + $this->assertEmpty( $this->getItemsFromApiResponse( $resultEnd ) ); + } + + public function testContinueParam() { + $user = $this->getTestUser(); + $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' ); + $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $target1, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $target2, + 'content' => 'Some Talk Page Content', + 'summary' => 'Create Talk page', + ], + [ + 'target' => $target3, + 'content' => 'Some Other Content', + 'summary' => 'Create the page', + ], + ] + ); + $this->watchPages( $user, [ $target1, $target2, $target3 ] ); + + $firstResult = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] ); + $this->assertArrayHasKey( 'continue', $firstResult[0] ); + $this->assertArrayHasKey( 'wlcontinue', $firstResult[0]['continue'] ); + + $continuationParam = $firstResult[0]['continue']['wlcontinue']; + + $continuedResult = $this->doListWatchlistRequest( + [ 'wlcontinue' => $continuationParam, 'wlprop' => 'title' ] + ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target3->getNamespace(), + 'title' => $this->getPrefixedText( $target3 ), + ], + [ + 'type' => 'new', + 'ns' => $target2->getNamespace(), + 'title' => $this->getPrefixedText( $target2 ), + ], + ], + $this->getItemsFromApiResponse( $firstResult ) + ); + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target1->getNamespace(), + 'title' => $this->getPrefixedText( $target1 ) + ] + ], + $this->getItemsFromApiResponse( $continuedResult ) + ); + } + + public function testOwnerAndTokenParams() { + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $this->getTestUser(), + $target, + 'Some Content', + 'Create the page' + ); + + $otherUser = $this->getNonLoggedInTestUser(); + $otherUser->setOption( 'watchlisttoken', '1234567890' ); + $otherUser->saveSettings(); + + $this->watchPages( $otherUser, [ $target ] ); + + $result = $this->doListWatchlistRequest( [ + 'wlowner' => $otherUser->getName(), + 'wltoken' => '1234567890', + 'wlprop' => 'title', + ] ); + + $this->assertEquals( + [ + [ + 'type' => 'new', + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ) + ] + ], + $this->getItemsFromApiResponse( $result ) + ); + } + + public function testOwnerAndTokenParams_wrongToken() { + $otherUser = $this->getNonLoggedInTestUser(); + $otherUser->setOption( 'watchlisttoken', '1234567890' ); + $otherUser->saveSettings(); + + $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' ); + + $this->doListWatchlistRequest( [ + 'wlowner' => $otherUser->getName(), + 'wltoken' => 'wrong-token', + ] ); + } + + public function testOwnerAndTokenParams_noWatchlistTokenSet() { + $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' ); + + $this->doListWatchlistRequest( [ + 'wlowner' => $this->getNonLoggedInTestUser()->getName(), + 'wltoken' => 'some-token', + ] ); + } + + public function testGeneratorWatchlistPropInfo_returnsWatchedPages() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdit( + $user, + $target, + 'Some Content', + 'Create the page' + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'info' ] ); + + $this->assertArrayHasKey( 'query', $result[0] ); + $this->assertArrayHasKey( 'pages', $result[0]['query'] ); + + // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them + $pages = array_values( $result[0]['query']['pages'] ); + + $this->assertArraySubsetsEqual( + $pages, + [ + [ + 'ns' => $target->getNamespace(), + 'title' => $this->getPrefixedText( $target ), + 'new' => true, + ] + ], + [ 'ns', 'title', 'new' ] + ); + } + + public function testGeneratorWatchlistPropRevisions_returnsWatchedItemsRevisions() { + $user = $this->getTestUser(); + $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' ); + $this->doPageEdits( + $user, + [ + [ + 'target' => $target, + 'content' => 'Some Content', + 'summary' => 'Create the page', + ], + [ + 'target' => $target, + 'content' => 'Some Other Content', + 'summary' => 'Change the content', + ], + ] + ); + $this->watchPages( $user, [ $target ] ); + + $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'revisions', 'gwlallrev' => '' ] ); + + $this->assertArrayHasKey( 'query', $result[0] ); + $this->assertArrayHasKey( 'pages', $result[0]['query'] ); + + // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them + $pages = array_values( $result[0]['query']['pages'] ); + + $this->assertCount( 1, $pages ); + $this->assertEquals( 0, $pages[0]['ns'] ); + $this->assertEquals( $this->getPrefixedText( $target ), $pages[0]['title'] ); + $this->assertArraySubsetsEqual( + $pages[0]['revisions'], + [ + [ + 'comment' => 'Create the page', + 'user' => $user->getName(), + 'minor' => false, + ], + [ + 'comment' => 'Change the content', + 'user' => $user->getName(), + 'minor' => false, + ], + ], + [ 'comment', 'user', 'minor' ] + ); + } + +} -- 2.20.1