Merge "filebackend: avoid use of wfWikiId() in FileBackendGroup"
[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 provideConstructor
78 * @param int $stage
79 * @param string|null $exceptionMsg
80 */
81 public function testConstructor( $stage, $exceptionMsg ) {
82 try {
83 $m = new CommentStore( Language::factory( 'qqx' ), $stage );
84 if ( $exceptionMsg !== null ) {
85 $this->fail( 'Expected exception not thrown' );
86 }
87 $this->assertInstanceOf( CommentStore::class, $m );
88 } catch ( InvalidArgumentException $ex ) {
89 $this->assertSame( $exceptionMsg, $ex->getMessage() );
90 }
91 }
92
93 public static function provideConstructor() {
94 return [
95 [ 0, '$stage must include a write mode' ],
96 [ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
97 [ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
98 [ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
99
100 [ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
101 [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
102 [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW, null ],
103 [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, null ],
104
105 [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
106 [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD, null ],
107 [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
108 [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, null ],
109
110 [ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
111 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
112 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
113 [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, null ],
114 ];
115 }
116
117 /**
118 * @dataProvider provideGetFields
119 * @param int $stage
120 * @param string $key
121 * @param array $expect
122 */
123 public function testGetFields_withKeyConstruction( $stage, $key, $expect ) {
124 $store = $this->makeStoreWithKey( $stage, $key );
125 $result = $store->getFields();
126 $this->assertEquals( $expect, $result );
127 }
128
129 /**
130 * @dataProvider provideGetFields
131 * @param int $stage
132 * @param string $key
133 * @param array $expect
134 */
135 public function testGetFields( $stage, $key, $expect ) {
136 $store = $this->makeStore( $stage );
137 $result = $store->getFields( $key );
138 $this->assertEquals( $expect, $result );
139 }
140
141 public static function provideGetFields() {
142 return [
143 'Simple table, old' => [
144 MIGRATION_OLD, 'ipb_reason',
145 [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
146 ],
147 'Simple table, write-both' => [
148 MIGRATION_WRITE_BOTH, 'ipb_reason',
149 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
150 ],
151 'Simple table, write-new' => [
152 MIGRATION_WRITE_NEW, 'ipb_reason',
153 [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
154 ],
155 'Simple table, new' => [
156 MIGRATION_NEW, 'ipb_reason',
157 [ 'ipb_reason_id' => 'ipb_reason_id' ],
158 ],
159 'Simple table, write-both/read-old' => [
160 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason',
161 [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
162 ],
163 'Simple table, write-both/read-new' => [
164 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason',
165 [ 'ipb_reason_id' => 'ipb_reason_id' ],
166 ],
167
168 'Revision, old' => [
169 MIGRATION_OLD, 'rev_comment',
170 [
171 'rev_comment_text' => 'rev_comment',
172 'rev_comment_data' => 'NULL',
173 'rev_comment_cid' => 'NULL',
174 ],
175 ],
176 'Revision, write-both' => [
177 MIGRATION_WRITE_BOTH, 'rev_comment',
178 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
179 ],
180 'Revision, write-new' => [
181 MIGRATION_WRITE_NEW, 'rev_comment',
182 [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
183 ],
184 'Revision, new' => [
185 MIGRATION_NEW, 'rev_comment',
186 [ 'rev_comment_pk' => 'rev_id' ],
187 ],
188 'Revision, write-both/read-old' => [
189 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment',
190 [
191 'rev_comment_text' => 'rev_comment',
192 'rev_comment_data' => 'NULL',
193 'rev_comment_cid' => 'NULL',
194 ],
195 ],
196 'Revision, write-both/read-new' => [
197 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment',
198 [ 'rev_comment_pk' => 'rev_id' ],
199 ],
200
201 'Image, old' => [
202 MIGRATION_OLD, 'img_description',
203 [
204 'img_description_text' => 'img_description',
205 'img_description_data' => 'NULL',
206 'img_description_cid' => 'NULL',
207 ],
208 ],
209 'Image, write-both' => [
210 MIGRATION_WRITE_BOTH, 'img_description',
211 [
212 'img_description_old' => 'img_description',
213 'img_description_id' => 'img_description_id'
214 ],
215 ],
216 'Image, write-new' => [
217 MIGRATION_WRITE_NEW, 'img_description',
218 [
219 'img_description_old' => 'img_description',
220 'img_description_id' => 'img_description_id'
221 ],
222 ],
223 'Image, new' => [
224 MIGRATION_NEW, 'img_description',
225 [
226 'img_description_id' => 'img_description_id'
227 ],
228 ],
229 'Image, write-both/read-old' => [
230 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description',
231 [
232 'img_description_text' => 'img_description',
233 'img_description_data' => 'NULL',
234 'img_description_cid' => 'NULL',
235 ],
236 ],
237 'Image, write-both/read-new' => [
238 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description',
239 [
240 'img_description_id' => 'img_description_id'
241 ],
242 ],
243 ];
244 }
245
246 /**
247 * @dataProvider provideGetJoin
248 * @param int $stage
249 * @param string $key
250 * @param array $expect
251 */
252 public function testGetJoin_withKeyConstruction( $stage, $key, $expect ) {
253 $store = $this->makeStoreWithKey( $stage, $key );
254 $result = $store->getJoin();
255 $this->assertEquals( $expect, $result );
256 }
257
258 /**
259 * @dataProvider provideGetJoin
260 * @param int $stage
261 * @param string $key
262 * @param array $expect
263 */
264 public function testGetJoin( $stage, $key, $expect ) {
265 $store = $this->makeStore( $stage );
266 $result = $store->getJoin( $key );
267 $this->assertEquals( $expect, $result );
268 }
269
270 public static function provideGetJoin() {
271 return [
272 'Simple table, old' => [
273 MIGRATION_OLD, 'ipb_reason', [
274 'tables' => [],
275 'fields' => [
276 'ipb_reason_text' => 'ipb_reason',
277 'ipb_reason_data' => 'NULL',
278 'ipb_reason_cid' => 'NULL',
279 ],
280 'joins' => [],
281 ],
282 ],
283 'Simple table, write-both' => [
284 MIGRATION_WRITE_BOTH, 'ipb_reason', [
285 'tables' => [ 'comment_ipb_reason' => 'comment' ],
286 'fields' => [
287 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
288 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
289 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
290 ],
291 'joins' => [
292 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
293 ],
294 ],
295 ],
296 'Simple table, write-new' => [
297 MIGRATION_WRITE_NEW, 'ipb_reason', [
298 'tables' => [ 'comment_ipb_reason' => 'comment' ],
299 'fields' => [
300 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
301 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
302 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
303 ],
304 'joins' => [
305 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
306 ],
307 ],
308 ],
309 'Simple table, new' => [
310 MIGRATION_NEW, 'ipb_reason', [
311 'tables' => [ 'comment_ipb_reason' => 'comment' ],
312 'fields' => [
313 'ipb_reason_text' => 'comment_ipb_reason.comment_text',
314 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
315 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
316 ],
317 'joins' => [
318 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
319 ],
320 ],
321 ],
322 'Simple table, write-both/read-old' => [
323 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', [
324 'tables' => [],
325 'fields' => [
326 'ipb_reason_text' => 'ipb_reason',
327 'ipb_reason_data' => 'NULL',
328 'ipb_reason_cid' => 'NULL',
329 ],
330 'joins' => [],
331 ],
332 ],
333 'Simple table, write-both/read-new' => [
334 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', [
335 'tables' => [ 'comment_ipb_reason' => 'comment' ],
336 'fields' => [
337 'ipb_reason_text' => 'comment_ipb_reason.comment_text',
338 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
339 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
340 ],
341 'joins' => [
342 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
343 ],
344 ],
345 ],
346
347 'Revision, old' => [
348 MIGRATION_OLD, 'rev_comment', [
349 'tables' => [],
350 'fields' => [
351 'rev_comment_text' => 'rev_comment',
352 'rev_comment_data' => 'NULL',
353 'rev_comment_cid' => 'NULL',
354 ],
355 'joins' => [],
356 ],
357 ],
358 'Revision, write-both' => [
359 MIGRATION_WRITE_BOTH, 'rev_comment', [
360 'tables' => [
361 'temp_rev_comment' => 'revision_comment_temp',
362 'comment_rev_comment' => 'comment',
363 ],
364 'fields' => [
365 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
366 'rev_comment_data' => 'comment_rev_comment.comment_data',
367 'rev_comment_cid' => 'comment_rev_comment.comment_id',
368 ],
369 'joins' => [
370 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
371 'comment_rev_comment' => [ 'LEFT JOIN',
372 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
373 ],
374 ],
375 ],
376 'Revision, write-new' => [
377 MIGRATION_WRITE_NEW, 'rev_comment', [
378 'tables' => [
379 'temp_rev_comment' => 'revision_comment_temp',
380 'comment_rev_comment' => 'comment',
381 ],
382 'fields' => [
383 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
384 'rev_comment_data' => 'comment_rev_comment.comment_data',
385 'rev_comment_cid' => 'comment_rev_comment.comment_id',
386 ],
387 'joins' => [
388 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
389 'comment_rev_comment' => [ 'LEFT JOIN',
390 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
391 ],
392 ],
393 ],
394 'Revision, new' => [
395 MIGRATION_NEW, 'rev_comment', [
396 'tables' => [
397 'temp_rev_comment' => 'revision_comment_temp',
398 'comment_rev_comment' => 'comment',
399 ],
400 'fields' => [
401 'rev_comment_text' => 'comment_rev_comment.comment_text',
402 'rev_comment_data' => 'comment_rev_comment.comment_data',
403 'rev_comment_cid' => 'comment_rev_comment.comment_id',
404 ],
405 'joins' => [
406 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
407 'comment_rev_comment' => [ 'JOIN',
408 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
409 ],
410 ],
411 ],
412 'Revision, write-both/read-old' => [
413 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', [
414 'tables' => [],
415 'fields' => [
416 'rev_comment_text' => 'rev_comment',
417 'rev_comment_data' => 'NULL',
418 'rev_comment_cid' => 'NULL',
419 ],
420 'joins' => [],
421 ],
422 ],
423 'Revision, write-both/read-new' => [
424 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', [
425 'tables' => [
426 'temp_rev_comment' => 'revision_comment_temp',
427 'comment_rev_comment' => 'comment',
428 ],
429 'fields' => [
430 'rev_comment_text' => 'comment_rev_comment.comment_text',
431 'rev_comment_data' => 'comment_rev_comment.comment_data',
432 'rev_comment_cid' => 'comment_rev_comment.comment_id',
433 ],
434 'joins' => [
435 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
436 'comment_rev_comment' => [ 'JOIN',
437 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
438 ],
439 ],
440 ],
441
442 'Image, old' => [
443 MIGRATION_OLD, 'img_description', [
444 'tables' => [],
445 'fields' => [
446 'img_description_text' => 'img_description',
447 'img_description_data' => 'NULL',
448 'img_description_cid' => 'NULL',
449 ],
450 'joins' => [],
451 ],
452 ],
453 'Image, write-both' => [
454 MIGRATION_WRITE_BOTH, 'img_description', [
455 'tables' => [
456 'comment_img_description' => 'comment',
457 ],
458 'fields' => [
459 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
460 'img_description_data' => 'comment_img_description.comment_data',
461 'img_description_cid' => 'comment_img_description.comment_id',
462 ],
463 'joins' => [
464 'comment_img_description' => [ 'LEFT JOIN',
465 'comment_img_description.comment_id = img_description_id',
466 ],
467 ],
468 ],
469 ],
470 'Image, write-new' => [
471 MIGRATION_WRITE_NEW, 'img_description', [
472 'tables' => [
473 'comment_img_description' => 'comment',
474 ],
475 'fields' => [
476 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
477 'img_description_data' => 'comment_img_description.comment_data',
478 'img_description_cid' => 'comment_img_description.comment_id',
479 ],
480 'joins' => [
481 'comment_img_description' => [ 'LEFT JOIN',
482 'comment_img_description.comment_id = img_description_id',
483 ],
484 ],
485 ],
486 ],
487 'Image, new' => [
488 MIGRATION_NEW, 'img_description', [
489 'tables' => [
490 'comment_img_description' => 'comment',
491 ],
492 'fields' => [
493 'img_description_text' => 'comment_img_description.comment_text',
494 'img_description_data' => 'comment_img_description.comment_data',
495 'img_description_cid' => 'comment_img_description.comment_id',
496 ],
497 'joins' => [
498 'comment_img_description' => [ 'JOIN',
499 'comment_img_description.comment_id = img_description_id',
500 ],
501 ],
502 ],
503 ],
504 'Image, write-both/read-old' => [
505 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', [
506 'tables' => [],
507 'fields' => [
508 'img_description_text' => 'img_description',
509 'img_description_data' => 'NULL',
510 'img_description_cid' => 'NULL',
511 ],
512 'joins' => [],
513 ],
514 ],
515 'Image, write-both/read-new' => [
516 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', [
517 'tables' => [
518 'comment_img_description' => 'comment',
519 ],
520 'fields' => [
521 'img_description_text' => 'comment_img_description.comment_text',
522 'img_description_data' => 'comment_img_description.comment_data',
523 'img_description_cid' => 'comment_img_description.comment_id',
524 ],
525 'joins' => [
526 'comment_img_description' => [ 'JOIN',
527 'comment_img_description.comment_id = img_description_id',
528 ],
529 ],
530 ],
531 ],
532 ];
533 }
534
535 private function assertComment( $expect, $actual, $from ) {
536 $this->assertSame( $expect['text'], $actual->text, "text $from" );
537 $this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
538 "message class $from" );
539 $this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
540 "message keys $from" );
541 $this->assertEquals( $expect['message']->text(), $actual->message->text(),
542 "message rendering $from" );
543 $this->assertEquals( $expect['text'], $actual->message->text(),
544 "message rendering and text $from" );
545 $this->assertEquals( $expect['data'], $actual->data, "data $from" );
546 }
547
548 /**
549 * @dataProvider provideInsertRoundTrip
550 * @param string $table
551 * @param string $key
552 * @param string $pk
553 * @param string|Message $comment
554 * @param array|null $data
555 * @param array $expect
556 */
557 public function testInsertRoundTrip( $table, $key, $pk, $comment, $data, $expect ) {
558 static $id = 1;
559
560 $expectOld = [
561 'text' => $expect['text'],
562 'message' => new RawMessage( '$1', [ Message::plaintextParam( $expect['text'] ) ] ),
563 'data' => null,
564 ];
565
566 $stages = [
567 MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
568 MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
569 MIGRATION_NEW ],
570 MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
571 MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
572
573 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
574 MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
575 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
576 ],
577 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
578 MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
579 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
580 ],
581 ];
582
583 foreach ( $stages as $writeStage => $possibleReadStages ) {
584 $wstore = $this->makeStore( $writeStage );
585 $usesTemp = $key === 'cs2_comment';
586
587 if ( $usesTemp ) {
588 list( $fields, $callback ) = $wstore->insertWithTempTable(
589 $this->db, $key, $comment, $data
590 );
591 } else {
592 $fields = $wstore->insert( $this->db, $key, $comment, $data );
593 }
594
595 if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
596 $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
597 } else {
598 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
599 }
600 if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
601 $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
602 } else {
603 $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
604 }
605
606 $this->db->insert( $table, [ $pk => ++$id ] + $fields, __METHOD__ );
607 if ( $usesTemp ) {
608 $callback( $id );
609 }
610
611 foreach ( $possibleReadStages as $readStage ) {
612 $rstore = $this->makeStore( $readStage );
613
614 $fieldRow = $this->db->selectRow(
615 $table,
616 $rstore->getFields( $key ),
617 [ $pk => $id ],
618 __METHOD__
619 );
620
621 $queryInfo = $rstore->getJoin( $key );
622 $joinRow = $this->db->selectRow(
623 [ $table ] + $queryInfo['tables'],
624 $queryInfo['fields'],
625 [ $pk => $id ],
626 __METHOD__,
627 [],
628 $queryInfo['joins']
629 );
630
631 $expectForCombination = (
632 ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
633 ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
634 ) ? $expectOld : $expect;
635 $this->assertComment(
636 $expectForCombination,
637 $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
638 "w=$writeStage, r=$readStage, from getFields()"
639 );
640 $this->assertComment(
641 $expectForCombination,
642 $rstore->getComment( $key, $joinRow ),
643 "w=$writeStage, r=$readStage, from getJoin()"
644 );
645 }
646 }
647 }
648
649 /**
650 * @dataProvider provideInsertRoundTrip
651 * @param string $table
652 * @param string $key
653 * @param string $pk
654 * @param string|Message $comment
655 * @param array|null $data
656 * @param array $expect
657 */
658 public function testInsertRoundTrip_withKeyConstruction(
659 $table, $key, $pk, $comment, $data, $expect
660 ) {
661 static $id = 1000;
662
663 $expectOld = [
664 'text' => $expect['text'],
665 'message' => new RawMessage( '$1', [ Message::plaintextParam( $expect['text'] ) ] ),
666 'data' => null,
667 ];
668
669 $stages = [
670 MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
671 MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
672 MIGRATION_NEW ],
673 MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
674 MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
675
676 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
677 MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
678 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
679 ],
680 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
681 MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
682 SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
683 ],
684 ];
685
686 foreach ( $stages as $writeStage => $possibleReadStages ) {
687 $wstore = $this->makeStoreWithKey( $writeStage, $key );
688 $usesTemp = $key === 'cs2_comment';
689
690 if ( $usesTemp ) {
691 list( $fields, $callback ) = $wstore->insertWithTempTable(
692 $this->db, $comment, $data
693 );
694 } else {
695 $fields = $wstore->insert( $this->db, $comment, $data );
696 }
697
698 if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
699 $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
700 } else {
701 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
702 }
703 if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
704 $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
705 } else {
706 $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
707 }
708
709 $this->db->insert( $table, [ $pk => ++$id ] + $fields, __METHOD__ );
710 if ( $usesTemp ) {
711 $callback( $id );
712 }
713
714 foreach ( $possibleReadStages as $readStage ) {
715 $rstore = $this->makeStoreWithKey( $readStage, $key );
716
717 $fieldRow = $this->db->selectRow(
718 $table,
719 $rstore->getFields(),
720 [ $pk => $id ],
721 __METHOD__
722 );
723
724 $queryInfo = $rstore->getJoin();
725 $joinRow = $this->db->selectRow(
726 [ $table ] + $queryInfo['tables'],
727 $queryInfo['fields'],
728 [ $pk => $id ],
729 __METHOD__,
730 [],
731 $queryInfo['joins']
732 );
733
734 $expectForCombination = (
735 ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
736 ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
737 ) ? $expectOld : $expect;
738 $this->assertComment(
739 $expectForCombination,
740 $rstore->getCommentLegacy( $this->db, $fieldRow ),
741 "w=$writeStage, r=$readStage, from getFields()"
742 );
743 $this->assertComment(
744 $expectForCombination,
745 $rstore->getComment( $joinRow ),
746 "w=$writeStage, r=$readStage, from getJoin()"
747 );
748 }
749 }
750 }
751
752 public static function provideInsertRoundTrip() {
753 $db = wfGetDB( DB_REPLICA ); // for timestamps
754
755 $msgComment = new Message( 'parentheses', [ 'message comment' ] );
756 $textCommentMsg = new RawMessage( '$1', [ Message::plaintextParam( '{{text}} comment' ) ] );
757 $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
758 $comStoreComment = new CommentStoreComment(
759 null, 'comment store comment', null, [ 'foo' => 'bar' ]
760 );
761
762 return [
763 'Simple table, text comment' => [
764 'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', null, [
765 'text' => '{{text}} comment',
766 'message' => $textCommentMsg,
767 'data' => null,
768 ]
769 ],
770 'Simple table, text comment with data' => [
771 'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', [ 'message' => 42 ], [
772 'text' => '{{text}} comment',
773 'message' => $textCommentMsg,
774 'data' => [ 'message' => 42 ],
775 ]
776 ],
777 'Simple table, message comment' => [
778 'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, null, [
779 'text' => '(message comment)',
780 'message' => $msgComment,
781 'data' => null,
782 ]
783 ],
784 'Simple table, message comment with data' => [
785 'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, [ 'message' => 42 ], [
786 'text' => '(message comment)',
787 'message' => $msgComment,
788 'data' => [ 'message' => 42 ],
789 ]
790 ],
791 'Simple table, nested message comment' => [
792 'commentstore1', 'cs1_comment', 'cs1_id', $nestedMsgComment, null, [
793 'text' => '(Main Page)',
794 'message' => $nestedMsgComment,
795 'data' => null,
796 ]
797 ],
798 'Simple table, CommentStoreComment' => [
799 'commentstore1', 'cs1_comment', 'cs1_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
800 'text' => 'comment store comment',
801 'message' => $comStoreComment->message,
802 'data' => [ 'foo' => 'bar' ],
803 ]
804 ],
805
806 'Revision, text comment' => [
807 'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', null, [
808 'text' => '{{text}} comment',
809 'message' => $textCommentMsg,
810 'data' => null,
811 ]
812 ],
813 'Revision, text comment with data' => [
814 'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', [ 'message' => 42 ], [
815 'text' => '{{text}} comment',
816 'message' => $textCommentMsg,
817 'data' => [ 'message' => 42 ],
818 ]
819 ],
820 'Revision, message comment' => [
821 'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, null, [
822 'text' => '(message comment)',
823 'message' => $msgComment,
824 'data' => null,
825 ]
826 ],
827 'Revision, message comment with data' => [
828 'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, [ 'message' => 42 ], [
829 'text' => '(message comment)',
830 'message' => $msgComment,
831 'data' => [ 'message' => 42 ],
832 ]
833 ],
834 'Revision, nested message comment' => [
835 'commentstore2', 'cs2_comment', 'cs2_id', $nestedMsgComment, null, [
836 'text' => '(Main Page)',
837 'message' => $nestedMsgComment,
838 'data' => null,
839 ]
840 ],
841 'Revision, CommentStoreComment' => [
842 'commentstore2', 'cs2_comment', 'cs2_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
843 'text' => 'comment store comment',
844 'message' => $comStoreComment->message,
845 'data' => [ 'foo' => 'bar' ],
846 ]
847 ],
848 ];
849 }
850
851 public function testGetCommentErrors() {
852 Wikimedia\suppressWarnings();
853 $reset = new ScopedCallback( 'Wikimedia\restoreWarnings' );
854
855 $store = $this->makeStore( MIGRATION_OLD );
856 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
857 $this->assertSame( '', $res->text );
858 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
859 $this->assertSame( 'comment', $res->text );
860
861 $store = $this->makeStore( MIGRATION_NEW );
862 try {
863 $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
864 $this->fail( 'Expected exception not thrown' );
865 } catch ( InvalidArgumentException $ex ) {
866 $this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
867 }
868 $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
869 $this->assertSame( 'comment', $res->text );
870 try {
871 $store->getComment( 'dummy', [ 'dummy_id' => 1 ] );
872 $this->fail( 'Expected exception not thrown' );
873 } catch ( InvalidArgumentException $ex ) {
874 $this->assertSame(
875 '$row does not contain fields needed for comment dummy and getComment(), '
876 . 'but does have fields for getCommentLegacy()',
877 $ex->getMessage()
878 );
879 }
880
881 $store = $this->makeStore( MIGRATION_NEW );
882 try {
883 $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ] );
884 $this->fail( 'Expected exception not thrown' );
885 } catch ( InvalidArgumentException $ex ) {
886 $this->assertSame(
887 '$row does not contain fields needed for comment rev_comment', $ex->getMessage()
888 );
889 }
890 $res = $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ], true );
891 $this->assertSame( 'comment', $res->text );
892 try {
893 $store->getComment( 'rev_comment', [ 'rev_comment_pk' => 1 ] );
894 $this->fail( 'Expected exception not thrown' );
895 } catch ( InvalidArgumentException $ex ) {
896 $this->assertSame(
897 '$row does not contain fields needed for comment rev_comment and getComment(), '
898 . 'but does have fields for getCommentLegacy()',
899 $ex->getMessage()
900 );
901 }
902 }
903
904 public static function provideStages() {
905 return [
906 'MIGRATION_OLD' => [ MIGRATION_OLD ],
907 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
908 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
909 'MIGRATION_NEW' => [ MIGRATION_NEW ],
910
911 'SCHEMA_COMPAT write-both/read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
912 'SCHEMA_COMPAT write-both/read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
913 ];
914 }
915
916 /**
917 * @dataProvider provideStages
918 * @param int $stage
919 * @expectedException InvalidArgumentException
920 * @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
921 */
922 public function testInsertWrong( $stage ) {
923 $store = $this->makeStore( $stage );
924 $store->insert( $this->db, 'rev_comment', 'foo' );
925 }
926
927 /**
928 * @dataProvider provideStages
929 * @param int $stage
930 * @expectedException InvalidArgumentException
931 * @expectedExceptionMessage Must use insert() for ipb_reason
932 */
933 public function testInsertWithTempTableWrong( $stage ) {
934 $store = $this->makeStore( $stage );
935 $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
936 }
937
938 /**
939 * @dataProvider provideStages
940 * @param int $stage
941 */
942 public function testInsertWithTempTableDeprecated( $stage ) {
943 $store = $this->makeStore( $stage );
944 $wrap = TestingAccessWrapper::newFromObject( $store );
945 $wrap->tempTables += [ 'ipb_reason' => [
946 'stage' => MIGRATION_NEW,
947 'deprecatedIn' => '1.30',
948 ] ];
949
950 $this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
951 list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
952 $this->assertTrue( is_callable( $callback ) );
953 }
954
955 public function testInsertTruncation() {
956 $comment = str_repeat( '💣', 16400 );
957 $truncated1 = str_repeat( '💣', 63 ) . '...';
958 $truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
959
960 $store = $this->makeStore( MIGRATION_WRITE_BOTH );
961 $fields = $store->insert( $this->db, 'ipb_reason', $comment );
962 $this->assertSame( $truncated1, $fields['ipb_reason'] );
963 $stored = $this->db->selectField(
964 'comment', 'comment_text', [ 'comment_id' => $fields['ipb_reason_id'] ], __METHOD__
965 );
966 $this->assertSame( $truncated2, $stored );
967 }
968
969 /**
970 * @expectedException OverflowException
971 * @expectedExceptionMessage Comment data is too long (65611 bytes, maximum is 65535)
972 */
973 public function testInsertTooMuchData() {
974 $store = $this->makeStore( MIGRATION_WRITE_BOTH );
975 $store->insert( $this->db, 'ipb_reason', 'foo', [
976 'long' => str_repeat( '💣', 16400 )
977 ] );
978 }
979
980 public function testGetStore() {
981 $this->assertInstanceOf( CommentStore::class, CommentStore::getStore() );
982 }
983
984 public function testNewKey() {
985 $this->hideDeprecated( 'CommentStore::newKey' );
986 $this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
987 }
988
989 }