jenkins-bot has submitted this change and it was merged. Change subject: Fix GetClaimsStatementFilter not failing correctly with invalid rank ......................................................................
Fix GetClaimsStatementFilter not failing correctly with invalid rank Luckily this is not critical in production, because the API takes care that no invalid rank can make it that far. See GetClaims::getAllowedParams. But when looking at the two classes I'm touching in this patch, there is no safety. Both filters (by rank and by property) had bugs and failed with type errors and "undefined variable" errors. The StatementRankSerializer did not had any safety. This also increases code coverage (which was the reason why I started working on this). Change-Id: I750c5c569cd336ff67f64744d23ab1666ce790b6 --- M repo/includes/StatementRankSerializer.php M repo/includes/api/GetClaimsStatementFilter.php M repo/tests/phpunit/includes/StatementRankSerializerTest.php A repo/tests/phpunit/includes/api/GetClaimsStatementFilterTest.php 4 files changed, 188 insertions(+), 38 deletions(-) Approvals: Daniel Kinzler: Looks good to me, approved jenkins-bot: Verified diff --git a/repo/includes/StatementRankSerializer.php b/repo/includes/StatementRankSerializer.php index f8ed719..d8da6c4 100644 --- a/repo/includes/StatementRankSerializer.php +++ b/repo/includes/StatementRankSerializer.php @@ -3,6 +3,8 @@ namespace Wikibase; use Deserializers\Deserializer; +use Deserializers\Exceptions\DeserializationException; +use Serializers\Exceptions\SerializationException; use Serializers\Serializer; use Wikibase\DataModel\Statement\Statement; @@ -15,6 +17,7 @@ * @author Jeroen De Dauw < jeroended...@gmail.com > * @author Daniel Kinzler * @author Addshore + * @author Thiemo Mättig */ class StatementRankSerializer implements Serializer, Deserializer { @@ -41,21 +44,32 @@ * * @param string $serializedRank * - * @return integer + * @throws DeserializationException + * @return int */ public function deserialize( $serializedRank ) { $ranks = array_flip( self::$rankMap ); + + if ( !array_key_exists( $serializedRank, $ranks ) ) { + throw new DeserializationException( 'Invalid rank serialization' ); + } + return $ranks[$serializedRank]; } /** * Serializes the rank. * - * @param integer $rank + * @param int $rank * + * @throws SerializationException * @return string */ public function serialize( $rank ) { + if ( !array_key_exists( $rank, self::$rankMap ) ) { + throw new SerializationException( 'Invalid rank' ); + } + return self::$rankMap[$rank]; } diff --git a/repo/includes/api/GetClaimsStatementFilter.php b/repo/includes/api/GetClaimsStatementFilter.php index 3f16596..37ac88e 100644 --- a/repo/includes/api/GetClaimsStatementFilter.php +++ b/repo/includes/api/GetClaimsStatementFilter.php @@ -2,6 +2,7 @@ namespace Wikibase\Repo\Api; +use Deserializers\Exceptions\DeserializationException; use Wikibase\DataModel\Entity\EntityId; use Wikibase\DataModel\Entity\EntityIdParser; use Wikibase\DataModel\Entity\EntityIdParsingException; @@ -13,6 +14,7 @@ * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroended...@gmail.com > * @author Addshore + * @author Thiemo Mättig */ class GetClaimsStatementFilter implements StatementFilter { @@ -29,11 +31,20 @@ private $idParser; /** - * @var array + * @var string[] */ private $requestParams; - public function __construct( EntityIdParser $idParser, ApiErrorReporter $errorReporter, array $requestParams ) { + /** + * @param EntityIdParser $idParser + * @param ApiErrorReporter $errorReporter + * @param string[] $requestParams + */ + public function __construct( + EntityIdParser $idParser, + ApiErrorReporter $errorReporter, + array $requestParams + ) { $this->idParser = $idParser; $this->errorReporter = $errorReporter; $this->requestParams = $requestParams; @@ -42,22 +53,27 @@ /** * @param Statement $statement * - * @return boolean + * @return bool */ public function statementMatches( Statement $statement ) { return $this->rankMatchesFilter( $statement->getRank() ) && $this->propertyMatchesFilter( $statement->getPropertyId() ); } + /** + * @param int $rank + * + * @return bool + */ private function rankMatchesFilter( $rank ) { - if ( $rank === null ) { - return true; - } - if ( isset( $this->requestParams['rank'] ) ) { - $statementRankSerializer = new StatementRankSerializer(); - $unserializedRank = $statementRankSerializer->deserialize( $this->requestParams['rank'] ); - return $rank === $unserializedRank; + try { + $serializer = new StatementRankSerializer(); + $deserializedRank = $serializer->deserialize( $this->requestParams['rank'] ); + return $rank === $deserializedRank; + } catch ( DeserializationException $ex ) { + $this->errorReporter->dieException( $ex, 'param-invalid' ); + } } return true; @@ -67,12 +83,10 @@ if ( isset( $this->requestParams['property'] ) ) { try { $parsedProperty = $this->idParser->parse( $this->requestParams['property'] ); - } catch ( EntityIdParsingException $e ) { - $this->errorReporter->dieException( $e, 'param-invalid' ); + return $propertyId->equals( $parsedProperty ); + } catch ( EntityIdParsingException $ex ) { + $this->errorReporter->dieException( $ex, 'param-invalid' ); } - - /** @var EntityId $parsedProperty */ - return $propertyId->equals( $parsedProperty ); } return true; diff --git a/repo/tests/phpunit/includes/StatementRankSerializerTest.php b/repo/tests/phpunit/includes/StatementRankSerializerTest.php index 2eb2838..761184e 100644 --- a/repo/tests/phpunit/includes/StatementRankSerializerTest.php +++ b/repo/tests/phpunit/includes/StatementRankSerializerTest.php @@ -3,7 +3,7 @@ namespace Wikibase\Test; use DataValues\Serializers\DataValueSerializer; -use Wikibase\DataModel\Entity\PropertyId; +use PHPUnit_Framework_TestCase; use Wikibase\DataModel\SerializerFactory; use Wikibase\DataModel\Snak\PropertyNoValueSnak; use Wikibase\DataModel\Statement\Statement; @@ -17,24 +17,41 @@ * * @licence GNU GPL v2+ * @author Addshore + * @author Thiemo Mättig */ -class StatementRankSerializerTest extends \PHPUnit_Framework_TestCase { +class StatementRankSerializerTest extends PHPUnit_Framework_TestCase { public function rankProvider() { - $ranks = array( - Statement::RANK_NORMAL, - Statement::RANK_PREFERRED, - Statement::RANK_DEPRECATED, + return array( + array( Statement::RANK_DEPRECATED, 'deprecated' ), + array( Statement::RANK_NORMAL, 'normal' ), + array( Statement::RANK_PREFERRED, 'preferred' ), ); - - return $this->arrayWrap( $ranks ); } /** * @dataProvider rankProvider */ - public function testRankSerialization( $rank ) { - $statement = new Statement( new PropertyNoValueSnak( new PropertyId( 'P42' ) ) ); + public function testSerialize( $rank, $expected ) { + $serializer = new StatementRankSerializer(); + $serialization = $serializer->serialize( $rank ); + $this->assertSame( $expected, $serialization ); + } + + /** + * @dataProvider rankProvider + */ + public function testDeserialize( $expected, $serialization ) { + $serializer = new StatementRankSerializer(); + $deserialization = $serializer->deserialize( $serialization ); + $this->assertSame( $expected, $deserialization ); + } + + /** + * @dataProvider rankProvider + */ + public function testSerializerFactoryRoundtrip( $rank ) { + $statement = new Statement( new PropertyNoValueSnak( 1 ) ); $statement->setRank( $rank ); $factory = new SerializerFactory( new DataValueSerializer() ); @@ -44,26 +61,29 @@ $rankSerializer = new StatementRankSerializer(); - $this->assertEquals( + $this->assertSame( $rank, $rankSerializer->deserialize( $serialization['rank'] ), - 'Roundtrip between rank serialization and unserialization 1' + 'reference serialization can be deserialized' ); - $this->assertEquals( + $this->assertSame( $serialization['rank'], $rankSerializer->serialize( $rank ), - 'Roundtrip between rank serialization and unserialization 2' + 'serialization is identical to reference' ); } - protected function arrayWrap( array $elements ) { - return array_map( - function ( $element ) { - return array( $element ); - }, - $elements - ); + public function testGivenInvalidRank_serializationFails() { + $serializer = new StatementRankSerializer(); + $this->setExpectedException( 'Serializers\Exceptions\SerializationException' ); + $serializer->serialize( -1 ); + } + + public function testGivenInvalidSerialization_deserializeFails() { + $serializer = new StatementRankSerializer(); + $this->setExpectedException( 'Deserializers\Exceptions\DeserializationException' ); + $serializer->deserialize( 'invalid' ); } } diff --git a/repo/tests/phpunit/includes/api/GetClaimsStatementFilterTest.php b/repo/tests/phpunit/includes/api/GetClaimsStatementFilterTest.php new file mode 100644 index 0000000..f5eb29b --- /dev/null +++ b/repo/tests/phpunit/includes/api/GetClaimsStatementFilterTest.php @@ -0,0 +1,102 @@ +<?php + +namespace Wikibase\DataModel\Services\Tests\Statement\Filter; + +use PHPUnit_Framework_TestCase; +use Wikibase\DataModel\Entity\BasicEntityIdParser; +use Wikibase\DataModel\Snak\PropertyNoValueSnak; +use Wikibase\DataModel\Statement\Statement; +use Wikibase\Repo\Api\ApiErrorReporter; +use Wikibase\Repo\Api\GetClaimsStatementFilter; + +/** + * @covers Wikibase\Repo\Api\GetClaimsStatementFilter + * + * @licence GNU GPL v2+ + * @author Thiemo Mättig + */ +class GetClaimsStatementFilterTest extends PHPUnit_Framework_TestCase { + + /** + * @param bool $expectsError + * + * @return ApiErrorReporter + */ + private function getApiErrorReporter( $expectsError = false ) { + $errorReporter = $this->getMockBuilder( 'Wikibase\Repo\Api\ApiErrorReporter' ) + ->disableOriginalConstructor() + ->getMock(); + + if ( !$expectsError ) { + $errorReporter->expects( $this->never() ) + ->method( 'dieException' ); + } else { + $errorReporter->expects( $this->once() ) + ->method( 'dieException' ) + ->with( + $this->isInstanceOf( 'RuntimeException' ), + 'param-invalid' + ); + } + + return $errorReporter; + } + + /** + * @dataProvider statementProvider + */ + public function testIsMatch( array $requestParams, Statement $statement, $expected ) { + $filter = new GetClaimsStatementFilter( + new BasicEntityIdParser(), + $this->getApiErrorReporter(), + $requestParams + ); + + $this->assertSame( $expected, $filter->statementMatches( $statement ) ); + } + + public function statementProvider() { + $statement = new Statement( new PropertyNoValueSnak( 1 ) ); + + return array( + // No filter + array( array(), $statement, true ), + + // Filter by rank + array( array( 'rank' => 'normal' ), $statement, true ), + array( array( 'rank' => 'deprecated' ), $statement, false ), + + // Filter by property + array( array( 'property' => 'p1' ), $statement, true ), + array( array( 'property' => 'p2' ), $statement, false ), + + // Filter by both rank and property + array( array( 'rank' => 'normal', 'property' => 'p1' ), $statement, true ), + array( array( 'rank' => 'deprecated', 'property' => 'p1' ), $statement, false ), + array( array( 'rank' => 'normal', 'property' => 'p2' ), $statement, false ), + ); + } + + public function testInvalidRankSerialization() { + $filter = new GetClaimsStatementFilter( + new BasicEntityIdParser(), + $this->getApiErrorReporter( true ), + array( 'rank' => 'invalid' ) + ); + + $statement = new Statement( new PropertyNoValueSnak( 1 ) ); + $this->assertTrue( $filter->statementMatches( $statement ) ); + } + + public function testInvalidPropertySerialization() { + $filter = new GetClaimsStatementFilter( + new BasicEntityIdParser(), + $this->getApiErrorReporter( true ), + array( 'property' => 'invalid' ) + ); + + $statement = new Statement( new PropertyNoValueSnak( 1 ) ); + $this->assertTrue( $filter->statementMatches( $statement ) ); + } + +} -- To view, visit https://gerrit.wikimedia.org/r/270004 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I750c5c569cd336ff67f64744d23ab1666ce790b6 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Thiemo Mättig (WMDE) <thiemo.maet...@wikimedia.de> Gerrit-Reviewer: Addshore <addshorew...@gmail.com> Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Hoo man <h...@online.de> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits