Rewrite UserEditTrackerTest as an integration test

As previously discussed, the database should usually not be mocked. I'm
changing many of these queries for the next stage of the actor
migration, and just copying my own query from the code to the test file
does not give me any confidence that the query I'm writing is correct.
The unit test was simply ensuring that the implementation of the class
doesn't change, it wasn't verifying correct operation.

Change-Id: Id3ee02fd547d0a7b5b7f35866ee317e8b09e6c18
This commit is contained in:
Tim Starling 2021-05-11 12:16:20 +10:00
parent 1bfa8e0791
commit 02ae9aa0ba
2 changed files with 128 additions and 344 deletions

View 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 ) );
}
}

View file

@ -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' ] );
}
}