Merge "Fix sessionfailure i18n message during authentication"
[lhc/web/wiklou.git] / tests / phpunit / includes / RevisionTest.php
1 <?php
2
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;
13
14 /**
15 * Test cases in RevisionTest should not interact with the Database.
16 * For test cases that need Database interaction see RevisionDbTestBase.
17 */
18 class RevisionTest extends MediaWikiTestCase {
19
20 public function provideConstructFromArray() {
21 yield 'with text' => [
22 [
23 'text' => 'hello world.',
24 'content_model' => CONTENT_MODEL_JAVASCRIPT
25 ],
26 ];
27 yield 'with content' => [
28 [
29 'content' => new JavaScriptContent( 'hellow world.' )
30 ],
31 ];
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
34 }
35
36 /**
37 * @param string $model
38 * @return Title
39 */
40 public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
41 $mock = $this->getMockBuilder( Title::class )
42 ->disableOriginalConstructor()
43 ->getMock();
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 ) );
59
60 return $mock;
61 }
62
63 /**
64 * @dataProvider provideConstructFromArray
65 * @covers Revision::__construct
66 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
67 */
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() );
73 }
74
75 /**
76 * @covers Revision::__construct
77 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
78 */
79 public function testConstructFromEmptyArray() {
80 $rev = new Revision( [], 0, $this->getMockTitle() );
81 $this->assertNull( $rev->getContent(), 'no content object should be available' );
82 }
83
84 /**
85 * @covers Revision::__construct
86 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
87 */
88 public function testConstructFromArrayWithBadPageId() {
89 MediaWiki\suppressWarnings();
90 $rev = new Revision( [ 'page' => 77777777 ] );
91 $this->assertSame( 77777777, $rev->getPage() );
92 MediaWiki\restoreWarnings();
93 }
94
95 public function provideConstructFromArray_userSetAsExpected() {
96 yield 'no user defaults to wgUser' => [
97 [
98 'content' => new JavaScriptContent( 'hello world.' ),
99 ],
100 null,
101 null,
102 ];
103 yield 'user text and id' => [
104 [
105 'content' => new JavaScriptContent( 'hello world.' ),
106 'user_text' => 'SomeTextUserName',
107 'user' => 99,
108
109 ],
110 99,
111 'SomeTextUserName',
112 ];
113 yield 'user text only' => [
114 [
115 'content' => new JavaScriptContent( 'hello world.' ),
116 'user_text' => '111.111.111.111',
117 ],
118 0,
119 '111.111.111.111',
120 ];
121 }
122
123 /**
124 * @dataProvider provideConstructFromArray_userSetAsExpected
125 * @covers Revision::__construct
126 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
127 *
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
131 */
132 public function testConstructFromArray_userSetAsExpected(
133 array $rowArray,
134 $expectedUserId,
135 $expectedUserName
136 ) {
137 $testUser = $this->getTestUser()->getUser();
138 $this->setMwGlobals( 'wgUser', $testUser );
139 if ( $expectedUserId === null ) {
140 $expectedUserId = $testUser->getId();
141 }
142 if ( $expectedUserName === null ) {
143 $expectedUserName = $testUser->getName();
144 }
145
146 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
147 $this->assertEquals( $expectedUserId, $rev->getUser() );
148 $this->assertEquals( $expectedUserName, $rev->getUserText() );
149 }
150
151 public function provideConstructFromArrayThrowsExceptions() {
152 yield 'content and text_id both not empty' => [
153 [
154 'content' => new WikitextContent( 'GOAT' ),
155 'text_id' => 'someid',
156 ],
157 new MWException( "Text already stored in external store (id someid), " .
158 "can't serialize content object" )
159 ];
160 yield 'unknown user id and no user name' => [
161 [
162 'content' => new JavaScriptContent( 'hello world.' ),
163 'user' => 9989,
164 ],
165 new MWException( 'user_text not given, and unknown user ID 9989' )
166 ];
167 yield 'with bad content object (class)' => [
168 [ 'content' => new stdClass() ],
169 new MWException( 'content field must contain a Content object.' )
170 ];
171 yield 'with bad content object (string)' => [
172 [ 'content' => 'ImAGoat' ],
173 new MWException( 'content field must contain a Content object.' )
174 ];
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'
179 )
180 ];
181 }
182
183 /**
184 * @dataProvider provideConstructFromArrayThrowsExceptions
185 * @covers Revision::__construct
186 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
187 */
188 public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
189 $this->setExpectedException(
190 get_class( $expectedException ),
191 $expectedException->getMessage(),
192 $expectedException->getCode()
193 );
194 new Revision( $rowArray, 0, $this->getMockTitle() );
195 }
196
197 /**
198 * @covers Revision::__construct
199 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
200 */
201 public function testConstructFromNothing() {
202 $this->setExpectedException(
203 InvalidArgumentException::class
204 );
205 new Revision( [] );
206 }
207
208 public function provideConstructFromRow() {
209 yield 'Full construction' => [
210 [
211 'rev_id' => '42',
212 'rev_page' => '23',
213 'rev_text_id' => '2',
214 'rev_timestamp' => '20171017114835',
215 'rev_user_text' => '127.0.0.1',
216 'rev_user' => '0',
217 'rev_minor_edit' => '0',
218 'rev_deleted' => '0',
219 'rev_len' => '46',
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',
227 ],
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() );
243 }
244 ];
245 yield 'default field values' => [
246 [
247 'rev_id' => '42',
248 'rev_page' => '23',
249 'rev_text_id' => '2',
250 'rev_timestamp' => '20171017114835',
251 'rev_user_text' => '127.0.0.1',
252 'rev_user' => '0',
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,
258 ],
259 function ( RevisionTest $testCase, Revision $rev ) {
260 // parent ID may be null
261 $testCase->assertSame( null, $rev->getParentId(), 'revision id' );
262
263 // given fields
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' );
270
271 // computed fields
272 $testCase->assertNotNull( $rev->getSize(), 'size' );
273 $testCase->assertNotNull( $rev->getSha1(), 'hash' );
274
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' );
278 }
279 ];
280 }
281
282 /**
283 * @dataProvider provideConstructFromRow
284 * @covers Revision::__construct
285 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
286 */
287 public function testConstructFromRow( array $arrayData, $assertions ) {
288 $data = 'Hello goat.'; // needs to match model and format
289
290 $blobStore = $this->getMockBuilder( SqlBlobStore::class )
291 ->disableOriginalConstructor()
292 ->getMock();
293
294 $blobStore->method( 'getBlob' )
295 ->will( $this->returnValue( $data ) );
296
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 );
305 }
306 ) );
307
308 // Note override internal service, so RevisionStore uses it as well.
309 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
310
311 $row = (object)$arrayData;
312 $rev = new Revision( $row, 0, $this->getMockTitle() );
313 $assertions( $this, $rev );
314 }
315
316 /**
317 * @covers Revision::__construct
318 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
319 */
320 public function testConstructFromRowWithBadPageId() {
321 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
322 $this->overrideMwServices();
323 MediaWiki\suppressWarnings();
324 $rev = new Revision( (object)[ 'rev_page' => 77777777 ] );
325 $this->assertSame( 77777777, $rev->getPage() );
326 MediaWiki\restoreWarnings();
327 }
328
329 public function provideGetRevisionText() {
330 yield 'Generic test' => [
331 'This is a goat of revision text.',
332 [
333 'old_flags' => '',
334 'old_text' => 'This is a goat of revision text.',
335 ],
336 ];
337 }
338
339 public function provideGetId() {
340 yield [
341 [],
342 null
343 ];
344 yield [
345 [ 'id' => 998 ],
346 998
347 ];
348 }
349
350 /**
351 * @dataProvider provideGetId
352 * @covers Revision::getId
353 */
354 public function testGetId( $rowArray, $expectedId ) {
355 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
356 $this->assertEquals( $expectedId, $rev->getId() );
357 }
358
359 public function provideSetId() {
360 yield [ '123', 123 ];
361 yield [ 456, 456 ];
362 }
363
364 /**
365 * @dataProvider provideSetId
366 * @covers Revision::setId
367 */
368 public function testSetId( $input, $expected ) {
369 $rev = new Revision( [], 0, $this->getMockTitle() );
370 $rev->setId( $input );
371 $this->assertSame( $expected, $rev->getId() );
372 }
373
374 public function provideSetUserIdAndName() {
375 yield [ '123', 123, 'GOaT' ];
376 yield [ 456, 456, 'GOaT' ];
377 }
378
379 /**
380 * @dataProvider provideSetUserIdAndName
381 * @covers Revision::setUserIdAndName
382 */
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 ) );
388 }
389
390 public function provideGetTextId() {
391 yield [ [], null ];
392 yield [ [ 'text_id' => '123' ], 123 ];
393 yield [ [ 'text_id' => 456 ], 456 ];
394 }
395
396 /**
397 * @dataProvider provideGetTextId
398 * @covers Revision::getTextId()
399 */
400 public function testGetTextId( $rowArray, $expected ) {
401 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
402 $this->assertSame( $expected, $rev->getTextId() );
403 }
404
405 public function provideGetParentId() {
406 yield [ [], null ];
407 yield [ [ 'parent_id' => '123' ], 123 ];
408 yield [ [ 'parent_id' => 456 ], 456 ];
409 }
410
411 /**
412 * @dataProvider provideGetParentId
413 * @covers Revision::getParentId()
414 */
415 public function testGetParentId( $rowArray, $expected ) {
416 $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
417 $this->assertSame( $expected, $rev->getParentId() );
418 }
419
420 /**
421 * @covers Revision::getRevisionText
422 * @dataProvider provideGetRevisionText
423 */
424 public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
425 $this->assertEquals(
426 $expected,
427 Revision::getRevisionText( (object)$rowData, $prefix, $wiki ) );
428 }
429
430 public function provideGetRevisionTextWithZlibExtension() {
431 yield 'Generic gzip test' => [
432 'This is a small goat of revision text.',
433 [
434 'old_flags' => 'gzip',
435 'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
436 ],
437 ];
438 }
439
440 /**
441 * @covers Revision::getRevisionText
442 * @dataProvider provideGetRevisionTextWithZlibExtension
443 */
444 public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
445 $this->checkPHPExtension( 'zlib' );
446 $this->testGetRevisionText( $expected, $rowData );
447 }
448
449 private function getWANObjectCache() {
450 return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
451 }
452
453 /**
454 * @return SqlBlobStore
455 */
456 private function getBlobStore() {
457 /** @var LoadBalancer $lb */
458 $lb = $this->getMockBuilder( LoadBalancer::class )
459 ->disableOriginalConstructor()
460 ->getMock();
461
462 $cache = $this->getWANObjectCache();
463
464 $blobStore = new SqlBlobStore( $lb, $cache );
465 return $blobStore;
466 }
467
468 private function mockBlobStoreFactory( $blobStore ) {
469 /** @var LoadBalancer $lb */
470 $factory = $this->getMockBuilder( BlobStoreFactory::class )
471 ->disableOriginalConstructor()
472 ->getMock();
473 $factory->expects( $this->any() )
474 ->method( 'newBlobStore' )
475 ->willReturn( $blobStore );
476 $factory->expects( $this->any() )
477 ->method( 'newSqlBlobStore' )
478 ->willReturn( $blobStore );
479 return $factory;
480 }
481
482 /**
483 * @return RevisionStore
484 */
485 private function getRevisionStore() {
486 /** @var LoadBalancer $lb */
487 $lb = $this->getMockBuilder( LoadBalancer::class )
488 ->disableOriginalConstructor()
489 ->getMock();
490
491 $cache = $this->getWANObjectCache();
492
493 $blobStore = new RevisionStore(
494 $lb,
495 $this->getBlobStore(),
496 $cache,
497 MediaWikiServices::getInstance()->getCommentStore()
498 );
499 return $blobStore;
500 }
501
502 public function provideGetRevisionTextWithLegacyEncoding() {
503 yield 'Utf8Native' => [
504 "Wiki est l'\xc3\xa9cole superieur !",
505 'fr',
506 'iso-8859-1',
507 [
508 'old_flags' => 'utf-8',
509 'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
510 ]
511 ];
512 yield 'Utf8Legacy' => [
513 "Wiki est l'\xc3\xa9cole superieur !",
514 'fr',
515 'iso-8859-1',
516 [
517 'old_flags' => '',
518 'old_text' => "Wiki est l'\xe9cole superieur !",
519 ]
520 ];
521 }
522
523 /**
524 * @covers Revision::getRevisionText
525 * @dataProvider provideGetRevisionTextWithLegacyEncoding
526 */
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 ) );
531
532 $this->testGetRevisionText( $expected, $rowData );
533 }
534
535 public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
536 /**
537 * WARNING!
538 * Do not set the external flag!
539 * Otherwise, getRevisionText will hit the live database (if ExternalStore is enabled)!
540 */
541 yield 'Utf8NativeGzip' => [
542 "Wiki est l'\xc3\xa9cole superieur !",
543 'fr',
544 'iso-8859-1',
545 [
546 'old_flags' => 'gzip,utf-8',
547 'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
548 ]
549 ];
550 yield 'Utf8LegacyGzip' => [
551 "Wiki est l'\xc3\xa9cole superieur !",
552 'fr',
553 'iso-8859-1',
554 [
555 'old_flags' => 'gzip',
556 'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
557 ]
558 ];
559 }
560
561 /**
562 * @covers Revision::getRevisionText
563 * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
564 */
565 public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
566 $this->checkPHPExtension( 'zlib' );
567
568 $blobStore = $this->getBlobStore();
569 $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
570 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
571
572 $this->testGetRevisionText( $expected, $rowData );
573 }
574
575 /**
576 * @covers Revision::compressRevisionText
577 */
578 public function testCompressRevisionTextUtf8() {
579 $row = new stdClass;
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" );
590 }
591
592 /**
593 * @covers Revision::compressRevisionText
594 */
595 public function testCompressRevisionTextUtf8Gzip() {
596 $this->checkPHPExtension( 'zlib' );
597
598 $blobStore = $this->getBlobStore();
599 $blobStore->setCompressBlobs( true );
600 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
601
602 $row = new stdClass;
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" );
613 }
614
615 /**
616 * @covers Revision::loadFromTitle
617 */
618 public function testLoadFromTitle() {
619 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
620 $this->overrideMwServices();
621 $title = $this->getMockTitle();
622
623 $conditions = [
624 'rev_id=page_latest',
625 'page_namespace' => $title->getNamespace(),
626 'page_title' => $title->getDBkey()
627 ];
628
629 $row = (object)[
630 'rev_id' => '42',
631 'rev_page' => $title->getArticleID(),
632 'rev_text_id' => '2',
633 'rev_timestamp' => '20171017114835',
634 'rev_user_text' => '127.0.0.1',
635 'rev_user' => '0',
636 'rev_minor_edit' => '0',
637 'rev_deleted' => '0',
638 'rev_len' => '46',
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',
646 ];
647
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' )
654 ->with(
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 ),
659 // Method name
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' )
665 )
666 ->willReturn( $row );
667
668 $revision = Revision::loadFromTitle( $db, $title );
669
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() );
678 }
679
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',
687 ];
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,
691 ];
692 yield '(no legacy encoding), serialized object in with object flag returns string' => [
693 false,
694 // Using a TitleValue object as it has a getText method (which is needed)
695 serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
696 [ 'object' ],
697 'HHJJDDFF',
698 ];
699 yield '(no legacy encoding), serialized object in with object & gzip flag returns string' => [
700 false,
701 // Using a TitleValue object as it has a getText method (which is needed)
702 gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
703 [ 'object', 'gzip' ],
704 '8219JJJ840',
705 ];
706 yield '(ISO-8859-1 encoding), string in string out' => [
707 'ISO-8859-1',
708 iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
709 [],
710 '1®Àþ1',
711 ];
712 yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
713 'ISO-8859-1',
714 gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
715 [ 'gzip' ],
716 '4®Àþ4',
717 ];
718 yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
719 'ISO-8859-1',
720 serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
721 [ 'object' ],
722 '3®Àþ3',
723 ];
724 yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
725 'ISO-8859-1',
726 gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
727 [ 'gzip', 'object' ],
728 '2®Àþ2',
729 ];
730 }
731
732 /**
733 * @dataProvider provideDecompressRevisionText
734 * @covers Revision::decompressRevisionText
735 *
736 * @param bool $legacyEncoding
737 * @param mixed $text
738 * @param array $flags
739 * @param mixed $expected
740 */
741 public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
742 $blobStore = $this->getBlobStore();
743 if ( $legacyEncoding ) {
744 $blobStore->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
745 }
746
747 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
748 $this->assertSame(
749 $expected,
750 Revision::decompressRevisionText( $text, $flags )
751 );
752 }
753
754 /**
755 * @covers Revision::getRevisionText
756 */
757 public function testGetRevisionText_returnsFalseWhenNoTextField() {
758 $this->assertFalse( Revision::getRevisionText( new stdClass() ) );
759 }
760
761 public function provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal() {
762 yield 'Just text' => [
763 (object)[ 'old_text' => 'SomeText' ],
764 'old_',
765 'SomeText'
766 ];
767 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
768 yield 'gzip text' => [
769 (object)[
770 'old_text' => "sttttr\002\022\000",
771 'old_flags' => 'gzip'
772 ],
773 'old_',
774 'AAAABBAAA'
775 ];
776 yield 'gzip text and different prefix' => [
777 (object)[
778 'jojo_text' => "sttttr\002\022\000",
779 'jojo_flags' => 'gzip'
780 ],
781 'jojo_',
782 'AAAABBAAA'
783 ];
784 }
785
786 /**
787 * @dataProvider provideTestGetRevisionText_returnsDecompressedTextFieldWhenNotExternal
788 * @covers Revision::getRevisionText
789 */
790 public function testGetRevisionText_returnsDecompressedTextFieldWhenNotExternal(
791 $row,
792 $prefix,
793 $expected
794 ) {
795 $this->assertSame( $expected, Revision::getRevisionText( $row, $prefix ) );
796 }
797
798 public function provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts() {
799 yield 'Just some text' => [ 'someNonUrlText' ];
800 yield 'No second URL part' => [ 'someProtocol://' ];
801 }
802
803 /**
804 * @dataProvider provideTestGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts
805 * @covers Revision::getRevisionText
806 */
807 public function testGetRevisionText_external_returnsFalseWhenNotEnoughUrlParts(
808 $text
809 ) {
810 $this->assertFalse(
811 Revision::getRevisionText(
812 (object)[
813 'old_text' => $text,
814 'old_flags' => 'external',
815 ]
816 )
817 );
818 }
819
820 /**
821 * @covers Revision::getRevisionText
822 */
823 public function testGetRevisionText_external_noOldId() {
824 $this->setService(
825 'ExternalStoreFactory',
826 new ExternalStoreFactory( [ 'ForTesting' ] )
827 );
828 $this->assertSame(
829 'AAAABBAAA',
830 Revision::getRevisionText(
831 (object)[
832 'old_text' => 'ForTesting://cluster1/12345',
833 'old_flags' => 'external,gzip',
834 ]
835 )
836 );
837 }
838
839 /**
840 * @covers Revision::getRevisionText
841 */
842 public function testGetRevisionText_external_oldId() {
843 $cache = $this->getWANObjectCache();
844 $this->setService( 'MainWANObjectCache', $cache );
845
846 $this->setService(
847 'ExternalStoreFactory',
848 new ExternalStoreFactory( [ 'ForTesting' ] )
849 );
850
851 $lb = $this->getMockBuilder( LoadBalancer::class )
852 ->disableOriginalConstructor()
853 ->getMock();
854
855 $blobStore = new SqlBlobStore( $lb, $cache );
856 $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
857
858 $this->assertSame(
859 'AAAABBAAA',
860 Revision::getRevisionText(
861 (object)[
862 'old_text' => 'ForTesting://cluster1/12345',
863 'old_flags' => 'external,gzip',
864 'old_id' => '7777',
865 ]
866 )
867 );
868
869 $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
870 $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
871 }
872
873 /**
874 * @covers Revision::userJoinCond
875 */
876 public function testUserJoinCond() {
877 $this->hideDeprecated( 'Revision::userJoinCond' );
878 $this->assertEquals(
879 [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
880 Revision::userJoinCond()
881 );
882 }
883
884 /**
885 * @covers Revision::pageJoinCond
886 */
887 public function testPageJoinCond() {
888 $this->hideDeprecated( 'Revision::pageJoinCond' );
889 $this->assertEquals(
890 [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
891 Revision::pageJoinCond()
892 );
893 }
894
895 private function overrideCommentStore() {
896 $mockStore = $this->getMockBuilder( CommentStore::class )
897 ->disableOriginalConstructor()
898 ->getMock();
899 $mockStore->expects( $this->any() )
900 ->method( 'getFields' )
901 ->willReturn( [ 'commentstore' => 'fields' ] );
902 $mockStore->expects( $this->any() )
903 ->method( 'getJoin' )
904 ->willReturn( [
905 'tables' => [ 'commentstore' => 'table' ],
906 'fields' => [ 'commentstore' => 'field' ],
907 'joins' => [ 'commentstore' => 'join' ],
908 ] );
909
910 $this->setService( 'CommentStore', $mockStore );
911 }
912
913 public function provideSelectFields() {
914 yield [
915 true,
916 [
917 'rev_id',
918 'rev_page',
919 'rev_text_id',
920 'rev_timestamp',
921 'rev_user_text',
922 'rev_user',
923 'rev_minor_edit',
924 'rev_deleted',
925 'rev_len',
926 'rev_parent_id',
927 'rev_sha1',
928 'commentstore' => 'fields',
929 'rev_content_format',
930 'rev_content_model',
931 ]
932 ];
933 yield [
934 false,
935 [
936 'rev_id',
937 'rev_page',
938 'rev_text_id',
939 'rev_timestamp',
940 'rev_user_text',
941 'rev_user',
942 'rev_minor_edit',
943 'rev_deleted',
944 'rev_len',
945 'rev_parent_id',
946 'rev_sha1',
947 'commentstore' => 'fields',
948 ]
949 ];
950 }
951
952 /**
953 * @dataProvider provideSelectFields
954 * @covers Revision::selectFields
955 */
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() );
961 }
962
963 public function provideSelectArchiveFields() {
964 yield [
965 true,
966 [
967 'ar_id',
968 'ar_page_id',
969 'ar_rev_id',
970 'ar_text',
971 'ar_text_id',
972 'ar_timestamp',
973 'ar_user_text',
974 'ar_user',
975 'ar_minor_edit',
976 'ar_deleted',
977 'ar_len',
978 'ar_parent_id',
979 'ar_sha1',
980 'commentstore' => 'fields',
981 'ar_content_format',
982 'ar_content_model',
983 ]
984 ];
985 yield [
986 false,
987 [
988 'ar_id',
989 'ar_page_id',
990 'ar_rev_id',
991 'ar_text',
992 'ar_text_id',
993 'ar_timestamp',
994 'ar_user_text',
995 'ar_user',
996 'ar_minor_edit',
997 'ar_deleted',
998 'ar_len',
999 'ar_parent_id',
1000 'ar_sha1',
1001 'commentstore' => 'fields',
1002 ]
1003 ];
1004 }
1005
1006 /**
1007 * @dataProvider provideSelectArchiveFields
1008 * @covers Revision::selectArchiveFields
1009 */
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() );
1015 }
1016
1017 /**
1018 * @covers Revision::selectTextFields
1019 */
1020 public function testSelectTextFields() {
1021 $this->hideDeprecated( 'Revision::selectTextFields' );
1022 $this->assertEquals(
1023 [
1024 'old_text',
1025 'old_flags',
1026 ],
1027 Revision::selectTextFields()
1028 );
1029 }
1030
1031 /**
1032 * @covers Revision::selectPageFields
1033 */
1034 public function testSelectPageFields() {
1035 $this->hideDeprecated( 'Revision::selectPageFields' );
1036 $this->assertEquals(
1037 [
1038 'page_namespace',
1039 'page_title',
1040 'page_id',
1041 'page_latest',
1042 'page_is_redirect',
1043 'page_len',
1044 ],
1045 Revision::selectPageFields()
1046 );
1047 }
1048
1049 /**
1050 * @covers Revision::selectUserFields
1051 */
1052 public function testSelectUserFields() {
1053 $this->hideDeprecated( 'Revision::selectUserFields' );
1054 $this->assertEquals(
1055 [
1056 'user_name',
1057 ],
1058 Revision::selectUserFields()
1059 );
1060 }
1061
1062 public function provideGetArchiveQueryInfo() {
1063 yield 'wgContentHandlerUseDB false' => [
1064 [
1065 'wgContentHandlerUseDB' => false,
1066 ],
1067 [
1068 'tables' => [
1069 'archive',
1070 'commentstore' => 'table',
1071 ],
1072 'fields' => [
1073 'ar_id',
1074 'ar_page_id',
1075 'ar_namespace',
1076 'ar_title',
1077 'ar_rev_id',
1078 'ar_text',
1079 'ar_text_id',
1080 'ar_timestamp',
1081 'ar_user_text',
1082 'ar_user',
1083 'ar_minor_edit',
1084 'ar_deleted',
1085 'ar_len',
1086 'ar_parent_id',
1087 'ar_sha1',
1088 'commentstore' => 'field'
1089 ],
1090 'joins' => [ 'commentstore' => 'join' ],
1091 ]
1092 ];
1093 yield 'wgContentHandlerUseDB true' => [
1094 [
1095 'wgContentHandlerUseDB' => true,
1096 ],
1097 [
1098 'tables' => [
1099 'archive',
1100 'commentstore' => 'table',
1101 ],
1102 'fields' => [
1103 'ar_id',
1104 'ar_page_id',
1105 'ar_namespace',
1106 'ar_title',
1107 'ar_rev_id',
1108 'ar_text',
1109 'ar_text_id',
1110 'ar_timestamp',
1111 'ar_user_text',
1112 'ar_user',
1113 'ar_minor_edit',
1114 'ar_deleted',
1115 'ar_len',
1116 'ar_parent_id',
1117 'ar_sha1',
1118 'commentstore' => 'field',
1119 'ar_content_format',
1120 'ar_content_model',
1121 ],
1122 'joins' => [ 'commentstore' => 'join' ],
1123 ]
1124 ];
1125 }
1126
1127 /**
1128 * @covers Revision::getArchiveQueryInfo
1129 * @dataProvider provideGetArchiveQueryInfo
1130 */
1131 public function testGetArchiveQueryInfo( $globals, $expected ) {
1132 $this->setMwGlobals( $globals );
1133 $this->overrideCommentStore();
1134
1135 $revisionStore = $this->getRevisionStore();
1136 $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
1137 $this->setService( 'RevisionStore', $revisionStore );
1138 $this->assertEquals(
1139 $expected,
1140 Revision::getArchiveQueryInfo()
1141 );
1142 }
1143
1144 public function provideGetQueryInfo() {
1145 yield 'wgContentHandlerUseDB false, opts none' => [
1146 [
1147 'wgContentHandlerUseDB' => false,
1148 ],
1149 [],
1150 [
1151 'tables' => [ 'revision', 'commentstore' => 'table' ],
1152 'fields' => [
1153 'rev_id',
1154 'rev_page',
1155 'rev_text_id',
1156 'rev_timestamp',
1157 'rev_user_text',
1158 'rev_user',
1159 'rev_minor_edit',
1160 'rev_deleted',
1161 'rev_len',
1162 'rev_parent_id',
1163 'rev_sha1',
1164 'commentstore' => 'field',
1165 ],
1166 'joins' => [ 'commentstore' => 'join' ],
1167 ],
1168 ];
1169 yield 'wgContentHandlerUseDB false, opts page' => [
1170 [
1171 'wgContentHandlerUseDB' => false,
1172 ],
1173 [ 'page' ],
1174 [
1175 'tables' => [ 'revision', 'commentstore' => 'table', 'page' ],
1176 'fields' => [
1177 'rev_id',
1178 'rev_page',
1179 'rev_text_id',
1180 'rev_timestamp',
1181 'rev_user_text',
1182 'rev_user',
1183 'rev_minor_edit',
1184 'rev_deleted',
1185 'rev_len',
1186 'rev_parent_id',
1187 'rev_sha1',
1188 'commentstore' => 'field',
1189 'page_namespace',
1190 'page_title',
1191 'page_id',
1192 'page_latest',
1193 'page_is_redirect',
1194 'page_len',
1195 ],
1196 'joins' => [
1197 'page' => [
1198 'INNER JOIN',
1199 [ 'page_id = rev_page' ],
1200 ],
1201 'commentstore' => 'join',
1202 ],
1203 ],
1204 ];
1205 yield 'wgContentHandlerUseDB false, opts user' => [
1206 [
1207 'wgContentHandlerUseDB' => false,
1208 ],
1209 [ 'user' ],
1210 [
1211 'tables' => [ 'revision', 'commentstore' => 'table', 'user' ],
1212 'fields' => [
1213 'rev_id',
1214 'rev_page',
1215 'rev_text_id',
1216 'rev_timestamp',
1217 'rev_user_text',
1218 'rev_user',
1219 'rev_minor_edit',
1220 'rev_deleted',
1221 'rev_len',
1222 'rev_parent_id',
1223 'rev_sha1',
1224 'commentstore' => 'field',
1225 'user_name',
1226 ],
1227 'joins' => [
1228 'user' => [
1229 'LEFT JOIN',
1230 [
1231 'rev_user != 0',
1232 'user_id = rev_user',
1233 ],
1234 ],
1235 'commentstore' => 'join',
1236 ],
1237 ],
1238 ];
1239 yield 'wgContentHandlerUseDB false, opts text' => [
1240 [
1241 'wgContentHandlerUseDB' => false,
1242 ],
1243 [ 'text' ],
1244 [
1245 'tables' => [ 'revision', 'commentstore' => 'table', 'text' ],
1246 'fields' => [
1247 'rev_id',
1248 'rev_page',
1249 'rev_text_id',
1250 'rev_timestamp',
1251 'rev_user_text',
1252 'rev_user',
1253 'rev_minor_edit',
1254 'rev_deleted',
1255 'rev_len',
1256 'rev_parent_id',
1257 'rev_sha1',
1258 'commentstore' => 'field',
1259 'old_text',
1260 'old_flags',
1261 ],
1262 'joins' => [
1263 'text' => [
1264 'INNER JOIN',
1265 [ 'rev_text_id=old_id' ],
1266 ],
1267 'commentstore' => 'join',
1268 ],
1269 ],
1270 ];
1271 yield 'wgContentHandlerUseDB false, opts 3' => [
1272 [
1273 'wgContentHandlerUseDB' => false,
1274 ],
1275 [ 'text', 'page', 'user' ],
1276 [
1277 'tables' => [ 'revision', 'commentstore' => 'table', 'page', 'user', 'text' ],
1278 'fields' => [
1279 'rev_id',
1280 'rev_page',
1281 'rev_text_id',
1282 'rev_timestamp',
1283 'rev_user_text',
1284 'rev_user',
1285 'rev_minor_edit',
1286 'rev_deleted',
1287 'rev_len',
1288 'rev_parent_id',
1289 'rev_sha1',
1290 'commentstore' => 'field',
1291 'page_namespace',
1292 'page_title',
1293 'page_id',
1294 'page_latest',
1295 'page_is_redirect',
1296 'page_len',
1297 'user_name',
1298 'old_text',
1299 'old_flags',
1300 ],
1301 'joins' => [
1302 'page' => [
1303 'INNER JOIN',
1304 [ 'page_id = rev_page' ],
1305 ],
1306 'user' => [
1307 'LEFT JOIN',
1308 [
1309 'rev_user != 0',
1310 'user_id = rev_user',
1311 ],
1312 ],
1313 'text' => [
1314 'INNER JOIN',
1315 [ 'rev_text_id=old_id' ],
1316 ],
1317 'commentstore' => 'join',
1318 ],
1319 ],
1320 ];
1321 yield 'wgContentHandlerUseDB true, opts none' => [
1322 [
1323 'wgContentHandlerUseDB' => true,
1324 ],
1325 [],
1326 [
1327 'tables' => [ 'revision', 'commentstore' => 'table' ],
1328 'fields' => [
1329 'rev_id',
1330 'rev_page',
1331 'rev_text_id',
1332 'rev_timestamp',
1333 'rev_user_text',
1334 'rev_user',
1335 'rev_minor_edit',
1336 'rev_deleted',
1337 'rev_len',
1338 'rev_parent_id',
1339 'rev_sha1',
1340 'commentstore' => 'field',
1341 'rev_content_format',
1342 'rev_content_model',
1343 ],
1344 'joins' => [ 'commentstore' => 'join' ],
1345 ],
1346 ];
1347 }
1348
1349 /**
1350 * @covers Revision::getQueryInfo
1351 * @dataProvider provideGetQueryInfo
1352 */
1353 public function testGetQueryInfo( $globals, $options, $expected ) {
1354 $this->setMwGlobals( $globals );
1355 $this->overrideCommentStore();
1356
1357 $revisionStore = $this->getRevisionStore();
1358 $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
1359 $this->setService( 'RevisionStore', $revisionStore );
1360
1361 $this->assertEquals(
1362 $expected,
1363 Revision::getQueryInfo( $options )
1364 );
1365 }
1366
1367 /**
1368 * @covers Revision::getSize
1369 */
1370 public function testGetSize() {
1371 $title = $this->getMockTitle();
1372
1373 $rec = new MutableRevisionRecord( $title );
1374 $rev = new Revision( $rec, 0, $title );
1375
1376 $this->assertSame( 0, $rev->getSize(), 'Size of no slots is 0' );
1377
1378 $rec->setSize( 13 );
1379 $this->assertSame( 13, $rev->getSize() );
1380 }
1381
1382 /**
1383 * @covers Revision::getSize
1384 */
1385 public function testGetSize_failure() {
1386 $title = $this->getMockTitle();
1387
1388 $rec = $this->getMockBuilder( RevisionRecord::class )
1389 ->disableOriginalConstructor()
1390 ->getMock();
1391
1392 $rec->method( 'getSize' )
1393 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1394
1395 $rev = new Revision( $rec, 0, $title );
1396 $this->assertNull( $rev->getSize() );
1397 }
1398
1399 /**
1400 * @covers Revision::getSha1
1401 */
1402 public function testGetSha1() {
1403 $title = $this->getMockTitle();
1404
1405 $rec = new MutableRevisionRecord( $title );
1406 $rev = new Revision( $rec, 0, $title );
1407
1408 $emptyHash = SlotRecord::base36Sha1( '' );
1409 $this->assertSame( $emptyHash, $rev->getSha1(), 'Sha1 of no slots is hash of empty string' );
1410
1411 $rec->setSha1( 'deadbeef' );
1412 $this->assertSame( 'deadbeef', $rev->getSha1() );
1413 }
1414
1415 /**
1416 * @covers Revision::getSha1
1417 */
1418 public function testGetSha1_failure() {
1419 $title = $this->getMockTitle();
1420
1421 $rec = $this->getMockBuilder( RevisionRecord::class )
1422 ->disableOriginalConstructor()
1423 ->getMock();
1424
1425 $rec->method( 'getSha1' )
1426 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1427
1428 $rev = new Revision( $rec, 0, $title );
1429 $this->assertNull( $rev->getSha1() );
1430 }
1431
1432 /**
1433 * @covers Revision::getContent
1434 */
1435 public function testGetContent() {
1436 $title = $this->getMockTitle();
1437
1438 $rec = new MutableRevisionRecord( $title );
1439 $rev = new Revision( $rec, 0, $title );
1440
1441 $this->assertNull( $rev->getContent(), 'Content of no slots is null' );
1442
1443 $content = new TextContent( 'Hello Kittens!' );
1444 $rec->setContent( 'main', $content );
1445 $this->assertSame( $content, $rev->getContent() );
1446 }
1447
1448 /**
1449 * @covers Revision::getContent
1450 */
1451 public function testGetContent_failure() {
1452 $title = $this->getMockTitle();
1453
1454 $rec = $this->getMockBuilder( RevisionRecord::class )
1455 ->disableOriginalConstructor()
1456 ->getMock();
1457
1458 $rec->method( 'getContent' )
1459 ->willThrowException( new RevisionAccessException( 'Oops!' ) );
1460
1461 $rev = new Revision( $rec, 0, $title );
1462 $this->assertNull( $rev->getContent() );
1463 }
1464
1465 }