Merge "RCFilters: rephrase the feedback link text"
[lhc/web/wiklou.git] / tests / phpunit / includes / CommentStoreTest.php
1 <?php
2
3 use Wikimedia\ScopedCallback;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * @group Database
8 * @covers CommentStore
9 * @covers CommentStoreComment
10 */
11 class CommentStoreTest extends MediaWikiLangTestCase {
12
13 protected $tablesUsed = [
14 'revision',
15 'revision_comment_temp',
16 'ipblocks',
17 'comment',
18 ];
19
20 /**
21 * Create a store for a particular stage
22 * @param int $stage
23 * @param string $key
24 * @return CommentStore
25 */
26 protected function makeStore( $stage, $key ) {
27 $store = new CommentStore( $key );
28 TestingAccessWrapper::newFromObject( $store )->stage = $stage;
29 return $store;
30 }
31
32 /**
33 * @dataProvider provideGetFields
34 * @param int $stage
35 * @param string $key
36 * @param array $expect
37 */
38 public function testGetFields( $stage, $key, $expect ) {
39 $store = $this->makeStore( $stage, $key );
40 $result = $store->getFields();
41 $this->assertEquals( $expect, $result );
42 }
43
44 public static function provideGetFields() {
45 return [
46 'Simple table, old' => [
47 MIGRATION_OLD, 'ipb_reason',
48 [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
49 ],
50 'Simple table, write-both' => [
51 MIGRATION_WRITE_BOTH, 'ipb_reason',
52 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
53 ],
54 'Simple table, write-new' => [
55 MIGRATION_WRITE_NEW, 'ipb_reason',
56 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
57 ],
58 'Simple table, new' => [
59 MIGRATION_NEW, 'ipb_reason',
60 [ 'ipb_reason_id' => 'ipb_reason_id' ],
61 ],
62
63 'Revision, old' => [
64 MIGRATION_OLD, 'rev_comment',
65 [
66 'rev_comment_text' => 'rev_comment',
67 'rev_comment_data' => 'NULL',
68 'rev_comment_cid' => 'NULL',
69 ],
70 ],
71 'Revision, write-both' => [
72 MIGRATION_WRITE_BOTH, 'rev_comment',
73 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
74 ],
75 'Revision, write-new' => [
76 MIGRATION_WRITE_NEW, 'rev_comment',
77 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
78 ],
79 'Revision, new' => [
80 MIGRATION_NEW, 'rev_comment',
81 [ 'rev_comment_pk' => 'rev_id' ],
82 ],
83
84 'Image, old' => [
85 MIGRATION_OLD, 'img_description',
86 [
87 'img_description_text' => 'img_description',
88 'img_description_data' => 'NULL',
89 'img_description_cid' => 'NULL',
90 ],
91 ],
92 'Image, write-both' => [
93 MIGRATION_WRITE_BOTH, 'img_description',
94 [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
95 ],
96 'Image, write-new' => [
97 MIGRATION_WRITE_NEW, 'img_description',
98 [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
99 ],
100 'Image, new' => [
101 MIGRATION_NEW, 'img_description',
102 [ 'img_description_pk' => 'img_name' ],
103 ],
104 ];
105 }
106
107 /**
108 * @dataProvider provideGetJoin
109 * @param int $stage
110 * @param string $key
111 * @param array $expect
112 */
113 public function testGetJoin( $stage, $key, $expect ) {
114 $store = $this->makeStore( $stage, $key );
115 $result = $store->getJoin();
116 $this->assertEquals( $expect, $result );
117 }
118
119 public static function provideGetJoin() {
120 return [
121 'Simple table, old' => [
122 MIGRATION_OLD, 'ipb_reason', [
123 'tables' => [],
124 'fields' => [
125 'ipb_reason_text' => 'ipb_reason',
126 'ipb_reason_data' => 'NULL',
127 'ipb_reason_cid' => 'NULL',
128 ],
129 'joins' => [],
130 ],
131 ],
132 'Simple table, write-both' => [
133 MIGRATION_WRITE_BOTH, 'ipb_reason', [
134 'tables' => [ 'comment_ipb_reason' => 'comment' ],
135 'fields' => [
136 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
137 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
138 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
139 ],
140 'joins' => [
141 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
142 ],
143 ],
144 ],
145 'Simple table, write-new' => [
146 MIGRATION_WRITE_NEW, 'ipb_reason', [
147 'tables' => [ 'comment_ipb_reason' => 'comment' ],
148 'fields' => [
149 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
150 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
151 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
152 ],
153 'joins' => [
154 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
155 ],
156 ],
157 ],
158 'Simple table, new' => [
159 MIGRATION_NEW, 'ipb_reason', [
160 'tables' => [ 'comment_ipb_reason' => 'comment' ],
161 'fields' => [
162 'ipb_reason_text' => 'comment_ipb_reason.comment_text',
163 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
164 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
165 ],
166 'joins' => [
167 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
168 ],
169 ],
170 ],
171
172 'Revision, old' => [
173 MIGRATION_OLD, 'rev_comment', [
174 'tables' => [],
175 'fields' => [
176 'rev_comment_text' => 'rev_comment',
177 'rev_comment_data' => 'NULL',
178 'rev_comment_cid' => 'NULL',
179 ],
180 'joins' => [],
181 ],
182 ],
183 'Revision, write-both' => [
184 MIGRATION_WRITE_BOTH, 'rev_comment', [
185 'tables' => [
186 'temp_rev_comment' => 'revision_comment_temp',
187 'comment_rev_comment' => 'comment',
188 ],
189 'fields' => [
190 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
191 'rev_comment_data' => 'comment_rev_comment.comment_data',
192 'rev_comment_cid' => 'comment_rev_comment.comment_id',
193 ],
194 'joins' => [
195 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
196 'comment_rev_comment' => [ 'LEFT JOIN',
197 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
198 ],
199 ],
200 ],
201 'Revision, write-new' => [
202 MIGRATION_WRITE_NEW, 'rev_comment', [
203 'tables' => [
204 'temp_rev_comment' => 'revision_comment_temp',
205 'comment_rev_comment' => 'comment',
206 ],
207 'fields' => [
208 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
209 'rev_comment_data' => 'comment_rev_comment.comment_data',
210 'rev_comment_cid' => 'comment_rev_comment.comment_id',
211 ],
212 'joins' => [
213 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
214 'comment_rev_comment' => [ 'LEFT JOIN',
215 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
216 ],
217 ],
218 ],
219 'Revision, new' => [
220 MIGRATION_NEW, 'rev_comment', [
221 'tables' => [
222 'temp_rev_comment' => 'revision_comment_temp',
223 'comment_rev_comment' => 'comment',
224 ],
225 'fields' => [
226 'rev_comment_text' => 'comment_rev_comment.comment_text',
227 'rev_comment_data' => 'comment_rev_comment.comment_data',
228 'rev_comment_cid' => 'comment_rev_comment.comment_id',
229 ],
230 'joins' => [
231 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
232 'comment_rev_comment' => [ 'JOIN',
233 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
234 ],
235 ],
236 ],
237
238 'Image, old' => [
239 MIGRATION_OLD, 'img_description', [
240 'tables' => [],
241 'fields' => [
242 'img_description_text' => 'img_description',
243 'img_description_data' => 'NULL',
244 'img_description_cid' => 'NULL',
245 ],
246 'joins' => [],
247 ],
248 ],
249 'Image, write-both' => [
250 MIGRATION_WRITE_BOTH, 'img_description', [
251 'tables' => [
252 'temp_img_description' => 'image_comment_temp',
253 'comment_img_description' => 'comment',
254 ],
255 'fields' => [
256 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
257 'img_description_data' => 'comment_img_description.comment_data',
258 'img_description_cid' => 'comment_img_description.comment_id',
259 ],
260 'joins' => [
261 'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
262 'comment_img_description' => [ 'LEFT JOIN',
263 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
264 ],
265 ],
266 ],
267 'Image, write-new' => [
268 MIGRATION_WRITE_NEW, 'img_description', [
269 'tables' => [
270 'temp_img_description' => 'image_comment_temp',
271 'comment_img_description' => 'comment',
272 ],
273 'fields' => [
274 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
275 'img_description_data' => 'comment_img_description.comment_data',
276 'img_description_cid' => 'comment_img_description.comment_id',
277 ],
278 'joins' => [
279 'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
280 'comment_img_description' => [ 'LEFT JOIN',
281 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
282 ],
283 ],
284 ],
285 'Image, new' => [
286 MIGRATION_NEW, 'img_description', [
287 'tables' => [
288 'temp_img_description' => 'image_comment_temp',
289 'comment_img_description' => 'comment',
290 ],
291 'fields' => [
292 'img_description_text' => 'comment_img_description.comment_text',
293 'img_description_data' => 'comment_img_description.comment_data',
294 'img_description_cid' => 'comment_img_description.comment_id',
295 ],
296 'joins' => [
297 'temp_img_description' => [ 'JOIN', 'temp_img_description.imgcomment_name = img_name' ],
298 'comment_img_description' => [ 'JOIN',
299 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
300 ],
301 ],
302 ],
303 ];
304 }
305
306 private function assertComment( $expect, $actual, $from ) {
307 $this->assertSame( $expect['text'], $actual->text, "text $from" );
308 $this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
309 "message class $from" );
310 $this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
311 "message keys $from" );
312 $this->assertEquals( $expect['message']->text(), $actual->message->text(),
313 "message rendering $from" );
314 $this->assertEquals( $expect['data'], $actual->data, "data $from" );
315 }
316
317 /**
318 * @dataProvider provideInsertRoundTrip
319 * @param string $table
320 * @param string $key
321 * @param string $pk
322 * @param string $extraFields
323 * @param string|Message $comment
324 * @param array|null $data
325 * @param array $expect
326 */
327 public function testInsertRoundTrip( $table, $key, $pk, $extraFields, $comment, $data, $expect ) {
328 $expectOld = [
329 'text' => $expect['text'],
330 'message' => new RawMessage( '$1', [ $expect['text'] ] ),
331 'data' => null,
332 ];
333
334 $stages = [
335 MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
336 MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
337 MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
338 MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
339 ];
340
341 foreach ( $stages as $writeStage => $readRange ) {
342 if ( $key === 'ipb_reason' ) {
343 $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
344 }
345
346 $wstore = $this->makeStore( $writeStage, $key );
347 $usesTemp = $key === 'rev_comment';
348
349 if ( $usesTemp ) {
350 list( $fields, $callback ) = $wstore->insertWithTempTable( $this->db, $comment, $data );
351 } else {
352 $fields = $wstore->insert( $this->db, $comment, $data );
353 }
354
355 if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
356 $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
357 } else {
358 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
359 }
360 if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
361 $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
362 } else {
363 $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
364 }
365
366 $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
367 $id = $this->db->insertId();
368 if ( $usesTemp ) {
369 $callback( $id );
370 }
371
372 for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
373 $rstore = $this->makeStore( $readStage, $key );
374
375 $fieldRow = $this->db->selectRow(
376 $table,
377 $rstore->getFields(),
378 [ $pk => $id ],
379 __METHOD__
380 );
381
382 $queryInfo = $rstore->getJoin();
383 $joinRow = $this->db->selectRow(
384 [ $table ] + $queryInfo['tables'],
385 $queryInfo['fields'],
386 [ $pk => $id ],
387 __METHOD__,
388 [],
389 $queryInfo['joins']
390 );
391
392 $this->assertComment(
393 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
394 $rstore->getCommentLegacy( $this->db, $fieldRow ),
395 "w=$writeStage, r=$readStage, from getFields()"
396 );
397 $this->assertComment(
398 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
399 $rstore->getComment( $joinRow ),
400 "w=$writeStage, r=$readStage, from getJoin()"
401 );
402 }
403 }
404 }
405
406 public static function provideInsertRoundTrip() {
407 $msgComment = new Message( 'parentheses', [ 'message comment' ] );
408 $textCommentMsg = new RawMessage( '$1', [ 'text comment' ] );
409 $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
410 $ipbfields = [
411 'ipb_range_start' => '',
412 'ipb_range_end' => '',
413 ];
414 $revfields = [
415 'rev_page' => 42,
416 'rev_text_id' => 42,
417 'rev_len' => 0,
418 ];
419 $comStoreComment = new CommentStoreComment(
420 null, 'comment store comment', null, [ 'foo' => 'bar' ]
421 );
422
423 return [
424 'Simple table, text comment' => [
425 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', null, [
426 'text' => 'text comment',
427 'message' => $textCommentMsg,
428 'data' => null,
429 ]
430 ],
431 'Simple table, text comment with data' => [
432 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', [ 'message' => 42 ], [
433 'text' => 'text comment',
434 'message' => $textCommentMsg,
435 'data' => [ 'message' => 42 ],
436 ]
437 ],
438 'Simple table, message comment' => [
439 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, null, [
440 'text' => '(message comment)',
441 'message' => $msgComment,
442 'data' => null,
443 ]
444 ],
445 'Simple table, message comment with data' => [
446 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, [ 'message' => 42 ], [
447 'text' => '(message comment)',
448 'message' => $msgComment,
449 'data' => [ 'message' => 42 ],
450 ]
451 ],
452 'Simple table, nested message comment' => [
453 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $nestedMsgComment, null, [
454 'text' => '(Main Page)',
455 'message' => $nestedMsgComment,
456 'data' => null,
457 ]
458 ],
459 'Simple table, CommentStoreComment' => [
460 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
461 'text' => 'comment store comment',
462 'message' => $comStoreComment->message,
463 'data' => [ 'foo' => 'bar' ],
464 ]
465 ],
466
467 'Revision, text comment' => [
468 'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', null, [
469 'text' => 'text comment',
470 'message' => $textCommentMsg,
471 'data' => null,
472 ]
473 ],
474 'Revision, text comment with data' => [
475 'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', [ 'message' => 42 ], [
476 'text' => 'text comment',
477 'message' => $textCommentMsg,
478 'data' => [ 'message' => 42 ],
479 ]
480 ],
481 'Revision, message comment' => [
482 'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, null, [
483 'text' => '(message comment)',
484 'message' => $msgComment,
485 'data' => null,
486 ]
487 ],
488 'Revision, message comment with data' => [
489 'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, [ 'message' => 42 ], [
490 'text' => '(message comment)',
491 'message' => $msgComment,
492 'data' => [ 'message' => 42 ],
493 ]
494 ],
495 'Revision, nested message comment' => [
496 'revision', 'rev_comment', 'rev_id', $revfields, $nestedMsgComment, null, [
497 'text' => '(Main Page)',
498 'message' => $nestedMsgComment,
499 'data' => null,
500 ]
501 ],
502 'Revision, CommentStoreComment' => [
503 'revision', 'rev_comment', 'rev_id', $revfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
504 'text' => 'comment store comment',
505 'message' => $comStoreComment->message,
506 'data' => [ 'foo' => 'bar' ],
507 ]
508 ],
509 ];
510 }
511
512 public function testGetCommentErrors() {
513 MediaWiki\suppressWarnings();
514 $reset = new ScopedCallback( 'MediaWiki\restoreWarnings' );
515
516 $store = $this->makeStore( MIGRATION_OLD, 'dummy' );
517 $res = $store->getComment( [ 'dummy' => 'comment' ] );
518 $this->assertSame( '', $res->text );
519 $res = $store->getComment( [ 'dummy' => 'comment' ], true );
520 $this->assertSame( 'comment', $res->text );
521
522 $store = $this->makeStore( MIGRATION_NEW, 'dummy' );
523 try {
524 $store->getComment( [ 'dummy' => 'comment' ] );
525 $this->fail( 'Expected exception not thrown' );
526 } catch ( InvalidArgumentException $ex ) {
527 $this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
528 }
529 $res = $store->getComment( [ 'dummy' => 'comment' ], true );
530 $this->assertSame( 'comment', $res->text );
531 try {
532 $store->getComment( [ 'dummy_id' => 1 ] );
533 $this->fail( 'Expected exception not thrown' );
534 } catch ( InvalidArgumentException $ex ) {
535 $this->assertSame(
536 '$row does not contain fields needed for comment dummy and getComment(), '
537 . 'but does have fields for getCommentLegacy()',
538 $ex->getMessage()
539 );
540 }
541
542 $store = $this->makeStore( MIGRATION_NEW, 'rev_comment' );
543 try {
544 $store->getComment( [ 'rev_comment' => 'comment' ] );
545 $this->fail( 'Expected exception not thrown' );
546 } catch ( InvalidArgumentException $ex ) {
547 $this->assertSame(
548 '$row does not contain fields needed for comment rev_comment', $ex->getMessage()
549 );
550 }
551 $res = $store->getComment( [ 'rev_comment' => 'comment' ], true );
552 $this->assertSame( 'comment', $res->text );
553 try {
554 $store->getComment( [ 'rev_comment_pk' => 1 ] );
555 $this->fail( 'Expected exception not thrown' );
556 } catch ( InvalidArgumentException $ex ) {
557 $this->assertSame(
558 '$row does not contain fields needed for comment rev_comment and getComment(), '
559 . 'but does have fields for getCommentLegacy()',
560 $ex->getMessage()
561 );
562 }
563 }
564
565 public static function provideStages() {
566 return [
567 'MIGRATION_OLD' => [ MIGRATION_OLD ],
568 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
569 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
570 'MIGRATION_NEW' => [ MIGRATION_NEW ],
571 ];
572 }
573
574 /**
575 * @dataProvider provideStages
576 * @param int $stage
577 * @expectedException InvalidArgumentException
578 * @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
579 */
580 public function testInsertWrong( $stage ) {
581 $store = $this->makeStore( $stage, 'rev_comment' );
582 $store->insert( $this->db, 'foo' );
583 }
584
585 /**
586 * @dataProvider provideStages
587 * @param int $stage
588 * @expectedException InvalidArgumentException
589 * @expectedExceptionMessage Must use insert() for ipb_reason
590 */
591 public function testInsertWithTempTableWrong( $stage ) {
592 $store = $this->makeStore( $stage, 'ipb_reason' );
593 $store->insertWithTempTable( $this->db, 'foo' );
594 }
595
596 /**
597 * @dataProvider provideStages
598 * @param int $stage
599 */
600 public function testInsertWithTempTableDeprecated( $stage ) {
601 $wrap = TestingAccessWrapper::newFromClass( CommentStore::class );
602 $wrap->formerTempTables += [ 'ipb_reason' => '1.30' ];
603
604 $this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
605 $store = $this->makeStore( $stage, 'ipb_reason' );
606 list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'foo' );
607 $this->assertTrue( is_callable( $callback ) );
608 }
609
610 public function testConstructor() {
611 $this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
612 }
613
614 }