jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/369881 )

Change subject: Fix handling of coalesced changes in InjectRCRecordsJob
......................................................................


Fix handling of coalesced changes in InjectRCRecordsJob

Instead of representing changes using their IDs, we represent them
using their complete field data.

However, composite changes represent their children using their IDs.

Bug: T171370
Bug: T172394
Change-Id: Iaa98722779507bb57d449649f1147314da685312
---
M client/WikibaseClient.php
M client/includes/Changes/InjectRCRecordsJob.php
M client/includes/WikibaseClient.php
M client/tests/phpunit/includes/Changes/InjectRCRecordsJobTest.php
M client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
M client/tests/phpunit/includes/WikibaseClientTest.php
M lib/includes/Changes/ChangeRow.php
M lib/includes/Changes/EntityChange.php
M lib/includes/Changes/EntityChangeFactory.php
M lib/tests/phpunit/Changes/EntityChangeFactoryTest.php
M lib/tests/phpunit/Changes/EntityChangeTest.php
M lib/tests/phpunit/Changes/TestChanges.php
M lib/tests/phpunit/Store/Sql/EntityChangeLookupTest.php
M repo/includes/WikibaseRepo.php
14 files changed, 427 insertions(+), 21 deletions(-)

Approvals:
  jenkins-bot: Verified
  Thiemo Mättig (WMDE): Looks good to me, approved



diff --git a/client/WikibaseClient.php b/client/WikibaseClient.php
index 012905d..836ac30 100644
--- a/client/WikibaseClient.php
+++ b/client/WikibaseClient.php
@@ -189,6 +189,7 @@
                $job = new Wikibase\Client\Changes\InjectRCRecordsJob(
                        $mwServices->getDBLoadBalancerFactory(),
                        $wbServices->getStore()->getEntityChangeLookup(),
+                       $wbServices->getEntityChangeFactory(),
                        $wbServices->getRecentChangeFactory(),
                        $params
                );
diff --git a/client/includes/Changes/InjectRCRecordsJob.php 
b/client/includes/Changes/InjectRCRecordsJob.php
index 54c4781..6a50900 100644
--- a/client/includes/Changes/InjectRCRecordsJob.php
+++ b/client/includes/Changes/InjectRCRecordsJob.php
@@ -14,6 +14,7 @@
 use Wikibase\Client\Store\TitleFactory;
 use Wikibase\Client\WikibaseClient;
 use Wikibase\EntityChange;
+use Wikibase\Lib\Changes\EntityChangeFactory;
 use Wikibase\Lib\Store\Sql\EntityChangeLookup;
 use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\LBFactory;
@@ -37,6 +38,11 @@
         * @var EntityChangeLookup
         */
        private $changeLookup;
