Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/repo/tests/phpunit/includes/Api/SetClaimTest.php
<?php

namespace Wikibase\Test\Repo\Api;

use DataValues\NumberValue;
use DataValues\Serializers\DataValueSerializer;
use DataValues\StringValue;
use FormatJson;
use UsageException;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\Property;
use Wikibase\DataModel\Reference;
use Wikibase\DataModel\ReferenceList;
use Wikibase\DataModel\SerializerFactory;
use Wikibase\DataModel\Services\Statement\GuidGenerator;
use Wikibase\DataModel\Snak\PropertyNoValueSnak;
use Wikibase\DataModel\Snak\PropertySomeValueSnak;
use Wikibase\DataModel\Snak\PropertyValueSnak;
use Wikibase\DataModel\Snak\Snak;
use Wikibase\DataModel\Snak\SnakList;
use Wikibase\DataModel\Statement\Statement;
use Wikibase\Repo\WikibaseRepo;

/**
 * @covers Wikibase\Repo\Api\SetClaim
 *
 * @group API
 * @group Database
 * @group Wikibase
 * @group WikibaseAPI
 * @group WikibaseRepo
 * @group SetClaimTest
 *
 * @group medium
 *
 * @license GPL-2.0+
 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 * @author Daniel Kinzler
 * @author Addshore
 */
class SetClaimTest extends WikibaseApiTestCase {

	private static $propertyIds;

	protected function setUp() {
		parent::setUp();

		if ( !self::$propertyIds ) {
			self::$propertyIds = $this->getPropertyIds();
		}
	}

	private function getPropertyIds() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		$propertyIds = array();

		for ( $i = 0; $i < 4; $i++ ) {
			$property = Property::newFromType( 'string' );

			$store->saveEntity( $property, 'testing', $GLOBALS['wgUser'], EDIT_NEW );

			$propertyIds[] = $property->getId();
		}

		return $propertyIds;
	}

	/**
	 * @return Snak[]
	 */
	private function getSnaks() {
		$snaks = array();

		$snaks[] = new PropertyNoValueSnak( self::$propertyIds[0] );
		$snaks[] = new PropertySomeValueSnak( self::$propertyIds[1] );
		$snaks[] = new PropertyValueSnak( self::$propertyIds[2], new StringValue( 'o_O' ) );

		return $snaks;
	}

	/**
	 * @return Statement[]
	 */
	private function getStatements() {
		$statements = array();

		$ranks = array(
			Statement::RANK_DEPRECATED,
			Statement::RANK_NORMAL,
			Statement::RANK_PREFERRED
		);

		$snaks = $this->getSnaks();
		$snakList = new SnakList( $snaks );
		$mainSnak = $snaks[0];
		$statement = new Statement( $mainSnak );
		$statement->setRank( $ranks[array_rand( $ranks )] );
		$statements[] = $statement;

		foreach ( $snaks as $snak ) {
			$statement = unserialize( serialize( $statement ) );
			$statement->getReferences()->addReference( new Reference( new SnakList( array( $snak ) ) ) );
			$statement->setRank( $ranks[array_rand( $ranks )] );
			$statements[] = $statement;
		}

		$statement = unserialize( serialize( $statement ) );

		$statement->getReferences()->addReference( new Reference( $snakList ) );
		$statement->setRank( $ranks[array_rand( $ranks )] );
		$statements[] = $statement;

		$statement = unserialize( serialize( $statement ) );
		$statement->setQualifiers( $snakList );
		$statement->getReferences()->addReference( new Reference( $snakList ) );
		$statement->setRank( $ranks[array_rand( $ranks )] );
		$statements[] = $statement;

		return $statements;
	}

	public function testAddClaim() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		$statements = $this->getStatements();

		foreach ( $statements as $statement ) {
			$item = new Item();
			$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
			$itemId = $item->getId();

			$guidGenerator = new GuidGenerator();
			$guid = $guidGenerator->newGuid( $itemId );

			$statement->setGuid( $guid );

			// Addition request
			$this->makeRequest( $statement, $itemId, 1, 'addition request' );

			// Reorder qualifiers
			if ( count( $statement->getQualifiers() ) > 0 ) {
				// Simply reorder the qualifiers by putting the first qualifier to the end. This is
				// supposed to be done in the serialized representation since changing the actual
				// object might apply intrinsic sorting.
				$serializerFactory = new SerializerFactory( new DataValueSerializer() );
				$statementSerializer = $serializerFactory->newStatementSerializer();
				$serialized = $statementSerializer->serialize( $statement );
				$firstPropertyId = array_shift( $serialized['qualifiers-order'] );
				array_push( $serialized['qualifiers-order'], $firstPropertyId );
				$this->makeRequest( $serialized, $itemId, 1, 'reorder qualifiers' );
			}

			$newSnak = new PropertyValueSnak( $statement->getPropertyId(), new StringValue( '\o/' ) );
			$newStatement = new Statement( $newSnak );
			$newStatement->setGuid( $guid );

			// Update request
			$this->makeRequest( $newStatement, $itemId, 1, 'update request' );
		}
	}

	private function getInvalidCases() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		$item = new Item();
		$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
		$q17 = $item->getId();

		$property = Property::newFromType( 'string' );
		$store->saveEntity( $property, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
		$p11 = $property->getId();

		$item = new Item();
		$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
		$qx = $item->getId();
		$store->deleteEntity( $qx, 'setclaimtest', $GLOBALS['wgUser'] );

		$property = Property::newFromType( 'string' );
		$store->saveEntity( $property, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
		$px = $property->getId();
		$store->deleteEntity( $px, 'setclaimtest', $GLOBALS['wgUser'] );

		$goodSnak = new PropertyValueSnak( $p11, new StringValue( 'good' ) );
		$badSnak = new PropertyValueSnak( $p11, new StringValue( ' x ' ) );
		$brokenSnak = new PropertyValueSnak( $p11, new NumberValue( 23 ) );
		$obsoleteSnak = new PropertyValueSnak( $px, new StringValue( ' x ' ) );

		$guidGenerator = new GuidGenerator();

		$cases = array();

		$statement = new Statement( $badSnak );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$cases['invalid value in main snak'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $brokenSnak );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$cases['mismatching value in main snak'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $obsoleteSnak );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$cases['obsolete snak using deleted property'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $goodSnak );
		$statement->setGuid( $guidGenerator->newGuid( $qx ) );
		$cases['good claim for deleted item'] = array( $qx, $statement, 'cant-load-entity-content' );

		$statement = new Statement( $goodSnak );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$statement->setQualifiers( new SnakList( array( $badSnak ) ) );
		$cases['bad snak in qualifiers'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $goodSnak );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$statement->setQualifiers( new SnakList( array( $brokenSnak ) ) );
		$cases['mismatching value in qualifier'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $goodSnak );
		$reference = new Reference( new SnakList( array( $badSnak ) ) );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$statement->setReferences( new ReferenceList( array( $reference ) ) );
		$cases['bad snak in reference'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $goodSnak );
		$reference = new Reference( new SnakList( array( $badSnak ) ) );
		$statement->setGuid( $guidGenerator->newGuid( $q17 ) );
		$statement->setReferences( new ReferenceList( array( $reference ) ) );
		$cases['mismatching value in reference'] = array( $q17, $statement, 'modification-failed' );

		$statement = new Statement( $goodSnak );
		$statement->setGuid( 'XXXX' );
		$cases['invalid GUID'] = array( $qx, $statement, 'invalid-claim' );

		return $cases;
	}

	public function testAddInvalidClaim() {
		$cases = $this->getInvalidCases();

		foreach ( $cases as $label => $case ) {
			list( $itemId, $statement, $error ) = $case;

			$this->makeRequest( $statement, $itemId, 1, $label, null, null, $error );
		}
	}

	public function testSetClaimAtIndex() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		$item = new Item();

		$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );
		$itemId = $item->getId();

		$guidGenerator = new GuidGenerator();

		for ( $i = 1; $i <= 3; $i++ ) {
			$item->getStatements()->addNewStatement(
				new PropertyNoValueSnak( $i ),
				null,
				null,
				$guidGenerator->newGuid( $itemId )
			);
		}

		$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_UPDATE );

		$guid = $guidGenerator->newGuid( $itemId );
		foreach ( $this->getStatements() as $statement ) {
			$statement->setGuid( $guid );

			// Add new statement at index 2:
			$this->makeRequest( $statement, $itemId, 4, 'addition request', 2 );
		}
	}

	/**
	 * @param Statement|array $statement Native or serialized statement object.
	 * @param ItemId $itemId
	 * @param int $expectedCount
	 * @param string $requestLabel A label to identify requests that are made in errors.
	 * @param int|null $index
	 * @param int|null $baserevid
	 * @param string|null $error
	 */
	private function makeRequest(
		$statement,
		ItemId $itemId,
		$expectedCount,
		$requestLabel,
		$index = null,
		$baserevid = null,
		$error = null
	) {
		$serializerFactory = new SerializerFactory( new DataValueSerializer() );
		$statementSerializer = $serializerFactory->newStatementSerializer();
		$statementDeserializer = WikibaseRepo::getDefaultInstance()->getExternalFormatStatementDeserializer();

		if ( $statement instanceof Statement ) {
			$serialized = $statementSerializer->serialize( $statement );
		} else {
			$serialized = $statement;
			$statement = $statementDeserializer->deserialize( $serialized );
		}

		$params = array(
			'action' => 'wbsetclaim',
			'claim' => FormatJson::encode( $serialized ),
		);

		if ( !is_null( $index ) ) {
			$params['index'] = $index;
		}

		if ( !is_null( $baserevid ) ) {
			$params['baserevid'] = $baserevid;
		}

		$resultArray = $this->assertApiRequest( $params, $error );

		if ( $resultArray ) {
			$this->assertValidResponse( $resultArray );
			$this->assertStatementWasSet( $statement, $itemId, $expectedCount, $requestLabel );
		}
	}

	/**
	 * @param array $params
	 * @param string|null $error
	 *
	 * @return array|bool
	 */
	private function assertApiRequest( array $params, $error ) {
		$resultArray = false;

		try {
			list( $resultArray, ) = $this->doApiRequestWithToken( $params );

			if ( $error !== null ) {
				$this->fail( "Did not cause expected error $error" );
			}
		} catch ( UsageException $ex ) {
			if ( $error ) {
				$this->assertEquals( $error, $ex->getCodeString(), 'expected error' );
			} else {
				$this->fail( "Caused unexpected error!" . $ex );
			}
		}

		return $resultArray;
	}

	private function assertValidResponse( array $resultArray ) {
		$this->assertResultSuccess( $resultArray );
		$this->assertInternalType( 'array', $resultArray, 'top level element is an array' );
		$this->assertArrayHasKey( 'pageinfo', $resultArray, 'top level element has a pageinfo key' );
		$this->assertArrayHasKey( 'claim', $resultArray, 'top level element has a statement key' );

		if ( isset( $resultArray['claim']['qualifiers'] ) ) {
			$this->assertArrayHasKey( 'qualifiers-order', $resultArray['claim'],
				'"qualifiers-order" key is set when returning qualifiers' );
		}
	}

	/**
	 * @param Statement $statement
	 * @param ItemId $itemId
	 * @param int $expectedCount
	 * @param string $requestLabel A label to identify requests that are made in errors.
	 */
	private function assertStatementWasSet(
		Statement $statement,
		ItemId $itemId,
		$expectedCount,
		$requestLabel
	) {
		$this->assertNotNull( $statement->getGuid(), 'Cannot search for statements with no GUID' );

		/** @var Item $item */
		$item = WikibaseRepo::getDefaultInstance()->getEntityLookup()->getEntity( $itemId );

		$statements = $item->getStatements();
		$savedStatement = $statements->getFirstStatementWithGuid( $statement->getGuid() );
		$this->assertNotNull( $savedStatement, "Statement list does not have statement after {$requestLabel}" );
		if ( count( $statement->getQualifiers() ) ) {
			$this->assertTrue( $statement->getQualifiers()->equals( $savedStatement->getQualifiers() ) );
		}

		$this->assertSame( $expectedCount, $statements->count(), "Statements count is wrong after {$requestLabel}" );
	}

	/**
	 * @see Bug T60394 - "specified index out of bounds" issue when moving a statement
	 * @note A hack is  in place in ChangeOpStatement to allow this
	 */
	public function testBugT60394SpecifiedIndexOutOfBounds() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		// Save new Item with empty statements:
		$item = new Item();
		$store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_NEW );

		// Update the same Item with a single statement:
		$itemId = $item->getId();
		$guidGenerator = new GuidGenerator();
		$item->getStatements()->addNewStatement(
			new PropertyNoValueSnak( self::$propertyIds[1] ),
			null,
			null,
			$guidGenerator->newGuid( $itemId )
		);
		$revision = $store->saveEntity( $item, 'setclaimtest', $GLOBALS['wgUser'], EDIT_UPDATE );

		// Add new statement at index 3 using the baserevid and a different property id
		$statement = new Statement( new PropertyNoValueSnak( self::$propertyIds[2] ) );
		$statement->setGuid( $guidGenerator->newGuid( $itemId ) );
		$this->makeRequest( $statement, $itemId, 2, 'addition request', 3, $revision->getRevisionId() );
	}

	public function testBadPropertyError() {
		$store = WikibaseRepo::getDefaultInstance()->getEntityStore();

		$property = Property::newFromType( 'quantity' );
		$property = $store->saveEntity( $property, '', $GLOBALS['wgUser'], EDIT_NEW )->getEntity();

		$item = new Item();
		/** @var Item $item */
		$item = $store->saveEntity( $item, '', $GLOBALS['wgUser'], EDIT_NEW )->getEntity();

		$guidGenerator = new GuidGenerator();
		$statement = new Statement( new PropertyNoValueSnak( $property->getId() ) );
		$statement->setGuid( $guidGenerator->newGuid( $item->getId() ) );

		$item->getStatements()->addStatement( $statement );
		$store->saveEntity( $item, '', $GLOBALS['wgUser'], EDIT_UPDATE );

		// try to change the main snak's property
		$badProperty = Property::newFromType( 'string' );
		$badProperty = $store->saveEntity( $badProperty, '', $GLOBALS['wgUser'], EDIT_NEW )->getEntity();

		$badSerialization = array(
			'id' => $statement->getGuid(),
			'mainsnak' => array(
				'snaktype' => 'novalue',
				'property' => $badProperty->getId()->getSerialization(),
			),
			'type' => 'statement',
			'rank' => 'normal',
		);

		$params = array(
			'action' => 'wbsetclaim',
			'claim' => FormatJson::encode( $badSerialization ),
		);

		try {
			$this->doApiRequestWithToken( $params );
			$this->fail( 'Changed main snak property did not raise an error' );
		} catch ( UsageException $e ) {
			$this->assertEquals( 'modification-failed', $e->getCodeString(), 'Changed main snak property' );
		}
	}

}