Completed
Push — master ( cfd44f...5307df )
by
unknown
07:21
created

getEntityView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
cc 1
nc 1
nop 0
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,
0 ignored issues
show
Unused Code introduced by
The parameter $termTypes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
			array $languageCodes = null
0 ignored issues
show
Unused Code introduced by
The parameter $languageCodes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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