Merge ".mailmap: Correct two contributor names"
[mediawiki.git] / tests / phpunit / includes / page / MovePageTest.php
blob7d0616492cb8deb866a5db670247ef5e928930ef
1 <?php
3 use MediaWiki\Config\ServiceOptions;
4 use MediaWiki\Content\WikitextContent;
5 use MediaWiki\Interwiki\InterwikiLookup;
6 use MediaWiki\MainConfigNames;
7 use MediaWiki\MediaWikiServices;
8 use MediaWiki\Page\MovePage;
9 use MediaWiki\Revision\RevisionRecord;
10 use MediaWiki\Revision\SlotRecord;
11 use MediaWiki\Storage\PageUpdatedEvent;
12 use MediaWiki\Tests\Language\LanguageEventIngressSpyTrait;
13 use MediaWiki\Tests\recentchanges\ChangeTrackingEventIngressSpyTrait;
14 use MediaWiki\Tests\Rest\Handler\MediaTestTrait;
15 use MediaWiki\Tests\Search\SearchEventIngressSpyTrait;
16 use MediaWiki\Tests\Unit\DummyServicesTrait;
17 use MediaWiki\Title\Title;
18 use MediaWiki\User\User;
19 use MediaWiki\Watchlist\WatchedItemStore;
20 use PHPUnit\Framework\Assert;
21 use Wikimedia\Rdbms\IConnectionProvider;
22 use Wikimedia\Rdbms\IDatabase;
23 use Wikimedia\Rdbms\IDBAccessObject;
25 /**
26 * @covers \MediaWiki\Page\MovePage
27 * @group Database
29 class MovePageTest extends MediaWikiIntegrationTestCase {
30 use DummyServicesTrait;
31 use MediaTestTrait;
32 use ChangeTrackingEventIngressSpyTrait;
33 use SearchEventIngressSpyTrait;
34 use LanguageEventIngressSpyTrait;
36 /**
37 * @param Title $old
38 * @param Title $new
39 * @param array $params Valid keys are: db, options,
40 * options is an indexed array that will overwrite our defaults, not a ServiceOptions, so it
41 * need not contain all keys.
42 * @return MovePage
44 private function newMovePageWithMocks( $old, $new, array $params = [] ): MovePage {
45 $mockProvider = $this->createNoOpMock( IConnectionProvider::class, [ 'getPrimaryDatabase' ] );
46 $mockProvider->method( 'getPrimaryDatabase' )
47 ->willReturn( $params['db'] ?? $this->createNoOpMock( IDatabase::class ) );
49 return new MovePage(
50 $old,
51 $new,
52 new ServiceOptions(
53 MovePage::CONSTRUCTOR_OPTIONS,
54 $params['options'] ?? [],
56 MainConfigNames::CategoryCollation => 'uppercase',
57 MainConfigNames::MaximumMovedPages => 100,
60 $mockProvider,
61 $this->getDummyNamespaceInfo(),
62 $this->createMock( WatchedItemStore::class ),
63 $this->makeMockRepoGroup(
64 [ 'Existent.jpg', 'Existent2.jpg', 'Existent-file-no-page.jpg' ]
66 $this->getServiceContainer()->getContentHandlerFactory(),
67 $this->getServiceContainer()->getRevisionStore(),
68 $this->getServiceContainer()->getSpamChecker(),
69 $this->getServiceContainer()->getHookContainer(),
70 $this->getServiceContainer()->getWikiPageFactory(),
71 $this->getServiceContainer()->getUserFactory(),
72 $this->getServiceContainer()->getUserEditTracker(),
73 $this->getServiceContainer()->getMovePageFactory(),
74 $this->getServiceContainer()->getCollationFactory(),
75 $this->getServiceContainer()->getPageUpdaterFactory(),
76 $this->getServiceContainer()->getRestrictionStore(),
77 $this->getServiceContainer()->getDeletePageFactory(),
78 $this->getServiceContainer()->getLogFormatterFactory()
82 protected function setUp(): void {
83 parent::setUp();
85 // To avoid problems with namespace localization
86 $this->overrideConfigValue( MainConfigNames::LanguageCode, 'en' );
88 // Ensure we have some pages that are guaranteed to exist or not
89 $this->getExistingTestPage( 'Existent' );
90 $this->getExistingTestPage( 'Existent2' );
91 $this->getExistingTestPage( 'File:Existent.jpg' );
92 $this->getExistingTestPage( 'File:Existent2.jpg' );
93 $this->getExistingTestPage( 'File:Non-file.jpg' );
94 // Special treatment as we can't just add wikitext to a JS page
95 $this->insertPage( 'MediaWiki:Existent.js', '// Hello this is JavaScript!' );
96 $this->getExistingTestPage( 'Hooked in place' );
97 $this->getNonexistingTestPage( 'Nonexistent' );
98 $this->getNonexistingTestPage( 'Nonexistent2' );
99 $this->getNonexistingTestPage( 'File:Nonexistent.jpg' );
100 $this->getNonexistingTestPage( 'File:Nonexistent.png' );
101 $this->getNonexistingTestPage( 'File:Existent-file-no-page.jpg' );
102 $this->getNonexistingTestPage( 'MediaWiki:Nonexistent' );
103 $this->getNonexistingTestPage( 'No content allowed' );
105 // Set a couple of hooks for specific pages
106 $this->setTemporaryHook( 'ContentModelCanBeUsedOn',
107 static function ( $modelId, Title $title, &$ok ) {
108 if ( $title->getPrefixedText() === 'No content allowed' ) {
109 $ok = false;
114 $this->setTemporaryHook( 'TitleIsMovable',
115 static function ( Title $title, &$result ) {
116 if ( strtolower( $title->getPrefixedText() ) === 'hooked in place' ) {
117 $result = false;
124 * @dataProvider provideIsValidMove
125 * @covers \MediaWiki\Page\MovePage::isValidMove
126 * @covers \MediaWiki\Page\MovePage::isValidMoveTarget
127 * @covers \MediaWiki\Page\MovePage::isValidFileMove
128 * @covers \MediaWiki\Page\MovePage::__construct
130 * @param string|Title $old
131 * @param string|Title $new
132 * @param StatusValue $expectedStatus
133 * @param array $extraOptions
135 public function testIsValidMove(
136 $old, $new, StatusValue $expectedStatus, array $extraOptions = []
138 $iwLookup = $this->createMock( InterwikiLookup::class );
139 $iwLookup->method( 'isValidInterwiki' )
140 ->willReturn( true );
142 $this->setService( 'InterwikiLookup', $iwLookup );
144 $old = $old instanceof Title ? $old : Title::newFromText( $old );
145 $new = $new instanceof Title ? $new : Title::newFromText( $new );
146 $mp = $this->newMovePageWithMocks( $old, $new, [ 'options' => $extraOptions ] );
147 $this->assertStatusMessagesExactly(
148 $expectedStatus,
149 $mp->isValidMove()
153 public static function provideIsValidMove() {
154 $ret = [
155 'Valid move with redirect' => [
156 'Existent',
157 'Nonexistent',
158 StatusValue::newGood(),
159 [ 'createRedirect' => true ]
161 'Valid move without redirect' => [
162 'Existent',
163 'Nonexistent',
164 StatusValue::newGood(),
165 [ 'createRedirect' => false ]
167 'Self move' => [
168 'Existent',
169 'Existent',
170 StatusValue::newFatal( 'selfmove' ),
172 'Move from empty name' => [
173 Title::makeTitle( NS_MAIN, '' ),
174 'Nonexistent',
175 // @todo More specific error message, or make the move valid if the page actually
176 // exists somehow in the database
177 StatusValue::newFatal( 'badarticleerror' ),
179 'Move to empty name' => [
180 'Existent',
181 Title::makeTitle( NS_MAIN, '' ),
182 StatusValue::newFatal( 'movepage-invalid-target-title' ),
184 'Move to invalid name' => [
185 'Existent',
186 Title::makeTitle( NS_MAIN, '<' ),
187 StatusValue::newFatal( 'movepage-invalid-target-title' ),
189 'Move between invalid names' => [
190 Title::makeTitle( NS_MAIN, '<' ),
191 Title::makeTitle( NS_MAIN, '>' ),
192 // @todo First error message should be more specific, or maybe we should make moving
193 // such pages valid if they actually exist somehow in the database
194 StatusValue::newFatal( 'movepage-source-doesnt-exist', '<' )
195 ->fatal( 'movepage-invalid-target-title' ),
197 'Move nonexistent' => [
198 'Nonexistent',
199 'Nonexistent2',
200 StatusValue::newFatal( 'movepage-source-doesnt-exist', 'Nonexistent' ),
202 'Move over existing' => [
203 'Existent',
204 'Existent2',
205 StatusValue::newFatal( 'articleexists', 'Existent2' ),
207 'Move from another wiki' => [
208 Title::makeTitle( NS_MAIN, 'Test', '', 'otherwiki' ),
209 'Nonexistent',
210 StatusValue::newFatal( 'immobile-source-namespace-iw' ),
212 'Move special page' => [
213 'Special:FooBar',
214 'Nonexistent',
215 StatusValue::newFatal( 'immobile-source-namespace', 'Special' ),
217 'Move to another wiki' => [
218 'Existent',
219 Title::makeTitle( NS_MAIN, 'Test', '', 'otherwiki' ),
220 StatusValue::newFatal( 'immobile-target-namespace-iw' ),
222 'Move to special page' => [
223 'Existent',
224 'Special:FooBar',
225 StatusValue::newFatal( 'immobile-target-namespace', 'Special' ),
227 'Move to allowed content model' => [
228 'MediaWiki:Existent.js',
229 'MediaWiki:Nonexistent',
230 StatusValue::newGood(),
232 'Move to prohibited content model' => [
233 'Existent',
234 'No content allowed',
235 StatusValue::newFatal( 'content-not-allowed-here', 'wikitext', 'No content allowed', 'main' ),
237 'Aborted by hook' => [
238 'Hooked in place',
239 'Nonexistent',
240 StatusValue::newFatal( 'immobile-source-namespace', '(Main)' ),
242 'Doubly aborted by hook' => [
243 'Hooked in place',
244 'Hooked In Place',
245 StatusValue::newFatal( 'immobile-source-namespace', '(Main)' )
246 ->fatal( 'immobile-target-namespace', '(Main)' ),
248 'Non-file to file' => [
249 'Existent',
250 'File:Nonexistent.jpg',
251 StatusValue::newFatal( 'nonfile-cannot-move-to-file' ),
253 'File to non-file' => [
254 'File:Existent.jpg',
255 'Nonexistent',
256 StatusValue::newFatal( 'imagenocrossnamespace' ),
258 'Existing file to non-existing file' => [
259 'File:Existent.jpg',
260 'File:Nonexistent.jpg',
261 StatusValue::newGood(),
263 'Existing file to existing file' => [
264 'File:Existent.jpg',
265 'File:Existent2.jpg',
266 StatusValue::newFatal( 'articleexists', 'File:Existent2.jpg' ),
268 'Existing file to existing file with no page' => [
269 'File:Existent.jpg',
270 'File:Existent-file-no-page.jpg',
271 // @todo Is this correct? Moving over an existing file with no page should succeed?
272 StatusValue::newGood(),
274 'Existing file to name with slash' => [
275 'File:Existent.jpg',
276 'File:Existent/slashed.jpg',
277 StatusValue::newFatal( 'imageinvalidfilename' ),
279 'Mismatched file extension' => [
280 'File:Existent.jpg',
281 'File:Nonexistent.png',
282 StatusValue::newFatal( 'imagetypemismatch' ),
284 'Non-file page in the File namespace' => [
285 'File:Non-file.jpg',
286 'File:Non-file-new.png',
287 StatusValue::newGood(),
289 'File too long' => [
290 'File:Existent.jpg',
291 'File:0123456789012345678901234567890123456789012345678901234567890123456789' .
292 '0123456789012345678901234567890123456789012345678901234567890123456789' .
293 '0123456789012345678901234567890123456789012345678901234567890123456789' .
294 '012345678901234567890123456789-long.jpg',
295 StatusValue::newFatal( 'filename-toolong' ),
297 // The FileRepo mock does not return true for ->backendSupportsUnicodePaths()
298 'Non-ascii' => [
299 'File:Existent.jpg',
300 'File:🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈.jpg',
301 StatusValue::newFatal( 'filename-toolong' )
302 ->fatal( 'windows-nonascii-filename' ),
304 'Non-file move long with unicode' => [
305 'File:Non-file.jpg',
306 'File:🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈🏳️‍🌈🏳️‍🌈🏳️‍🌈 🏳️‍🌈.jpg',
307 StatusValue::newGood()
309 'File just extension' => [
310 'File:Existent.jpg',
311 'File:.jpg',
312 StatusValue::newFatal( 'filename-tooshort' )
313 ->fatal( 'imagetypemismatch' ),
316 return $ret;
320 * @dataProvider provideIsValidMove
322 * @param string|Title $old Old name
323 * @param string|Title $new New name
324 * @param StatusValue $expectedStatus
325 * @param array $extraOptions
327 public function testMove( $old, $new, StatusValue $expectedStatus, array $extraOptions = [] ) {
328 $iwLookup = $this->createMock( InterwikiLookup::class );
329 $iwLookup->method( 'isValidInterwiki' )
330 ->willReturn( true );
332 $this->setService( 'InterwikiLookup', $iwLookup );
334 $old = $old instanceof Title ? $old : Title::newFromText( $old );
335 $new = $new instanceof Title ? $new : Title::newFromText( $new );
337 $createRedirect = $extraOptions['createRedirect'] ?? true;
338 unset( $extraOptions['createRedirect'] );
339 $params = [ 'options' => $extraOptions ];
341 if ( !$expectedStatus->isGood() ) {
342 $obj = $this->newMovePageWithMocks( $old, $new, $params );
343 $status = $obj->move( $this->getTestUser()->getUser() );
344 $this->assertStatusMessagesExactly(
345 $expectedStatus,
346 $status
348 } else {
349 $oldPageId = $old->getArticleID();
350 $status = $this->getServiceContainer()
351 ->getMovePageFactory()
352 ->newMovePage( $old, $new )
353 ->move( $this->getTestUser()->getUser(), 'move reason', $createRedirect );
354 $this->assertStatusOK( $status );
355 $this->assertMoved( $old, $new, $oldPageId, $createRedirect );
358 'nullRevision' => $nullRevision,
359 'redirectRevision' => $redirectRevision
360 ] = $status->getValue();
361 $this->assertInstanceOf( RevisionRecord::class, $nullRevision );
362 $this->assertSame( $oldPageId, $nullRevision->getPageId() );
363 if ( $createRedirect ) {
364 $this->assertInstanceOf( RevisionRecord::class, $redirectRevision );
365 $this->assertSame( $old->getArticleID( IDBAccessObject::READ_LATEST ), $redirectRevision->getPageId() );
366 } else {
367 $this->assertNull( $redirectRevision );
373 * Test for the move operation being aborted via the TitleMove hook
374 * @covers \MediaWiki\Page\MovePage::move
376 public function testMoveAbortedByTitleMoveHook() {
377 $error = 'Preventing move operation with TitleMove hook.';
378 $this->setTemporaryHook( 'TitleMove',
379 static function ( $old, $new, $user, $reason, $status ) use ( $error ) {
380 $status->fatal( $error );
384 $oldTitle = Title::makeTitle( NS_MAIN, 'Some old title' );
385 $this->editPage(
386 $oldTitle,
387 new WikitextContent( 'foo' ),
388 'bar',
389 NS_MAIN,
390 $this->getTestSysop()->getAuthority()
392 $newTitle = Title::makeTitle( NS_MAIN, 'A brand new title' );
393 $mp = $this->newMovePageWithMocks( $oldTitle, $newTitle );
394 $user = User::newFromName( 'TitleMove tester' );
395 $status = $mp->move( $user, 'Reason', true );
396 $this->assertStatusError( $error, $status );
400 * Test moving subpages from one page to another
401 * @covers \MediaWiki\Page\MovePage::moveSubpages
403 public function testMoveSubpages() {
404 $name = ucfirst( __FUNCTION__ );
406 $subPages = [ "Talk:$name/1", "Talk:$name/2" ];
407 $ids = [];
408 $pages = [
409 $name,
410 "Talk:$name",
411 "$name 2",
412 "Talk:$name 2",
414 foreach ( array_merge( $pages, $subPages ) as $page ) {
415 $ids[$page] = $this->createPage( $page );
418 $oldTitle = Title::newFromText( "Talk:$name" );
419 $newTitle = Title::newFromText( "Talk:$name 2" );
420 $status = $this->getServiceContainer()
421 ->getMovePageFactory()
422 ->newMovePage( $oldTitle, $newTitle )
423 ->moveSubpages( $this->getTestUser()->getUser(), 'Reason', true );
425 $this->assertStatusGood( $status,
426 "Moving subpages from Talk:{$name} to Talk:{$name} 2 was not completely successful." );
427 foreach ( $subPages as $page ) {
428 $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
433 * Test moving subpages from one page to another
434 * @covers \MediaWiki\Page\MovePage::moveSubpagesIfAllowed
436 public function testMoveSubpagesIfAllowed() {
437 $name = ucfirst( __FUNCTION__ );
439 $subPages = [ "Talk:$name/1", "Talk:$name/2" ];
440 $ids = [];
441 $pages = [
442 $name,
443 "Talk:$name",
444 "$name 2",
445 "Talk:$name 2",
447 foreach ( array_merge( $pages, $subPages ) as $page ) {
448 $ids[$page] = $this->createPage( $page );
451 $oldTitle = Title::newFromText( "Talk:$name" );
452 $newTitle = Title::newFromText( "Talk:$name 2" );
453 $status = $this->getServiceContainer()
454 ->getMovePageFactory()
455 ->newMovePage( $oldTitle, $newTitle )
456 ->moveSubpagesIfAllowed( $this->getTestUser()->getUser(), 'Reason', true );
458 $this->assertStatusGood( $status,
459 "Moving subpages from Talk:{$name} to Talk:{$name} 2 was not completely successful." );
460 foreach ( $subPages as $page ) {
461 $this->assertMoved( $page, str_replace( $name, "$name 2", $page ), $ids[$page] );
466 * Shortcut function to create a page and return its id.
468 * @param string $name Page to create
469 * @return int ID of created page
471 protected function createPage( $name ) {
472 return $this->editPage( $name, 'Content' )->getNewRevision()->getPageId();
476 * @param string $from Prefixed name of source
477 * @param string|Title $to Prefixed name of destination
478 * @param string|Title $id Page id of the page to move
479 * @param bool $createRedirect
481 protected function assertMoved( $from, $to, $id, bool $createRedirect = true ) {
482 Title::clearCaches();
483 $fromTitle = $from instanceof Title ? $from : Title::newFromText( $from );
484 $toTitle = $to instanceof Title ? $to : Title::newFromText( $to );
486 $this->assertTrue( $toTitle->exists(),
487 "Destination {$toTitle->getPrefixedText()} does not exist" );
489 if ( !$createRedirect ) {
490 $this->assertFalse( $fromTitle->exists(),
491 "Source {$fromTitle->getPrefixedText()} exists" );
492 } else {
493 $this->assertTrue( $fromTitle->exists(),
494 "Source {$fromTitle->getPrefixedText()} does not exist" );
495 $this->assertTrue( $fromTitle->isRedirect(),
496 "Source {$fromTitle->getPrefixedText()} is not a redirect" );
498 $target = $this->getServiceContainer()
499 ->getRevisionLookup()
500 ->getRevisionByTitle( $fromTitle )
501 ->getContent( SlotRecord::MAIN )
502 ->getRedirectTarget();
503 $this->assertSame( $toTitle->getPrefixedText(), $target->getPrefixedText() );
506 $this->assertSame( $id, $toTitle->getArticleID() );
510 * Test redirect handling
512 * @covers \MediaWiki\Page\MovePage::isValidMove
514 public function testRedirects() {
515 $this->editPage( 'ExistentRedirect', '#REDIRECT [[Existent]]' );
516 $mp = $this->newMovePageWithMocks(
517 Title::makeTitle( NS_MAIN, 'Existent' ),
518 Title::makeTitle( NS_MAIN, 'ExistentRedirect' )
520 $this->assertStatusGood(
521 $mp->isValidMove(),
522 'Can move over normal redirect'
525 $this->editPage( 'ExistentRedirect3', '#REDIRECT [[Existent]]' );
526 $mp = $this->newMovePageWithMocks(
527 Title::makeTitle( NS_MAIN, 'Existent2' ),
528 Title::makeTitle( NS_MAIN, 'ExistentRedirect3' )
530 $this->assertStatusError(
531 'redirectexists',
532 $mp->isValidMove(),
533 'Cannot move over redirect with a different target'
536 $this->editPage( 'ExistentRedirect3', '#REDIRECT [[Existent2]]' );
537 $mp = $this->newMovePageWithMocks(
538 Title::makeTitle( NS_MAIN, 'Existent' ),
539 Title::makeTitle( NS_MAIN, 'ExistentRedirect3' )
541 $this->assertStatusError(
542 'articleexists',
543 $mp->isValidMove(),
544 'Multi-revision redirects count as articles'
549 * Assert that links tables are updated after cross namespace page move (T299275).
551 public function testCrossNamespaceLinksUpdate() {
552 $title = Title::makeTitle( NS_TEMPLATE, 'Test' );
553 $this->getExistingTestPage( $title );
555 $wikitext = "[[Test]], [[Image:Existent.jpg]], {{Test}}";
557 $old = Title::makeTitle( NS_USER, __METHOD__ );
558 $this->editPage( $old, $wikitext );
559 $pageId = $old->getId();
561 // do a cross-namespace move
562 $new = Title::makeTitle( NS_PROJECT, __METHOD__ );
563 $obj = $this->newMovePageWithMocks( $old, $new, [ 'db' => $this->getDb() ] );
564 $status = $obj->move( $this->getTestUser()->getUser() );
566 // sanity checks
567 $this->assertStatusOK( $status );
568 $this->assertSame( $pageId, $new->getId() );
569 $this->assertNotSame( $pageId, $old->getId() );
571 // ensure links tables where updated
572 $this->newSelectQueryBuilder()
573 ->select( [ 'lt_namespace', 'lt_title', 'pl_from_namespace' ] )
574 ->from( 'pagelinks' )
575 ->join( 'linktarget', null, 'pl_target_id=lt_id' )
576 ->where( [ 'pl_from' => $pageId ] )
577 ->assertResultSet( [
578 [ NS_MAIN, 'Test', NS_PROJECT ]
579 ] );
580 $targetId = MediaWikiServices::getInstance()->getLinkTargetLookup()->getLinkTargetId( $title );
581 $this->newSelectQueryBuilder()
582 ->select( [ 'tl_target_id', 'tl_from_namespace' ] )
583 ->from( 'templatelinks' )
584 ->where( [ 'tl_from' => $pageId ] )
585 ->assertResultSet( [
586 [ $targetId, NS_PROJECT ]
587 ] );
588 $this->newSelectQueryBuilder()
589 ->select( [ 'il_to', 'il_from_namespace' ] )
590 ->from( 'imagelinks' )
591 ->where( [ 'il_from' => $pageId ] )
592 ->assertResultSet( [
593 [ 'Existent.jpg', NS_PROJECT ]
594 ] );
598 * Regression test for T381225
600 public function testEventEmission() {
601 $calls = 0;
603 $old = Title::makeTitle( NS_MEDIAWIKI, 'Foo' );
604 $oldPage = $this->getExistingTestPage( $old );
605 $oldRev = $oldPage->getRevisionRecord();
606 $oldPageId = $old->getId();
608 $new = Title::makeTitle( NS_MEDIAWIKI, 'Bar' );
609 $this->getNonexistingTestPage( $new );
611 // clear the queue
612 $this->runJobs();
614 $this->getServiceContainer()->getDomainEventSource()->registerListener(
615 'PageUpdated',
616 static function ( PageUpdatedEvent $event ) use ( &$calls, $old, $oldPageId, $new, $oldRev ) {
617 // for the existing page under the new title
618 if ( $event->getPage()->isSamePageAs( $new ) ) {
619 Assert::assertFalse( $event->isNew(), 'isNew' );
620 Assert::assertTrue( $event->isRevisionChange(), 'isRevisionChange' );
621 Assert::assertFalse( $event->isContentChange(), 'isContentChange' );
622 Assert::assertSame( $oldPageId, $event->getPage()->getId() );
623 Assert::assertSame( $oldRev->getId(), $event->getOldRevision()->getId() );
625 Assert::assertTrue(
626 $event->hasFlag( PageUpdatedEvent::FLAG_MOVED ),
627 PageUpdatedEvent::FLAG_MOVED
630 Assert::assertTrue(
631 $event->hasFlag( PageUpdatedEvent::FLAG_SILENT ),
632 PageUpdatedEvent::FLAG_SILENT
636 // for the redirect page
637 if ( $event->getPage()->isSamePageAs( $old ) ) {
638 Assert::assertTrue( $event->isNew(), 'isNew' );
639 Assert::assertTrue( $event->isRevisionChange(), 'isRevisionChange' );
640 Assert::assertTrue( $event->isContentChange(), 'isContentChange' );
642 Assert::assertTrue(
643 $event->hasFlag( PageUpdatedEvent::FLAG_SILENT ),
644 PageUpdatedEvent::FLAG_SILENT
647 Assert::assertTrue(
648 $event->hasFlag( PageUpdatedEvent::FLAG_AUTOMATED ),
649 PageUpdatedEvent::FLAG_AUTOMATED
653 // TODO: assert more properties
655 $calls++;
659 // Now move the page
660 $obj = $this->newMovePageWithMocks( $old, $new, [ 'db' => $this->getDb() ] );
661 $obj->move( $this->getTestUser()->getUser() );
663 $this->runDeferredUpdates();
664 $this->assertSame( 2, $calls );
668 * Regression test for T381225
670 public function testEventPropagation() {
671 $old = Title::makeTitle( NS_MEDIAWIKI, 'Foo' );
672 $this->getExistingTestPage( $old );
674 $new = Title::makeTitle( NS_MEDIAWIKI, 'Bar' );
675 $this->getNonexistingTestPage( $new );
677 // clear the queue
678 $this->runJobs();
680 $this->installChangeTrackingEventIngressSpyForPageMove();
681 $this->installSearchEventIngressSpyForPageMove();
682 $this->installLanguageEventIngressSpyForPageMove();
684 // Now move the page
685 $obj = $this->newMovePageWithMocks( $old, $new, [ 'db' => $this->getDb() ] );
686 $obj->move( $this->getTestUser()->getUser() );
688 // NOTE: assertions are applied by the spies installed earlier.
689 $this->runDeferredUpdates();