Merge "Prevent write operations to database replicas."
[lhc/web/wiklou.git] / tests / phpunit / includes / RevisionTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * Test cases in RevisionTest should not interact with the Database.
7 * For test cases that need Database interaction see RevisionDbTestBase.
8 */
9 class RevisionTest extends MediaWikiTestCase {
10
11 public function provideConstructFromArray() {
12 yield 'with text' => [
13 [
14 'text' => 'hello world.',
15 'content_model' => CONTENT_MODEL_JAVASCRIPT
16 ],
17 ];
18 yield 'with content' => [
19 [
20 'content' => new JavaScriptContent( 'hellow world.' )
21 ],
22 ];
23 // FIXME: test with and without user ID, and with a user object.
24 // We can't prepare that here though, since we don't yet have a dummy DB
25 }
26
27 /**
28 * @param string $model
29 * @return Title
30 */
31 public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
32 $mock = $this->getMockBuilder( Title::class )
33 ->disableOriginalConstructor()
34 ->getMock();
35 $mock->expects( $this->any() )
36 ->method( 'getNamespace' )
37 ->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
38 $mock->expects( $this->any() )
39 ->method( 'getPrefixedText' )
40 ->will( $this->returnValue( 'RevisionTest' ) );
41 $mock->expects( $this->any() )
42 ->method( 'getDBKey' )
43 ->will( $this->returnValue( 'RevisionTest' ) );
44 $mock->expects( $this->any() )
45 ->method( 'getArticleID' )
46 ->will( $this->returnValue( 23 ) );
47 $mock->expects( $this->any() )
48 ->method( 'getModel' )
49 ->will( $this->returnValue( $model ) );
50
51 return $mock;
52 }
53
54 /**
55 * @dataProvider provideConstructFromArray
56 * @covers Revision::__construct
57 * @covers Revision::constructFromRowArray
58 */
59 public function testConstructFromArray( array $rowArray ) {
60 $rev = new Revision( $rowArray );
61 $this->assertNotNull( $rev->getContent(), 'no content object available' );
62 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
63 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
64 }
65
66 /**
67 * @covers Revision::__construct
68 * @covers Revision::constructFromRowArray
69 */
70 public function testConstructFromEmptyArray() {
71 $rev = new Revision( [], 0, $this->getMockTitle() );
72 $this->assertNull( $rev->getContent(), 'no content object should be available' );
73 }
74
75 public function provideConstructFromArray_userSetAsExpected() {
76 yield 'no user defaults to wgUser' => [
77 [
78 'content' => new JavaScriptContent( 'hello world.' ),
79 ],
80 null,
81 null,
82 ];
83 yield 'user text and id' => [
84 [
85 'content' => new JavaScriptContent( 'hello world.' ),
86 'user_text' => 'SomeTextUserName',
87 'user' => 99,
88
89 ],
90 99,
91 'SomeTextUserName',
92 ];
93 // Note: the below XXX test cases are odd and probably result in unexpected behaviour if used
94 // in production code.
95 yield 'XXX: user text only' => [
96 [
97 'content' => new JavaScriptContent( 'hello world.' ),
98 'user_text' => '111.111.111.111',
99 ],
100 null,
101 '111.111.111.111',
102 ];
103 yield 'XXX: user id only' => [
104 [
105 'content' => new JavaScriptContent( 'hello world.' ),
106 'user' => 9989,
107 ],
108 9989,
109 null,
110 ];
111 }
112
113 /**
114 * @dataProvider provideConstructFromArray_userSetAsExpected
115 * @covers Revision::__construct
116 * @covers Revision::constructFromRowArray
117 *
118 * @param array $rowArray
119 * @param mixed $expectedUserId null to expect the current wgUser ID
120 * @param mixed $expectedUserName null to expect the current wgUser name
121 */
122 public function testConstructFromArray_userSetAsExpected(
123 array $rowArray,
124 $expectedUserId,
125 $expectedUserName
126 ) {
127 $testUser = $this->getTestUser()->getUser();
128 $this->setMwGlobals( 'wgUser', $testUser );
129 if ( $expectedUserId === null ) {
130 $expectedUserId = $testUser->getId();
131 }
132 if ( $expectedUserName === null ) {
133 $expectedUserName = $testUser->getName();
134 }
135
136 $rev = new Revision( $rowArray );
137 $this->assertEquals( $expectedUserId, $rev->getUser() );
138 $this->assertEquals( $expectedUserName, $rev->getUserText() );
139 }
140
141 public function provideConstructFromArrayThrowsExceptions() {
142 yield 'content and text_id both not empty' => [
143 [
144 'content' => new WikitextContent( 'GOAT' ),
145 'text_id' => 'someid',
146 ],
147 new MWException( "Text already stored in external store (id someid), " .
148 "can't serialize content object" )
149 ];
150 yield 'with bad content object (class)' => [
151 [ 'content' => new stdClass() ],
152 new MWException( '`content` field must contain a Content object.' )
153 ];
154 yield 'with bad content object (string)' => [
155 [ 'content' => 'ImAGoat' ],
156 new MWException( '`content` field must contain a Content object.' )
157 ];
158 yield 'bad row format' => [
159 'imastring, not a row',
160 new MWException( 'Revision constructor passed invalid row format.' )
161 ];
162 }
163
164 /**
165 * @dataProvider provideConstructFromArrayThrowsExceptions
166 * @covers Revision::__construct
167 * @covers Revision::constructFromRowArray
168 */
169 public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
170 $this->setExpectedException(
171 get_class( $expectedException ),
172 $expectedException->getMessage(),
173 $expectedException->getCode()
174 );
175 new Revision( $rowArray );
176 }
177
178 /**
179 * @covers Revision::__construct
180 * @covers Revision::constructFromRowArray
181 */
182 public function testConstructFromNothing() {
183 $rev = new Revision( [] );
184 $this->assertNull( $rev->getId(), 'getId()' );
185 }
186
187 public function provideConstructFromRow() {
188 yield 'Full construction' => [
189 [
190 'rev_id' => '2',
191 'rev_page' => '1',
192 'rev_text_id' => '2',
193 'rev_timestamp' => '20171017114835',
194 'rev_user_text' => '127.0.0.1',
195 'rev_user' => '0',
196 'rev_minor_edit' => '0',
197 'rev_deleted' => '0',
198 'rev_len' => '46',
199 'rev_parent_id' => '1',
200 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
201 'rev_comment_text' => 'Goat Comment!',
202 'rev_comment_data' => null,
203 'rev_comment_cid' => null,
204 'rev_content_format' => 'GOATFORMAT',
205 'rev_content_model' => 'GOATMODEL',
206 ],
207 function ( RevisionTest $testCase, Revision $rev ) {
208 $testCase->assertSame( 2, $rev->getId() );
209 $testCase->assertSame( 1, $rev->getPage() );
210 $testCase->assertSame( 2, $rev->getTextId() );
211 $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
212 $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
213 $testCase->assertSame( 0, $rev->getUser() );
214 $testCase->assertSame( false, $rev->isMinor() );
215 $testCase->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
216 $testCase->assertSame( 46, $rev->getSize() );
217 $testCase->assertSame( 1, $rev->getParentId() );
218 $testCase->assertSame( 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z', $rev->getSha1() );
219 $testCase->assertSame( 'Goat Comment!', $rev->getComment() );
220 $testCase->assertSame( 'GOATFORMAT', $rev->getContentFormat() );
221 $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
222 }
223 ];
224 yield 'null fields' => [
225 [
226 'rev_id' => '2',
227 'rev_page' => '1',
228 'rev_text_id' => '2',
229 'rev_timestamp' => '20171017114835',
230 'rev_user_text' => '127.0.0.1',
231 'rev_user' => '0',
232 'rev_minor_edit' => '0',
233 'rev_deleted' => '0',
234 'rev_comment_text' => 'Goat Comment!',
235 'rev_comment_data' => null,
236 'rev_comment_cid' => null,
237 ],
238 function ( RevisionTest $testCase, Revision $rev ) {
239 $testCase->assertNull( $rev->getSize() );
240 $testCase->assertNull( $rev->getParentId() );
241 $testCase->assertNull( $rev->getSha1() );
242 $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat() );
243 $testCase->assertSame( 'wikitext', $rev->getContentModel() );
244 }
245 ];
246 }
247
248 /**
249 * @dataProvider provideConstructFromRow
250 * @covers Revision::__construct
251 * @covers Revision::constructFromRowArray
252 */
253 public function testConstructFromRow( array $arrayData, $assertions ) {
254 $row = (object)$arrayData;
255 $rev = new Revision( $row );
256 $assertions( $this, $rev );
257 }
258
259 public function provideGetRevisionText() {
260 yield 'Generic test' => [
261 'This is a goat of revision text.',
262 [
263 'old_flags' => '',
264 'old_text' => 'This is a goat of revision text.',
265 ],
266 ];
267 }
268
269 public function provideGetId() {
270 yield [
271 [],
272 null
273 ];
274 yield [
275 [ 'id' => 998 ],
276 998
277 ];
278 }
279
280 /**
281 * @dataProvider provideGetId
282 * @covers Revision::getId
283 */
284 public function testGetId( $rowArray, $expectedId ) {
285 $rev = new Revision( $rowArray );
286 $this->assertEquals( $expectedId, $rev->getId() );
287 }
288
289 public function provideSetId() {
290 yield [ '123', 123 ];
291 yield [ 456, 456 ];
292 }
293
294 /**
295 * @dataProvider provideSetId
296 * @covers Revision::setId
297 */
298 public function testSetId( $input, $expected ) {
299 $rev = new Revision( [] );
300 $rev->setId( $input );
301 $this->assertSame( $expected, $rev->getId() );
302 }
303
304 public function provideSetUserIdAndName() {
305 yield [ '123', 123, 'GOaT' ];
306 yield [ 456, 456, 'GOaT' ];
307 }
308
309 /**
310 * @dataProvider provideSetUserIdAndName
311 * @covers Revision::setUserIdAndName
312 */
313 public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
314 $rev = new Revision( [] );
315 $rev->setUserIdAndName( $inputId, $name );
316 $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
317 $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
318 }
319
320 public function provideGetTextId() {
321 yield [ [], null ];
322 yield [ [ 'text_id' => '123' ], 123 ];
323 yield [ [ 'text_id' => 456 ], 456 ];
324 }
325
326 /**
327 * @dataProvider provideGetTextId
328 * @covers Revision::getTextId()
329 */
330 public function testGetTextId( $rowArray, $expected ) {
331 $rev = new Revision( $rowArray );
332 $this->assertSame( $expected, $rev->getTextId() );
333 }
334
335 public function provideGetParentId() {
336 yield [ [], null ];
337 yield [ [ 'parent_id' => '123' ], 123 ];
338 yield [ [ 'parent_id' => 456 ], 456 ];
339 }
340
341 /**
342 * @dataProvider provideGetParentId
343 * @covers Revision::getParentId()
344 */
345 public function testGetParentId( $rowArray, $expected ) {
346 $rev = new Revision( $rowArray );
347 $this->assertSame( $expected, $rev->getParentId() );
348 }
349
350 /**
351 * @covers Revision::getRevisionText
352 * @dataProvider provideGetRevisionText
353 */
354 public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
355 $this->assertEquals(
356 $expected,
357 Revision::getRevisionText( (object)$rowData, $prefix, $wiki ) );
358 }
359
360 public function provideGetRevisionTextWithZlibExtension() {
361 yield 'Generic gzip test' => [
362 'This is a small goat of revision text.',
363 [
364 'old_flags' => 'gzip',
365 'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
366 ],
367 ];
368 }
369
370 /**
371 * @covers Revision::getRevisionText
372 * @dataProvider provideGetRevisionTextWithZlibExtension
373 */
374 public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
375 $this->checkPHPExtension( 'zlib' );
376 $this->testGetRevisionText( $expected, $rowData );
377 }
378
379 public function provideGetRevisionTextWithLegacyEncoding() {
380 yield 'Utf8Native' => [
381 "Wiki est l'\xc3\xa9cole superieur !",
382 'iso-8859-1',
383 [
384 'old_flags' => 'utf-8',
385 'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
386 ]
387 ];
388 yield 'Utf8Legacy' => [
389 "Wiki est l'\xc3\xa9cole superieur !",
390 'iso-8859-1',
391 [
392 'old_flags' => '',
393 'old_text' => "Wiki est l'\xe9cole superieur !",
394 ]
395 ];
396 }
397
398 /**
399 * @covers Revision::getRevisionText
400 * @dataProvider provideGetRevisionTextWithLegacyEncoding
401 */
402 public function testGetRevisionWithLegacyEncoding( $expected, $encoding, $rowData ) {
403 $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
404 $this->testGetRevisionText( $expected, $rowData );
405 }
406
407 public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
408 /**
409 * WARNING!
410 * Do not set the external flag!
411 * Otherwise, getRevisionText will hit the live database (if ExternalStore is enabled)!
412 */
413 yield 'Utf8NativeGzip' => [
414 "Wiki est l'\xc3\xa9cole superieur !",
415 'iso-8859-1',
416 [
417 'old_flags' => 'gzip,utf-8',
418 'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
419 ]
420 ];
421 yield 'Utf8LegacyGzip' => [
422 "Wiki est l'\xc3\xa9cole superieur !",
423 'iso-8859-1',
424 [
425 'old_flags' => 'gzip',
426 'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
427 ]
428 ];
429 }
430
431 /**
432 * @covers Revision::getRevisionText
433 * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
434 */
435 public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $encoding, $rowData ) {
436 $this->checkPHPExtension( 'zlib' );
437 $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
438 $this->testGetRevisionText( $expected, $rowData );
439 }
440
441 /**
442 * @covers Revision::compressRevisionText
443 */
444 public function testCompressRevisionTextUtf8() {
445 $row = new stdClass;
446 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
447 $row->old_flags = Revision::compressRevisionText( $row->old_text );
448 $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
449 "Flags should contain 'utf-8'" );
450 $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
451 "Flags should not contain 'gzip'" );
452 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
453 $row->old_text, "Direct check" );
454 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
455 Revision::getRevisionText( $row ), "getRevisionText" );
456 }
457
458 /**
459 * @covers Revision::compressRevisionText
460 */
461 public function testCompressRevisionTextUtf8Gzip() {
462 $this->checkPHPExtension( 'zlib' );
463 $this->setMwGlobals( 'wgCompressRevisions', true );
464
465 $row = new stdClass;
466 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
467 $row->old_flags = Revision::compressRevisionText( $row->old_text );
468 $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
469 "Flags should contain 'utf-8'" );
470 $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
471 "Flags should contain 'gzip'" );
472 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
473 gzinflate( $row->old_text ), "Direct check" );
474 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
475 Revision::getRevisionText( $row ), "getRevisionText" );
476 }
477
478 public function provideFetchFromConds() {
479 yield [ 0, [] ];
480 yield [ Revision::READ_LOCKING, [ 'FOR UPDATE' ] ];
481 }
482
483 /**
484 * @dataProvider provideFetchFromConds
485 * @covers Revision::fetchFromConds
486 */
487 public function testFetchFromConds( $flags, array $options ) {
488 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
489 $conditions = [ 'conditionsArray' ];
490
491 $db = $this->getMock( IDatabase::class );
492 $db->expects( $this->once() )
493 ->method( 'selectRow' )
494 ->with(
495 $this->equalTo( [ 'revision', 'page', 'user' ] ),
496 // We don't really care about the fields are they come from the selectField methods
497 $this->isType( 'array' ),
498 $this->equalTo( $conditions ),
499 // Method name
500 $this->equalTo( 'Revision::fetchFromConds' ),
501 $this->equalTo( $options ),
502 // We don't really care about the join conds are they come from the joinCond methods
503 $this->isType( 'array' )
504 )
505 ->willReturn( 'RETURNVALUE' );
506
507 $wrapper = TestingAccessWrapper::newFromClass( Revision::class );
508 $result = $wrapper->fetchFromConds( $db, $conditions, $flags );
509
510 $this->assertEquals( 'RETURNVALUE', $result );
511 }
512
513 public function provideDecompressRevisionText() {
514 yield '(no legacy encoding), false in false out' => [ false, false, [], false ];
515 yield '(no legacy encoding), empty in empty out' => [ false, '', [], '' ];
516 yield '(no legacy encoding), empty in empty out' => [ false, 'A', [], 'A' ];
517 yield '(no legacy encoding), string in with gzip flag returns string' => [
518 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
519 false, "sttttr\002\022\000", [ 'gzip' ], 'AAAABBAAA',
520 ];
521 yield '(no legacy encoding), string in with object flag returns false' => [
522 // gzip string below generated with serialize( 'JOJO' )
523 false, "s:4:\"JOJO\";", [ 'object' ], false,
524 ];
525 yield '(no legacy encoding), serialized object in with object flag returns string' => [
526 false,
527 // Using a TitleValue object as it has a getText method (which is needed)
528 serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
529 [ 'object' ],
530 'HHJJDDFF',
531 ];
532 yield '(no legacy encoding), serialized object in with object & gzip flag returns string' => [
533 false,
534 // Using a TitleValue object as it has a getText method (which is needed)
535 gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
536 [ 'object', 'gzip' ],
537 '8219JJJ840',
538 ];
539 yield '(ISO-8859-1 encoding), string in string out' => [
540 'ISO-8859-1',
541 iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
542 [],
543 '1®Àþ1',
544 ];
545 yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
546 'ISO-8859-1',
547 gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
548 [ 'gzip' ],
549 '4®Àþ4',
550 ];
551 yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
552 'ISO-8859-1',
553 serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
554 [ 'object' ],
555 '3®Àþ3',
556 ];
557 yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
558 'ISO-8859-1',
559 gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
560 [ 'gzip', 'object' ],
561 '2®Àþ2',
562 ];
563 }
564
565 /**
566 * @dataProvider provideDecompressRevisionText
567 * @covers Revision::decompressRevisionText
568 *
569 * @param bool $legacyEncoding
570 * @param mixed $text
571 * @param array $flags
572 * @param mixed $expected
573 */
574 public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
575 $this->setMwGlobals( 'wgLegacyEncoding', $legacyEncoding );
576 $this->setMwGlobals( 'wgLanguageCode', 'en' );
577 $this->assertSame(
578 $expected,
579 Revision::decompressRevisionText( $text, $flags )
580 );
581 }
582
583 /**
584 * @covers Revision::getRevisionText
585 */
586 public function testGetRevisionText_returnsFalseWhenNoTextField() {
587 $this->assertFalse( Revision::getRevisionText( new stdClass() ) );
588 }
589
590 public function provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal() {
591 yield 'Just text' => [
592 (object)[ 'old_text' => 'SomeText' ],
593 'old_',
594 'SomeText'
595 ];
596 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
597 yield 'gzip text' => [
598 (object)[
599 'old_text' => "sttttr\002\022\000",
600 'old_flags' => 'gzip'
601 ],
602 'old_',
603 'AAAABBAAA'
604 ];
605 yield 'gzip text and different prefix' => [
606 (object)[
607 'jojo_text' => "sttttr\002\022\000",
608 'jojo_flags' => 'gzip'
609 ],
610 'jojo_',
611 'AAAABBAAA'
612 ];
613 }
614
615 /**
616 * @dataProvider provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal
617 * @covers Revision::getRevisionText
618 */
619 public function testGetRevisionText_returnsDecompressedTextFieldWhenNotExternal(
620 $row,
621 $prefix,
622 $expected
623 ) {
624 $this->assertSame( $expected, Revision::getRevisionText( $row, $prefix ) );
625 }
626
627 public function provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts() {
628 yield 'Just some text' => [ 'someNonUrlText' ];
629 yield 'No second URL part' => [ 'someProtocol://' ];
630 }
631
632 /**
633 * @dataProvider provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts
634 * @covers Revision::getRevisionText
635 */
636 public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
637 $text
638 ) {
639 $this->assertFalse(
640 Revision::getRevisionText(
641 (object)[
642 'old_text' => $text,
643 'old_flags' => 'external',
644 ]
645 )
646 );
647 }
648
649 /**
650 * @covers Revision::getRevisionText
651 */
652 public function testGetRevisionText_external_noOldId() {
653 $this->setService(
654 'ExternalStoreFactory',
655 new ExternalStoreFactory( [ 'ForTesting' ] )
656 );
657 $this->assertSame(
658 'AAAABBAAA',
659 Revision::getRevisionText(
660 (object)[
661 'old_text' => 'ForTesting://cluster1/12345',
662 'old_flags' => 'external,gzip',
663 ]
664 )
665 );
666 }
667
668 /**
669 * @covers Revision::getRevisionText
670 */
671 public function testGetRevisionText_external_oldId() {
672 $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
673 $this->setService( 'MainWANObjectCache', $cache );
674 $this->setService(
675 'ExternalStoreFactory',
676 new ExternalStoreFactory( [ 'ForTesting' ] )
677 );
678
679 $cacheKey = $cache->makeKey( 'revisiontext', 'textid', '7777' );
680
681 $this->assertSame(
682 'AAAABBAAA',
683 Revision::getRevisionText(
684 (object)[
685 'old_text' => 'ForTesting://cluster1/12345',
686 'old_flags' => 'external,gzip',
687 'old_id' => '7777',
688 ]
689 )
690 );
691 $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
692 }
693
694 /**
695 * @covers Revision::userJoinCond
696 */
697 public function testUserJoinCond() {
698 $this->hideDeprecated( 'Revision::userJoinCond' );
699 $this->assertEquals(
700 [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
701 Revision::userJoinCond()
702 );
703 }
704
705 /**
706 * @covers Revision::pageJoinCond
707 */
708 public function testPageJoinCond() {
709 $this->hideDeprecated( 'Revision::pageJoinCond' );
710 $this->assertEquals(
711 [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
712 Revision::pageJoinCond()
713 );
714 }
715
716 public function provideSelectFields() {
717 yield [
718 true,
719 [
720 'rev_id',
721 'rev_page',
722 'rev_text_id',
723 'rev_timestamp',
724 'rev_user_text',
725 'rev_user',
726 'rev_minor_edit',
727 'rev_deleted',
728 'rev_len',
729 'rev_parent_id',
730 'rev_sha1',
731 'rev_comment_text' => 'rev_comment',
732 'rev_comment_data' => 'NULL',
733 'rev_comment_cid' => 'NULL',
734 'rev_content_format',
735 'rev_content_model',
736 ]
737 ];
738 yield [
739 false,
740 [
741 'rev_id',
742 'rev_page',
743 'rev_text_id',
744 'rev_timestamp',
745 'rev_user_text',
746 'rev_user',
747 'rev_minor_edit',
748 'rev_deleted',
749 'rev_len',
750 'rev_parent_id',
751 'rev_sha1',
752 'rev_comment_text' => 'rev_comment',
753 'rev_comment_data' => 'NULL',
754 'rev_comment_cid' => 'NULL',
755 ]
756 ];
757 }
758
759 /**
760 * @dataProvider provideSelectFields
761 * @covers Revision::selectFields
762 * @todo a true unit test would mock CommentStore
763 */
764 public function testSelectFields( $contentHandlerUseDB, $expected ) {
765 $this->hideDeprecated( 'Revision::selectFields' );
766 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
767 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
768 $this->assertEquals( $expected, Revision::selectFields() );
769 }
770
771 public function provideSelectArchiveFields() {
772 yield [
773 true,
774 [
775 'ar_id',
776 'ar_page_id',
777 'ar_rev_id',
778 'ar_text',
779 'ar_text_id',
780 'ar_timestamp',
781 'ar_user_text',
782 'ar_user',
783 'ar_minor_edit',
784 'ar_deleted',
785 'ar_len',
786 'ar_parent_id',
787 'ar_sha1',
788 'ar_comment_text' => 'ar_comment',
789 'ar_comment_data' => 'NULL',
790 'ar_comment_cid' => 'NULL',
791 'ar_content_format',
792 'ar_content_model',
793 ]
794 ];
795 yield [
796 false,
797 [
798 'ar_id',
799 'ar_page_id',
800 'ar_rev_id',
801 'ar_text',
802 'ar_text_id',
803 'ar_timestamp',
804 'ar_user_text',
805 'ar_user',
806 'ar_minor_edit',
807 'ar_deleted',
808 'ar_len',
809 'ar_parent_id',
810 'ar_sha1',
811 'ar_comment_text' => 'ar_comment',
812 'ar_comment_data' => 'NULL',
813 'ar_comment_cid' => 'NULL',
814 ]
815 ];
816 }
817
818 /**
819 * @dataProvider provideSelectArchiveFields
820 * @covers Revision::selectArchiveFields
821 * @todo a true unit test would mock CommentStore
822 */
823 public function testSelectArchiveFields( $contentHandlerUseDB, $expected ) {
824 $this->hideDeprecated( 'Revision::selectArchiveFields' );
825 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
826 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
827 $this->assertEquals( $expected, Revision::selectArchiveFields() );
828 }
829
830 /**
831 * @covers Revision::selectTextFields
832 */
833 public function testSelectTextFields() {
834 $this->hideDeprecated( 'Revision::selectTextFields' );
835 $this->assertEquals(
836 [
837 'old_text',
838 'old_flags',
839 ],
840 Revision::selectTextFields()
841 );
842 }
843
844 /**
845 * @covers Revision::selectPageFields
846 */
847 public function testSelectPageFields() {
848 $this->hideDeprecated( 'Revision::selectPageFields' );
849 $this->assertEquals(
850 [
851 'page_namespace',
852 'page_title',
853 'page_id',
854 'page_latest',
855 'page_is_redirect',
856 'page_len',
857 ],
858 Revision::selectPageFields()
859 );
860 }
861
862 /**
863 * @covers Revision::selectUserFields
864 */
865 public function testSelectUserFields() {
866 $this->hideDeprecated( 'Revision::selectUserFields' );
867 $this->assertEquals(
868 [
869 'user_name',
870 ],
871 Revision::selectUserFields()
872 );
873 }
874
875 public function provideGetArchiveQueryInfo() {
876 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD' => [
877 [
878 'wgContentHandlerUseDB' => false,
879 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
880 ],
881 [
882 'tables' => [ 'archive' ],
883 'fields' => [
884 'ar_id',
885 'ar_page_id',
886 'ar_rev_id',
887 'ar_text',
888 'ar_text_id',
889 'ar_timestamp',
890 'ar_user_text',
891 'ar_user',
892 'ar_minor_edit',
893 'ar_deleted',
894 'ar_len',
895 'ar_parent_id',
896 'ar_sha1',
897 'ar_comment_text' => 'ar_comment',
898 'ar_comment_data' => 'NULL',
899 'ar_comment_cid' => 'NULL',
900 ],
901 'joins' => [],
902 ]
903 ];
904 yield 'wgContentHandlerUseDB true, wgCommentTableSchemaMigrationStage OLD' => [
905 [
906 'wgContentHandlerUseDB' => true,
907 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
908 ],
909 [
910 'tables' => [ 'archive' ],
911 'fields' => [
912 'ar_id',
913 'ar_page_id',
914 'ar_rev_id',
915 'ar_text',
916 'ar_text_id',
917 'ar_timestamp',
918 'ar_user_text',
919 'ar_user',
920 'ar_minor_edit',
921 'ar_deleted',
922 'ar_len',
923 'ar_parent_id',
924 'ar_sha1',
925 'ar_comment_text' => 'ar_comment',
926 'ar_comment_data' => 'NULL',
927 'ar_comment_cid' => 'NULL',
928 'ar_content_format',
929 'ar_content_model',
930 ],
931 'joins' => [],
932 ]
933 ];
934 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage WRITE_BOTH' => [
935 [
936 'wgContentHandlerUseDB' => false,
937 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
938 ],
939 [
940 'tables' => [
941 'archive',
942 'comment_ar_comment' => 'comment',
943 ],
944 'fields' => [
945 'ar_id',
946 'ar_page_id',
947 'ar_rev_id',
948 'ar_text',
949 'ar_text_id',
950 'ar_timestamp',
951 'ar_user_text',
952 'ar_user',
953 'ar_minor_edit',
954 'ar_deleted',
955 'ar_len',
956 'ar_parent_id',
957 'ar_sha1',
958 'ar_comment_text' => 'COALESCE( comment_ar_comment.comment_text, ar_comment )',
959 'ar_comment_data' => 'comment_ar_comment.comment_data',
960 'ar_comment_cid' => 'comment_ar_comment.comment_id',
961 ],
962 'joins' => [
963 'comment_ar_comment' => [
964 'LEFT JOIN',
965 'comment_ar_comment.comment_id = ar_comment_id',
966 ],
967 ],
968 ]
969 ];
970 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage WRITE_NEW' => [
971 [
972 'wgContentHandlerUseDB' => false,
973 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
974 ],
975 [
976 'tables' => [
977 'archive',
978 'comment_ar_comment' => 'comment',
979 ],
980 'fields' => [
981 'ar_id',
982 'ar_page_id',
983 'ar_rev_id',
984 'ar_text',
985 'ar_text_id',
986 'ar_timestamp',
987 'ar_user_text',
988 'ar_user',
989 'ar_minor_edit',
990 'ar_deleted',
991 'ar_len',
992 'ar_parent_id',
993 'ar_sha1',
994 'ar_comment_text' => 'COALESCE( comment_ar_comment.comment_text, ar_comment )',
995 'ar_comment_data' => 'comment_ar_comment.comment_data',
996 'ar_comment_cid' => 'comment_ar_comment.comment_id',
997 ],
998 'joins' => [
999 'comment_ar_comment' => [
1000 'LEFT JOIN',
1001 'comment_ar_comment.comment_id = ar_comment_id',
1002 ],
1003 ],
1004 ]
1005 ];
1006 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage NEW' => [
1007 [
1008 'wgContentHandlerUseDB' => false,
1009 'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
1010 ],
1011 [
1012 'tables' => [
1013 'archive',
1014 'comment_ar_comment' => 'comment',
1015 ],
1016 'fields' => [
1017 'ar_id',
1018 'ar_page_id',
1019 'ar_rev_id',
1020 'ar_text',
1021 'ar_text_id',
1022 'ar_timestamp',
1023 'ar_user_text',
1024 'ar_user',
1025 'ar_minor_edit',
1026 'ar_deleted',
1027 'ar_len',
1028 'ar_parent_id',
1029 'ar_sha1',
1030 'ar_comment_text' => 'comment_ar_comment.comment_text',
1031 'ar_comment_data' => 'comment_ar_comment.comment_data',
1032 'ar_comment_cid' => 'comment_ar_comment.comment_id',
1033 ],
1034 'joins' => [
1035 'comment_ar_comment' => [
1036 'JOIN',
1037 'comment_ar_comment.comment_id = ar_comment_id',
1038 ],
1039 ],
1040 ]
1041 ];
1042 }
1043
1044 /**
1045 * @covers Revision::getArchiveQueryInfo
1046 * @dataProvider provideGetArchiveQueryInfo
1047 */
1048 public function testGetArchiveQueryInfo( $globals, $expected ) {
1049 $this->setMwGlobals( $globals );
1050 $this->assertEquals(
1051 $expected,
1052 Revision::getArchiveQueryInfo()
1053 );
1054 }
1055
1056 public function provideGetQueryInfo() {
1057 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD, opts none' => [
1058 [
1059 'wgContentHandlerUseDB' => false,
1060 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1061 ],
1062 [],
1063 [
1064 'tables' => [ 'revision' ],
1065 'fields' => [
1066 'rev_id',
1067 'rev_page',
1068 'rev_text_id',
1069 'rev_timestamp',
1070 'rev_user_text',
1071 'rev_user',
1072 'rev_minor_edit',
1073 'rev_deleted',
1074 'rev_len',
1075 'rev_parent_id',
1076 'rev_sha1',
1077 'rev_comment_text' => 'rev_comment',
1078 'rev_comment_data' => 'NULL',
1079 'rev_comment_cid' => 'NULL',
1080 ],
1081 'joins' => [],
1082 ],
1083 ];
1084 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD, opts page' => [
1085 [
1086 'wgContentHandlerUseDB' => false,
1087 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1088 ],
1089 [ 'page' ],
1090 [
1091 'tables' => [ 'revision', 'page' ],
1092 'fields' => [
1093 'rev_id',
1094 'rev_page',
1095 'rev_text_id',
1096 'rev_timestamp',
1097 'rev_user_text',
1098 'rev_user',
1099 'rev_minor_edit',
1100 'rev_deleted',
1101 'rev_len',
1102 'rev_parent_id',
1103 'rev_sha1',
1104 'rev_comment_text' => 'rev_comment',
1105 'rev_comment_data' => 'NULL',
1106 'rev_comment_cid' => 'NULL',
1107 'page_namespace',
1108 'page_title',
1109 'page_id',
1110 'page_latest',
1111 'page_is_redirect',
1112 'page_len',
1113 ],
1114 'joins' => [
1115 'page' => [
1116 'INNER JOIN',
1117 [ 'page_id = rev_page' ],
1118 ],
1119 ],
1120 ],
1121 ];
1122 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD, opts user' => [
1123 [
1124 'wgContentHandlerUseDB' => false,
1125 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1126 ],
1127 [ 'user' ],
1128 [
1129 'tables' => [ 'revision', 'user' ],
1130 'fields' => [
1131 'rev_id',
1132 'rev_page',
1133 'rev_text_id',
1134 'rev_timestamp',
1135 'rev_user_text',
1136 'rev_user',
1137 'rev_minor_edit',
1138 'rev_deleted',
1139 'rev_len',
1140 'rev_parent_id',
1141 'rev_sha1',
1142 'rev_comment_text' => 'rev_comment',
1143 'rev_comment_data' => 'NULL',
1144 'rev_comment_cid' => 'NULL',
1145 'user_name',
1146 ],
1147 'joins' => [
1148 'user' => [
1149 'LEFT JOIN',
1150 [
1151 'rev_user != 0',
1152 'user_id = rev_user',
1153 ],
1154 ],
1155 ],
1156 ],
1157 ];
1158 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD, opts text' => [
1159 [
1160 'wgContentHandlerUseDB' => false,
1161 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1162 ],
1163 [ 'text' ],
1164 [
1165 'tables' => [ 'revision', 'text' ],
1166 'fields' => [
1167 'rev_id',
1168 'rev_page',
1169 'rev_text_id',
1170 'rev_timestamp',
1171 'rev_user_text',
1172 'rev_user',
1173 'rev_minor_edit',
1174 'rev_deleted',
1175 'rev_len',
1176 'rev_parent_id',
1177 'rev_sha1',
1178 'rev_comment_text' => 'rev_comment',
1179 'rev_comment_data' => 'NULL',
1180 'rev_comment_cid' => 'NULL',
1181 'old_text',
1182 'old_flags',
1183 ],
1184 'joins' => [
1185 'text' => [
1186 'INNER JOIN',
1187 [ 'rev_text_id=old_id' ],
1188 ],
1189 ],
1190 ],
1191 ];
1192 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage OLD, opts 3' => [
1193 [
1194 'wgContentHandlerUseDB' => false,
1195 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1196 ],
1197 [ 'text', 'page', 'user' ],
1198 [
1199 'tables' => [ 'revision', 'page', 'user', 'text' ],
1200 'fields' => [
1201 'rev_id',
1202 'rev_page',
1203 'rev_text_id',
1204 'rev_timestamp',
1205 'rev_user_text',
1206 'rev_user',
1207 'rev_minor_edit',
1208 'rev_deleted',
1209 'rev_len',
1210 'rev_parent_id',
1211 'rev_sha1',
1212 'rev_comment_text' => 'rev_comment',
1213 'rev_comment_data' => 'NULL',
1214 'rev_comment_cid' => 'NULL',
1215 'page_namespace',
1216 'page_title',
1217 'page_id',
1218 'page_latest',
1219 'page_is_redirect',
1220 'page_len',
1221 'user_name',
1222 'old_text',
1223 'old_flags',
1224 ],
1225 'joins' => [
1226 'page' => [
1227 'INNER JOIN',
1228 [ 'page_id = rev_page' ],
1229 ],
1230 'user' => [
1231 'LEFT JOIN',
1232 [
1233 'rev_user != 0',
1234 'user_id = rev_user',
1235 ],
1236 ],
1237 'text' => [
1238 'INNER JOIN',
1239 [ 'rev_text_id=old_id' ],
1240 ],
1241 ],
1242 ],
1243 ];
1244 yield 'wgContentHandlerUseDB true, wgCommentTableSchemaMigrationStage OLD, opts none' => [
1245 [
1246 'wgContentHandlerUseDB' => true,
1247 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
1248 ],
1249 [],
1250 [
1251 'tables' => [ 'revision' ],
1252 'fields' => [
1253 'rev_id',
1254 'rev_page',
1255 'rev_text_id',
1256 'rev_timestamp',
1257 'rev_user_text',
1258 'rev_user',
1259 'rev_minor_edit',
1260 'rev_deleted',
1261 'rev_len',
1262 'rev_parent_id',
1263 'rev_sha1',
1264 'rev_comment_text' => 'rev_comment',
1265 'rev_comment_data' => 'NULL',
1266 'rev_comment_cid' => 'NULL',
1267 'rev_content_format',
1268 'rev_content_model',
1269 ],
1270 'joins' => [],
1271 ],
1272 ];
1273 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage WRITE_BOTH, opts none' => [
1274 [
1275 'wgContentHandlerUseDB' => false,
1276 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
1277 ],
1278 [],
1279 [
1280 'tables' => [
1281 'revision',
1282 'temp_rev_comment' => 'revision_comment_temp',
1283 'comment_rev_comment' => 'comment',
1284 ],
1285 'fields' => [
1286 'rev_id',
1287 'rev_page',
1288 'rev_text_id',
1289 'rev_timestamp',
1290 'rev_user_text',
1291 'rev_user',
1292 'rev_minor_edit',
1293 'rev_deleted',
1294 'rev_len',
1295 'rev_parent_id',
1296 'rev_sha1',
1297 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
1298 'rev_comment_data' => 'comment_rev_comment.comment_data',
1299 'rev_comment_cid' => 'comment_rev_comment.comment_id',
1300 ],
1301 'joins' => [
1302 'temp_rev_comment' => [
1303 'LEFT JOIN',
1304 'temp_rev_comment.revcomment_rev = rev_id',
1305 ],
1306 'comment_rev_comment' => [
1307 'LEFT JOIN',
1308 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id',
1309 ],
1310 ],
1311 ],
1312 ];
1313 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage WRITE_NEW, opts none' => [
1314 [
1315 'wgContentHandlerUseDB' => false,
1316 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
1317 ],
1318 [],
1319 [
1320 'tables' => [
1321 'revision',
1322 'temp_rev_comment' => 'revision_comment_temp',
1323 'comment_rev_comment' => 'comment',
1324 ],
1325 'fields' => [
1326 'rev_id',
1327 'rev_page',
1328 'rev_text_id',
1329 'rev_timestamp',
1330 'rev_user_text',
1331 'rev_user',
1332 'rev_minor_edit',
1333 'rev_deleted',
1334 'rev_len',
1335 'rev_parent_id',
1336 'rev_sha1',
1337 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
1338 'rev_comment_data' => 'comment_rev_comment.comment_data',
1339 'rev_comment_cid' => 'comment_rev_comment.comment_id',
1340 ],
1341 'joins' => [
1342 'temp_rev_comment' => [
1343 'LEFT JOIN',
1344 'temp_rev_comment.revcomment_rev = rev_id',
1345 ],
1346 'comment_rev_comment' => [
1347 'LEFT JOIN',
1348 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id',
1349 ],
1350 ],
1351 ],
1352 ];
1353 yield 'wgContentHandlerUseDB false, wgCommentTableSchemaMigrationStage NEW, opts none' => [
1354 [
1355 'wgContentHandlerUseDB' => false,
1356 'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
1357 ],
1358 [],
1359 [
1360 'tables' => [
1361 'revision',
1362 'temp_rev_comment' => 'revision_comment_temp',
1363 'comment_rev_comment' => 'comment',
1364 ],
1365 'fields' => [
1366 'rev_id',
1367 'rev_page',
1368 'rev_text_id',
1369 'rev_timestamp',
1370 'rev_user_text',
1371 'rev_user',
1372 'rev_minor_edit',
1373 'rev_deleted',
1374 'rev_len',
1375 'rev_parent_id',
1376 'rev_sha1',
1377 'rev_comment_text' => 'comment_rev_comment.comment_text',
1378 'rev_comment_data' => 'comment_rev_comment.comment_data',
1379 'rev_comment_cid' => 'comment_rev_comment.comment_id',
1380 ],
1381 'joins' => [
1382 'temp_rev_comment' => [
1383 'JOIN',
1384 'temp_rev_comment.revcomment_rev = rev_id',
1385 ],
1386 'comment_rev_comment' => [
1387 'JOIN',
1388 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id',
1389 ],
1390 ],
1391 ],
1392 ];
1393 }
1394
1395 /**
1396 * @covers Revision::getQueryInfo
1397 * @dataProvider provideGetQueryInfo
1398 */
1399 public function testGetQueryInfo( $globals, $options, $expected ) {
1400 $this->setMwGlobals( $globals );
1401 $this->assertEquals(
1402 $expected,
1403 Revision::getQueryInfo( $options )
1404 );
1405 }
1406
1407 }