Completed
Push — master ( 0a57e3...259aca )
by
unknown
07:19 queued 10s
created

mockCacheWithContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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