* @author Bernhard Posselt * @copyright 2012 Alessandro Cosentino * @copyright 2012-2014 Bernhard Posselt */ namespace OCA\News\Tests\Unit\Service; use FeedIo\Explorer; use FeedIo\Reader\ReadErrorException; use OC\L10N\L10N; use OCA\News\Db\FeedMapperV2; use OCA\News\Fetcher\FeedFetcher; use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\FeedServiceV2; use OCA\News\Service\ItemServiceV2; use OCA\News\Utility\Time; use OCP\AppFramework\Db\DoesNotExistException; use OCA\News\Db\Feed; use OCA\News\Db\Item; use OCP\IConfig; use OCP\IL10N; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; class FeedServiceTest extends TestCase { /** * @var \PHPUnit\Framework\MockObject\MockObject|FeedMapperV2 */ private $mapper; /** * @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2 */ private $itemService; /** @var FeedServiceV2 */ private $class; /** * @var string */ private $uid; /** * @var \PHPUnit\Framework\MockObject\MockObject|FeedFetcher */ private $fetcher; /** * @var int */ private $time; /** * @var int */ private $autoPurgeMinimumInterval; /** * @var \PHPUnit\Framework\MockObject\MockObject|\HTMLPurifier */ private $purifier; /** * @var \PHPUnit\Framework\MockObject\MockObject|L10N */ private $l10n; /** * @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface */ private $logger; /** * @var \PHPUnit\Framework\MockObject\MockObject|Explorer */ private $explorer; protected function setUp(): void { $this->logger = $this->getMockBuilder(LoggerInterface::class) ->disableOriginalConstructor() ->getMock(); $this->time = 222; $this->autoPurgeMinimumInterval = 10; $timeFactory = $this->getMockBuilder(Time::class) ->disableOriginalConstructor() ->getMock(); $timeFactory->expects($this->any()) ->method('getTime') ->will($this->returnValue($this->time)); $this->l10n = $this->getMockBuilder(IL10N::class) ->disableOriginalConstructor() ->getMock(); $this->mapper = $this ->getMockBuilder(FeedMapperV2::class) ->disableOriginalConstructor() ->getMock(); $this->fetcher = $this ->getMockBuilder(FeedFetcher::class) ->disableOriginalConstructor() ->getMock(); $this->explorer = $this ->getMockBuilder(Explorer::class) ->disableOriginalConstructor() ->getMock(); $this->itemService = $this ->getMockBuilder(ItemServiceV2::class) ->disableOriginalConstructor() ->getMock(); $this->purifier = $this ->getMockBuilder(\HTMLPurifier::class) ->disableOriginalConstructor() ->getMock(); $config = $this->getMockBuilder(IConfig::class) ->disableOriginalConstructor() ->getMock(); $config->expects($this->any()) ->method('getAppValue') ->with('news', 'autoPurgeMinimumInterval') ->will($this->returnValue($this->autoPurgeMinimumInterval)); $this->class = new FeedServiceV2( $this->mapper, $this->fetcher, $this->itemService, $this->explorer, $this->purifier, $this->logger ); $this->uid = 'jack'; } /** * @covers \OCA\News\Service\FeedServiceV2::findAll */ public function testFindAll() { $this->response = []; $this->mapper->expects($this->once()) ->method('findAllFromUser') ->with($this->uid) ->will($this->returnValue([])); $result = $this->class->findAllForUser($this->uid); $this->assertEquals([], $result); } public function testCreateDoesNotFindFeed() { $ex = new ReadErrorException('hi'); $url = 'test'; $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->willReturn(new Feed()); $this->fetcher->expects($this->never()) ->method('fetch') ->with($url) ->will($this->throwException($ex)); $this->expectException(ServiceConflictException::class); $this->expectExceptionMessage('Feed with this URL exists'); $this->class->create($this->uid, $url, 1); } public function testCreate() { $url = 'http://test'; $folderId = 10; $createdFeed = new Feed(); $createdFeed->setUrl($url); $createdFeed->setUrlHash('hsssi'); $createdFeed->setLink($url); $createdFeed->setTitle('hehoy'); $createdFeed->setBasicAuthUser('user'); $createdFeed->setBasicAuthPassword('pass'); $item1 = new Item(); $item1->setFeedId(4); $item1->setGuidHash('hi'); $item2 = new Item(); $item2->setFeedId(4); $item2->setGuidHash('yo'); $return = [ $createdFeed, [$item1, $item2] ]; $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException(new DoesNotExistException('no'))); $this->explorer->expects($this->once()) ->method('discover') ->with($url) ->will($this->returnValue([])); $this->fetcher->expects($this->once()) ->method('fetch') ->with($url) ->will($this->returnValue($return)); $this->mapper->expects($this->once()) ->method('insert') ->with($createdFeed) ->will( $this->returnCallback( function () use ($createdFeed) { $createdFeed->setId(4); return $createdFeed; } ) ); $feed = $this->class->create( $this->uid, $url, $folderId, false, null, 'user', 'pass' ); $this->assertEquals($feed->getFolderId(), $folderId); $this->assertEquals($feed->getUrl(), $url); $this->assertEquals($feed->getArticlesPerUpdate(), 2); $this->assertEquals($feed->getBasicAuthUser(), 'user'); $this->assertEquals($feed->getBasicAuthPassword(), 'pass'); } public function testCreateSetsTitle() { $url = 'http://test'; $folderId = 10; $createdFeed = new Feed(); $createdFeed->setUrl($url); $createdFeed->setUrlHash('hsssi'); $createdFeed->setLink($url); $createdFeed->setTitle('hehoy'); $createdFeed->setBasicAuthUser('user'); $createdFeed->setBasicAuthPassword('pass'); $item1 = new Item(); $item1->setFeedId(4); $item1->setGuidHash('hi'); $item2 = new Item(); $item2->setFeedId(4); $item2->setGuidHash('yo'); $return = [ $createdFeed, [$item1, $item2] ]; $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException(new DoesNotExistException('no'))); $this->explorer->expects($this->once()) ->method('discover') ->with($url) ->will($this->returnValue([])); $this->fetcher->expects($this->once()) ->method('fetch') ->with($url) ->will($this->returnValue($return)); $this->mapper->expects($this->once()) ->method('insert') ->with($createdFeed) ->will( $this->returnCallback( function () use ($createdFeed) { $createdFeed->setId(4); return $createdFeed; } ) ); $feed = $this->class->create( $this->uid, $url, $folderId, false, 'title', 'user', 'pass' ); $this->assertEquals($feed->getFolderId(), $folderId); $this->assertEquals($feed->getUrl(), $url); $this->assertEquals($feed->getArticlesPerUpdate(), 2); $this->assertEquals($feed->getTitle(), 'title'); $this->assertEquals($feed->getBasicAuthUser(), 'user'); $this->assertEquals($feed->getBasicAuthPassword(), 'pass'); } public function testCreateDiscovers() { $url = 'http://test'; $folderId = 10; $createdFeed = new Feed(); $createdFeed->setUrl($url); $createdFeed->setUrlHash('hsssi'); $createdFeed->setLink($url); $createdFeed->setTitle('hehoy'); $createdFeed->setBasicAuthUser('user'); $createdFeed->setBasicAuthPassword('pass'); $item1 = new Item(); $item1->setFeedId(4); $item1->setGuidHash('hi'); $item2 = new Item(); $item2->setFeedId(4); $item2->setGuidHash('yo'); $return = [ $createdFeed, [$item1, $item2] ]; $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException(new DoesNotExistException('no'))); $this->explorer->expects($this->once()) ->method('discover') ->with($url) ->will($this->returnValue(['http://discover.test'])); $this->fetcher->expects($this->once()) ->method('fetch') ->with('http://discover.test') ->will($this->returnValue($return)); $this->mapper->expects($this->once()) ->method('insert') ->with($createdFeed) ->will( $this->returnCallback( function () use ($createdFeed) { $createdFeed->setId(4); return $createdFeed; } ) ); $feed = $this->class->create( $this->uid, $url, $folderId, false, null, 'user', 'pass' ); $this->assertEquals($feed->getFolderId(), $folderId); $this->assertEquals($feed->getUrl(), $url); $this->assertEquals($feed->getArticlesPerUpdate(), 2); $this->assertEquals($feed->getBasicAuthUser(), 'user'); $this->assertEquals($feed->getBasicAuthPassword(), 'pass'); } public function testCreateItemGuidExistsAlready() { $url = 'http://test'; $folderId = 10; $ex = new DoesNotExistException('yo'); $createdFeed = new Feed(); $createdFeed->setUrl($url); $createdFeed->setUrlHash($url); $createdFeed->setLink($url); $item1 = new Item(); $item1->setFeedId(5); $item1->setGuidHash('hi'); $item2 = new Item(); $item2->setFeedId(5); $item2->setGuidHash('yo'); $return = [ $createdFeed, [$item1, $item2] ]; $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException($ex)); $this->fetcher->expects($this->once()) ->method('fetch') ->with($this->equalTo($url)) ->will($this->returnValue($return)); $this->mapper->expects($this->once()) ->method('insert') ->with($this->equalTo($createdFeed)) ->will( $this->returnCallback( function () use ($createdFeed) { $createdFeed->setId(5); return $createdFeed; } ) ); $feed = $this->class->create($this->uid, $url, $folderId); $this->assertEquals($feed->getFolderId(), $folderId); $this->assertEquals($feed->getUrl(), $url); } public function testCreateUnableToFetchFeed() { $url = 'http://test'; $folderId = 10; $this->fetcher->expects($this->once()) ->method('fetch') ->with($url) ->willReturn([null, []]); $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException(new DoesNotExistException('no'))); $this->expectException(ServiceNotFoundException::class); $this->expectExceptionMessage('Failed to fetch feed'); $this->class->create($this->uid, $url, $folderId); } public function testCreateUnableToParseFeed() { $url = 'http://test'; $folderId = 10; $this->fetcher->expects($this->once()) ->method('fetch') ->with($url) ->will($this->throwException(new ReadErrorException('ERROR'))); $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, $url) ->will($this->throwException(new DoesNotExistException('no'))); $this->expectException(ServiceNotFoundException::class); $this->expectExceptionMessage('ERROR'); $this->class->create($this->uid, $url, $folderId); } public function testFetchReturnsOnBlock() { $feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $feed->expects($this->once()) ->method('getPreventUpdate') ->will($this->returnValue(TRUE)); $this->assertSame($feed, $this->class->fetch($feed)); } public function testFetchAllReturnsOnAllBlock() { $feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $this->mapper->expects($this->once()) ->method('findAll') ->will($this->returnValue([$feed, $feed])); $feed->expects($this->exactly(2)) ->method('getPreventUpdate') ->will($this->returnValue(TRUE)); $this->class->fetchAll(); } public function testFetchReturnsOnReadError() { $feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $feed->expects($this->once()) ->method('getPreventUpdate') ->will($this->returnValue(FALSE)); $feed->expects($this->once()) ->method('getLocation') ->will($this->returnValue('location')); $this->fetcher->expects($this->once()) ->method('fetch') ->will($this->throwException(new ReadErrorException('FAIL'))); $feed->expects($this->once()) ->method('getUpdateErrorCount') ->will($this->returnValue(1)); $feed->expects($this->once()) ->method('setUpdateErrorCount') ->with(2); $feed->expects($this->once()) ->method('setLastUpdateError') ->with('FAIL'); $this->mapper->expects($this->once()) ->method('update') ->with($feed); $this->class->fetch($feed); } public function testFetchReturnsNoUpdate() { $feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $feed->expects($this->once()) ->method('getPreventUpdate') ->will($this->returnValue(FALSE)); $feed->expects($this->once()) ->method('getLocation') ->will($this->returnValue('location')); $this->fetcher->expects($this->once()) ->method('fetch') ->will($this->returnValue([null, []])); $this->mapper->expects($this->never()) ->method('update'); $this->assertSame($feed, $this->class->fetch($feed)); } public function testFetchSucceedsEmptyItems() { $feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $feed->expects($this->once()) ->method('getPreventUpdate') ->will($this->returnValue(FALSE)); $feed->expects($this->once()) ->method('getLocation') ->will($this->returnValue('location')); $feed->expects($this->once()) ->method('setUnreadCount') ->with(0) ->will($this->returnSelf()); $new_feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $this->fetcher->expects($this->once()) ->method('fetch') ->will($this->returnValue([$new_feed, []])); $this->mapper->expects($this->exactly(1)) ->method('update') ->with($feed) ->will($this->returnValue($feed)); $this->assertEquals($feed, $this->class->fetch($feed)); } public function testFetchSucceedsFullItems() { $feed = Feed::fromParams([ 'id' => 1, 'location' => 'url.com', 'updateMode' => 1, ]); $new_feed = $this->getMockBuilder(Feed::class) ->disableOriginalConstructor() ->getMock(); $item1 = Item::fromParams(['id' => 1, 'body' => '1']); $item2 = Item::fromParams(['id' => 2, 'body' => '2']); $this->fetcher->expects($this->once()) ->method('fetch') ->will($this->returnValue([$new_feed, [$item1, $item2]])); $this->mapper->expects($this->exactly(1)) ->method('update') ->with($feed) ->will($this->returnValue($feed)); $this->purifier->expects($this->exactly(2)) ->method('purify') ->withConsecutive(['2', null], ['1', null]) ->will($this->returnArgument(0)); $this->itemService->expects($this->exactly(2)) ->method('insertOrUpdate') ->withConsecutive([$item2], [$item1]) ->will($this->returnValue($feed)); $this->assertSame($feed, $this->class->fetch($feed)); $this->assertEquals(2, $feed->getUnreadCount()); } public function testMarkDeleted() { $feed = Feed::fromParams(['id' => 3]); $feed2 = Feed::fromParams(['id' => 3]); $feed2->setDeletedAt($this->time); $this->mapper->expects($this->once()) ->method('findFromUser') ->with($this->uid, 3) ->will($this->returnValue($feed)); $this->mapper->expects($this->once()) ->method('update') ->with($feed); $this->class->update($this->uid, $feed); } public function testUnmarkDeleted() { $feed = Feed::fromParams(['id' => 3]); $feed2 = Feed::fromParams(['id' => 3]); $feed2->setDeletedAt(0); $this->mapper->expects($this->once()) ->method('findFromUser') ->with($this->uid, 3) ->will($this->returnValue($feed)); $this->mapper->expects($this->once()) ->method('update') ->with($feed2); $this->class->update($this->uid, $feed); } public function testPurgeDeleted() { $feed1 = new Feed(); $feed1->setId(3); $feed2 = new Feed(); $feed2->setId(5); $feeds = [$feed1, $feed2]; $time = $this->time - $this->autoPurgeMinimumInterval; $this->mapper->expects($this->exactly(1)) ->method('purgeDeleted') ->with($this->uid, 1); $this->class->purgeDeleted($this->uid, 1); } public function testPurgeDeletedWithoutInterval() { $this->mapper->expects($this->exactly(1)) ->method('purgeDeleted') ->with($this->uid, false); $this->class->purgeDeleted($this->uid, false); } public function testfindAllFromAllUsers() { $expected = ['hi']; $this->mapper->expects($this->once()) ->method('findAll') ->will($this->returnValue($expected)); $result = $this->class->findAll(); $this->assertEquals($expected, $result); } public function testOrdering() { $feed = Feed::fromRow(['id' => 3]); $this->mapper->expects($this->once()) ->method('findFromUser') ->with($this->uid, $feed->getId()) ->will($this->returnValue($feed)); $feed->setOrdering(2); $this->mapper->expects($this->once()) ->method('update') ->with($feed); $this->class->update($this->uid, $feed); } public function testPatchEnableFullText() { $feed = Feed::fromRow( [ 'id' => 3, 'http_last_modified' => 1, 'full_text_enabled' => false ] ); $feed2 = Feed::fromRow(['id' => 3]); $this->mapper->expects($this->exactly(1)) ->method('findFromUser') ->with($this->uid,$feed->getId()) ->willReturnOnConsecutiveCalls($this->returnValue($feed)); $feed2->setFullTextEnabled(false); $feed2->setHttpLastModified('1'); $feed2->resetUpdatedFields(); $this->mapper->expects($this->exactly(1)) ->method('update') ->with($feed2); $this->class->update($this->uid, $feed); } public function testPatchDoesNotExist() { $this->expectException('OCA\News\Service\Exceptions\ServiceNotFoundException'); $feed = Feed::fromRow(['id' => 3]); $this->mapper->expects($this->once()) ->method('findFromUser') ->will($this->throwException(new DoesNotExistException(''))); $this->class->update($this->uid, $feed); } public function testSetPinned() { $feed = Feed::fromRow(['id' => 3, 'pinned' => false]); $this->mapper->expects($this->once()) ->method('findFromUser') ->with($this->uid, $feed->getId()) ->will($this->returnValue($feed)); $feed->setPinned(true); $this->mapper->expects($this->once()) ->method('update') ->with($feed); $this->class->update($this->uid, $feed); } public function testExistsForUser() { $feed = Feed::fromRow(['id' => 3, 'pinned' => false]); $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, 'url') ->will($this->returnValue($feed)); $this->assertTrue($this->class->existsForUser($this->uid, 'url')); } public function testDoesNotExistsForUser() { $this->mapper->expects($this->once()) ->method('findByURL') ->with($this->uid, 'url') ->will($this->throwException(new DoesNotExistException('no!'))); $this->assertFalse($this->class->existsForUser($this->uid, 'url')); } public function testFindAllFromUser() { $this->mapper->expects($this->once()) ->method('findAllFromUser') ->with($this->uid, []) ->will($this->returnValue([])); $this->assertEquals([], $this->class->findAllForUser($this->uid, [])); } public function testFindAllFromFolder() { $this->mapper->expects($this->once()) ->method('findAllFromFolder') ->with(null) ->will($this->returnValue([])); $this->assertEquals([], $this->class->findAllFromFolder(null)); } public function testFindAllFromUserRecursive() { $feed1 = new Feed(); $feed1->setId(1); $feed2 = new Feed(); $feed2->setId(2); $this->mapper->expects($this->once()) ->method('findAllFromUser') ->with($this->uid) ->will($this->returnValue([$feed1, $feed2])); $this->itemService->expects($this->exactly(2)) ->method('findAllInFeed') ->withConsecutive(['jack', 1], ['jack', 2]) ->willReturn(['a']); $feeds = $this->class->findAllForUserRecursive($this->uid); $this->assertEquals(['a'], $feeds[0]->items); $this->assertEquals(['a'], $feeds[1]->items); } public function testRead() { $feed1 = new Feed(); $feed1->setId(1); $this->mapper->expects($this->once()) ->method('findFromUser') ->with($this->uid, 1) ->will($this->returnValue($feed1)); $this->mapper->expects($this->exactly(1)) ->method('read') ->withConsecutive(['jack', 1, null]); $this->class->read($this->uid, 1); } }