From daa2c7dea555b334ffb7516f8af64ad1d090f39b Mon Sep 17 00:00:00 2001 From: Bernhard Posselt Date: Thu, 18 Apr 2013 15:56:12 +0200 Subject: added test for fetcher class fix #47 --- dependencyinjection/dicontainer.php | 6 +- tests/classloader.php | 2 + tests/unit/utility/FeedFetcherTest.php | 231 ++++++++++++++++++++++++++++++++- utility/feedfetcher.php | 127 ++++++++++-------- 4 files changed, 310 insertions(+), 56 deletions(-) diff --git a/dependencyinjection/dicontainer.php b/dependencyinjection/dicontainer.php index e637b51a2..9ea52b505 100644 --- a/dependencyinjection/dicontainer.php +++ b/dependencyinjection/dicontainer.php @@ -147,7 +147,11 @@ class DIContainer extends BaseContainer { }); $this['FeedFetcher'] = $this->share(function($c){ - return new FeedFetcher($c['API'], $c['FaviconFetcher'], + return new FeedFetcher( + $c['API'], + $c['SimplePieAPIFactory'], + $c['FaviconFetcher'], + $c['TimeFactory'], $c['simplePieCacheDirectory'], $c['simplePieCacheDuration']); }); diff --git a/tests/classloader.php b/tests/classloader.php index 310dbea07..d52656a05 100644 --- a/tests/classloader.php +++ b/tests/classloader.php @@ -21,6 +21,8 @@ * */ +require_once __DIR__ . '/../../appframework/3rdparty/SimplePie/autoloader.php'; + // to execute without owncloud, we need to create our own classloader spl_autoload_register(function ($className){ if (strpos($className, 'OCA\\') === 0) { diff --git a/tests/unit/utility/FeedFetcherTest.php b/tests/unit/utility/FeedFetcherTest.php index ba4a53db1..bb027a778 100644 --- a/tests/unit/utility/FeedFetcherTest.php +++ b/tests/unit/utility/FeedFetcherTest.php @@ -25,15 +25,89 @@ namespace OCA\News\Utility; +use \OCA\News\Db\Item; +use \OCA\News\Db\Feed; + require_once(__DIR__ . "/../../classloader.php"); class FeedFetcherTest extends \OCA\AppFramework\Utility\TestUtility { private $fetcher; + private $core; + private $coreFactory; + private $faviconFetcher; + private $url; + private $cacheDirectory; + private $cacheDuration; + private $time; + private $item; + + // items + private $permalink; + private $title; + private $guid; + private $pub; + private $body; + private $author; + private $enclosureLink; + + // feed + private $feedTitle; + private $feedLink; + private $feedImage; + private $webFavicon; protected function setUp(){ - $this->fetcher = new FeedFetcher($this->getAPIMock(), 'dir', 300); + $this->core = $this->getMockBuilder( + '\SimplePie_Core') + ->disableOriginalConstructor() + ->getMock(); + $this->coreFactory = $this->getMockBuilder( + '\OCA\AppFramework\Utility\SimplePieAPIFactory') + ->disableOriginalConstructor() + ->getMock(); + $this->coreFactory->expects($this->any()) + ->method('getCore') + ->will($this->returnValue($this->core)); + $this->item = $this->getMockBuilder( + '\SimplePie_Item') + ->disableOriginalConstructor() + ->getMock(); + $this->faviconFetcher = $this->getMockBuilder( + '\OCA\AppFramework\Utility\FaviconFetcher') + ->disableOriginalConstructor() + ->getMock(); + $this->time = 2323; + $timeFactory = $this->getMockBuilder( + '\OCA\AppFramework\Utility\TimeFactory') + ->disableOriginalConstructor() + ->getMock(); + $timeFactory->expects($this->any()) + ->method('getTime') + ->will($this->returnValue($this->time)); + $this->cacheDuration = 100; + $this->cacheDirectory = 'dir/'; + $this->fetcher = new FeedFetcher($this->getAPIMock(), + $this->coreFactory, + $this->faviconFetcher, + $timeFactory, + $this->cacheDirectory, + $this->cacheDuration); + $this->url = 'tests'; + + $this->permalink = 'http://permalink'; + $this->title = 'my title'; + $this->guid = 'hey guid here'; + $this->body = 'let the bodies hit the floor'; + $this->pub = 23111; + $this->author = 'boogieman'; + $this->enclosureLink = 'http://enclosure.you'; + + $this->feedTitle = '<e;its a title'; + $this->feedLink = 'http://goatse'; + $this->feedImage = '/an/image'; + $this->webFavicon = 'http://anon.google.com'; } @@ -43,5 +117,156 @@ class FeedFetcherTest extends \OCA\AppFramework\Utility\TestUtility { $this->assertTrue($this->fetcher->canHandle($url)); } - // TODO: write tests for the remaining methods -} \ No newline at end of file + + public function testFetchThrowsExceptionWhenInitFailed() { + $this->core->expects($this->once()) + ->method('set_feed_url') + ->with($this->equalTo($this->url)); + $this->core->expects($this->once()) + ->method('enable_cache') + ->with($this->equalTo(true)); + $this->core->expects($this->once()) + ->method('set_cache_location') + ->with($this->equalTo($this->cacheDirectory)); + $this->core->expects($this->once()) + ->method('set_cache_duration') + ->with($this->equalTo($this->cacheDuration)); + $this->setExpectedException('\OCA\News\Utility\FetcherException'); + $this->fetcher->fetch($this->url); + } + + + public function testShouldCatchExceptionsAndThrowOwnException() { + $this->core->expects($this->once()) + ->method('init') + ->will($this->returnValue(true)); + $this->core->expects($this->once()) + ->method('get_items') + ->will($this->throwException(new \Exception('oh noes!'))); + $this->setExpectedException('\OCA\News\Utility\FetcherException'); + $this->fetcher->fetch($this->url); + } + + + private function expectCore($method, $return) { + $this->core->expects($this->once()) + ->method($method) + ->will($this->returnValue($return)); + } + + private function expectItem($method, $return) { + $this->item->expects($this->once()) + ->method($method) + ->will($this->returnValue($return)); + } + + + private function createItem($author=false, $enclosureType=null) { + $this->expectItem('get_permalink', $this->permalink); + $this->expectItem('get_title', $this->title); + $this->expectItem('get_id', $this->guid); + $this->expectItem('get_content', $this->body); + $this->expectItem('get_date', $this->pub); + + $item = new Item(); + $item->setStatus(0); + $item->setUnread(); + $item->setUrl($this->permalink); + $item->setTitle($this->title); + $item->setGuid($this->guid); + $item->setGuidHash(md5($this->guid)); + $item->setBody($this->body); + $item->setPubDate($this->pub); + $item->setLastModified($this->time); + if($author) { + $mock = $this->getMock('author', array('get_name')); + $mock->expects($this->once()) + ->method('get_name') + ->will($this->returnValue($this->author)); + $this->expectItem('get_author', $mock); + $item->setAuthor($this->author); + } + + if($enclosureType === 'audio/ogg') { + $mock = $this->getMock('enclosure', array('get_type', 'get_link')); + $mock->expects($this->any()) + ->method('get_type') + ->will($this->returnValue($enclosureType)); + $this->expectItem('get_enclosure', $this->mock); + $item->setEnclosureMime($enclosureType); + $item->setEnclosureLink($this->enclosureLink); + } + return $item; + } + + + private function createFeed($hasFavicon=false, $hasWebFavicon=false) { + $this->expectCore('get_title', $this->feedTitle); + $this->expectCore('get_link', $this->feedLink); + + $feed = new Feed(); + $feed->setTitle(html_entity_decode($this->feedTitle)); + $feed->setUrl($this->url); + $feed->setLink($this->feedLink); + $feed->setUrlHash(md5($this->url)); + $feed->setAdded($this->time); + + if($hasFavicon) { + $this->expectCore('get_image_url', $this->feedImage); + $feed->setFaviconLink($this->feedImage); + } else { + $feed->setFaviconLink(null); + $this->expectCore('get_image_url', null); + } + + if($hasWebFavicon) { + $this->faviconFetcher->expects($this->once()) + ->method('fetch') + ->with($this->equalTo($this->feedLink)) + ->will($this->returnValue($this->webFavicon)); + $feed->setFaviconLink($this->webFavicon); + } + + return $feed; + } + + + public function testFetchMapItems(){ + $this->core->expects($this->once()) + ->method('init') + ->will($this->returnValue(true)); + $item = $this->createItem(); + $feed = $this->createFeed(); + $this->expectCore('get_items', array($this->item)); + $result = $this->fetcher->fetch($this->url); + + $this->assertEquals(array($feed, array($item)), $result); + } + + + public function testFetchMapItemsAuthorExists(){ + $this->core->expects($this->once()) + ->method('init') + ->will($this->returnValue(true)); + $item = $this->createItem(true); + $feed = $this->createFeed(true); + $this->expectCore('get_items', array($this->item)); + $result = $this->fetcher->fetch($this->url); + + $this->assertEquals(array($feed, array($item)), $result); + } + + + public function testFetchMapItemsEnclosureExists(){ + $this->core->expects($this->once()) + ->method('init') + ->will($this->returnValue(true)); + $item = $this->createItem(false, true); + $feed = $this->createFeed(false, true); + $this->expectCore('get_items', array($this->item)); + $result = $this->fetcher->fetch($this->url); + + $this->assertEquals(array($feed, array($item)), $result); + } + +} diff --git a/utility/feedfetcher.php b/utility/feedfetcher.php index 1113877d0..b6b0161da 100644 --- a/utility/feedfetcher.php +++ b/utility/feedfetcher.php @@ -27,6 +27,8 @@ namespace OCA\News\Utility; use \OCA\AppFramework\Core\API; use \OCA\AppFramework\Utility\FaviconFetcher; +use \OCA\AppFramework\Utility\SimplePieAPIFactory; +use \OCA\AppFramework\Utility\TimeFactory; use \OCA\News\Db\Item; use \OCA\News\Db\Feed; @@ -38,13 +40,21 @@ class FeedFetcher implements IFeedFetcher { private $cacheDirectory; private $cacheDuration; private $faviconFetcher; - - public function __construct(API $api, FaviconFetcher $faviconFetcher, - $cacheDirectory, $cacheDuration){ + private $simplePieFactory; + private $time; + + public function __construct(API $api, + SimplePieAPIFactory $simplePieFactory, + FaviconFetcher $faviconFetcher, + TimeFactory $time, + $cacheDirectory, + $cacheDuration){ $this->api = $api; $this->cacheDirectory = $cacheDirectory; $this->cacheDuration = $cacheDuration; $this->faviconFetcher = $faviconFetcher; + $this->simplePieFactory = $simplePieFactory; + $this->time = $time; } @@ -63,9 +73,8 @@ class FeedFetcher implements IFeedFetcher { * @return array an array containing the new feed and its items */ public function fetch($url) { - // TODO: write unittests! - $simplePie = new \SimplePie_Core(); - $simplePie->set_feed_url( $url ); + $simplePie = $this->simplePieFactory->getCore(); + $simplePie->set_feed_url($url); $simplePie->enable_cache(true); $simplePie->set_cache_location($this->cacheDirectory); $simplePie->set_cache_duration($this->cacheDuration); @@ -74,59 +83,17 @@ class FeedFetcher implements IFeedFetcher { throw new FetcherException('Could not initialize simple pie'); } + try { + // somehow $simplePie turns into a feed after init $items = array(); if ($feedItems = $simplePie->get_items()) { foreach($feedItems as $feedItem) { - $item = new Item(); - $item->setStatus(0); - $item->setUnread(); - $item->setUrl( $feedItem->get_permalink() ); - // unescape content because angularjs helps agains XSS - $item->setTitle(html_entity_decode($feedItem->get_title())); - $item->setGuid( $feedItem->get_id() ); - $item->setGuidHash( md5($feedItem->get_id()) ); - $item->setBody( $feedItem->get_content() ); - $item->setPubDate( $feedItem->get_date('U') ); - $item->setLastModified(time()); - - $author = $feedItem->get_author(); - if ($author !== null) { - $item->setAuthor( $author->get_name() ); - } - - // TODO: make it work for video files also - $enclosure = $feedItem->get_enclosure(); - if($enclosure !== null) { - $enclosureType = $enclosure->get_type(); - if(stripos($enclosureType, "audio/") !== false) { - $item->setEnclosureMime($enclosureType); - $item->setEnclosureLink($enclosure->get_link()); - } - } - - array_push($items, $item); + array_push($items, $this->buildItem($feedItem)); } } - $feed = new Feed(); - // unescape content because angularjs helps agains XSS - $feed->setTitle(html_entity_decode($simplePie->get_title())); - $feed->setUrl($url); - $feed->setLink($simplePie->get_link()); - $feed->setUrlHash(md5($url)); - $feed->setAdded(time()); - - // get the favicon from the feed - $favicon = $simplePie->get_image_url(); - if ($favicon) { - $feed->setFaviconLink($favicon); - - // or the webpage - } else { - $webFavicon = $this->faviconFetcher->fetch($feed->getLink()); - $feed->setFaviconLink($webFavicon); - } + $feed = $this->buildFeed($simplePie, $url); return array($feed, $items); @@ -137,4 +104,60 @@ class FeedFetcher implements IFeedFetcher { } + protected function buildItem($simplePieItem) { + $item = new Item(); + $item->setStatus(0); + $item->setUnread(); + $item->setUrl($simplePieItem->get_permalink()); + // unescape content because angularjs helps agains XSS + $item->setTitle(html_entity_decode($simplePieItem->get_title())); + $guid = $simplePieItem->get_id(); + $item->setGuid($guid); + $item->setGuidHash(md5($guid)); + $item->setBody($simplePieItem->get_content()); + $item->setPubDate($simplePieItem->get_date('U')); + $item->setLastModified($this->time->getTime()); + + $author = $simplePieItem->get_author(); + if ($author !== null) { + $item->setAuthor($author->get_name()); + } + + // TODO: make it work for video files also + $enclosure = $simplePieItem->get_enclosure(); + if($enclosure !== null) { + $enclosureType = $enclosure->get_type(); + if(stripos($enclosureType, "audio/") !== false) { + $item->setEnclosureMime($enclosureType); + $item->setEnclosureLink($enclosure->get_link()); + } + } + + return $item; + } + + + protected function buildFeed($simplePieFeed, $url) { + $feed = new Feed(); + + // unescape content because angularjs helps agains XSS + $feed->setTitle(html_entity_decode($simplePieFeed->get_title())); + $feed->setUrl($url); + $feed->setLink($simplePieFeed->get_link()); + $feed->setUrlHash(md5($url)); + $feed->setAdded($this->time->getTime()); + + // get the favicon from the feed or the webpage + $favicon = $simplePieFeed->get_image_url(); + + if ($favicon) { + $feed->setFaviconLink($favicon); + } else { + $webFavicon = $this->faviconFetcher->fetch($feed->getLink()); + $feed->setFaviconLink($webFavicon); + } + + return $feed; + } + } \ No newline at end of file -- cgit v1.2.3