+
+       /**
+        * @var EntityChangeFactory
+        */
+       private $changeFactory;
 
        /**
         * @var RecentChangeFactory
@@ -82,8 +88,13 @@
                        $pages[$id] = [ $t->getNamespace(), $t->getDBkey() ];
                }
 
+               // Note: Avoid serializing Change objects. Original changes can 
be restored
+               // from $changeData['info']['change-ids'], see getChange().
+               $changeData = $change->getFields();
+               $changeData['info'] = $change->getSerializedInfo( [ 'changes' ] 
);
+
                $params = [
-                       'change' => $change->getId(),
+                       'change' => $changeData,
                        'pages' => $pages
                ];
 
@@ -91,7 +102,6 @@
                        'wikibase-InjectRCRecords',
                        $params
                );
-
        }
 
        /**
@@ -100,6 +110,7 @@
         *
         * @param LBFactory $lbFactory
         * @param EntityChangeLookup $changeLookup
+        * @param EntityChangeFactory $changeFactory
         * @param RecentChangeFactory $rcFactory
         * @param array $params Needs to have two keys: "change": the id of the 
change,
         *     "pages": array of pages, represented as $pageId => [ $namespace, 
$dbKey ].
@@ -107,6 +118,7 @@
        public function __construct(
                LBFactory $lbFactory,
                EntityChangeLookup $changeLookup,
+               EntityChangeFactory $changeFactory,
                RecentChangeFactory $rcFactory,
                array $params
        ) {
@@ -118,6 +130,13 @@
                        '$params',
                        '$params[\'change\'] not set.'
                );
+               // TODO: disallow integer once T172394 has been deployed and 
old jobs have cleared the queue.
+               Assert::parameterType(
+                       'integer|array',
+                       $params['change'],
+                       '$params[\'change\']'
+               );
+
                Assert::parameter(
                        isset( $params['pages'] ),
                        '$params',
@@ -131,6 +150,7 @@
 
                $this->lbFactory = $lbFactory;
                $this->changeLookup = $changeLookup;
+               $this->changeFactory = $changeFactory;
                $this->rcFactory = $rcFactory;
 
                $this->titleFactory = new TitleFactory();
@@ -182,16 +202,31 @@
         */
        private function getChange() {
                $params = $this->getParams();
-               $changeId = $params['change'];
+               $changeData = $params['change'];
 
-               $this->logger->debug( __FUNCTION__ . ": loading change 
$changeId." );
+               if ( is_int( $changeData ) ) {
+                       // TODO: this can be removed once T172394 has been 
deployed
+                       //       and old jobs have cleared the queue.
+                       $this->logger->debug( __FUNCTION__ . ": loading change 
$changeData." );
 
-               $changes = $this->changeLookup->loadByChangeIds( [ $changeId ] 
);
+                       $changes = $this->changeLookup->loadByChangeIds( [ 
$changeData ] );
 
-               $change = reset( $changes );
+                       $change = reset( $changes );
 
-               if ( !$change ) {
-                       $this->logger->error( __FUNCTION__ . ": failed to load 
change $changeId." );
+                       if ( !$change ) {
+                               $this->logger->error( __FUNCTION__ . ": failed 
to load change $changeData." );
+                       }
+               } else {
+                       $change = $this->changeFactory->newFromFieldData( 
$params['change'] );
+
+                       // If the current change was composed of other child 
changes, restore the
+                       // child objects.
+                       $info = $change->getInfo();
+                       if ( isset( $info['change-ids'] ) && !isset( 
$info['changes'] ) ) {
+                               $children = 
$this->changeLookup->loadByChangeIds( $info['change-ids'] );
+                               $info['changes'] = $children;
+                               $change->setField( 'info', $info );
+                       }
                }
 
                return $change;
@@ -209,7 +244,7 @@
                $titles = [];
 
                foreach ( $pages as $pageId => list( $namespace, $dbKey ) ) {
-                       $titles[] = $this->titleFactory->makeTitle( $namespace, 
$dbKey );
+                       $titles[$pageId] = $this->titleFactory->makeTitle( 
$namespace, $dbKey );
                }
 
                return $titles;
diff --git a/client/includes/WikibaseClient.php 
b/client/includes/WikibaseClient.php
index 083f143..164f09f 100644
--- a/client/includes/WikibaseClient.php
+++ b/client/includes/WikibaseClient.php
@@ -1072,7 +1072,7 @@
        /**
         * @return EntityChangeFactory
         */
-       private function getEntityChangeFactory() {
+       public function getEntityChangeFactory() {
                //TODO: take this from a setting or registry.
                $changeClasses = [
                        Item::ENTITY_TYPE => ItemChange::class,
@@ -1081,6 +1081,7 @@
 
                return new EntityChangeFactory(
                        $this->getEntityDiffer(),
+                       $this->getEntityIdParser(),
                        $changeClasses
                );
        }
diff --git a/client/tests/phpunit/includes/Changes/InjectRCRecordsJobTest.php 
b/client/tests/phpunit/includes/Changes/InjectRCRecordsJobTest.php
index e182b76..41c54bf 100644
--- a/client/tests/phpunit/includes/Changes/InjectRCRecordsJobTest.php
+++ b/client/tests/phpunit/includes/Changes/InjectRCRecordsJobTest.php
@@ -9,9 +9,19 @@
 use Wikibase\Client\RecentChanges\RecentChangeFactory;
 use Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector;
 use Wikibase\Client\Store\TitleFactory;
+use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Services\Diff\EntityDiffer;
+use Wikibase\DataModel\Services\Diff\ItemDiff;
 use Wikibase\EntityChange;
+use Wikibase\ItemChange;
+use Wikibase\Lib\Changes\EntityChangeFactory;
 use Wikibase\Lib\Store\Sql\EntityChangeLookup;
+use Wikibase\Lib\Tests\Changes\EntityChangeFactoryTest;
 use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * @covers Wikibase\Client\Changes\InjectRCRecordsJob
@@ -43,21 +53,39 @@
        }
 
        /**
-        * @param EntityChange $change
+        * @param EntityChange[] $knownChanges
         *
         * @return PHPUnit_Framework_MockObject_MockObject|EntityChangeLookup
         */
-       private function getEntityChangeLookupMock( EntityChange $change ) {
+       private function getEntityChangeLookupMock( array $knownChanges = [] ) {
                $changeLookup = $this->getMockBuilder( 
EntityChangeLookup::class )
                        ->disableOriginalConstructor()
                        ->getMock();
 
+               $changes = [];
+               foreach ( $knownChanges as $change ) {
+                       $id = $change->getId();
+                       $changes[$id] = $change;
+               }
+
                $changeLookup->expects( $this->any() )
                        ->method( 'loadByChangeIds' )
-                       ->with( [ $change->getId() ] )
-                       ->will( $this->returnValue( [ $change ] ) );
+                       ->will( $this->returnCallback( function ( $ids ) use ( 
$changes ) {
+                               return array_values( array_intersect_key( 
$changes, array_flip( $ids ) ) );
+                       } ) );
 
                return $changeLookup;
+       }
+
+       /**
+        * @return EntityChangeFactory
+        */
+       private function getEntityChangeFactory() {
+               return new EntityChangeFactory(
+                       new EntityDiffer(),
+                       new BasicEntityIdParser(),
+                       [ Item::ENTITY_TYPE => ItemChange::class ]
+               );
        }
 
        /**
@@ -111,6 +139,10 @@
                        ->will( $this->returnValue( $text ) );
 
                $title->expects( $this->any() )
+                       ->method( 'getPrefixedDBkey' )
+                       ->will( $this->returnValue( $text ) );
+
+               $title->expects( $this->any() )
                        ->method( 'getNamespace' )
                        ->will( $this->returnValue( 0 ) );
 
@@ -119,10 +151,13 @@
 
        /**
         * @param int $id
+        * @param array $fields
         *
         * @return PHPUnit_Framework_MockObject_MockObject|EntityChange
         */
-       private function getEntityChangeMock( $id = 77 ) {
+       private function getEntityChangeMock( $id = 77, array $fields = [] ) {
+               $info = isset( $fields['info'] ) ? $fields['info'] : [];
+
                $change = $this->getMockBuilder( EntityChange::class )
                        ->disableOriginalConstructor()
                        ->getMock();
@@ -130,6 +165,18 @@
                $change->expects( $this->any() )
                        ->method( 'getId' )
                        ->will( $this->returnValue( $id ) );
+
+               $change->expects( $this->any() )
+                       ->method( 'getFields' )
+                       ->will( $this->returnValue( $fields ) );
+
+               $change->expects( $this->any() )
+                       ->method( 'getInfo' )
+                       ->will( $this->returnValue( $info ) );
+
+               $change->expects( $this->any() )
+                       ->method( 'getSerializedInfo' )
+                       ->will( $this->returnValue( json_encode( $info ) ) );
 
                return $change;
        }
@@ -156,12 +203,221 @@
                return $LBFactory;
        }
 
+       public function provideMakeJobSpecification() {
+               $title = $this->getTitleMock( 'Foo', 21 );
+               $changeFactory = $this->getEntityChangeFactory();
+               $itemId = new ItemId( 'Q7' );
+
+               $child1 = $changeFactory->newFromFieldData( [
+                       'id' => 101,
+                       'type' => 'wikibase-item~change',
+                       'object_id' => $itemId->getSerialization(),
+               ] );
+
+               $child2 = $changeFactory->newFromFieldData( [
+                       'id' => 102,
+                       'type' => 'wikibase-item~change',
+                       'object_id' => $itemId->getSerialization(),
+               ] );
+
+               $diff = new ItemDiff();
+
+               return [
+                       'mock change' => [
+                               [ $title ],
+                               $this->getEntityChangeMock(
+                                       17,
+                                       [
+                                               'id' => 17,
+                                               'object_id' => 'Q7',
+                                               'type' => 
'wikibase-item~change',
+                                               'Test' => 'Kitten',
+                                               'info' => []
+                                       ]
+                               ),
+                       ],
+                       'simple change with ID' => [
+                               [ $title ],
+                               $changeFactory->newForEntity(
+                                       'change',
+                                       new ItemId( 'Q7' ),
+                                       [ 'id' => 7 ]
+                               ),
+                       ],
+                       'simple change with data but no ID' => [
+                               [ $title ],
+                               $changeFactory->newForEntity(
+                                       'change',
+                                       $itemId,
+                                       [
+                                               'type' => 
'wikibase-item~change',
+                                               'object_id' => 
$itemId->getSerialization(),
+                                               'info' => [
+                                                       'diff' => $diff,
+                                                       'stuff' => 'Fun Stuff',
+                                                       'metadata' => [
+                                                               'foo' => 
'Kitten'
+                                                       ]
+                                               ]
+                                       ]
+                               ),
+                       ],
+                       'composite change without ID' => [
+                               [ $title ],
+                               $changeFactory->newForEntity(
+                                       'change',
+                                       $itemId,
+                                       [
+                                               'type' => 
'wikibase-item~change',
+                                               'object_id' => 
$itemId->getSerialization(),
+                                               'info' => [
+                                                       'diff' => $diff,
+                                                       'change-ids' => [ 101, 
102 ],
+                                                       'changes' => [ $child1, 
$child2 ],
+                                               ]
+                                       ]
+                               ),
+                               [ $child1, $child2 ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideMakeJobSpecification()
+        * @param Title[] $titles
+        * @param EntityChange $change
+        */
+       public function testMakeJobSpecification( array $titles, EntityChange 
$change, array $knownChanges = [] ) {
+               $spec = InjectRCRecordsJob::makeJobSpecification( $titles, 
$change );
+
+               $changeLookup = $this->getEntityChangeLookupMock( $knownChanges 
);
+               $changeFactory = $this->getEntityChangeFactory();
+               $rcFactory = $this->getRCFactoryMock();
+
+               $job = new InjectRCRecordsJob(
+                       $this->getLBFactoryMock(),
+                       $changeLookup,
+                       $changeFactory,
+                       $rcFactory,
+                       $spec->getParams()
+               );
+
+               $actualChange = TestingAccessWrapper::newFromObject( $job 
)->getChange();
+
+               $this->assertEquals( $change->getId(), $actualChange->getId(), 
'Change ID' );
+               $this->assertEquals( $change->getFields(), 
$actualChange->getFields(), 'Change Fields' );
+
+               $actualTitles = TestingAccessWrapper::newFromObject( $job 
)->getTitles();
+
+               $this->assertEquals(
+                       $this->getTitleIDs( $titles ),
+                       array_keys( $actualTitles ),
+                       'Title ID'
+               );
+               $this->assertEquals(
+                       $this->getTitleDBKeys( $titles ),
+                       array_values( $this->getTitleDBKeys( $actualTitles ) ),
+                       'Title DBKey'
+               );
+       }
+
+       public function provideConstruction() {
+               $change = $this->getEntityChangeMock(
+                       17,
+                       [
+                               'id' => 17,
+                               'object_id' => 'Q7',
+                               'type' => 'wikibase-item~change',
+                               'Test' => 'Kitten',
+                               'info' => []
+                       ]
+               );
+               $title = $this->getTitleMock( 'Foo', 21 );
+
+               return [
+                       // TODO: drop the change ID test case once T172394 has 
been deployed
+                       //       and old jobs have cleared the queue.
+                       'job spec using change ID' => [
+                               [
+                                       'change' => $change->getId(),
+                                       'pages' => $this->getPageSpecData( [ 
$title ] )
+                               ],
+                               $change,
+                               [ $title ],
+                       ],
+                       'job spec using change field data' => [
+                               [
+                                       'change' => $change->getFields(),
+                                       'pages' => $this->getPageSpecData( [ 
$title ] )
+                               ],
+                               $change,
+                               [ $title ],
+                       ],
+               ];
+       }
+
+       /**
+        * @param Title[] $titles
+        *
+        * @return array[]
+        */
+       private function getPageSpecData( array $titles ) {
+               $pages = [];
+
+               foreach ( $titles as $title ) {
+                       $id = $title->getArticleId();
+                       $pages[$id] = [ $title->getNamespace(), 
$title->getDBkey() ];
+               }
+
+               return $pages;
+       }
+
+       /**
+        * @dataProvider provideConstruction()
+        */
+       public function testConstruction(
+               array $params,
+               EntityChange $expectedChange,
+               array $expectedTitles
+       ) {
+               $changeLookup = $this->getEntityChangeLookupMock( [ 
$expectedChange ] );
+               $changeFactory = $this->getEntityChangeFactory();
+               $rcFactory = $this->getRCFactoryMock();
+
+               $job = new InjectRCRecordsJob(
+                       $this->getLBFactoryMock(),
+                       $changeLookup,
+                       $changeFactory,
+                       $rcFactory,
+                       $params
+               );
+
+               $actualChange = TestingAccessWrapper::newFromObject( $job 
)->getChange();
+
+               $this->assertEquals( $expectedChange->getId(), 
$actualChange->getId(), 'Change ID' );
+               $this->assertEquals( $expectedChange->getFields(), 
$actualChange->getFields(), 'Change Fields' );
+
+               $actualTitles = TestingAccessWrapper::newFromObject( $job 
)->getTitles();
+
+               $this->assertEquals(
+                       $this->getTitleIDs( $expectedTitles ),
+                       array_keys( $actualTitles ),
+                       'Title ID'
+               );
+               $this->assertEquals(
+                       $this->getTitleDBKeys( $expectedTitles ),
+                       array_values( $this->getTitleDBKeys( $actualTitles ) ),
+                       'Title DBKey'
+               );
+       }
+
        public function testRun() {
                $title = $this->getTitleMock( 'Foo', 21 );
                $change = $this->getEntityChangeMock( 17 );
                $rc = $this->getRecentChangeMock();
 
-               $changeLookup = $this->getEntityChangeLookupMock( $change );
+               $changeLookup = $this->getEntityChangeLookupMock( [ $change ] );
+               $changeFactory = $this->getEntityChangeFactory();
 
                $rcFactory = $this->getRCFactoryMock();
 
@@ -186,6 +442,7 @@
                $job = new InjectRCRecordsJob(
                        $this->getLBFactoryMock(),
                        $changeLookup,
+                       $changeFactory,
                        $rcFactory,
                        $params
                );
@@ -199,7 +456,8 @@
        public function testRun_batch() {
                $change = $this->getEntityChangeMock();
                $rc = $this->getRecentChangeMock();
-               $changeLookup = $this->getEntityChangeLookupMock( $change );
+               $changeLookup = $this->getEntityChangeLookupMock( [ $change ] );
+               $changeFactory = $this->getEntityChangeFactory();
 
                $rcFactory = $this->getRCFactoryMock();
 
@@ -223,6 +481,7 @@
                $job = new InjectRCRecordsJob(
                        $lbFactory,
                        $changeLookup,
+                       $changeFactory,
                        $rcFactory,
                        $params
                );
@@ -233,4 +492,32 @@
                $job->run();
        }
 
+       /**
+        * @param Title[] $titles
+        *
+        * @return int[]
+        */
+       private function getTitleIDs( array $titles ) {
+               return array_map(
+                       function( Title $title ) {
+                               return $title->getArticleId();
+                       },
+                       $titles
+               );
+       }
+
+       /**
+        * @param Title[] $titles
+        *
+        * @return string[]
+        */
+       private function getTitleDBKeys( array $titles ) {
+               return array_map(
+                       function( Title $title ) {
+                               return $title->getPrefixedDBkey();
+                       },
+                       $titles
+               );
+       }
+
 }
diff --git a/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php 
b/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
index 5315a06..37e33d5 100644
--- a/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
+++ b/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
@@ -123,6 +123,14 @@
                        ->method( 'getId' )
                        ->will( $this->returnValue( $id ) );
 
+               $change->expects( $this->any() )
+                       ->method( 'getFields' )
+                       ->will( $this->returnValue( [ 'id' => $id, 'info' => [] 
] ) );
+
+               $change->expects( $this->any() )
+                       ->method( 'getSerializedInfo' )
+                       ->will( $this->returnValue( '{}' ) );
+
                return $change;
        }
 
@@ -229,7 +237,7 @@
                                $this->assertArrayHasKey( 'change', $params, 
'$params["change"]' );
                                $this->assertArrayHasKey( 'pages', $params, 
'$params["pages"]' );
 
-                               $this->assertSame( $change->getId(), 
$params["change"] );
+                               $this->assertSame( $change->getId(), 
$params['change']['id'] );
 
                                $pages += $params['pages']; // addition uses 
keys, array_merge does not
                        } ) );
diff --git a/client/tests/phpunit/includes/WikibaseClientTest.php 
b/client/tests/phpunit/includes/WikibaseClientTest.php
index 4e51f2a..5465207 100644
--- a/client/tests/phpunit/includes/WikibaseClientTest.php
+++ b/client/tests/phpunit/includes/WikibaseClientTest.php
@@ -29,6 +29,7 @@
 use Wikibase\Client\LangLinkHandler;
 use Wikibase\LanguageFallbackChain;
 use Wikibase\LanguageFallbackChainFactory;
+use Wikibase\Lib\Changes\EntityChangeFactory;
 use Wikibase\Lib\ContentLanguages;
 use Wikibase\Lib\DataTypeDefinitions;
 use Wikibase\Lib\EntityTypeDefinitions;
@@ -299,6 +300,11 @@
                $this->assertInstanceOf( RestrictedEntityLookup::class, 
$restrictedEntityLookup );
        }
 
+       public function testGetEntityChangeFactory() {
+               $entityChangeFactory = 
$this->getWikibaseClient()->getEntityChangeFactory();
+               $this->assertInstanceOf( EntityChangeFactory::class, 
$entityChangeFactory );
+       }
+
        public function propertyOrderUrlProvider() {
                return [
                        [ 'page-url' ],
diff --git a/lib/includes/Changes/ChangeRow.php 
b/lib/includes/Changes/ChangeRow.php
index 8158770..0fc4916 100644
--- a/lib/includes/Changes/ChangeRow.php
+++ b/lib/includes/Changes/ChangeRow.php
@@ -114,9 +114,12 @@
        }
 
        /**
+        * @param string[] $skipKeys Keys of the info array to skip during 
serialization. Useful for
+        *        omitting undesired or unserializable data from the 
serialization.
+        *
         * @return string JSON
         */
-       abstract public function getSerializedInfo();
+       abstract public function getSerializedInfo( $skipKeys = [] );
 
        /**
         * Unserializes the info field using json_decode.
diff --git a/lib/includes/Changes/EntityChange.php 
b/lib/includes/Changes/EntityChange.php
index 6a8211f..7865fef 100644
--- a/lib/includes/Changes/EntityChange.php
+++ b/lib/includes/Changes/EntityChange.php
@@ -223,11 +223,15 @@
        /**
         * @see ChangeRow::getSerializedInfo
         *
+        * @param string[] $skipKeys
+        *
         * @return string JSON
         */
-       public function getSerializedInfo() {
+       public function getSerializedInfo( $skipKeys = [] ) {
                $info = $this->getInfo();
 
+               $info = array_diff_key( $info, array_flip( $skipKeys ) );
+
                if ( isset( $info['diff'] ) ) {
                        $diff = $info['diff'];
 
diff --git a/lib/includes/Changes/EntityChangeFactory.php 
b/lib/includes/Changes/EntityChangeFactory.php
index 6fa052d..8fb9411 100644
--- a/lib/includes/Changes/EntityChangeFactory.php
+++ b/lib/includes/Changes/EntityChangeFactory.php
@@ -5,12 +5,14 @@
 use MWException;
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\EntityIdParser;
 use Wikibase\DataModel\Services\Diff\EntityDiffer;
 use Wikibase\DataModel\Statement\StatementListProvider;
 use Wikibase\DataModel\Term\AliasGroupList;
 use Wikibase\DataModel\Term\FingerprintProvider;
 use Wikibase\DataModel\Term\TermList;
 use Wikibase\EntityChange;
+use Wikimedia\Assert\Assert;
 
 /**
  * Factory for EntityChange objects
@@ -27,20 +29,28 @@
        private $entityDiffer;
 
        /**
+        * @var EntityIdParser
+        */
+       private $idParser;
+
+       /**
         * @var string[] Maps entity type IDs to subclasses of EntityChange.
         */
        private $changeClasses;
 
        /**
         * @param EntityDiffer $entityDiffer
+        * @param EntityIdParser $idParser
         * @param string[] $changeClasses Maps entity type IDs to subclasses of 
EntityChange.
         * Entity types not mapped explicitly are assumed to use EntityChange 
itself.
         */
        public function __construct(
                EntityDiffer $entityDiffer,
+               EntityIdParser $idParser,
                array $changeClasses = []
        ) {
                $this->entityDiffer = $entityDiffer;
+               $this->idParser = $idParser;
                $this->changeClasses = $changeClasses;
        }
 
@@ -90,6 +100,20 @@
        }
 
        /**
+        * @param array $fields all data fields, including at least 'type' and 
'object_id'.
+        * @return EntityChange
+        */
+       public function newFromFieldData( array $fields ) {
+               Assert::parameter( isset( $fields['type'] ), 
'$fields[\'type\']', 'must be set' );
+               Assert::parameter( isset( $fields['object_id'] ), 
'$fields[\'object_id\']', 'must be set' );
+
+               $action = explode( '~', $fields['type'] )[1];
+               $entityId = $this->idParser->parse( $fields['object_id'] );
+
+               return $this->newForEntity( $action, $entityId, $fields );
+       }
+
+       /**
         * Constructs an EntityChange from the given old and new Entity.
         *
         * @param string      $action The action name
diff --git a/lib/tests/phpunit/Changes/EntityChangeFactoryTest.php 
b/lib/tests/phpunit/Changes/EntityChangeFactoryTest.php
index 3ffbabf..0ca1f57 100644
--- a/lib/tests/phpunit/Changes/EntityChangeFactoryTest.php
+++ b/lib/tests/phpunit/Changes/EntityChangeFactoryTest.php
@@ -5,6 +5,7 @@
 use Diff\DiffOp\Diff\Diff;
 use Diff\DiffOp\DiffOpAdd;
 use Diff\DiffOp\DiffOpRemove;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\Entity\ItemId;
@@ -41,6 +42,7 @@
 
                $factory = new EntityChangeFactory(
                        new EntityDiffer(),
+                       new BasicEntityIdParser(),
                        $changeClasses
                );
 
@@ -88,6 +90,26 @@
                $this->assertEquals( $entityId, $change->getEntityId() );
        }
 
+       /**
+        * @dataProvider newForEntityProvider
+        *
+        * @param string $action
+        * @param EntityId $entityId
+        * @param string $expectedClass
+        */
+       public function testNewFromFieldData( $action, EntityId $entityId, 
$expectedClass ) {
+               $fields = [];
+               $fields['type'] = 'wikibase-' . $entityId->getEntityType() . 
'~' . $action;
+               $fields['object_id'] = $entityId->getSerialization();
+
+               $factory = $this->getEntityChangeFactory();
+
+               $change = $factory->newFromFieldData( $fields );
+               $this->assertInstanceOf( $expectedClass, $change );
+               $this->assertEquals( $action, $change->getAction() );
+               $this->assertEquals( $entityId, $change->getEntityId() );
+       }
+
        public function testNewFromUpdate() {
                $itemId = new ItemId( 'Q1' );
 
diff --git a/lib/tests/phpunit/Changes/EntityChangeTest.php 
b/lib/tests/phpunit/Changes/EntityChangeTest.php
index 9deb658..463b5ba 100644
--- a/lib/tests/phpunit/Changes/EntityChangeTest.php
+++ b/lib/tests/phpunit/Changes/EntityChangeTest.php
@@ -280,6 +280,13 @@
                $this->assertSame( $expected, $change->getSerializedInfo() );
        }
 
+       public function testSerializeSkips() {
+               $info = [ 'field' => 'value', 'evil' => 'nope!' ];
+               $expected = '{"field":"value"}';
+               $change = new EntityChange( [ 'info' => $info ] );
+               $this->assertSame( $expected, $change->getSerializedInfo( [ 
'evil' ] ) );
+       }
+
        public function testDoesNotSerializeObjects() {
                $info = [ 'array' => [ 'object' => new EntityChange() ] ];
                $change = new EntityChange( [ 'info' => $info ] );
diff --git a/lib/tests/phpunit/Changes/TestChanges.php 
b/lib/tests/phpunit/Changes/TestChanges.php
index 9cfabdc..e876cc0 100644
--- a/lib/tests/phpunit/Changes/TestChanges.php
+++ b/lib/tests/phpunit/Changes/TestChanges.php
@@ -3,6 +3,7 @@
 namespace Wikibase\Lib\Tests\Changes;
 
 use Wikibase\ChangeRow;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\Property;
@@ -37,6 +38,7 @@
 
                $factory = new EntityChangeFactory(
                        new EntityDiffer(),
+                       new BasicEntityIdParser(),
                        $changeClasses
                );
 
diff --git a/lib/tests/phpunit/Store/Sql/EntityChangeLookupTest.php 
b/lib/tests/phpunit/Store/Sql/EntityChangeLookupTest.php
index 23d691f..5c1fb36 100644
--- a/lib/tests/phpunit/Store/Sql/EntityChangeLookupTest.php
+++ b/lib/tests/phpunit/Store/Sql/EntityChangeLookupTest.php
@@ -2,6 +2,7 @@
 
 namespace Wikibase\Lib\Tests\Store\Sql;
 
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
 use Wikibase\DataModel\Entity\ItemIdParser;
 use Wikibase\DataModel\Services\Diff\EntityDiffer;
 use Wikibase\EntityChange;
@@ -24,7 +25,11 @@
 
        private function newEntityChangeLookup( $wiki ) {
                return new EntityChangeLookup(
-                       new EntityChangeFactory( new EntityDiffer(), [ 'item' 
=> EntityChange::class ] ),
+                       new EntityChangeFactory(
+                               new EntityDiffer(),
+                               new BasicEntityIdParser(),
+                               [ 'item' => EntityChange::class ]
+                       ),
                        new ItemIdParser(),
                        $wiki
                );
diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php
index 025bd48..0da1df2 100644
--- a/repo/includes/WikibaseRepo.php
+++ b/repo/includes/WikibaseRepo.php
@@ -639,6 +639,7 @@
 
                return new EntityChangeFactory(
                        $this->getEntityDiffer(),
+                       $this->getEntityIdParser(),
                        $changeClasses
                );
        }

-- 
To view, visit https://gerrit.wikimedia.org/r/369881
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Iaa98722779507bb57d449649f1147314da685312
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Catrope <r...@wikimedia.org>
Gerrit-Reviewer: Krinkle <krinklem...@gmail.com>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.maet...@wikimedia.de>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to