1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare( strict_types = 1 ); |
4
|
|
|
namespace Wikibase\Repo\Tests\FederatedProperties\ParserOutput; |
5
|
|
|
|
6
|
|
|
use Language; |
7
|
|
|
use MediaWikiIntegrationTestCase; |
8
|
|
|
use Psr\SimpleCache\CacheInterface; |
9
|
|
|
use RepoGroup; |
10
|
|
|
use Title; |
11
|
|
|
use Wikibase\DataModel\Entity\EntityId; |
12
|
|
|
use Wikibase\DataModel\Entity\Item; |
13
|
|
|
use Wikibase\DataModel\Entity\ItemId; |
14
|
|
|
use Wikibase\DataModel\Entity\PropertyId; |
15
|
|
|
use Wikibase\DataModel\Services\Entity\PropertyDataTypeMatcher; |
16
|
|
|
use Wikibase\DataModel\Services\EntityId\SuffixEntityIdParser; |
17
|
|
|
use Wikibase\DataModel\Services\Lookup\InMemoryDataTypeLookup; |
18
|
|
|
use Wikibase\DataModel\Snak\PropertyNoValueSnak; |
19
|
|
|
use Wikibase\DataModel\Statement\Statement; |
20
|
|
|
use Wikibase\Lib\Store\EntityRevision; |
21
|
|
|
use Wikibase\Lib\Store\EntityTitleLookup; |
22
|
|
|
use Wikibase\Lib\TermLanguageFallbackChain; |
23
|
|
|
use Wikibase\Repo\EntityReferenceExtractors\EntityReferenceExtractorCollection; |
24
|
|
|
use Wikibase\Repo\EntityReferenceExtractors\EntityReferenceExtractorDelegator; |
25
|
|
|
use Wikibase\Repo\EntityReferenceExtractors\SiteLinkBadgeItemReferenceExtractor; |
26
|
|
|
use Wikibase\Repo\EntityReferenceExtractors\StatementEntityReferenceExtractor; |
27
|
|
|
use Wikibase\Repo\FederatedProperties\ApiEntityLookup; |
28
|
|
|
use Wikibase\Repo\FederatedProperties\ApiRequestExecutionException; |
29
|
|
|
use Wikibase\Repo\FederatedProperties\FederatedPropertiesEntityParserOutputGenerator; |
30
|
|
|
use Wikibase\Repo\FederatedProperties\FederatedPropertiesError; |
31
|
|
|
use Wikibase\Repo\LinkedData\EntityDataFormatProvider; |
32
|
|
|
use Wikibase\Repo\ParserOutput\CompositeStatementDataUpdater; |
33
|
|
|
use Wikibase\Repo\ParserOutput\DispatchingEntityMetaTagsCreatorFactory; |
34
|
|
|
use Wikibase\Repo\ParserOutput\DispatchingEntityViewFactory; |
35
|
|
|
use Wikibase\Repo\ParserOutput\ExternalLinksDataUpdater; |
36
|
|
|
use Wikibase\Repo\ParserOutput\FullEntityParserOutputGenerator; |
37
|
|
|
use Wikibase\Repo\ParserOutput\ImageLinksDataUpdater; |
38
|
|
|
use Wikibase\Repo\ParserOutput\ItemParserOutputUpdater; |
39
|
|
|
use Wikibase\Repo\ParserOutput\ParserOutputJsConfigBuilder; |
40
|
|
|
use Wikibase\Repo\ParserOutput\ReferencedEntitiesDataUpdater; |
41
|
|
|
use Wikibase\View\EntityDocumentView; |
42
|
|
|
use Wikibase\View\EntityMetaTagsCreator; |
43
|
|
|
use Wikibase\View\LocalizedTextProvider; |
44
|
|
|
use Wikibase\View\Template\TemplateFactory; |
45
|
|
|
use Wikibase\View\ViewContent; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @covers \Wikibase\Repo\FederatedProperties\FederatedPropertiesEntityParserOutputGenerator |
49
|
|
|
* |
50
|
|
|
* @group Wikibase |
51
|
|
|
* |
52
|
|
|
* @license GPL-2.0-or-later |
53
|
|
|
*/ |
54
|
|
|
class FederatedPropertiesEntityParserOutputGeneratorTest extends MediaWikiIntegrationTestCase { |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var DispatchingEntityViewFactory |
58
|
|
|
*/ |
59
|
|
|
private $entityViewFactory; |
60
|
|
|
|
61
|
|
|
protected function setUp(): void { |
62
|
|
|
parent::setUp(); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
public function testShouldPrefetchFederatedProperties() { |
66
|
|
|
$labelLanguage = 'en'; |
67
|
|
|
$userLanguage = 'en'; |
68
|
|
|
|
69
|
|
|
$item = new Item( new ItemId( 'Q7799929' ) ); |
70
|
|
|
$item->setLabel( $labelLanguage, 'kitten item' ); |
71
|
|
|
|
72
|
|
|
$statementWithReference = new Statement( new PropertyNoValueSnak( 1 ) ); |
73
|
|
|
$statementWithReference->addNewReference( new PropertyNoValueSnak( 4 ) ); |
74
|
|
|
|
75
|
|
|
$item->getStatements()->addStatement( $statementWithReference ); |
76
|
|
|
$item->getStatements()->addStatement( new Statement( new PropertyNoValueSnak( 2 ) ) ); |
77
|
|
|
$item->getStatements()->addStatement( new Statement( new PropertyNoValueSnak( 3 ) ) ); |
78
|
|
|
$item->getStatements()->addStatement( new Statement( new PropertyNoValueSnak( 3 ) ) ); |
79
|
|
|
|
80
|
|
|
$expectedIds = [ |
81
|
|
|
new PropertyId( "P1" ), |
82
|
|
|
new PropertyId( "P2" ), |
83
|
|
|
new PropertyId( "P3" ), |
84
|
|
|
new PropertyId( "P4" ), |
85
|
|
|
]; |
86
|
|
|
|
87
|
|
|
$this->entityViewFactory = $this->mockEntityViewFactory( false ); |
88
|
|
|
|
89
|
|
|
$prefetchingTermLookup = $this->createMock( ApiEntityLookup::class ); |
90
|
|
|
$prefetchingTermLookup->expects( $this->once() ) |
91
|
|
|
->method( 'fetchEntities' ) |
92
|
|
|
->willReturnCallback( $this->getPrefetchTermsCallback( |
93
|
|
|
$expectedIds |
94
|
|
|
) ); |
95
|
|
|
|
96
|
|
|
$innerPog = $this->getFullGeneratorMock(); |
97
|
|
|
$entityParserOutputGenerator = $this->newEntityParserOutputGenerator( $prefetchingTermLookup, $innerPog, $userLanguage ); |
98
|
|
|
|
99
|
|
|
$entityParserOutputGenerator->getParserOutput( new EntityRevision( $item, 4711 ), false ); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function testShouldNotCallPrefetchIfNoProperties() { |
103
|
|
|
$labelLanguage = 'en'; |
104
|
|
|
$userLanguage = 'en'; |
105
|
|
|
|
106
|
|
|
$item = new Item( new ItemId( 'Q7799929' ) ); |
107
|
|
|
$item->setLabel( $labelLanguage, 'kitten item' ); |
108
|
|
|
|
109
|
|
|
$prefetchingTermLookup = $this->createMock( ApiEntityLookup::class ); |
110
|
|
|
$prefetchingTermLookup->expects( $this->never() ) |
111
|
|
|
->method( 'fetchEntities' ); |
112
|
|
|
|
113
|
|
|
$this->entityViewFactory = $this->mockEntityViewFactory( false ); |
114
|
|
|
|
115
|
|
|
$innerPog = $this->getFullGeneratorMock(); |
116
|
|
|
$entityParserOutputGenerator = $this->newEntityParserOutputGenerator( $prefetchingTermLookup, $innerPog, $userLanguage ); |
117
|
|
|
|
118
|
|
|
$entityParserOutputGenerator->getParserOutput( new EntityRevision( $item, 4711 ), false ); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @dataProvider errorPageProvider |
123
|
|
|
*/ |
124
|
|
|
public function testGetParserOutputHandlesFederatedApiException( $labelLanguage, $userLanguage ) { |
125
|
|
|
|
126
|
|
|
$item = new Item( new ItemId( 'Q7799929' ) ); |
127
|
|
|
$item->setLabel( $labelLanguage, 'kitten item' ); |
128
|
|
|
|
129
|
|
|
$prefetchingTermLookup = $this->createMock( ApiEntityLookup::class ); |
130
|
|
|
$prefetchingTermLookup->expects( $this->never() ) |
131
|
|
|
->method( 'fetchEntities' ); |
132
|
|
|
|
133
|
|
|
$updater = $this->createMock( ItemParserOutputUpdater::class ); |
134
|
|
|
|
135
|
|
|
$this->entityViewFactory = $this->mockEntityViewFactory( false ); |
136
|
|
|
|
137
|
|
|
$entityParserOutputGenerator = $this->newEntityParserOutputGenerator( |
138
|
|
|
$prefetchingTermLookup, |
139
|
|
|
$this->getFullGeneratorMock( [ $updater ], $userLanguage ), |
140
|
|
|
$userLanguage |
141
|
|
|
); |
142
|
|
|
$updater->method( 'updateParserOutput' ) |
143
|
|
|
->willThrowException( new ApiRequestExecutionException() ); |
144
|
|
|
|
145
|
|
|
// T254888 Exception will be handled and show an error page. |
146
|
|
|
$this->expectException( FederatedPropertiesError::class ); |
147
|
|
|
|
148
|
|
|
$entityParserOutputGenerator->getParserOutput( new EntityRevision( $item, 4711 ), false ); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
public function testParserOutputLoadModule() { |
152
|
|
|
$item = new Item( new ItemId( 'Q7799929' ) ); |
153
|
|
|
$item->setLabel( 'en', 'kitten item' ); |
154
|
|
|
$entityRevision = new EntityRevision( $item, 4711 ); |
155
|
|
|
|
156
|
|
|
$prefetchingTermLookup = $this->createMock( ApiEntityLookup::class ); |
157
|
|
|
$prefetchingTermLookup->expects( $this->never() ) |
158
|
|
|
->method( 'fetchEntities' ); |
159
|
|
|
|
160
|
|
|
$updater = $this->createMock( ItemParserOutputUpdater::class ); |
161
|
|
|
|
162
|
|
|
$this->entityViewFactory = $this->mockEntityViewFactory( true ); |
163
|
|
|
|
164
|
|
|
$parserOutputGen = $this->newEntityParserOutputGenerator( |
165
|
|
|
$prefetchingTermLookup, |
166
|
|
|
$this->getFullGeneratorMock( [ $updater ], 'en' ), |
167
|
|
|
'en' |
168
|
|
|
); |
169
|
|
|
|
170
|
|
|
$parserOutput = $parserOutputGen->getParserOutput( $entityRevision ); |
171
|
|
|
$resourceLoaderModules = $parserOutput->getModules(); |
172
|
|
|
$this->assertContains( 'wikibase.federatedPropertiesEditRequestFailureNotice', $resourceLoaderModules ); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
protected function getPrefetchTermsCallback( $expectedIds ) { |
176
|
|
|
$prefetchTerms = function ( |
177
|
|
|
array $entityIds, |
178
|
|
|
array $termTypes = null, |
|
|
|
|
179
|
|
|
array $languageCodes = null |
|
|
|
|
180
|
|
|
) use ( |
181
|
|
|
$expectedIds |
182
|
|
|
) { |
183
|
|
|
$expectedIdStrings = array_map( function( EntityId $id ) { |
184
|
|
|
return $id->getSerialization(); |
185
|
|
|
}, $expectedIds ); |
186
|
|
|
$entityIdStrings = array_map( function( EntityId $id ) { |
187
|
|
|
return $id->getSerialization(); |
188
|
|
|
}, $entityIds ); |
189
|
|
|
|
190
|
|
|
sort( $expectedIdStrings ); |
191
|
|
|
sort( $entityIdStrings ); |
192
|
|
|
|
193
|
|
|
$this->assertEquals( $expectedIdStrings, $entityIdStrings ); |
194
|
|
|
}; |
195
|
|
|
return $prefetchTerms; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
private function newEntityParserOutputGenerator( $prefetchingTermLookup, $fullGenerator, $languageCode = 'en' ) { |
199
|
|
|
return new FederatedPropertiesEntityParserOutputGenerator( |
200
|
|
|
$fullGenerator, |
201
|
|
|
Language::factory( $languageCode ), |
202
|
|
|
$prefetchingTermLookup |
203
|
|
|
); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
private function getFullGeneratorMock( $dataUpdaters = null, $language = 'en', $title = null, $description = null ) { |
207
|
|
|
$entityDataFormatProvider = new EntityDataFormatProvider(); |
208
|
|
|
$entityDataFormatProvider->setAllowedFormats( [ 'json', 'ntriples' ] ); |
209
|
|
|
|
210
|
|
|
$entityTitleLookup = $this->getEntityTitleLookupMock(); |
211
|
|
|
|
212
|
|
|
$propertyDataTypeMatcher = new PropertyDataTypeMatcher( $this->getPropertyDataTypeLookup() ); |
213
|
|
|
$repoGroup = $this->createMock( RepoGroup::class ); |
214
|
|
|
|
215
|
|
|
$statementUpdater = new CompositeStatementDataUpdater( |
216
|
|
|
new ExternalLinksDataUpdater( $propertyDataTypeMatcher ), |
217
|
|
|
new ImageLinksDataUpdater( $propertyDataTypeMatcher, $repoGroup ) |
218
|
|
|
); |
219
|
|
|
|
220
|
|
|
if ( $dataUpdaters === null ) { |
221
|
|
|
$dataUpdaters = [ |
222
|
|
|
new ItemParserOutputUpdater( $statementUpdater ), |
223
|
|
|
new ReferencedEntitiesDataUpdater( |
224
|
|
|
$this->newEntityReferenceExtractor(), |
225
|
|
|
$entityTitleLookup |
226
|
|
|
) |
227
|
|
|
]; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$cache = $this->createMock( CacheInterface::class ); |
231
|
|
|
$cache->method( 'get' ) |
232
|
|
|
->willReturn( false ); |
233
|
|
|
|
234
|
|
|
return new FullEntityParserOutputGenerator( |
235
|
|
|
$this->entityViewFactory, |
236
|
|
|
$this->getEntityMetaTagsFactory( $title, $description ), |
237
|
|
|
$this->getConfigBuilderMock(), |
238
|
|
|
$entityTitleLookup, |
239
|
|
|
$this->newLanguageFallbackChain(), |
240
|
|
|
TemplateFactory::getDefaultInstance(), |
241
|
|
|
$this->createMock( LocalizedTextProvider::class ), |
242
|
|
|
$entityDataFormatProvider, |
243
|
|
|
$dataUpdaters, |
244
|
|
|
Language::factory( $language ) |
245
|
|
|
); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
public function errorPageProvider() { |
249
|
|
|
return [ |
250
|
|
|
[ 'en', 'en' ], |
251
|
|
|
[ 'de', 'en' ], |
252
|
|
|
]; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @return TermLanguageFallbackChain |
257
|
|
|
*/ |
258
|
|
|
private function newLanguageFallbackChain() { |
259
|
|
|
$fallbackChain = $this->getMockBuilder( TermLanguageFallbackChain::class ) |
260
|
|
|
->disableOriginalConstructor() |
261
|
|
|
->getMock(); |
262
|
|
|
|
263
|
|
|
$fallbackChain->expects( $this->any() ) |
264
|
|
|
->method( 'extractPreferredValue' ) |
265
|
|
|
->will( $this->returnCallback( function( $labels ) { |
266
|
|
|
if ( array_key_exists( 'en', $labels ) ) { |
267
|
|
|
return [ |
268
|
|
|
'value' => $labels['en'], |
269
|
|
|
'language' => 'en', |
270
|
|
|
'source' => 'en' |
271
|
|
|
]; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
return null; |
275
|
|
|
} ) ); |
276
|
|
|
|
277
|
|
|
$fallbackChain->method( 'getFetchLanguageCodes' ) |
278
|
|
|
->willReturn( [ 'en' ] ); |
279
|
|
|
|
280
|
|
|
return $fallbackChain; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @param bool $createView |
285
|
|
|
* |
286
|
|
|
* @return DispatchingEntityViewFactory |
287
|
|
|
*/ |
288
|
|
|
private function mockEntityViewFactory( $createView ) { |
289
|
|
|
$entityViewFactory = $this->getMockBuilder( DispatchingEntityViewFactory::class ) |
290
|
|
|
->disableOriginalConstructor() |
291
|
|
|
->getMock(); |
292
|
|
|
|
293
|
|
|
$entityViewFactory->expects( $createView ? $this->once() : $this->never() ) |
294
|
|
|
->method( 'newEntityView' ) |
295
|
|
|
->will( $this->returnValue( $this->getEntityView() ) ); |
296
|
|
|
|
297
|
|
|
return $entityViewFactory; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* @return EntityDocumentView |
302
|
|
|
*/ |
303
|
|
|
private function getEntityView() { |
304
|
|
|
$entityView = $this->getMockBuilder( EntityDocumentView::class ) |
305
|
|
|
->setMethods( [ |
306
|
|
|
'getTitleHtml', |
307
|
|
|
'getContent' |
308
|
|
|
] ) |
309
|
|
|
->disableOriginalConstructor() |
310
|
|
|
->getMockForAbstractClass(); |
311
|
|
|
|
312
|
|
|
$entityView->expects( $this->any() ) |
313
|
|
|
->method( 'getTitleHtml' ) |
314
|
|
|
->will( $this->returnValue( '<TITLE>' ) ); |
315
|
|
|
|
316
|
|
|
$viewContent = new ViewContent( |
317
|
|
|
'<HTML>', |
318
|
|
|
[] |
319
|
|
|
); |
320
|
|
|
|
321
|
|
|
$entityView->expects( $this->any() ) |
322
|
|
|
->method( 'getContent' ) |
323
|
|
|
->will( $this->returnValue( $viewContent ) ); |
324
|
|
|
|
325
|
|
|
return $entityView; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @return DispatchingEntityMetaTagsCreatorFactory |
330
|
|
|
*/ |
331
|
|
|
private function getEntityMetaTagsFactory( $title = null, $description = null ) { |
332
|
|
|
$entityMetaTagsCreatorFactory = $this->createMock( DispatchingEntityMetaTagsCreatorFactory::class ); |
333
|
|
|
|
334
|
|
|
$entityMetaTagsCreatorFactory |
335
|
|
|
->method( 'newEntityMetaTags' ) |
336
|
|
|
->will( $this->returnValue( $this->getMetaTags( $title, $description ) ) ); |
337
|
|
|
|
338
|
|
|
return $entityMetaTagsCreatorFactory; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* @return EntityMetaTags |
343
|
|
|
*/ |
344
|
|
|
private function getMetaTags( $title, $description ) { |
345
|
|
|
$entityMetaTagsCreator = $this->getMockBuilder( EntityMetaTagsCreator::class ) |
346
|
|
|
->setMethods( [ |
347
|
|
|
'getMetaTags', |
348
|
|
|
] ) |
349
|
|
|
->disableOriginalConstructor() |
350
|
|
|
->getMockForAbstractClass(); |
351
|
|
|
|
352
|
|
|
$tags = []; |
353
|
|
|
|
354
|
|
|
$tags[ 'title' ] = $title; |
355
|
|
|
|
356
|
|
|
if ( $description !== null ) { |
357
|
|
|
$tags[ 'description' ] = $description; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
$entityMetaTagsCreator->expects( $this->any() ) |
361
|
|
|
->method( 'getMetaTags' ) |
362
|
|
|
->will( $this->returnValue( $tags ) ); |
363
|
|
|
|
364
|
|
|
return $entityMetaTagsCreator; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* @return ParserOutputJsConfigBuilder |
369
|
|
|
*/ |
370
|
|
|
private function getConfigBuilderMock() { |
371
|
|
|
$configBuilder = $this->getMockBuilder( ParserOutputJsConfigBuilder::class ) |
372
|
|
|
->disableOriginalConstructor() |
373
|
|
|
->getMock(); |
374
|
|
|
|
375
|
|
|
$configBuilder->expects( $this->any() ) |
376
|
|
|
->method( 'build' ) |
377
|
|
|
->will( $this->returnValue( [ '<JS>' ] ) ); |
378
|
|
|
|
379
|
|
|
return $configBuilder; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* @return EntityTitleLookup |
384
|
|
|
*/ |
385
|
|
|
private function getEntityTitleLookupMock() { |
386
|
|
|
$entityTitleLookup = $this->createMock( EntityTitleLookup::class ); |
387
|
|
|
|
388
|
|
|
$entityTitleLookup->expects( $this->any() ) |
389
|
|
|
->method( 'getTitleForId' ) |
390
|
|
|
->will( $this->returnCallback( function( EntityId $id ) { |
391
|
|
|
return Title::makeTitle( |
392
|
|
|
NS_MAIN, |
393
|
|
|
$id->getEntityType() . ':' . $id->getSerialization() |
394
|
|
|
); |
395
|
|
|
} ) ); |
396
|
|
|
|
397
|
|
|
return $entityTitleLookup; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
private function getPropertyDataTypeLookup() { |
401
|
|
|
$dataTypeLookup = new InMemoryDataTypeLookup(); |
402
|
|
|
|
403
|
|
|
$dataTypeLookup->setDataTypeForProperty( new PropertyId( 'P42' ), 'url' ); |
404
|
|
|
$dataTypeLookup->setDataTypeForProperty( new PropertyId( 'P10' ), 'commonsMedia' ); |
405
|
|
|
|
406
|
|
|
return $dataTypeLookup; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
private function newEntityReferenceExtractor() { |
410
|
|
|
return new EntityReferenceExtractorDelegator( [ |
411
|
|
|
'item' => function() { |
412
|
|
|
return new EntityReferenceExtractorCollection( [ |
413
|
|
|
new SiteLinkBadgeItemReferenceExtractor(), |
414
|
|
|
new StatementEntityReferenceExtractor( |
415
|
|
|
$this->getMockBuilder( SuffixEntityIdParser::class ) |
416
|
|
|
->disableOriginalConstructor() |
417
|
|
|
->getMock() |
418
|
|
|
) |
419
|
|
|
] ); |
420
|
|
|
} |
421
|
|
|
], $this->getMockBuilder( StatementEntityReferenceExtractor::class ) |
422
|
|
|
->disableOriginalConstructor() |
423
|
|
|
->getMock() ); |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
|
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.