Merge "shell: Add debug logging to find binaries that aren't being restricted"
[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 public function testGetRecentChange() {
344 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
345 $content = new WikitextContent( __METHOD__ );
346 $status = $page->doEditContent( $content, __METHOD__ );
347 /** @var Revision $rev */
348 $rev = $status->value['revision'];
349
350 $store = MediaWikiServices::getInstance()->getRevisionStore();
351 $revRecord = $store->getRevisionById( $rev->getId() );
352 $recentChange = $store->getRecentChange( $revRecord );
353
354 $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
355 $this->assertEquals( $rev->getRecentChange(), $recentChange );
356 }
357
358 /**
359 * @covers \MediaWiki\Storage\RevisionStore::getRevisionById
360 */
361 public function testGetRevisionById() {
362 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
363 $content = new WikitextContent( __METHOD__ );
364 $status = $page->doEditContent( $content, __METHOD__ );
365 /** @var Revision $rev */
366 $rev = $status->value['revision'];
367
368 $store = MediaWikiServices::getInstance()->getRevisionStore();
369 $revRecord = $store->getRevisionById( $rev->getId() );
370
371 $this->assertSame( $rev->getId(), $revRecord->getId() );
372 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
373 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
374 }
375
376 /**
377 * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTitle
378 */
379 public function testGetRevisionByTitle() {
380 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
381 $content = new WikitextContent( __METHOD__ );
382 $status = $page->doEditContent( $content, __METHOD__ );
383 /** @var Revision $rev */
384 $rev = $status->value['revision'];
385
386 $store = MediaWikiServices::getInstance()->getRevisionStore();
387 $revRecord = $store->getRevisionByTitle( $page->getTitle() );
388
389 $this->assertSame( $rev->getId(), $revRecord->getId() );
390 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
391 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
392 }
393
394 /**
395 * @covers \MediaWiki\Storage\RevisionStore::getRevisionByPageId
396 */
397 public function testGetRevisionByPageId() {
398 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
399 $content = new WikitextContent( __METHOD__ );
400 $status = $page->doEditContent( $content, __METHOD__ );
401 /** @var Revision $rev */
402 $rev = $status->value['revision'];
403
404 $store = MediaWikiServices::getInstance()->getRevisionStore();
405 $revRecord = $store->getRevisionByPageId( $page->getId() );
406
407 $this->assertSame( $rev->getId(), $revRecord->getId() );
408 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
409 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
410 }
411
412 /**
413 * @covers \MediaWiki\Storage\RevisionStore::getRevisionFromTimestamp
414 */
415 public function testGetRevisionFromTimestamp() {
416 // Make sure there is 1 second between the last revision and the rev we create...
417 // Otherwise we might not get the correct revision and the test may fail...
418 // :(
419 sleep( 1 );
420 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
421 $content = new WikitextContent( __METHOD__ );
422 $status = $page->doEditContent( $content, __METHOD__ );
423 /** @var Revision $rev */
424 $rev = $status->value['revision'];
425
426 $store = MediaWikiServices::getInstance()->getRevisionStore();
427 $revRecord = $store->getRevisionFromTimestamp(
428 $page->getTitle(),
429 $rev->getTimestamp()
430 );
431
432 $this->assertSame( $rev->getId(), $revRecord->getId() );
433 $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
434 $this->assertSame( __METHOD__, $revRecord->getComment()->text );
435 }
436
437 private function revisionToRow( Revision $rev ) {
438 $page = WikiPage::factory( $rev->getTitle() );
439
440 return (object)[
441 'rev_id' => (string)$rev->getId(),
442 'rev_page' => (string)$rev->getPage(),
443 'rev_text_id' => (string)$rev->getTextId(),
444 'rev_timestamp' => (string)$rev->getTimestamp(),
445 'rev_user_text' => (string)$rev->getUserText(),
446 'rev_user' => (string)$rev->getUser(),
447 'rev_minor_edit' => $rev->isMinor() ? '1' : '0',
448 'rev_deleted' => (string)$rev->getVisibility(),
449 'rev_len' => (string)$rev->getSize(),
450 'rev_parent_id' => (string)$rev->getParentId(),
451 'rev_sha1' => (string)$rev->getSha1(),
452 'rev_comment_text' => $rev->getComment(),
453 'rev_comment_data' => null,
454 'rev_comment_cid' => null,
455 'rev_content_format' => $rev->getContentFormat(),
456 'rev_content_model' => $rev->getContentModel(),
457 'page_namespace' => (string)$page->getTitle()->getNamespace(),
458 'page_title' => $page->getTitle()->getDBkey(),
459 'page_id' => (string)$page->getId(),
460 'page_latest' => (string)$page->getLatest(),
461 'page_is_redirect' => $page->isRedirect() ? '1' : '0',
462 'page_len' => (string)$page->getContent()->getSize(),
463 'user_name' => (string)$rev->getUserText(),
464 ];
465 }
466
467 private function assertRevisionRecordMatchesRevision(
468 Revision $rev,
469 RevisionRecord $record
470 ) {
471 $this->assertSame( $rev->getId(), $record->getId() );
472 $this->assertSame( $rev->getPage(), $record->getPageId() );
473 $this->assertSame( $rev->getTimestamp(), $record->getTimestamp() );
474 $this->assertSame( $rev->getUserText(), $record->getUser()->getName() );
475 $this->assertSame( $rev->getUser(), $record->getUser()->getId() );
476 $this->assertSame( $rev->isMinor(), $record->isMinor() );
477 $this->assertSame( $rev->getVisibility(), $record->getVisibility() );
478 $this->assertSame( $rev->getSize(), $record->getSize() );
479 /**
480 * @note As of MW 1.31, the database schema allows the parent ID to be
481 * NULL to indicate that it is unknown.
482 */
483 $expectedParent = $rev->getParentId();
484 if ( $expectedParent === null ) {
485 $expectedParent = 0;
486 }
487 $this->assertSame( $expectedParent, $record->getParentId() );
488 $this->assertSame( $rev->getSha1(), $record->getSha1() );
489 $this->assertSame( $rev->getComment(), $record->getComment()->text );
490 $this->assertSame( $rev->getContentFormat(), $record->getContent( 'main' )->getDefaultFormat() );
491 $this->assertSame( $rev->getContentModel(), $record->getContent( 'main' )->getModel() );
492 $this->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
493 }
494
495 /**
496 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
497 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
498 */
499 public function testNewRevisionFromRow_anonEdit() {
500 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
501 /** @var Revision $rev */
502 $rev = $page->doEditContent(
503 new WikitextContent( __METHOD__. 'a' ),
504 __METHOD__. 'a'
505 )->value['revision'];
506
507 $store = MediaWikiServices::getInstance()->getRevisionStore();
508 $record = $store->newRevisionFromRow(
509 $this->revisionToRow( $rev ),
510 [],
511 $page->getTitle()
512 );
513 $this->assertRevisionRecordMatchesRevision( $rev, $record );
514 }
515
516 /**
517 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
518 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
519 */
520 public function testNewRevisionFromRow_userEdit() {
521 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
522 /** @var Revision $rev */
523 $rev = $page->doEditContent(
524 new WikitextContent( __METHOD__. 'b' ),
525 __METHOD__ . 'b',
526 0,
527 false,
528 $this->getTestUser()->getUser()
529 )->value['revision'];
530
531 $store = MediaWikiServices::getInstance()->getRevisionStore();
532 $record = $store->newRevisionFromRow(
533 $this->revisionToRow( $rev ),
534 [],
535 $page->getTitle()
536 );
537 $this->assertRevisionRecordMatchesRevision( $rev, $record );
538 }
539
540 /**
541 * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
542 */
543 public function testNewRevisionFromArchiveRow() {
544 $store = MediaWikiServices::getInstance()->getRevisionStore();
545 $title = Title::newFromText( __METHOD__ );
546 $page = WikiPage::factory( $title );
547 /** @var Revision $orig */
548 $orig = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
549 ->value['revision'];
550 $page->doDeleteArticle( __METHOD__ );
551
552 $db = wfGetDB( DB_MASTER );
553 $arQuery = $store->getArchiveQueryInfo();
554 $res = $db->select(
555 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
556 __METHOD__, [], $arQuery['joins']
557 );
558 $this->assertTrue( is_object( $res ), 'query failed' );
559
560 $row = $res->fetchObject();
561 $res->free();
562 $record = $store->newRevisionFromArchiveRow( $row );
563
564 $this->assertRevisionRecordMatchesRevision( $orig, $record );
565 }
566
567 /**
568 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromId
569 */
570 public function testLoadRevisionFromId() {
571 $title = Title::newFromText( __METHOD__ );
572 $page = WikiPage::factory( $title );
573 /** @var Revision $rev */
574 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
575 ->value['revision'];
576
577 $store = MediaWikiServices::getInstance()->getRevisionStore();
578 $result = $store->loadRevisionFromId( wfGetDB( DB_MASTER ), $rev->getId() );
579 $this->assertRevisionRecordMatchesRevision( $rev, $result );
580 }
581
582 /**
583 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromPageId
584 */
585 public function testLoadRevisionFromPageId() {
586 $title = Title::newFromText( __METHOD__ );
587 $page = WikiPage::factory( $title );
588 /** @var Revision $rev */
589 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
590 ->value['revision'];
591
592 $store = MediaWikiServices::getInstance()->getRevisionStore();
593 $result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
594 $this->assertRevisionRecordMatchesRevision( $rev, $result );
595 }
596
597 /**
598 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTitle
599 */
600 public function testLoadRevisionFromTitle() {
601 $title = Title::newFromText( __METHOD__ );
602 $page = WikiPage::factory( $title );
603 /** @var Revision $rev */
604 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
605 ->value['revision'];
606
607 $store = MediaWikiServices::getInstance()->getRevisionStore();
608 $result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
609 $this->assertRevisionRecordMatchesRevision( $rev, $result );
610 }
611
612 /**
613 * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTimestamp
614 */
615 public function testLoadRevisionFromTimestamp() {
616 $title = Title::newFromText( __METHOD__ );
617 $page = WikiPage::factory( $title );
618 /** @var Revision $revOne */
619 $revOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
620 ->value['revision'];
621 // Sleep to ensure different timestamps... )(evil)
622 sleep( 1 );
623 /** @var Revision $revTwo */
624 $revTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
625 ->value['revision'];
626
627 $store = MediaWikiServices::getInstance()->getRevisionStore();
628 $this->assertNull(
629 $store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
630 );
631 $this->assertSame(
632 $revOne->getId(),
633 $store->loadRevisionFromTimestamp(
634 wfGetDB( DB_MASTER ),
635 $title,
636 $revOne->getTimestamp()
637 )->getId()
638 );
639 $this->assertSame(
640 $revTwo->getId(),
641 $store->loadRevisionFromTimestamp(
642 wfGetDB( DB_MASTER ),
643 $title,
644 $revTwo->getTimestamp()
645 )->getId()
646 );
647 }
648
649 /**
650 * @covers \MediaWiki\Storage\RevisionStore::listRevisionSizes
651 */
652 public function testGetParentLengths() {
653 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
654 /** @var Revision $revOne */
655 $revOne = $page->doEditContent(
656 new WikitextContent( __METHOD__ ), __METHOD__
657 )->value['revision'];
658 /** @var Revision $revTwo */
659 $revTwo = $page->doEditContent(
660 new WikitextContent( __METHOD__ . '2' ), __METHOD__
661 )->value['revision'];
662
663 $store = MediaWikiServices::getInstance()->getRevisionStore();
664 $this->assertSame(
665 [
666 $revOne->getId() => strlen( __METHOD__ ),
667 ],
668 $store->listRevisionSizes(
669 wfGetDB( DB_MASTER ),
670 [ $revOne->getId() ]
671 )
672 );
673 $this->assertSame(
674 [
675 $revOne->getId() => strlen( __METHOD__ ),
676 $revTwo->getId() => strlen( __METHOD__ ) + 1,
677 ],
678 $store->listRevisionSizes(
679 wfGetDB( DB_MASTER ),
680 [ $revOne->getId(), $revTwo->getId() ]
681 )
682 );
683 }
684
685 /**
686 * @covers \MediaWiki\Storage\RevisionStore::getPreviousRevision
687 */
688 public function testGetPreviousRevision() {
689 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
690 /** @var Revision $revOne */
691 $revOne = $page->doEditContent(
692 new WikitextContent( __METHOD__ ), __METHOD__
693 )->value['revision'];
694 /** @var Revision $revTwo */
695 $revTwo = $page->doEditContent(
696 new WikitextContent( __METHOD__ . '2' ), __METHOD__
697 )->value['revision'];
698
699 $store = MediaWikiServices::getInstance()->getRevisionStore();
700 $this->assertNull(
701 $store->getPreviousRevision( $store->getRevisionById( $revOne->getId() ) )
702 );
703 $this->assertSame(
704 $revOne->getId(),
705 $store->getPreviousRevision( $store->getRevisionById( $revTwo->getId() ) )->getId()
706 );
707 }
708
709 /**
710 * @covers \MediaWiki\Storage\RevisionStore::getNextRevision
711 */
712 public function testGetNextRevision() {
713 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
714 /** @var Revision $revOne */
715 $revOne = $page->doEditContent(
716 new WikitextContent( __METHOD__ ), __METHOD__
717 )->value['revision'];
718 /** @var Revision $revTwo */
719 $revTwo = $page->doEditContent(
720 new WikitextContent( __METHOD__ . '2' ), __METHOD__
721 )->value['revision'];
722
723 $store = MediaWikiServices::getInstance()->getRevisionStore();
724 $this->assertSame(
725 $revTwo->getId(),
726 $store->getNextRevision( $store->getRevisionById( $revOne->getId() ) )->getId()
727 );
728 $this->assertNull(
729 $store->getNextRevision( $store->getRevisionById( $revTwo->getId() ) )
730 );
731 }
732
733 /**
734 * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
735 */
736 public function testGetTimestampFromId_found() {
737 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
738 /** @var Revision $rev */
739 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
740 ->value['revision'];
741
742 $store = MediaWikiServices::getInstance()->getRevisionStore();
743 $result = $store->getTimestampFromId(
744 $page->getTitle(),
745 $rev->getId()
746 );
747
748 $this->assertSame( $rev->getTimestamp(), $result );
749 }
750
751 /**
752 * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
753 */
754 public function testGetTimestampFromId_notFound() {
755 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
756 /** @var Revision $rev */
757 $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
758 ->value['revision'];
759
760 $store = MediaWikiServices::getInstance()->getRevisionStore();
761 $result = $store->getTimestampFromId(
762 $page->getTitle(),
763 $rev->getId() + 1
764 );
765
766 $this->assertFalse( $result );
767 }
768
769 /**
770 * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByPageId
771 */
772 public function testCountRevisionsByPageId() {
773 $store = MediaWikiServices::getInstance()->getRevisionStore();
774 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
775
776 $this->assertSame(
777 0,
778 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
779 );
780 $page->doEditContent( new WikitextContent( 'a' ), 'a' );
781 $this->assertSame(
782 1,
783 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
784 );
785 $page->doEditContent( new WikitextContent( 'b' ), 'b' );
786 $this->assertSame(
787 2,
788 $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
789 );
790 }
791
792 /**
793 * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByTitle
794 */
795 public function testCountRevisionsByTitle() {
796 $store = MediaWikiServices::getInstance()->getRevisionStore();
797 $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
798
799 $this->assertSame(
800 0,
801 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
802 );
803 $page->doEditContent( new WikitextContent( 'a' ), 'a' );
804 $this->assertSame(
805 1,
806 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
807 );
808 $page->doEditContent( new WikitextContent( 'b' ), 'b' );
809 $this->assertSame(
810 2,
811 $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
812 );
813 }
814
815 /**
816 * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
817 */
818 public function testUserWasLastToEdit_false() {
819 $sysop = $this->getTestSysop()->getUser();
820 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
821 $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
822
823 $store = MediaWikiServices::getInstance()->getRevisionStore();
824 $result = $store->userWasLastToEdit(
825 wfGetDB( DB_MASTER ),
826 $page->getId(),
827 $sysop->getId(),
828 '20160101010101'
829 );
830 $this->assertFalse( $result );
831 }
832
833 /**
834 * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
835 */
836 public function testUserWasLastToEdit_true() {
837 $startTime = wfTimestampNow();
838 $sysop = $this->getTestSysop()->getUser();
839 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
840 $page->doEditContent(
841 new WikitextContent( __METHOD__ ),
842 __METHOD__,
843 0,
844 false,
845 $sysop
846 );
847
848 $store = MediaWikiServices::getInstance()->getRevisionStore();
849 $result = $store->userWasLastToEdit(
850 wfGetDB( DB_MASTER ),
851 $page->getId(),
852 $sysop->getId(),
853 $startTime
854 );
855 $this->assertTrue( $result );
856 }
857
858 /**
859 * @covers \MediaWiki\Storage\RevisionStore::getKnownCurrentRevision
860 */
861 public function testGetKnownCurrentRevision() {
862 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
863 /** @var Revision $rev */
864 $rev = $page->doEditContent(
865 new WikitextContent( __METHOD__. 'b' ),
866 __METHOD__ . 'b',
867 0,
868 false,
869 $this->getTestUser()->getUser()
870 )->value['revision'];
871
872 $store = MediaWikiServices::getInstance()->getRevisionStore();
873 $record = $store->getKnownCurrentRevision(
874 $page->getTitle(),
875 $rev->getId()
876 );
877
878 $this->assertRevisionRecordMatchesRevision( $rev, $record );
879 }
880
881 public function provideNewMutableRevisionFromArray() {
882 yield 'Basic array, with page & id' => [
883 [
884 'id' => 2,
885 'page' => 1,
886 'text_id' => 2,
887 'timestamp' => '20171017114835',
888 'user_text' => '111.0.1.2',
889 'user' => 0,
890 'minor_edit' => false,
891 'deleted' => 0,
892 'len' => 46,
893 'parent_id' => 1,
894 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
895 'comment' => 'Goat Comment!',
896 'content_format' => 'text/x-wiki',
897 'content_model' => 'wikitext',
898 ]
899 ];
900 yield 'Basic array, content object' => [
901 [
902 'id' => 2,
903 'page' => 1,
904 'timestamp' => '20171017114835',
905 'user_text' => '111.0.1.2',
906 'user' => 0,
907 'minor_edit' => false,
908 'deleted' => 0,
909 'len' => 46,
910 'parent_id' => 1,
911 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
912 'comment' => 'Goat Comment!',
913 'content' => new WikitextContent( 'Some Content' ),
914 ]
915 ];
916 yield 'Basic array, with title' => [
917 [
918 'title' => Title::newFromText( 'SomeText' ),
919 'text_id' => 2,
920 'timestamp' => '20171017114835',
921 'user_text' => '111.0.1.2',
922 'user' => 0,
923 'minor_edit' => false,
924 'deleted' => 0,
925 'len' => 46,
926 'parent_id' => 1,
927 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
928 'comment' => 'Goat Comment!',
929 'content_format' => 'text/x-wiki',
930 'content_model' => 'wikitext',
931 ]
932 ];
933 yield 'Basic array, no user field' => [
934 [
935 'id' => 2,
936 'page' => 1,
937 'text_id' => 2,
938 'timestamp' => '20171017114835',
939 'user_text' => '111.0.1.3',
940 'minor_edit' => false,
941 'deleted' => 0,
942 'len' => 46,
943 'parent_id' => 1,
944 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
945 'comment' => 'Goat Comment!',
946 'content_format' => 'text/x-wiki',
947 'content_model' => 'wikitext',
948 ]
949 ];
950 }
951
952 /**
953 * @dataProvider provideNewMutableRevisionFromArray
954 * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
955 */
956 public function testNewMutableRevisionFromArray( array $array ) {
957 $store = MediaWikiServices::getInstance()->getRevisionStore();
958
959 $result = $store->newMutableRevisionFromArray( $array );
960
961 if ( isset( $array['id'] ) ) {
962 $this->assertSame( $array['id'], $result->getId() );
963 }
964 if ( isset( $array['page'] ) ) {
965 $this->assertSame( $array['page'], $result->getPageId() );
966 }
967 $this->assertSame( $array['timestamp'], $result->getTimestamp() );
968 $this->assertSame( $array['user_text'], $result->getUser()->getName() );
969 if ( isset( $array['user'] ) ) {
970 $this->assertSame( $array['user'], $result->getUser()->getId() );
971 }
972 $this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
973 $this->assertSame( $array['deleted'], $result->getVisibility() );
974 $this->assertSame( $array['len'], $result->getSize() );
975 $this->assertSame( $array['parent_id'], $result->getParentId() );
976 $this->assertSame( $array['sha1'], $result->getSha1() );
977 $this->assertSame( $array['comment'], $result->getComment()->text );
978 if ( isset( $array['content'] ) ) {
979 $this->assertTrue(
980 $result->getSlot( 'main' )->getContent()->equals( $array['content'] )
981 );
982 } else {
983 $this->assertSame(
984 $array['content_format'],
985 $result->getSlot( 'main' )->getContent()->getDefaultFormat()
986 );
987 $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
988 }
989 }
990
991 }