overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*0 of 0 rows updated/' ); $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); } public static function provideCapitalLinksValues() { return [ '$wgCapitalLinks is false' => [ false, 'Will be moving pages to first letter lowercase titles', ], '$wgCapitalLinks is true' => [ true, 'Will be moving pages to first letter capitalized titles', ], ]; } /** @dataProvider provideCapitalLinksValues */ public function testWhenPagesExistButDoNotNeedMoving( bool $capitalLinks, string $stringToExpectInOutput ) { // Generate a test page which does not need moving $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $titleBeforeCall = 'test'; if ( $capitalLinks ) { $titleBeforeCall = ucfirst( $titleBeforeCall ); } $this->getExistingTestPage( Title::newFromText( $titleBeforeCall ) ); // Verify that the DB has been set up correctly for this test, in case Title::newFromText does // normalisation that causes the title to be in a different form to what we want. $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValue( $titleBeforeCall ); // Run the maintenance script $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Assert on the output, checking that the script says the page are already fixed. $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*0 of.*rows updated/' ); $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); if ( $capitalLinks ) { $expectedMoveLineOutput = '"Test" already uppercase'; } else { $expectedMoveLineOutput = '"test" already lowercase'; } $this->assertStringContainsString( $expectedMoveLineOutput, $actualOutput ); // Expect that no moves have occurred, as the page were already in the correct form $this->newSelectQueryBuilder() ->select( 'COUNT(*)' ) ->from( 'logging' ) ->join( 'actor', null, 'actor_id=log_actor' ) ->where( [ 'actor_name' => 'Conversion script' ] ) ->caller( __METHOD__ ) ->assertFieldValue( 0 ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValue( $titleBeforeCall ); } /** @dataProvider provideCapitalLinksValues */ public function testWhenPagesExistAndNeedToBeMoved( bool $capitalLinks, string $stringToExpectInOutput ) { // Generate three testing titles when $wgCapitalLinks is reversed from the value // provided in $capitalLinks (so we can test that the script does convert). $titles = [ 'a', 'test', 'test_abc' ]; $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks ); if ( $capitalLinks ) { $titlesBeforeCall = array_map( 'lcfirst', $titles ); } else { $titlesBeforeCall = array_map( 'ucfirst', $titles ); } foreach ( $titlesBeforeCall as $title ) { $this->getExistingTestPage( Title::newFromText( $title ) ); } // Check that the titles in the DB have the correct form (either uppercase or lowercase first character), // as Title::newFromText could have modified this. $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( $titlesBeforeCall ); // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion. $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Assert that the script outputs it has renamed all of the pages $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*3 of.*rows updated/' ); $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); foreach ( $titles as $title ) { $title = str_replace( '_', ' ', $title ); if ( $capitalLinks ) { $originTitle = lcfirst( $title ); $destTitle = ucfirst( $title ); } else { $originTitle = ucfirst( $title ); $destTitle = lcfirst( $title ); } $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": OK", $actualOutput ); } // Assert that the pages have actually been moved in the DB $this->newSelectQueryBuilder() ->select( 'COUNT(*)' ) ->from( 'logging' ) ->join( 'actor', null, 'actor_id=log_actor' ) ->where( [ 'actor_name' => 'Conversion script' ] ) ->caller( __METHOD__ ) ->assertFieldValue( 3 ); if ( $capitalLinks ) { $expectedTitles = array_map( 'ucfirst', $titles ); } else { // When $wgCapitalLinks is false, redirects are left from the old name // as they are still valid titles. $expectedTitles = array_merge( array_map( 'ucfirst', $titles ), array_map( 'lcfirst', $titles ) ); } $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( $expectedTitles ); } /** @dataProvider provideCapitalLinksValues */ public function testWhenPageExistsWithTalkPageThatNeedsMove( bool $capitalLinks, string $stringToExpectInOutput ) { // Generate a page and an associated talk page that will need to be moved. $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks ); $originTitle = 'abc'; if ( !$capitalLinks ) { $originTitle = ucfirst( $originTitle ); } $this->getExistingTestPage( Title::newFromText( $originTitle ) ); $this->getExistingTestPage( Title::newFromText( $originTitle, NS_TALK ) ); // Verify that the page_title column is set up correctly for the test. $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ $originTitle, $originTitle ] ); // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion. $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Assert that the script says both the mainspace page and it's associated talk page have moved. $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*2 of.*rows updated/' ); $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle ); $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": OK", $actualOutput ); $this->assertStringContainsString( "\"Talk:$originTitle\" -> \"Talk:$destTitle\": OK", $actualOutput ); // Assert that what the script says it has done has been applied in the DB $this->newSelectQueryBuilder() ->select( 'COUNT(*)' ) ->from( 'logging' ) ->join( 'actor', null, 'actor_id=log_actor' ) ->where( [ 'actor_name' => 'Conversion script' ] ) ->caller( __METHOD__ ) ->assertFieldValue( 2 ); if ( $capitalLinks ) { $expectedTitles = [ $destTitle, $destTitle ]; } else { // When $wgCapitalLinks is false, redirects are left from the old name // as they are still valid titles. $expectedTitles = [ $originTitle, $originTitle, $destTitle, $destTitle ]; } $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValues( $expectedTitles ); } /** @dataProvider provideCapitalLinksValues */ public function testWhenPagesExistForDryRun( bool $capitalLinks, string $stringToExpectInOutput ) { // Generate a page that needs moving $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks ); $originTitle = 'abc'; if ( !$capitalLinks ) { $originTitle = ucfirst( $originTitle ); } $this->getExistingTestPage( Title::newFromText( $originTitle ) ); $this->getExistingTestPage( Title::newFromText( $originTitle, NS_TALK ) ); // Verify the page_title column before running the script as we want to assert no change by the // script, so need to know the form before calling the script. $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ $originTitle, $originTitle ] ); // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion. $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->setOption( 'dry-run', 1 ); $this->maintenance->execute(); // Expect that the script processes the pages, but does not move them as it's a dry run. $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*2 of.*rows updated/' ); $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle ); $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": DRY RUN, NOT MOVED", $actualOutput ); $this->assertStringContainsString( "\"Talk:$originTitle\" -> \"Talk:$destTitle\": DRY RUN, NOT MOVED", $actualOutput ); // Expect that the DB is untouched as this was a dry-run. $this->newSelectQueryBuilder() ->select( 'COUNT(*)' ) ->from( 'logging' ) ->join( 'actor', null, 'actor_id=log_actor' ) ->where( [ 'actor_name' => 'Conversion script' ] ) ->caller( __METHOD__ ) ->assertFieldValue( 0 ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ $originTitle, $originTitle ] ); } /** @dataProvider provideCapitalLinksValues */ public function testWhenMovePageIsValidMoveHookDeniesMove( bool $capitalLinks, string $stringToExpectInOutput ) { // Generate a page that needs moving $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks ); $originTitle = 'abc'; if ( !$capitalLinks ) { $originTitle = ucfirst( $originTitle ); } $this->getExistingTestPage( Title::newFromText( $originTitle ) ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValue( $originTitle ); // Add a hook for MovePageIsValidMove that denies all attempts to move pages $this->setTemporaryHook( 'MovePageIsValidMove', static function ( $unusedOne, $unusedTwo, StatusValue $status ) { $status->fatal( 'test-status' ); } ); // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion. $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Assert that the output indicates the move failed $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput ); $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle ); $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": FAILED", $actualOutput ); // Expect that the titles have not been moved because the move was disallowed $this->newSelectQueryBuilder() ->select( 'COUNT(*)' ) ->from( 'logging' ) ->join( 'actor', null, 'actor_id=log_actor' ) ->where( [ 'actor_name' => 'Conversion script' ] ) ->caller( __METHOD__ ) ->assertFieldValue( 0 ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => [ 0, 1 ] ] ) ->caller( __METHOD__ ) ->assertFieldValue( $originTitle ); } public function testWhenToLowercaseFindsNewTitleAlreadyExists() { // Get two pages which have the same name except for the capitalisation of the first character. $this->overrideConfigValue( MainConfigNames::CapitalLinks, false ); $this->getExistingTestPage( Title::newFromText( 'test' ) ); $this->getExistingTestPage( Title::newFromText( 'Test' ) ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ 'Test', 'test' ] ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Check that the maintenance script skipped the uppercase page because the lowercase version already exists. $this->expectOutputRegex( '/"Test" skipped; "test" already exists/' ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ 'Test', 'test' ] ); } public function testWhenToUppercaseFindsNewTitleAlreadyExists() { // Get two pages which have the same name except for the capitalisation of the first character. $this->overrideConfigValue( MainConfigNames::CapitalLinks, false ); $this->getExistingTestPage( Title::newFromText( 'test' ) ); $this->getExistingTestPage( Title::newFromText( 'Test' ) ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ 'Test', 'test' ] ); $this->overrideConfigValue( MainConfigNames::CapitalLinks, true ); $this->maintenance->setOption( 'namespace', 0 ); $this->maintenance->execute(); // Check that the lowercase version is renamed to 'CleanupCaps/test' because the uppercase version already // exists. This is done to ensure that the title is not considered invalid by the Title class $actualOutput = $this->getActualOutputForAssertion(); $this->assertStringContainsString( "\"test\" -> \"CapsCleanup/test\": OK", $actualOutput ); $this->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->where( [ 'page_namespace' => 0 ] ) ->caller( __METHOD__ ) ->assertFieldValues( [ 'CapsCleanup/test', 'Test' ] ); } }