3 use MediaWiki\User\UserIdentity
;
4 use Wikimedia\TestingAccessWrapper
;
8 * @covers ActorMigration
10 class ActorMigrationTest
extends MediaWikiLangTestCase
{
12 protected $tablesUsed = [
14 'revision_actor_temp',
21 * Create an ActorMigration for a particular stage
23 * @return ActorMigration
25 protected function makeMigration( $stage ) {
26 return new ActorMigration( $stage );
30 * @dataProvider provideGetJoin
33 * @param array $expect
35 public function testGetJoin( $stage, $key, $expect ) {
36 $m = $this->makeMigration( $stage );
37 $result = $m->getJoin( $key );
38 $this->assertEquals( $expect, $result );
41 public static function provideGetJoin() {
43 'Simple table, old' => [
44 MIGRATION_OLD
, 'rc_user', [
47 'rc_user' => 'rc_user',
48 'rc_user_text' => 'rc_user_text',
54 'Simple table, write-both' => [
55 MIGRATION_WRITE_BOTH
, 'rc_user', [
56 'tables' => [ 'actor_rc_user' => 'actor' ],
58 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
59 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
60 'rc_actor' => 'rc_actor',
63 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
67 'Simple table, write-new' => [
68 MIGRATION_WRITE_NEW
, 'rc_user', [
69 'tables' => [ 'actor_rc_user' => 'actor' ],
71 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
72 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
73 'rc_actor' => 'rc_actor',
76 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
80 'Simple table, new' => [
81 MIGRATION_NEW
, 'rc_user', [
82 'tables' => [ 'actor_rc_user' => 'actor' ],
84 'rc_user' => 'actor_rc_user.actor_user',
85 'rc_user_text' => 'actor_rc_user.actor_name',
86 'rc_actor' => 'rc_actor',
89 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
95 MIGRATION_OLD
, 'ipb_by', [
99 'ipb_by_text' => 'ipb_by_text',
100 'ipb_by_actor' => 'NULL',
105 'ipblocks, write-both' => [
106 MIGRATION_WRITE_BOTH
, 'ipb_by', [
107 'tables' => [ 'actor_ipb_by' => 'actor' ],
109 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
110 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
111 'ipb_by_actor' => 'ipb_by_actor',
114 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
118 'ipblocks, write-new' => [
119 MIGRATION_WRITE_NEW
, 'ipb_by', [
120 'tables' => [ 'actor_ipb_by' => 'actor' ],
122 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
123 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
124 'ipb_by_actor' => 'ipb_by_actor',
127 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
132 MIGRATION_NEW
, 'ipb_by', [
133 'tables' => [ 'actor_ipb_by' => 'actor' ],
135 'ipb_by' => 'actor_ipb_by.actor_user',
136 'ipb_by_text' => 'actor_ipb_by.actor_name',
137 'ipb_by_actor' => 'ipb_by_actor',
140 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
146 MIGRATION_OLD
, 'rev_user', [
149 'rev_user' => 'rev_user',
150 'rev_user_text' => 'rev_user_text',
151 'rev_actor' => 'NULL',
156 'Revision, write-both' => [
157 MIGRATION_WRITE_BOTH
, 'rev_user', [
159 'temp_rev_user' => 'revision_actor_temp',
160 'actor_rev_user' => 'actor',
163 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
164 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
165 'rev_actor' => 'temp_rev_user.revactor_actor',
168 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
169 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
173 'Revision, write-new' => [
174 MIGRATION_WRITE_NEW
, 'rev_user', [
176 'temp_rev_user' => 'revision_actor_temp',
177 'actor_rev_user' => 'actor',
180 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
181 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
182 'rev_actor' => 'temp_rev_user.revactor_actor',
185 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
186 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
191 MIGRATION_NEW
, 'rev_user', [
193 'temp_rev_user' => 'revision_actor_temp',
194 'actor_rev_user' => 'actor',
197 'rev_user' => 'actor_rev_user.actor_user',
198 'rev_user_text' => 'actor_rev_user.actor_name',
199 'rev_actor' => 'temp_rev_user.revactor_actor',
202 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
203 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
211 * @dataProvider provideGetWhere
214 * @param UserIdentity[] $users
216 * @param array $expect
218 public function testGetWhere( $stage, $key, $users, $useId, $expect ) {
219 $expect['conds'] = '(' . implode( ') OR (', $expect['orconds'] ) . ')';
221 if ( count( $users ) === 1 ) {
222 $users = reset( $users );
225 $m = $this->makeMigration( $stage );
226 $result = $m->getWhere( $this->db
, $key, $users, $useId );
227 $this->assertEquals( $expect, $result );
230 public function provideGetWhere() {
231 $makeUserIdentity = function ( $id, $name, $actor ) {
232 $u = $this->getMock( UserIdentity
::class );
233 $u->method( 'getId' )->willReturn( $id );
234 $u->method( 'getName' )->willReturn( $name );
235 $u->method( 'getActorId' )->willReturn( $actor );
239 $genericUser = [ $makeUserIdentity( 1, 'User1', 11 ) ];
240 $complicatedUsers = [
241 $makeUserIdentity( 1, 'User1', 11 ),
242 $makeUserIdentity( 2, 'User2', 12 ),
243 $makeUserIdentity( 3, 'User3', 0 ),
244 $makeUserIdentity( 0, '192.168.12.34', 34 ),
245 $makeUserIdentity( 0, '192.168.12.35', 0 ),
249 'Simple table, old' => [
250 MIGRATION_OLD
, 'rc_user', $genericUser, true, [
252 'orconds' => [ 'userid' => "rc_user = '1'" ],
256 'Simple table, write-both' => [
257 MIGRATION_WRITE_BOTH
, 'rc_user', $genericUser, true, [
260 'actor' => "rc_actor = '11'",
261 'userid' => "rc_actor = '0' AND rc_user = '1'"
266 'Simple table, write-new' => [
267 MIGRATION_WRITE_NEW
, 'rc_user', $genericUser, true, [
270 'actor' => "rc_actor = '11'",
271 'userid' => "rc_actor = '0' AND rc_user = '1'"
276 'Simple table, new' => [
277 MIGRATION_NEW
, 'rc_user', $genericUser, true, [
279 'orconds' => [ 'actor' => "rc_actor = '11'" ],
285 MIGRATION_OLD
, 'ipb_by', $genericUser, true, [
287 'orconds' => [ 'userid' => "ipb_by = '1'" ],
291 'ipblocks, write-both' => [
292 MIGRATION_WRITE_BOTH
, 'ipb_by', $genericUser, true, [
295 'actor' => "ipb_by_actor = '11'",
296 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
301 'ipblocks, write-new' => [
302 MIGRATION_WRITE_NEW
, 'ipb_by', $genericUser, true, [
305 'actor' => "ipb_by_actor = '11'",
306 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
312 MIGRATION_NEW
, 'ipb_by', $genericUser, true, [
314 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
320 MIGRATION_OLD
, 'rev_user', $genericUser, true, [
322 'orconds' => [ 'userid' => "rev_user = '1'" ],
326 'Revision, write-both' => [
327 MIGRATION_WRITE_BOTH
, 'rev_user', $genericUser, true, [
329 'temp_rev_user' => 'revision_actor_temp',
333 "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
334 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
337 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
341 'Revision, write-new' => [
342 MIGRATION_WRITE_NEW
, 'rev_user', $genericUser, true, [
344 'temp_rev_user' => 'revision_actor_temp',
348 "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
349 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
352 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
357 MIGRATION_NEW
, 'rev_user', $genericUser, true, [
359 'temp_rev_user' => 'revision_actor_temp',
361 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
363 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
368 'Multiple users, old' => [
369 MIGRATION_OLD
, 'rc_user', $complicatedUsers, true, [
372 'userid' => "rc_user IN ('1','2','3') ",
373 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
378 'Multiple users, write-both' => [
379 MIGRATION_WRITE_BOTH
, 'rc_user', $complicatedUsers, true, [
382 'actor' => "rc_actor IN ('11','12','34') ",
383 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
384 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
389 'Multiple users, write-new' => [
390 MIGRATION_WRITE_NEW
, 'rc_user', $complicatedUsers, true, [
393 'actor' => "rc_actor IN ('11','12','34') ",
394 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
395 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
400 'Multiple users, new' => [
401 MIGRATION_NEW
, 'rc_user', $complicatedUsers, true, [
403 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
408 'Multiple users, no use ID, old' => [
409 MIGRATION_OLD
, 'rc_user', $complicatedUsers, false, [
412 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
417 'Multiple users, write-both' => [
418 MIGRATION_WRITE_BOTH
, 'rc_user', $complicatedUsers, false, [
421 'actor' => "rc_actor IN ('11','12','34') ",
422 'username' => "rc_actor = '0' AND "
423 . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
428 'Multiple users, write-new' => [
429 MIGRATION_WRITE_NEW
, 'rc_user', $complicatedUsers, false, [
432 'actor' => "rc_actor IN ('11','12','34') ",
433 'username' => "rc_actor = '0' AND "
434 . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
439 'Multiple users, new' => [
440 MIGRATION_NEW
, 'rc_user', $complicatedUsers, false, [
442 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
450 * @dataProvider provideInsertRoundTrip
451 * @param string $table
454 * @param array $extraFields
456 public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
457 $u = $this->getTestUser()->getUser();
458 $user = $this->getMock( UserIdentity
::class );
459 $user->method( 'getId' )->willReturn( $u->getId() );
460 $user->method( 'getName' )->willReturn( $u->getName() );
461 if ( $u->getActorId( $this->db
) ) {
462 $user->method( 'getActorId' )->willReturn( $u->getActorId() );
466 [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
469 $user->method( 'getActorId' )->willReturn( $this->db
->insertId() );
473 MIGRATION_OLD
=> [ MIGRATION_OLD
, MIGRATION_WRITE_NEW
],
474 MIGRATION_WRITE_BOTH
=> [ MIGRATION_OLD
, MIGRATION_NEW
],
475 MIGRATION_WRITE_NEW
=> [ MIGRATION_WRITE_BOTH
, MIGRATION_NEW
],
476 MIGRATION_NEW
=> [ MIGRATION_WRITE_BOTH
, MIGRATION_NEW
],
479 $nameKey = $key . '_text';
480 $actorKey = $key === 'ipb_by' ?
'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
482 foreach ( $stages as $writeStage => $readRange ) {
483 if ( $key === 'ipb_by' ) {
484 $extraFields['ipb_address'] = __CLASS__
. "#$writeStage";
487 $w = $this->makeMigration( $writeStage );
488 $usesTemp = $key === 'rev_user';
491 list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db
, $key, $user );
493 $fields = $w->getInsertValues( $this->db
, $key, $user );
496 if ( $writeStage <= MIGRATION_WRITE_BOTH
) {
497 $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
498 $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
500 $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
501 $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
503 if ( $writeStage >= MIGRATION_WRITE_BOTH
&& !$usesTemp ) {
504 $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
506 $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
509 $this->db
->insert( $table, $extraFields +
$fields, __METHOD__
);
510 $id = $this->db
->insertId();
512 $callback( $id, $extraFields );
515 for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++
) {
516 $r = $this->makeMigration( $readStage );
518 $queryInfo = $r->getJoin( $key );
519 $row = $this->db
->selectRow(
520 [ $table ] +
$queryInfo['tables'],
521 $queryInfo['fields'],
528 $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
529 $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
531 $readStage === MIGRATION_OLD ||
$writeStage === MIGRATION_OLD ?
0 : $user->getActorId(),
532 (int)$row->$actorKey,
533 "w=$writeStage, r=$readStage, actor"
539 public static function provideInsertRoundTrip() {
540 $db = wfGetDB( DB_REPLICA
); // for timestamps
548 'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
549 'rc_timestamp' => $db->timestamp(),
551 'rc_title' => 'Test',
552 'rc_this_oldid' => 42,
553 'rc_last_oldid' => 41,
554 'rc_source' => 'test',
556 'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
557 'ipb_range_start' => '',
558 'ipb_range_end' => '',
559 'ipb_timestamp' => $db->timestamp(),
560 'ipb_expiry' => $db->getInfinity(),
562 'revision' => [ 'revision', 'rev_user', 'rev_id', [
566 'rev_timestamp' => $db->timestamp(),
571 public static function provideStages() {
573 'MIGRATION_OLD' => [ MIGRATION_OLD
],
574 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH
],
575 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW
],
576 'MIGRATION_NEW' => [ MIGRATION_NEW
],
581 * @dataProvider provideStages
583 * @expectedException InvalidArgumentException
584 * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
586 public function testInsertWrong( $stage ) {
587 $m = $this->makeMigration( $stage );
588 $m->getInsertValues( $this->db
, 'rev_user', $this->getTestUser()->getUser() );
592 * @dataProvider provideStages
594 * @expectedException InvalidArgumentException
595 * @expectedExceptionMessage Must use getInsertValues() for rc_user
597 public function testInsertWithTempTableWrong( $stage ) {
598 $m = $this->makeMigration( $stage );
599 $m->getInsertValuesWithTempTable( $this->db
, 'rc_user', $this->getTestUser()->getUser() );
603 * @dataProvider provideStages
606 public function testInsertWithTempTableDeprecated( $stage ) {
607 $wrap = TestingAccessWrapper
::newFromClass( ActorMigration
::class );
608 $wrap->formerTempTables +
= [ 'rc_user' => '1.30' ];
610 $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
611 $m = $this->makeMigration( $stage );
612 list( $fields, $callback )
613 = $m->getInsertValuesWithTempTable( $this->db
, 'rc_user', $this->getTestUser()->getUser() );
614 $this->assertTrue( is_callable( $callback ) );
618 * @dataProvider provideStages
620 * @expectedException InvalidArgumentException
621 * @expectedExceptionMessage $extra[rev_timestamp] is not provided
623 public function testInsertWithTempTableCallbackMissingFields( $stage ) {
624 $m = $this->makeMigration( $stage );
625 list( $fields, $callback )
626 = $m->getInsertValuesWithTempTable( $this->db
, 'rev_user', $this->getTestUser()->getUser() );
630 public function testInsertUserIdentity() {
631 $user = $this->getTestUser()->getUser();
632 $userIdentity = $this->getMock( UserIdentity
::class );
633 $userIdentity->method( 'getId' )->willReturn( $user->getId() );
634 $userIdentity->method( 'getName' )->willReturn( $user->getName() );
635 $userIdentity->method( 'getActorId' )->willReturn( 0 );
637 list( $cFields, $cCallback ) = CommentStore
::newKey( 'rev_comment' )
638 ->insertWithTempTable( $this->db
, '' );
639 $m = $this->makeMigration( MIGRATION_WRITE_BOTH
);
640 list( $fields, $callback ) =
641 $m->getInsertValuesWithTempTable( $this->db
, 'rev_user', $userIdentity );
646 'rev_timestamp' => $this->db
->timestamp(),
648 $this->db
->insert( 'revision', $extraFields +
$fields, __METHOD__
);
649 $id = $this->db
->insertId();
650 $callback( $id, $extraFields );
653 $qi = Revision
::getQueryInfo();
654 $row = $this->db
->selectRow(
655 $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__
, [], $qi['joins']
657 $this->assertSame( $user->getId(), (int)$row->rev_user
);
658 $this->assertSame( $user->getName(), $row->rev_user_text
);
659 $this->assertSame( $user->getActorId(), (int)$row->rev_actor
);
661 $m = $this->makeMigration( MIGRATION_WRITE_BOTH
);
662 $fields = $m->getInsertValues( $this->db
, 'dummy_user', $userIdentity );
663 $this->assertSame( $user->getId(), $fields['dummy_user'] );
664 $this->assertSame( $user->getName(), $fields['dummy_user_text'] );
665 $this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
668 public function testConstructor() {
669 $m = ActorMigration
::newMigration();
670 $this->assertInstanceOf( ActorMigration
::class, $m );
671 $this->assertSame( $m, ActorMigration
::newMigration() );
675 * @dataProvider provideIsAnon
677 * @param string $isAnon
678 * @param string $isNotAnon
680 public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
681 $m = $this->makeMigration( $stage );
682 $this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
683 $this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
686 public static function provideIsAnon() {
688 'MIGRATION_OLD' => [ MIGRATION_OLD
, 'foo = 0', 'foo != 0' ],
689 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH
, 'foo = 0', 'foo != 0' ],
690 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW
, 'foo = 0', 'foo != 0' ],
691 'MIGRATION_NEW' => [ MIGRATION_NEW
, 'foo IS NULL', 'foo IS NOT NULL' ],