3 use MediaWiki\Config\HashConfig
;
4 use MediaWiki\Config\ServiceOptions
;
5 use MediaWiki\Deferred\DeferredUpdates
;
6 use MediaWiki\MainConfigNames
;
7 use MediaWiki\Revision\MutableRevisionRecord
;
8 use MediaWiki\Revision\RevisionLookup
;
9 use MediaWiki\Revision\RevisionRecord
;
10 use MediaWiki\Tests\Unit\DummyServicesTrait
;
11 use MediaWiki\Title\Title
;
12 use MediaWiki\User\TalkPageNotificationManager
;
13 use MediaWiki\User\UserIdentity
;
14 use MediaWiki\User\UserIdentityValue
;
15 use MediaWiki\Utils\MWTimestamp
;
16 use PHPUnit\Framework\AssertionFailedError
;
19 * @covers \MediaWiki\User\TalkPageNotificationManager
22 class TalkPageNotificationManagerTest
extends MediaWikiIntegrationTestCase
{
23 use DummyServicesTrait
;
25 private function editUserTalk( UserIdentity
$user, string $text ): RevisionRecord
{
26 // UserIdentity doesn't have getUserPage/getTalkPage, but we can easily recreate
27 // it, and its easier than needing to depend on a full user object
28 $userTalk = Title
::makeTitle( NS_USER_TALK
, $user->getName() );
29 $status = $this->editPage(
34 $this->getTestSysop()->getUser()
36 $this->assertStatusGood( $status, 'create revision of user talk' );
37 return $status->getNewRevision();
40 private function getManager(
41 bool $disableAnonTalk = false,
42 bool $isReadOnly = false,
43 ?RevisionLookup
$revisionLookup = null
45 $services = $this->getServiceContainer();
46 return new TalkPageNotificationManager(
48 TalkPageNotificationManager
::CONSTRUCTOR_OPTIONS
,
50 MainConfigNames
::DisableAnonTalk
=> $disableAnonTalk
53 $services->getConnectionProvider(),
54 $this->getDummyReadOnlyMode( $isReadOnly ),
55 $revisionLookup ??
$services->getRevisionLookup(),
56 $this->createHookContainer(),
57 $services->getUserFactory()
61 public static function provideUserHasNewMessages() {
62 yield
'Registered user' => [ UserIdentityValue
::newRegistered( 123, 'MyName' ) ];
63 yield
'Anonymous user' => [ UserIdentityValue
::newAnonymous( '1.2.3.4' ) ];
67 * @dataProvider provideUserHasNewMessages
68 * @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
69 * @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
70 * @covers \MediaWiki\User\TalkPageNotificationManager::clearInstanceCache
71 * @covers \MediaWiki\User\TalkPageNotificationManager::removeUserHasNewMessages
73 public function testUserHasNewMessages( UserIdentity
$user ) {
74 $manager = $this->getManager();
75 $this->assertFalse( $manager->userHasNewMessages( $user ),
76 'Should be false before updated' );
77 $revRecord = $this->editUserTalk( $user, __METHOD__
);
78 $manager->setUserHasNewMessages( $user, $revRecord );
79 $this->assertTrue( $manager->userHasNewMessages( $user ),
80 'Should be true after updated' );
81 $manager->clearInstanceCache( $user );
82 $this->assertTrue( $manager->userHasNewMessages( $user ),
83 'Should be true after cache cleared' );
84 $manager->removeUserHasNewMessages( $user );
85 $this->assertFalse( $manager->userHasNewMessages( $user ),
86 'Should be false after updated' );
87 $manager->clearInstanceCache( $user );
88 $this->assertFalse( $manager->userHasNewMessages( $user ),
89 'Should be false after cache cleared' );
90 $manager->setUserHasNewMessages( $user, null );
91 $this->assertTrue( $manager->userHasNewMessages( $user ),
92 'Should be true after updated' );
93 $manager->removeUserHasNewMessages( $user );
94 $this->assertFalse( $manager->userHasNewMessages( $user ),
95 'Should be false after updated' );
99 * @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
100 * @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
102 public function testUserHasNewMessagesDisabledAnon() {
103 $user = new UserIdentityValue( 0, '1.2.3.4' );
104 $revRecord = $this->editUserTalk( $user, __METHOD__
);
105 $manager = $this->getManager( true );
106 $this->assertFalse( $manager->userHasNewMessages( $user ),
107 'New anon should have no new messages' );
108 $manager->setUserHasNewMessages( $user, $revRecord );
109 $this->assertFalse( $manager->userHasNewMessages( $user ),
110 'Must not set new messages for anon if disabled' );
111 $manager->clearInstanceCache( $user );
112 $this->assertFalse( $manager->userHasNewMessages( $user ),
113 'Must not set to database if anon messages disabled' );
117 * @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
119 public function testGetLatestSeenMessageTimestamp() {
120 $user = $this->getTestUser()->getUser();
121 $firstRev = $this->editUserTalk( $user, __METHOD__
. ' 1' );
122 $secondRev = $this->editUserTalk( $user, __METHOD__
. ' 2' );
123 $manager = $this->getManager();
124 $manager->setUserHasNewMessages( $user, $secondRev );
125 $this->assertSame( $firstRev->getTimestamp(), $manager->getLatestSeenMessageTimestamp( $user ) );
129 * @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
131 public function testGetLatestSeenMessageTimestampOutOfOrderRevision() {
132 $user = $this->getTestUser()->getUser();
133 $firstRev = $this->editUserTalk( $user, __METHOD__
. ' 1' );
134 $secondRev = $this->editUserTalk( $user, __METHOD__
. ' 2' );
135 $thirdRev = $this->editUserTalk( $user, __METHOD__
. ' 3' );
136 $veryOldTimestamp = MWTimestamp
::convert( TS_MW
, 1 );
137 $mockOldRev = $this->createMock( RevisionRecord
::class );
138 $mockOldRev->method( 'getTimestamp' )
139 ->willReturn( $veryOldTimestamp );
140 $mockRevLookup = $this->getMockForAbstractClass( RevisionLookup
::class );
141 $mockRevLookup->method( 'getPreviousRevision' )
142 ->willReturnCallback( static function ( RevisionRecord
$rev )
143 use ( $firstRev, $secondRev, $thirdRev, $mockOldRev )
145 if ( $rev === $secondRev ) {
148 if ( $rev === $thirdRev ) {
151 throw new AssertionFailedError(
152 'RevisionLookup::getPreviousRevision called with wrong rev ' . $rev->getId()
155 $manager = $this->getManager( false, false, $mockRevLookup );
156 $manager->setUserHasNewMessages( $user, $thirdRev );
157 $this->assertSame( $veryOldTimestamp, $manager->getLatestSeenMessageTimestamp( $user ) );
158 $manager->setUserHasNewMessages( $user, $secondRev );
159 $this->assertSame( $veryOldTimestamp, $manager->getLatestSeenMessageTimestamp( $user ) );
163 * @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
165 public function testGetLatestSeenMessageTimestampNoNewMessages() {
166 $user = $this->getTestUser()->getUser();
167 $manager = $this->getManager();
168 $this->assertNull( $manager->getLatestSeenMessageTimestamp( $user ),
169 'Must be null if no new messages' );
173 * @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
174 * @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
175 * @covers \MediaWiki\User\TalkPageNotificationManager::removeUserHasNewMessages
177 public function testDoesNotCrashOnReadOnly() {
178 $user = $this->getTestUser()->getUser();
179 $this->editUserTalk( $user, __METHOD__
);
181 $manager = $this->getManager( false, true );
182 $this->assertTrue( $manager->userHasNewMessages( $user ) );
183 $manager->removeUserHasNewMessages( $user );
184 $this->assertFalse( $manager->userHasNewMessages( $user ) );
188 * @covers \MediaWiki\User\TalkPageNotificationManager::clearForPageView
190 public function testClearForPageView() {
191 $user = $this->getTestUser()->getUser();
192 $title = $user->getTalkPage();
193 $revision = new MutableRevisionRecord( $title );
194 $revision->setPageId( 100 );
195 $revision->setId( 101 );
196 $manager = $this->getManager();
197 $manager->setUserHasNewMessages( $user );
198 $this->assertTrue( $manager->userHasNewMessages( $user ) );
200 // DB should have the notification
201 $this->newSelectQueryBuilder()
202 ->select( 'user_id' )
203 ->from( 'user_newtalk' )
204 ->where( [ 'user_id' => $user->getId() ] )
205 ->assertFieldValue( $user->getId() );
207 $this->getDb()->startAtomic( __METHOD__
); // let deferred updates queue up
209 $updateCountBefore = DeferredUpdates
::pendingUpdatesCount();
210 $manager->clearForPageView( $user, $revision );
211 // Cache should already be updated
212 $this->assertFalse( $manager->userHasNewMessages( $user ) );
214 $updateCountAfter = DeferredUpdates
::pendingUpdatesCount();
215 $this->assertGreaterThan( $updateCountBefore, $updateCountAfter, 'An update should have been queued' );
217 $this->getDb()->endAtomic( __METHOD__
); // run deferred updates
218 $this->runDeferredUpdates();
220 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount(), 'No pending updates' );
222 // Notification should have been deleted from the DB
223 $this->newSelectQueryBuilder()
224 ->select( 'user_id' )
225 ->from( 'user_newtalk' )
226 ->where( [ 'user_id' => $user->getId() ] )
227 ->assertEmptyResult();