SECURITY: blacklist CSS var()
[lhc/web/wiklou.git] / tests / phpunit / includes / CommentStoreTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\Rdbms\IMaintainableDatabase;
5 use Wikimedia\ScopedCallback;
6 use Wikimedia\TestingAccessWrapper;
7
8 /**
9 * @group Database
10 * @covers CommentStore
11 * @covers CommentStoreComment
12 */
13 class CommentStoreTest extends MediaWikiLangTestCase {
14
15 protected $tablesUsed = [
16 'revision',
17 'revision_comment_temp',
18 'ipblocks',
19 'comment',
20 ];
21
22 protected function getSchemaOverrides( IMaintainableDatabase $db ) {
23 return [
24 'scripts' => [
25 __DIR__ . '/CommentStoreTest.sql',
26 ],
27 'drop' => [],
28 'create' => [ 'commentstore1', 'commentstore2', 'commentstore2_temp' ],
29 'alter' => [],
30 ];
31 }
32
33 /**
34 * Create a store for a particular stage
35 * @param int $stage
36 * @return CommentStore
37 */
38 protected function makeStore( $stage ) {
39 $store = new CommentStore( MediaWikiServices::getInstance()->getContentLanguage(), $stage );
40
41 TestingAccessWrapper::newFromObject( $store )->tempTables += [ 'cs2_comment' => [
42 'table' => 'commentstore2_temp',
43 'pk' => 'cs2t_id',
44 'field' => 'cs2t_comment_id',
45 'joinPK' => 'cs2_id',
46 'stage' => MIGRATION_OLD,
47 'deprecatedIn' => null,
48 ] ];
49
50 return $store;
51 }
52
53 /**
54 * Create a store for a particular stage and key (for testing deprecated behaviour)
55 * @param int $stage
56 * @param string $key
57 * @return CommentStore
58 */
59 protected function makeStoreWithKey( $stage, $key ) {
60 $this->hideDeprecated( 'CommentStore::newKey' );
61 $store = CommentStore::newKey( $key );
62 TestingAccessWrapper::newFromObject( $store )->stage = $stage;
63
64 TestingAccessWrapper::newFromObject( $store )->tempTables += [ 'cs2_comment' => [
65 'table' => 'commentstore2_temp',
66 'pk' => 'cs2t_id',
67 'field' => 'cs2t_comment_id',
68 'joinPK' => 'cs2_id',
69 'stage' => MIGRATION_OLD,
70 'deprecatedIn' => null,
71 ] ];
72
73 return $store;
74 }
75
76 /**
77 * @dataProvider provideGetFields
78 * @param int $stage
79 * @param string $key
80 * @param array $expect
81 */
82 public function testGetFields_withKeyConstruction( $stage, $key, $expect ) {
83 $store = $this->makeStoreWithKey( $stage, $key );
84 $result = $store->getFields();
85 $this->assertEquals( $expect, $result );
86 }
87
88 /**
89 * @dataProvider provideGetFields
90 * @param int $stage
91 * @param string $key
92 * @param array $expect
93 */
94 public function testGetFields( $stage, $key, $expect ) {
95 $store = $this->makeStore( $stage );
96 $result = $store->getFields( $key );
97 $this->assertEquals( $expect, $result );
98 }
99
100 public static function provideGetFields() {
101 return [
102 'Simple table, old' => [
103 MIGRATION_OLD, 'ipb_reason',
104 [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
105 ],
106 'Simple table, write-both' => [
107 MIGRATION_WRITE_BOTH, 'ipb_reason',
108 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
109 ],
110 'Simple table, write-new' => [
111 MIGRATION_WRITE_NEW, 'ipb_reason',
112 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
113 ],
114 'Simple table, new' => [
115 MIGRATION_NEW, 'ipb_reason',
116 [ 'ipb_reason_id' => 'ipb_reason_id' ],
117 ],
118
119 'Revision, old' => [
120 MIGRATION_OLD, 'rev_comment',
121 [
122 'rev_comment_text' => 'rev_comment',
123 'rev_comment_data' => 'NULL',
124 'rev_comment_cid' => 'NULL',
125 ],
126 ],
127 'Revision, write-both' => [
128 MIGRATION_WRITE_BOTH, 'rev_comment',
129 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
130 ],
131 'Revision, write-new' => [
132 MIGRATION_WRITE_NEW, 'rev_comment',
133 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
134 ],
135 'Revision, new' => [
136 MIGRATION_NEW, 'rev_comment',
137 [ 'rev_comment_pk' => 'rev_id' ],
138 ],
139
140 'Image, old' => [
141 MIGRATION_OLD, 'img_description',
142 [
143 'img_description_text' => 'img_description',
144 'img_description_data' => 'NULL',
145 'img_description_cid' => 'NULL',
146 ],
147 ],
148 'Image, write-both' => [
149 MIGRATION_WRITE_BOTH, 'img_description',
150 [
151 'img_description_old' => 'img_description',
152 'img_description_id' => 'img_description_id'
153 ],
154 ],
155 'Image, write-new' => [
156 MIGRATION_WRITE_NEW, 'img_description',
157 [
158 'img_description_old' => 'img_description',
159 'img_description_id' => 'img_description_id'
160 ],
161 ],
162 'Image, new' => [
163 MIGRATION_NEW, 'img_description',
164 [
165 'img_description_id' => 'img_description_id'
166 ],
167 ],
168 ];
169 }
170
171 /**
172 * @dataProvider provideGetJoin
173 * @param int $stage
174 * @param string $key
175 * @param array $expect
176 */
177 public function testGetJoin_withKeyConstruction( $stage, $key, $expect ) {
178 $store = $this->makeStoreWithKey( $stage, $key );
179 $result = $store->getJoin();
180 $this->assertEquals( $expect, $result );
181 }
182
183 /**
184 * @dataProvider provideGetJoin
185 * @param int $stage
186 * @param string $key
187 * @param array $expect
188 */
189 public function testGetJoin( $stage, $key, $expect ) {
190 $store = $this->makeStore( $stage );
191 $result = $store->getJoin( $key );
192 $this->assertEquals( $expect, $result );
193 }
194
195 public static function provideGetJoin() {
196 return [
197 'Simple table, old' => [
198 MIGRATION_OLD, 'ipb_reason', [
199 'tables' => [],
200 'fields' => [
201 'ipb_reason_text' => 'ipb_reason',
202 'ipb_reason_data' => 'NULL',
203 'ipb_reason_cid' => 'NULL',
204 ],
205 'joins' => [],
206 ],
207 ],
208 'Simple table, write-both' => [
209 MIGRATION_WRITE_BOTH, 'ipb_reason', [
210 'tables' => [ 'comment_ipb_reason' => 'comment' ],
211 'fields' => [
212 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
213 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
214 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
215 ],
216 'joins' => [
217 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
218 ],
219 ],
220 ],
221 'Simple table, write-new' => [
222 MIGRATION_WRITE_NEW, 'ipb_reason', [
223 'tables' => [ 'comment_ipb_reason' => 'comment' ],
224 'fields' => [
225 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
226 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
227 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
228 ],
229 'joins' => [
230 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
231 ],
232 ],
233 ],
234 'Simple table, new' => [
235 MIGRATION_NEW, 'ipb_reason', [
236 'tables' => [ 'comment_ipb_reason' => 'comment' ],
237 'fields' => [
238 'ipb_reason_text' => 'comment_ipb_reason.comment_text',
239 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
240 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
241 ],
242 'joins' => [
243 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
244 ],
245 ],
246 ],
247
248 'Revision, old' => [
249 MIGRATION_OLD, 'rev_comment', [
250 'tables' => [],
251 'fields' => [
252 'rev_comment_text' => 'rev_comment',
253 'rev_comment_data' => 'NULL',
254 'rev_comment_cid' => 'NULL',
255 ],
256 'joins' => [],
257 ],
258 ],
259 'Revision, write-both' => [
260 MIGRATION_WRITE_BOTH, 'rev_comment', [
261 'tables' => [
262 'temp_rev_comment' => 'revision_comment_temp',
263 'comment_rev_comment' => 'comment',
264 ],
265 'fields' => [
266 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
267 'rev_comment_data' => 'comment_rev_comment.comment_data',
268 'rev_comment_cid' => 'comment_rev_comment.comment_id',
269 ],
270 'joins' => [
271 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
272 'comment_rev_comment' => [ 'LEFT JOIN',
273 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
274 ],
275 ],
276 ],
277 'Revision, write-new' => [
278 MIGRATION_WRITE_NEW, 'rev_comment', [
279 'tables' => [
280 'temp_rev_comment' => 'revision_comment_temp',
281 'comment_rev_comment' => 'comment',
282 ],
283 'fields' => [
284 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
285 'rev_comment_data' => 'comment_rev_comment.comment_data',
286 'rev_comment_cid' => 'comment_rev_comment.comment_id',
287 ],
288 'joins' => [
289 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
290 'comment_rev_comment' => [ 'LEFT JOIN',
291 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
292 ],
293 ],
294 ],
295 'Revision, new' => [
296 MIGRATION_NEW, 'rev_comment', [
297 'tables' => [
298 'temp_rev_comment' => 'revision_comment_temp',
299 'comment_rev_comment' => 'comment',
300 ],
301 'fields' => [
302 'rev_comment_text' => 'comment_rev_comment.comment_text',
303 'rev_comment_data' => 'comment_rev_comment.comment_data',
304 'rev_comment_cid' => 'comment_rev_comment.comment_id',
305 ],
306 'joins' => [
307 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
308 'comment_rev_comment' => [ 'JOIN',
309 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
310 ],
311 ],
312 ],
313
314 'Image, old' => [
315 MIGRATION_OLD, 'img_description', [
316 'tables' => [],
317 'fields' => [
318 'img_description_text' => 'img_description',
319 'img_description_data' => 'NULL',
320 'img_description_cid' => 'NULL',
321 ],
322 'joins' => [],
323 ],
324 ],
325 'Image, write-both' => [
326 MIGRATION_WRITE_BOTH, 'img_description', [
327 'tables' => [
328 'comment_img_description' => 'comment',
329 ],
330 'fields' => [
331 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
332 'img_description_data' => 'comment_img_description.comment_data',
333 'img_description_cid' => 'comment_img_description.comment_id',
334 ],
335 'joins' => [
336 'comment_img_description' => [ 'LEFT JOIN',
337 'comment_img_description.comment_id = img_description_id',
338 ],
339 ],
340 ],
341 ],
342 'Image, write-new' => [
343 MIGRATION_WRITE_NEW, 'img_description', [
344 'tables' => [
345 'comment_img_description' => 'comment',
346 ],
347 'fields' => [
348 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
349 'img_description_data' => 'comment_img_description.comment_data',
350 'img_description_cid' => 'comment_img_description.comment_id',
351 ],
352 'joins' => [
353 'comment_img_description' => [ 'LEFT JOIN',
354 'comment_img_description.comment_id = img_description_id',
355 ],
356 ],
357 ],
358 ],
359 'Image, new' => [
360 MIGRATION_NEW, 'img_description', [
361 'tables' => [
362 'comment_img_description' => 'comment',
363 ],
364 'fields' => [
365 'img_description_text' => 'comment_img_description.comment_text',
366 'img_description_data' => 'comment_img_description.comment_data',
367 'img_description_cid' => 'comment_img_description.comment_id',
368 ],
369 'joins' => [
370 'comment_img_description' => [ 'JOIN',
371 'comment_img_description.comment_id = img_description_id',
372 ],
373 ],
374 ],
375 ],
376 ];
377 }
378
379 private function assertComment( $expect, $actual, $from ) {
380 $this->assertSame( $expect['text'], $actual->text, "text $from" );
381 $this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
382 "message class $from" );
383 $this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
384 "message keys $from" );
385 $this->assertEquals( $expect['message']->text(), $actual->message->text(),
386 "message rendering $from" );
387 $this->assertEquals( $expect['text'], $actual->message->text(),
388 "message rendering and text $from" );
389 $this->assertEquals( $expect['data'], $actual->data, "data $from" );
390 }
391
392 /**
393 * @dataProvider provideInsertRoundTrip
394 * @param string $table
395 * @param string $key
396 * @param string $pk
397 * @param string|Message $comment
398 * @param array|null $data
399 * @param array $expect
400 */
401 public function testInsertRoundTrip( $table, $key, $pk, $comment, $data, $expect ) {
402 static $id = 1;
403
404 $expectOld = [
405 'text' => $expect['text'],
406 'message' => new RawMessage( '$1', [ Message::plaintextParam( $expect['text'] ) ] ),
407 'data' => null,
408 ];
409
410 $stages = [
411 MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
412 MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
413 MIGRATION_NEW ],
414 MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
415 MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
416 ];
417
418 foreach ( $stages as $writeStage => $possibleReadStages ) {
419 $wstore = $this->makeStore( $writeStage );
420 $usesTemp = $key === 'cs2_comment';
421
422 if ( $usesTemp ) {
423 list( $fields, $callback ) = $wstore->insertWithTempTable(
424 $this->db, $key, $comment, $data
425 );
426 } else {
427 $fields = $wstore->insert( $this->db, $key, $comment, $data );
428 }
429
430 if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
431 $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
432 } else {
433 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
434 }
435 if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
436 $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
437 } else {
438 $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
439 }
440
441 $this->db->insert( $table, [ $pk => ++$id ] + $fields, __METHOD__ );
442 if ( $usesTemp ) {
443 $callback( $id );
444 }
445
446 foreach ( $possibleReadStages as $readStage ) {
447 $rstore = $this->makeStore( $readStage );
448
449 $fieldRow = $this->db->selectRow(
450 $table,
451 $rstore->getFields( $key ),
452 [ $pk => $id ],
453 __METHOD__
454 );
455
456 $queryInfo = $rstore->getJoin( $key );
457 $joinRow = $this->db->selectRow(
458 [ $table ] + $queryInfo['tables'],
459 $queryInfo['fields'],
460 [ $pk => $id ],
461 __METHOD__,
462 [],
463 $queryInfo['joins']
464 );
465
466 $this->assertComment(
467 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
468 $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
469 "w=$writeStage, r=$readStage, from getFields()"
470 );
471 $this->assertComment(
472 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
473 $rstore->getComment( $key, $joinRow ),
474 "w=$writeStage, r=$readStage, from getJoin()"
475 );
476 }
477 }
478 }
479
480 /**
481 * @dataProvider provideInsertRoundTrip
482 * @param string $table
483 * @param string $key
484 * @param string $pk
485 * @param string|Message $comment
486 * @param array|null $data
487 * @param array $expect
488 */
489 public function testInsertRoundTrip_withKeyConstruction(
490 $table, $key, $pk, $comment, $data, $expect
491 ) {
492 static $id = 1000;
493
494 $expectOld = [
495 'text' => $expect['text'],
496 'message' => new RawMessage( '$1', [ Message::plaintextParam( $expect['text'] ) ] ),
497 'data' => null,
498 ];
499
500 $stages = [
501 MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
502 MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
503 MIGRATION_NEW ],
504 MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
505 MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
506 ];
507
508 foreach ( $stages as $writeStage => $possibleReadStages ) {
509 $wstore = $this->makeStoreWithKey( $writeStage, $key );
510 $usesTemp = $key === 'cs2_comment';
511
512 if ( $usesTemp ) {
513 list( $fields, $callback ) = $wstore->insertWithTempTable(
514 $this->db, $comment, $data
515 );
516 } else {
517 $fields = $wstore->insert( $this->db, $comment, $data );
518 }
519
520 if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
521 $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
522 } else {
523 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
524 }
525 if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
526 $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
527 } else {
528 $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
529 }
530
531 $this->db->insert( $table, [ $pk => ++$id ] + $fields, __METHOD__ );
532 if ( $usesTemp ) {
533 $callback( $id );
534 }
535
536 foreach ( $possibleReadStages as $readStage ) {
537 $rstore = $this->makeStoreWithKey( $readStage, $key );
538
539 $fieldRow = $this->db->selectRow(
540 $table,
541 $rstore->getFields(),
542 [ $pk => $id ],
543 __METHOD__
544 );
545
546 $queryInfo = $rstore->getJoin();
547 $joinRow = $this->db->selectRow(
548 [ $table ] + $queryInfo['tables'],
549 $queryInfo['fields'],
550 [ $pk => $id ],
551 __METHOD__,
552 [],
553 $queryInfo['joins']
554 );
555
556 $this->assertComment(
557 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
558 $rstore->getCommentLegacy( $this->db, $fieldRow ),
559 "w=$writeStage, r=$readStage, from getFields()"
560 );
561 $this->assertComment(
562 $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
563 $rstore->getComment( $joinRow ),
564 "w=$writeStage, r=$readStage, from getJoin()"
565 );
566 }
567 }
568 }
569
570 public static function provideInsertRoundTrip() {
571 $db = wfGetDB( DB_REPLICA ); // for timestamps
572
573 $msgComment = new Message( 'parentheses', [ 'message comment' ] );
574 $textCommentMsg = new RawMessage( '$1', [ Message::plaintextParam( '{{text}} comment' ) ] );
575 $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
576 $comStoreComment = new CommentStoreComment(
577 null, 'comment store comment', null, [ 'foo' => 'bar' ]
578 );
579
580 return [
581 'Simple table, text comment' => [
582 'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', null, [
583 'text' => '{{text}} comment',
584 'message' => $textCommentMsg,
585 'data' => null,
586 ]
587 ],
588 'Simple table, text comment with data' => [
589 'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', [ 'message' => 42 ], [
590 'text' => '{{text}} comment',
591 'message' => $textCommentMsg,
592 'data' => [ 'message' => 42 ],
593 ]
594 ],
595 'Simple table, message comment' => [
596 'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, null, [
597 'text' => '(message comment)',
598 'message' => $msgComment,
599 'data' => null,
600 ]
601 ],
602 'Simple table, message comment with data' => [
603 'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, [ 'message' => 42 ], [
604 'text' => '(message comment)',
605 'message' => $msgComment,
606 'data' => [ 'message' => 42 ],
607 ]
608 ],
609 'Simple table, nested message comment' => [
610 'commentstore1', 'cs1_comment', 'cs1_id', $nestedMsgComment, null, [
611 'text' => '(Main Page)',
612 'message' => $nestedMsgComment,
613 'data' => null,
614 ]
615 ],
616 'Simple table, CommentStoreComment' => [
617 'commentstore1', 'cs1_comment', 'cs1_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
618 'text' => 'comment store comment',
619 'message' => $comStoreComment->message,
620 'data' => [ 'foo' => 'bar' ],
621 ]
622 ],
623
624 'Revision, text comment' => [
625 'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', null, [
626 'text' => '{{text}} comment',
627 'message' => $textCommentMsg,
628 'data' => null,
629 ]
630 ],
631 'Revision, text comment with data' => [
632 'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', [ 'message' => 42 ], [
633 'text' => '{{text}} comment',
634 'message' => $textCommentMsg,
635 'data' => [ 'message' => 42 ],
636 ]
637 ],
638 'Revision, message comment' => [
639 'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, null, [
640 'text' => '(message comment)',
641 'message' => $msgComment,
642 'data' => null,
643 ]
644 ],
645 'Revision, message comment with data' => [
646 'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, [ 'message' => 42 ], [
647 'text' => '(message comment)',
648 'message' => $msgComment,
649 'data' => [ 'message' => 42 ],
650 ]
651 ],
652 'Revision, nested message comment' => [
653 'commentstore2', 'cs2_comment', 'cs2_id', $nestedMsgComment, null, [
654 'text' => '(Main Page)',
655 'message' => $nestedMsgComment,
656 'data' => null,
657 ]
658 ],
659 'Revision, CommentStoreComment' => [
660 'commentstore2', 'cs2_comment', 'cs2_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
661 'text' => 'comment store comment',
662 'message' => $comStoreComment->message,
663 'data' => [ 'foo' => 'bar' ],
664 ]
665 ],
666 ];
667 }
668
669 public function testGetCommentErrors() {
670 Wikimedia\suppressWarnings();
671 $reset = new ScopedCallback( 'Wikimedia\restoreWarnings' );
672
673 $store = $this->makeStore( MIGRATION_OLD );
674 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
675 $this->assertSame( '', $res->text );
676 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
677 $this->assertSame( 'comment', $res->text );
678
679 $store = $this->makeStore( MIGRATION_NEW );
680 try {
681 $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
682 $this->fail( 'Expected exception not thrown' );
683 } catch ( InvalidArgumentException $ex ) {
684 $this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
685 }
686 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
687 $this->assertSame( 'comment', $res->text );
688 try {
689 $store->getComment( 'dummy', [ 'dummy_id' => 1 ] );
690 $this->fail( 'Expected exception not thrown' );
691 } catch ( InvalidArgumentException $ex ) {
692 $this->assertSame(
693 '$row does not contain fields needed for comment dummy and getComment(), '
694 . 'but does have fields for getCommentLegacy()',
695 $ex->getMessage()
696 );
697 }
698
699 $store = $this->makeStore( MIGRATION_NEW );
700 try {
701 $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ] );
702 $this->fail( 'Expected exception not thrown' );
703 } catch ( InvalidArgumentException $ex ) {
704 $this->assertSame(
705 '$row does not contain fields needed for comment rev_comment', $ex->getMessage()
706 );
707 }
708 $res = $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ], true );
709 $this->assertSame( 'comment', $res->text );
710 try {
711 $store->getComment( 'rev_comment', [ 'rev_comment_pk' => 1 ] );
712 $this->fail( 'Expected exception not thrown' );
713 } catch ( InvalidArgumentException $ex ) {
714 $this->assertSame(
715 '$row does not contain fields needed for comment rev_comment and getComment(), '
716 . 'but does have fields for getCommentLegacy()',
717 $ex->getMessage()
718 );
719 }
720 }
721
722 public static function provideStages() {
723 return [
724 'MIGRATION_OLD' => [ MIGRATION_OLD ],
725 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
726 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
727 'MIGRATION_NEW' => [ MIGRATION_NEW ],
728 ];
729 }
730
731 /**
732 * @dataProvider provideStages
733 * @param int $stage
734 * @expectedException InvalidArgumentException
735 * @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
736 */
737 public function testInsertWrong( $stage ) {
738 $store = $this->makeStore( $stage );
739 $store->insert( $this->db, 'rev_comment', 'foo' );
740 }
741
742 /**
743 * @dataProvider provideStages
744 * @param int $stage
745 * @expectedException InvalidArgumentException
746 * @expectedExceptionMessage Must use insert() for ipb_reason
747 */
748 public function testInsertWithTempTableWrong( $stage ) {
749 $store = $this->makeStore( $stage );
750 $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
751 }
752
753 /**
754 * @dataProvider provideStages
755 * @param int $stage
756 */
757 public function testInsertWithTempTableDeprecated( $stage ) {
758 $store = $this->makeStore( $stage );
759 $wrap = TestingAccessWrapper::newFromObject( $store );
760 $wrap->tempTables += [ 'ipb_reason' => [
761 'stage' => MIGRATION_NEW,
762 'deprecatedIn' => '1.30',
763 ] ];
764
765 $this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
766 list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
767 $this->assertTrue( is_callable( $callback ) );
768 }
769
770 public function testInsertTruncation() {
771 $comment = str_repeat( '💣', 16400 );
772 $truncated1 = str_repeat( '💣', 63 ) . '...';
773 $truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
774
775 $store = $this->makeStore( MIGRATION_WRITE_BOTH );
776 $fields = $store->insert( $this->db, 'ipb_reason', $comment );
777 $this->assertSame( $truncated1, $fields['ipb_reason'] );
778 $stored = $this->db->selectField(
779 'comment', 'comment_text', [ 'comment_id' => $fields['ipb_reason_id'] ], __METHOD__
780 );
781 $this->assertSame( $truncated2, $stored );
782 }
783
784 /**
785 * @expectedException OverflowException
786 * @expectedExceptionMessage Comment data is too long (65611 bytes, maximum is 65535)
787 */
788 public function testInsertTooMuchData() {
789 $store = $this->makeStore( MIGRATION_WRITE_BOTH );
790 $store->insert( $this->db, 'ipb_reason', 'foo', [
791 'long' => str_repeat( '💣', 16400 )
792 ] );
793 }
794
795 public function testGetStore() {
796 $this->assertInstanceOf( CommentStore::class, CommentStore::getStore() );
797 }
798
799 public function testNewKey() {
800 $this->hideDeprecated( 'CommentStore::newKey' );
801 $this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
802 }
803
804 }