wiki.techinc.nl/tests/phpunit/includes/RevisionDbTestBase.php

1479 lines
40 KiB
PHP
Raw Normal View History

<?php
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\RevisionStore;
use MediaWiki\Storage\IncompleteRevisionException;
use MediaWiki\Storage\RevisionRecord;
/**
* RevisionDbTestBase contains test cases for the Revision class that have Database interactions.
*
* @group Database
* @group medium
*/
abstract class RevisionDbTestBase extends MediaWikiTestCase {
/**
* @var WikiPage $testPage
*/
private $testPage;
public function __construct( $name = null, array $data = [], $dataName = '' ) {
parent::__construct( $name, $data, $dataName );
$this->tablesUsed = array_merge( $this->tablesUsed,
[
'page',
'revision',
'ip_changes',
'text',
'archive',
'recentchanges',
'logging',
'page_props',
'pagelinks',
'categorylinks',
'langlinks',
'externallinks',
'imagelinks',
'templatelinks',
'iwlinks'
]
);
}
protected function setUp() {
global $wgContLang;
parent::setUp();
$this->mergeMwGlobalArrayValue(
'wgExtraNamespaces',
[
12312 => 'Dummy',
12313 => 'Dummy_talk',
]
);
$this->mergeMwGlobalArrayValue(
'wgNamespaceContentModels',
[
12312 => DummyContentForTesting::MODEL_ID,
]
);
$this->mergeMwGlobalArrayValue(
'wgContentHandlers',
[
DummyContentForTesting::MODEL_ID => 'DummyContentHandlerForTesting',
RevisionTestModifyableContent::MODEL_ID => 'RevisionTestModifyableContentHandler',
]
);
$this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
if ( !$this->testPage ) {
/**
* We have to create a new page for each subclass as the page creation may result
* in different DB fields being filled based on configuration.
*/
$this->testPage = $this->createPage( __CLASS__, __CLASS__ );
}
}
protected function tearDown() {
global $wgContLang;
parent::tearDown();
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
}
abstract protected function getContentHandlerUseDB();
private function makeRevisionWithProps( $props = null ) {
if ( $props === null ) {
$props = [];
}
if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
$props['text'] = 'Lorem Ipsum';
}
if ( !isset( $props['user_text'] ) ) {
$props['user_text'] = 'Tester';
}
if ( !isset( $props['user'] ) ) {
$props['user'] = 0;
}
if ( !isset( $props['comment'] ) ) {
$props['comment'] = 'just a test';
}
if ( !isset( $props['page'] ) ) {
$props['page'] = $this->testPage->getId();
}
if ( !isset( $props['content_model'] ) ) {
$props['content_model'] = CONTENT_MODEL_WIKITEXT;
}
$rev = new Revision( $props );
$dbw = wfGetDB( DB_MASTER );
$rev->insertOn( $dbw );
return $rev;
}
/**
* @param string $titleString
* @param string $text
* @param string|null $model
*
* @return WikiPage
*/
private function createPage( $titleString, $text, $model = null ) {
if ( !preg_match( '/:/', $titleString ) &&
( $model === null || $model === CONTENT_MODEL_WIKITEXT )
) {
$ns = $this->getDefaultWikitextNS();
$titleString = MWNamespace::getCanonicalName( $ns ) . ':' . $titleString;
}
$title = Title::newFromText( $titleString );
$wikipage = new WikiPage( $title );
// Delete the article if it already exists
if ( $wikipage->exists() ) {
$wikipage->doDeleteArticle( "done" );
}
$content = ContentHandler::makeContent( $text, $title, $model );
$wikipage->doEditContent( $content, __METHOD__, EDIT_NEW );
return $wikipage;
}
private function assertRevEquals( Revision $orig, Revision $rev = null ) {
$this->assertNotNull( $rev, 'missing revision' );
$this->assertEquals( $orig->getId(), $rev->getId() );
$this->assertEquals( $orig->getPage(), $rev->getPage() );
$this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
$this->assertEquals( $orig->getUser(), $rev->getUser() );
$this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
$this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
$this->assertEquals( $orig->getSha1(), $rev->getSha1() );
}
/**
* @covers Revision::getRecentChange
*/
public function testGetRecentChange() {
$rev = $this->testPage->getRevision();
$recentChange = $rev->getRecentChange();
// Make sure various attributes look right / the correct entry has been retrieved.
$this->assertEquals( $rev->getTimestamp(), $recentChange->getAttribute( 'rc_timestamp' ) );
$this->assertEquals(
$rev->getTitle()->getNamespace(),
$recentChange->getAttribute( 'rc_namespace' )
);
$this->assertEquals(
$rev->getTitle()->getDBkey(),
$recentChange->getAttribute( 'rc_title' )
);
$this->assertEquals( $rev->getUser(), $recentChange->getAttribute( 'rc_user' ) );
$this->assertEquals( $rev->getUserText(), $recentChange->getAttribute( 'rc_user_text' ) );
$this->assertEquals( $rev->getComment(), $recentChange->getAttribute( 'rc_comment' ) );
$this->assertEquals( $rev->getPage(), $recentChange->getAttribute( 'rc_cur_id' ) );
$this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
}
/**
* @covers Revision::insertOn
*/
public function testInsertOn_success() {
$parentId = $this->testPage->getLatest();
// If an ExternalStore is set don't use it.
$this->setMwGlobals( 'wgDefaultExternalStore', false );
$rev = new Revision( [
'page' => $this->testPage->getId(),
'title' => $this->testPage->getTitle(),
'text' => 'Revision Text',
'comment' => 'Revision comment',
] );
$revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
$this->assertInternalType( 'integer', $revId );
$this->assertSame( $revId, $rev->getId() );
// getTextId() must be an int!
$this->assertInternalType( 'integer', $rev->getTextId() );
$mainSlot = $rev->getRevisionRecord()->getSlot( 'main', RevisionRecord::RAW );
// we currently only support storage in the text table
$textId = MediaWikiServices::getInstance()
->getBlobStore()
->getTextIdFromAddress( $mainSlot->getAddress() );
$this->assertSelect(
'text',
[ 'old_id', 'old_text' ],
"old_id = $textId",
[ [ strval( $textId ), 'Revision Text' ] ]
);
$this->assertSelect(
'revision',
[
'rev_id',
'rev_page',
'rev_text_id',
'rev_user',
'rev_minor_edit',
'rev_deleted',
'rev_len',
'rev_parent_id',
'rev_sha1',
],
"rev_id = {$rev->getId()}",
[ [
strval( $rev->getId() ),
strval( $this->testPage->getId() ),
strval( $textId ),
'0',
'0',
'0',
'13',
strval( $parentId ),
's0ngbdoxagreuf2vjtuxzwdz64n29xm',
] ]
);
}
/**
* @covers Revision::insertOn
*/
public function testInsertOn_exceptionOnNoPage() {
// If an ExternalStore is set don't use it.
$this->setMwGlobals( 'wgDefaultExternalStore', false );
$this->setExpectedException(
IncompleteRevisionException::class,
"rev_page field must not be 0!"
);
$title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
$rev = new Revision( [], 0, $title );
$rev->insertOn( wfGetDB( DB_MASTER ) );
}
/**
* @covers Revision::newFromTitle
*/
public function testNewFromTitle_withoutId() {
$latestRevId = $this->testPage->getLatest();
$rev = Revision::newFromTitle( $this->testPage->getTitle() );
$this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
$this->assertEquals( $latestRevId, $rev->getId() );
}
/**
* @covers Revision::newFromTitle
*/
public function testNewFromTitle_withId() {
$latestRevId = $this->testPage->getLatest();
$rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId );
$this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
$this->assertEquals( $latestRevId, $rev->getId() );
}
/**
* @covers Revision::newFromTitle
*/
public function testNewFromTitle_withBadId() {
$latestRevId = $this->testPage->getLatest();
$rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId + 1 );
$this->assertNull( $rev );
}
/**
* @covers Revision::newFromRow
*/
public function testNewFromRow() {
$orig = $this->makeRevisionWithProps();
$dbr = wfGetDB( DB_REPLICA );
$revQuery = Revision::getQueryInfo();
$res = $dbr->select( $revQuery['tables'], $revQuery['fields'], [ 'rev_id' => $orig->getId() ],
__METHOD__, [], $revQuery['joins'] );
$this->assertTrue( is_object( $res ), 'query failed' );
$row = $res->fetchObject();
$res->free();
$rev = Revision::newFromRow( $row );
$this->assertRevEquals( $orig, $rev );
}
public function provideNewFromArchiveRow() {
yield [
function ( $f ) {
return $f;
},
];
yield [
function ( $f ) {
return $f + [ 'ar_namespace', 'ar_title' ];
},
];
yield [
function ( $f ) {
unset( $f['ar_text'] );
return $f;
},
];
yield [
function ( $f ) {
unset( $f['ar_text_id'] );
return $f;
},
];
yield [
function ( $f ) {
unset( $f['ar_page_id'] );
return $f;
},
];
yield [
function ( $f ) {
unset( $f['ar_parent_id'] );
return $f;
},
];
yield [
function ( $f ) {
unset( $f['ar_rev_id'] );
return $f;
},
];
yield [
function ( $f ) {
unset( $f['ar_sha1'] );
return $f;
},
];
}
/**
* @dataProvider provideNewFromArchiveRow
* @covers Revision::newFromArchiveRow
*/
public function testNewFromArchiveRow( $selectModifier ) {
$services = MediaWikiServices::getInstance();
$store = new RevisionStore(
$services->getDBLoadBalancer(),
$services->getService( '_SqlBlobStore' ),
$services->getMainWANObjectCache()
);
$store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
$this->setService( 'RevisionStore', $store );
$page = $this->createPage(
'RevisionStorageTest_testNewFromArchiveRow',
'Lorem Ipsum',
CONTENT_MODEL_WIKITEXT
);
$orig = $page->getRevision();
$page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
$dbr = wfGetDB( DB_REPLICA );
$arQuery = Revision::getArchiveQueryInfo();
$arQuery['fields'] = $selectModifier( $arQuery['fields'] );
$res = $dbr->select(
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
__METHOD__, [], $arQuery['joins']
);
$this->assertTrue( is_object( $res ), 'query failed' );
$row = $res->fetchObject();
$res->free();
// MCR migration note: $row is now required to contain ar_title and ar_namespace.
// Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
$rev = Revision::newFromArchiveRow( $row );
$this->assertRevEquals( $orig, $rev );
}
/**
* @covers Revision::newFromArchiveRow
*/
public function testNewFromArchiveRowOverrides() {
$page = $this->createPage(
'RevisionStorageTest_testNewFromArchiveRow',
'Lorem Ipsum',
CONTENT_MODEL_WIKITEXT
);
$orig = $page->getRevision();
$page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
$dbr = wfGetDB( DB_REPLICA );
$arQuery = Revision::getArchiveQueryInfo();
$res = $dbr->select(
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
__METHOD__, [], $arQuery['joins']
);
$this->assertTrue( is_object( $res ), 'query failed' );
$row = $res->fetchObject();
$res->free();
$rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
$this->assertNotEquals( $orig->getComment(), $rev->getComment() );
$this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
}
/**
* @covers Revision::newFromId
*/
public function testNewFromId() {
$orig = $this->testPage->getRevision();
$rev = Revision::newFromId( $orig->getId() );
$this->assertRevEquals( $orig, $rev );
}
/**
* @covers Revision::newFromPageId
*/
public function testNewFromPageId() {
$rev = Revision::newFromPageId( $this->testPage->getId() );
$this->assertRevEquals(
$this->testPage->getRevision(),
$rev
);
}
/**
* @covers Revision::newFromPageId
*/
public function testNewFromPageIdWithLatestId() {
$rev = Revision::newFromPageId(
$this->testPage->getId(),
$this->testPage->getLatest()
);
$this->assertRevEquals(
$this->testPage->getRevision(),
$rev
);
}
/**
* @covers Revision::newFromPageId
*/
public function testNewFromPageIdWithNotLatestId() {
$content = new WikitextContent( __METHOD__ );
$this->testPage->doEditContent( $content, __METHOD__ );
$rev = Revision::newFromPageId(
$this->testPage->getId(),
$this->testPage->getRevision()->getPrevious()->getId()
);
$this->assertRevEquals(
$this->testPage->getRevision()->getPrevious(),
$rev
);
}
/**
* @covers Revision::fetchRevision
*/
public function testFetchRevision() {
// Hidden process cache assertion below
$this->testPage->getRevision()->getId();
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$id = $this->testPage->getRevision()->getId();
$this->hideDeprecated( 'Revision::fetchRevision' );
$res = Revision::fetchRevision( $this->testPage->getTitle() );
# note: order is unspecified
$rows = [];
while ( ( $row = $res->fetchObject() ) ) {
$rows[$row->rev_id] = $row;
}
$this->assertEmpty( $rows, 'expected empty set' );
}
/**
* @covers Revision::getPage
*/
public function testGetPage() {
$page = $this->testPage;
$orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
$rev = Revision::newFromId( $orig->getId() );
$this->assertEquals( $page->getId(), $rev->getPage() );
}
/**
* @covers Revision::isCurrent
*/
public function testIsCurrent() {
$rev1 = $this->testPage->getRevision();
# @todo find out if this should be true
# $this->assertTrue( $rev1->isCurrent() );
$rev1x = Revision::newFromId( $rev1->getId() );
$this->assertTrue( $rev1x->isCurrent() );
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$rev2 = $this->testPage->getRevision();
# @todo find out if this should be true
# $this->assertTrue( $rev2->isCurrent() );
$rev1x = Revision::newFromId( $rev1->getId() );
$this->assertFalse( $rev1x->isCurrent() );
$rev2x = Revision::newFromId( $rev2->getId() );
$this->assertTrue( $rev2x->isCurrent() );
}
/**
* @covers Revision::getPrevious
*/
public function testGetPrevious() {
$oldestRevision = $this->testPage->getOldestRevision();
$latestRevision = $this->testPage->getLatest();
$this->assertNull( $oldestRevision->getPrevious() );
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$newRevision = $this->testPage->getRevision();
$this->assertNotNull( $newRevision->getPrevious() );
$this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
}
/**
* @covers Revision::getNext
*/
public function testGetNext() {
$rev1 = $this->testPage->getRevision();
$this->assertNull( $rev1->getNext() );
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$rev2 = $this->testPage->getRevision();
$this->assertNotNull( $rev1->getNext() );
$this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
}
/**
* @covers Revision::newNullRevision
*/
public function testNewNullRevision() {
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$orig = $this->testPage->getRevision();
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::newNullRevision( $dbw, $this->testPage->getId(), 'a null revision', false );
$this->assertNotEquals( $orig->getId(), $rev->getId(),
'new null revision should have a different id from the original revision' );
$this->assertEquals( $orig->getTextId(), $rev->getTextId(),
'new null revision should have the same text id as the original revision' );
$this->assertEquals( $orig->getSha1(), $rev->getSha1(),
'new null revision should have the same SHA1 as the original revision' );
$this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
'new null revision should have the same content as the original revision' );
$this->assertEquals( __METHOD__, $rev->getContent()->getNativeData() );
}
/**
* @covers Revision::insertOn
*/
public function testInsertOn() {
$ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
$orig = $this->makeRevisionWithProps( [
'user_text' => $ip
] );
// Make sure the revision was copied to ip_changes
$dbr = wfGetDB( DB_REPLICA );
$res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
$row = $res->fetchObject();
$this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
$this->assertEquals(
$orig->getTimestamp(),
wfTimestamp( TS_MW, $row->ipc_rev_timestamp )
);
}
Clean and repair many phpunit tests (+ fix implied configuration) This commit depends on the introduction of MediaWikiTestCase::setMwGlobals in change Iccf6ea81f4. Various tests already set their globals, but forgot to restore them afterwards, or forgot to call the parent setUp, tearDown... Either way they won't have to anymore with setMwGlobals. Consistent use of function characteristics: * protected function setUp * protected function tearDown * public static function (provide..) (Matching the function signature with PHPUnit/Framework/TestCase.php) Replaces: * public function (setUp|tearDown)\( * protected function $1( * \tfunction (setUp|tearDown)\( * \tprotected function $1( * \tfunction (data|provide)\( * \tpublic static function $1\( Also renamed a few "data#", "provider#" and "provides#" functions to "provide#" for consistency. This also removes confusion where the /media tests had a few private methods called dataFile(), which were sometimes expected to be data providers. Fixes: TimestampTest often failed due to a previous test setting a different language (it tests "1 hour ago" so need to make sure it is set to English). MWNamespaceTest became a lot cleaner now that it executes with a known context. Though the now-redundant code that was removed didn't work anyway because wgContentNamespaces isn't keyed by namespace id, it had them was values... FileBackendTest: * Fixed: "PHP Fatal: Using $this when not in object context" HttpTest * Added comment about: "PHP Fatal: Call to protected MWHttpRequest::__construct()" (too much unrelated code to fix in this commit) ExternalStoreTest * Add an assertTrue as well, without it the test is useless because regardless of whether wgExternalStores is true or false it only uses it if it is an array. Change-Id: I9d2b148e57bada64afeb7d5a99bec0e58f8e1561
2012-10-08 10:56:20 +00:00
public static function provideUserWasLastToEdit() {
yield 'actually the last edit' => [ 3, true ];
yield 'not the current edit, but still by this user' => [ 2, true ];
yield 'edit by another user' => [ 1, false ];
yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
}
/**
Clean and repair many phpunit tests (+ fix implied configuration) This commit depends on the introduction of MediaWikiTestCase::setMwGlobals in change Iccf6ea81f4. Various tests already set their globals, but forgot to restore them afterwards, or forgot to call the parent setUp, tearDown... Either way they won't have to anymore with setMwGlobals. Consistent use of function characteristics: * protected function setUp * protected function tearDown * public static function (provide..) (Matching the function signature with PHPUnit/Framework/TestCase.php) Replaces: * public function (setUp|tearDown)\( * protected function $1( * \tfunction (setUp|tearDown)\( * \tprotected function $1( * \tfunction (data|provide)\( * \tpublic static function $1\( Also renamed a few "data#", "provider#" and "provides#" functions to "provide#" for consistency. This also removes confusion where the /media tests had a few private methods called dataFile(), which were sometimes expected to be data providers. Fixes: TimestampTest often failed due to a previous test setting a different language (it tests "1 hour ago" so need to make sure it is set to English). MWNamespaceTest became a lot cleaner now that it executes with a known context. Though the now-redundant code that was removed didn't work anyway because wgContentNamespaces isn't keyed by namespace id, it had them was values... FileBackendTest: * Fixed: "PHP Fatal: Using $this when not in object context" HttpTest * Added comment about: "PHP Fatal: Call to protected MWHttpRequest::__construct()" (too much unrelated code to fix in this commit) ExternalStoreTest * Add an assertTrue as well, without it the test is useless because regardless of whether wgExternalStores is true or false it only uses it if it is an array. Change-Id: I9d2b148e57bada64afeb7d5a99bec0e58f8e1561
2012-10-08 10:56:20 +00:00
* @dataProvider provideUserWasLastToEdit
*/
public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
$userA = User::newFromName( "RevisionStorageTest_userA" );
$userB = User::newFromName( "RevisionStorageTest_userB" );
if ( $userA->getId() === 0 ) {
$userA = User::createNew( $userA->getName() );
}
if ( $userB->getId() === 0 ) {
$userB = User::createNew( $userB->getName() );
}
$ns = $this->getDefaultWikitextNS();
$dbw = wfGetDB( DB_MASTER );
$revisions = [];
// create revisions -----------------------------
$page = WikiPage::factory( Title::newFromText(
'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
Insert test page into DB not to violate constraint Page with id 0 does not exist at the time transaction is committed to the database. Unit tests with table constraints enabled fail with: 1) RevisionStorageTest::testUserWasLastToEdit with data set #0 (3, true) DBQueryError: A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script Query: INSERT INTO "unittest_revision" (rev_id,rev_page,rev_text_id /* more fields */ ) VALUES ('461','0','460' /* more fields */ Function: Revision::insertOn Error: 23503 ERROR: insert or update on table "unittest_revision" violates foreign key constraint "ut_revision_rev_page_fkey" DETAIL: Key (rev_page)=(0) is not present in table "unittest_page". /usr/home/saper/test/mytest/includes/db/Database.php:1111 /usr/home/saper/test/mytest/includes/db/DatabasePostgres.php:511 /usr/home/saper/test/mytest/includes/db/Database.php:1077 /usr/home/saper/test/mytest/includes/db/DatabasePostgres.php:871 /usr/home/saper/test/mytest/includes/Revision.php:1422 /usr/home/saper/test/mytest/tests/phpunit/includes/RevisionStorageTest.php:488 /usr/home/saper/test/mytest/tests/phpunit/MediaWikiTestCase.php:123 /usr/home/saper/test/mytest/tests/phpunit/MediaWikiPHPUnitCommand.php:80 /usr/home/saper/test/mytest/tests/phpunit/MediaWikiPHPUnitCommand.php:64 /usr/home/saper/test/mytest/tests/phpunit/phpunit.php:115 Change-Id: I653a8bccdaa748a9bea453cd1dbf609a30e1ff6f
2013-12-07 15:40:58 +00:00
$page->insertOn( $dbw );
$revisions[0] = new Revision( [
'page' => $page->getId(),
// we need the title to determine the page's default content model
'title' => $page->getTitle(),
'timestamp' => '20120101000000',
'user' => $userA->getId(),
'text' => 'zero',
'content_model' => CONTENT_MODEL_WIKITEXT,
'comment' => 'edit zero'
] );
$revisions[0]->insertOn( $dbw );
$revisions[1] = new Revision( [
'page' => $page->getId(),
// still need the title, because $page->getId() is 0 (there's no entry in the page table)
'title' => $page->getTitle(),
'timestamp' => '20120101000100',
'user' => $userA->getId(),
'text' => 'one',
'content_model' => CONTENT_MODEL_WIKITEXT,
'comment' => 'edit one'
] );
$revisions[1]->insertOn( $dbw );
$revisions[2] = new Revision( [
'page' => $page->getId(),
'title' => $page->getTitle(),
'timestamp' => '20120101000200',
'user' => $userB->getId(),
'text' => 'two',
'content_model' => CONTENT_MODEL_WIKITEXT,
'comment' => 'edit two'
] );
$revisions[2]->insertOn( $dbw );
$revisions[3] = new Revision( [
'page' => $page->getId(),
'title' => $page->getTitle(),
'timestamp' => '20120101000300',
'user' => $userA->getId(),
'text' => 'three',
'content_model' => CONTENT_MODEL_WIKITEXT,
'comment' => 'edit three'
] );
$revisions[3]->insertOn( $dbw );
$revisions[4] = new Revision( [
'page' => $page->getId(),
'title' => $page->getTitle(),
'timestamp' => '20120101000200',
'user' => $userA->getId(),
'text' => 'zero',
'content_model' => CONTENT_MODEL_WIKITEXT,
'comment' => 'edit four'
] );
$revisions[4]->insertOn( $dbw );
// test it ---------------------------------
$since = $revisions[$sinceIdx]->getTimestamp();
$allRows = iterator_to_array( $dbw->select(
'revision',
[ 'rev_id', 'rev_timestamp', 'rev_user' ],
[
'rev_page' => $page->getId(),
//'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
],
__METHOD__,
[ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
) );
$wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
$this->assertEquals( $expectedLast, $wasLast );
}
/**
* @param string $text
* @param string $title
* @param string $model
* @param string $format
*
* @return Revision
*/
private function newTestRevision( $text, $title = "Test",
$model = CONTENT_MODEL_WIKITEXT, $format = null
) {
if ( is_string( $title ) ) {
$title = Title::newFromText( $title );
}
$content = ContentHandler::makeContent( $text, $title, $model, $format );
$rev = new Revision(
[
'id' => 42,
'page' => 23,
'title' => $title,
'content' => $content,
'length' => $content->getSize(),
'comment' => "testing",
'minor_edit' => false,
'content_format' => $format,
]
);
return $rev;
}
public function provideGetContentModel() {
// NOTE: we expect the help namespace to always contain wikitext
return [
[ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
[ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ],
[ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
];
}
/**
* @dataProvider provideGetContentModel
* @covers Revision::getContentModel
*/
public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
$rev = $this->newTestRevision( $text, $title, $model, $format );
$this->assertEquals( $expectedModel, $rev->getContentModel() );
}
public function provideGetContentFormat() {
// NOTE: we expect the help namespace to always contain wikitext
return [
[ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
[ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ],
[ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ],
[ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
];
}
/**
* @dataProvider provideGetContentFormat
* @covers Revision::getContentFormat
*/
public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
$rev = $this->newTestRevision( $text, $title, $model, $format );
$this->assertEquals( $expectedFormat, $rev->getContentFormat() );
}
public function provideGetContentHandler() {
// NOTE: we expect the help namespace to always contain wikitext
return [
[ 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ],
[ 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ],
[ serialize( 'hello world' ), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ],
];
}
/**
* @dataProvider provideGetContentHandler
* @covers Revision::getContentHandler
*/
public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
$rev = $this->newTestRevision( $text, $title, $model, $format );
$this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
}
public function provideGetContent() {
// NOTE: we expect the help namespace to always contain wikitext
return [
[ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
[
serialize( 'hello world' ),
'Hello',
DummyContentForTesting::MODEL_ID,
null,
Revision::FOR_PUBLIC,
serialize( 'hello world' )
],
[
serialize( 'hello world' ),
'Dummy:Hello',
null,
null,
Revision::FOR_PUBLIC,
serialize( 'hello world' )
],
];
}
/**
* @dataProvider provideGetContent
* @covers Revision::getContent
*/
public function testGetContent( $text, $title, $model, $format,
$audience, $expectedSerialization
) {
$rev = $this->newTestRevision( $text, $title, $model, $format );
$content = $rev->getContent( $audience );
$this->assertEquals(
$expectedSerialization,
is_null( $content ) ? null : $content->serialize( $format )
);
}
/**
* @covers Revision::getContent
*/
public function testGetContent_failure() {
$rev = new Revision( [
'page' => $this->testPage->getId(),
'content_model' => $this->testPage->getContentModel(),
'text_id' => 123456789, // not in the test DB
] );
MediaWiki\suppressWarnings(); // bad text_id will trigger a warning.
$this->assertNull( $rev->getContent(),
"getContent() should return null if the revision's text blob could not be loaded." );
// NOTE: check this twice, once for lazy initialization, and once with the cached value.
$this->assertNull( $rev->getContent(),
"getContent() should return null if the revision's text blob could not be loaded." );
MediaWiki\suppressWarnings( 'end' );
}
public function provideGetSize() {
return [
[ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
[ serialize( "hello world." ), DummyContentForTesting::MODEL_ID, 12 ],
];
}
/**
* @covers Revision::getSize
* @dataProvider provideGetSize
*/
public function testGetSize( $text, $model, $expected_size ) {
$rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
$this->assertEquals( $expected_size, $rev->getSize() );
}
public function provideGetSha1() {
return [
[ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
[
serialize( "hello world." ),
DummyContentForTesting::MODEL_ID,
Revision::base36Sha1( serialize( "hello world." ) )
],
];
}
/**
* @covers Revision::getSha1
* @dataProvider provideGetSha1
*/
public function testGetSha1( $text, $model, $expected_hash ) {
$rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
$this->assertEquals( $expected_hash, $rev->getSha1() );
}
/**
* Tests whether $rev->getContent() returns a clone when needed.
*
* @covers Revision::getContent
*/
public function testGetContentClone() {
$content = new RevisionTestModifyableContent( "foo" );
$rev = new Revision(
[
'id' => 42,
'page' => 23,
'title' => Title::newFromText( "testGetContentClone_dummy" ),
'content' => $content,
'length' => $content->getSize(),
'comment' => "testing",
'minor_edit' => false,
]
);
/** @var RevisionTestModifyableContent $content */
$content = $rev->getContent( Revision::RAW );
$content->setText( "bar" );
/** @var RevisionTestModifyableContent $content2 */
$content2 = $rev->getContent( Revision::RAW );
// content is mutable, expect clone
$this->assertNotSame( $content, $content2, "expected a clone" );
// clone should contain the original text
$this->assertEquals( "foo", $content2->getText() );
$content2->setText( "bla bla" );
// clones should be independent
$this->assertEquals( "bar", $content->getText() );
}
/**
* Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
* @covers Revision::getContent
*/
public function testGetContentUncloned() {
$rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
$content = $rev->getContent( Revision::RAW );
$content2 = $rev->getContent( Revision::RAW );
// for immutable content like wikitext, this should be the same object
$this->assertSame( $content, $content2 );
}
/**
* @covers Revision::loadFromId
*/
public function testLoadFromId() {
$rev = $this->testPage->getRevision();
$this->hideDeprecated( 'Revision::loadFromId' );
$this->assertRevEquals(
$rev,
Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
);
}
/**
* @covers Revision::loadFromPageId
*/
public function testLoadFromPageId() {
$this->assertRevEquals(
$this->testPage->getRevision(),
Revision::loadFromPageId( wfGetDB( DB_MASTER ), $this->testPage->getId() )
);
}
/**
* @covers Revision::loadFromPageId
*/
public function testLoadFromPageIdWithLatestRevId() {
$this->assertRevEquals(
$this->testPage->getRevision(),
Revision::loadFromPageId(
wfGetDB( DB_MASTER ),
$this->testPage->getId(),
$this->testPage->getLatest()
)
);
}
/**
* @covers Revision::loadFromPageId
*/
public function testLoadFromPageIdWithNotLatestRevId() {
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$this->assertRevEquals(
$this->testPage->getRevision()->getPrevious(),
Revision::loadFromPageId(
wfGetDB( DB_MASTER ),
$this->testPage->getId(),
$this->testPage->getRevision()->getPrevious()->getId()
)
);
}
/**
* @covers Revision::loadFromTitle
*/
public function testLoadFromTitle() {
$this->assertRevEquals(
$this->testPage->getRevision(),
Revision::loadFromTitle( wfGetDB( DB_MASTER ), $this->testPage->getTitle() )
);
}
/**
* @covers Revision::loadFromTitle
*/
public function testLoadFromTitleWithLatestRevId() {
$this->assertRevEquals(
$this->testPage->getRevision(),
Revision::loadFromTitle(
wfGetDB( DB_MASTER ),
$this->testPage->getTitle(),
$this->testPage->getLatest()
)
);
}
/**
* @covers Revision::loadFromTitle
*/
public function testLoadFromTitleWithNotLatestRevId() {
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$this->assertRevEquals(
$this->testPage->getRevision()->getPrevious(),
Revision::loadFromTitle(
wfGetDB( DB_MASTER ),
$this->testPage->getTitle(),
$this->testPage->getRevision()->getPrevious()->getId()
)
);
}
/**
* @covers Revision::loadFromTimestamp()
*/
public function testLoadFromTimestamp() {
$this->assertRevEquals(
$this->testPage->getRevision(),
Revision::loadFromTimestamp(
wfGetDB( DB_MASTER ),
$this->testPage->getTitle(),
$this->testPage->getRevision()->getTimestamp()
)
);
}
/**
* @covers Revision::getParentLengths
*/
public function testGetParentLengths_noRevIds() {
$this->assertSame(
[],
Revision::getParentLengths(
wfGetDB( DB_MASTER ),
[]
)
);
}
/**
* @covers Revision::getParentLengths
*/
public function testGetParentLengths_oneRevId() {
$text = '831jr091jr0921kr21kr0921kjr0921j09rj1';
$textLength = strlen( $text );
$this->testPage->doEditContent( new WikitextContent( $text ), __METHOD__ );
$rev[1] = $this->testPage->getLatest();
$this->assertSame(
[ $rev[1] => $textLength ],
Revision::getParentLengths(
wfGetDB( DB_MASTER ),
[ $rev[1] ]
)
);
}
/**
* @covers Revision::getParentLengths
*/
public function testGetParentLengths_multipleRevIds() {
$textOne = '831jr091jr0921kr21kr0921kjr0921j09rj1';
$textOneLength = strlen( $textOne );
$textTwo = '831jr091jr092121j09rj1';
$textTwoLength = strlen( $textTwo );
$this->testPage->doEditContent( new WikitextContent( $textOne ), __METHOD__ );
$rev[1] = $this->testPage->getLatest();
$this->testPage->doEditContent( new WikitextContent( $textTwo ), __METHOD__ );
$rev[2] = $this->testPage->getLatest();
$this->assertSame(
[ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
Revision::getParentLengths(
wfGetDB( DB_MASTER ),
[ $rev[1], $rev[2] ]
)
);
}
/**
* @covers Revision::getTitle
*/
public function testGetTitle_fromExistingRevision() {
$this->assertTrue(
$this->testPage->getTitle()->equals(
$this->testPage->getRevision()->getTitle()
)
);
}
/**
* @covers Revision::getTitle
*/
public function testGetTitle_fromRevisionWhichWillLoadTheTitle() {
$rev = new Revision( [ 'id' => $this->testPage->getLatest() ] );
$this->assertTrue(
$this->testPage->getTitle()->equals(
$rev->getTitle()
)
);
}
/**
* @covers Revision::isMinor
*/
public function testIsMinor_true() {
// Use a sysop to ensure we can mark edits as minor
$sysop = $this->getTestSysop()->getUser();
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
__METHOD__,
EDIT_MINOR,
false,
$sysop
);
$rev = $this->testPage->getRevision();
$this->assertSame( true, $rev->isMinor() );
}
/**
* @covers Revision::isMinor
*/
public function testIsMinor_false() {
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
__METHOD__,
0
);
$rev = $this->testPage->getRevision();
$this->assertSame( false, $rev->isMinor() );
}
/**
* @covers Revision::getTimestamp
*/
public function testGetTimestamp() {
$testTimestamp = wfTimestampNow();
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
__METHOD__
);
$rev = $this->testPage->getRevision();
$this->assertInternalType( 'string', $rev->getTimestamp() );
$this->assertTrue( strlen( $rev->getTimestamp() ) == strlen( 'YYYYMMDDHHMMSS' ) );
$this->assertContains( substr( $testTimestamp, 0, 10 ), $rev->getTimestamp() );
}
/**
* @covers Revision::getUser
* @covers Revision::getUserText
*/
public function testGetUserAndText() {
$sysop = $this->getTestSysop()->getUser();
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
__METHOD__,
0,
false,
$sysop
);
$rev = $this->testPage->getRevision();
$this->assertSame( $sysop->getId(), $rev->getUser() );
$this->assertSame( $sysop->getName(), $rev->getUserText() );
}
/**
* @covers Revision::isDeleted
*/
public function testIsDeleted_nothingDeleted() {
$rev = $this->testPage->getRevision();
$this->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
$this->assertSame( false, $rev->isDeleted( Revision::DELETED_COMMENT ) );
$this->assertSame( false, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
$this->assertSame( false, $rev->isDeleted( Revision::DELETED_USER ) );
}
/**
* @covers Revision::getVisibility
*/
public function testGetVisibility_nothingDeleted() {
$rev = $this->testPage->getRevision();
$this->assertSame( 0, $rev->getVisibility() );
}
/**
* @covers Revision::getComment
*/
public function testGetComment_notDeleted() {
$expectedSummary = 'goatlicious summary';
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
$expectedSummary
);
$rev = $this->testPage->getRevision();
$this->assertSame( $expectedSummary, $rev->getComment() );
}
/**
* @covers Revision::isUnpatrolled
*/
public function testIsUnpatrolled_returnsRecentChangesId() {
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$rev = $this->testPage->getRevision();
$this->assertGreaterThan( 0, $rev->isUnpatrolled() );
$this->assertSame( $rev->getRecentChange()->getAttribute( 'rc_id' ), $rev->isUnpatrolled() );
}
/**
* @covers Revision::isUnpatrolled
*/
public function testIsUnpatrolled_returnsZeroIfPatrolled() {
// This assumes that sysops are auto patrolled
$sysop = $this->getTestSysop()->getUser();
$this->testPage->doEditContent(
new WikitextContent( __METHOD__ ),
__METHOD__,
0,
false,
$sysop
);
$rev = $this->testPage->getRevision();
$this->assertSame( 0, $rev->isUnpatrolled() );
}
/**
* This is a simple blanket test for all simple content getters and is methods to provide some
* coverage before the split of Revision into multiple classes for MCR work.
* @covers Revision::getContent
* @covers Revision::getSerializedData
* @covers Revision::getContentModel
* @covers Revision::getContentFormat
* @covers Revision::getContentHandler
*/
public function testSimpleContentGetters() {
$expectedText = 'testSimpleContentGetters in Revision. Goats love MCR...';
$expectedSummary = 'goatlicious testSimpleContentGetters summary';
$this->testPage->doEditContent(
new WikitextContent( $expectedText ),
$expectedSummary
);
$rev = $this->testPage->getRevision();
$this->assertSame( $expectedText, $rev->getContent()->getNativeData() );
$this->assertSame( $expectedText, $rev->getSerializedData() );
$this->assertSame( $this->testPage->getContentModel(), $rev->getContentModel() );
$this->assertSame( $this->testPage->getContent()->getDefaultFormat(), $rev->getContentFormat() );
$this->assertSame( $this->testPage->getContentHandler(), $rev->getContentHandler() );
}
/**
* @covers Revision::newKnownCurrent
*/
public function testNewKnownCurrent() {
// Setup the services
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
$this->setService( 'MainWANObjectCache', $cache );
$db = wfGetDB( DB_MASTER );
// Get a fresh revision to use during testing
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$rev = $this->testPage->getRevision();
// Clear any previous cache for the revision during creation
$key = $cache->makeGlobalKey( 'revision-row-1.29',
$db->getDomainID(),
$rev->getPage(),
$rev->getId()
);
$cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
$this->assertFalse( $cache->get( $key ) );
// Get the new revision and make sure it is in the cache and correct
$newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
$this->assertRevEquals( $rev, $newRev );
$cachedRow = $cache->get( $key );
$this->assertNotFalse( $cachedRow );
$this->assertEquals( $rev->getId(), $cachedRow->rev_id );
}
public function provideUserCanBitfield() {
yield [ 0, 0, [], null, true ];
// Bitfields match, user has no permissions
yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], null, false ];
yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], null, false ];
yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], null, false ];
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], null, false ];
// Bitfields match, user (admin) does have permissions
yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], null, true ];
yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], null, true ];
yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], null, true ];
// Bitfields match, user (admin) does not have permissions
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], null, false ];
// Bitfields match, user (oversight) does have permissions
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], null, true ];
// Check permissions using the title
yield [
Revision::DELETED_TEXT,
Revision::DELETED_TEXT,
[ 'sysop' ],
Title::newFromText( __METHOD__ ),
true,
];
yield [
Revision::DELETED_TEXT,
Revision::DELETED_TEXT,
[],
Title::newFromText( __METHOD__ ),
false,
];
}
/**
* @dataProvider provideUserCanBitfield
* @covers Revision::userCanBitfield
*/
public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
$this->setMwGlobals(
'wgGroupPermissions',
[
'sysop' => [
'deletedtext' => true,
'deletedhistory' => true,
],
'oversight' => [
'viewsuppressed' => true,
'suppressrevision' => true,
],
]
);
$user = $this->getTestUser( $userGroups )->getUser();
$this->assertSame(
$expected,
Revision::userCanBitfield( $bitField, $field, $user, $title )
);
// Fallback to $wgUser
$this->setMwGlobals(
'wgUser',
$user
);
$this->assertSame(
$expected,
Revision::userCanBitfield( $bitField, $field, null, $title )
);
}
public function provideUserCan() {
yield [ 0, 0, [], true ];
// Bitfields match, user has no permissions
yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], false ];
yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], false ];
yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], false ];
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], false ];
// Bitfields match, user (admin) does have permissions
yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], true ];
yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], true ];
yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], true ];
// Bitfields match, user (admin) does not have permissions
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], false ];
// Bitfields match, user (oversight) does have permissions
yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], true ];
}
/**
* @dataProvider provideUserCan
* @covers Revision::userCan
*/
public function testUserCan( $bitField, $field, $userGroups, $expected ) {
$this->setMwGlobals(
'wgGroupPermissions',
[
'sysop' => [
'deletedtext' => true,
'deletedhistory' => true,
],
'oversight' => [
'viewsuppressed' => true,
'suppressrevision' => true,
],
]
);
$user = $this->getTestUser( $userGroups )->getUser();
$revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
$this->assertSame(
$expected,
$revision->userCan( $field, $user )
);
}
}