Merge "Rewrite UserEditTrackerTest as an integration test"
This commit is contained in:
commit
333b30069e
2 changed files with 128 additions and 344 deletions
128
tests/phpunit/includes/user/UserEditTrackerTest.php
Normal file
128
tests/phpunit/includes/user/UserEditTrackerTest.php
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Revision\MutableRevisionRecord;
|
||||
use MediaWiki\Revision\SlotRecord;
|
||||
use MediaWiki\User\UserFactory;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use MediaWiki\User\UserIdentityValue;
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\UserEditTracker
|
||||
* @group Database
|
||||
*/
|
||||
class UserEditTrackerTest extends MediaWikiIntegrationTestCase {
|
||||
/**
|
||||
* Do an edit
|
||||
*
|
||||
* @param UserIdentity $user
|
||||
* @param string $timestamp
|
||||
* @param bool $create
|
||||
*/
|
||||
private function editTrackerDoEdit( $user, $timestamp, $create ) {
|
||||
$title = Title::newFromText( __FUNCTION__ );
|
||||
$page = $this->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 ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\User\UserEditTracker;
|
||||
use MediaWiki\User\UserIdentityValue;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
use Wikimedia\Rdbms\LoadBalancer;
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\UserEditTracker
|
||||
*
|
||||
* @author DannyS712
|
||||
*/
|
||||
class UserEditTrackerTest extends MediaWikiUnitTestCase {
|
||||
private $reqId;
|
||||
|
||||
/** @before */
|
||||
public function reqIdSetUp() {
|
||||
$this->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' ] );
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue