3 use MediaWiki\MediaWikiServices
;
4 use MediaWiki\Storage\BlobStoreFactory
;
5 use MediaWiki\Storage\MutableRevisionRecord
;
6 use MediaWiki\Storage\RevisionAccessException
;
7 use MediaWiki\Storage\RevisionRecord
;
8 use MediaWiki\Storage\RevisionStore
;
9 use MediaWiki\Storage\SlotRecord
;
10 use MediaWiki\Storage\SqlBlobStore
;
11 use Wikimedia\Rdbms\IDatabase
;
12 use Wikimedia\Rdbms\LoadBalancer
;
15 * Test cases in RevisionTest should not interact with the Database.
16 * For test cases that need Database interaction see RevisionDbTestBase.
18 class RevisionTest
extends MediaWikiTestCase
{
20 public function provideConstructFromArray() {
21 yield
'with text' => [
23 'text' => 'hello world.',
24 'content_model' => CONTENT_MODEL_JAVASCRIPT
27 yield
'with content' => [
29 'content' => new JavaScriptContent( 'hellow world.' )
32 // FIXME: test with and without user ID, and with a user object.
33 // We can't prepare that here though, since we don't yet have a dummy DB
37 * @param string $model
40 public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT
) {
41 $mock = $this->getMockBuilder( Title
::class )
42 ->disableOriginalConstructor()
44 $mock->expects( $this->any() )
45 ->method( 'getNamespace' )
46 ->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
47 $mock->expects( $this->any() )
48 ->method( 'getPrefixedText' )
49 ->will( $this->returnValue( 'RevisionTest' ) );
50 $mock->expects( $this->any() )
51 ->method( 'getDBkey' )
52 ->will( $this->returnValue( 'RevisionTest' ) );
53 $mock->expects( $this->any() )
54 ->method( 'getArticleID' )
55 ->will( $this->returnValue( 23 ) );
56 $mock->expects( $this->any() )
57 ->method( 'getModel' )
58 ->will( $this->returnValue( $model ) );
64 * @dataProvider provideConstructFromArray
65 * @covers Revision::__construct
66 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
68 public function testConstructFromArray( $rowArray ) {
69 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
70 $this->assertNotNull( $rev->getContent(), 'no content object available' );
71 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT
, $rev->getContent()->getModel() );
72 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT
, $rev->getContentModel() );
76 * @covers Revision::__construct
77 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
79 public function testConstructFromEmptyArray() {
80 $rev = new Revision( [], 0, $this->getMockTitle() );
81 $this->assertNull( $rev->getContent(), 'no content object should be available' );
85 * @covers Revision::__construct
86 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
88 public function testConstructFromArrayWithBadPageId() {
89 Wikimedia\
suppressWarnings();
90 $rev = new Revision( [ 'page' => 77777777 ] );
91 $this->assertSame( 77777777, $rev->getPage() );
92 Wikimedia\restoreWarnings
();
95 public function provideConstructFromArray_userSetAsExpected() {
96 yield
'no user defaults to wgUser' => [
98 'content' => new JavaScriptContent( 'hello world.' ),
103 yield
'user text and id' => [
105 'content' => new JavaScriptContent( 'hello world.' ),
106 'user_text' => 'SomeTextUserName',
113 yield
'user text only' => [
115 'content' => new JavaScriptContent( 'hello world.' ),
116 'user_text' => '111.111.111.111',
124 * @dataProvider provideConstructFromArray_userSetAsExpected
125 * @covers Revision::__construct
126 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
128 * @param array $rowArray
129 * @param mixed $expectedUserId null to expect the current wgUser ID
130 * @param mixed $expectedUserName null to expect the current wgUser name
132 public function testConstructFromArray_userSetAsExpected(
137 $testUser = $this->getTestUser()->getUser();
138 $this->setMwGlobals( 'wgUser', $testUser );
139 if ( $expectedUserId === null ) {
140 $expectedUserId = $testUser->getId();
142 if ( $expectedUserName === null ) {
143 $expectedUserName = $testUser->getName();
146 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
147 $this->assertEquals( $expectedUserId, $rev->getUser() );
148 $this->assertEquals( $expectedUserName, $rev->getUserText() );
151 public function provideConstructFromArrayThrowsExceptions() {
152 yield
'content and text_id both not empty' => [
154 'content' => new WikitextContent( 'GOAT' ),
155 'text_id' => 'someid',
157 new MWException( "Text already stored in external store (id someid), " .
158 "can't serialize content object" )
160 yield
'unknown user id and no user name' => [
162 'content' => new JavaScriptContent( 'hello world.' ),
165 new MWException( 'user_text not given, and unknown user ID 9989' )
167 yield
'with bad content object (class)' => [
168 [ 'content' => new stdClass() ],
169 new MWException( 'content field must contain a Content object.' )
171 yield
'with bad content object (string)' => [
172 [ 'content' => 'ImAGoat' ],
173 new MWException( 'content field must contain a Content object.' )
175 yield
'bad row format' => [
176 'imastring, not a row',
177 new InvalidArgumentException(
178 '$row must be a row object, an associative array, or a RevisionRecord'
184 * @dataProvider provideConstructFromArrayThrowsExceptions
185 * @covers Revision::__construct
186 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
188 public function testConstructFromArrayThrowsExceptions( $rowArray, Exception
$expectedException ) {
189 $this->setExpectedException(
190 get_class( $expectedException ),
191 $expectedException->getMessage(),
192 $expectedException->getCode()
194 new Revision( $rowArray, 0, $this->getMockTitle() );
198 * @covers Revision::__construct
199 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
201 public function testConstructFromNothing() {
202 $this->setExpectedException(
203 InvalidArgumentException
::class
208 public function provideConstructFromRow() {
209 yield
'Full construction' => [
213 'rev_text_id' => '2',
214 'rev_timestamp' => '20171017114835',
215 'rev_user_text' => '127.0.0.1',
217 'rev_minor_edit' => '0',
218 'rev_deleted' => '0',
220 'rev_parent_id' => '1',
221 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
222 'rev_comment_text' => 'Goat Comment!',
223 'rev_comment_data' => null,
224 'rev_comment_cid' => null,
225 'rev_content_format' => 'GOATFORMAT',
226 'rev_content_model' => 'GOATMODEL',
228 function ( RevisionTest
$testCase, Revision
$rev ) {
229 $testCase->assertSame( 42, $rev->getId() );
230 $testCase->assertSame( 23, $rev->getPage() );
231 $testCase->assertSame( 2, $rev->getTextId() );
232 $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
233 $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
234 $testCase->assertSame( 0, $rev->getUser() );
235 $testCase->assertSame( false, $rev->isMinor() );
236 $testCase->assertSame( false, $rev->isDeleted( Revision
::DELETED_TEXT
) );
237 $testCase->assertSame( 46, $rev->getSize() );
238 $testCase->assertSame( 1, $rev->getParentId() );
239 $testCase->assertSame( 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z', $rev->getSha1() );
240 $testCase->assertSame( 'Goat Comment!', $rev->getComment() );
241 $testCase->assertSame( 'GOATFORMAT', $rev->getContentFormat() );
242 $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
245 yield
'default field values' => [
249 'rev_text_id' => '2',
250 'rev_timestamp' => '20171017114835',
251 'rev_user_text' => '127.0.0.1',
253 'rev_minor_edit' => '0',
254 'rev_deleted' => '0',
255 'rev_comment_text' => 'Goat Comment!',
256 'rev_comment_data' => null,
257 'rev_comment_cid' => null,
259 function ( RevisionTest
$testCase, Revision
$rev ) {
260 // parent ID may be null
261 $testCase->assertSame( null, $rev->getParentId(), 'revision id' );
264 $testCase->assertSame( $rev->getTimestamp(), '20171017114835', 'timestamp' );
265 $testCase->assertSame( $rev->getUserText(), '127.0.0.1', 'user name' );
266 $testCase->assertSame( $rev->getUser(), 0, 'user id' );
267 $testCase->assertSame( $rev->getComment(), 'Goat Comment!' );
268 $testCase->assertSame( false, $rev->isMinor(), 'minor edit' );
269 $testCase->assertSame( 0, $rev->getVisibility(), 'visibility flags' );
272 $testCase->assertNotNull( $rev->getSize(), 'size' );
273 $testCase->assertNotNull( $rev->getSha1(), 'hash' );
275 // NOTE: model and format will be detected based on the namespace of the (mock) title
276 $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat(), 'format' );
277 $testCase->assertSame( 'wikitext', $rev->getContentModel(), 'model' );
283 * @dataProvider provideConstructFromRow
284 * @covers Revision::__construct
285 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
287 public function testConstructFromRow( array $arrayData, $assertions ) {
288 $data = 'Hello goat.'; // needs to match model and format
290 $blobStore = $this->getMockBuilder( SqlBlobStore
::class )
291 ->disableOriginalConstructor()
294 $blobStore->method( 'getBlob' )
295 ->will( $this->returnValue( $data ) );
297 $blobStore->method( 'getTextIdFromAddress' )
298 ->will( $this->returnCallback(
299 function ( $address ) {
300 // Turn "tt:1234" into 12345.
301 // Note that this must be functional so we can test getTextId().
302 // Ideally, we'd un-mock getTextIdFromAddress and use its actual implementation.
303 $parts = explode( ':', $address );
304 return (int)array_pop( $parts );
308 // Note override internal service, so RevisionStore uses it as well.
309 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
311 $row = (object)$arrayData;
312 $rev = new Revision( $row, 0, $this->getMockTitle() );
313 $assertions( $this, $rev );
317 * @covers Revision::__construct
318 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
320 public function testConstructFromRowWithBadPageId() {
321 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD
);
322 $this->overrideMwServices();
323 Wikimedia\
suppressWarnings();
324 $rev = new Revision( (object)[ 'rev_page' => 77777777 ] );
325 $this->assertSame( 77777777, $rev->getPage() );
326 Wikimedia\restoreWarnings
();
329 public function provideGetRevisionText() {
330 yield
'Generic test' => [
331 'This is a goat of revision text.',
334 'old_text' => 'This is a goat of revision text.',
339 public function provideGetId() {
351 * @dataProvider provideGetId
352 * @covers Revision::getId
354 public function testGetId( $rowArray, $expectedId ) {
355 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
356 $this->assertEquals( $expectedId, $rev->getId() );
359 public function provideSetId() {
360 yield
[ '123', 123 ];
365 * @dataProvider provideSetId
366 * @covers Revision::setId
368 public function testSetId( $input, $expected ) {
369 $rev = new Revision( [], 0, $this->getMockTitle() );
370 $rev->setId( $input );
371 $this->assertSame( $expected, $rev->getId() );
374 public function provideSetUserIdAndName() {
375 yield
[ '123', 123, 'GOaT' ];
376 yield
[ 456, 456, 'GOaT' ];
380 * @dataProvider provideSetUserIdAndName
381 * @covers Revision::setUserIdAndName
383 public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
384 $rev = new Revision( [], 0, $this->getMockTitle() );
385 $rev->setUserIdAndName( $inputId, $name );
386 $this->assertSame( $expectedId, $rev->getUser( Revision
::RAW
) );
387 $this->assertEquals( $name, $rev->getUserText( Revision
::RAW
) );
390 public function provideGetTextId() {
392 yield
[ [ 'text_id' => '123' ], 123 ];
393 yield
[ [ 'text_id' => 456 ], 456 ];
397 * @dataProvider provideGetTextId
398 * @covers Revision::getTextId()
400 public function testGetTextId( $rowArray, $expected ) {
401 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
402 $this->assertSame( $expected, $rev->getTextId() );
405 public function provideGetParentId() {
407 yield
[ [ 'parent_id' => '123' ], 123 ];
408 yield
[ [ 'parent_id' => 456 ], 456 ];
412 * @dataProvider provideGetParentId
413 * @covers Revision::getParentId()
415 public function testGetParentId( $rowArray, $expected ) {
416 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
417 $this->assertSame( $expected, $rev->getParentId() );
421 * @covers Revision::getRevisionText
422 * @dataProvider provideGetRevisionText
424 public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
427 Revision
::getRevisionText( (object)$rowData, $prefix, $wiki ) );
430 public function provideGetRevisionTextWithZlibExtension() {
431 yield
'Generic gzip test' => [
432 'This is a small goat of revision text.',
434 'old_flags' => 'gzip',
435 'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
441 * @covers Revision::getRevisionText
442 * @dataProvider provideGetRevisionTextWithZlibExtension
444 public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
445 $this->checkPHPExtension( 'zlib' );
446 $this->testGetRevisionText( $expected, $rowData );
449 private function getWANObjectCache() {
450 return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
454 * @return SqlBlobStore
456 private function getBlobStore() {
457 /** @var LoadBalancer $lb */
458 $lb = $this->getMockBuilder( LoadBalancer
::class )
459 ->disableOriginalConstructor()
462 $cache = $this->getWANObjectCache();
464 $blobStore = new SqlBlobStore( $lb, $cache );
468 private function mockBlobStoreFactory( $blobStore ) {
469 /** @var LoadBalancer $lb */
470 $factory = $this->getMockBuilder( BlobStoreFactory
::class )
471 ->disableOriginalConstructor()
473 $factory->expects( $this->any() )
474 ->method( 'newBlobStore' )
475 ->willReturn( $blobStore );
476 $factory->expects( $this->any() )
477 ->method( 'newSqlBlobStore' )
478 ->willReturn( $blobStore );
483 * @return RevisionStore
485 private function getRevisionStore() {
486 /** @var LoadBalancer $lb */
487 $lb = $this->getMockBuilder( LoadBalancer
::class )
488 ->disableOriginalConstructor()
491 $cache = $this->getWANObjectCache();
493 $blobStore = new RevisionStore(
495 $this->getBlobStore(),
497 MediaWikiServices
::getInstance()->getCommentStore()
502 public function provideGetRevisionTextWithLegacyEncoding() {
503 yield
'Utf8Native' => [
504 "Wiki est l'\xc3\xa9cole superieur !",
508 'old_flags' => 'utf-8',
509 'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
512 yield
'Utf8Legacy' => [
513 "Wiki est l'\xc3\xa9cole superieur !",
518 'old_text' => "Wiki est l'\xe9cole superieur !",
524 * @covers Revision::getRevisionText
525 * @dataProvider provideGetRevisionTextWithLegacyEncoding
527 public function testGetRevisionWithLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
528 $blobStore = $this->getBlobStore();
529 $blobStore->setLegacyEncoding( $encoding, Language
::factory( $lang ) );
530 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
532 $this->testGetRevisionText( $expected, $rowData );
535 public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
538 * Do not set the external flag!
539 * Otherwise, getRevisionText will hit the live database (if ExternalStore is enabled)!
541 yield
'Utf8NativeGzip' => [
542 "Wiki est l'\xc3\xa9cole superieur !",
546 'old_flags' => 'gzip,utf-8',
547 'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
550 yield
'Utf8LegacyGzip' => [
551 "Wiki est l'\xc3\xa9cole superieur !",
555 'old_flags' => 'gzip',
556 'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
562 * @covers Revision::getRevisionText
563 * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
565 public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
566 $this->checkPHPExtension( 'zlib' );
568 $blobStore = $this->getBlobStore();
569 $blobStore->setLegacyEncoding( $encoding, Language
::factory( $lang ) );
570 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
572 $this->testGetRevisionText( $expected, $rowData );
576 * @covers Revision::compressRevisionText
578 public function testCompressRevisionTextUtf8() {
580 $row->old_text
= "Wiki est l'\xc3\xa9cole superieur !";
581 $row->old_flags
= Revision
::compressRevisionText( $row->old_text
);
582 $this->assertTrue( false !== strpos( $row->old_flags
, 'utf-8' ),
583 "Flags should contain 'utf-8'" );
584 $this->assertFalse( false !== strpos( $row->old_flags
, 'gzip' ),
585 "Flags should not contain 'gzip'" );
586 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
587 $row->old_text
, "Direct check" );
588 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
589 Revision
::getRevisionText( $row ), "getRevisionText" );
593 * @covers Revision::compressRevisionText
595 public function testCompressRevisionTextUtf8Gzip() {
596 $this->checkPHPExtension( 'zlib' );
598 $blobStore = $this->getBlobStore();
599 $blobStore->setCompressBlobs( true );
600 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
603 $row->old_text
= "Wiki est l'\xc3\xa9cole superieur !";
604 $row->old_flags
= Revision
::compressRevisionText( $row->old_text
);
605 $this->assertTrue( false !== strpos( $row->old_flags
, 'utf-8' ),
606 "Flags should contain 'utf-8'" );
607 $this->assertTrue( false !== strpos( $row->old_flags
, 'gzip' ),
608 "Flags should contain 'gzip'" );
609 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
610 gzinflate( $row->old_text
), "Direct check" );
611 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
612 Revision
::getRevisionText( $row ), "getRevisionText" );
616 * @covers Revision::loadFromTitle
618 public function testLoadFromTitle() {
619 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD
);
620 $this->overrideMwServices();
621 $title = $this->getMockTitle();
624 'rev_id=page_latest',
625 'page_namespace' => $title->getNamespace(),
626 'page_title' => $title->getDBkey()
631 'rev_page' => $title->getArticleID(),
632 'rev_text_id' => '2',
633 'rev_timestamp' => '20171017114835',
634 'rev_user_text' => '127.0.0.1',
636 'rev_minor_edit' => '0',
637 'rev_deleted' => '0',
639 'rev_parent_id' => '1',
640 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
641 'rev_comment_text' => 'Goat Comment!',
642 'rev_comment_data' => null,
643 'rev_comment_cid' => null,
644 'rev_content_format' => 'GOATFORMAT',
645 'rev_content_model' => 'GOATMODEL',
648 $db = $this->getMock( IDatabase
::class );
649 $db->expects( $this->any() )
650 ->method( 'getDomainId' )
651 ->will( $this->returnValue( wfWikiID() ) );
652 $db->expects( $this->once() )
653 ->method( 'selectRow' )
655 $this->equalTo( [ 'revision', 'page', 'user' ] ),
656 // We don't really care about the fields are they come from the selectField methods
657 $this->isType( 'array' ),
658 $this->equalTo( $conditions ),
660 $this->stringContains( 'fetchRevisionRowFromConds' ),
661 // We don't really care about the options here
662 $this->isType( 'array' ),
663 // We don't really care about the join conds are they come from the joinCond methods
664 $this->isType( 'array' )
666 ->willReturn( $row );
668 $revision = Revision
::loadFromTitle( $db, $title );
670 $this->assertEquals( $title->getArticleID(), $revision->getTitle()->getArticleID() );
671 $this->assertEquals( $row->rev_id
, $revision->getId() );
672 $this->assertEquals( $row->rev_len
, $revision->getSize() );
673 $this->assertEquals( $row->rev_sha1
, $revision->getSha1() );
674 $this->assertEquals( $row->rev_parent_id
, $revision->getParentId() );
675 $this->assertEquals( $row->rev_timestamp
, $revision->getTimestamp() );
676 $this->assertEquals( $row->rev_comment_text
, $revision->getComment() );
677 $this->assertEquals( $row->rev_user_text
, $revision->getUserText() );
680 public function provideDecompressRevisionText() {
681 yield
'(no legacy encoding), false in false out' => [ false, false, [], false ];
682 yield
'(no legacy encoding), empty in empty out' => [ false, '', [], '' ];
683 yield
'(no legacy encoding), empty in empty out' => [ false, 'A', [], 'A' ];
684 yield
'(no legacy encoding), string in with gzip flag returns string' => [
685 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
686 false, "sttttr\002\022\000", [ 'gzip' ], 'AAAABBAAA',
688 yield
'(no legacy encoding), string in with object flag returns false' => [
689 // gzip string below generated with serialize( 'JOJO' )
690 false, "s:4:\"JOJO\";", [ 'object' ], false,
692 yield
'(no legacy encoding), serialized object in with object flag returns string' => [
694 // Using a TitleValue object as it has a getText method (which is needed)
695 serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
699 yield
'(no legacy encoding), serialized object in with object & gzip flag returns string' => [
701 // Using a TitleValue object as it has a getText method (which is needed)
702 gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
703 [ 'object', 'gzip' ],
706 yield
'(ISO-8859-1 encoding), string in string out' => [
708 iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
712 yield
'(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
714 gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
718 yield
'(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
720 serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
724 yield
'(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
726 gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
727 [ 'gzip', 'object' ],
733 * @dataProvider provideDecompressRevisionText
734 * @covers Revision::decompressRevisionText
736 * @param bool $legacyEncoding
738 * @param array $flags
739 * @param mixed $expected
741 public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
742 $blobStore = $this->getBlobStore();
743 if ( $legacyEncoding ) {
744 $blobStore->setLegacyEncoding( $legacyEncoding, Language
::factory( 'en' ) );
747 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
750 Revision
::decompressRevisionText( $text, $flags )
755 * @covers Revision::getRevisionText
757 public function testGetRevisionText_returnsFalseWhenNoTextField() {
758 $this->assertFalse( Revision
::getRevisionText( new stdClass() ) );
761 public function provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal() {
762 yield
'Just text' => [
763 (object)[ 'old_text' => 'SomeText' ],
767 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
768 yield
'gzip text' => [
770 'old_text' => "sttttr\002\022\000",
771 'old_flags' => 'gzip'
776 yield
'gzip text and different prefix' => [
778 'jojo_text' => "sttttr\002\022\000",
779 'jojo_flags' => 'gzip'
787 * @dataProvider provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal
788 * @covers Revision::getRevisionText
790 public function testGetRevisionText_returnsDecompressedTextFieldWhenNotExternal(
795 $this->assertSame( $expected, Revision
::getRevisionText( $row, $prefix ) );
798 public function provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts() {
799 yield
'Just some text' => [ 'someNonUrlText' ];
800 yield
'No second URL part' => [ 'someProtocol://' ];
804 * @dataProvider provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts
805 * @covers Revision::getRevisionText
807 public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
811 Revision
::getRevisionText(
814 'old_flags' => 'external',
821 * @covers Revision::getRevisionText
823 public function testGetRevisionText_external_noOldId() {
825 'ExternalStoreFactory',
826 new ExternalStoreFactory( [ 'ForTesting' ] )
830 Revision
::getRevisionText(
832 'old_text' => 'ForTesting://cluster1/12345',
833 'old_flags' => 'external,gzip',
840 * @covers Revision::getRevisionText
842 public function testGetRevisionText_external_oldId() {
843 $cache = $this->getWANObjectCache();
844 $this->setService( 'MainWANObjectCache', $cache );
847 'ExternalStoreFactory',
848 new ExternalStoreFactory( [ 'ForTesting' ] )
851 $lb = $this->getMockBuilder( LoadBalancer
::class )
852 ->disableOriginalConstructor()
855 $blobStore = new SqlBlobStore( $lb, $cache );
856 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
860 Revision
::getRevisionText(
862 'old_text' => 'ForTesting://cluster1/12345',
863 'old_flags' => 'external,gzip',
869 $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
870 $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
874 * @covers Revision::userJoinCond
876 public function testUserJoinCond() {
877 $this->hideDeprecated( 'Revision::userJoinCond' );
879 [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
880 Revision
::userJoinCond()
885 * @covers Revision::pageJoinCond
887 public function testPageJoinCond() {
888 $this->hideDeprecated( 'Revision::pageJoinCond' );
890 [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
891 Revision
::pageJoinCond()
895 private function overrideCommentStore() {
896 $mockStore = $this->getMockBuilder( CommentStore
::class )
897 ->disableOriginalConstructor()
899 $mockStore->expects( $this->any() )
900 ->method( 'getFields' )
901 ->willReturn( [ 'commentstore' => 'fields' ] );
902 $mockStore->expects( $this->any() )
903 ->method( 'getJoin' )
905 'tables' => [ 'commentstore' => 'table' ],
906 'fields' => [ 'commentstore' => 'field' ],
907 'joins' => [ 'commentstore' => 'join' ],
910 $this->setService( 'CommentStore', $mockStore );
913 public function provideSelectFields() {
928 'commentstore' => 'fields',
929 'rev_content_format',
947 'commentstore' => 'fields',
953 * @dataProvider provideSelectFields
954 * @covers Revision::selectFields
956 public function testSelectFields( $contentHandlerUseDB, $expected ) {
957 $this->hideDeprecated( 'Revision::selectFields' );
958 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
959 $this->overrideCommentStore();
960 $this->assertEquals( $expected, Revision
::selectFields() );
963 public function provideSelectArchiveFields() {
980 'commentstore' => 'fields',
1001 'commentstore' => 'fields',
1007 * @dataProvider provideSelectArchiveFields
1008 * @covers Revision::selectArchiveFields
1010 public function testSelectArchiveFields( $contentHandlerUseDB, $expected ) {
1011 $this->hideDeprecated( 'Revision::selectArchiveFields' );
1012 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
1013 $this->overrideCommentStore();
1014 $this->assertEquals( $expected, Revision
::selectArchiveFields() );
1018 * @covers Revision::selectTextFields
1020 public function testSelectTextFields() {
1021 $this->hideDeprecated( 'Revision::selectTextFields' );
1022 $this->assertEquals(
1027 Revision
::selectTextFields()
1032 * @covers Revision::selectPageFields
1034 public function testSelectPageFields() {
1035 $this->hideDeprecated( 'Revision::selectPageFields' );
1036 $this->assertEquals(
1045 Revision
::selectPageFields()
1050 * @covers Revision::selectUserFields
1052 public function testSelectUserFields() {
1053 $this->hideDeprecated( 'Revision::selectUserFields' );
1054 $this->assertEquals(
1058 Revision
::selectUserFields()
1062 public function provideGetArchiveQueryInfo() {
1063 yield
'wgContentHandlerUseDB false' => [
1065 'wgContentHandlerUseDB' => false,
1070 'commentstore' => 'table',
1088 'commentstore' => 'field'
1090 'joins' => [ 'commentstore' => 'join' ],
1093 yield
'wgContentHandlerUseDB true' => [
1095 'wgContentHandlerUseDB' => true,
1100 'commentstore' => 'table',
1118 'commentstore' => 'field',
1119 'ar_content_format',
1122 'joins' => [ 'commentstore' => 'join' ],
1128 * @covers Revision::getArchiveQueryInfo
1129 * @dataProvider provideGetArchiveQueryInfo
1131 public function testGetArchiveQueryInfo( $globals, $expected ) {
1132 $this->setMwGlobals( $globals );
1133 $this->overrideCommentStore();
1135 $revisionStore = $this->getRevisionStore();
1136 $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
1137 $this->setService( 'RevisionStore', $revisionStore );
1138 $this->assertEquals(
1140 Revision
::getArchiveQueryInfo()
1144 public function provideGetQueryInfo() {
1145 yield
'wgContentHandlerUseDB false, opts none' => [
1147 'wgContentHandlerUseDB' => false,
1151 'tables' => [ 'revision', 'commentstore' => 'table' ],
1164 'commentstore' => 'field',
1166 'joins' => [ 'commentstore' => 'join' ],
1169 yield
'wgContentHandlerUseDB false, opts page' => [
1171 'wgContentHandlerUseDB' => false,
1175 'tables' => [ 'revision', 'commentstore' => 'table', 'page' ],
1188 'commentstore' => 'field',
1199 [ 'page_id = rev_page' ],
1201 'commentstore' => 'join',
1205 yield
'wgContentHandlerUseDB false, opts user' => [
1207 'wgContentHandlerUseDB' => false,
1211 'tables' => [ 'revision', 'commentstore' => 'table', 'user' ],
1224 'commentstore' => 'field',
1232 'user_id = rev_user',
1235 'commentstore' => 'join',
1239 yield
'wgContentHandlerUseDB false, opts text' => [
1241 'wgContentHandlerUseDB' => false,
1245 'tables' => [ 'revision', 'commentstore' => 'table', 'text' ],
1258 'commentstore' => 'field',
1265 [ 'rev_text_id=old_id' ],
1267 'commentstore' => 'join',
1271 yield
'wgContentHandlerUseDB false, opts 3' => [
1273 'wgContentHandlerUseDB' => false,
1275 [ 'text', 'page', 'user' ],
1277 'tables' => [ 'revision', 'commentstore' => 'table', 'page', 'user', 'text' ],
1290 'commentstore' => 'field',
1304 [ 'page_id = rev_page' ],
1310 'user_id = rev_user',
1315 [ 'rev_text_id=old_id' ],
1317 'commentstore' => 'join',
1321 yield
'wgContentHandlerUseDB true, opts none' => [
1323 'wgContentHandlerUseDB' => true,
1327 'tables' => [ 'revision', 'commentstore' => 'table' ],
1340 'commentstore' => 'field',
1341 'rev_content_format',
1342 'rev_content_model',
1344 'joins' => [ 'commentstore' => 'join' ],
1350 * @covers Revision::getQueryInfo
1351 * @dataProvider provideGetQueryInfo
1353 public function testGetQueryInfo( $globals, $options, $expected ) {
1354 $this->setMwGlobals( $globals );
1355 $this->overrideCommentStore();
1357 $revisionStore = $this->getRevisionStore();
1358 $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
1359 $this->setService( 'RevisionStore', $revisionStore );
1361 $this->assertEquals(
1363 Revision
::getQueryInfo( $options )
1368 * @covers Revision::getSize
1370 public function testGetSize() {
1371 $title = $this->getMockTitle();
1373 $rec = new MutableRevisionRecord( $title );
1374 $rev = new Revision( $rec, 0, $title );
1376 $this->assertSame( 0, $rev->getSize(), 'Size of no slots is 0' );
1378 $rec->setSize( 13 );
1379 $this->assertSame( 13, $rev->getSize() );
1383 * @covers Revision::getSize
1385 public function testGetSize_failure() {
1386 $title = $this->getMockTitle();
1388 $rec = $this->getMockBuilder( RevisionRecord
::class )
1389 ->disableOriginalConstructor()
1392 $rec->method( 'getSize' )
1393 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1395 $rev = new Revision( $rec, 0, $title );
1396 $this->assertNull( $rev->getSize() );
1400 * @covers Revision::getSha1
1402 public function testGetSha1() {
1403 $title = $this->getMockTitle();
1405 $rec = new MutableRevisionRecord( $title );
1406 $rev = new Revision( $rec, 0, $title );
1408 $emptyHash = SlotRecord
::base36Sha1( '' );
1409 $this->assertSame( $emptyHash, $rev->getSha1(), 'Sha1 of no slots is hash of empty string' );
1411 $rec->setSha1( 'deadbeef' );
1412 $this->assertSame( 'deadbeef', $rev->getSha1() );
1416 * @covers Revision::getSha1
1418 public function testGetSha1_failure() {
1419 $title = $this->getMockTitle();
1421 $rec = $this->getMockBuilder( RevisionRecord
::class )
1422 ->disableOriginalConstructor()
1425 $rec->method( 'getSha1' )
1426 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1428 $rev = new Revision( $rec, 0, $title );
1429 $this->assertNull( $rev->getSha1() );
1433 * @covers Revision::getContent
1435 public function testGetContent() {
1436 $title = $this->getMockTitle();
1438 $rec = new MutableRevisionRecord( $title );
1439 $rev = new Revision( $rec, 0, $title );
1441 $this->assertNull( $rev->getContent(), 'Content of no slots is null' );
1443 $content = new TextContent( 'Hello Kittens!' );
1444 $rec->setContent( 'main', $content );
1445 $this->assertSame( $content, $rev->getContent() );
1449 * @covers Revision::getContent
1451 public function testGetContent_failure() {
1452 $title = $this->getMockTitle();
1454 $rec = $this->getMockBuilder( RevisionRecord
::class )
1455 ->disableOriginalConstructor()
1458 $rec->method( 'getContent' )
1459 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1461 $rev = new Revision( $rec, 0, $title );
1462 $this->assertNull( $rev->getContent() );