| Current File : /home/jvzmxxx/wiki/extensions/Wikibase/lib/tests/phpunit/MockRepositoryTest.php |
<?php
namespace Wikibase\Lib\Tests;
use MWException;
use User;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\EntityRedirect;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\Property;
use Wikibase\DataModel\Entity\PropertyId;
use Wikibase\DataModel\Entity\BasicEntityIdParser;
use Wikibase\DataModel\Services\Lookup\EntityRedirectLookupException;
use Wikibase\DataModel\SiteLink;
use Wikibase\EntityRevision;
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
use Wikibase\Lib\Store\StorageException;
/**
* @covers Wikibase\Lib\Tests\MockRepository
*
* @group Wikibase
* @group WikibaseLib
* @group WikibaseEntityLookup
*
* @license GPL-2.0+
* @author Daniel Kinzler
*/
class MockRepositoryTest extends \MediaWikiTestCase {
/**
* @var MockRepository|null
*/
private $repo = null;
protected function setUp() {
parent::setUp();
$this->repo = new MockRepository();
}
public function testHasEntity() {
$q23 = new ItemId( 'q23' );
$q42 = new ItemId( 'q42' );
$p23 = new PropertyId( 'p23' );
$p42 = new PropertyId( 'p42' );
$item = new Item( $q23 );
$this->repo->putEntity( $item );
$prop = Property::newFromType( 'string' );
$prop->setId( $p23 );
$this->repo->putEntity( $prop );
// test item
$this->assertTrue( $this->repo->hasEntity( $q23 ) );
$this->assertFalse( $this->repo->hasEntity( $q42 ) );
// test prop
$this->assertTrue( $this->repo->hasEntity( $p23 ) );
$this->assertFalse( $this->repo->hasEntity( $p42 ) );
}
public function testGetEntity() {
$item = new Item();
$item->setLabel( 'en', 'foo' );
// set up a data Item
$this->repo->putEntity( $item, 23 );
$itemId = $item->getId();
// set up another version of the data Item
$item->setLabel( 'de', 'bar' );
$this->repo->putEntity( $item, 24 );
// set up a property
$prop = Property::newFromType( 'string' );
$prop->setLabel( 'en', 'foo' );
$prop->setId( $itemId->getNumericId() ); // same numeric id, different prefix
$propId = $prop->getId();
$this->repo->putEntity( $prop );
// test latest item
/** @var Item $item */
$item = $this->repo->getEntity( $itemId );
$this->assertNotNull( $item, 'Entity ' . $itemId );
$this->assertInstanceOf( Item::class, $item, 'Entity ' . $itemId );
$this->assertEquals( 'foo', $item->getFingerprint()->getLabel( 'en' )->getText() );
$this->assertEquals( 'bar', $item->getFingerprint()->getLabel( 'de' )->getText() );
// test we can't mess with entities in the repo
$item->setLabel( 'en', 'STRANGE' );
$item = $this->repo->getEntity( $itemId );
$this->assertEquals( 'foo', $item->getFingerprint()->getLabel( 'en' )->getText() );
// test latest prop
$prop = $this->repo->getEntity( $propId );
$this->assertNotNull( $prop, 'Entity ' . $propId );
$this->assertInstanceOf( Property::class, $prop, 'Entity ' . $propId );
}
public function testGetEntityRevision() {
$item = new Item();
$item->setLabel( 'en', 'foo' );
// set up a data Item
$this->repo->putEntity( $item, 23, '20130101000000' );
$itemId = $item->getId();
// set up another version of the data Item
$item->setLabel( 'de', 'bar' );
$this->repo->putEntity( $item, 24 );
// set up a property
$prop = Property::newFromType( 'string' );
$prop->setLabel( 'en', 'foo' );
$prop->setId( $itemId->getNumericId() ); // same numeric id, different prefix
$propId = $prop->getId();
$this->repo->putEntity( $prop );
// test latest item
$itemRev = $this->repo->getEntityRevision( $itemId );
$this->assertNotNull( $item, 'Entity ' . $itemId );
$this->assertInstanceOf( EntityRevision::class, $itemRev, 'Entity ' . $itemId );
$this->assertInstanceOf( Item::class, $itemRev->getEntity(), 'Entity ' . $itemId );
$this->assertEquals( 24, $itemRev->getRevisionId() );
// test item by rev id
$itemRev = $this->repo->getEntityRevision( $itemId, 23 );
$this->assertNotNull( $item, 'Entity ' . $itemId . '@23' );
$this->assertInstanceOf( EntityRevision::class, $itemRev, 'Entity ' . $itemId );
$this->assertInstanceOf( Item::class, $itemRev->getEntity(), 'Entity ' . $itemId );
$this->assertEquals( 23, $itemRev->getRevisionId() );
$this->assertEquals( '20130101000000', $itemRev->getTimestamp() );
// test latest prop
$propRev = $this->repo->getEntityRevision( $propId );
$this->assertNotNull( $propRev, 'Entity ' . $propId );
$this->assertInstanceOf( EntityRevision::class, $propRev, 'Entity ' . $propId );
$this->assertInstanceOf( Property::class, $propRev->getEntity(), 'Entity ' . $propId );
}
public function testGetItemIdForLink() {
$item = new Item();
$item->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Foo' );
// test item lookup
$this->repo->putEntity( $item );
$itemId = $item->getId();
$this->assertEquals( $itemId, $this->repo->getItemIdForLink( 'enwiki', 'Foo' ) );
$this->assertNull( $this->repo->getItemIdForLink( 'xywiki', 'Foo' ) );
// test lookup after item modification
$item->getSiteLinkList()->setNewSiteLink( 'enwiki', 'Bar' );
$this->repo->putEntity( $item );
$this->assertNull( $this->repo->getItemIdForLink( 'enwiki', 'Foo' ) );
$this->assertEquals( $itemId, $this->repo->getItemIdForLink( 'enwiki', 'Bar' ) );
// test lookup after item deletion
$this->repo->removeEntity( $itemId );
$this->assertNull( $this->repo->getItemIdForLink( 'enwiki', 'Foo' ) );
$this->assertNull( $this->repo->getItemIdForLink( 'enwiki', 'Bar' ) );
}
public function provideGetLinks() {
$cases = array();
$a = new Item( new ItemId( 'Q1' ) );
$a->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Foo' );
$a->getSiteLinkList()->addNewSiteLink( 'dewiki', 'Bar' );
$b = new Item( new ItemId( 'Q2' ) );
$b->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Bar' );
$b->getSiteLinkList()->addNewSiteLink( 'dewiki', 'Xoo' );
$items = array( $a, $b );
// #0: all ---------
$cases[] = array( $items,
array(), // items
array(), // sites
array(), // pages
array( // expected
array( 'enwiki', 'Foo', 1 ),
array( 'dewiki', 'Bar', 1 ),
array( 'enwiki', 'Bar', 2 ),
array( 'dewiki', 'Xoo', 2 ),
)
);
// #1: mismatch ---------
$cases[] = array( $items,
array(), // items
array( 'enwiki' ), // sites
array( 'Xoo' ), // pages
array() // expected
);
// #2: by item ---------
$cases[] = array( $items,
array( 1 ), // items
array(), // sites
array(), // pages
array( // expected
array( 'enwiki', 'Foo', 1 ),
array( 'dewiki', 'Bar', 1 ),
)
);
// #3: by site ---------
$cases[] = array( $items,
array(), // items
array( 'enwiki' ), // sites
array(), // pages
array( // expected
array( 'enwiki', 'Foo', 1 ),
array( 'enwiki', 'Bar', 2 ),
)
);
// #4: by page ---------
$cases[] = array( $items,
array(), // items
array(), // sites
array( 'Bar' ), // pages
array( // expected
array( 'dewiki', 'Bar', 1 ),
array( 'enwiki', 'Bar', 2 ),
)
);
// #5: by site and page ---------
$cases[] = array( $items,
array(), // items
array( 'dewiki' ), // sites
array( 'Bar' ), // pages
array( // expected
array( 'dewiki', 'Bar', 1 ),
)
);
return $cases;
}
/**
* @dataProvider provideGetLinks
*/
public function testGetLinks( array $items, array $itemIds, array $sites, array $pages, array $expectedLinks ) {
foreach ( $items as $item ) {
$this->repo->putEntity( $item );
}
$links = $this->repo->getLinks( $itemIds, $sites, $pages );
$this->assertArrayEquals( $expectedLinks, $links );
}
public function provideGetEntities() {
return array(
array( // #0: empty
array(), // ids
array(), // expected
),
array( // #1: some entities
array( // ids
'Q1',
'Q2',
),
array( // expected
'Q1' => array(
'de' => 'eins',
'en' => 'one',
),
'Q2' => array(
'en' => 'two',
),
),
),
array( // #2: bad ID
array( 'Q1', 'Q22' ), // ids
array( // expected
'Q1' => array(
'en' => 'one',
'de' => 'eins',
),
'Q22' => null,
),
)
);
}
protected function setupGetEntities() {
$one = new Item( new ItemId( 'Q1' ) );
$one->setLabel( 'en', 'one' );
$two = new Item( new ItemId( 'Q2' ) );
$two->setLabel( 'en', 'two' );
$three = new Item( new ItemId( 'Q3' ) );
$three->setLabel( 'en', 'three' );
$three->setLabel( 'de', 'drei' );
$three->setDescription( 'en', 'the third' );
$prop = Property::newFromType( 'string' );
$prop->setId( 4 );
$prop->setLabel( 'en', 'property!' );
$this->repo->putEntity( $one, 1001 );
$this->repo->putEntity( $two, 1002 );
$this->repo->putEntity( $three, 1003 );
$this->repo->putEntity( $prop, 1101 );
$one->setLabel( 'de', 'eins' );
$this->repo->putEntity( $one, 1011 );
}
/**
* @dataProvider provideGetEntities
*/
public function testGetEntities( $ids, $expected, $expectedError = false ) {
$this->setupGetEntities();
$idParser = new BasicEntityIdParser();
// convert string IDs to EntityId objects
foreach ( $ids as $i => $id ) {
if ( is_string( $id ) ) {
$ids[ $i ] = $idParser->parse( $id );
}
}
$entities = false;
// do it!
try {
$entities = $this->repo->getEntities( $ids );
if ( $expectedError !== false ) {
$this->fail( 'expected error: ' . $expectedError );
}
} catch ( MWException $ex ) {
if ( $expectedError !== false ) {
$this->assertInstanceOf( $expectedError, $ex );
} else {
$this->fail( 'error: ' . $ex->getMessage() );
}
}
if ( !is_array( $expected ) ) {
// expected some kind of special return value, e.g. false.
$this->assertEquals( $expected, $entities, 'return value' );
return;
} else {
$this->assertType( 'array', $entities, 'return value' );
}
// extract map of entity IDs to label arrays.
/* @var EntityDocument $e */
$actual = array();
foreach ( $entities as $key => $e ) {
if ( is_object( $e ) ) {
$actual[ $e->getId()->getSerialization() ] = $e->getFingerprint()->getLabels()->toTextArray();
} else {
$actual[ $key ] = $e;
}
}
// check that we found the right number of entities
$this->assertEquals( count( $expected ), count( $actual ), 'number of entities found' );
foreach ( $expected as $id => $labels ) {
// check that thew correct entity was found
$this->assertArrayHasKey( $id, $actual );
if ( is_array( $labels ) ) {
// check that the entity contains the expected labels
$this->assertArrayEquals( $labels, $actual[$id] );
} else {
// typically, $labels would be null here.
// check that the entity/revision wasn't found
$this->assertEquals( $labels, $actual[$id] );
}
}
}
public function testGetSiteLinksForItem() {
$one = new Item( new ItemId( 'Q1' ) );
$one->getSiteLinkList()->addNewSiteLink( 'dewiki', 'Xoo' );
$one->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Foo' );
$this->repo->putEntity( $one );
// check link retrieval
$this->assertEquals(
array(
new SiteLink( 'dewiki', 'Xoo' ),
new SiteLink( 'enwiki', 'Foo' ),
),
$this->repo->getSiteLinksForItem( $one->getId() )
);
// check links of unknown id
$this->assertEmpty( $this->repo->getSiteLinksForItem( new ItemId( 'q123' ) ) );
}
public function provideSaveEntity() {
$item = new Item();
$item->setLabel( 'en', 'one' );
$secondItem = new Item( new ItemId( 'Q1' ) );
$secondItem->setLabel( 'en', 'one' );
$secondItem->setLabel( 'it', 'uno' );
$thirdItem = new Item( new ItemId( 'Q1' ) );
$thirdItem->setLabel( 'en', 'one' );
$fourthItem = new Item( new ItemId( 'Q123' ) );
$fourthItem->setLabel( 'en', 'one two three' );
$fourthItem->setLabel( 'de', 'eins zwei drei' );
$fifthItem = new Item( new ItemId( 'Q1' ) );
$fifthItem->setLabel( 'en', 'one' );
$fifthItem->setLabel( 'de', 'eins' );
return array(
'fresh' => array(
'entity' => $item,
'flags' => EDIT_NEW,
'baseRevid' => false,
),
'update' => array(
'entity' => $secondItem,
'flags' => EDIT_UPDATE,
'baseRevid' => 1011,
),
'not fresh' => array(
'entity' => $thirdItem,
'flags' => EDIT_NEW,
'baseRevid' => false,
'error' => StorageException::class
),
'not exists' => array(
'entity' => $fourthItem,
'flags' => EDIT_UPDATE,
'baseRevid' => false,
'error' => StorageException::class
),
'bad base' => array(
'entity' => $fifthItem,
'flags' => EDIT_UPDATE,
'baseRevid' => 1234,
'error' => StorageException::class
),
);
}
/**
* @dataProvider provideSaveEntity
*/
public function testSaveEntity( EntityDocument $entity, $flags, $baseRevId, $error = null ) {
$this->setupGetEntities();
if ( $error !== null ) {
$this->setExpectedException( $error );
}
$rev = $this->repo->saveEntity( $entity, 'f00', $GLOBALS['wgUser'], $flags, $baseRevId );
$logEntry = $this->repo->getLogEntry( $rev->getRevisionId() );
$this->assertNotNull( $logEntry );
$this->assertEquals( $rev->getRevisionId(), $logEntry['revision'] );
$this->assertEquals( $entity->getId()->getSerialization(), $logEntry['entity'] );
$this->assertEquals( 'f00', $logEntry['summary'] );
$savedEntity = $this->repo->getEntity( $entity->getId() );
$this->assertTrue( $entity->getFingerprint()->equals( $rev->getEntity()->getFingerprint() ) );
$this->assertTrue( $entity->getFingerprint()->equals( $savedEntity->getFingerprint() ) );
// test we can't mess with entities in the repo
$entity->getFingerprint()->setLabel( 'en', 'STRANGE' );
$entity = $this->repo->getEntity( $entity->getId() );
$this->assertNotNull( $entity );
$this->assertNotEquals( 'STRANGE', $entity->getFingerprint()->getLabel( 'en' )->getText() );
}
public function testSaveRedirect() {
$this->setupGetEntities();
$q10 = new ItemId( 'Q10' );
$q1 = new ItemId( 'Q1' );
$redirect = new EntityRedirect( $q10, $q1 );
$revId = $this->repo->saveRedirect( $redirect, 'redirected Q10 to Q1', $GLOBALS['wgUser'] );
$this->assertGreaterThan( 0, $revId );
$logEntry = $this->repo->getLogEntry( $revId );
$this->assertNotNull( $logEntry );
$this->assertEquals( $revId, $logEntry['revision'] );
$this->assertEquals( $redirect->getEntityId()->getSerialization(), $logEntry['entity'] );
$this->assertEquals( 'redirected Q10 to Q1', $logEntry['summary'] );
$this->setExpectedException( RevisionedUnresolvedRedirectException::class );
$this->repo->getEntity( $q10 );
}
public function testGetLogEntry() {
$this->setupGetEntities();
$q10 = new ItemId( 'Q10' );
$q11 = new ItemId( 'Q11' );
$redirect = new EntityRedirect( $q10, $q11 );
$revId = $this->repo->saveRedirect( $redirect, 'foo', $GLOBALS['wgUser'], EDIT_NEW );
$logEntry = $this->repo->getLogEntry( $revId );
$this->assertNotNull( $logEntry );
$this->assertEquals( $revId, $logEntry['revision'] );
$this->assertEquals( 'Q10', $logEntry['entity'] );
$this->assertEquals( 'foo', $logEntry['summary'] );
}
public function testGetLatestLogEntryFor() {
$this->setupGetEntities();
$q10 = new ItemId( 'Q10' );
$q11 = new ItemId( 'Q11' );
$q12 = new ItemId( 'Q12' );
// first entry
$redirect = new EntityRedirect( $q10, $q11 );
$revId = $this->repo->saveRedirect( $redirect, 'foo', $GLOBALS['wgUser'], EDIT_NEW );
$logEntry = $this->repo->getLatestLogEntryFor( $q10 );
$this->assertNotNull( $logEntry );
$this->assertEquals( $revId, $logEntry['revision'] );
$this->assertEquals( 'Q10', $logEntry['entity'] );
$this->assertEquals( 'foo', $logEntry['summary'] );
// second entry
$redirect = new EntityRedirect( $q10, $q12 );
$revId = $this->repo->saveRedirect( $redirect, 'bar', $GLOBALS['wgUser'], EDIT_NEW );
$logEntry = $this->repo->getLatestLogEntryFor( $q10 );
$this->assertNotNull( $logEntry );
$this->assertEquals( $revId, $logEntry['revision'] );
$this->assertEquals( 'Q10', $logEntry['entity'] );
$this->assertEquals( 'bar', $logEntry['summary'] );
}
public function testDeleteEntity() {
$item = new Item( new ItemId( 'Q23' ) );
$this->repo->putEntity( $item );
$this->repo->deleteEntity( $item->getId(), 'testing', $GLOBALS['wgUser'] );
$this->assertFalse( $this->repo->hasEntity( $item->getId() ) );
}
public function testPutRedirect() {
$redirect = new EntityRedirect( new ItemId( 'Q11' ), new ItemId( 'Q1' ) );
$this->repo->putRedirect( $redirect );
try {
$this->repo->getEntityRevision( new ItemId( 'Q11' ) );
$this->fail( 'getEntityRevision() should fail for redirects' );
} catch ( RevisionedUnresolvedRedirectException $ex ) {
$this->assertEquals( 'Q1', $ex->getRedirectTargetId()->getSerialization() );
$this->assertGreaterThan( 0, $ex->getRevisionId() );
$this->assertNotEmpty( $ex->getRevisionTimestamp() );
}
$this->repo->putRedirect( $redirect, 117, '20150505000000' );
try {
$this->repo->getEntityRevision( new ItemId( 'Q11' ) );
$this->fail( 'getEntityRevision() should fail for redirects' );
} catch ( RevisionedUnresolvedRedirectException $ex ) {
$this->assertEquals( 'Q1', $ex->getRedirectTargetId()->getSerialization() );
$this->assertEquals( 117, $ex->getRevisionId() );
$this->assertEquals( '20150505000000', $ex->getRevisionTimestamp() );
}
}
public function testDeleteRedirect() {
$redirect = new EntityRedirect( new ItemId( 'Q11' ), new ItemId( 'Q1' ) );
$this->repo->putRedirect( $redirect );
$this->setExpectedException( RevisionedUnresolvedRedirectException::class );
$this->repo->deleteEntity( $redirect->getEntityId(), 'testing', $GLOBALS['wgUser'] );
}
public function testUpdateWatchlist() {
$user = User::newFromName( 'WikiPageEntityStoreTestUser2' );
$item = new Item();
$this->repo->saveEntity( $item, 'testing', $user, EDIT_NEW );
$itemId = $item->getId();
$this->repo->updateWatchlist( $user, $itemId, true );
$this->assertTrue( $this->repo->isWatching( $user, $itemId ) );
$this->repo->updateWatchlist( $user, $itemId, false );
$this->assertFalse( $this->repo->isWatching( $user, $itemId ) );
}
public function testUserWasLastToEdit() {
$user1 = User::newFromName( 'WikiPageEntityStoreTestUserWasLastToEdit1' );
$user2 = User::newFromName( 'WikiPageEntityStoreTestUserWasLastToEdit2' );
// initial revision
$item = new Item( new ItemId( 'Q42' ) );
$item->setLabel( 'en', 'one' );
$rev1 = $this->repo->saveEntity( $item, 'testing 1', $user1, EDIT_NEW );
$itemId = $item->getId();
$this->assertTrue(
$this->repo->userWasLastToEdit( $user1, $itemId, $rev1->getRevisionId() ),
'user was first and last to edit'
);
$this->assertFalse(
$this->repo->userWasLastToEdit( $user2, $itemId, $rev1->getRevisionId() ),
'user has not edited yet'
);
// second edit by another user
$item = new Item( new ItemId( 'Q42' ) );
$item->setLabel( 'en', 'two' );
$rev2 = $this->repo->saveEntity( $item, 'testing 2', $user2, EDIT_UPDATE );
$this->assertFalse(
$this->repo->userWasLastToEdit( $user1, $itemId, $rev1->getRevisionId() ),
'original user was no longer last to edit'
);
$this->assertTrue(
$this->repo->userWasLastToEdit( $user2, $itemId, $rev2->getRevisionId() ),
'second user has just edited'
);
// subsequent edit by the original user
$item = new Item( new ItemId( 'Q42' ) );
$item->setLabel( 'en', 'three' );
$rev3 = $this->repo->saveEntity( $item, 'testing 3', $user1, EDIT_UPDATE );
$this->assertFalse(
$this->repo->userWasLastToEdit( $user1, $itemId, $rev1->getRevisionId() ),
'another user had edited at some point'
);
$this->assertTrue(
$this->repo->userWasLastToEdit( $user1, $itemId, $rev3->getRevisionId() ),
'original user was last to edit'
);
$this->assertFalse(
$this->repo->userWasLastToEdit( $user2, $itemId, $rev2->getRevisionId() ),
'other user was no longer last to edit'
);
}
public function testGetRedirectIds() {
$mock = new MockRepository();
$q5 = new ItemId( 'Q5' );
$q55 = new ItemId( 'Q55' );
$q555 = new ItemId( 'Q555' );
$mock->putRedirect( new EntityRedirect( $q55, $q5 ) );
$mock->putRedirect( new EntityRedirect( $q555, $q5 ) );
$this->assertEmpty( $mock->getRedirectIds( $q55 ), 'no redirects to redirect' );
$this->assertEquals( array( $q55, $q555 ), $mock->getRedirectIds( $q5 ), 'two redirects' );
}
public function testGetRedirectForEntityId() {
$mock = new MockRepository();
$q5 = new ItemId( 'Q5' );
$q55 = new ItemId( 'Q55' );
$q77 = new ItemId( 'Q77' );
$mock->putEntity( new Item( $q5 ) );
$mock->putRedirect( new EntityRedirect( $q55, $q5 ) );
$this->assertNull( $mock->getRedirectForEntityId( $q5 ), 'not a redirect' );
$this->assertEquals( $q5, $mock->getRedirectForEntityId( $q55 ) );
$this->setExpectedException( EntityRedirectLookupException::class );
$mock->getRedirectForEntityId( $q77 );
}
}