Use RepoGroup service in MovePage
[lhc/web/wiklou.git] / tests / phpunit / includes / MovePageTest.php
1 <?php
2
3 use MediaWiki\Config\ServiceOptions;
4 use MediaWiki\Page\MovePageFactory;
5 use MediaWiki\Permissions\PermissionManager;
6 use Wikimedia\Rdbms\IDatabase;
7 use Wikimedia\Rdbms\LoadBalancer;
8
9 /**
10 * @group Database
11 */
12 class MovePageTest extends MediaWikiTestCase {
13 /**
14 * @param string $class
15 * @return object A mock that throws on any method call
16 */
17 private function getNoOpMock( $class ) {
18 $mock = $this->createMock( $class );
19 $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
20 return $mock;
21 }
22
23 /**
24 * @param LinkTarget $old
25 * @param LinkTarget $new
26 * @param array $params Valid keys are: db, options, nsInfo, wiStore, repoGroup.
27 * options is an indexed array that will overwrite our defaults, not a ServiceOptions, so it
28 * need not contain all keys.
29 * @return MovePage
30 */
31 private function newMovePage( $old, $new, array $params = [] ) : MovePage {
32 $mockLB = $this->createMock( LoadBalancer::class );
33 $mockLB->method( 'getConnection' )
34 ->willReturn( $params['db'] ?? $this->getNoOpMock( IDatabase::class ) );
35 $mockLB->expects( $this->never() )
36 ->method( $this->anythingBut( 'getConnection', '__destruct' ) );
37
38 $mockLocalFile = $this->createMock( LocalFile::class );
39 $mockLocalFile->method( 'exists' )->willReturn( false );
40 $mockLocalFile->expects( $this->never() )
41 ->method( $this->anythingBut( 'exists', 'load', '__destruct' ) );
42
43 $mockLocalRepo = $this->createMock( LocalRepo::class );
44 $mockLocalRepo->method( 'newFile' )->willReturn( $mockLocalFile );
45 $mockLocalRepo->expects( $this->never() )
46 ->method( $this->anythingBut( 'newFile', '__destruct' ) );
47
48 $mockRepoGroup = $this->createMock( RepoGroup::class );
49 $mockRepoGroup->method( 'getLocalRepo' )->willReturn( $mockLocalRepo );
50 $mockRepoGroup->expects( $this->never() )
51 ->method( $this->anythingBut( 'getLocalRepo', '__destruct' ) );
52
53 return new MovePage(
54 $old,
55 $new,
56 new ServiceOptions(
57 MovePageFactory::$constructorOptions,
58 $params['options'] ?? [],
59 [
60 'CategoryCollation' => 'uppercase',
61 'ContentHandlerUseDB' => true,
62 ]
63 ),
64 $mockLB,
65 $params['nsInfo'] ?? $this->getNoOpMock( NamespaceInfo::class ),
66 $params['wiStore'] ?? $this->getNoOpMock( WatchedItemStore::class ),
67 $params['permMgr'] ?? $this->getNoOpMock( PermissionManager::class ),
68 $params['repoGroup'] ?? $mockRepoGroup
69 );
70 }
71
72 public function setUp() {
73 parent::setUp();
74 $this->tablesUsed[] = 'page';
75 $this->tablesUsed[] = 'revision';
76 $this->tablesUsed[] = 'comment';
77 }
78
79 /**
80 * @dataProvider provideIsValidMove
81 * @covers MovePage::isValidMove
82 * @covers MovePage::isValidFileMove
83 */
84 public function testIsValidMove( $old, $new, $error ) {
85 global $wgMultiContentRevisionSchemaMigrationStage;
86 if ( $wgMultiContentRevisionSchemaMigrationStage === SCHEMA_COMPAT_OLD ) {
87 // We can only set this to false with the old schema
88 $this->setMwGlobals( 'wgContentHandlerUseDB', false );
89 }
90 $mp = $this->newMovePage(
91 Title::newFromText( $old ),
92 Title::newFromText( $new ),
93 [ 'options' => [ 'ContentHandlerUseDB' => false ] ]
94 );
95 $status = $mp->isValidMove();
96 if ( $error === true ) {
97 $this->assertTrue( $status->isGood() );
98 } else {
99 $this->assertTrue( $status->hasMessage( $error ) );
100 }
101 }
102
103 /**
104 * This should be kept in sync with TitleTest::provideTestIsValidMoveOperation
105 */
106 public static function provideIsValidMove() {
107 global $wgMultiContentRevisionSchemaMigrationStage;
108 $ret = [
109 // for MovePage::isValidMove
110 [ 'Test', 'Test', 'selfmove' ],
111 [ 'Special:FooBar', 'Test', 'immobile-source-namespace' ],
112 [ 'Test', 'Special:FooBar', 'immobile-target-namespace' ],
113 [ 'Page', 'File:Test.jpg', 'nonfile-cannot-move-to-file' ],
114 // for MovePage::isValidFileMove
115 [ 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ],
116 ];
117 if ( $wgMultiContentRevisionSchemaMigrationStage === SCHEMA_COMPAT_OLD ) {
118 // The error can only occur if $wgContentHandlerUseDB is false, which doesn't work with
119 // the new schema, so omit the test in that case
120 array_push( $ret,
121 [ 'MediaWiki:Common.js', 'Help:Some wikitext page', 'bad-target-model' ] );
122 }
123 return $ret;
124 }
125
126 /**
127 * Integration test to catch regressions like T74870. Taken and modified
128 * from SemanticMediaWiki
129 *
130 * @covers Title::moveTo
131 * @covers MovePage::move
132 */
133 public function testTitleMoveCompleteIntegrationTest() {
134 $this->hideDeprecated( 'Title::moveTo' );
135
136 $oldTitle = Title::newFromText( 'Help:Some title' );
137 WikiPage::factory( $oldTitle )->doEditContent( new WikitextContent( 'foo' ), 'bar' );
138 $newTitle = Title::newFromText( 'Help:Some other title' );
139 $this->assertNull(
140 WikiPage::factory( $newTitle )->getRevision()
141 );
142
143 $this->assertTrue( $oldTitle->moveTo( $newTitle, false, 'test1', true ) );
144 $this->assertNotNull(
145 WikiPage::factory( $oldTitle )->getRevision()
146 );
147 $this->assertNotNull(
148 WikiPage::factory( $newTitle )->getRevision()
149 );
150 }
151
152 /**
153 * Test for the move operation being aborted via the TitleMove hook
154 * @covers MovePage::move
155 */
156 public function testMoveAbortedByTitleMoveHook() {
157 $error = 'Preventing move operation with TitleMove hook.';
158 $this->setTemporaryHook( 'TitleMove',
159 function ( $old, $new, $user, $reason, $status ) use ( $error ) {
160 $status->fatal( $error );
161 }
162 );
163
164 $oldTitle = Title::newFromText( 'Some old title' );
165 WikiPage::factory( $oldTitle )->doEditContent( new WikitextContent( 'foo' ), 'bar' );
166 $newTitle = Title::newFromText( 'A brand new title' );
167 $mp = $this->newMovePage( $oldTitle, $newTitle );
168 $user = User::newFromName( 'TitleMove tester' );
169 $status = $mp->move( $user, 'Reason', true );
170 $this->assertTrue( $status->hasMessage( $error ) );
171 }
172
173 /**
174 * Test moving subpages from one page to another
175 * @covers MovePage::moveSubpages
176 */
177 public function testMoveSubpages() {
178 $name = ucfirst( __FUNCTION__ );
179
180 $subPages = [ "Talk:$name/1", "Talk:$name/2" ];
181 $ids = [];
182 $pages = [
183 $name,
184 "Talk:$name",
185 "$name 2",
186 "Talk:$name 2",
187 ];
188 foreach ( array_merge( $pages, $subPages ) as $page ) {
189 $ids[$page] = $this->createPage( $page );
190 }
191
192 $oldTitle = Title::newFromText( "Talk:$name" );
193 $newTitle = Title::newFromText( "Talk:$name 2" );
194 $mp = new MovePage( $oldTitle, $newTitle );
195 $status = $mp->moveSubpages( $this->getTestUser()->getUser(), 'Reason', true );
196
197 $this->assertTrue( $status->isGood(),
198 "Moving subpages from Talk:{$name} to Talk:{$name} 2 was not completely successful." );
199 foreach ( $subPages as $page ) {
200 $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
201 }
202 }
203
204 /**
205 * Test moving subpages from one page to another
206 * @covers MovePage::moveSubpagesIfAllowed
207 */
208 public function testMoveSubpagesIfAllowed() {
209 $name = ucfirst( __FUNCTION__ );
210
211 $subPages = [ "Talk:$name/1", "Talk:$name/2" ];
212 $ids = [];
213 $pages = [
214 $name,
215 "Talk:$name",
216 "$name 2",
217 "Talk:$name 2",
218 ];
219 foreach ( array_merge( $pages, $subPages ) as $page ) {
220 $ids[$page] = $this->createPage( $page );
221 }
222
223 $oldTitle = Title::newFromText( "Talk:$name" );
224 $newTitle = Title::newFromText( "Talk:$name 2" );
225 $mp = new MovePage( $oldTitle, $newTitle );
226 $status = $mp->moveSubpagesIfAllowed( $this->getTestUser()->getUser(), 'Reason', true );
227
228 $this->assertTrue( $status->isGood(),
229 "Moving subpages from Talk:{$name} to Talk:{$name} 2 was not completely successful." );
230 foreach ( $subPages as $page ) {
231 $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
232 }
233 }
234
235 /**
236 * Shortcut function to create a page and return its id.
237 *
238 * @param string $name Page to create
239 * @return int ID of created page
240 */
241 protected function createPage( $name ) {
242 return $this->editPage( $name, 'Content' )->value['revision']->getPage();
243 }
244
245 /**
246 * @param string $from Prefixed name of source
247 * @param string $to Prefixed name of destination
248 * @param string $id Page id of the page to move
249 * @param array|string|null $opts Options: 'noredirect' to expect no redirect
250 */
251 protected function assertMoved( $from, $to, $id, $opts = null ) {
252 $opts = (array)$opts;
253
254 Title::clearCaches();
255 $fromTitle = Title::newFromText( $from );
256 $toTitle = Title::newFromText( $to );
257
258 $this->assertTrue( $toTitle->exists(),
259 "Destination {$toTitle->getPrefixedText()} does not exist" );
260
261 if ( in_array( 'noredirect', $opts ) ) {
262 $this->assertFalse( $fromTitle->exists(),
263 "Source {$fromTitle->getPrefixedText()} exists" );
264 } else {
265 $this->assertTrue( $fromTitle->exists(),
266 "Source {$fromTitle->getPrefixedText()} does not exist" );
267 $this->assertTrue( $fromTitle->isRedirect(),
268 "Source {$fromTitle->getPrefixedText()} is not a redirect" );
269
270 $target = Revision::newFromTitle( $fromTitle )->getContent()->getRedirectTarget();
271 $this->assertSame( $toTitle->getPrefixedText(), $target->getPrefixedText() );
272 }
273
274 $this->assertSame( $id, $toTitle->getArticleID() );
275 }
276 }