| Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/repo/tests/phpunit/includes/Api/MergeItemsTest.php |
<?php
namespace Wikibase\Test\Repo\Api;
use HashSiteStore;
use Language;
use RequestContext;
use Status;
use TestSites;
use Title;
use UsageException;
use User;
use Wikibase\ChangeOp\ChangeOpFactoryProvider;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityRedirect;
use Wikibase\DataModel\Entity\BasicEntityIdParser;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Services\Statement\GuidGenerator;
use Wikibase\LabelDescriptionDuplicateDetector;
use Wikibase\Lib\Store\EntityTitleLookup;
use Wikibase\Repo\Api\ApiErrorReporter;
use Wikibase\Repo\Api\MergeItems;
use Wikibase\Repo\Interactors\ItemMergeInteractor;
use Wikibase\Repo\Interactors\RedirectCreationInteractor;
use Wikibase\Repo\Store\EntityPermissionChecker;
use Wikibase\Repo\Validators\EntityConstraintProvider;
use Wikibase\Repo\Validators\SnakValidator;
use Wikibase\Repo\Validators\TermValidatorFactory;
use Wikibase\Repo\WikibaseRepo;
use Wikibase\Test\EntityModificationTestHelper;
use Wikibase\Lib\Tests\MockRepository;
/**
* @covers Wikibase\Repo\Api\MergeItems
*
* @group API
* @group Wikibase
* @group WikibaseAPI
* @group WikibaseRepo
* @group MergeItemsTest
* @group Database
*
* @license GPL-2.0+
* @author Addshore
* @author Lucie-Aimée Kaffee
*/
class MergeItemsTest extends \MediaWikiTestCase {
/**
* @var MockRepository|null
*/
private $mockRepository = null;
/**
* @var EntityModificationTestHelper|null
*/
private $entityModificationTestHelper = null;
/**
* @var ApiModuleTestHelper|null
*/
private $apiModuleTestHelper = null;
protected function setUp() {
parent::setUp();
$this->entityModificationTestHelper = new EntityModificationTestHelper();
$this->apiModuleTestHelper = new ApiModuleTestHelper();
$this->mockRepository = $this->entityModificationTestHelper->getMockRepository();
$this->entityModificationTestHelper->putEntities( array(
'Q1' => array(),
'Q2' => array(),
'P1' => array( 'datatype' => 'string' ),
'P2' => array( 'datatype' => 'string' ),
) );
$this->entityModificationTestHelper->putRedirects( array(
'Q11' => 'Q1',
'Q12' => 'Q2',
) );
}
/**
* @return EntityPermissionChecker
*/
private function getPermissionCheckers() {
$permissionChecker = $this->getMock( EntityPermissionChecker::class );
$permissionChecker->expects( $this->any() )
->method( 'getPermissionStatusForEntityId' )
->will( $this->returnCallback( function( User $user, $permission ) {
if ( $user->getName() === 'UserWithoutPermission' && $permission === 'edit' ) {
return Status::newFatal( 'permissiondenied' );
} else {
return Status::newGood();
}
} ) );
return $permissionChecker;
}
/**
* @param EntityRedirect|null $redirect
*
* @return RedirectCreationInteractor
*/
public function getMockRedirectCreationInteractor( EntityRedirect $redirect = null ) {
$mock = $this->getMockBuilder( RedirectCreationInteractor::class )
->disableOriginalConstructor()
->getMock();
if ( $redirect ) {
$mock->expects( $this->once() )
->method( 'createRedirect' )
->with( $redirect->getEntityId(), $redirect->getTargetId() )
->will( $this->returnCallback( function() use ( $redirect ) {
return $redirect;
} ) );
} else {
$mock->expects( $this->never() )
->method( 'createRedirect' );
}
return $mock;
}
/**
* @return EntityTitleLookup
*/
private function getEntityTitleLookup() {
$entityTitleLookup = $this->getMock( EntityTitleLookup::class );
$entityTitleLookup->expects( $this->any() )
->method( 'getTitleForId' )
->will( $this->returnCallback( function( EntityId $entityId ) {
return Title::newFromText( $entityId->getSerialization() );
} ) );
return $entityTitleLookup;
}
/**
* @param MergeItems $module
* @param EntityRedirect|null $expectedRedirect
*/
private function overrideServices( MergeItems $module, EntityRedirect $expectedRedirect = null ) {
$idParser = new BasicEntityIdParser();
$wikibaseRepo = WikibaseRepo::getDefaultInstance();
$errorReporter = new ApiErrorReporter(
$module,
$wikibaseRepo->getExceptionLocalizer(),
Language::factory( 'en' )
);
$apiHelperFactory = $wikibaseRepo->getApiHelperFactory( new RequestContext() );
$resultBuilder = $apiHelperFactory->getResultBuilder( $module );
$changeOpsFactoryProvider = new ChangeOpFactoryProvider(
$this->getConstraintProvider(),
new GuidGenerator(),
$wikibaseRepo->getStatementGuidValidator(),
$wikibaseRepo->getStatementGuidParser(),
$this->getSnakValidator(),
$this->getTermValidatorFactory(),
new HashSiteStore( TestSites::getSites() )
);
$module->setServices(
$idParser,
$errorReporter,
$resultBuilder,
new ItemMergeInteractor(
$changeOpsFactoryProvider->getMergeChangeOpFactory(),
$this->mockRepository,
$this->mockRepository,
$this->getPermissionCheckers(),
$wikibaseRepo->getSummaryFormatter(),
$module->getUser(),
$this->getMockRedirectCreationInteractor( $expectedRedirect ),
$this->getEntityTitleLookup()
)
);
}
/**
* @return EntityConstraintProvider
*/
private function getConstraintProvider() {
$constraintProvider = $this->getMockBuilder( EntityConstraintProvider::class )
->disableOriginalConstructor()
->getMock();
$constraintProvider->expects( $this->any() )
->method( 'getUpdateValidators' )
->will( $this->returnValue( array() ) );
return $constraintProvider;
}
/**
* @return SnakValidator
*/
private function getSnakValidator() {
$snakValidator = $this->getMockBuilder( SnakValidator::class )
->disableOriginalConstructor()
->getMock();
$snakValidator->expects( $this->any() )
->method( 'validate' )
->will( $this->returnValue( Status::newGood() ) );
return $snakValidator;
}
/**
* @return TermValidatorFactory
*/
private function getTermValidatorFactory() {
$dupeDetector = $this->getMockBuilder( LabelDescriptionDuplicateDetector::class )
->disableOriginalConstructor()
->getMock();
$dupeDetector->expects( $this->any() )
->method( 'detectTermConflicts' )
->will( $this->returnValue( Status::newGood() ) );
return new TermValidatorFactory(
100,
array( 'en', 'de', 'fr' ),
new BasicEntityIdParser(),
$dupeDetector
);
}
private function callApiModule( $params, EntityRedirect $expectedRedirect = null ) {
$module = $this->apiModuleTestHelper->newApiModule( MergeItems::class, 'wbmergeitems', $params );
$this->overrideServices( $module, $expectedRedirect );
$module->execute();
$data = $module->getResult()->getResultData( null, array(
'BC' => array(),
'Types' => array(),
'Strip' => 'all',
) );
return $data;
}
public function provideData() {
$testCases = array();
$testCases['labelMerge'] = array(
array( 'labels' => array( 'en' => array( 'language' => 'en', 'value' => 'foo' ) ) ),
array(),
array(),
array( 'labels' => array( 'en' => array( 'language' => 'en', 'value' => 'foo' ) ) ),
true,
);
$testCases['ignoreConflictSitelinksMerge'] = array(
array( 'sitelinks' => array(
'dewiki' => array( 'site' => 'dewiki', 'title' => 'RemainFrom' ),
'enwiki' => array( 'site' => 'enwiki', 'title' => 'PlFrom' ),
) ),
array( 'sitelinks' => array( 'dewiki' => array( 'site' => 'dewiki', 'title' => 'RemainTo' ) ) ),
array( 'sitelinks' => array( 'dewiki' => array( 'site' => 'dewiki', 'title' => 'RemainFrom' ) ) ),
array( 'sitelinks' => array(
'dewiki' => array( 'site' => 'dewiki', 'title' => 'RemainTo' ),
'enwiki' => array( 'site' => 'enwiki', 'title' => 'PlFrom' ),
) ),
false,
'sitelink',
);
$testCases['statementMerge'] = array(
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array( 'value' => 'imastring', 'type' => 'string' ) ),
'type' => 'statement', 'rank' => 'normal', 'id' => 'deadbeefdeadbeefdeadbeefdeadbeef' ) ) ) ),
array(),
array(),
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array( 'value' => 'imastring', 'type' => 'string' ) ),
'type' => 'statement', 'rank' => 'normal' ) ) ) ),
true,
);
$testCases['ignoreConflictStatementMerge'] = array(
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array(
'value' => array( 'entity-type' => 'item', 'numeric-id' => 2 ), 'type' => 'wikibase-entityid' )
),
'type' => 'statement', 'rank' => 'normal', 'id' => 'deadbeefdeadbeefdeadbeefdeadbeef' ) ) ) ),
array(),
array(),
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array(
'value' => array( 'entity-type' => 'item', 'numeric-id' => 2 ), 'type' => 'wikibase-entityid' )
),
'type' => 'statement', 'rank' => 'normal' ) ) )
),
true,
'statement',
);
return $testCases;
}
/**
* @dataProvider provideData
*/
public function testMergeRequest( $pre1, $pre2, $expectedFrom, $expectedTo, $expectRedirect, $ignoreConflicts = null ) {
// -- set up params ---------------------------------
$params = array(
'action' => 'wbmergeitems',
'fromid' => 'Q1',
'toid' => 'Q2',
'summary' => 'CustomSummary!',
);
if ( $ignoreConflicts !== null ) {
$params['ignoreconflicts'] = $ignoreConflicts;
}
// -- prefill the entities --------------------------------------------
$this->entityModificationTestHelper->putEntity( $pre1, 'Q1' );
$this->entityModificationTestHelper->putEntity( $pre2, 'Q2' );
// -- do the request --------------------------------------------
$redirect = $expectRedirect
? new EntityRedirect( new ItemId( 'Q1' ), new ItemId( 'Q2' ) )
: null;
$result = $this->callApiModule( $params, $redirect );
// -- check the result --------------------------------------------
$this->assertResultCorrect( $result );
// -- check the items --------------------------------------------
$this->assertItemsCorrect( $result, $expectedFrom, $expectedTo );
// -- check redirect --------------------------------------------
$this->assertRedirectCorrect( $result, $redirect );
// -- check the edit summaries --------------------------------------------
$this->assertEditSummariesCorrect( $result );
}
private function assertResultCorrect( array $result ) {
$this->apiModuleTestHelper->assertResultSuccess( $result );
$this->apiModuleTestHelper->assertResultHasKeyInPath( array( 'from', 'id' ), $result );
$this->apiModuleTestHelper->assertResultHasKeyInPath( array( 'to', 'id' ), $result );
$this->assertEquals( 'Q1', $result['from']['id'] );
$this->assertEquals( 'Q2', $result['to']['id'] );
$this->apiModuleTestHelper->assertResultHasKeyInPath( array( 'from', 'lastrevid' ), $result );
$this->apiModuleTestHelper->assertResultHasKeyInPath( array( 'to', 'lastrevid' ), $result );
$this->assertGreaterThan( 0, $result['from']['lastrevid'] );
$this->assertGreaterThan( 0, $result['to']['lastrevid'] );
}
private function assertItemsCorrect( array $result, array $expectedFrom, array $expectedTo ) {
$actualFrom = $this->entityModificationTestHelper->getEntity( $result['from']['id'], true ); //resolve redirects
$this->entityModificationTestHelper->assertEntityEquals( $expectedFrom, $actualFrom );
$actualTo = $this->entityModificationTestHelper->getEntity( $result['to']['id'], true );
$this->entityModificationTestHelper->assertEntityEquals( $expectedTo, $actualTo );
}
private function assertRedirectCorrect( array $result, EntityRedirect $redirect = null ) {
$this->assertArrayHasKey( 'redirected', $result );
if ( $redirect ) {
$this->assertEquals( 1, $result['redirected'] );
} else {
$this->assertEquals( 0, $result['redirected'] );
}
}
private function assertEditSummariesCorrect( array $result ) {
$this->entityModificationTestHelper->assertRevisionSummary( array( 'wbmergeitems' ), $result['from']['lastrevid'] );
$this->entityModificationTestHelper->assertRevisionSummary( '/CustomSummary/', $result['from']['lastrevid'] );
$this->entityModificationTestHelper->assertRevisionSummary( array( 'wbmergeitems' ), $result['to']['lastrevid'] );
$this->entityModificationTestHelper->assertRevisionSummary( '/CustomSummary/', $result['to']['lastrevid'] );
}
public function provideExceptionParamsData() {
return array(
array( //0 no ids given
'p' => array(),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'param-missing'
) )
),
array( //1 only from id
'p' => array( 'fromid' => 'Q1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'param-missing'
) )
),
array( //2 only to id
'p' => array( 'toid' => 'Q1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'param-missing'
) )
),
array( //3 toid bad
'p' => array( 'fromid' => 'Q1', 'toid' => 'ABCDE' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'invalid-entity-id'
) )
),
array( //4 fromid bad
'p' => array( 'fromid' => 'ABCDE', 'toid' => 'Q1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'invalid-entity-id'
) )
),
array( //5 both same id
'p' => array( 'fromid' => 'Q1', 'toid' => 'Q1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'invalid-entity-id',
'message' => 'You must provide unique ids'
) )
),
array( //6 from id is property
'p' => array( 'fromid' => 'P1', 'toid' => 'Q1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'not-item'
) )
),
array( //7 to id is property
'p' => array( 'fromid' => 'Q1', 'toid' => 'P1' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'not-item'
) )
),
array( //8 bad ignoreconficts
'p' => array( 'fromid' => 'Q2', 'toid' => 'Q2', 'ignoreconflicts' => 'foo' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'invalid-entity-id'
) )
),
array( //9 bad ignoreconficts
'p' => array( 'fromid' => 'Q2', 'toid' => 'Q2', 'ignoreconflicts' => 'label|foo' ),
'e' => array( 'exception' => array(
'type' => UsageException::class,
'code' => 'invalid-entity-id'
) )
),
);
}
/**
* @dataProvider provideExceptionParamsData
*/
public function testMergeItemsParamsExceptions( $params, $expected ) {
// -- set any defaults ------------------------------------
$params['action'] = 'wbmergeitems';
try {
$this->callApiModule( $params );
$this->fail( 'Expected UsageException!' );
} catch ( UsageException $ex ) {
$this->apiModuleTestHelper->assertUsageException( $expected, $ex );
}
}
public function provideExceptionConflictsData() {
return array(
array(
array( 'descriptions' => array( 'en' => array( 'language' => 'en', 'value' => 'foo' ) ) ),
array( 'descriptions' => array( 'en' => array( 'language' => 'en', 'value' => 'foo2' ) ) ),
),
array(
array( 'sitelinks' => array( 'dewiki' => array( 'site' => 'dewiki', 'title' => 'Foo' ) ) ),
array( 'sitelinks' => array( 'dewiki' => array( 'site' => 'dewiki', 'title' => 'Foo2' ) ) ),
),
array(
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array(
'value' => array( 'entity-type' => 'item', 'numeric-id' => 2 ), 'type' => 'wikibase-entityid' )
),
'type' => 'statement', 'rank' => 'normal' ) ) )
),
array(),
),
array(
array(),
array( 'claims' => array( 'P1' => array( array( 'mainsnak' => array(
'snaktype' => 'value', 'property' => 'P1', 'datavalue' => array(
'value' => array( 'entity-type' => 'item', 'numeric-id' => 1 ), 'type' => 'wikibase-entityid' )
),
'type' => 'statement', 'rank' => 'normal' ) ) )
),
)
);
}
/**
* @dataProvider provideExceptionConflictsData
*/
public function testMergeItemsConflictsExceptions( $pre1, $pre2 ) {
$expected = array( 'exception' => array( 'type' => UsageException::class, 'code' => 'failed-save' ) );
// -- prefill the entities --------------------------------------------
$this->entityModificationTestHelper->putEntity( $pre1, 'Q1' );
$this->entityModificationTestHelper->putEntity( $pre2, 'Q2' );
$params = array(
'action' => 'wbmergeitems',
'fromid' => 'Q1',
'toid' => 'Q2',
);
// -- do the request --------------------------------------------
try {
$this->callApiModule( $params );
$this->fail( 'Expected UsageException!' );
} catch ( UsageException $ex ) {
$this->apiModuleTestHelper->assertUsageException( $expected, $ex );
}
}
public function testMergeNonExistingItem() {
$params = array(
'action' => 'wbmergeitems',
'fromid' => 'Q60457977',
'toid' => 'Q60457978'
);
try {
$this->callApiModule( $params );
$this->fail( 'Expected UsageException!' );
} catch ( UsageException $ex ) {
$this->apiModuleTestHelper->assertUsageException( 'no-such-entity', $ex );
}
}
}