RevisionStorageTest: code style fixes
[lhc/web/wiklou.git] / tests / phpunit / includes / RevisionStorageTest.php
1 <?php
2
3 /**
4 * Test class for Revision storage.
5 *
6 * @group ContentHandler
7 * @group Database
8 * ^--- important, causes temporary tables to be used instead of the real database
9 *
10 * @group medium
11 * ^--- important, causes tests not to fail with timeout
12 */
13 class RevisionStorageTest extends MediaWikiTestCase {
14 /**
15 * @var WikiPage $the_page
16 */
17 private $the_page;
18
19 public function __construct( $name = null, array $data = [], $dataName = '' ) {
20 parent::__construct( $name, $data, $dataName );
21
22 $this->tablesUsed = array_merge( $this->tablesUsed,
23 [ 'page',
24 'revision',
25 'ip_changes',
26 'text',
27
28 'recentchanges',
29 'logging',
30
31 'page_props',
32 'pagelinks',
33 'categorylinks',
34 'langlinks',
35 'externallinks',
36 'imagelinks',
37 'templatelinks',
38 'iwlinks' ] );
39 }
40
41 protected function setUp() {
42 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
43
44 parent::setUp();
45
46 $wgExtraNamespaces[12312] = 'Dummy';
47 $wgExtraNamespaces[12313] = 'Dummy_talk';
48
49 $wgNamespaceContentModels[12312] = 'DUMMY';
50 $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
51
52 MWNamespace::clearCaches();
53 // Reset namespace cache
54 $wgContLang->resetNamespaces();
55 if ( !$this->the_page ) {
56 $this->the_page = $this->createPage(
57 'RevisionStorageTest_the_page',
58 "just a dummy page",
59 CONTENT_MODEL_WIKITEXT
60 );
61 }
62
63 $this->tablesUsed[] = 'archive';
64 }
65
66 protected function tearDown() {
67 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
68
69 parent::tearDown();
70
71 unset( $wgExtraNamespaces[12312] );
72 unset( $wgExtraNamespaces[12313] );
73
74 unset( $wgNamespaceContentModels[12312] );
75 unset( $wgContentHandlers['DUMMY'] );
76
77 MWNamespace::clearCaches();
78 // Reset namespace cache
79 $wgContLang->resetNamespaces();
80 }
81
82 protected function makeRevision( $props = null ) {
83 if ( $props === null ) {
84 $props = [];
85 }
86
87 if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
88 $props['text'] = 'Lorem Ipsum';
89 }
90
91 if ( !isset( $props['comment'] ) ) {
92 $props['comment'] = 'just a test';
93 }
94
95 if ( !isset( $props['page'] ) ) {
96 $props['page'] = $this->the_page->getId();
97 }
98
99 $rev = new Revision( $props );
100
101 $dbw = wfGetDB( DB_MASTER );
102 $rev->insertOn( $dbw );
103
104 return $rev;
105 }
106
107 protected function createPage( $page, $text, $model = null ) {
108 if ( is_string( $page ) ) {
109 if ( !preg_match( '/:/', $page ) &&
110 ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
111 ) {
112 $ns = $this->getDefaultWikitextNS();
113 $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page;
114 }
115
116 $page = Title::newFromText( $page );
117 }
118
119 if ( $page instanceof Title ) {
120 $page = new WikiPage( $page );
121 }
122
123 if ( $page->exists() ) {
124 $page->doDeleteArticle( "done" );
125 }
126
127 $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
128 $page->doEditContent( $content, "testing", EDIT_NEW );
129
130 return $page;
131 }
132
133 protected function assertRevEquals( Revision $orig, Revision $rev = null ) {
134 $this->assertNotNull( $rev, 'missing revision' );
135
136 $this->assertEquals( $orig->getId(), $rev->getId() );
137 $this->assertEquals( $orig->getPage(), $rev->getPage() );
138 $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
139 $this->assertEquals( $orig->getUser(), $rev->getUser() );
140 $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
141 $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
142 $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
143 }
144
145 /**
146 * @covers Revision::__construct
147 */
148 public function testConstructFromRow() {
149 $orig = $this->makeRevision();
150
151 $dbr = wfGetDB( DB_REPLICA );
152 $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
153 $this->assertTrue( is_object( $res ), 'query failed' );
154
155 $row = $res->fetchObject();
156 $res->free();
157
158 $rev = new Revision( $row );
159
160 $this->assertRevEquals( $orig, $rev );
161 }
162
163 /**
164 * @covers Revision::newFromRow
165 */
166 public function testNewFromRow() {
167 $orig = $this->makeRevision();
168
169 $dbr = wfGetDB( DB_REPLICA );
170 $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
171 $this->assertTrue( is_object( $res ), 'query failed' );
172
173 $row = $res->fetchObject();
174 $res->free();
175
176 $rev = Revision::newFromRow( $row );
177
178 $this->assertRevEquals( $orig, $rev );
179 }
180
181 /**
182 * @covers Revision::newFromArchiveRow
183 */
184 public function testNewFromArchiveRow() {
185 $page = $this->createPage(
186 'RevisionStorageTest_testNewFromArchiveRow',
187 'Lorem Ipsum',
188 CONTENT_MODEL_WIKITEXT
189 );
190 $orig = $page->getRevision();
191 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
192
193 $dbr = wfGetDB( DB_REPLICA );
194 $res = $dbr->select(
195 'archive', Revision::selectArchiveFields(), [ 'ar_rev_id' => $orig->getId() ]
196 );
197 $this->assertTrue( is_object( $res ), 'query failed' );
198
199 $row = $res->fetchObject();
200 $res->free();
201
202 $rev = Revision::newFromArchiveRow( $row );
203
204 $this->assertRevEquals( $orig, $rev );
205 }
206
207 /**
208 * @covers Revision::newFromId
209 */
210 public function testNewFromId() {
211 $orig = $this->makeRevision();
212
213 $rev = Revision::newFromId( $orig->getId() );
214
215 $this->assertRevEquals( $orig, $rev );
216 }
217
218 /**
219 * @covers Revision::fetchRevision
220 */
221 public function testFetchRevision() {
222 $page = $this->createPage(
223 'RevisionStorageTest_testFetchRevision',
224 'one',
225 CONTENT_MODEL_WIKITEXT
226 );
227
228 // Hidden process cache assertion below
229 $page->getRevision()->getId();
230
231 $page->doEditContent( new WikitextContent( 'two' ), 'second rev' );
232 $id = $page->getRevision()->getId();
233
234 $res = Revision::fetchRevision( $page->getTitle() );
235
236 # note: order is unspecified
237 $rows = [];
238 while ( ( $row = $res->fetchObject() ) ) {
239 $rows[$row->rev_id] = $row;
240 }
241
242 $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
243 $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
244 }
245
246 /**
247 * @covers Revision::selectFields
248 */
249 public function testSelectFields() {
250 global $wgContentHandlerUseDB;
251
252 $fields = Revision::selectFields();
253
254 $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
255 $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
256 $this->assertTrue(
257 in_array( 'rev_timestamp', $fields ),
258 'missing rev_timestamp in list of fields'
259 );
260 $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
261
262 if ( $wgContentHandlerUseDB ) {
263 $this->assertTrue( in_array( 'rev_content_model', $fields ),
264 'missing rev_content_model in list of fields' );
265 $this->assertTrue( in_array( 'rev_content_format', $fields ),
266 'missing rev_content_format in list of fields' );
267 }
268 }
269
270 /**
271 * @covers Revision::getPage
272 */
273 public function testGetPage() {
274 $page = $this->the_page;
275
276 $orig = $this->makeRevision( [ 'page' => $page->getId() ] );
277 $rev = Revision::newFromId( $orig->getId() );
278
279 $this->assertEquals( $page->getId(), $rev->getPage() );
280 }
281
282 /**
283 * @covers Revision::getContent
284 */
285 public function testGetContent_failure() {
286 $rev = new Revision( [
287 'page' => $this->the_page->getId(),
288 'content_model' => $this->the_page->getContentModel(),
289 'text_id' => 123456789, // not in the test DB
290 ] );
291
292 $this->assertNull( $rev->getContent(),
293 "getContent() should return null if the revision's text blob could not be loaded." );
294
295 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
296 $this->assertNull( $rev->getContent(),
297 "getContent() should return null if the revision's text blob could not be loaded." );
298 }
299
300 /**
301 * @covers Revision::getContent
302 */
303 public function testGetContent() {
304 $orig = $this->makeRevision( [ 'text' => 'hello hello.' ] );
305 $rev = Revision::newFromId( $orig->getId() );
306
307 $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() );
308 }
309
310 /**
311 * @covers Revision::getContentModel
312 */
313 public function testGetContentModel() {
314 global $wgContentHandlerUseDB;
315
316 if ( !$wgContentHandlerUseDB ) {
317 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
318 }
319
320 $orig = $this->makeRevision( [ 'text' => 'hello hello.',
321 'content_model' => CONTENT_MODEL_JAVASCRIPT ] );
322 $rev = Revision::newFromId( $orig->getId() );
323
324 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
325 }
326
327 /**
328 * @covers Revision::getContentFormat
329 */
330 public function testGetContentFormat() {
331 global $wgContentHandlerUseDB;
332
333 if ( !$wgContentHandlerUseDB ) {
334 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
335 }
336
337 $orig = $this->makeRevision( [
338 'text' => 'hello hello.',
339 'content_model' => CONTENT_MODEL_JAVASCRIPT,
340 'content_format' => CONTENT_FORMAT_JAVASCRIPT
341 ] );
342 $rev = Revision::newFromId( $orig->getId() );
343
344 $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() );
345 }
346
347 /**
348 * @covers Revision::isCurrent
349 */
350 public function testIsCurrent() {
351 $page = $this->createPage(
352 'RevisionStorageTest_testIsCurrent',
353 'Lorem Ipsum',
354 CONTENT_MODEL_WIKITEXT
355 );
356 $rev1 = $page->getRevision();
357
358 # @todo find out if this should be true
359 # $this->assertTrue( $rev1->isCurrent() );
360
361 $rev1x = Revision::newFromId( $rev1->getId() );
362 $this->assertTrue( $rev1x->isCurrent() );
363
364 $page->doEditContent(
365 ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
366 'second rev'
367 );
368 $rev2 = $page->getRevision();
369
370 # @todo find out if this should be true
371 # $this->assertTrue( $rev2->isCurrent() );
372
373 $rev1x = Revision::newFromId( $rev1->getId() );
374 $this->assertFalse( $rev1x->isCurrent() );
375
376 $rev2x = Revision::newFromId( $rev2->getId() );
377 $this->assertTrue( $rev2x->isCurrent() );
378 }
379
380 /**
381 * @covers Revision::getPrevious
382 */
383 public function testGetPrevious() {
384 $page = $this->createPage(
385 'RevisionStorageTest_testGetPrevious',
386 'Lorem Ipsum testGetPrevious',
387 CONTENT_MODEL_WIKITEXT
388 );
389 $rev1 = $page->getRevision();
390
391 $this->assertNull( $rev1->getPrevious() );
392
393 $page->doEditContent(
394 ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
395 'second rev testGetPrevious' );
396 $rev2 = $page->getRevision();
397
398 $this->assertNotNull( $rev2->getPrevious() );
399 $this->assertEquals( $rev1->getId(), $rev2->getPrevious()->getId() );
400 }
401
402 /**
403 * @covers Revision::getNext
404 */
405 public function testGetNext() {
406 $page = $this->createPage(
407 'RevisionStorageTest_testGetNext',
408 'Lorem Ipsum testGetNext',
409 CONTENT_MODEL_WIKITEXT
410 );
411 $rev1 = $page->getRevision();
412
413 $this->assertNull( $rev1->getNext() );
414
415 $page->doEditContent(
416 ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
417 'second rev testGetNext'
418 );
419 $rev2 = $page->getRevision();
420
421 $this->assertNotNull( $rev1->getNext() );
422 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
423 }
424
425 /**
426 * @covers Revision::newNullRevision
427 */
428 public function testNewNullRevision() {
429 $page = $this->createPage(
430 'RevisionStorageTest_testNewNullRevision',
431 'some testing text',
432 CONTENT_MODEL_WIKITEXT
433 );
434 $orig = $page->getRevision();
435
436 $dbw = wfGetDB( DB_MASTER );
437 $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false );
438
439 $this->assertNotEquals( $orig->getId(), $rev->getId(),
440 'new null revision shold have a different id from the original revision' );
441 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
442 'new null revision shold have the same text id as the original revision' );
443 $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
444 }
445
446 /**
447 * @covers Revision::insertOn
448 */
449 public function testInsertOn() {
450 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
451
452 $orig = $this->makeRevision( [
453 'user_text' => $ip
454 ] );
455
456 // Make sure the revision was copied to ip_changes
457 $dbr = wfGetDB( DB_REPLICA );
458 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
459 $row = $res->fetchObject();
460
461 $this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
462 $this->assertEquals( $orig->getTimestamp(), $row->ipc_rev_timestamp );
463 }
464
465 public static function provideUserWasLastToEdit() {
466 yield 'actually the last edit' => [ 3, true ];
467 yield 'not the current edit, but still by this user' => [ 2, true ];
468 yield 'edit by another user' => [ 1, false ];
469 yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
470 }
471
472 /**
473 * @dataProvider provideUserWasLastToEdit
474 */
475 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
476 $userA = User::newFromName( "RevisionStorageTest_userA" );
477 $userB = User::newFromName( "RevisionStorageTest_userB" );
478
479 if ( $userA->getId() === 0 ) {
480 $userA = User::createNew( $userA->getName() );
481 }
482
483 if ( $userB->getId() === 0 ) {
484 $userB = User::createNew( $userB->getName() );
485 }
486
487 $ns = $this->getDefaultWikitextNS();
488
489 $dbw = wfGetDB( DB_MASTER );
490 $revisions = [];
491
492 // create revisions -----------------------------
493 $page = WikiPage::factory( Title::newFromText(
494 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
495 $page->insertOn( $dbw );
496
497 $revisions[0] = new Revision( [
498 'page' => $page->getId(),
499 // we need the title to determine the page's default content model
500 'title' => $page->getTitle(),
501 'timestamp' => '20120101000000',
502 'user' => $userA->getId(),
503 'text' => 'zero',
504 'content_model' => CONTENT_MODEL_WIKITEXT,
505 'summary' => 'edit zero'
506 ] );
507 $revisions[0]->insertOn( $dbw );
508
509 $revisions[1] = new Revision( [
510 'page' => $page->getId(),
511 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
512 'title' => $page->getTitle(),
513 'timestamp' => '20120101000100',
514 'user' => $userA->getId(),
515 'text' => 'one',
516 'content_model' => CONTENT_MODEL_WIKITEXT,
517 'summary' => 'edit one'
518 ] );
519 $revisions[1]->insertOn( $dbw );
520
521 $revisions[2] = new Revision( [
522 'page' => $page->getId(),
523 'title' => $page->getTitle(),
524 'timestamp' => '20120101000200',
525 'user' => $userB->getId(),
526 'text' => 'two',
527 'content_model' => CONTENT_MODEL_WIKITEXT,
528 'summary' => 'edit two'
529 ] );
530 $revisions[2]->insertOn( $dbw );
531
532 $revisions[3] = new Revision( [
533 'page' => $page->getId(),
534 'title' => $page->getTitle(),
535 'timestamp' => '20120101000300',
536 'user' => $userA->getId(),
537 'text' => 'three',
538 'content_model' => CONTENT_MODEL_WIKITEXT,
539 'summary' => 'edit three'
540 ] );
541 $revisions[3]->insertOn( $dbw );
542
543 $revisions[4] = new Revision( [
544 'page' => $page->getId(),
545 'title' => $page->getTitle(),
546 'timestamp' => '20120101000200',
547 'user' => $userA->getId(),
548 'text' => 'zero',
549 'content_model' => CONTENT_MODEL_WIKITEXT,
550 'summary' => 'edit four'
551 ] );
552 $revisions[4]->insertOn( $dbw );
553
554 // test it ---------------------------------
555 $since = $revisions[$sinceIdx]->getTimestamp();
556
557 $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
558
559 $this->assertEquals( $expectedLast, $wasLast );
560 }
561 }