3 use MediaWiki\Cache\CacheKeyHelper
;
4 use MediaWiki\Cache\GenderCache
;
5 use MediaWiki\Cache\LinkBatch
;
6 use MediaWiki\Cache\LinkCache
;
7 use MediaWiki\Language\Language
;
8 use MediaWiki\Linker\LinksMigration
;
9 use MediaWiki\Linker\LinkTarget
;
10 use MediaWiki\Logger\LoggerFactory
;
11 use MediaWiki\Page\PageReference
;
12 use MediaWiki\Page\PageReferenceValue
;
13 use MediaWiki\Title\Title
;
14 use MediaWiki\Title\TitleFormatter
;
15 use MediaWiki\Title\TitleValue
;
16 use Wikimedia\Rdbms\IConnectionProvider
;
21 * @covers \MediaWiki\Cache\LinkBatch
23 class LinkBatchTest
extends MediaWikiIntegrationTestCase
{
26 * @covers \MediaWiki\Cache\LinkBatch::__construct()
27 * @covers \MediaWiki\Cache\LinkBatch::getSize()
28 * @covers \MediaWiki\Cache\LinkBatch::isEmpty()
30 public function testConstructEmptyWithServices() {
31 $batch = new LinkBatch(
33 $this->createMock( LinkCache
::class ),
34 $this->createMock( TitleFormatter
::class ),
35 $this->createMock( Language
::class ),
36 $this->createMock( GenderCache
::class ),
37 $this->createMock( IConnectionProvider
::class ),
38 $this->createMock( LinksMigration
::class ),
39 LoggerFactory
::getInstance( 'LinkBatch' )
42 $this->assertTrue( $batch->isEmpty() );
43 $this->assertSame( 0, $batch->getSize() );
47 * @covers \MediaWiki\Cache\LinkBatch::__construct()
48 * @covers \MediaWiki\Cache\LinkBatch::getSize()
49 * @covers \MediaWiki\Cache\LinkBatch::isEmpty()
51 public function testConstructWithServices() {
52 $batch = new LinkBatch(
54 new TitleValue( NS_MAIN
, 'Foo' ),
55 new TitleValue( NS_TALK
, 'Bar' ),
57 $this->createMock( LinkCache
::class ),
58 $this->createMock( TitleFormatter
::class ),
59 $this->createMock( Language
::class ),
60 $this->createMock( GenderCache
::class ),
61 $this->createMock( IConnectionProvider
::class ),
62 $this->createMock( LinksMigration
::class ),
63 LoggerFactory
::getInstance( 'LinkBatch' )
66 $this->assertFalse( $batch->isEmpty() );
67 $this->assertSame( 2, $batch->getSize() );
71 * @param iterable<LinkTarget>|iterable<PageReference> $objects
76 private function newLinkBatch( $objects = [] ) {
79 $this->createMock( LinkCache
::class ),
80 $this->createMock( TitleFormatter
::class ),
81 $this->createMock( Language
::class ),
82 $this->createMock( GenderCache
::class ),
83 $this->getServiceContainer()->getConnectionProvider(),
84 $this->getServiceContainer()->getLinksMigration(),
85 LoggerFactory
::getInstance( 'LinkBatch' )
90 * @covers \MediaWiki\Cache\LinkBatch::addObj()
91 * @covers \MediaWiki\Cache\LinkBatch::getSize()
93 public function testAddObj() {
94 $batch = $this->newLinkBatch(
96 new TitleValue( NS_MAIN
, 'Foo' ),
97 new PageReferenceValue( NS_USER
, 'Foo', PageReference
::LOCAL
),
101 $batch->addObj( new PageReferenceValue( NS_TALK
, 'Bar', PageReference
::LOCAL
) );
102 $batch->addObj( new TitleValue( NS_MAIN
, 'Foo' ) );
104 $this->assertSame( 3, $batch->getSize() );
105 $this->assertCount( 3, $batch->getPageIdentities() );
109 * @covers \MediaWiki\Cache\LinkBatch::add()
110 * @covers \MediaWiki\Cache\LinkBatch::getSize()
112 public function testAdd() {
113 $batch = $this->newLinkBatch(
115 new TitleValue( NS_MAIN
, 'Foo' )
119 $batch->add( NS_TALK
, 'Bar' );
120 $batch->add( NS_MAIN
, 'Foo' );
122 $this->assertSame( 2, $batch->getSize() );
123 $this->assertCount( 2, $batch->getPageIdentities() );
126 public function testExecute() {
127 $existing1 = $this->getExistingTestPage( __METHOD__
. '1' )->getTitle();
128 $existing2 = $this->getExistingTestPage( __METHOD__
. '2' )->getTitle();
129 $nonexisting1 = $this->getNonexistingTestPage( __METHOD__
. 'x' )->getTitle();
130 $nonexisting2 = $this->getNonexistingTestPage( __METHOD__
. 'y' )->getTitle();
132 $cache = $this->createMock( LinkCache
::class );
137 $cache->expects( $this->exactly( 2 ) )
138 ->method( 'addGoodLinkObjFromRow' )
139 ->willReturnCallback( static function ( TitleValue
$title, $row ) use ( &$good ) {
140 $good["$title"] = $title;
143 $cache->expects( $this->exactly( 2 ) )
144 ->method( 'addBadLinkObj' )
145 ->willReturnCallback( static function ( TitleValue
$title ) use ( &$bad ) {
146 $bad["$title"] = $title;
149 $services = $this->getServiceContainer();
151 $batch = new LinkBatch(
154 // TODO: This would be even better with mocked dependencies
155 $services->getTitleFormatter(),
156 $services->getContentLanguage(),
157 $services->getGenderCache(),
158 $services->getConnectionProvider(),
159 $services->getLinksMigration(),
160 LoggerFactory
::getInstance( 'LinkBatch' )
163 $batch->addObj( $existing1 );
164 $batch->addObj( $existing2 );
165 $batch->addObj( $nonexisting1 );
166 $batch->addObj( $nonexisting2 );
168 // Bad stuff, should be skipped!
169 $batch->add( NS_MAIN
, '_X' );
170 $batch->add( NS_MAIN
, 'X_' );
171 $batch->add( NS_MAIN
, '' );
175 $this->assertArrayHasKey( $existing1->getTitleValue()->__toString(), $good );
176 $this->assertArrayHasKey( $existing2->getTitleValue()->__toString(), $good );
178 $this->assertArrayHasKey( $nonexisting1->getTitleValue()->__toString(), $bad );
179 $this->assertArrayHasKey( $nonexisting2->getTitleValue()->__toString(), $bad );
181 $expected = array_map(
182 [ CacheKeyHelper
::class, 'getKeyForPage' ],
183 [ $existing1, $existing2, $nonexisting1, $nonexisting2 ]
187 [ CacheKeyHelper
::class, 'getKeyForPage' ],
188 $batch->getPageIdentities()
194 $this->assertEquals( $expected, $actual );
197 public function testDoGenderQueryWithEmptyLinkBatch() {
198 $batch = new LinkBatch(
200 $this->createMock( LinkCache
::class ),
201 $this->createMock( TitleFormatter
::class ),
202 $this->createNoOpMock( Language
::class ),
203 $this->createNoOpMock( GenderCache
::class ),
204 $this->createMock( IConnectionProvider
::class ),
205 $this->createMock( LinksMigration
::class ),
206 LoggerFactory
::getInstance( 'LinkBatch' )
209 $this->assertFalse( $batch->doGenderQuery() );
212 public function testDoGenderQueryWithLanguageWithoutGenderDistinction() {
213 $language = $this->createMock( Language
::class );
214 $language->method( 'needsGenderDistinction' )->willReturn( false );
216 $batch = new LinkBatch(
218 $this->createMock( LinkCache
::class ),
219 $this->createMock( TitleFormatter
::class ),
221 $this->createNoOpMock( GenderCache
::class ),
222 $this->createMock( IConnectionProvider
::class ),
223 $this->createMock( LinksMigration
::class ),
224 LoggerFactory
::getInstance( 'LinkBatch' )
227 new TitleValue( NS_MAIN
, 'Foo' )
230 $this->assertFalse( $batch->doGenderQuery() );
233 public function testDoGenderQueryWithLanguageWithGenderDistinction() {
234 $language = $this->createMock( Language
::class );
235 $language->method( 'needsGenderDistinction' )->willReturn( true );
237 $genderCache = $this->createMock( GenderCache
::class );
238 $genderCache->expects( $this->once() )->method( 'doLinkBatch' );
240 $batch = new LinkBatch(
242 $this->createMock( LinkCache
::class ),
243 $this->createMock( TitleFormatter
::class ),
246 $this->createMock( IConnectionProvider
::class ),
247 $this->createMock( LinksMigration
::class ),
248 LoggerFactory
::getInstance( 'LinkBatch' )
251 new TitleValue( NS_MAIN
, 'Foo' )
254 $this->assertTrue( $batch->doGenderQuery() );
257 public static function provideBadObjects() {
258 yield
'null' => [ null ];
259 yield
'empty' => [ Title
::makeTitle( NS_MAIN
, '' ) ];
260 yield
'bad user' => [ Title
::makeTitle( NS_USER
, '#12345' ) ];
261 yield
'section' => [ new TitleValue( NS_MAIN
, '', '#See_also' ) ];
262 yield
'special' => [ new TitleValue( NS_SPECIAL
, 'RecentChanges' ) ];
266 * @dataProvider provideBadObjects
268 public function testAddBadObj( $obj ) {
269 $linkBatch = $this->newLinkBatch();
270 $linkBatch->addObj( $obj );
271 $linkBatch->execute();
272 $this->addToAssertionCount( 1 );
275 public static function provideBadDBKeys() {
276 yield
'empty' => [ '' ];
277 yield
'section' => [ '#See_also' ];
278 yield
'pipe' => [ 'foo|bar' ];
282 * @dataProvider provideBadDBKeys
284 public function testAddBadDBKeys( $key ) {
285 $linkBatch = $this->newLinkBatch();
286 $linkBatch->add( NS_MAIN
, $key );
287 $linkBatch->execute();
288 $this->addToAssertionCount( 1 );