diff --git a/tests/phpunit/includes/user/UserEditTrackerTest.php b/tests/phpunit/includes/user/UserEditTrackerTest.php new file mode 100644 index 00000000000..ed1fb7a17e8 --- /dev/null +++ b/tests/phpunit/includes/user/UserEditTrackerTest.php @@ -0,0 +1,128 @@ +getServiceContainer()->getWikiPageFactory()->newFromTitle( $title ); + if ( $create ) { + $page->insertOn( $this->db ); + } + + $rev = new MutableRevisionRecord( $title ); + $rev->setContent( SlotRecord::MAIN, new WikitextContent( $timestamp ) ); + $rev->setComment( CommentStoreComment::newUnsavedComment( '' ) ); + $rev->setTimestamp( $timestamp ); + $rev->setUser( $user ); + $rev->setPageId( $page->getId() ); + $this->getServiceContainer()->getRevisionStore()->insertRevisionOn( $rev, $this->db ); + } + + /** + * Change the user_editcount field in the DB + * + * @param UserIdentity $user + * @param int|null $count + */ + private function setDbEditCount( $user, $count ) { + $this->db->update( + 'user', + [ 'user_editcount' => $count ], + [ 'user_id' => $user->getId() ], + __METHOD__ ); + } + + public function testGetUserEditCount() { + // Set user_editcount to 5 + $user = $this->getMutableTestUser()->getUser(); + $update = new UserEditCountUpdate( $user, 5 ); + $update->doUpdate(); + + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->assertSame( 5, $tracker->getUserEditCount( $user ) ); + + // Now fetch from cache + $this->assertSame( 5, $tracker->getUserEditCount( $user ) ); + } + + public function testGetUserEditCount_exception() { + // getUserEditCount throws if the user id is falsy + $userId = 0; + $user = new UserIdentityValue( $userId, __CLASS__ ); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->expectException( InvalidArgumentException::class ); + $this->expectExceptionMessage( 'requires a user ID' ); + $tracker->getUserEditCount( $user ); + } + + public function testGetUserEditCount_null() { + // getUserEditCount doesn't find a value in user_editcount and calls + // initializeUserEditCount + $user = $this->getMutableTestUser()->getUserIdentity(); + $this->setDbEditCount( $user, null ); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->assertSame( 0, $tracker->getUserEditCount( $user ) ); + } + + public function testInitializeUserEditCount() { + $user = $this->getMutableTestUser()->getUser(); + $this->editTrackerDoEdit( $user, '20200101000000', true ); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $tracker->initializeUserEditCount( $user ); + $this->runJobs(); + $this->assertSame( 1, $tracker->getUserEditCount( $user ) ); + } + + public function testGetEditTimestamp() { + $user = $this->getMutableTestUser()->getUser(); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->assertFalse( $tracker->getFirstEditTimestamp( $user ) ); + $this->assertFalse( $tracker->getLatestEditTimestamp( $user ) ); + + $ts1 = '20010101000000'; + $ts2 = '20020101000000'; + $ts3 = '20030101000000'; + $this->editTrackerDoEdit( $user, $ts3, false ); + $this->editTrackerDoEdit( $user, $ts2, false ); + $this->editTrackerDoEdit( $user, $ts1, true ); + + $this->assertSame( $ts1, $tracker->getFirstEditTimestamp( $user ) ); + $this->assertSame( $ts3, $tracker->getLatestEditTimestamp( $user ) ); + } + + public function testGetEditTimestamp_anon() { + $user = $this->getServiceContainer()->getUserFactory() + ->newFromName( '127.0.0.1', UserFactory::RIGOR_NONE ); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->editTrackerDoEdit( $user, '20200101000000', true ); + $this->assertFalse( $tracker->getFirstEditTimestamp( $user ) ); + $this->assertFalse( $tracker->getLatestEditTimestamp( $user ) ); + } + + public function testClearUserEditCache() { + $user = $this->getMutableTestUser()->getUser(); + $tracker = $this->getServiceContainer()->getUserEditTracker(); + $this->assertSame( 0, $tracker->getUserEditCount( $user ) ); + $this->setDbEditCount( $user, 1 ); + $this->assertSame( 0, $tracker->getUserEditCount( $user ) ); + $tracker->clearUserEditCache( $user ); + $this->assertSame( 1, $tracker->getUserEditCount( $user ) ); + } + +} diff --git a/tests/phpunit/unit/includes/user/UserEditTrackerTest.php b/tests/phpunit/unit/includes/user/UserEditTrackerTest.php deleted file mode 100644 index bd0a7f3619c..00000000000 --- a/tests/phpunit/unit/includes/user/UserEditTrackerTest.php +++ /dev/null @@ -1,344 +0,0 @@ -reqId = WebRequest::getRequestId(); - WebRequest::overrideRequestId( '000' ); - } - - /** @after */ - public function reqIdTearDown() { - WebRequest::overrideRequestId( $this->reqId ); - } - - public function testGetUserEditCount() { - // getUserEditCount returns a value found in user_editcount - $userId = 345623; - $methodName = 'MediaWiki\User\UserEditTracker::getUserEditCount'; - $editCount = 5; - - $actorMigration = $this->createMock( ActorMigration::class ); - - $database = $this->createMock( Database::class ); - $database->expects( $this->once() ) - ->method( 'selectField' ) - ->with( - 'user', - 'user_editcount', - [ 'user_id' => $userId ], - $methodName - ) - ->willReturn( $editCount ); - $loadBalancer = $this->createMock( LoadBalancer::class ); - $loadBalancer->expects( $this->once() ) - ->method( 'getConnectionRef' ) - ->with( DB_REPLICA ) - ->willReturn( $database ); - - $user = new UserIdentityValue( $userId, 'TestUser' ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - $this->assertSame( - $editCount, - $tracker->getUserEditCount( $user ) - ); - - // Now fetch from cache - $this->assertSame( - $editCount, - $tracker->getUserEditCount( $user ) - ); - } - - public function testGetUserEditCount_exception() { - // getUserEditCount throws if the user id is falsy - $userId = 0; - - $actorMigration = $this->createMock( ActorMigration::class ); - $loadBalancer = $this->createMock( LoadBalancer::class ); - - $user = new UserIdentityValue( $userId, __CLASS__ ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - - $this->expectException( InvalidArgumentException::class ); - $this->expectExceptionMessage( 'requires a user ID' ); - $tracker->getUserEditCount( $user ); - } - - public function testGetUserEditCount_null() { - // getUserEditCount doesn't find a value in user_editcount and calls - // initializeUserEditCount - $userId = 343; - $actorId = 9842; - $methodName1 = 'MediaWiki\User\UserEditTracker::getUserEditCount'; - $methodName2 = 'MediaWiki\User\UserEditTracker::initializeUserEditCount'; - $editCount = 17; - - $user = new UserIdentityValue( $userId, 'TestUser' ); - - $database1 = $this->createMock( Database::class ); - $database1->expects( $this->once() ) - ->method( 'selectField' ) - ->with( - 'user', - 'user_editcount', - [ 'user_id' => $userId ], - $methodName1 - ) - ->willReturn( null ); - - $database2 = $this->createMock( Database::class ); - $database2->expects( $this->once() ) - ->method( 'selectField' ) - ->with( - [ 'revision', 'temp_rev_user' => 'revision_actor_temp' ], - 'COUNT(*)', - [ [ 'temp_rev_actor.revactor_actor' => $actorId ] ], - $methodName2, - [], - [ 'temp_rev_actor' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] ] - ) - ->willReturn( $editCount ); - - $loadBalancer = $this->createMock( LoadBalancer::class ); - $loadBalancer->expects( $this->exactly( 2 ) ) - ->method( 'getConnectionRef' ) - ->withConsecutive( - [ DB_REPLICA ], - [ DB_REPLICA ] - ) - ->will( - $this->onConsecutiveCalls( - $database1, - $database2 - ) - ); - $actorMigration = $this->createMock( ActorMigration::class ); - $actorMigration->expects( $this->once() ) - ->method( 'getWhere' ) - ->with( - $database2, - 'rev_user', - $user - ) - ->willReturn( [ - 'tables' => [ 'temp_rev_user' => 'revision_actor_temp' ], - 'conds' => [ 'temp_rev_actor.revactor_actor' => $actorId ], - 'joins' => [ 'temp_rev_actor' => - [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] - ] - ] ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - $jobQueueGroup->expects( $this->once() ) - ->method( 'push' ) - ->willReturnCallback( function ( IJobSpecification $job ) use ( $user, $editCount ): void { - $this->assertEquals( 'userEditCountInit', $job->getType() ); - $this->assertEquals( [ - 'userId' => $user->getId(), - 'editCount' => $editCount, - 'requestId' => '000', - ], $job->getParams() ); - } ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - $this->assertSame( - $editCount, - $tracker->getUserEditCount( $user ) - ); - } - - public function testInitializeUserEditCount() { - // initializeUserEditCount counts the revisions in the database - $userId = 7281; - $actorId = 3982; - $methodName = 'MediaWiki\User\UserEditTracker::initializeUserEditCount'; - $editCount = 341; - - $user = new UserIdentityValue( $userId, 'TestUser' ); - - $database1 = $this->createMock( Database::class ); - $database1->expects( $this->once() ) - ->method( 'selectField' ) - ->with( - [ 'revision', 'temp_rev_user' => 'revision_actor_temp' ], - 'COUNT(*)', - [ [ 'temp_rev_actor.revactor_actor' => $actorId ] ], - $methodName, - [], - [ 'temp_rev_actor' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] ] - ) - ->willReturn( $editCount ); - - $loadBalancer = $this->createMock( LoadBalancer::class ); - $loadBalancer->method( 'getConnectionRef' ) - ->with( DB_REPLICA ) - ->willReturn( $database1 ); - $actorMigration = $this->createMock( ActorMigration::class ); - $actorMigration->expects( $this->once() ) - ->method( 'getWhere' ) - ->with( - $database1, - 'rev_user', - $user - ) - ->willReturn( [ - 'tables' => [ 'temp_rev_user' => 'revision_actor_temp' ], - 'conds' => [ 'temp_rev_actor.revactor_actor' => $actorId ], - 'joins' => [ 'temp_rev_actor' => - [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] - ] - ] ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - $jobQueueGroup->expects( $this->once() ) - ->method( 'push' ) - ->willReturnCallback( function ( IJobSpecification $job ) use ( $user, $editCount ): void { - $this->assertEquals( 'userEditCountInit', $job->getType() ); - $this->assertEquals( [ - 'userId' => $user->getId(), - 'editCount' => $editCount, - 'requestId' => '000', - ], $job->getParams() ); - } ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - $this->assertSame( - $editCount, - $tracker->initializeUserEditCount( $user ) - ); - } - - /** - * @dataProvider provideTestGetEditTimestamp - * @param string $type either 'first' or 'latest' - * @param string $time either 'null' for returning null, or a timestamp - */ - public function testGetEditTimestamp( $type, $time ) { - $methodName = 'MediaWiki\User\UserEditTracker::getUserEditTimestamp'; - $actorId = 982110; - - $user = new UserIdentityValue( 1, 'TestUser' ); - - $expectedSort = ( $type === 'first' ) ? 'ASC' : 'DESC'; - $dbTime = ( $time === 'null' ) ? null : $time; - - $database = $this->createMock( Database::class ); - $database->expects( $this->once() ) - ->method( 'selectField' ) - ->with( - [ - 'revision', - 'temp_rev_user' => 'revision_actor_temp' - ], - 'revactor_timestamp', - [ [ 'temp_rev_actor.revactor_actor' => $actorId ] ], - $methodName, - [ - 'ORDER BY' => 'revactor_timestamp ' . $expectedSort - ], - [ 'temp_rev_actor' => - [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] - ] - ) - ->willReturn( $dbTime ); - $loadBalancer = $this->createMock( LoadBalancer::class ); - $loadBalancer->expects( $this->once() ) - ->method( 'getConnectionRef' ) - ->with( DB_REPLICA ) - ->willReturn( $database ); - - $actorMigration = $this->createMock( ActorMigration::class ); - $actorMigration->expects( $this->once() ) - ->method( 'getWhere' ) - ->with( - $database, - 'rev_user', - $user - ) - ->willReturn( [ - 'tables' => [ 'temp_rev_user' => 'revision_actor_temp' ], - 'conds' => [ 'temp_rev_actor.revactor_actor' => $actorId ], - 'joins' => [ 'temp_rev_actor' => - [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ] - ] - ] ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - if ( $type === 'first' ) { - $ret = $tracker->getFirstEditTimestamp( $user ); - } else { - $ret = $tracker->getLatestEditTimestamp( $user ); - } - - if ( $dbTime === null ) { - $this->assertFalse( $ret ); - } else { - $this->assertSame( wfTimestamp( TS_MW, $dbTime ), $ret ); - } - } - - public function provideTestGetEditTimestamp() { - return [ - 'first with no edits' => [ 'first', 'null' ], - 'first with edits' => [ 'first', '20200421194632' ], - 'latest with no edits' => [ 'latest', 'null' ], - 'latest with edits' => [ 'latest', '20200421194632' ], - ]; - } - - public function testGetEditTimestamp_anon() { - $actorMigration = $this->createMock( ActorMigration::class ); - $loadBalancer = $this->createMock( LoadBalancer::class ); - - $user = new UserIdentityValue( 0, 'TestUser' ); - - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - $this->assertFalse( $tracker->getFirstEditTimestamp( $user ) ); - $this->assertFalse( $tracker->getLatestEditTimestamp( $user ) ); - } - - public function testClearUserEditCache() { - $actorMigration = $this->createMock( ActorMigration::class ); - $loadBalancer = $this->createMock( LoadBalancer::class ); - $jobQueueGroup = $this->createMock( JobQueueGroup::class ); - - $tracker = new UserEditTracker( $actorMigration, $loadBalancer, $jobQueueGroup ); - - $anon = new UserIdentityValue( 0, 'TestUser' ); - - $user = new UserIdentityValue( 123, 'TestUser' ); - - $accessible = TestingAccessWrapper::newFromObject( $tracker ); - $accessible->userEditCountCache = [ 'u123' => 5 ]; - - $tracker->clearUserEditCache( $anon ); // getId called once, early return - $tracker->clearUserEditCache( $user ); // actually cleared - - $this->assertNull( $accessible->userEditCountCache[ 'u123' ] ); - } - -}