Remove tests that were skipped for MCR-enabled schema
[lhc/web/wiklou.git] / tests / phpunit / includes / Revision / RevisionStoreTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Revision;
4
5 use CommentStore;
6 use InvalidArgumentException;
7 use MediaWiki\MediaWikiServices;
8 use MediaWiki\Revision\RevisionAccessException;
9 use MediaWiki\Revision\RevisionStore;
10 use MediaWiki\Revision\SlotRoleRegistry;
11 use MediaWiki\Storage\SqlBlobStore;
12 use Wikimedia\Rdbms\ILoadBalancer;
13 use Wikimedia\Rdbms\MaintainableDBConnRef;
14 use MediaWikiTestCase;
15 use MWException;
16 use WANObjectCache;
17 use Wikimedia\Rdbms\IDatabase;
18 use Wikimedia\Rdbms\LoadBalancer;
19 use Wikimedia\TestingAccessWrapper;
20
21 /**
22 * Tests RevisionStore
23 */
24 class RevisionStoreTest extends MediaWikiTestCase {
25
26 /**
27 * @param LoadBalancer $loadBalancer
28 * @param SqlBlobStore $blobStore
29 * @param WANObjectCache $WANObjectCache
30 *
31 * @return RevisionStore
32 */
33 private function getRevisionStore(
34 $loadBalancer = null,
35 $blobStore = null,
36 $WANObjectCache = null
37 ) {
38 global $wgMultiContentRevisionSchemaMigrationStage;
39 // the migration stage should be irrelevant, since all the tests that interact with
40 // the database are in RevisionStoreDbTest, not here.
41
42 return new RevisionStore(
43 $loadBalancer ?: $this->getMockLoadBalancer(),
44 $blobStore ?: $this->getMockSqlBlobStore(),
45 $WANObjectCache ?: $this->getHashWANObjectCache(),
46 MediaWikiServices::getInstance()->getCommentStore(),
47 MediaWikiServices::getInstance()->getContentModelStore(),
48 MediaWikiServices::getInstance()->getSlotRoleStore(),
49 MediaWikiServices::getInstance()->getSlotRoleRegistry(),
50 $wgMultiContentRevisionSchemaMigrationStage,
51 MediaWikiServices::getInstance()->getActorMigration()
52 );
53 }
54
55 /**
56 * @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
57 */
58 private function getMockLoadBalancer() {
59 return $this->getMockBuilder( LoadBalancer::class )
60 ->disableOriginalConstructor()->getMock();
61 }
62
63 /**
64 * @return \PHPUnit_Framework_MockObject_MockObject|IDatabase
65 */
66 private function getMockDatabase() {
67 return $this->getMockBuilder( IDatabase::class )
68 ->disableOriginalConstructor()->getMock();
69 }
70
71 /**
72 * @param ILoadBalancer $mockLoadBalancer
73 * @param Database $db
74 * @return callable
75 */
76 private function getMockDBConnRefCallback( ILoadBalancer $mockLoadBalancer, IDatabase $db ) {
77 return function ( $i, $g, $domain, $flg ) use ( $mockLoadBalancer, $db ) {
78 return new MaintainableDBConnRef( $mockLoadBalancer, $db, $i );
79 };
80 }
81
82 /**
83 * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
84 */
85 private function getMockSqlBlobStore() {
86 return $this->getMockBuilder( SqlBlobStore::class )
87 ->disableOriginalConstructor()->getMock();
88 }
89
90 /**
91 * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
92 */
93 private function getMockCommentStore() {
94 return $this->getMockBuilder( CommentStore::class )
95 ->disableOriginalConstructor()->getMock();
96 }
97
98 /**
99 * @return \PHPUnit_Framework_MockObject_MockObject|SlotRoleRegistry
100 */
101 private function getMockSlotRoleRegistry() {
102 return $this->getMockBuilder( SlotRoleRegistry::class )
103 ->disableOriginalConstructor()->getMock();
104 }
105
106 private function getHashWANObjectCache() {
107 return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
108 }
109
110 public function provideSetContentHandlerUseDB() {
111 return [
112 // ContentHandlerUseDB can be true or false pre migration.
113 // During and after migration it can not be false...
114 [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, true ],
115 [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, true ],
116 [ false, SCHEMA_COMPAT_NEW, true ],
117 // ...but it can be true.
118 [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
119 [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
120 [ true, SCHEMA_COMPAT_NEW, false ],
121 ];
122 }
123
124 /**
125 * @dataProvider provideSetContentHandlerUseDB
126 * @covers \MediaWiki\Revision\RevisionStore::getContentHandlerUseDB
127 * @covers \MediaWiki\Revision\RevisionStore::setContentHandlerUseDB
128 */
129 public function testSetContentHandlerUseDB( $contentHandlerDb, $migrationMode, $expectedFail ) {
130 if ( $expectedFail ) {
131 $this->setExpectedException( MWException::class );
132 }
133
134 $nameTables = MediaWikiServices::getInstance()->getNameTableStoreFactory();
135
136 $store = new RevisionStore(
137 $this->getMockLoadBalancer(),
138 $this->getMockSqlBlobStore(),
139 $this->getHashWANObjectCache(),
140 $this->getMockCommentStore(),
141 $nameTables->getContentModels(),
142 $nameTables->getSlotRoles(),
143 $this->getMockSlotRoleRegistry(),
144 $migrationMode,
145 MediaWikiServices::getInstance()->getActorMigration()
146 );
147
148 $store->setContentHandlerUseDB( $contentHandlerDb );
149 $this->assertSame( $contentHandlerDb, $store->getContentHandlerUseDB() );
150 }
151
152 /**
153 * @covers \MediaWiki\Revision\RevisionStore::getTitle
154 */
155 public function testGetTitle_successFromPageId() {
156 $mockLoadBalancer = $this->getMockLoadBalancer();
157 // Title calls wfGetDB() so we have to set the main service
158 $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
159
160 $db = $this->getMockDatabase();
161 // RevisionStore uses getConnectionRef
162 $mockLoadBalancer->expects( $this->any() )
163 ->method( 'getConnectionRef' )
164 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
165 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
166 $mockLoadBalancer->expects( $this->atLeastOnce() )
167 ->method( 'getMaintenanceConnectionRef' )
168 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
169
170 // First call to Title::newFromID, faking no result (db lag?)
171 $db->expects( $this->at( 0 ) )
172 ->method( 'selectRow' )
173 ->with(
174 'page',
175 $this->anything(),
176 [ 'page_id' => 1 ]
177 )
178 ->willReturn( (object)[
179 'page_namespace' => '1',
180 'page_title' => 'Food',
181 ] );
182
183 $store = $this->getRevisionStore( $mockLoadBalancer );
184 $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
185
186 $this->assertSame( 1, $title->getNamespace() );
187 $this->assertSame( 'Food', $title->getDBkey() );
188 }
189
190 /**
191 * @covers \MediaWiki\Revision\RevisionStore::getTitle
192 */
193 public function testGetTitle_successFromPageIdOnFallback() {
194 $mockLoadBalancer = $this->getMockLoadBalancer();
195 // Title calls wfGetDB() so we have to set the main service
196 $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
197
198 $db = $this->getMockDatabase();
199 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
200 // Assert that the first call uses a REPLICA and the second falls back to master
201 $mockLoadBalancer->expects( $this->atLeastOnce() )
202 ->method( 'getConnectionRef' )
203 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
204 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
205 $mockLoadBalancer->expects( $this->exactly( 2 ) )
206 ->method( 'getMaintenanceConnectionRef' )
207 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
208
209 // First call to Title::newFromID, faking no result (db lag?)
210 $db->expects( $this->at( 0 ) )
211 ->method( 'selectRow' )
212 ->with(
213 'page',
214 $this->anything(),
215 [ 'page_id' => 1 ]
216 )
217 ->willReturn( false );
218
219 // First select using rev_id, faking no result (db lag?)
220 $db->expects( $this->at( 1 ) )
221 ->method( 'selectRow' )
222 ->with(
223 [ 'revision', 'page' ],
224 $this->anything(),
225 [ 'rev_id' => 2 ]
226 )
227 ->willReturn( false );
228
229 // Second call to Title::newFromID, no result
230 $db->expects( $this->at( 2 ) )
231 ->method( 'selectRow' )
232 ->with(
233 'page',
234 $this->anything(),
235 [ 'page_id' => 1 ]
236 )
237 ->willReturn( (object)[
238 'page_namespace' => '2',
239 'page_title' => 'Foodey',
240 ] );
241
242 $store = $this->getRevisionStore( $mockLoadBalancer );
243 $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
244
245 $this->assertSame( 2, $title->getNamespace() );
246 $this->assertSame( 'Foodey', $title->getDBkey() );
247 }
248
249 /**
250 * @covers \MediaWiki\Revision\RevisionStore::getTitle
251 */
252 public function testGetTitle_successFromRevId() {
253 $mockLoadBalancer = $this->getMockLoadBalancer();
254 // Title calls wfGetDB() so we have to set the main service
255 $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
256
257 $db = $this->getMockDatabase();
258 $mockLoadBalancer->expects( $this->atLeastOnce() )
259 ->method( 'getConnectionRef' )
260 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
261 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
262 // RevisionStore getTitle uses getMaintenanceConnectionRef
263 $mockLoadBalancer->expects( $this->atLeastOnce() )
264 ->method( 'getMaintenanceConnectionRef' )
265 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
266
267 // First call to Title::newFromID, faking no result (db lag?)
268 $db->expects( $this->at( 0 ) )
269 ->method( 'selectRow' )
270 ->with(
271 'page',
272 $this->anything(),
273 [ 'page_id' => 1 ]
274 )
275 ->willReturn( false );
276
277 // First select using rev_id, faking no result (db lag?)
278 $db->expects( $this->at( 1 ) )
279 ->method( 'selectRow' )
280 ->with(
281 [ 'revision', 'page' ],
282 $this->anything(),
283 [ 'rev_id' => 2 ]
284 )
285 ->willReturn( (object)[
286 'page_namespace' => '1',
287 'page_title' => 'Food2',
288 ] );
289
290 $store = $this->getRevisionStore( $mockLoadBalancer );
291 $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
292
293 $this->assertSame( 1, $title->getNamespace() );
294 $this->assertSame( 'Food2', $title->getDBkey() );
295 }
296
297 /**
298 * @covers \MediaWiki\Revision\RevisionStore::getTitle
299 */
300 public function testGetTitle_successFromRevIdOnFallback() {
301 $mockLoadBalancer = $this->getMockLoadBalancer();
302 // Title calls wfGetDB() so we have to set the main service
303 $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
304
305 $db = $this->getMockDatabase();
306 // Assert that the first call uses a REPLICA and the second falls back to master
307 // RevisionStore uses getMaintenanceConnectionRef
308 $mockLoadBalancer->expects( $this->atLeastOnce() )
309 ->method( 'getConnectionRef' )
310 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
311 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
312 $mockLoadBalancer->expects( $this->exactly( 2 ) )
313 ->method( 'getMaintenanceConnectionRef' )
314 ->willReturnCallback( $this->getMockDBConnRefCallback( $mockLoadBalancer, $db ) );
315
316 // First call to Title::newFromID, faking no result (db lag?)
317 $db->expects( $this->at( 0 ) )
318 ->method( 'selectRow' )
319 ->with(
320 'page',
321 $this->anything(),
322 [ 'page_id' => 1 ]
323 )
324 ->willReturn( false );
325
326 // First select using rev_id, faking no result (db lag?)
327 $db->expects( $this->at( 1 ) )
328 ->method( 'selectRow' )
329 ->with(
330 [ 'revision', 'page' ],
331 $this->anything(),
332 [ 'rev_id' => 2 ]
333 )
334 ->willReturn( false );
335
336 // Second call to Title::newFromID, no result
337 $db->expects( $this->at( 2 ) )
338 ->method( 'selectRow' )
339 ->with(
340 'page',
341 $this->anything(),
342 [ 'page_id' => 1 ]
343 )
344 ->willReturn( false );
345
346 // Second select using rev_id, result
347 $db->expects( $this->at( 3 ) )
348 ->method( 'selectRow' )
349 ->with(
350 [ 'revision', 'page' ],
351 $this->anything(),
352 [ 'rev_id' => 2 ]
353 )
354 ->willReturn( (object)[
355 'page_namespace' => '2',
356 'page_title' => 'Foodey',
357 ] );
358
359 $store = $this->getRevisionStore( $mockLoadBalancer );
360 $title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
361
362 $this->assertSame( 2, $title->getNamespace() );
363 $this->assertSame( 'Foodey', $title->getDBkey() );
364 }
365
366 /**
367 * @covers \MediaWiki\Revision\RevisionStore::getTitle
368 */
369 public function testGetTitle_correctFallbackAndthrowsExceptionAfterFallbacks() {
370 $mockLoadBalancer = $this->getMockLoadBalancer();
371 // Title calls wfGetDB() so we have to set the main service
372 $this->setService( 'DBLoadBalancer', $mockLoadBalancer );
373
374 $db = $this->getMockDatabase();
375 // Title calls wfGetDB() which uses getMaintenanceConnectionRef
376 // Assert that the first call uses a REPLICA and the second falls back to master
377
378 // RevisionStore getTitle uses getConnectionRef
379 // Title::newFromID uses getMaintenanceConnectionRef
380 foreach ( [
381 'getConnectionRef', 'getMaintenanceConnectionRef'
382 ] as $method ) {
383 $mockLoadBalancer->expects( $this->exactly( 2 ) )
384 ->method( $method )
385 ->willReturnCallback( function ( $masterOrReplica ) use ( $db ) {
386 static $callCounter = 0;
387 $callCounter++;
388 // The first call should be to a REPLICA, and the second a MASTER.
389 if ( $callCounter === 1 ) {
390 $this->assertSame( DB_REPLICA, $masterOrReplica );
391 } elseif ( $callCounter === 2 ) {
392 $this->assertSame( DB_MASTER, $masterOrReplica );
393 }
394 return $db;
395 } );
396 }
397 // First and third call to Title::newFromID, faking no result
398 foreach ( [ 0, 2 ] as $counter ) {
399 $db->expects( $this->at( $counter ) )
400 ->method( 'selectRow' )
401 ->with(
402 'page',
403 $this->anything(),
404 [ 'page_id' => 1 ]
405 )
406 ->willReturn( false );
407 }
408
409 foreach ( [ 1, 3 ] as $counter ) {
410 $db->expects( $this->at( $counter ) )
411 ->method( 'selectRow' )
412 ->with(
413 [ 'revision', 'page' ],
414 $this->anything(),
415 [ 'rev_id' => 2 ]
416 )
417 ->willReturn( false );
418 }
419
420 $store = $this->getRevisionStore( $mockLoadBalancer );
421
422 $this->setExpectedException( RevisionAccessException::class );
423 $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
424 }
425
426 public function provideMigrationConstruction() {
427 return [
428 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
429 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
430 [ SCHEMA_COMPAT_NEW, false ],
431 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, true ],
432 [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, true ],
433 [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, true ],
434 ];
435 }
436
437 /**
438 * @covers \MediaWiki\Revision\RevisionStore::__construct
439 * @dataProvider provideMigrationConstruction
440 */
441 public function testMigrationConstruction( $migration, $expectException ) {
442 if ( $expectException ) {
443 $this->setExpectedException( InvalidArgumentException::class );
444 }
445 $loadBalancer = $this->getMockLoadBalancer();
446 $blobStore = $this->getMockSqlBlobStore();
447 $cache = $this->getHashWANObjectCache();
448 $commentStore = $this->getMockCommentStore();
449 $services = MediaWikiServices::getInstance();
450 $nameTables = $services->getNameTableStoreFactory();
451 $contentModelStore = $nameTables->getContentModels();
452 $slotRoleStore = $nameTables->getSlotRoles();
453 $slotRoleRegistry = $services->getSlotRoleRegistry();
454 $store = new RevisionStore(
455 $loadBalancer,
456 $blobStore,
457 $cache,
458 $commentStore,
459 $nameTables->getContentModels(),
460 $nameTables->getSlotRoles(),
461 $slotRoleRegistry,
462 $migration,
463 $services->getActorMigration()
464 );
465 if ( !$expectException ) {
466 $store = TestingAccessWrapper::newFromObject( $store );
467 $this->assertSame( $loadBalancer, $store->loadBalancer );
468 $this->assertSame( $blobStore, $store->blobStore );
469 $this->assertSame( $cache, $store->cache );
470 $this->assertSame( $commentStore, $store->commentStore );
471 $this->assertSame( $contentModelStore, $store->contentModelStore );
472 $this->assertSame( $slotRoleStore, $store->slotRoleStore );
473 $this->assertSame( $migration, $store->mcrMigrationStage );
474 }
475 }
476
477 }