Merge "Revision: Handle all return values of Title::newFromId"
[lhc/web/wiklou.git] / tests / phpunit / includes / Storage / RevisionStoreDbTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Storage;
4
5 use CommentStoreComment;
6 use Exception;
7 use InvalidArgumentException;
8 use MediaWiki\Linker\LinkTarget;
9 use MediaWiki\MediaWikiServices;
10 use MediaWiki\Storage\IncompleteRevisionException;
11 use MediaWiki\Storage\MutableRevisionRecord;
12 use MediaWiki\Storage\RevisionRecord;
13 use MediaWiki\Storage\SlotRecord;
14 use MediaWikiTestCase;
15 use Revision;
16 use TestUserRegistry;
17 use Title;
18 use WikiPage;
19 use WikitextContent;
20
21 /**
22 * @group Database
23 */
24 class RevisionStoreDbTest extends MediaWikiTestCase {
25
26 private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
27 $this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
28 $this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
29 $this->assertEquals( $l1->getFragment(), $l2->getFragment() );
30 $this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
31 }
32
33 private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
34 $this->assertEquals( $r1->getUser()->getName(), $r2->getUser()->getName() );
35 $this->assertEquals( $r1->getUser()->getId(), $r2->getUser()->getId() );
36 $this->assertEquals( $r1->getComment(), $r2->getComment() );
37 $this->assertEquals( $r1->getPageAsLinkTarget(), $r2->getPageAsLinkTarget() );
38 $this->assertEquals( $r1->getTimestamp(), $r2->getTimestamp() );
39 $this->assertEquals( $r1->getVisibility(), $r2->getVisibility() );
40 $this->assertEquals( $r1->getSha1(), $r2->getSha1() );
41 $this->assertEquals( $r1->getParentId(), $r2->getParentId() );
42 $this->assertEquals( $r1->getSize(), $r2->getSize() );
43 $this->assertEquals( $r1->getPageId(), $r2->getPageId() );
44 $this->assertEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
45 $this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
46 $this->assertEquals( $r1->isMinor(), $r2->isMinor() );
47 foreach ( $r1->getSlotRoles() as $role ) {
48 $this->assertEquals( $r1->getSlot( $role ), $r2->getSlot( $role ) );
49 $this->assertEquals( $r1->getContent( $role ), $r2->getContent( $role ) );
50 }
51 foreach ( [
52 RevisionRecord::DELETED_TEXT,
53 RevisionRecord::DELETED_COMMENT,
54 RevisionRecord::DELETED_USER,
55 RevisionRecord::DELETED_RESTRICTED,
56 ] as $field ) {
57 $this->assertEquals( $r1->isDeleted( $field ), $r2->isDeleted( $field ) );
58 }
59 }
60
61 /**
62 * @param mixed[] $details
63 *
64 * @return RevisionRecord
65 */
66 private function getRevisionRecordFromDetailsArray( $title, $details = [] ) {
67 // Convert some values that can't be provided by dataProviders
68 $page = WikiPage::factory( $title );
69 if ( isset( $details['user'] ) && $details['user'] === true ) {
70 $details['user'] = $this->getTestUser()->getUser();
71 }
72 if ( isset( $details['page'] ) && $details['page'] === true ) {
73 $details['page'] = $page->getId();
74 }
75 if ( isset( $details['parent'] ) && $details['parent'] === true ) {
76 $details['parent'] = $page->getLatest();
77 }
78
79 // Create the RevisionRecord with any available data
80 $rev = new MutableRevisionRecord( $title );
81 isset( $details['slot'] ) ? $rev->setSlot( $details['slot'] ) : null;
82 isset( $details['parent'] ) ? $rev->setParentId( $details['parent'] ) : null;
83 isset( $details['page'] ) ? $rev->setPageId( $details['page'] ) : null;
84 isset( $details['size'] ) ? $rev->setSize( $details['size'] ) : null;
85 isset( $details['sha1'] ) ? $rev->setSha1( $details['sha1'] ) : null;
86 isset( $details['comment'] ) ? $rev->setComment( $details['comment'] ) : null;
87 isset( $details['timestamp'] ) ? $rev->setTimestamp( $details['timestamp'] ) : null;
88 isset( $details['minor'] ) ? $rev->setMinorEdit( $details['minor'] ) : null;
89 isset( $details['user'] ) ? $rev->setUser( $details['user'] ) : null;
90 isset( $details['visibility'] ) ? $rev->setVisibility( $details['visibility'] ) : null;
91 isset( $details['id'] ) ? $rev->setId( $details['id'] ) : null;
92
93 return $rev;
94 }
95
96 private function getRandomCommentStoreComment() {
97 return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) );
98 }
99
100 public function provideInsertRevisionOn_successes() {
101 yield 'Bare minimum revision insertion' => [
102 Title::newFromText( 'UTPage' ),
103 [
104 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
105 'parent' => true,
106 'comment' => $this->getRandomCommentStoreComment(),
107 'timestamp' => '20171117010101',
108 'user' => true,
109 ],
110 ];
111 yield 'Detailed revision insertion' => [
112 Title::newFromText( 'UTPage' ),
113 [
114 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
115 'parent' => true,
116 'page' => true,
117 'comment' => $this->getRandomCommentStoreComment(),
118 'timestamp' => '20171117010101',
119 'user' => true,
120 'minor' => true,
121 'visibility' => RevisionRecord::DELETED_RESTRICTED,
122 ],
123 ];
124 }
125
126 /**
127 * @dataProvider provideInsertRevisionOn_successes
128 * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
129 */
130 public function testInsertRevisionOn_successes( Title $title, array $revDetails = [] ) {
131 $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
132
133 $store = MediaWikiServices::getInstance()->getRevisionStore();
134 $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
135
136 $this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() );
137 $this->assertRevisionRecordsEqual( $rev, $return );
138 }
139
140 /**
141 * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
142 */
143 public function testInsertRevisionOn_blobAddressExists() {
144 $title = Title::newFromText( 'UTPage' );
145 $revDetails = [
146 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
147 'parent' => true,
148 'comment' => $this->getRandomCommentStoreComment(),
149 'timestamp' => '20171117010101',
150 'user' => true,
151 ];
152
153 $store = MediaWikiServices::getInstance()->getRevisionStore();
154
155 // Insert the first revision
156 $revOne = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
157 $firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_MASTER ) );
158 $this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
159 $this->assertRevisionRecordsEqual( $revOne, $firstReturn );
160
161 // Insert a second revision inheriting the same blob address
162 $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( 'main' ) );
163 $revTwo = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
164 $secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_MASTER ) );
165 $this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() );
166 $this->assertRevisionRecordsEqual( $revTwo, $secondReturn );
167
168 // Assert that the same blob address has been used.
169 $this->assertEquals(
170 $firstReturn->getSlot( 'main' )->getAddress(),
171 $secondReturn->getSlot( 'main' )->getAddress()
172 );
173 // And that different revisions have been created.
174 $this->assertNotSame(
175 $firstReturn->getId(),
176 $secondReturn->getId()
177 );
178 }
179
180 public function provideInsertRevisionOn_failures() {
181 yield 'no slot' => [
182 Title::newFromText( 'UTPage' ),
183 [
184 'comment' => $this->getRandomCommentStoreComment(),
185 'timestamp' => '20171117010101',
186 'user' => true,
187 ],
188 new InvalidArgumentException( 'At least one slot needs to be defined!' )
189 ];
190 yield 'slot that is not main slot' => [
191 Title::newFromText( 'UTPage' ),
192 [
193 'slot' => SlotRecord::newUnsaved( 'lalala', new WikitextContent( 'Chicken' ) ),
194 'comment' => $this->getRandomCommentStoreComment(),
195 'timestamp' => '20171117010101',
196 'user' => true,
197 ],
198 new InvalidArgumentException( 'Only the main slot is supported for now!' )
199 ];
200 yield 'no timestamp' => [
201 Title::newFromText( 'UTPage' ),
202 [
203 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
204 'comment' => $this->getRandomCommentStoreComment(),
205 'user' => true,
206 ],
207 new IncompleteRevisionException( 'timestamp field must not be NULL!' )
208 ];
209 yield 'no comment' => [
210 Title::newFromText( 'UTPage' ),
211 [
212 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
213 'timestamp' => '20171117010101',
214 'user' => true,
215 ],
216 new IncompleteRevisionException( 'comment must not be NULL!' )
217 ];
218 yield 'no user' => [
219 Title::newFromText( 'UTPage' ),
220 [
221 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
222 'comment' => $this->getRandomCommentStoreComment(),
223 'timestamp' => '20171117010101',
224 ],
225 new IncompleteRevisionException( 'user must not be NULL!' )
226 ];
227 }
228
229 /**
230 * @dataProvider provideInsertRevisionOn_failures
231 * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
232 */
233 public function testInsertRevisionOn_failures(
234 Title $title,
235 array $revDetails = [],
236 Exception $exception ) {
237 $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
238
239 $store = MediaWikiServices::getInstance()->getRevisionStore();
240
241 $this->setExpectedException(
242 get_class( $exception ),
243 $exception->getMessage(),
244 $exception->getCode()
245 );
246 $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
247 }
248
249 public function provideNewNullRevision() {
250 yield [
251 Title::newFromText( 'UTPage' ),
252 CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ),
253 true,
254 ];
255 yield [
256 Title::newFromText( 'UTPage' ),
257 CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ),
258 false,
259 ];
260 }
261
262 /**
263 * @dataProvider provideNewNullRevision
264 * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
265 */
266 public function testNewNullRevision( Title $title, $comment, $minor ) {
267 $store = MediaWikiServices::getInstance()->getRevisionStore();
268 $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
269 $record = $store->newNullRevision(
270 wfGetDB( DB_MASTER ),
271 $title,
272 $comment,
273 $minor,
274 $user
275 );
276
277 $this->assertEquals( $title->getNamespace(), $record->getPageAsLinkTarget()->getNamespace() );
278 $this->assertEquals( $title->getDBkey(), $record->getPageAsLinkTarget()->getDBkey() );
279 $this->assertEquals( $comment, $record->getComment() );
280 $this->assertEquals( $minor, $record->isMinor() );
281 $this->assertEquals( $user->getName(), $record->getUser()->getName() );
282 }
283
284 /**
285 * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
286 */
287 public function testNewNullRevision_nonExistingTitle() {
288 $store = MediaWikiServices::getInstance()->getRevisionStore();
289 $record = $store->newNullRevision(
290 wfGetDB( DB_MASTER ),
291 Title::newFromText( __METHOD__ . '.iDontExist!' ),
292 CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment' ),
293 false,
294 TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser()
295 );
296 $this->assertNull( $record );
297 }
298
299 /**
300 * @covers \MediaWiki\Storage\RevisionStore::isUnpatrolled
301 */
302 public function testIsUnpatrolled_returnsRecentChangesId() {
303 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
304 $status = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
305 /** @var Revision $rev */
306 $rev = $status->value['revision'];
307
308 $store = MediaWikiServices::getInstance()->getRevisionStore();
309 $revisionRecord = $store->getRevisionById( $rev->getId() );
310 $result = $store->isUnpatrolled( $revisionRecord );
311
312 $this->assertGreaterThan( 0, $result );
313 $this->assertSame(
314 $page->getRevision()->getRecentChange()->getAttribute( 'rc_id' ),
315 $result
316 );
317 }
318
319 /**
320 * @covers \MediaWiki\Storage\RevisionStore::isUnpatrolled
321 */
322 public function testIsUnpatrolled_returnsZeroIfPatrolled() {
323 // This assumes that sysops are auto patrolled
324 $sysop = $this->getTestSysop()->getUser();
325 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
326 $status = $page->doEditContent(
327 new WikitextContent( __METHOD__ ),
328 __METHOD__,
329 0,
330 false,
331 $sysop
332 );
333 /** @var Revision $rev */
334 $rev = $status->value['revision'];
335
336 $store = MediaWikiServices::getInstance()->getRevisionStore();
337 $revisionRecord = $store->getRevisionById( $rev->getId() );
338 $result = $store->isUnpatrolled( $revisionRecord );
339
340 $this->assertSame( 0, $result );
341 }
342
343 /**
344 * @covers \MediaWiki\Storage\RevisionStore::getRecentChange
345 */
346 public function testGetRecentChange() {
347 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
348 $content = new WikitextContent( __METHOD__ );
349 $status = $page->doEditContent( $content, __METHOD__ );
350 /** @var Revision $rev */
351 $rev = $status->value['revision'];
352
353 $store = MediaWikiServices::getInstance()->getRevisionStore();
354 $revRecord = $store->getRevisionById( $rev->getId() );
355 $recentChange = $store->getRecentChange( $revRecord );
356
357 $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
358 $this->assertEquals( $rev->getRecentChange(), $recentChange );
359 }
360
361 /**
362 * @covers \MediaWiki\Storage\RevisionStore::getRevisionById
363 */
364 public function testGetRevisionById() {
365 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
366 $content = new WikitextContent( __METHOD__ );
367 $status = $page->doEditContent( $content, __METHOD__ );
368 /** @var Revision $rev */
369 $rev = $status->value['revision'];
370
371 $store = MediaWikiServices::getInstance()->getRevisionStore();
372 $revRecord = $store->getRevisionById( $rev->getId() );
373
374 $this->assertSame( $rev->getId(), $revRecord->getId() );
375 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
376 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
377 }
378
379 /**
380 * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTitle
381 */
382 public function testGetRevisionByTitle() {
383 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
384 $content = new WikitextContent( __METHOD__ );
385 $status = $page->doEditContent( $content, __METHOD__ );
386 /** @var Revision $rev */
387 $rev = $status->value['revision'];
388
389 $store = MediaWikiServices::getInstance()->getRevisionStore();
390 $revRecord = $store->getRevisionByTitle( $page->getTitle() );
391
392 $this->assertSame( $rev->getId(), $revRecord->getId() );
393 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
394 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
395 }
396
397 /**
398 * @covers \MediaWiki\Storage\RevisionStore::getRevisionByPageId
399 */
400 public function testGetRevisionByPageId() {
401 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
402 $content = new WikitextContent( __METHOD__ );
403 $status = $page->doEditContent( $content, __METHOD__ );
404 /** @var Revision $rev */
405 $rev = $status->value['revision'];
406
407 $store = MediaWikiServices::getInstance()->getRevisionStore();
408 $revRecord = $store->getRevisionByPageId( $page->getId() );
409
410 $this->assertSame( $rev->getId(), $revRecord->getId() );
411 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
412 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
413 }
414
415 /**
416 * @covers \MediaWiki\Storage\RevisionStore::getRevisionFromTimestamp
417 */
418 public function testGetRevisionFromTimestamp() {
419 // Make sure there is 1 second between the last revision and the rev we create...
420 // Otherwise we might not get the correct revision and the test may fail...
421 // :(
422 sleep( 1 );
423 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
424 $content = new WikitextContent( __METHOD__ );
425 $status = $page->doEditContent( $content, __METHOD__ );
426 /** @var Revision $rev */
427 $rev = $status->value['revision'];
428
429 $store = MediaWikiServices::getInstance()->getRevisionStore();
430 $revRecord = $store->getRevisionFromTimestamp(
431 $page->getTitle(),
432 $rev->getTimestamp()
433 );
434
435 $this->assertSame( $rev->getId(), $revRecord->getId() );
436 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
437 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
438 }
439
440 private function revisionToRow( Revision $rev ) {
441 $page = WikiPage::factory( $rev->getTitle() );
442
443 return (object)[
444 'rev_id' => (string)$rev->getId(),
445 'rev_page' => (string)$rev->getPage(),
446 'rev_text_id' => (string)$rev->getTextId(),
447 'rev_timestamp' => (string)$rev->getTimestamp(),
448 'rev_user_text' => (string)$rev->getUserText(),
449 'rev_user' => (string)$rev->getUser(),
450 'rev_minor_edit' => $rev->isMinor() ? '1' : '0',
451 'rev_deleted' => (string)$rev->getVisibility(),
452 'rev_len' => (string)$rev->getSize(),
453 'rev_parent_id' => (string)$rev->getParentId(),
454 'rev_sha1' => (string)$rev->getSha1(),
455 'rev_comment_text' => $rev->getComment(),
456 'rev_comment_data' => null,
457 'rev_comment_cid' => null,
458 'rev_content_format' => $rev->getContentFormat(),
459 'rev_content_model' => $rev->getContentModel(),
460 'page_namespace' => (string)$page->getTitle()->getNamespace(),
461 'page_title' => $page->getTitle()->getDBkey(),
462 'page_id' => (string)$page->getId(),
463 'page_latest' => (string)$page->getLatest(),
464 'page_is_redirect' => $page->isRedirect() ? '1' : '0',
465 'page_len' => (string)$page->getContent()->getSize(),
466 'user_name' => (string)$rev->getUserText(),
467 ];
468 }
469
470 private function assertRevisionRecordMatchesRevision(
471 Revision $rev,
472 RevisionRecord $record
473 ) {
474 $this->assertSame( $rev->getId(), $record->getId() );
475 $this->assertSame( $rev->getPage(), $record->getPageId() );
476 $this->assertSame( $rev->getTimestamp(), $record->getTimestamp() );
477 $this->assertSame( $rev->getUserText(), $record->getUser()->getName() );
478 $this->assertSame( $rev->getUser(), $record->getUser()->getId() );
479 $this->assertSame( $rev->isMinor(), $record->isMinor() );
480 $this->assertSame( $rev->getVisibility(), $record->getVisibility() );
481 $this->assertSame( $rev->getSize(), $record->getSize() );
482 /**
483 * @note As of MW 1.31, the database schema allows the parent ID to be
484 * NULL to indicate that it is unknown.
485 */
486 $expectedParent = $rev->getParentId();
487 if ( $expectedParent === null ) {
488 $expectedParent = 0;
489 }
490 $this->assertSame( $expectedParent, $record->getParentId() );
491 $this->assertSame( $rev->getSha1(), $record->getSha1() );
492 $this->assertSame( $rev->getComment(), $record->getComment()->text );
493 $this->assertSame( $rev->getContentFormat(), $record->getContent( 'main' )->getDefaultFormat() );
494 $this->assertSame( $rev->getContentModel(), $record->getContent( 'main' )->getModel() );
495 $this->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
496 }
497
498 /**
499 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
500 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
501 */
502 public function testNewRevisionFromRow_anonEdit() {
503 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
504 /** @var Revision $rev */
505 $rev = $page->doEditContent(
506 new WikitextContent( __METHOD__. 'a' ),
507 __METHOD__. 'a'
508 )->value['revision'];
509
510 $store = MediaWikiServices::getInstance()->getRevisionStore();
511 $record = $store->newRevisionFromRow(
512 $this->revisionToRow( $rev ),
513 [],
514 $page->getTitle()
515 );
516 $this->assertRevisionRecordMatchesRevision( $rev, $record );
517 }
518
519 /**
520 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
521 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
522 */
523 public function testNewRevisionFromRow_userEdit() {
524 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
525 /** @var Revision $rev */
526 $rev = $page->doEditContent(
527 new WikitextContent( __METHOD__. 'b' ),
528 __METHOD__ . 'b',
529 0,
530 false,
531 $this->getTestUser()->getUser()
532 )->value['revision'];
533
534 $store = MediaWikiServices::getInstance()->getRevisionStore();
535 $record = $store->newRevisionFromRow(
536 $this->revisionToRow( $rev ),
537 [],
538 $page->getTitle()
539 );
540 $this->assertRevisionRecordMatchesRevision( $rev, $record );
541 }
542
543 /**
544 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
545 */
546 public function testNewRevisionFromArchiveRow() {
547 $store = MediaWikiServices::getInstance()->getRevisionStore();
548 $title = Title::newFromText( __METHOD__ );
549 $page = WikiPage::factory( $title );
550 /** @var Revision $orig */
551 $orig = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
552 ->value['revision'];
553 $page->doDeleteArticle( __METHOD__ );
554
555 $db = wfGetDB( DB_MASTER );
556 $arQuery = $store->getArchiveQueryInfo();
557 $res = $db->select(
558 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
559 __METHOD__, [], $arQuery['joins']
560 );
561 $this->assertTrue( is_object( $res ), 'query failed' );
562
563 $row = $res->fetchObject();
564 $res->free();
565 $record = $store->newRevisionFromArchiveRow( $row );
566
567 $this->assertRevisionRecordMatchesRevision( $orig, $record );
568 }
569
570 /**
571 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromId
572 */
573 public function testLoadRevisionFromId() {
574 $title = Title::newFromText( __METHOD__ );
575 $page = WikiPage::factory( $title );
576 /** @var Revision $rev */
577 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
578 ->value['revision'];
579
580 $store = MediaWikiServices::getInstance()->getRevisionStore();
581 $result = $store->loadRevisionFromId( wfGetDB( DB_MASTER ), $rev->getId() );
582 $this->assertRevisionRecordMatchesRevision( $rev, $result );
583 }
584
585 /**
586 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromPageId
587 */
588 public function testLoadRevisionFromPageId() {
589 $title = Title::newFromText( __METHOD__ );
590 $page = WikiPage::factory( $title );
591 /** @var Revision $rev */
592 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
593 ->value['revision'];
594
595 $store = MediaWikiServices::getInstance()->getRevisionStore();
596 $result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
597 $this->assertRevisionRecordMatchesRevision( $rev, $result );
598 }
599
600 /**
601 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTitle
602 */
603 public function testLoadRevisionFromTitle() {
604 $title = Title::newFromText( __METHOD__ );
605 $page = WikiPage::factory( $title );
606 /** @var Revision $rev */
607 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
608 ->value['revision'];
609
610 $store = MediaWikiServices::getInstance()->getRevisionStore();
611 $result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
612 $this->assertRevisionRecordMatchesRevision( $rev, $result );
613 }
614
615 /**
616 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTimestamp
617 */
618 public function testLoadRevisionFromTimestamp() {
619 $title = Title::newFromText( __METHOD__ );
620 $page = WikiPage::factory( $title );
621 /** @var Revision $revOne */
622 $revOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
623 ->value['revision'];
624 // Sleep to ensure different timestamps... )(evil)
625 sleep( 1 );
626 /** @var Revision $revTwo */
627 $revTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
628 ->value['revision'];
629
630 $store = MediaWikiServices::getInstance()->getRevisionStore();
631 $this->assertNull(
632 $store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
633 );
634 $this->assertSame(
635 $revOne->getId(),
636 $store->loadRevisionFromTimestamp(
637 wfGetDB( DB_MASTER ),
638 $title,
639 $revOne->getTimestamp()
640 )->getId()
641 );
642 $this->assertSame(
643 $revTwo->getId(),
644 $store->loadRevisionFromTimestamp(
645 wfGetDB( DB_MASTER ),
646 $title,
647 $revTwo->getTimestamp()
648 )->getId()
649 );
650 }
651
652 /**
653 * @covers \MediaWiki\Storage\RevisionStore::listRevisionSizes
654 */
655 public function testGetParentLengths() {
656 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
657 /** @var Revision $revOne */
658 $revOne = $page->doEditContent(
659 new WikitextContent( __METHOD__ ), __METHOD__
660 )->value['revision'];
661 /** @var Revision $revTwo */
662 $revTwo = $page->doEditContent(
663 new WikitextContent( __METHOD__ . '2' ), __METHOD__
664 )->value['revision'];
665
666 $store = MediaWikiServices::getInstance()->getRevisionStore();
667 $this->assertSame(
668 [
669 $revOne->getId() => strlen( __METHOD__ ),
670 ],
671 $store->listRevisionSizes(
672 wfGetDB( DB_MASTER ),
673 [ $revOne->getId() ]
674 )
675 );
676 $this->assertSame(
677 [
678 $revOne->getId() => strlen( __METHOD__ ),
679 $revTwo->getId() => strlen( __METHOD__ ) + 1,
680 ],
681 $store->listRevisionSizes(
682 wfGetDB( DB_MASTER ),
683 [ $revOne->getId(), $revTwo->getId() ]
684 )
685 );
686 }
687
688 /**
689 * @covers \MediaWiki\Storage\RevisionStore::getPreviousRevision
690 */
691 public function testGetPreviousRevision() {
692 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
693 /** @var Revision $revOne */
694 $revOne = $page->doEditContent(
695 new WikitextContent( __METHOD__ ), __METHOD__
696 )->value['revision'];
697 /** @var Revision $revTwo */
698 $revTwo = $page->doEditContent(
699 new WikitextContent( __METHOD__ . '2' ), __METHOD__
700 )->value['revision'];
701
702 $store = MediaWikiServices::getInstance()->getRevisionStore();
703 $this->assertNull(
704 $store->getPreviousRevision( $store->getRevisionById( $revOne->getId() ) )
705 );
706 $this->assertSame(
707 $revOne->getId(),
708 $store->getPreviousRevision( $store->getRevisionById( $revTwo->getId() ) )->getId()
709 );
710 }
711
712 /**
713 * @covers \MediaWiki\Storage\RevisionStore::getNextRevision
714 */
715 public function testGetNextRevision() {
716 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
717 /** @var Revision $revOne */
718 $revOne = $page->doEditContent(
719 new WikitextContent( __METHOD__ ), __METHOD__
720 )->value['revision'];
721 /** @var Revision $revTwo */
722 $revTwo = $page->doEditContent(
723 new WikitextContent( __METHOD__ . '2' ), __METHOD__
724 )->value['revision'];
725
726 $store = MediaWikiServices::getInstance()->getRevisionStore();
727 $this->assertSame(
728 $revTwo->getId(),
729 $store->getNextRevision( $store->getRevisionById( $revOne->getId() ) )->getId()
730 );
731 $this->assertNull(
732 $store->getNextRevision( $store->getRevisionById( $revTwo->getId() ) )
733 );
734 }
735
736 /**
737 * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
738 */
739 public function testGetTimestampFromId_found() {
740 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
741 /** @var Revision $rev */
742 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
743 ->value['revision'];
744
745 $store = MediaWikiServices::getInstance()->getRevisionStore();
746 $result = $store->getTimestampFromId(
747 $page->getTitle(),
748 $rev->getId()
749 );
750
751 $this->assertSame( $rev->getTimestamp(), $result );
752 }
753
754 /**
755 * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
756 */
757 public function testGetTimestampFromId_notFound() {
758 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
759 /** @var Revision $rev */
760 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
761 ->value['revision'];
762
763 $store = MediaWikiServices::getInstance()->getRevisionStore();
764 $result = $store->getTimestampFromId(
765 $page->getTitle(),
766 $rev->getId() + 1
767 );
768
769 $this->assertFalse( $result );
770 }
771
772 /**
773 * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByPageId
774 */
775 public function testCountRevisionsByPageId() {
776 $store = MediaWikiServices::getInstance()->getRevisionStore();
777 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
778
779 $this->assertSame(
780 0,
781 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
782 );
783 $page->doEditContent( new WikitextContent( 'a' ), 'a' );
784 $this->assertSame(
785 1,
786 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
787 );
788 $page->doEditContent( new WikitextContent( 'b' ), 'b' );
789 $this->assertSame(
790 2,
791 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
792 );
793 }
794
795 /**
796 * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByTitle
797 */
798 public function testCountRevisionsByTitle() {
799 $store = MediaWikiServices::getInstance()->getRevisionStore();
800 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
801
802 $this->assertSame(
803 0,
804 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
805 );
806 $page->doEditContent( new WikitextContent( 'a' ), 'a' );
807 $this->assertSame(
808 1,
809 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
810 );
811 $page->doEditContent( new WikitextContent( 'b' ), 'b' );
812 $this->assertSame(
813 2,
814 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
815 );
816 }
817
818 /**
819 * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
820 */
821 public function testUserWasLastToEdit_false() {
822 $sysop = $this->getTestSysop()->getUser();
823 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
824 $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
825
826 $store = MediaWikiServices::getInstance()->getRevisionStore();
827 $result = $store->userWasLastToEdit(
828 wfGetDB( DB_MASTER ),
829 $page->getId(),
830 $sysop->getId(),
831 '20160101010101'
832 );
833 $this->assertFalse( $result );
834 }
835
836 /**
837 * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
838 */
839 public function testUserWasLastToEdit_true() {
840 $startTime = wfTimestampNow();
841 $sysop = $this->getTestSysop()->getUser();
842 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
843 $page->doEditContent(
844 new WikitextContent( __METHOD__ ),
845 __METHOD__,
846 0,
847 false,
848 $sysop
849 );
850
851 $store = MediaWikiServices::getInstance()->getRevisionStore();
852 $result = $store->userWasLastToEdit(
853 wfGetDB( DB_MASTER ),
854 $page->getId(),
855 $sysop->getId(),
856 $startTime
857 );
858 $this->assertTrue( $result );
859 }
860
861 /**
862 * @covers \MediaWiki\Storage\RevisionStore::getKnownCurrentRevision
863 */
864 public function testGetKnownCurrentRevision() {
865 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
866 /** @var Revision $rev */
867 $rev = $page->doEditContent(
868 new WikitextContent( __METHOD__. 'b' ),
869 __METHOD__ . 'b',
870 0,
871 false,
872 $this->getTestUser()->getUser()
873 )->value['revision'];
874
875 $store = MediaWikiServices::getInstance()->getRevisionStore();
876 $record = $store->getKnownCurrentRevision(
877 $page->getTitle(),
878 $rev->getId()
879 );
880
881 $this->assertRevisionRecordMatchesRevision( $rev, $record );
882 }
883
884 public function provideNewMutableRevisionFromArray() {
885 yield 'Basic array, with page & id' => [
886 [
887 'id' => 2,
888 'page' => 1,
889 'text_id' => 2,
890 'timestamp' => '20171017114835',
891 'user_text' => '111.0.1.2',
892 'user' => 0,
893 'minor_edit' => false,
894 'deleted' => 0,
895 'len' => 46,
896 'parent_id' => 1,
897 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
898 'comment' => 'Goat Comment!',
899 'content_format' => 'text/x-wiki',
900 'content_model' => 'wikitext',
901 ]
902 ];
903 yield 'Basic array, content object' => [
904 [
905 'id' => 2,
906 'page' => 1,
907 'timestamp' => '20171017114835',
908 'user_text' => '111.0.1.2',
909 'user' => 0,
910 'minor_edit' => false,
911 'deleted' => 0,
912 'len' => 46,
913 'parent_id' => 1,
914 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
915 'comment' => 'Goat Comment!',
916 'content' => new WikitextContent( 'Some Content' ),
917 ]
918 ];
919 yield 'Basic array, with title' => [
920 [
921 'title' => Title::newFromText( 'SomeText' ),
922 'text_id' => 2,
923 'timestamp' => '20171017114835',
924 'user_text' => '111.0.1.2',
925 'user' => 0,
926 'minor_edit' => false,
927 'deleted' => 0,
928 'len' => 46,
929 'parent_id' => 1,
930 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
931 'comment' => 'Goat Comment!',
932 'content_format' => 'text/x-wiki',
933 'content_model' => 'wikitext',
934 ]
935 ];
936 yield 'Basic array, no user field' => [
937 [
938 'id' => 2,
939 'page' => 1,
940 'text_id' => 2,
941 'timestamp' => '20171017114835',
942 'user_text' => '111.0.1.3',
943 'minor_edit' => false,
944 'deleted' => 0,
945 'len' => 46,
946 'parent_id' => 1,
947 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
948 'comment' => 'Goat Comment!',
949 'content_format' => 'text/x-wiki',
950 'content_model' => 'wikitext',
951 ]
952 ];
953 }
954
955 /**
956 * @dataProvider provideNewMutableRevisionFromArray
957 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
958 */
959 public function testNewMutableRevisionFromArray( array $array ) {
960 $store = MediaWikiServices::getInstance()->getRevisionStore();
961
962 $result = $store->newMutableRevisionFromArray( $array );
963
964 if ( isset( $array['id'] ) ) {
965 $this->assertSame( $array['id'], $result->getId() );
966 }
967 if ( isset( $array['page'] ) ) {
968 $this->assertSame( $array['page'], $result->getPageId() );
969 }
970 $this->assertSame( $array['timestamp'], $result->getTimestamp() );
971 $this->assertSame( $array['user_text'], $result->getUser()->getName() );
972 if ( isset( $array['user'] ) ) {
973 $this->assertSame( $array['user'], $result->getUser()->getId() );
974 }
975 $this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
976 $this->assertSame( $array['deleted'], $result->getVisibility() );
977 $this->assertSame( $array['len'], $result->getSize() );
978 $this->assertSame( $array['parent_id'], $result->getParentId() );
979 $this->assertSame( $array['sha1'], $result->getSha1() );
980 $this->assertSame( $array['comment'], $result->getComment()->text );
981 if ( isset( $array['content'] ) ) {
982 $this->assertTrue(
983 $result->getSlot( 'main' )->getContent()->equals( $array['content'] )
984 );
985 } else {
986 $this->assertSame(
987 $array['content_format'],
988 $result->getSlot( 'main' )->getContent()->getDefaultFormat()
989 );
990 $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
991 }
992 }
993
994 }