Merge "RCFilters: define consistent interface in ChangesListFilterGroup"
[lhc/web/wiklou.git] / tests / phpunit / includes / RevisionUnitTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @group ContentHandler
7 */
8 class RevisionUnitTest extends MediaWikiTestCase {
9
10 public function provideConstructFromArray() {
11 yield 'with text' => [
12 [
13 'text' => 'hello world.',
14 'content_model' => CONTENT_MODEL_JAVASCRIPT
15 ],
16 ];
17 yield 'with content' => [
18 [
19 'content' => new JavaScriptContent( 'hellow world.' )
20 ],
21 ];
22 }
23
24 /**
25 * @dataProvider provideConstructFromArray
26 * @covers Revision::__construct
27 * @covers Revision::constructFromRowArray
28 */
29 public function testConstructFromArray( array $rowArray ) {
30 $rev = new Revision( $rowArray );
31 $this->assertNotNull( $rev->getContent(), 'no content object available' );
32 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
33 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
34 }
35
36 public function provideConstructFromArrayThrowsExceptions() {
37 yield 'content and text_id both not empty' => [
38 [
39 'content' => new WikitextContent( 'GOAT' ),
40 'text_id' => 'someid',
41 ],
42 new MWException( "Text already stored in external store (id someid), " .
43 "can't serialize content object" )
44 ];
45 yield 'with bad content object (class)' => [
46 [ 'content' => new stdClass() ],
47 new MWException( '`content` field must contain a Content object.' )
48 ];
49 yield 'with bad content object (string)' => [
50 [ 'content' => 'ImAGoat' ],
51 new MWException( '`content` field must contain a Content object.' )
52 ];
53 yield 'bad row format' => [
54 'imastring, not a row',
55 new MWException( 'Revision constructor passed invalid row format.' )
56 ];
57 }
58
59 /**
60 * @dataProvider provideConstructFromArrayThrowsExceptions
61 * @covers Revision::__construct
62 * @covers Revision::constructFromRowArray
63 */
64 public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
65 $this->setExpectedException(
66 get_class( $expectedException ),
67 $expectedException->getMessage(),
68 $expectedException->getCode()
69 );
70 new Revision( $rowArray );
71 }
72
73 public function provideConstructFromRow() {
74 yield 'Full construction' => [
75 [
76 'rev_id' => '2',
77 'rev_page' => '1',
78 'rev_text_id' => '2',
79 'rev_timestamp' => '20171017114835',
80 'rev_user_text' => '127.0.0.1',
81 'rev_user' => '0',
82 'rev_minor_edit' => '0',
83 'rev_deleted' => '0',
84 'rev_len' => '46',
85 'rev_parent_id' => '1',
86 'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
87 'rev_comment_text' => 'Goat Comment!',
88 'rev_comment_data' => null,
89 'rev_comment_cid' => null,
90 'rev_content_format' => 'GOATFORMAT',
91 'rev_content_model' => 'GOATMODEL',
92 ],
93 function ( RevisionUnitTest $testCase, Revision $rev ) {
94 $testCase->assertSame( 2, $rev->getId() );
95 $testCase->assertSame( 1, $rev->getPage() );
96 $testCase->assertSame( 2, $rev->getTextId() );
97 $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
98 $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
99 $testCase->assertSame( 0, $rev->getUser() );
100 $testCase->assertSame( false, $rev->isMinor() );
101 $testCase->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
102 $testCase->assertSame( 46, $rev->getSize() );
103 $testCase->assertSame( 1, $rev->getParentId() );
104 $testCase->assertSame( 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z', $rev->getSha1() );
105 $testCase->assertSame( 'Goat Comment!', $rev->getComment() );
106 $testCase->assertSame( 'GOATFORMAT', $rev->getContentFormat() );
107 $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
108 }
109 ];
110 yield 'null fields' => [
111 [
112 'rev_id' => '2',
113 'rev_page' => '1',
114 'rev_text_id' => '2',
115 'rev_timestamp' => '20171017114835',
116 'rev_user_text' => '127.0.0.1',
117 'rev_user' => '0',
118 'rev_minor_edit' => '0',
119 'rev_deleted' => '0',
120 'rev_comment_text' => 'Goat Comment!',
121 'rev_comment_data' => null,
122 'rev_comment_cid' => null,
123 ],
124 function ( RevisionUnitTest $testCase, Revision $rev ) {
125 $testCase->assertNull( $rev->getSize() );
126 $testCase->assertNull( $rev->getParentId() );
127 $testCase->assertNull( $rev->getSha1() );
128 $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat() );
129 $testCase->assertSame( 'wikitext', $rev->getContentModel() );
130 }
131 ];
132 }
133
134 /**
135 * @dataProvider provideConstructFromRow
136 * @covers Revision::__construct
137 * @covers Revision::constructFromDbRowObject
138 */
139 public function testConstructFromRow( array $arrayData, $assertions ) {
140 $row = (object)$arrayData;
141 $rev = new Revision( $row );
142 $assertions( $this, $rev );
143 }
144
145 public function provideGetRevisionText() {
146 yield 'Generic test' => [
147 'This is a goat of revision text.',
148 [
149 'old_flags' => '',
150 'old_text' => 'This is a goat of revision text.',
151 ],
152 ];
153 }
154
155 public function provideGetId() {
156 yield [
157 [],
158 null
159 ];
160 yield [
161 [ 'id' => 998 ],
162 998
163 ];
164 }
165
166 /**
167 * @dataProvider provideGetId
168 * @covers Revision::getId
169 */
170 public function testGetId( $rowArray, $expectedId ) {
171 $rev = new Revision( $rowArray );
172 $this->assertEquals( $expectedId, $rev->getId() );
173 }
174
175 public function provideSetId() {
176 yield [ '123', 123 ];
177 yield [ 456, 456 ];
178 }
179
180 /**
181 * @dataProvider provideSetId
182 * @covers Revision::setId
183 */
184 public function testSetId( $input, $expected ) {
185 $rev = new Revision( [] );
186 $rev->setId( $input );
187 $this->assertSame( $expected, $rev->getId() );
188 }
189
190 public function provideSetUserIdAndName() {
191 yield [ '123', 123, 'GOaT' ];
192 yield [ 456, 456, 'GOaT' ];
193 }
194
195 /**
196 * @dataProvider provideSetUserIdAndName
197 * @covers Revision::setUserIdAndName
198 */
199 public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
200 $rev = new Revision( [] );
201 $rev->setUserIdAndName( $inputId, $name );
202 $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
203 $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
204 }
205
206 public function provideGetTextId() {
207 yield [ [], null ];
208 yield [ [ 'text_id' => '123' ], 123 ];
209 yield [ [ 'text_id' => 456 ], 456 ];
210 }
211
212 /**
213 * @dataProvider provideGetTextId
214 * @covers Revision::getTextId()
215 */
216 public function testGetTextId( $rowArray, $expected ) {
217 $rev = new Revision( $rowArray );
218 $this->assertSame( $expected, $rev->getTextId() );
219 }
220
221 public function provideGetParentId() {
222 yield [ [], null ];
223 yield [ [ 'parent_id' => '123' ], 123 ];
224 yield [ [ 'parent_id' => 456 ], 456 ];
225 }
226
227 /**
228 * @dataProvider provideGetParentId
229 * @covers Revision::getParentId()
230 */
231 public function testGetParentId( $rowArray, $expected ) {
232 $rev = new Revision( $rowArray );
233 $this->assertSame( $expected, $rev->getParentId() );
234 }
235
236 /**
237 * @covers Revision::getRevisionText
238 * @dataProvider provideGetRevisionText
239 */
240 public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
241 $this->assertEquals(
242 $expected,
243 Revision::getRevisionText( (object)$rowData, $prefix, $wiki ) );
244 }
245
246 public function provideGetRevisionTextWithZlibExtension() {
247 yield 'Generic gzip test' => [
248 'This is a small goat of revision text.',
249 [
250 'old_flags' => 'gzip',
251 'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
252 ],
253 ];
254 }
255
256 /**
257 * @covers Revision::getRevisionText
258 * @dataProvider provideGetRevisionTextWithZlibExtension
259 */
260 public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
261 $this->checkPHPExtension( 'zlib' );
262 $this->testGetRevisionText( $expected, $rowData );
263 }
264
265 public function provideGetRevisionTextWithLegacyEncoding() {
266 yield 'Utf8Native' => [
267 "Wiki est l'\xc3\xa9cole superieur !",
268 'iso-8859-1',
269 [
270 'old_flags' => 'utf-8',
271 'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
272 ]
273 ];
274 yield 'Utf8Legacy' => [
275 "Wiki est l'\xc3\xa9cole superieur !",
276 'iso-8859-1',
277 [
278 'old_flags' => '',
279 'old_text' => "Wiki est l'\xe9cole superieur !",
280 ]
281 ];
282 }
283
284 /**
285 * @covers Revision::getRevisionText
286 * @dataProvider provideGetRevisionTextWithLegacyEncoding
287 */
288 public function testGetRevisionWithLegacyEncoding( $expected, $encoding, $rowData ) {
289 $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
290 $this->testGetRevisionText( $expected, $rowData );
291 }
292
293 public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
294 /**
295 * WARNING!
296 * Do not set the external flag!
297 * Otherwise, getRevisionText will hit the live database (if ExternalStore is enabled)!
298 */
299 yield 'Utf8NativeGzip' => [
300 "Wiki est l'\xc3\xa9cole superieur !",
301 'iso-8859-1',
302 [
303 'old_flags' => 'gzip,utf-8',
304 'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
305 ]
306 ];
307 yield 'Utf8LegacyGzip' => [
308 "Wiki est l'\xc3\xa9cole superieur !",
309 'iso-8859-1',
310 [
311 'old_flags' => 'gzip',
312 'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
313 ]
314 ];
315 }
316
317 /**
318 * @covers Revision::getRevisionText
319 * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
320 */
321 public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $encoding, $rowData ) {
322 $this->checkPHPExtension( 'zlib' );
323 $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
324 $this->testGetRevisionText( $expected, $rowData );
325 }
326
327 /**
328 * @covers Revision::compressRevisionText
329 */
330 public function testCompressRevisionTextUtf8() {
331 $row = new stdClass;
332 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
333 $row->old_flags = Revision::compressRevisionText( $row->old_text );
334 $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
335 "Flags should contain 'utf-8'" );
336 $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
337 "Flags should not contain 'gzip'" );
338 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
339 $row->old_text, "Direct check" );
340 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
341 Revision::getRevisionText( $row ), "getRevisionText" );
342 }
343
344 /**
345 * @covers Revision::compressRevisionText
346 */
347 public function testCompressRevisionTextUtf8Gzip() {
348 $this->checkPHPExtension( 'zlib' );
349 $this->setMwGlobals( 'wgCompressRevisions', true );
350
351 $row = new stdClass;
352 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
353 $row->old_flags = Revision::compressRevisionText( $row->old_text );
354 $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
355 "Flags should contain 'utf-8'" );
356 $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
357 "Flags should contain 'gzip'" );
358 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
359 gzinflate( $row->old_text ), "Direct check" );
360 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
361 Revision::getRevisionText( $row ), "getRevisionText" );
362 }
363
364 /**
365 * @covers Revision::userJoinCond
366 */
367 public function testUserJoinCond() {
368 $this->assertEquals(
369 [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
370 Revision::userJoinCond()
371 );
372 }
373
374 /**
375 * @covers Revision::pageJoinCond
376 */
377 public function testPageJoinCond() {
378 $this->assertEquals(
379 [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
380 Revision::pageJoinCond()
381 );
382 }
383
384 public function provideSelectFields() {
385 yield [
386 true,
387 [
388 'rev_id',
389 'rev_page',
390 'rev_text_id',
391 'rev_timestamp',
392 'rev_user_text',
393 'rev_user',
394 'rev_minor_edit',
395 'rev_deleted',
396 'rev_len',
397 'rev_parent_id',
398 'rev_sha1',
399 'rev_comment_text' => 'rev_comment',
400 'rev_comment_data' => 'NULL',
401 'rev_comment_cid' => 'NULL',
402 'rev_content_format',
403 'rev_content_model',
404 ]
405 ];
406 yield [
407 false,
408 [
409 'rev_id',
410 'rev_page',
411 'rev_text_id',
412 'rev_timestamp',
413 'rev_user_text',
414 'rev_user',
415 'rev_minor_edit',
416 'rev_deleted',
417 'rev_len',
418 'rev_parent_id',
419 'rev_sha1',
420 'rev_comment_text' => 'rev_comment',
421 'rev_comment_data' => 'NULL',
422 'rev_comment_cid' => 'NULL',
423 ]
424 ];
425 }
426
427 /**
428 * @dataProvider provideSelectFields
429 * @covers Revision::selectFields
430 * @todo a true unit test would mock CommentStore
431 */
432 public function testSelectFields( $contentHandlerUseDB, $expected ) {
433 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
434 $this->assertEquals( $expected, Revision::selectFields() );
435 }
436
437 public function provideSelectArchiveFields() {
438 yield [
439 true,
440 [
441 'ar_id',
442 'ar_page_id',
443 'ar_rev_id',
444 'ar_text',
445 'ar_text_id',
446 'ar_timestamp',
447 'ar_user_text',
448 'ar_user',
449 'ar_minor_edit',
450 'ar_deleted',
451 'ar_len',
452 'ar_parent_id',
453 'ar_sha1',
454 'ar_comment_text' => 'ar_comment',
455 'ar_comment_data' => 'NULL',
456 'ar_comment_cid' => 'NULL',
457 'ar_content_format',
458 'ar_content_model',
459 ]
460 ];
461 yield [
462 false,
463 [
464 'ar_id',
465 'ar_page_id',
466 'ar_rev_id',
467 'ar_text',
468 'ar_text_id',
469 'ar_timestamp',
470 'ar_user_text',
471 'ar_user',
472 'ar_minor_edit',
473 'ar_deleted',
474 'ar_len',
475 'ar_parent_id',
476 'ar_sha1',
477 'ar_comment_text' => 'ar_comment',
478 'ar_comment_data' => 'NULL',
479 'ar_comment_cid' => 'NULL',
480 ]
481 ];
482 }
483
484 /**
485 * @dataProvider provideSelectArchiveFields
486 * @covers Revision::selectArchiveFields
487 * @todo a true unit test would mock CommentStore
488 */
489 public function testSelectArchiveFields( $contentHandlerUseDB, $expected ) {
490 $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
491 $this->assertEquals( $expected, Revision::selectArchiveFields() );
492 }
493
494 /**
495 * @covers Revision::selectTextFields
496 */
497 public function testSelectTextFields() {
498 $this->assertEquals(
499 [
500 'old_text',
501 'old_flags',
502 ],
503 Revision::selectTextFields()
504 );
505 }
506
507 /**
508 * @covers Revision::selectPageFields
509 */
510 public function testSelectPageFields() {
511 $this->assertEquals(
512 [
513 'page_namespace',
514 'page_title',
515 'page_id',
516 'page_latest',
517 'page_is_redirect',
518 'page_len',
519 ],
520 Revision::selectPageFields()
521 );
522 }
523
524 /**
525 * @covers Revision::selectUserFields
526 */
527 public function testSelectUserFields() {
528 $this->assertEquals(
529 [
530 'user_name',
531 ],
532 Revision::selectUserFields()
533 );
534 }
535
536 public function provideFetchFromConds() {
537 yield [ 0, [] ];
538 yield [ Revision::READ_LOCKING, [ 'FOR UPDATE' ] ];
539 }
540
541 /**
542 * @dataProvider provideFetchFromConds
543 * @covers Revision::fetchFromConds
544 */
545 public function testFetchFromConds( $flags, array $options ) {
546 $conditions = [ 'conditionsArray' ];
547
548 $db = $this->getMock( IDatabase::class );
549 $db->expects( $this->once() )
550 ->method( 'selectRow' )
551 ->with(
552 $this->equalTo( [ 'revision', 'page', 'user' ] ),
553 // We don't really care about the fields are they come from the selectField methods
554 $this->isType( 'array' ),
555 $this->equalTo( $conditions ),
556 // Method name
557 $this->equalTo( 'Revision::fetchFromConds' ),
558 $this->equalTo( $options ),
559 // We don't really care about the join conds are they come from the joinCond methods
560 $this->isType( 'array' )
561 )
562 ->willReturn( 'RETURNVALUE' );
563
564 $wrapper = TestingAccessWrapper::newFromClass( Revision::class );
565 $result = $wrapper->fetchFromConds( $db, $conditions, $flags );
566
567 $this->assertEquals( 'RETURNVALUE', $result );
568 }
569 }