+<?php
+
+/**
+ * @author Addshore
+ *
+ * @covers WatchedItemStore
+ */
+class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
+ */
+ private function getMockDb() {
+ return $this->getMock( IDatabase::class );
+ }
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
+ */
+ private function getMockLoadBalancer( $mockDb ) {
+ $mock = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'getConnection' )
+ ->will( $this->returnValue( $mockDb ) );
+ $mock->expects( $this->any() )
+ ->method( 'getReadOnlyReason' )
+ ->will( $this->returnValue( false ) );
+ return $mock;
+ }
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|BagOStuff
+ */
+ private function getMockCache() {
+ $mock = $this->getMockBuilder( BagOStuff::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'makeKey' )
+ ->will( $this->returnCallback( function() {
+ return implode( ':', func_get_args() );
+ } ) );
+ return $mock;
+ }
+
+ /**
+ * @param int $id
+ * @return PHPUnit_Framework_MockObject_MockObject|User
+ */
+ private function getMockNonAnonUserWithId( $id ) {
+ $mock = $this->getMock( User::class );
+ $mock->expects( $this->any() )
+ ->method( 'isAnon' )
+ ->will( $this->returnValue( false ) );
+ $mock->expects( $this->any() )
+ ->method( 'getId' )
+ ->will( $this->returnValue( $id ) );
+ return $mock;
+ }
+
+ /**
+ * @return User
+ */
+ private function getAnonUser() {
+ return User::newFromName( 'Anon_User' );
+ }
+
+ private function getFakeRow( array $rowValues ) {
+ $fakeRow = new stdClass();
+ foreach ( $rowValues as $valueName => $value ) {
+ $fakeRow->$valueName = $value;
+ }
+ return $fakeRow;
+ }
+
+ public function testGetDefaultInstance() {
+ $instanceOne = WatchedItemStore::getDefaultInstance();
+ $instanceTwo = WatchedItemStore::getDefaultInstance();
+ $this->assertSame( $instanceOne, $instanceTwo );
+ }
+
+ public function testDuplicateEntry_nothingToDuplicate() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Old_Title',
+ ],
+ 'WatchedItemStore::duplicateEntry',
+ [ 'FOR UPDATE' ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ new HashBagOStuff( [ 'maxKeys' => 100 ] )
+ );
+
+ $store->duplicateEntry(
+ Title::newFromText( 'Old_Title' ),
+ Title::newFromText( 'New_Title' )
+ );
+ }
+
+ public function testDuplicateEntry_somethingToDuplicate() {
+ $fakeRows = [
+ $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
+ $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->at( 0 ) )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Old_Title',
+ ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+ $mockDb->expects( $this->at( 1 ) )
+ ->method( 'replace' )
+ ->with(
+ 'watchlist',
+ [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'New_Title',
+ 'wl_notificationtimestamp' => '20151212010101',
+ ],
+ [
+ 'wl_user' => 2,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'New_Title',
+ 'wl_notificationtimestamp' => null,
+ ],
+ ],
+ $this->isType( 'string' )
+ );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ new HashBagOStuff( [ 'maxKeys' => 100 ] )
+ );
+
+ $store->duplicateEntry(
+ Title::newFromText( 'Old_Title' ),
+ Title::newFromText( 'New_Title' )
+ );
+ }
+
+ public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->at( 0 ) )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Old_Title',
+ ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+ $mockDb->expects( $this->at( 1 ) )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 1,
+ 'wl_title' => 'Old_Title',
+ ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ new HashBagOStuff( [ 'maxKeys' => 100 ] )
+ );
+
+ $store->duplicateAllAssociatedEntries(
+ Title::newFromText( 'Old_Title' ),
+ Title::newFromText( 'New_Title' )
+ );
+ }
+
+ public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
+ $fakeRows = [
+ $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->at( 0 ) )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Old_Title',
+ ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+ $mockDb->expects( $this->at( 1 ) )
+ ->method( 'replace' )
+ ->with(
+ 'watchlist',
+ [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'New_Title',
+ 'wl_notificationtimestamp' => '20151212010101',
+ ],
+ ],
+ $this->isType( 'string' )
+ );
+ $mockDb->expects( $this->at( 2 ) )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user',
+ 'wl_notificationtimestamp',
+ ],
+ [
+ 'wl_namespace' => 1,
+ 'wl_title' => 'Old_Title',
+ ]
+ )
+ ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+ $mockDb->expects( $this->at( 3 ) )
+ ->method( 'replace' )
+ ->with(
+ 'watchlist',
+ [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 1,
+ 'wl_title' => 'New_Title',
+ 'wl_notificationtimestamp' => '20151212010101',
+ ],
+ ],
+ $this->isType( 'string' )
+ );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ new HashBagOStuff( [ 'maxKeys' => 100 ] )
+ );
+
+ $store->duplicateAllAssociatedEntries(
+ Title::newFromText( 'Old_Title' ),
+ Title::newFromText( 'New_Title' )
+ );
+ }
+
+ public function testAddWatch_nonAnonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'insert' )
+ ->with(
+ 'watchlist',
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Some_Page',
+ 'wl_notificationtimestamp' => null,
+ ]
+ ]
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:Some_Page:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $store->addWatch(
+ $this->getMockNonAnonUserWithId( 1 ),
+ Title::newFromText( 'Some_Page' )
+ );
+ }
+
+ public function testAddWatch_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'insert' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $store->addWatch(
+ $this->getAnonUser(),
+ Title::newFromText( 'Some_Page' )
+ );
+ }
+
+ public function testAddWatchBatch_nonAnonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'insert' )
+ ->with(
+ 'watchlist',
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Some_Page',
+ 'wl_notificationtimestamp' => null,
+ ],
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 1,
+ 'wl_title' => 'Some_Page',
+ 'wl_notificationtimestamp' => null,
+ ]
+ ]
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->exactly( 2 ) )
+ ->method( 'delete' );
+ $mockCache->expects( $this->at( 1 ) )
+ ->method( 'delete' )
+ ->with( '0:Some_Page:1' );
+ $mockCache->expects( $this->at( 3 ) )
+ ->method( 'delete' )
+ ->with( '1:Some_Page:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $mockUser = $this->getMockNonAnonUserWithId( 1 );
+
+ $this->assertTrue(
+ $store->addWatchBatch(
+ [
+ [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
+ [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
+ ]
+ )
+ );
+ }
+
+ public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'insert' )
+ ->with(
+ 'watchlist',
+ [
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'Some_Page',
+ 'wl_notificationtimestamp' => null,
+ ]
+ ]
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:Some_Page:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertTrue(
+ $store->addWatchBatch(
+ [
+ [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
+ [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
+ ]
+ )
+ );
+ }
+
+ public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'insert' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $anonUser = $this->getAnonUser();
+ $this->assertFalse(
+ $store->addWatchBatch(
+ [
+ [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
+ [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
+ ]
+ )
+ );
+ }
+
+ public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'insert' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->addWatchBatch( [] )
+ );
+ }
+
+ public function testLoadWatchedItem_existingItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue(
+ $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+ ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with(
+ '0:SomeDbKey:1'
+ );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $watchedItem = $store->loadWatchedItem(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ );
+ $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+ $this->assertEquals( 1, $watchedItem->getUser()->getId() );
+ $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
+ $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
+ }
+
+ public function testLoadWatchedItem_noItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue( [] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->loadWatchedItem(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testLoadWatchedItem_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->loadWatchedItem(
+ $this->getAnonUser(),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testRemoveWatch_existingItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ );
+ $mockDb->expects( $this->once() )
+ ->method( 'affectedRows' )
+ ->will( $this->returnValue( 1 ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertTrue(
+ $store->removeWatch(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testRemoveWatch_noItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ 'watchlist',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ );
+ $mockDb->expects( $this->once() )
+ ->method( 'affectedRows' )
+ ->will( $this->returnValue( 0 ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->removeWatch(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testRemoveWatch_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'delete' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->removeWatch(
+ $this->getAnonUser(),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testGetWatchedItem_existingItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue(
+ $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+ ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'get' )
+ ->with(
+ '0:SomeDbKey:1'
+ )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with(
+ '0:SomeDbKey:1'
+ );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $watchedItem = $store->getWatchedItem(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ );
+ $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+ $this->assertEquals( 1, $watchedItem->getUser()->getId() );
+ $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
+ $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
+ }
+
+ public function testGetWatchedItem_cachedItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockUser = $this->getMockNonAnonUserWithId( 1 );
+ $linkTarget = new TitleValue( 0, 'SomeDbKey' );
+ $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'get' )
+ ->with(
+ '0:SomeDbKey:1'
+ )
+ ->will( $this->returnValue( $cachedItem ) );
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ $cachedItem,
+ $store->getWatchedItem(
+ $mockUser,
+ $linkTarget
+ )
+ );
+ }
+
+ public function testGetWatchedItem_noItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue( [] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->getWatchedItem(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testGetWatchedItem_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->getWatchedItem(
+ $this->getAnonUser(),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testIsWatchedItem_existingItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue(
+ $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+ ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with(
+ '0:SomeDbKey:1'
+ );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertTrue(
+ $store->isWatched(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testIsWatchedItem_noItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue( [] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->isWatched(
+ $this->getMockNonAnonUserWithId( 1 ),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testIsWatchedItem_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->isWatched(
+ $this->getAnonUser(),
+ new TitleValue( 0, 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testResetNotificationTimestamp_anonymousUser() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->resetNotificationTimestamp(
+ $this->getAnonUser(),
+ Title::newFromText( 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testResetNotificationTimestamp_noItem() {
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue( [] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )
+ ->method( 'set' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertFalse(
+ $store->resetNotificationTimestamp(
+ $this->getMockNonAnonUserWithId( 1 ),
+ Title::newFromText( 'SomeDbKey' )
+ )
+ );
+ }
+
+ public function testResetNotificationTimestamp_item() {
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $title = Title::newFromText( 'SomeDbKey' );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue(
+ $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+ ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with(
+ '0:SomeDbKey:1',
+ $this->isInstanceOf( WatchedItem::class )
+ );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ // Note: This does not actually assert the job is correct
+ $callableCallCounter = 0;
+ $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
+ $callableCallCounter++;
+ $this->assertInternalType( 'callable', $callable );
+ };
+ $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
+
+ $this->assertTrue(
+ $store->resetNotificationTimestamp(
+ $user,
+ $title
+ )
+ );
+ $this->assertEquals( 1, $callableCallCounter );
+ }
+
+ public function testResetNotificationTimestamp_noItemForced() {
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $title = Title::newFromText( 'SomeDbKey' );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockDb->expects( $this->never() )
+ ->method( 'set' );
+ $mockDb->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ // Note: This does not actually assert the job is correct
+ $callableCallCounter = 0;
+ $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
+ $callableCallCounter++;
+ $this->assertInternalType( 'callable', $callable );
+ };
+ $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
+
+ $this->assertTrue(
+ $store->resetNotificationTimestamp(
+ $user,
+ $title,
+ 'force'
+ )
+ );
+ $this->assertEquals( 1, $callableCallCounter );
+ }
+
+ /**
+ * @param $text
+ * @param int $ns
+ *
+ * @return PHPUnit_Framework_MockObject_MockObject|Title
+ */
+ private function getMockTitle( $text, $ns = 0 ) {
+ $title = $this->getMock( Title::class );
+ $title->expects( $this->any() )
+ ->method( 'getText' )
+ ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
+ $title->expects( $this->any() )
+ ->method( 'getDbKey' )
+ ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
+ $title->expects( $this->any() )
+ ->method( 'getNamespace' )
+ ->will( $this->returnValue( $ns ) );
+ return $title;
+ }
+
+ public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $oldid = 22;
+ $title = $this->getMockTitle( 'SomeTitle' );
+ $title->expects( $this->once() )
+ ->method( 'getNextRevisionID' )
+ ->with( $oldid )
+ ->will( $this->returnValue( false ) );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )
+ ->method( 'selectRow' );
+
+ $mockCache = $this->getMockCache();
+ $mockDb->expects( $this->never() )
+ ->method( 'set' );
+ $mockDb->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ // Note: This does not actually assert the job is correct
+ $callableCallCounter = 0;
+ $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+ function( $callable ) use ( &$callableCallCounter ) {
+ $callableCallCounter++;
+ $this->assertInternalType( 'callable', $callable );
+ }
+ );
+
+ $this->assertTrue(
+ $store->resetNotificationTimestamp(
+ $user,
+ $title,
+ 'force',
+ $oldid
+ )
+ );
+ $this->assertEquals( 1, $callableCallCounter );
+ }
+
+ public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $oldid = 22;
+ $title = $this->getMockTitle( 'SomeDbKey' );
+ $title->expects( $this->once() )
+ ->method( 'getNextRevisionID' )
+ ->with( $oldid )
+ ->will( $this->returnValue( 33 ) );
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->once() )
+ ->method( 'selectRow' )
+ ->with(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [
+ 'wl_user' => 1,
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ ]
+ )
+ ->will( $this->returnValue(
+ $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+ ) );
+
+ $mockCache = $this->getMockCache();
+ $mockDb->expects( $this->never() )
+ ->method( 'set' );
+ $mockDb->expects( $this->never() )
+ ->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ // Note: This does not actually assert the job is correct
+ $addUpdateCallCounter = 0;
+ $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+ function( $callable ) use ( &$addUpdateCallCounter ) {
+ $addUpdateCallCounter++;
+ $this->assertInternalType( 'callable', $callable );
+ }
+ );
+
+ $getTimestampCallCounter = 0;
+ $store->overrideRevisionGetTimestampFromIdCallback(
+ function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
+ $getTimestampCallCounter++;
+ $this->assertEquals( $title, $titleParam );
+ $this->assertEquals( $oldid, $oldidParam );
+ }
+ );
+
+ $this->assertTrue(
+ $store->resetNotificationTimestamp(
+ $user,
+ $title,
+ 'force',
+ $oldid
+ )
+ );
+ $this->assertEquals( 1, $addUpdateCallCounter );
+ $this->assertEquals( 1, $getTimestampCallCounter );
+ }
+
+}