Follows-up I361fde0de7f4406bce6ed075ed397effa5be3359. Per T253461, not mass-changing source code, but the use of the native error silencing operator (@) is especially useful in tests because: 1. It requires any/all statements to be explicitly marked. The suppressWarnings/restoreWarnings sections encourage developers to be "lazy" and thus encapsulate more than needed if there are multiple ones near each other, which would ignore potentially important warnings in a test case, which is generally exactly the time when it is really useful to get warnings etc. 2. It avoids leaking state, for example in LBFactoryTest the assertFalse call would throw a PHPUnit assertion error (not meant to be caught by the local catch), and thus won't reach AtEase::restoreWarnings. This then causes later code to end up in a mismatching state and creates a confusing error_reporting state. See .phpcs.xml, where the at operator is allowed for all test code. Change-Id: I68d1725d685e0a7586468bc9de6dc29ceea31b8a
849 lines
27 KiB
PHP
849 lines
27 KiB
PHP
<?php
|
|
|
|
use Wikimedia\Rdbms\IMaintainableDatabase;
|
|
|
|
/**
|
|
* @group Database
|
|
* @covers CommentStore
|
|
* @covers CommentStoreComment
|
|
*/
|
|
class CommentStoreTest extends MediaWikiLangTestCase {
|
|
|
|
protected $tablesUsed = [
|
|
'revision',
|
|
'revision_comment_temp',
|
|
'ipblocks',
|
|
'comment',
|
|
];
|
|
|
|
protected function getSchemaOverrides( IMaintainableDatabase $db ) {
|
|
return [
|
|
'scripts' => [
|
|
__DIR__ . '/CommentStoreTest.sql',
|
|
],
|
|
'drop' => [],
|
|
'create' => [ 'commentstore1', 'commentstore2', 'commentstore2_temp' ],
|
|
'alter' => [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create a store for a particular stage
|
|
* @param int $stage
|
|
* @return CommentStore
|
|
*/
|
|
private function makeStore( $stage ) {
|
|
$lang = $this->createMock( Language::class );
|
|
$lang->method( 'truncateForDatabase' )->willReturnCallback( static function ( $str, $len ) {
|
|
return strlen( $str ) > $len ? substr( $str, 0, $len - 3 ) . '...' : $str;
|
|
} );
|
|
$lang->method( 'truncateForVisual' )->willReturnCallback( static function ( $str, $len ) {
|
|
return mb_strlen( $str ) > $len ? mb_substr( $str, 0, $len - 3 ) . '...' : $str;
|
|
} );
|
|
return new class( $lang, $stage ) extends CommentStore {
|
|
protected const TEMP_TABLES = [
|
|
'rev_comment' => [
|
|
'table' => 'revision_comment_temp',
|
|
'pk' => 'revcomment_rev',
|
|
'field' => 'revcomment_comment_id',
|
|
'joinPK' => 'rev_id',
|
|
'stage' => MIGRATION_OLD,
|
|
'deprecatedIn' => null,
|
|
],
|
|
'cs2_comment' => [
|
|
'table' => 'commentstore2_temp',
|
|
'pk' => 'cs2t_id',
|
|
'field' => 'cs2t_comment_id',
|
|
'joinPK' => 'cs2_id',
|
|
'stage' => MIGRATION_OLD,
|
|
'deprecatedIn' => null,
|
|
],
|
|
];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideConstructor
|
|
* @param int $stage
|
|
* @param string|null $exceptionMsg
|
|
*/
|
|
public function testConstructor( $stage, $exceptionMsg ) {
|
|
try {
|
|
$m = new CommentStore(
|
|
$this->createMock( Language::class ),
|
|
$stage
|
|
);
|
|
if ( $exceptionMsg !== null ) {
|
|
$this->fail( 'Expected exception not thrown' );
|
|
}
|
|
$this->assertInstanceOf( CommentStore::class, $m );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame( $exceptionMsg, $ex->getMessage() );
|
|
}
|
|
}
|
|
|
|
public static function provideConstructor() {
|
|
return [
|
|
[ 0, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW, null ],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, null ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD, null ],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, null ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, null ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetFields
|
|
* @param int $stage
|
|
* @param string $key
|
|
* @param array $expect
|
|
*/
|
|
public function testGetFields( $stage, $key, $expect ) {
|
|
$store = $this->makeStore( $stage );
|
|
$result = $store->getFields( $key );
|
|
$this->assertEquals( $expect, $result );
|
|
}
|
|
|
|
public static function provideGetFields() {
|
|
return [
|
|
'Simple table, old' => [
|
|
MIGRATION_OLD, 'ipb_reason',
|
|
[ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
|
|
],
|
|
'Simple table, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'ipb_reason',
|
|
[ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
|
|
],
|
|
'Simple table, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'ipb_reason',
|
|
[ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
|
|
],
|
|
'Simple table, new' => [
|
|
MIGRATION_NEW, 'ipb_reason',
|
|
[ 'ipb_reason_id' => 'ipb_reason_id' ],
|
|
],
|
|
'Simple table, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason',
|
|
[ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
|
|
],
|
|
'Simple table, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason',
|
|
[ 'ipb_reason_id' => 'ipb_reason_id' ],
|
|
],
|
|
|
|
'Revision, old' => [
|
|
MIGRATION_OLD, 'rev_comment',
|
|
[
|
|
'rev_comment_text' => 'rev_comment',
|
|
'rev_comment_data' => 'NULL',
|
|
'rev_comment_cid' => 'NULL',
|
|
],
|
|
],
|
|
'Revision, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'rev_comment',
|
|
[ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
|
|
],
|
|
'Revision, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'rev_comment',
|
|
[ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
|
|
],
|
|
'Revision, new' => [
|
|
MIGRATION_NEW, 'rev_comment',
|
|
[ 'rev_comment_pk' => 'rev_id' ],
|
|
],
|
|
'Revision, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment',
|
|
[
|
|
'rev_comment_text' => 'rev_comment',
|
|
'rev_comment_data' => 'NULL',
|
|
'rev_comment_cid' => 'NULL',
|
|
],
|
|
],
|
|
'Revision, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment',
|
|
[ 'rev_comment_pk' => 'rev_id' ],
|
|
],
|
|
|
|
'Image, old' => [
|
|
MIGRATION_OLD, 'img_description',
|
|
[
|
|
'img_description_text' => 'img_description',
|
|
'img_description_data' => 'NULL',
|
|
'img_description_cid' => 'NULL',
|
|
],
|
|
],
|
|
'Image, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'img_description',
|
|
[
|
|
'img_description_old' => 'img_description',
|
|
'img_description_id' => 'img_description_id'
|
|
],
|
|
],
|
|
'Image, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'img_description',
|
|
[
|
|
'img_description_old' => 'img_description',
|
|
'img_description_id' => 'img_description_id'
|
|
],
|
|
],
|
|
'Image, new' => [
|
|
MIGRATION_NEW, 'img_description',
|
|
[
|
|
'img_description_id' => 'img_description_id'
|
|
],
|
|
],
|
|
'Image, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description',
|
|
[
|
|
'img_description_text' => 'img_description',
|
|
'img_description_data' => 'NULL',
|
|
'img_description_cid' => 'NULL',
|
|
],
|
|
],
|
|
'Image, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description',
|
|
[
|
|
'img_description_id' => 'img_description_id'
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetJoin
|
|
* @param int $stage
|
|
* @param string $key
|
|
* @param array $expect
|
|
*/
|
|
public function testGetJoin( $stage, $key, $expect ) {
|
|
$store = $this->makeStore( $stage );
|
|
$result = $store->getJoin( $key );
|
|
$this->assertEquals( $expect, $result );
|
|
}
|
|
|
|
public static function provideGetJoin() {
|
|
return [
|
|
'Simple table, old' => [
|
|
MIGRATION_OLD, 'ipb_reason', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'ipb_reason',
|
|
'ipb_reason_data' => 'NULL',
|
|
'ipb_reason_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'ipb_reason', [
|
|
'tables' => [ 'comment_ipb_reason' => 'comment' ],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
|
|
'ipb_reason_data' => 'comment_ipb_reason.comment_data',
|
|
'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Simple table, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'ipb_reason', [
|
|
'tables' => [ 'comment_ipb_reason' => 'comment' ],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
|
|
'ipb_reason_data' => 'comment_ipb_reason.comment_data',
|
|
'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Simple table, new' => [
|
|
MIGRATION_NEW, 'ipb_reason', [
|
|
'tables' => [ 'comment_ipb_reason' => 'comment' ],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'comment_ipb_reason.comment_text',
|
|
'ipb_reason_data' => 'comment_ipb_reason.comment_data',
|
|
'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Simple table, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'ipb_reason',
|
|
'ipb_reason_data' => 'NULL',
|
|
'ipb_reason_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', [
|
|
'tables' => [ 'comment_ipb_reason' => 'comment' ],
|
|
'fields' => [
|
|
'ipb_reason_text' => 'comment_ipb_reason.comment_text',
|
|
'ipb_reason_data' => 'comment_ipb_reason.comment_data',
|
|
'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
|
|
],
|
|
],
|
|
],
|
|
|
|
'Revision, old' => [
|
|
MIGRATION_OLD, 'rev_comment', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rev_comment_text' => 'rev_comment',
|
|
'rev_comment_data' => 'NULL',
|
|
'rev_comment_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'rev_comment', [
|
|
'tables' => [
|
|
'temp_rev_comment' => 'revision_comment_temp',
|
|
'comment_rev_comment' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
|
|
'rev_comment_data' => 'comment_rev_comment.comment_data',
|
|
'rev_comment_cid' => 'comment_rev_comment.comment_id',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
|
|
'comment_rev_comment' => [ 'LEFT JOIN',
|
|
'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Revision, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'rev_comment', [
|
|
'tables' => [
|
|
'temp_rev_comment' => 'revision_comment_temp',
|
|
'comment_rev_comment' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
|
|
'rev_comment_data' => 'comment_rev_comment.comment_data',
|
|
'rev_comment_cid' => 'comment_rev_comment.comment_id',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
|
|
'comment_rev_comment' => [ 'LEFT JOIN',
|
|
'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Revision, new' => [
|
|
MIGRATION_NEW, 'rev_comment', [
|
|
'tables' => [
|
|
'temp_rev_comment' => 'revision_comment_temp',
|
|
'comment_rev_comment' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'rev_comment_text' => 'comment_rev_comment.comment_text',
|
|
'rev_comment_data' => 'comment_rev_comment.comment_data',
|
|
'rev_comment_cid' => 'comment_rev_comment.comment_id',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
|
|
'comment_rev_comment' => [ 'JOIN',
|
|
'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Revision, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rev_comment_text' => 'rev_comment',
|
|
'rev_comment_data' => 'NULL',
|
|
'rev_comment_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', [
|
|
'tables' => [
|
|
'temp_rev_comment' => 'revision_comment_temp',
|
|
'comment_rev_comment' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'rev_comment_text' => 'comment_rev_comment.comment_text',
|
|
'rev_comment_data' => 'comment_rev_comment.comment_data',
|
|
'rev_comment_cid' => 'comment_rev_comment.comment_id',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
|
|
'comment_rev_comment' => [ 'JOIN',
|
|
'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
|
|
],
|
|
],
|
|
],
|
|
|
|
'Image, old' => [
|
|
MIGRATION_OLD, 'img_description', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'img_description_text' => 'img_description',
|
|
'img_description_data' => 'NULL',
|
|
'img_description_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Image, write-both' => [
|
|
MIGRATION_WRITE_BOTH, 'img_description', [
|
|
'tables' => [
|
|
'comment_img_description' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
|
|
'img_description_data' => 'comment_img_description.comment_data',
|
|
'img_description_cid' => 'comment_img_description.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_img_description' => [ 'LEFT JOIN',
|
|
'comment_img_description.comment_id = img_description_id',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'Image, write-new' => [
|
|
MIGRATION_WRITE_NEW, 'img_description', [
|
|
'tables' => [
|
|
'comment_img_description' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
|
|
'img_description_data' => 'comment_img_description.comment_data',
|
|
'img_description_cid' => 'comment_img_description.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_img_description' => [ 'LEFT JOIN',
|
|
'comment_img_description.comment_id = img_description_id',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'Image, new' => [
|
|
MIGRATION_NEW, 'img_description', [
|
|
'tables' => [
|
|
'comment_img_description' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'img_description_text' => 'comment_img_description.comment_text',
|
|
'img_description_data' => 'comment_img_description.comment_data',
|
|
'img_description_cid' => 'comment_img_description.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_img_description' => [ 'JOIN',
|
|
'comment_img_description.comment_id = img_description_id',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'Image, write-both/read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'img_description_text' => 'img_description',
|
|
'img_description_data' => 'NULL',
|
|
'img_description_cid' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Image, write-both/read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', [
|
|
'tables' => [
|
|
'comment_img_description' => 'comment',
|
|
],
|
|
'fields' => [
|
|
'img_description_text' => 'comment_img_description.comment_text',
|
|
'img_description_data' => 'comment_img_description.comment_data',
|
|
'img_description_cid' => 'comment_img_description.comment_id',
|
|
],
|
|
'joins' => [
|
|
'comment_img_description' => [ 'JOIN',
|
|
'comment_img_description.comment_id = img_description_id',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
private function assertComment( $expect, $actual, $from ) {
|
|
$this->assertSame( $expect['text'], $actual->text, "text $from" );
|
|
$this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
|
|
"message class $from" );
|
|
$this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
|
|
"message keys $from" );
|
|
$this->assertEquals( $expect['message']->text(), $actual->message->text(),
|
|
"message rendering $from" );
|
|
$this->assertEquals( $expect['text'], $actual->message->text(),
|
|
"message rendering and text $from" );
|
|
$this->assertEquals( $expect['data'], $actual->data, "data $from" );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideInsertRoundTrip
|
|
* @param string $table
|
|
* @param string $key
|
|
* @param string $pk
|
|
* @param string|Message $comment
|
|
* @param array|null $data
|
|
* @param array $expect
|
|
*/
|
|
public function testInsertRoundTrip( $table, $key, $pk, $comment, $data, $expect ) {
|
|
static $id = 1;
|
|
|
|
$expectOld = [
|
|
'text' => $expect['text'],
|
|
'message' => new RawMessage( '$1', [ Message::plaintextParam( $expect['text'] ) ] ),
|
|
'data' => null,
|
|
];
|
|
|
|
$stages = [
|
|
MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
|
|
MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
|
|
MIGRATION_NEW ],
|
|
MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
|
|
MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
|
|
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
|
|
MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
|
|
],
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
|
|
MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
|
|
],
|
|
];
|
|
|
|
foreach ( $stages as $writeStage => $possibleReadStages ) {
|
|
$wstore = $this->makeStore( $writeStage );
|
|
$usesTemp = $key === 'cs2_comment';
|
|
|
|
if ( $usesTemp ) {
|
|
list( $fields, $callback ) = $wstore->insertWithTempTable(
|
|
$this->db, $key, $comment, $data
|
|
);
|
|
} else {
|
|
$fields = $wstore->insert( $this->db, $key, $comment, $data );
|
|
}
|
|
|
|
if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
|
|
$this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
|
|
} else {
|
|
$this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
|
|
}
|
|
if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
|
|
$this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
|
|
} else {
|
|
$this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
|
|
}
|
|
|
|
$this->db->insert( $table, [ $pk => ++$id ] + $fields, __METHOD__ );
|
|
if ( $usesTemp ) {
|
|
$callback( $id );
|
|
}
|
|
|
|
foreach ( $possibleReadStages as $readStage ) {
|
|
$rstore = $this->makeStore( $readStage );
|
|
|
|
$fieldRow = $this->db->selectRow(
|
|
$table,
|
|
$rstore->getFields( $key ),
|
|
[ $pk => $id ],
|
|
__METHOD__
|
|
);
|
|
|
|
$queryInfo = $rstore->getJoin( $key );
|
|
$joinRow = $this->db->selectRow(
|
|
[ $table ] + $queryInfo['tables'],
|
|
$queryInfo['fields'],
|
|
[ $pk => $id ],
|
|
__METHOD__,
|
|
[],
|
|
$queryInfo['joins']
|
|
);
|
|
|
|
$expectForCombination = (
|
|
( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
|
|
( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
|
|
) ? $expectOld : $expect;
|
|
$this->assertComment(
|
|
$expectForCombination,
|
|
$rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
|
|
"w=$writeStage, r=$readStage, from getFields()"
|
|
);
|
|
$this->assertComment(
|
|
$expectForCombination,
|
|
$rstore->getComment( $key, $joinRow ),
|
|
"w=$writeStage, r=$readStage, from getJoin()"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function provideInsertRoundTrip() {
|
|
$db = wfGetDB( DB_REPLICA ); // for timestamps
|
|
|
|
$msgComment = new Message( 'parentheses', [ 'message comment' ] );
|
|
$textCommentMsg = new RawMessage( '$1', [ Message::plaintextParam( '{{text}} comment' ) ] );
|
|
$nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
|
|
$comStoreComment = new CommentStoreComment(
|
|
null, 'comment store comment', null, [ 'foo' => 'bar' ]
|
|
);
|
|
|
|
return [
|
|
'Simple table, text comment' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', null, [
|
|
'text' => '{{text}} comment',
|
|
'message' => $textCommentMsg,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Simple table, text comment with data' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', '{{text}} comment', [ 'message' => 42 ], [
|
|
'text' => '{{text}} comment',
|
|
'message' => $textCommentMsg,
|
|
'data' => [ 'message' => 42 ],
|
|
]
|
|
],
|
|
'Simple table, message comment' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, null, [
|
|
'text' => '(message comment)',
|
|
'message' => $msgComment,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Simple table, message comment with data' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', $msgComment, [ 'message' => 42 ], [
|
|
'text' => '(message comment)',
|
|
'message' => $msgComment,
|
|
'data' => [ 'message' => 42 ],
|
|
]
|
|
],
|
|
'Simple table, nested message comment' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', $nestedMsgComment, null, [
|
|
'text' => '(Main Page)',
|
|
'message' => $nestedMsgComment,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Simple table, CommentStoreComment' => [
|
|
'commentstore1', 'cs1_comment', 'cs1_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
|
|
'text' => 'comment store comment',
|
|
'message' => $comStoreComment->message,
|
|
'data' => [ 'foo' => 'bar' ],
|
|
]
|
|
],
|
|
|
|
'Revision, text comment' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', null, [
|
|
'text' => '{{text}} comment',
|
|
'message' => $textCommentMsg,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Revision, text comment with data' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', '{{text}} comment', [ 'message' => 42 ], [
|
|
'text' => '{{text}} comment',
|
|
'message' => $textCommentMsg,
|
|
'data' => [ 'message' => 42 ],
|
|
]
|
|
],
|
|
'Revision, message comment' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, null, [
|
|
'text' => '(message comment)',
|
|
'message' => $msgComment,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Revision, message comment with data' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', $msgComment, [ 'message' => 42 ], [
|
|
'text' => '(message comment)',
|
|
'message' => $msgComment,
|
|
'data' => [ 'message' => 42 ],
|
|
]
|
|
],
|
|
'Revision, nested message comment' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', $nestedMsgComment, null, [
|
|
'text' => '(Main Page)',
|
|
'message' => $nestedMsgComment,
|
|
'data' => null,
|
|
]
|
|
],
|
|
'Revision, CommentStoreComment' => [
|
|
'commentstore2', 'cs2_comment', 'cs2_id', clone $comStoreComment, [ 'baz' => 'baz' ], [
|
|
'text' => 'comment store comment',
|
|
'message' => $comStoreComment->message,
|
|
'data' => [ 'foo' => 'bar' ],
|
|
]
|
|
],
|
|
];
|
|
}
|
|
|
|
public function testGetCommentErrors() {
|
|
$store = $this->makeStore( MIGRATION_OLD );
|
|
// Ignore: Missing dummy_text and dummy_data fields
|
|
$res = @$store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
|
|
$this->assertSame( '', $res->text );
|
|
$res = @$store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
|
|
$this->assertSame( 'comment', $res->text );
|
|
|
|
$store = $this->makeStore( MIGRATION_NEW );
|
|
try {
|
|
$store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
|
|
}
|
|
// Ignore: Using deprecated fallback handling for comment dummy
|
|
$res = @$store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
|
|
$this->assertSame( 'comment', $res->text );
|
|
try {
|
|
$store->getComment( 'dummy', [ 'dummy_id' => 1 ] );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'$row does not contain fields needed for comment dummy and getComment(), '
|
|
. 'but does have fields for getCommentLegacy()',
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
|
|
$store = $this->makeStore( MIGRATION_NEW );
|
|
try {
|
|
$store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ] );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'$row does not contain fields needed for comment rev_comment', $ex->getMessage()
|
|
);
|
|
}
|
|
$res = @$store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ], true );
|
|
$this->assertSame( 'comment', $res->text );
|
|
try {
|
|
$store->getComment( 'rev_comment', [ 'rev_comment_pk' => 1 ] );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'$row does not contain fields needed for comment rev_comment and getComment(), '
|
|
. 'but does have fields for getCommentLegacy()',
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
}
|
|
|
|
public static function provideStages() {
|
|
return [
|
|
'MIGRATION_OLD' => [ MIGRATION_OLD ],
|
|
'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
|
|
'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
|
|
'MIGRATION_NEW' => [ MIGRATION_NEW ],
|
|
|
|
'SCHEMA_COMPAT write-both/read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
|
|
'SCHEMA_COMPAT write-both/read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
*/
|
|
public function testInsertWrong( $stage ) {
|
|
$store = $this->makeStore( $stage );
|
|
$this->expectException( InvalidArgumentException::class );
|
|
$this->expectExceptionMessage( "Must use insertWithTempTable() for rev_comment" );
|
|
$store->insert( $this->db, 'rev_comment', 'foo' );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
*/
|
|
public function testInsertWithTempTableWrong( $stage ) {
|
|
$store = $this->makeStore( $stage );
|
|
$this->expectException( InvalidArgumentException::class );
|
|
$this->expectExceptionMessage( "Must use insert() for ipb_reason" );
|
|
$store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
*/
|
|
public function testInsertWithTempTableDeprecated( $stage ) {
|
|
$lang = $this->getServiceContainer()->getContentLanguage();
|
|
$store = new class( $lang, $stage ) extends CommentStore {
|
|
protected const TEMP_TABLES = [
|
|
'ipb_reason' => [
|
|
'stage' => MIGRATION_NEW,
|
|
'deprecatedIn' => '1.30',
|
|
],
|
|
];
|
|
};
|
|
|
|
$this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
|
|
list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
|
|
$this->assertIsCallable( $callback );
|
|
}
|
|
|
|
public function testInsertTruncation() {
|
|
$comment = str_repeat( '💣', 16400 );
|
|
$truncated1 = str_repeat( '💣', 63 ) . '...';
|
|
$truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
|
|
|
|
$store = $this->makeStore( MIGRATION_WRITE_BOTH );
|
|
$fields = $store->insert( $this->db, 'ipb_reason', $comment );
|
|
$this->assertSame( $truncated1, $fields['ipb_reason'] );
|
|
$stored = $this->db->selectField(
|
|
'comment', 'comment_text', [ 'comment_id' => $fields['ipb_reason_id'] ], __METHOD__
|
|
);
|
|
$this->assertSame( $truncated2, $stored );
|
|
}
|
|
|
|
public function testInsertTooMuchData() {
|
|
$store = $this->makeStore( MIGRATION_WRITE_BOTH );
|
|
$this->expectException( OverflowException::class );
|
|
$this->expectExceptionMessage( "Comment data is too long (65611 bytes, maximum is 65535)" );
|
|
$store->insert( $this->db, 'ipb_reason', 'foo', [
|
|
'long' => str_repeat( '💣', 16400 )
|
|
] );
|
|
}
|
|
|
|
public function testGetStore() {
|
|
$this->assertInstanceOf( CommentStore::class, CommentStore::getStore() );
|
|
}
|
|
|
|
}
|