3 use MediaWiki\User\UserIdentity
;
4 use MediaWiki\MediaWikiServices
;
5 use Wikimedia\TestingAccessWrapper
;
9 * @covers ActorMigration
11 class ActorMigrationTest
extends MediaWikiLangTestCase
{
13 protected $tablesUsed = [
15 'revision_actor_temp',
22 * Create an ActorMigration for a particular stage
24 * @return ActorMigration
26 protected function makeMigration( $stage ) {
27 return new ActorMigration( $stage );
31 * @dataProvider provideGetJoin
34 * @param array $expect
36 public function testGetJoin( $stage, $key, $expect ) {
37 $m = $this->makeMigration( $stage );
38 $result = $m->getJoin( $key );
39 $this->assertEquals( $expect, $result );
42 public static function provideGetJoin() {
44 'Simple table, old' => [
45 MIGRATION_OLD
, 'rc_user', [
48 'rc_user' => 'rc_user',
49 'rc_user_text' => 'rc_user_text',
55 'Simple table, write-both' => [
56 MIGRATION_WRITE_BOTH
, 'rc_user', [
57 'tables' => [ 'actor_rc_user' => 'actor' ],
59 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
60 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
61 'rc_actor' => 'rc_actor',
64 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
68 'Simple table, write-new' => [
69 MIGRATION_WRITE_NEW
, 'rc_user', [
70 'tables' => [ 'actor_rc_user' => 'actor' ],
72 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
73 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
74 'rc_actor' => 'rc_actor',
77 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
81 'Simple table, new' => [
82 MIGRATION_NEW
, 'rc_user', [
83 'tables' => [ 'actor_rc_user' => 'actor' ],
85 'rc_user' => 'actor_rc_user.actor_user',
86 'rc_user_text' => 'actor_rc_user.actor_name',
87 'rc_actor' => 'rc_actor',
90 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
96 MIGRATION_OLD
, 'ipb_by', [
100 'ipb_by_text' => 'ipb_by_text',
101 'ipb_by_actor' => 'NULL',
106 'ipblocks, write-both' => [
107 MIGRATION_WRITE_BOTH
, 'ipb_by', [
108 'tables' => [ 'actor_ipb_by' => 'actor' ],
110 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
111 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
112 'ipb_by_actor' => 'ipb_by_actor',
115 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
119 'ipblocks, write-new' => [
120 MIGRATION_WRITE_NEW
, 'ipb_by', [
121 'tables' => [ 'actor_ipb_by' => 'actor' ],
123 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
124 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
125 'ipb_by_actor' => 'ipb_by_actor',
128 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
133 MIGRATION_NEW
, 'ipb_by', [
134 'tables' => [ 'actor_ipb_by' => 'actor' ],
136 'ipb_by' => 'actor_ipb_by.actor_user',
137 'ipb_by_text' => 'actor_ipb_by.actor_name',
138 'ipb_by_actor' => 'ipb_by_actor',
141 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
147 MIGRATION_OLD
, 'rev_user', [
150 'rev_user' => 'rev_user',
151 'rev_user_text' => 'rev_user_text',
152 'rev_actor' => 'NULL',
157 'Revision, write-both' => [
158 MIGRATION_WRITE_BOTH
, 'rev_user', [
160 'temp_rev_user' => 'revision_actor_temp',
161 'actor_rev_user' => 'actor',
164 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
165 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
166 'rev_actor' => 'temp_rev_user.revactor_actor',
169 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
170 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
174 'Revision, write-new' => [
175 MIGRATION_WRITE_NEW
, 'rev_user', [
177 'temp_rev_user' => 'revision_actor_temp',
178 'actor_rev_user' => 'actor',
181 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
182 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
183 'rev_actor' => 'temp_rev_user.revactor_actor',
186 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
187 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
192 MIGRATION_NEW
, 'rev_user', [
194 'temp_rev_user' => 'revision_actor_temp',
195 'actor_rev_user' => 'actor',
198 'rev_user' => 'actor_rev_user.actor_user',
199 'rev_user_text' => 'actor_rev_user.actor_name',
200 'rev_actor' => 'temp_rev_user.revactor_actor',
203 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
204 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
212 * @dataProvider provideGetWhere
215 * @param UserIdentity[] $users
217 * @param array $expect
219 public function testGetWhere( $stage, $key, $users, $useId, $expect ) {
220 $expect['conds'] = '(' . implode( ') OR (', $expect['orconds'] ) . ')';
222 if ( count( $users ) === 1 ) {
223 $users = reset( $users );
226 $m = $this->makeMigration( $stage );
227 $result = $m->getWhere( $this->db
, $key, $users, $useId );
228 $this->assertEquals( $expect, $result );
231 public function provideGetWhere() {
232 $makeUserIdentity = function ( $id, $name, $actor ) {
233 $u = $this->getMock( UserIdentity
::class );
234 $u->method( 'getId' )->willReturn( $id );
235 $u->method( 'getName' )->willReturn( $name );
236 $u->method( 'getActorId' )->willReturn( $actor );
240 $genericUser = [ $makeUserIdentity( 1, 'User1', 11 ) ];
241 $complicatedUsers = [
242 $makeUserIdentity( 1, 'User1', 11 ),
243 $makeUserIdentity( 2, 'User2', 12 ),
244 $makeUserIdentity( 3, 'User3', 0 ),
245 $makeUserIdentity( 0, '192.168.12.34', 34 ),
246 $makeUserIdentity( 0, '192.168.12.35', 0 ),
250 'Simple table, old' => [
251 MIGRATION_OLD
, 'rc_user', $genericUser, true, [
253 'orconds' => [ 'userid' => "rc_user = '1'" ],
257 'Simple table, write-both' => [
258 MIGRATION_WRITE_BOTH
, 'rc_user', $genericUser, true, [
261 'actor' => "rc_actor = '11'",
262 'userid' => "rc_actor = '0' AND rc_user = '1'"
267 'Simple table, write-new' => [
268 MIGRATION_WRITE_NEW
, 'rc_user', $genericUser, true, [
271 'actor' => "rc_actor = '11'",
272 'userid' => "rc_actor = '0' AND rc_user = '1'"
277 'Simple table, new' => [
278 MIGRATION_NEW
, 'rc_user', $genericUser, true, [
280 'orconds' => [ 'actor' => "rc_actor = '11'" ],
286 MIGRATION_OLD
, 'ipb_by', $genericUser, true, [
288 'orconds' => [ 'userid' => "ipb_by = '1'" ],
292 'ipblocks, write-both' => [
293 MIGRATION_WRITE_BOTH
, 'ipb_by', $genericUser, true, [
296 'actor' => "ipb_by_actor = '11'",
297 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
302 'ipblocks, write-new' => [
303 MIGRATION_WRITE_NEW
, 'ipb_by', $genericUser, true, [
306 'actor' => "ipb_by_actor = '11'",
307 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
313 MIGRATION_NEW
, 'ipb_by', $genericUser, true, [
315 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
321 MIGRATION_OLD
, 'rev_user', $genericUser, true, [
323 'orconds' => [ 'userid' => "rev_user = '1'" ],
327 'Revision, write-both' => [
328 MIGRATION_WRITE_BOTH
, 'rev_user', $genericUser, true, [
330 'temp_rev_user' => 'revision_actor_temp',
334 "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
335 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
338 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
342 'Revision, write-new' => [
343 MIGRATION_WRITE_NEW
, 'rev_user', $genericUser, true, [
345 'temp_rev_user' => 'revision_actor_temp',
349 "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
350 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
353 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
358 MIGRATION_NEW
, 'rev_user', $genericUser, true, [
360 'temp_rev_user' => 'revision_actor_temp',
362 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
364 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
369 'Multiple users, old' => [
370 MIGRATION_OLD
, 'rc_user', $complicatedUsers, true, [
373 'userid' => "rc_user IN ('1','2','3') ",
374 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
379 'Multiple users, write-both' => [
380 MIGRATION_WRITE_BOTH
, 'rc_user', $complicatedUsers, true, [
383 'actor' => "rc_actor IN ('11','12','34') ",
384 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
385 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
390 'Multiple users, write-new' => [
391 MIGRATION_WRITE_NEW
, 'rc_user', $complicatedUsers, true, [
394 'actor' => "rc_actor IN ('11','12','34') ",
395 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
396 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
401 'Multiple users, new' => [
402 MIGRATION_NEW
, 'rc_user', $complicatedUsers, true, [
404 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
409 'Multiple users, no use ID, old' => [
410 MIGRATION_OLD
, 'rc_user', $complicatedUsers, false, [
413 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
418 'Multiple users, write-both' => [
419 MIGRATION_WRITE_BOTH
, 'rc_user', $complicatedUsers, false, [
422 'actor' => "rc_actor IN ('11','12','34') ",
423 'username' => "rc_actor = '0' AND "
424 . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
429 'Multiple users, write-new' => [
430 MIGRATION_WRITE_NEW
, 'rc_user', $complicatedUsers, false, [
433 'actor' => "rc_actor IN ('11','12','34') ",
434 'username' => "rc_actor = '0' AND "
435 . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
440 'Multiple users, new' => [
441 MIGRATION_NEW
, 'rc_user', $complicatedUsers, false, [
443 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
451 * @dataProvider provideInsertRoundTrip
452 * @param string $table
455 * @param array $extraFields
457 public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
458 $u = $this->getTestUser()->getUser();
459 $user = $this->getMock( UserIdentity
::class );
460 $user->method( 'getId' )->willReturn( $u->getId() );
461 $user->method( 'getName' )->willReturn( $u->getName() );
462 if ( $u->getActorId( $this->db
) ) {
463 $user->method( 'getActorId' )->willReturn( $u->getActorId() );
467 [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
470 $user->method( 'getActorId' )->willReturn( $this->db
->insertId() );
474 MIGRATION_OLD
=> [ MIGRATION_OLD
, MIGRATION_WRITE_BOTH
, MIGRATION_WRITE_NEW
],
475 MIGRATION_WRITE_BOTH
=> [ MIGRATION_OLD
, MIGRATION_WRITE_BOTH
, MIGRATION_WRITE_NEW
,
477 MIGRATION_WRITE_NEW
=> [ MIGRATION_WRITE_BOTH
, MIGRATION_WRITE_NEW
, MIGRATION_NEW
],
478 MIGRATION_NEW
=> [ MIGRATION_WRITE_BOTH
, MIGRATION_WRITE_NEW
, MIGRATION_NEW
],
481 $nameKey = $key . '_text';
482 $actorKey = $key === 'ipb_by' ?
'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
484 foreach ( $stages as $writeStage => $possibleReadStages ) {
485 if ( $key === 'ipb_by' ) {
486 $extraFields['ipb_address'] = __CLASS__
. "#$writeStage";
489 $w = $this->makeMigration( $writeStage );
490 $usesTemp = $key === 'rev_user';
493 list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db
, $key, $user );
495 $fields = $w->getInsertValues( $this->db
, $key, $user );
498 if ( $writeStage <= MIGRATION_WRITE_BOTH
) {
499 $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
500 $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
502 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
503 $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
505 if ( $writeStage >= MIGRATION_WRITE_BOTH
&& !$usesTemp ) {
506 $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
508 $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
511 $this->db
->insert( $table, $extraFields +
$fields, __METHOD__
);
512 $id = $this->db
->insertId();
514 $callback( $id, $extraFields );
517 foreach ( $possibleReadStages as $readStage ) {
518 $r = $this->makeMigration( $readStage );
520 $queryInfo = $r->getJoin( $key );
521 $row = $this->db
->selectRow(
522 [ $table ] +
$queryInfo['tables'],
523 $queryInfo['fields'],
530 $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
531 $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
533 $readStage === MIGRATION_OLD ||
$writeStage === MIGRATION_OLD ?
0 : $user->getActorId(),
534 (int)$row->$actorKey,
535 "w=$writeStage, r=$readStage, actor"
541 public static function provideInsertRoundTrip() {
542 $db = wfGetDB( DB_REPLICA
); // for timestamps
550 'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
551 'rc_timestamp' => $db->timestamp(),
553 'rc_title' => 'Test',
554 'rc_this_oldid' => 42,
555 'rc_last_oldid' => 41,
556 'rc_source' => 'test',
558 'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
559 'ipb_range_start' => '',
560 'ipb_range_end' => '',
561 'ipb_timestamp' => $db->timestamp(),
562 'ipb_expiry' => $db->getInfinity(),
564 'revision' => [ 'revision', 'rev_user', 'rev_id', [
568 'rev_timestamp' => $db->timestamp(),
573 public static function provideStages() {
575 'MIGRATION_OLD' => [ MIGRATION_OLD
],
576 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH
],
577 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW
],
578 'MIGRATION_NEW' => [ MIGRATION_NEW
],
583 * @dataProvider provideStages
585 * @expectedException InvalidArgumentException
586 * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
588 public function testInsertWrong( $stage ) {
589 $m = $this->makeMigration( $stage );
590 $m->getInsertValues( $this->db
, 'rev_user', $this->getTestUser()->getUser() );
594 * @dataProvider provideStages
596 * @expectedException InvalidArgumentException
597 * @expectedExceptionMessage Must use getInsertValues() for rc_user
599 public function testInsertWithTempTableWrong( $stage ) {
600 $m = $this->makeMigration( $stage );
601 $m->getInsertValuesWithTempTable( $this->db
, 'rc_user', $this->getTestUser()->getUser() );
605 * @dataProvider provideStages
608 public function testInsertWithTempTableDeprecated( $stage ) {
609 $wrap = TestingAccessWrapper
::newFromClass( ActorMigration
::class );
610 $wrap->formerTempTables +
= [ 'rc_user' => '1.30' ];
612 $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
613 $m = $this->makeMigration( $stage );
614 list( $fields, $callback )
615 = $m->getInsertValuesWithTempTable( $this->db
, 'rc_user', $this->getTestUser()->getUser() );
616 $this->assertTrue( is_callable( $callback ) );
620 * @dataProvider provideStages
622 * @expectedException InvalidArgumentException
623 * @expectedExceptionMessage $extra[rev_timestamp] is not provided
625 public function testInsertWithTempTableCallbackMissingFields( $stage ) {
626 $m = $this->makeMigration( $stage );
627 list( $fields, $callback )
628 = $m->getInsertValuesWithTempTable( $this->db
, 'rev_user', $this->getTestUser()->getUser() );
632 public function testInsertUserIdentity() {
633 $user = $this->getTestUser()->getUser();
634 $userIdentity = $this->getMock( UserIdentity
::class );
635 $userIdentity->method( 'getId' )->willReturn( $user->getId() );
636 $userIdentity->method( 'getName' )->willReturn( $user->getName() );
637 $userIdentity->method( 'getActorId' )->willReturn( 0 );
639 list( $cFields, $cCallback ) = MediaWikiServices
::getInstance()->getCommentStore()
640 ->insertWithTempTable( $this->db
, 'rev_comment', '' );
641 $m = $this->makeMigration( MIGRATION_WRITE_BOTH
);
642 list( $fields, $callback ) =
643 $m->getInsertValuesWithTempTable( $this->db
, 'rev_user', $userIdentity );
648 'rev_timestamp' => $this->db
->timestamp(),
650 $this->db
->insert( 'revision', $extraFields +
$fields, __METHOD__
);
651 $id = $this->db
->insertId();
652 $callback( $id, $extraFields );
655 $qi = Revision
::getQueryInfo();
656 $row = $this->db
->selectRow(
657 $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__
, [], $qi['joins']
659 $this->assertSame( $user->getId(), (int)$row->rev_user
);
660 $this->assertSame( $user->getName(), $row->rev_user_text
);
661 $this->assertSame( $user->getActorId(), (int)$row->rev_actor
);
663 $m = $this->makeMigration( MIGRATION_WRITE_BOTH
);
664 $fields = $m->getInsertValues( $this->db
, 'dummy_user', $userIdentity );
665 $this->assertSame( $user->getId(), $fields['dummy_user'] );
666 $this->assertSame( $user->getName(), $fields['dummy_user_text'] );
667 $this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
670 public function testConstructor() {
671 $m = ActorMigration
::newMigration();
672 $this->assertInstanceOf( ActorMigration
::class, $m );
673 $this->assertSame( $m, ActorMigration
::newMigration() );
677 * @dataProvider provideIsAnon
679 * @param string $isAnon
680 * @param string $isNotAnon
682 public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
683 $m = $this->makeMigration( $stage );
684 $this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
685 $this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
688 public static function provideIsAnon() {
690 'MIGRATION_OLD' => [ MIGRATION_OLD
, 'foo = 0', 'foo != 0' ],
691 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH
, 'foo = 0', 'foo != 0' ],
692 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW
, 'foo = 0', 'foo != 0' ],
693 'MIGRATION_NEW' => [ MIGRATION_NEW
, 'foo IS NULL', 'foo IS NOT NULL' ],