Completed
Push — master ( 259aca...c91093 )
by
unknown
06:39
created

provideTermTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare( strict_types=1 );
4
namespace Wikibase\Client\Tests\Unit\DataAccess\Scribunto;
5
6
use Language;
7
use MediaWiki\Languages\LanguageFactory;
8
use MediaWiki\MediaWikiServices;
9
use PHPUnit\Framework\TestCase;
10
use Wikibase\Client\DataAccess\Scribunto\CachingFallbackBasedTermLookup;
11
use Wikibase\DataModel\Entity\ItemId;
12
use Wikibase\DataModel\Term\TermFallback;
13
use Wikibase\DataModel\Term\TermTypes;
14
use Wikibase\Lib\ContentLanguages;
15
use Wikibase\Lib\FormatterCache\TermFallbackCacheFacade;
16
use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookup;
17
use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
18
use Wikibase\Lib\Store\RedirectResolvingLatestRevisionLookup;
19
20
/**
21
 * @covers \Wikibase\Client\DataAccess\Scribunto\CachingFallbackBasedTermLookup
22
 *
23
 * @group Wikibase
24
 * @group WikibaseClient
25
 * @group WikibaseScribunto
26
 *
27
 * @license GPL-2.0-or-later
28
 */
29
class CachingFallbackBasedTermLookupTest extends TestCase {
30
31
	public const ITEM_Q1_REVISION = 1;
32
33
	private $termFallbackCache;
34
	private $revisionLookup;
35
	private $lookupFactory;
36
	private $factoryReturnLookup;
37
	/**
38
	 * @var LanguageFactory|\PHPUnit\Framework\MockObject\MockObject
39
	 */
40
	private $languageFactory;
41
	/**
42
	 * @var \PHPUnit\Framework\MockObject\MockObject|ContentLanguages
43
	 */
44
	private $contentLanguages;
45
46
	public function setUp(): void {
47
		parent::setUp();
48
		$this->termFallbackCache = $this->createMock( TermFallbackCacheFacade::class );
49
		$this->revisionLookup = $this->createMock( RedirectResolvingLatestRevisionLookup::class );
50
		$this->lookupFactory = $this->createMock( LanguageFallbackLabelDescriptionLookupFactory::class );
51
		$this->factoryReturnLookup = $this->createMock( LanguageFallbackLabelDescriptionLookup::class );
52
		$this->languageFactory = $this->createMock( LanguageFactory::class );
53
		$this->contentLanguages = $this->createMock( ContentLanguages::class );
54
55
		$englishLanguage = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
56
		$this->languageFactory->expects( $this->any() )
57
			->method( 'getLanguage' )
58
			->with( 'en' )
59
			->willReturn( $englishLanguage );
60
	}
61
62
	public function getLookup(): CachingFallbackBasedTermLookup {
63
		return new CachingFallbackBasedTermLookup(
64
			$this->termFallbackCache,
65
			$this->revisionLookup,
66
			$this->lookupFactory,
67
			$this->languageFactory,
68
			$this->contentLanguages
69
		);
70
	}
71
72
	public function testGetLabelChecksTheCacheAndUsesIfValueThere() {
73
		$term = 'cat';
74
		$itemId = new ItemId( 'Q1' );
75
76
		$this->mockHasContentLanguage( true );
77
		$this->mockCacheWithContent( $term, $itemId );
78
79
		$lookup = $this->getLookup();
80
		$this->assertEquals(
81
			$term,
82
			$lookup->getLabel( $itemId, 'en' )
83
		);
84
	}
85
86
	public function testGetDescriptionChecksTheCacheAndUsesIfValueThere() {
87
		$term = 'cat';
88
		$itemId = new ItemId( 'Q1' );
89
90
		$this->mockHasContentLanguage( true );
91
		$this->mockCacheWithContent( $term, $itemId );
92
93
		$lookup = $this->getLookup();
94
		$this->assertEquals(
95
			$term,
96
			$lookup->getDescription( $itemId, 'en' )
97
		);
98
	}
99
100
	private function getTermFallback( $term, $requestLanguageCode, $actualLanguageCode = null ): ?TermFallback {
101
		if ( $term === null ) {
102
			return null;
103
		}
104
105
		return new TermFallback(
106
			$requestLanguageCode,
107
			$term,
108
			$actualLanguageCode ? $actualLanguageCode : $requestLanguageCode,
109
			null
110
		);
111
	}
112
113
	public function nonCachingLookupProvider() {
114
		$englishLanguage = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
115
116
		$englishCat = $this->getTermFallback( 'cat', 'en' );
117
		$swedishEnglishCat = $this->getTermFallback( 'katt', 'en', 'sv' );
118
119
		$dataset = [
120
			[ // 'finds term in requested language, caches it and returns it'
121
				$englishCat,
122
				$englishLanguage,
123
				'cat'
124
			],
125
			[ // 'finds null and caches it, returns null'
126
				null,
127
				$englishLanguage,
128
				null
129
			],
130
			[ // finds term in different language and caches it and returns null
131
				$swedishEnglishCat,
132
				$englishLanguage,
133
				null
134
			]
135
		];
136
137
		$datasetLabels = array_map( function( $testCase ) {
138
			$testCase[] = TermTypes::TYPE_LABEL;
139
			return $testCase;
140
		}, $dataset );
141
142
		$datasetDescriptions = array_map( function( $testCase ) {
143
			$testCase[] = TermTypes::TYPE_DESCRIPTION;
144
			return $testCase;
145
		}, $dataset );
146
147
		return array_merge( $datasetLabels, $datasetDescriptions );
148
	}
149
150
	/**
151
	 * @dataProvider nonCachingLookupProvider
152
	 *
153
	 * @param TermFallback|null $termFallback
154
	 * @param Language $language
155
	 * @param string|null $expectedTerm
156
	 * @param string $termType
157
	 */
158
	public function testGetTermUsesInternalLookupWithCacheMiss(
159
		?TermFallback $termFallback,
160
		Language $language,
161
		$expectedTerm,
162
		string $termType
163
	) {
164
		$itemId = new ItemId( 'Q1' );
165
		$methodName = $termType === TermTypes::TYPE_LABEL ? 'getLabel' : 'getDescription';
166
167
		$this->mockHasContentLanguage( true );
168
169
		// no cache hit
170
		$this->mockCacheEmpty( $itemId );
171
172
		// should return a fallback
173
		$this->mockInternalLookupWithContent( $itemId, $termFallback, $methodName );
174
175
		// should store this in the cache
176
		$this->mockCacheSetExpectation(
177
			$termFallback,
178
			$itemId,
179
			self::ITEM_Q1_REVISION,
180
			$language->getCode(),
181
			$termType
182
		);
183
184
		$lookup = $this->getLookup();
185
186
		$this->assertEquals(
187
			$expectedTerm,
188
			$lookup->$methodName( $itemId, $language->getCode() )
189
		);
190
	}
191
192
	public function testDoesNotPolluteCacheWithNonExistingLanguages() {
193
		$itemId = new ItemId( 'Q1' );
194
195
		// called with invalid language
196
		$this->mockHasContentLanguage( false );
197
198
		// found by revisionLookup
199
		$this->mockRevisionLookup( $itemId );
200
201
		// but never calls cache
202
		$this->termFallbackCache->expects( $this->never() )->method( 'get' );
203
204
		$lookup = $this->getLookup();
205
		$result = $lookup->getLabel( $itemId, 'some weird thing' );
206
		$this->assertNull( $result );
207
	}
208
209
	public function testShouldNotCreateMultipleLookupsForSameLanguage() {
210
		$itemOneId = new ItemId( 'Q1' );
211
		$itemTwoId = new ItemId( 'Q2' );
212
213
		$language = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
214
		$termFallbackOne = $this->getTermFallback( 'cat', 'en' );
215
		$termFallbackTwo = $this->getTermFallback( 'hat', 'en' );
216
217
		$this->mockHasContentLanguage( true );
218
219
		$this->revisionLookup
220
			->method( 'lookupLatestRevisionResolvingRedirect' )
221
			->withConsecutive( [ $itemOneId ], [ $itemTwoId ] )
222
			->willReturnOnConsecutiveCalls(
223
				[ 1, $itemOneId ],
224
				[ 2, $itemTwoId ]
225
			);
226
227
		$this->termFallbackCache
228
			->expects( $this->atLeastOnce() )
229
			->method( 'get' )
230
			->willReturn( TermFallbackCacheFacade::NO_VALUE );
231
232
		$this->factoryReturnLookup->method( 'getLabel' )
233
			->withConsecutive( [ $itemOneId ], [ $itemTwoId ] )
234
			->willReturnOnConsecutiveCalls( $termFallbackOne, $termFallbackTwo );
235
236
		$this->lookupFactory->expects( $this->once() )
237
			->method( 'newLabelDescriptionLookup' )
238
			->with( $this->anything() )
239
			->willReturn( $this->factoryReturnLookup );
240
241
		$lookup = $this->getLookup();
242
243
		$this->assertEquals(
244
			'cat',
245
			$lookup->getLabel( $itemOneId, $language->getCode() )
246
		);
247
248
		$this->assertEquals(
249
			'hat',
250
			$lookup->getLabel( $itemTwoId, $language->getCode() )
251
		);
252
	}
253
254
	/** @dataProvider provideTermTypes */
255
	public function testReturnsExistingTermsForMultipleLanguageCodes( string $termType ) {
256
		$getOne = $termType === TermTypes::TYPE_LABEL ? 'getLabel' : 'getDescription';
257
		$getMultiple = $termType === TermTypes::TYPE_LABEL ? 'getLabels' : 'getDescriptions';
258
259
		$itemId = new ItemId( 'Q1' );
260
		$enTerm = $this->getTermFallback( 'cat', 'en' );
261
262
		$this->contentLanguages->method( 'hasLanguage' )
0 ignored issues
show
Bug introduced by
The method method() does not seem to exist on object<Wikibase\Lib\ContentLanguages>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
263
			->willReturnCallback( function ( $languageCode ) {
264
				return $languageCode === 'en';
265
			} );
266
		$this->mockHasContentLanguage( true );
267
		$this->mockCacheEmpty( $itemId );
268
		$this->mockInternalLookupWithContent( $itemId, $enTerm, $getOne );
269
270
		$lookup = $this->getLookup();
271
272
		$this->assertEquals(
273
			[ 'en' => 'cat' ],
274
			$lookup->$getMultiple( $itemId, [ 'en', 'sv' ] )
275
		);
276
	}
277
278
	public function provideTermTypes() {
279
		yield [ TermTypes::TYPE_LABEL ];
280
		yield [ TermTypes::TYPE_DESCRIPTION ];
281
	}
282
283
	private function mockCacheWithContent( string $term, $itemId ): void {
284
		$termFallback = new TermFallback( 'en', $term, 'en', 'en' );
285
		$this->revisionLookup->method( 'lookupLatestRevisionResolvingRedirect' )->willReturn( [ 1, $itemId ] );
286
		$this->termFallbackCache->expects( $this->atLeastOnce() )->method( 'get' )->willReturn( $termFallback );
287
	}
288
289
	private function mockHasContentLanguage( bool $return ) {
290
		$this->contentLanguages
0 ignored issues
show
Bug introduced by
The method expects() does not seem to exist on object<Wikibase\Lib\ContentLanguages>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
291
			->expects( $this->any() )
292
			->method( 'hasLanguage' )
293
			->with( $this->anything() )
294
			->willReturn( $return );
295
	}
296
297
	private function mockRevisionLookup( $itemId ) {
298
		$this->revisionLookup
299
			->method( 'lookupLatestRevisionResolvingRedirect' )
300
			->willReturn( [ self::ITEM_Q1_REVISION, $itemId ] );
301
	}
302
303
	private function mockCacheEmpty( $itemId ): void {
304
		$this->mockRevisionLookup( $itemId );
305
306
		$this->termFallbackCache
307
			->expects( $this->atLeastOnce() )
308
			->method( 'get' )
309
			->willReturn( TermFallbackCacheFacade::NO_VALUE );
310
	}
311
312
	private function mockCacheSetExpectation(
313
		$termFallback,
314
		$targetEntityId,
315
		$revisionId,
316
		$languageCode,
317
		$termType = TermTypes::TYPE_LABEL
318
	): void {
319
320
		$this->termFallbackCache
321
			->expects( $this->once() )
322
			->method( 'set' )
323
			->with( $termFallback, $targetEntityId, $revisionId, $languageCode, $termType );
324
	}
325
326
	private function mockInternalLookupWithContent(
327
		$itemId,
328
		?TermFallback $termFallback,
329
		string $methodName
330
	): void {
331
332
		$this->factoryReturnLookup->expects( $this->once() )
333
			->method( $methodName )
334
			->with( $itemId )
335
			->willReturn( $termFallback );
336
337
		$this->lookupFactory->expects( $this->once() )
338
			->method( 'newLabelDescriptionLookup' )
339
			->with( $this->anything() )
340
			->willReturn( $this->factoryReturnLookup );
341
	}
342
}
343