wiki.techinc.nl/tests/phpunit/includes/Revision/RevisionStoreTest.php
daniel 3b6345ca16 PageStore: Use LinkCache
This makes the data stored by LinkCache compatible with PageStoreRecord,
so we can use LinkCache inside PageStore.

This causes PageStore to make use of local caching as well as WANObjectCache.

Note that getPageById() does not yet benefit from cache, but does
populate the cache.

Bug: T278940
Change-Id: Icc27a0d9299a3e4ce45521daef87ad06ec06f064
2021-09-01 08:24:34 +00:00

348 lines
8.8 KiB
PHP

<?php
namespace MediaWiki\Tests\Revision;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionStore;
use MediaWikiIntegrationTestCase;
use MWTimestamp;
use PHPUnit\Framework\MockObject\MockObject;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\Rdbms\MaintainableDBConnRef;
use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Tests RevisionStore
*/
class RevisionStoreTest extends MediaWikiIntegrationTestCase {
/**
* @return RevisionStore
*/
private function getRevisionStore() {
return $this->getServiceContainer()->getRevisionStore();
}
/**
* @param IDatabase $db
*
* @return MockObject|ILoadBalancer
*/
private function installMockLoadBalancer( IDatabase $db ) {
$lb = $this->createNoOpMock(
ILoadBalancer::class,
[ 'getConnectionRef', 'getLocalDomainID', 'reuseConnection' ]
);
$dbRef = new MaintainableDBConnRef( $lb, $db, DB_PRIMARY );
$lb->method( 'getConnectionRef' )->willReturn( $dbRef );
$lb->method( 'getLocalDomainID' )->willReturn( 'fake' );
$lbf = $this->createNoOpMock( LBFactory::class, [ 'getMainLB', 'getLocalDomainID' ] );
$lbf->method( 'getMainLB' )->willReturn( $lb );
$lbf->method( 'getLocalDomainID' )->willReturn( 'fake' );
$this->setService( 'DBLoadBalancerFactory', $lbf );
return $lb;
}
/**
* @return MockObject|IDatabase
*/
private function installMockDatabase() {
$db = $this->getMockBuilder( IDatabase::class )
->disableAutoReturnValueGeneration()
->disableOriginalConstructor()->getMock();
$this->installMockLoadBalancer( $db );
return $db;
}
/**
* @return MockObject|IDatabase
*/
private function getMockDatabase() {
return $this->getMockBuilder( IDatabase::class )
->disableOriginalConstructor()->getMock();
}
private function getDummyPageRow( $extra = [] ) {
return (object)( $extra + [
'page_id' => 1337,
'page_namespace' => 0,
'page_title' => 'Test',
'page_is_redirect' => 0,
'page_is_new' => 0,
'page_touched' => MWTimestamp::now(),
'page_links_updated' => MWTimestamp::now(),
'page_latest' => 23948576,
'page_len' => 2323,
'page_content_model' => CONTENT_MODEL_WIKITEXT,
'page_restrictions' => ''
] );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromPageId() {
$db = $this->installMockDatabase();
// First query is by page ID. Return result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( $this->getDummyPageRow( [
'page_id' => '1',
'page_namespace' => '3',
'page_title' => 'Food',
] ) );
$db->method( 'selectRow' )
->willReturn( false );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 3, $title->getNamespace() );
$this->assertSame( 'Food', $title->getDBkey() );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromPageIdOnFallback() {
$db = $this->installMockDatabase();
// First query, by page_id, no result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// Second query, by rev_id, no result
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( false );
// Retrying on master...
// Third query, by page_id again
$db->expects( $this->at( 2 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '2',
'page_title' => 'Foodey',
] ) );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 2, $title->getNamespace() );
$this->assertSame( 'Foodey', $title->getDBkey() );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromRevId() {
$db = $this->installMockDatabase();
// First call to Title::newFromID, faking no result (db lag?)
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// Second select using rev_id, faking no result (db lag?)
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '1',
'page_title' => 'Food2',
] ) );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 1, $title->getNamespace() );
$this->assertSame( 'Food2', $title->getDBkey() );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_successFromRevIdOnFallback() {
$db = $this->installMockDatabase();
// First query, by page_id, no result
$db->expects( $this->at( 0 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// Second query, by rev_id, no result
$db->expects( $this->at( 1 ) )
->method( 'selectRow' )
->with(
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( false );
// Retrying on master...
// Third query, by page_id again, still no result
$db->expects( $this->at( 2 ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
// Forth query, by rev_id agin
$db->expects( $this->at( 3 ) )
->method( 'selectRow' )
->with(
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( $this->getDummyPageRow( [
'page_namespace' => '2',
'page_title' => 'Foodey',
] ) );
$store = $this->getRevisionStore();
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
$this->assertSame( 2, $title->getNamespace() );
$this->assertSame( 'Foodey', $title->getDBkey() );
}
/**
* @covers \MediaWiki\Revision\RevisionStore::getTitle
*/
public function testGetTitle_correctFallbackAndthrowsExceptionAfterFallbacks() {
$db = $this->getMockDatabase();
$mockLoadBalancer = $this->installMockLoadBalancer( $db );
// Assert that the first call uses a REPLICA and the second falls back to master
// RevisionStore getTitle uses getConnectionRef
$mockLoadBalancer->expects( $this->exactly( 4 ) )
->method( 'getConnectionRef' )
->willReturnCallback( function ( $masterOrReplica ) use ( $db ) {
static $callCounter = 0;
$callCounter++;
// The first call should be to a REPLICA, and the second a MASTER.
if ( $callCounter < 3 ) {
$this->assertSame( DB_REPLICA, $masterOrReplica );
} else {
$this->assertSame( DB_PRIMARY, $masterOrReplica );
}
return $db;
} );
// First and third call to Title::newFromID, faking no result
foreach ( [ 0, 2 ] as $counter ) {
$db->expects( $this->at( $counter ) )
->method( 'selectRow' )
->with(
[ 'page' ],
$this->anything(),
[ 'page_id' => 1 ]
)
->willReturn( false );
}
foreach ( [ 1, 3 ] as $counter ) {
$db->expects( $this->at( $counter ) )
->method( 'selectRow' )
->with(
[ 0 => 'page', 'revision' => 'revision' ],
$this->anything(),
[ 'rev_id' => 2 ]
)
->willReturn( false );
}
$store = $this->getRevisionStore( $mockLoadBalancer );
$this->expectException( RevisionAccessException::class );
$store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
}
public function provideIsRevisionRow() {
yield 'invalid row type' => [
'row' => new class() {
},
'expect' => false,
];
yield 'invalid row' => [
'row' => (object)[ 'blabla' => 'bla' ],
'expect' => false,
];
yield 'valid row' => [
'row' => (object)[
'rev_id' => 321,
'rev_page' => 123,
'rev_timestamp' => ConvertibleTimestamp::now(),
'rev_minor_edit' => 0,
'rev_deleted' => 0,
'rev_len' => 10,
'rev_parent_id' => 123,
'rev_sha1' => 'abc',
'rev_comment_text' => 'blabla',
'rev_comment_data' => 'blablabla',
'rev_comment_cid' => 1,
'rev_actor' => 1,
'rev_user' => 1,
'rev_user_text' => 'alala',
],
'expect' => true,
];
}
/**
* @covers \MediaWiki\Storage\RevisionStore::isRevisionRow
* @dataProvider provideIsRevisionRow
*/
public function testIsRevisionRow( $row, bool $expect ) {
$this->assertSame( $expect, $this->getRevisionStore()->isRevisionRow( $row ) );
}
}