Remove PHPunit integration tests

Signed-off-by: Sean Molenaar <sean@seanmolenaar.eu>
This commit is contained in:
Sean Molenaar 2020-12-26 13:09:41 +01:00 committed by Sean Molenaar
parent 27bd540580
commit 05377d023e
40 changed files with 2431 additions and 2436 deletions

View File

@ -1,10 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), older entries don't fully match.
## [Unreleased]
### Changed
- Remove outdated feed DB code
- add background & hover for entries
## [15.1.1] - 2020-12-27
### Changed

View File

@ -51,7 +51,7 @@ class UpdateFeed extends Command
$feedId = $input->getArgument('feed-id');
$userId = $input->getArgument('user-id');
try {
$feed = $this->feedService->findForUser($userId, $feedId);
$feed = $this->feedService->find($userId, $feedId);
$updated_feed = $this->feedService->fetch($feed);
} catch (\Exception $e) {
$output->writeln(

View File

@ -1,76 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Controller;
use \OCA\News\Db\IAPI;
/**
* Class EntityApiSerializer
*
* @package OCA\News\Controller
* @deprecated use ApiPayloadTrait
*/
class EntityApiSerializer
{
private $level;
public function __construct($level)
{
$this->level = $level;
}
/**
* Call toAPI() method on all entities. Works on
*
* @param mixed $data :
* * Entity
* * Entity[]
* * array('level' => Entity[])
* * Response
* @return array|mixed
*/
public function serialize($data)
{
if ($data instanceof IAPI) {
return [$this->level => [$data->toAPI()]];
}
if (is_array($data) && array_key_exists($this->level, $data)) {
$data[$this->level] = $this->convert($data[$this->level]);
} elseif (is_array($data)) {
$data = [$this->level => $this->convert($data)];
}
return $data;
}
private function convert(array $entities)
{
$converted = [];
foreach ($entities as $entity) {
if ($entity instanceof IAPI) {
$converted[] = $entity->toAPI();
// break if it contains anything else than entities
} else {
return $entities;
}
}
return $converted;
}
}

View File

@ -25,10 +25,8 @@ use \OCP\IRequest;
use \OCP\IUserSession;
use \OCP\AppFramework\Http;
use \OCA\News\Service\FeedService;
use \OCA\News\Service\ItemService;
use Psr\Log\LoggerInterface;
use function GuzzleHttp\Psr7\uri_for;
class FeedApiController extends ApiController
{
@ -45,12 +43,6 @@ class FeedApiController extends ApiController
*/
private $feedService;
/**
* TODO: Remove
* @var FeedService
*/
private $oldFeedService;
/**
* @var LoggerInterface
*/
@ -64,14 +56,12 @@ class FeedApiController extends ApiController
public function __construct(
IRequest $request,
?IUserSession $userSession,
FeedService $oldFeedService,
FeedServiceV2 $feedService,
ItemService $oldItemService,
LoggerInterface $logger
) {
parent::__construct($request, $userSession);
$this->feedService = $feedService;
$this->oldFeedService = $oldFeedService;
$this->oldItemService = $oldItemService;
$this->logger = $logger;
}
@ -189,11 +179,9 @@ class FeedApiController extends ApiController
}
try {
$this->oldFeedService->patch(
$feedId,
$this->getUserId(),
['folderId' => $folderId]
);
$feed = $this->feedService->find($this->getUserId(), $feedId);
$feed->setFolderId($folderId);
$this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -215,11 +203,9 @@ class FeedApiController extends ApiController
public function rename(int $feedId, string $feedTitle)
{
try {
$this->oldFeedService->patch(
$feedId,
$this->getUserId(),
['title' => $feedTitle]
);
$feed = $this->feedService->find($this->getUserId(), $feedId);
$feed->setTitle($feedTitle);
$this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}

View File

@ -15,14 +15,15 @@ namespace OCA\News\Controller;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IConfig;
use OCP\AppFramework\Http;
use OCA\News\Service\ItemService;
use OCA\News\Service\FeedService;
use OCA\News\Db\FeedType;
use OCP\IUserSession;
@ -30,7 +31,9 @@ class FeedController extends Controller
{
use JSONHttpErrorTrait;
//TODO: Remove
/**
* @var FeedServiceV2
*/
private $feedService;
//TODO: Remove
private $itemService;
@ -38,6 +41,10 @@ class FeedController extends Controller
* @var FolderServiceV2
*/
private $folderService;
/**
* @var ImportService
*/
private $importService;
/**
* @var IConfig
*/
@ -46,8 +53,9 @@ class FeedController extends Controller
public function __construct(
IRequest $request,
FolderServiceV2 $folderService,
FeedService $feedService,
FeedServiceV2 $feedService,
ItemService $itemService,
ImportService $importService,
IConfig $settings,
?IUserSession $userSession
) {
@ -55,6 +63,7 @@ class FeedController extends Controller
$this->folderService = $folderService;
$this->feedService = $feedService;
$this->itemService = $itemService;
$this->importService = $importService;
$this->settings = $settings;
}
@ -74,12 +83,13 @@ class FeedController extends Controller
];
try {
$params['newestItemId'] =
$this->itemService->getNewestItemId($this->getUserId());
$id = $this->itemService->getNewestItemId($this->getUserId());
// An exception occurs if there is a newest item. If there is none,
// simply ignore it and do not add the newestItemId
$params['newestItemId'] = $id;
} catch (ServiceNotFoundException $ex) {
//NO-OP
}
return $params;
@ -102,24 +112,22 @@ class FeedController extends Controller
'lastViewedFeedType'
);
// cast from null to int is 0
if ($feedType !== null) {
$feedType = (int) $feedType;
}
// check if feed or folder exists
try {
if ($feedType === FeedType::FOLDER) {
if ($feedId === 0) {
$feedId = null;
}
$this->folderService->find($this->getUserId(), $feedId);
} elseif ($feedType === FeedType::FEED) {
$this->feedService->find($this->getUserId(), $feedId);
if ($feedType === null) {
throw new ServiceNotFoundException('First launch');
}
// if its the first launch, those values will be null
} elseif ($feedType === null) {
throw new ServiceNotFoundException('');
$feedType = intval($feedType);
switch ($feedType) {
case FeedType::FOLDER:
$this->folderService->find($this->getUserId(), $feedId);
break;
case FeedType::FEED:
$this->feedService->find($this->getUserId(), $feedId);
break;
default:
break;
}
} catch (ServiceNotFoundException $ex) {
$feedId = 0;
@ -162,9 +170,10 @@ class FeedController extends Controller
$this->feedService->purgeDeleted($this->getUserId(), false);
$feed = $this->feedService->create(
$this->getUserId(),
$url,
$parentFolderId,
$this->getUserId(),
false,
$title,
$user,
$password
@ -172,12 +181,12 @@ class FeedController extends Controller
$params = ['feeds' => [$feed]];
try {
$params['newestItemId'] =
$this->itemService->getNewestItemId($this->getUserId());
$id = $this->itemService->getNewestItemId($this->getUserId());
// An exception occurs if there is a newest item. If there is none,
// simply ignore it and do not add the newestItemId
$params['newestItemId'] = $id;
} catch (ServiceNotFoundException $ex) {
//NO-OP
}
return $params;
@ -199,7 +208,9 @@ class FeedController extends Controller
public function delete(int $feedId)
{
try {
$this->feedService->markDeleted($feedId, $this->getUserId());
$feed = $this->feedService->find($this->getUserId(), $feedId);
$feed->setDeletedAt(time());
$this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -218,7 +229,8 @@ class FeedController extends Controller
public function update(int $feedId)
{
try {
$feed = $this->feedService->update($this->getUserId(), $feedId);
$old_feed = $this->feedService->find($this->getUserId(), $feedId);
$feed = $this->feedService->fetch($old_feed);
return [
'feeds' => [
@ -244,7 +256,7 @@ class FeedController extends Controller
*/
public function import(array $json): array
{
$feed = $this->feedService->importArticles($json, $this->getUserId());
$feed = $this->importService->importArticles($this->getUserId(), $json);
$params = [
'starred' => $this->itemService->starredCount($this->getUserId())
@ -290,7 +302,9 @@ class FeedController extends Controller
public function restore(int $feedId)
{
try {
$this->feedService->unmarkDeleted($feedId, $this->getUserId());
$feed = $this->feedService->find($this->getUserId(), $feedId);
$feed->setDeletedAt(null);
$this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -320,24 +334,32 @@ class FeedController extends Controller
?int $folderId = null,
?string $title = null
) {
$attributes = [
'pinned' => $pinned,
'fullTextEnabled' => $fullTextEnabled,
'updateMode' => $updateMode,
'ordering' => $ordering,
'title' => $title,
'folderId' => $folderId === 0 ? null : $folderId
];
try {
$feed = $this->feedService->find($this->getUserId(), $feedId);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
$diff = array_filter(
$attributes,
function ($value) {
return $value !== null;
}
);
$fId = $folderId === 0 ? null : $folderId;
$feed->setFolderId($fId);
if ($pinned !== null) {
$feed->setPinned($pinned);
}
if ($fullTextEnabled !== null) {
$feed->setFullTextEnabled($fullTextEnabled);
}
if ($updateMode !== null) {
$feed->setUpdateMode($updateMode);
}
if ($ordering !== null) {
$feed->setOrdering($ordering);
}
if ($title !== null) {
$feed->setTitle($title);
}
try {
$this->feedService->patch($feedId, $this->getUserId(), $diff);
$this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}

View File

@ -14,12 +14,12 @@
namespace OCA\News\Controller;
use OCA\News\Service\Exceptions\ServiceException;
use OCA\News\Service\FeedServiceV2;
use OCP\AppFramework\Http\JSONResponse;
use \OCP\IRequest;
use \OCP\AppFramework\Http;
use \OCA\News\Service\FolderServiceV2;
use \OCA\News\Service\FeedService;
use \OCA\News\Service\ItemService;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
use \OCA\News\Service\Exceptions\ServiceConflictException;
@ -33,7 +33,9 @@ class FolderController extends Controller
* @var FolderServiceV2
*/
private $folderService;
//TODO: Remove
/**
* @var FeedServiceV2
*/
private $feedService;
//TODO: Remove
private $itemService;
@ -41,7 +43,7 @@ class FolderController extends Controller
public function __construct(
IRequest $request,
FolderServiceV2 $folderService,
FeedService $feedService,
FeedServiceV2 $feedService,
ItemService $itemService,
?IUserSession $userSession
) {

View File

@ -13,6 +13,7 @@
namespace OCA\News\Controller;
use OCA\News\Service\FeedServiceV2;
use \OCP\IRequest;
use \OCP\IConfig;
use \OCP\AppFramework\Http;
@ -20,7 +21,6 @@ use \OCP\AppFramework\Http;
use \OCA\News\Service\Exceptions\ServiceException;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
use \OCA\News\Service\ItemService;
use \OCA\News\Service\FeedService;
use OCP\IUserSession;
class ItemController extends Controller
@ -28,12 +28,18 @@ class ItemController extends Controller
use JSONHttpErrorTrait;
private $itemService;
/**
* @var FeedServiceV2
*/
private $feedService;
/**
* @var IConfig
*/
private $settings;
public function __construct(
IRequest $request,
FeedService $feedService,
FeedServiceV2 $feedService,
ItemService $itemService,
IConfig $settings,
?IUserSession $userSession

View File

@ -25,6 +25,16 @@ class Feed extends Entity implements IAPI, \JsonSerializable
{
use EntityJSONSerializer;
/**
* Silently import new items
*/
const UPDATE_MODE_SILENT = 0;
/**
* Mark new items as unread.
*/
const UPDATE_MODE_NORMAL = 1;
/** @var string */
protected $userId = '';
/** @var string */

View File

@ -1,202 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Db;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IDBConnection;
use OCP\AppFramework\Db\Entity;
/**
* Class LegacyFeedMapper
*
* @package OCA\News\Db
* @deprecated use FeedMapper
*/
class FeedMapper extends NewsMapper
{
const TABLE_NAME = 'news_feeds';
public function __construct(IDBConnection $db, Time $time)
{
parent::__construct($db, $time, Feed::class);
}
public function find(string $userId, int $id)
{
$sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'JOIN ( ' .
'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'LEFT JOIN `*PREFIX*news_items` `items` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
// WARNING: this is a desperate attempt at making this query
// work because prepared statements dont work. This is a
// POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT.
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE `feeds`.`id` = ? ' .
'AND `feeds`.`user_id` = ? ' .
'GROUP BY `feeds`.`id` ' .
') `item_numbers` ' .
'ON `item_numbers`.`id` = `feeds`.`id` ';
$params = [true, $id, $userId];
return $this->findEntity($sql, $params);
}
public function findAllFromUser(string $userId): array
{
$sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'JOIN ( ' .
'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `feeds`.`folder_id` = `folders`.`id` ' .
'LEFT JOIN `*PREFIX*news_items` `items` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
// WARNING: this is a desperate attempt at making this query
// work because prepared statements dont work. This is a
// POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT.
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE `feeds`.`user_id` = ? ' .
'AND (`feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
') ' .
'AND `feeds`.`deleted_at` = 0 ' .
'GROUP BY `feeds`.`id` ' .
') `item_numbers` ' .
'ON `item_numbers`.`id` = `feeds`.`id` ';
$params = [true, $userId];
return $this->findEntities($sql, $params);
}
public function findAll(): array
{
$sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'JOIN ( ' .
'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `feeds`.`folder_id` = `folders`.`id` ' .
'LEFT JOIN `*PREFIX*news_items` `items` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
// WARNING: this is a desperate attempt at making this query
// work because prepared statements dont work. This is a
// POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT.
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE (`feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
') ' .
'AND `feeds`.`deleted_at` = 0 ' .
'GROUP BY `feeds`.`id` ' .
') `item_numbers` ' .
'ON `item_numbers`.`id` = `feeds`.`id` ';
return $this->findEntities($sql, [true]);
}
public function findByUrlHash($hash, $userId)
{
$sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'JOIN ( ' .
'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' .
'FROM `*PREFIX*news_feeds` `feeds` ' .
'LEFT JOIN `*PREFIX*news_items` `items` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
// WARNING: this is a desperate attempt at making this query
// work because prepared statements dont work. This is a
// POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT.
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE `feeds`.`url_hash` = ? ' .
'AND `feeds`.`user_id` = ? ' .
'GROUP BY `feeds`.`id` ' .
') `item_numbers` ' .
'ON `item_numbers`.`id` = `feeds`.`id` ';
$params = [true, $hash, $userId];
return $this->findEntity($sql, $params);
}
public function delete(Entity $entity): Entity
{
// someone please slap me for doing this manually :P
// we needz CASCADE + FKs please
$sql = 'DELETE FROM `*PREFIX*news_items` WHERE `feed_id` = ?';
$params = [$entity->getId()];
$this->execute($sql, $params);
return parent::delete($entity);
}
/**
* @param int $deleteOlderThan if given gets all entries with a delete date
* older than that timestamp
* @param string $userId if given returns only entries from the given user
* @return array with the database rows
*/
public function getToDelete($deleteOlderThan = null, $userId = null)
{
$sql = 'SELECT * FROM `*PREFIX*news_feeds` ' .
'WHERE `deleted_at` > 0 ';
$params = [];
// sometimes we want to delete all entries
if ($deleteOlderThan !== null) {
$sql .= 'AND `deleted_at` < ? ';
$params[] = $deleteOlderThan;
}
// we need to sometimes only delete feeds of a user
if ($userId !== null) {
$sql .= 'AND `user_id` = ?';
$params[] = $userId;
}
return $this->findEntities($sql, $params);
}
/**
* Deletes all feeds of a user, delete items first since the user_id
* is not defined in there
*
* @param string $userId the name of the user
*/
public function deleteUser($userId)
{
$sql = 'DELETE FROM `*PREFIX*news_feeds` WHERE `user_id` = ?';
$this->execute($sql, [$userId]);
}
public function findFromUser(string $userId, int $id): Entity
{
return $this->find($userId, $id);
}
}

View File

@ -52,7 +52,7 @@ class FeedMapperV2 extends NewsMapperV2
{
$builder = $this->db->getQueryBuilder();
$builder->select('feeds.*', $builder->func()->count('items.id', 'unreadCount'))
->from($this->tableName, 'feeds')
->from(static::TABLE_NAME, 'feeds')
->leftJoin(
'feeds',
ItemMapperV2::TABLE_NAME,
@ -82,8 +82,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findFromUser(string $userId, int $id): Entity
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
->from($this->tableName)
$builder->select('*')
->from(static::TABLE_NAME)
->where('user_id = :user_id')
->andWhere('id = :id')
->setParameter(':user_id', $userId)
@ -101,7 +101,7 @@ class FeedMapperV2 extends NewsMapperV2
{
$builder = $this->db->getQueryBuilder();
$builder->select('*')
->from($this->tableName)
->from(static::TABLE_NAME)
->where('deleted_at = 0');
return $this->findEntities($builder);
@ -121,8 +121,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findByURL(string $userId, string $url): Entity
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
->from($this->tableName)
$builder->select('*')
->from(static::TABLE_NAME)
->where('user_id = :user_id')
->andWhere('url = :url')
->setParameter(':user_id', $userId)
@ -141,8 +141,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findAllFromFolder(?int $id): array
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
->from($this->tableName);
$builder->select('*')
->from(static::TABLE_NAME);
if (is_null($id)) {
$builder->where('folder_id IS NULL');

View File

@ -17,6 +17,7 @@ use Exception;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\Mapper;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@ -27,13 +28,25 @@ use OCP\IDBConnection;
* @package OCA\News\Db
* @deprecated use ItemMapper
*/
class ItemMapper extends NewsMapper
class ItemMapper extends Mapper
{
const TABLE_NAME = 'news_items';
/**
* @var Time
*/
private $time;
/**
* NewsMapper constructor.
*
* @param IDBConnection $db Database connection
* @param Time $time Time class
*/
public function __construct(IDBConnection $db, Time $time)
{
parent::__construct($db, $time, Item::class);
parent::__construct($db, static::TABLE_NAME, Item::class);
$this->time = $time;
}
private function makeSelectQuery(
@ -107,7 +120,7 @@ class ItemMapper extends NewsMapper
/**
* @param int $id
* @param string $userId
* @return \OCA\News\Db\Item
* @return \OCA\News\Db\Item|Entity
*/
public function find(string $userId, int $id)
{
@ -332,7 +345,15 @@ class ItemMapper extends NewsMapper
return $this->findEntities($sql, $params);
}
/**
* @param $guidHash
* @param $feedId
* @param $userId
*
* @return Entity|Item
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findByGuidHash($guidHash, $feedId, $userId)
{
$sql = $this->makeSelectQuery(
@ -411,23 +432,6 @@ class ItemMapper extends NewsMapper
}
/**
* Deletes all items of a user
*
* @param string $userId the name of the user
*/
public function deleteUser($userId)
{
$sql = 'DELETE FROM `*PREFIX*news_items` ' .
'WHERE `feed_id` IN (' .
'SELECT `feeds`.`id` FROM `*PREFIX*news_feeds` `feeds` ' .
'WHERE `feeds`.`user_id` = ?' .
')';
$this->execute($sql, [$userId]);
}
/**
* Returns a list of ids and userid of all items
*/
@ -492,29 +496,74 @@ class ItemMapper extends NewsMapper
}
}
/**
* NO-OP
*
* @param string $userId
*
* @return array
*/
public function findAllFromUser(string $userId): array
public function update(Entity $entity): Entity
{
return [];
$entity->setLastModified($this->time->getMicroTime());
return parent::update($entity);
}
public function findFromUser(string $userId, int $id): Entity
public function insert(Entity $entity): Entity
{
return $this->find($id, $userId);
$entity->setLastModified($this->time->getMicroTime());
return parent::insert($entity);
}
/**
* NO-OP
* @return array
* Remove deleted items.
*
* @return void
*/
public function findAll(): array
public function purgeDeleted(): void
{
return [];
$builder = $this->db->getQueryBuilder();
$builder->delete($this->tableName)
->where('deleted_at != 0')
->execute();
}
/**
* Performs a SELECT query with all arguments appened to the WHERE clause
* The SELECT will be performed on the current table and take the entity
* that is related for transforming the properties into column names
*
* Important: This method does not filter marked as deleted rows!
*
* @param array $search an assoc array from property to filter value
* @param int|null $limit Output limit
* @param int|null $offset Output offset
*
* @depreacted Legacy function
*
* @return Entity[]
*/
public function where(array $search = [], ?int $limit = null, ?int $offset = null)
{
$entity = new $this->entityClass();
// turn keys into sql query filter, e.g. feedId -> feed_id = :feedId
$filter = array_map(
function ($property) use ($entity) {
// check if the property actually exists on the entity to prevent
// accidental Sql injection
if (!property_exists($entity, $property)) {
$msg = 'Property ' . $property . ' does not exist on '
. $this->entityClass;
throw new \BadFunctionCallException($msg);
}
$column = $entity->propertyToColumn($property);
return $column . ' = :' . $property;
},
array_keys($search)
);
$andStatement = implode(' AND ', $filter);
$sql = 'SELECT * FROM `' . $this->getTableName() . '`';
if (count($search) > 0) {
$sql .= 'WHERE ' . $andStatement;
}
return $this->findEntities($sql, $search, $limit, $offset);
}
}

View File

@ -120,6 +120,11 @@ class ItemMapperV2 extends NewsMapperV2
return $this->findEntity($builder);
}
/**
* @param int $feedId
*
* @return array
*/
public function findAllForFeed(int $feedId): array
{
$builder = $this->db->getQueryBuilder();

View File

@ -1,158 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Db;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\IDBConnection;
use OCP\AppFramework\Db\Mapper;
use OCP\AppFramework\Db\Entity;
/**
* Class NewsMapper
*
* @package OCA\News\Db
*/
abstract class NewsMapper extends Mapper
{
const TABLE_NAME = '';
/**
* @var Time
*/
private $time;
/**
* NewsMapper constructor.
*
* @param IDBConnection $db Database connection
* @param Time $time Time class
* @param string $entity Entity class
*/
public function __construct(
IDBConnection $db,
Time $time,
string $entity
) {
parent::__construct($db, static::TABLE_NAME, $entity);
$this->time = $time;
}
public function update(Entity $entity): Entity
{
$entity->setLastModified($this->time->getMicroTime());
return parent::update($entity);
}
public function insert(Entity $entity): Entity
{
$entity->setLastModified($this->time->getMicroTime());
return parent::insert($entity);
}
/**
* Remove deleted items.
*
* @return void
*/
public function purgeDeleted(): void
{
$builder = $this->db->getQueryBuilder();
$builder->delete($this->tableName)
->where('deleted_at != 0')
->execute();
}
abstract public function find(string $userId, int $id);
/**
* Find all items.
*
* @return Entity[]
*/
abstract public function findAll(): array;
/**
* Find all items for a user.
*
* @param string $userId ID of the user
*
* @return Entity[]
*/
abstract public function findAllFromUser(string $userId): array;
/**
* Find item for a user.
*
* @param string $userId ID of the user
* @param int $id ID of the item
*
* @return Feed
*
* @throws DoesNotExistException The item is not found
* @throws MultipleObjectsReturnedException Multiple items found
*/
abstract public function findFromUser(string $userId, int $id): Entity;
/**
* Performs a SELECT query with all arguments appened to the WHERE clause
* The SELECT will be performed on the current table and take the entity
* that is related for transforming the properties into column names
*
* Important: This method does not filter marked as deleted rows!
*
* @param array $search an assoc array from property to filter value
* @param int|null $limit Output limit
* @param int|null $offset Output offset
*
* @depreacted Legacy function
*
* @return array
*/
public function where(array $search = [], ?int $limit = null, ?int $offset = null)
{
$entity = new $this->entityClass();
// turn keys into sql query filter, e.g. feedId -> feed_id = :feedId
$filter = array_map(
function ($property) use ($entity) {
// check if the property actually exists on the entity to prevent
// accidental Sql injection
if (!property_exists($entity, $property)) {
$msg = 'Property ' . $property . ' does not exist on '
. $this->entityClass;
throw new \BadFunctionCallException($msg);
}
$column = $entity->propertyToColumn($property);
return $column . ' = :' . $property;
},
array_keys($search)
);
$andStatement = implode(' AND ', $filter);
$sql = 'SELECT * FROM `' . $this->getTableName() . '`';
if (count($search) > 0) {
$sql .= 'WHERE ' . $andStatement;
}
return $this->findEntities($sql, $search, $limit, $offset);
}
}

View File

@ -14,6 +14,8 @@
namespace OCA\News\Fetcher;
use FeedIo\Reader\ReadErrorException;
use OCA\News\Db\Feed;
use OCA\News\Db\Item;
interface IFeedFetcher
{
@ -29,7 +31,7 @@ interface IFeedFetcher
* @param string|null $user if given, basic auth is set for this feed
* @param string|null $password if given, basic auth is set for this feed. Ignored if user is empty
*
* @return array an array containing the new feed and its items, first
* @return <Feed, Item[]> an array containing the new feed and its items, first
* element being the Feed and second element being an array of Items
*
* @throws ReadErrorException if the Feed-IO fetcher encounters a problem

View File

@ -14,9 +14,9 @@
namespace OCA\News\Hooks;
use OCA\News\AppInfo\Application;
use OCA\News\Service\ItemService;
use OCA\News\Service\FeedService;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ItemServiceV2;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\BeforeUserDeletedEvent;
@ -37,8 +37,8 @@ class UserDeleteHook implements IEventListener
$container = $app->getContainer();
// order is important!
$container->get(ItemService::class)->deleteUser($userId);
$container->get(FeedService::class)->deleteUser($userId);
$container->get(ItemServiceV2::class)->deleteUser($userId);
$container->get(FeedServiceV2::class)->deleteUser($userId);
$container->get(FolderServiceV2::class)->deleteUser($userId);
}
}

View File

@ -1,521 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Service;
use FeedIo\Reader\ReadErrorException;
use HTMLPurifier;
use OCA\News\AppInfo\Application;
use OCP\IConfig;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCP\AppFramework\Db\Entity;
use OCP\IL10N;
use OCP\AppFramework\Db\DoesNotExistException;
use OCA\News\Db\Feed;
use OCA\News\Db\Item;
use OCA\News\Db\FeedMapper;
use OCA\News\Db\ItemMapper;
use OCA\News\Fetcher\Fetcher;
use OCA\News\Utility\Time;
use Psr\Log\LoggerInterface;
/**
* Class LegacyFeedService
*
* @package OCA\News\Service
* @deprecated use FeedServiceV2
*/
class FeedService extends Service
{
private $feedFetcher;
private $itemMapper;
private $feedMapper;
private $l10n;
private $timeFactory;
private $autoPurgeMinimumInterval;
private $purifier;
private $loggerParams;
public function __construct(
FeedMapper $legacyFeedMapper,
Fetcher $feedFetcher,
ItemMapper $legacyItemMapper,
LoggerInterface $logger,
IL10N $l10n,
Time $timeFactory,
IConfig $config,
HTMLPurifier $purifier
) {
parent::__construct($legacyFeedMapper, $logger);
$this->feedFetcher = $feedFetcher;
$this->feedMapper = $legacyFeedMapper;
$this->itemMapper = $legacyItemMapper;
$this->logger = $logger;
$this->l10n = $l10n;
$this->timeFactory = $timeFactory;
$this->autoPurgeMinimumInterval = $config->getAppValue(
Application::NAME,
'autoPurgeMinimumInterval',
Application::DEFAULT_SETTINGS['autoPurgeMinimumInterval']
);
$this->purifier = $purifier;
$this->loggerParams = ['app' => Application::NAME];
}
/**
* Finds all feeds of a user
*
* @param string $userId the name of the user
*
* @return Feed[]
*/
public function findAllForUser($userId, array $params = []): array
{
return $this->feedMapper->findAllFromUser($userId);
}
/**
* Finds all feeds from all users
*
* @return array of feeds
*/
public function findAllFromAllUsers()
{
return $this->findAll();
}
/**
* Creates a new feed
*
* @param string $feedUrl the url to the feed
* @param int|null $folderId the folder where it should be put into, null for root
* folder
* @param string $userId for which user the feed should be created
* @param string|null $title if given, this is used for the opml feed title
* @param string|null $user if given, basic auth is set for this feed
* @param string|null $password if given, basic auth is set for this
* feed. Ignored if user is null or an empty string
*
* @return Feed the newly created feed
* @throws ServiceConflictException if the feed exists already
* @throws ServiceNotFoundException if the url points to an invalid feed
*/
public function create(
string $feedUrl,
?int $folderId,
string $userId,
string $title = null,
string $user = null,
string $password = null
) {
// first try if the feed exists already
try {
/**
* @var Feed $feed
* @var Item[] $items
*/
list($feed, $items) = $this->feedFetcher->fetch($feedUrl, true, null, false, $user, $password);
// try again if feed exists depending on the reported link
if ($feed === null) {
throw new ServiceNotFoundException($this->l10n->t('Can not add feed: Unable to parse feed'));
}
try {
$hash = $feed->getUrlHash();
$this->feedMapper->findByUrlHash($hash, $userId);
throw new ServiceConflictException(
$this->l10n->t('Can not add feed: Exists already')
);
} catch (DoesNotExistException $ex) {
// If no matching feed was found everything was ok
}
// insert feed
$itemCount = count($items);
$feed->setBasicAuthUser($user);
$feed->setBasicAuthPassword($password);
$feed->setFolderId($folderId);
$feed->setUserId($userId);
$feed->setArticlesPerUpdate($itemCount);
if (!empty($title)) {
$feed->setTitle($title);
}
$feed = $this->feedMapper->insert($feed);
// insert items in reverse order because the first one is usually
// the newest item
$unreadCount = 0;
foreach (array_reverse($items) as $item) {
$item->setFeedId($feed->getId());
// check if item exists (guidhash is the same)
// and ignore it if it does
try {
$this->itemMapper->findByGuidHash(
$item->getGuidHash(),
$item->getFeedId(),
$userId
);
continue;
} catch (DoesNotExistException $ex) {
$unreadCount += 1;
$item->setBody($this->purifier->purify($item->getBody()));
$this->itemMapper->insert($item);
}
}
// set unread count
$feed->setUnreadCount($unreadCount);
return $feed;
} catch (ReadErrorException $ex) {
$this->logger->debug($ex->getMessage(), $this->loggerParams);
throw new ServiceNotFoundException($ex->getMessage());
}
}
/**
* Runs all the feed updates
*/
public function updateAll()
{
// TODO: this method is not covered by any tests
$feeds = $this->feedMapper->findAll();
foreach ($feeds as $feed) {
try {
$this->update($feed->getId(), $feed->getUserId());
} catch (\Exception $ex) {
// something is really wrong here, log it
$this->logger->error(
'Unexpected error when updating feed ' . $ex->getMessage(),
$this->loggerParams
);
}
}
}
/**
* Updates a single feed
*
* @param string $userId the id of the user
* @param int $feedId the id of the feed that should be updated
* @param bool $forceUpdate update even if the article exists already
*
* @throws ServiceNotFoundException if the feed does not exist
* @return Feed the updated feed entity
*/
public function update(string $userId, int $feedId, $forceUpdate = false)
{
/** @var Feed $existingFeed */
$existingFeed = $this->find($userId, $feedId);
if ($existingFeed->getPreventUpdate() === true) {
return $existingFeed;
}
// for backwards compability it can be that the location is not set
// yet, if so use the url
$location = $existingFeed->getLocation();
if (!$location) {
$location = $existingFeed->getUrl();
}
try {
list($fetchedFeed, $items) = $this->feedFetcher->fetch(
$location,
false,
$existingFeed->getHttpLastModified(),
$existingFeed->getFullTextEnabled(),
$existingFeed->getBasicAuthUser(),
$existingFeed->getBasicAuthPassword()
);
// if there is no feed it means that no update took place
if (!$fetchedFeed) {
return $existingFeed;
}
// update number of articles on every feed update
$itemCount = count($items);
// this is needed to adjust to updates that add more items
// than when the feed was created. You can't update the count
// if it's lower because it may be due to the caching headers
// that were sent as the request and it might cause unwanted
// deletion and reappearing of feeds
if ($itemCount > $existingFeed->getArticlesPerUpdate()) {
$existingFeed->setArticlesPerUpdate($itemCount);
}
$existingFeed->setHttpLastModified(
$fetchedFeed->getHttpLastModified()
);
$existingFeed->setHttpEtag($fetchedFeed->getHttpEtag());
$existingFeed->setLocation($fetchedFeed->getLocation());
// insert items in reverse order because the first one is
// usually the newest item
for ($i = $itemCount - 1; $i >= 0; $i--) {
$item = $items[$i];
$item->setFeedId($existingFeed->getId());
try {
$dbItem = $this->itemMapper->findByGuidHash(
$item->getGuidHash(),
$feedId,
$userId
);
// in case of update
if ($forceUpdate
|| $item->getUpdatedDate() > $dbItem->getUpdatedDate()
) {
$dbItem->setTitle($item->getTitle());
$dbItem->setUrl($item->getUrl());
$dbItem->setAuthor($item->getAuthor());
$dbItem->setSearchIndex($item->getSearchIndex());
$dbItem->setRtl($item->getRtl());
$dbItem->setLastModified($item->getLastModified());
$dbItem->setPubDate($item->getPubDate());
$dbItem->setUpdatedDate($item->getUpdatedDate());
$dbItem->setEnclosureMime($item->getEnclosureMime());
$dbItem->setEnclosureLink($item->getEnclosureLink());
$dbItem->setBody(
$this->purifier->purify($item->getBody())
);
// update modes: 0 nothing, 1 set unread
if ($existingFeed->getUpdateMode() === 1) {
$dbItem->setUnread(true);
}
$this->itemMapper->update($dbItem);
}
} catch (DoesNotExistException $ex) {
$item->setBody(
$this->purifier->purify($item->getBody())
);
$this->itemMapper->insert($item);
}
}
// mark feed as successfully updated
$existingFeed->setUpdateErrorCount(0);
$existingFeed->setLastUpdateError('');
} catch (ReadErrorException $ex) {
$existingFeed->setUpdateErrorCount(
$existingFeed->getUpdateErrorCount() + 1
);
$existingFeed->setLastUpdateError($ex->getMessage());
}
$this->feedMapper->update($existingFeed);
return $this->find($userId, $feedId);
}
/**
* Import articles
*
* @param array $json the array with json
* @param string $userId the username
*
* @return Feed if one had to be created for nonexistent feeds
*/
public function importArticles($json, $userId)
{
$url = 'http://nextcloud/nofeed';
$urlHash = md5($url);
// build assoc array for fast access
$feeds = $this->findAllForUser($userId);
$feedsDict = [];
foreach ($feeds as $feed) {
$feedsDict[$feed->getLink()] = $feed;
}
$createdFeed = false;
// loop over all items and get the corresponding feed
// if the feed does not exist, create a separate feed for them
foreach ($json as $entry) {
$item = Item::fromImport($entry);
$feedLink = $entry['feedLink']; // this is not set on the item yet
if (array_key_exists($feedLink, $feedsDict)) {
$feed = $feedsDict[$feedLink];
$item->setFeedId($feed->getId());
} elseif (array_key_exists($url, $feedsDict)) {
$feed = $feedsDict[$url];
$item->setFeedId($feed->getId());
} else {
$createdFeed = true;
$feed = new Feed();
$feed->setUserId($userId);
$feed->setLink($url);
$feed->setUrl($url);
$feed->setTitle($this->l10n->t('Articles without feed'));
$feed->setAdded($this->timeFactory->getTime());
$feed->setFolderId(null);
$feed->setPreventUpdate(true);
/** @var Feed $feed */
$feed = $this->feedMapper->insert($feed);
$item->setFeedId($feed->getId());
$feedsDict[$feed->getLink()] = $feed;
}
try {
// if item exists, copy the status
$existingItem = $this->itemMapper->findByGuidHash(
$item->getGuidHash(),
$feed->getId(),
$userId
);
$existingItem->setStatus($item->getStatus());
$this->itemMapper->update($existingItem);
} catch (DoesNotExistException $ex) {
$item->setBody($this->purifier->purify($item->getBody()));
$item->generateSearchIndex();
$this->itemMapper->insert($item);
}
}
if ($createdFeed) {
return $this->feedMapper->findByUrlHash($urlHash, $userId);
}
return null;
}
/**
* Use this to mark a feed as deleted. That way it can be un-deleted
*
* @param int $feedId the id of the feed that should be deleted
* @param string $userId the name of the user for security reasons
*
* @throws ServiceNotFoundException when feed does not exist
*/
public function markDeleted(int $feedId, string $userId)
{
$feed = $this->find($userId, $feedId);
$feed->setDeletedAt($this->timeFactory->getTime());
$this->feedMapper->update($feed);
}
/**
* Use this to undo a feed deletion
*
* @param int $feedId the id of the feed that should be restored
* @param string $userId the name of the user for security reasons
*
* @throws ServiceNotFoundException when feed does not exist
*/
public function unmarkDeleted(int $feedId, string $userId)
{
$feed = $this->find($userId, $feedId);
$feed->setDeletedAt(0);
$this->feedMapper->update($feed);
}
/**
* Deletes all deleted feeds
*
* @param string $userId if given it purges only feeds of that user
* @param boolean $useInterval defaults to true, if true it only purges
* entries in a given interval to give the user a chance to undo the
* deletion
*/
public function purgeDeleted($userId = null, $useInterval = true)
{
$deleteOlderThan = null;
if ($useInterval) {
$now = $this->timeFactory->getTime();
$deleteOlderThan = $now - $this->autoPurgeMinimumInterval;
}
$toDelete = $this->feedMapper->getToDelete($deleteOlderThan, $userId);
foreach ($toDelete as $feed) {
$this->feedMapper->delete($feed);
}
}
/**
* Deletes all feeds of a user, delete items first since the user_id
* is not defined in there
*
* @param string $userId the name of the user
*/
public function deleteUser($userId)
{
$this->feedMapper->deleteUser($userId);
}
/**
* @param int $feedId ID of the feed.
* @param string $userId ID of the user.
* @param array $diff An array containing the fields to update, e.g.:
* <code>
* [
* 'ordering' => 1,
* 'fullTextEnabled' => true,
* 'pinned' => true,
* 'updateMode' => 0,
* 'title' => 'title'
* ]
* </code>
*
* @throws ServiceNotFoundException if feed does not exist
* @return Feed The patched feed
*/
public function patch(int $feedId, string $userId, array $diff = [])
{
$feed = $this->find($userId, $feedId);
foreach ($diff as $attribute => $value) {
$method = 'set' . ucfirst($attribute);
$feed->$method($value);
}
// special feed updates
if (array_key_exists('fullTextEnabled', $diff)) {
// disable caching for the next update
$feed->setHttpEtag('');
$feed->setHttpLastModified(0);
$this->feedMapper->update($feed);
return $this->update($userId, $feedId, true);
}
return $this->feedMapper->update($feed);
}
public function findAll(): array
{
return $this->feedMapper->findAll();
}
}

View File

@ -29,7 +29,6 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCA\News\Db\Feed;
use OCA\News\Db\Item;
use OCA\News\Db\FeedMapper;
use OCA\News\Db\ItemMapper;
use OCA\News\Fetcher\Fetcher;
use OCA\News\Config\Config;
@ -104,22 +103,6 @@ class FeedServiceV2 extends Service
return $this->mapper->findAllFromUser($userId, $params);
}
/**
* Finds a feed of a user
*
* @param string $userId the name of the user
* @param string $id the id of the feed
*
* @return Feed
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findForUser(string $userId, string $id): Feed
{
return $this->mapper->findFromUser($userId, $id);
}
/**
* @param int|null $id
*
@ -139,6 +122,7 @@ class FeedServiceV2 extends Service
*/
public function findAllForUserRecursive(string $userId): array
{
/** @var Feed[] $feeds */
$feeds = $this->mapper->findAllFromUser($userId);
foreach ($feeds as &$feed) {
@ -167,12 +151,24 @@ class FeedServiceV2 extends Service
* @return bool
*/
public function existsForUser(string $userID, string $url): bool
{
return $this->findByURL($userID, $url) !== null;
}
/**
* Check if a feed exists for a user
*
* @param string $userID the name of the user
* @param string $url the feed URL
*
* @return Entity|Feed|null
*/
public function findByURL(string $userID, string $url): ?Entity
{
try {
$this->mapper->findByURL($userID, $url);
return true;
return $this->mapper->findByURL($userID, $url);
} catch (DoesNotExistException $e) {
return false;
return null;
}
}
@ -274,58 +270,59 @@ class FeedServiceV2 extends Service
$feed->getBasicAuthUser(),
$feed->getBasicAuthPassword()
);
// if there is no feed it means that no update took place
if (!$fetchedFeed) {
return $feed;
}
// update number of articles on every feed update
$itemCount = count($items);
// this is needed to adjust to updates that add more items
// than when the feed was created. You can't update the count
// if it's lower because it may be due to the caching headers
// that were sent as the request and it might cause unwanted
// deletion and reappearing of feeds
if ($itemCount > $feed->getArticlesPerUpdate()) {
$feed->setArticlesPerUpdate($itemCount);
}
$feed->setHttpLastModified($fetchedFeed->getHttpLastModified())
->setHttpEtag($fetchedFeed->getHttpEtag())
->setLocation($fetchedFeed->getLocation());
// insert items in reverse order because the first one is
// usually the newest item
for ($i = $itemCount - 1; $i >= 0; $i--) {
$item = $items[$i];
$item->setFeedId($feed->getId())
->setBody($this->purifier->purify($item->getBody()));
// update modes: 0 nothing, 1 set unread
if ($feed->getUpdateMode() === 1) {
$item->setUnread(true);
}
$this->itemService->insertOrUpdate($item);
}
// mark feed as successfully updated
$feed->setUpdateErrorCount(0);
$feed->setLastUpdateError(null);
} catch (ReadErrorException $ex) {
$feed->setUpdateErrorCount($feed->getUpdateErrorCount() + 1);
$feed->setLastUpdateError($ex->getMessage());
return $this->mapper->update($feed);
}
return $this->mapper->update($feed);
}
// if there is no feed it means that no update took place
if (!$fetchedFeed) {
return $feed;
}
public function delete(string $user, int $id): void
{
$feed = $this->mapper->findFromUser($user, $id);
$this->mapper->delete($feed);
// update number of articles on every feed update
$itemCount = count($items);
// this is needed to adjust to updates that add more items
// than when the feed was created. You can't update the count
// if it's lower because it may be due to the caching headers
// that were sent as the request and it might cause unwanted
// deletion and reappearing of feeds
if ($itemCount > $feed->getArticlesPerUpdate()) {
$feed->setArticlesPerUpdate($itemCount);
}
$feed->setHttpLastModified($fetchedFeed->getHttpLastModified())
->setHttpEtag($fetchedFeed->getHttpEtag())
->setLocation($fetchedFeed->getLocation());
foreach (array_reverse($items) as &$item) {
$item->setFeedId($feed->getId())
->setBody($this->purifier->purify($item->getBody()));
// update modes: 0 nothing, 1 set unread
if ($feed->getUpdateMode() === Feed::UPDATE_MODE_NORMAL) {
$item->setUnread(true);
}
$item = $this->itemService->insertOrUpdate($item);
}
// mark feed as successfully updated
$feed->setUpdateErrorCount(0);
$feed->setLastUpdateError(null);
$unreadCount = 0;
array_map(function (Item $item) use (&$unreadCount) {
if ($item->isUnread()) {
$unreadCount++;
}
}, $items);
return $this->mapper->update($feed)->setUnreadCount($unreadCount);
}
/**
@ -341,6 +338,11 @@ class FeedServiceV2 extends Service
$this->mapper->purgeDeleted($userID, $minTimestamp);
}
/**
* Fetch all feeds.
*
* @see FeedServiceV2::fetch()
*/
public function fetchAll(): void
{
foreach ($this->findAll() as $feed) {

View File

@ -63,28 +63,6 @@ class FolderServiceV2 extends Service
return $this->mapper->findAllFromUser($userId, $params);
}
/**
* Finds a folder of a user
*
* @param string $userId The name/ID of the user
* @param int|null $folderId ID of the folder
*
* @return Folder
*
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function findForUser(string $userId, ?int $folderId): Entity
{
try {
return $this->mapper->findFromUser($userId, $folderId);
} catch (DoesNotExistException $e) {
throw new ServiceNotFoundException('Folder not found');
} catch (MultipleObjectsReturnedException $e) {
throw new ServiceConflictException('Multiple folders found');
}
}
/**
* Find all folders and it's feeds.
*
@ -133,23 +111,6 @@ class FolderServiceV2 extends Service
return $this->mapper->insert($folder);
}
/**
* Delete a feed.
*
* @param string $userId Folder owner
* @param int $folderId Folder ID
*
* @return Folder
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function delete(string $userId, int $folderId): Entity
{
$folder = $this->findForUser($userId, $folderId);
return $this->mapper->delete($folder);
}
/**
* Purge all deleted folders.
*
@ -174,8 +135,9 @@ class FolderServiceV2 extends Service
*/
public function rename(string $userId, int $folderId, string $newName): Entity
{
$folder = $this->findForUser($userId, $folderId);
$folder = $this->find($userId, $folderId);
$folder->setName($newName);
return $this->mapper->update($folder);
}
@ -192,7 +154,7 @@ class FolderServiceV2 extends Service
*/
public function markDelete(string $userId, int $folderId, bool $mark): Entity
{
$folder = $this->findForUser($userId, $folderId);
$folder = $this->find($userId, $folderId);
$time = $mark ? $this->timeFactory->getTime() : 0;
$folder->setDeletedAt($time);
@ -212,21 +174,8 @@ class FolderServiceV2 extends Service
*/
public function open(string $userId, ?int $folderId, bool $open): Entity
{
$folder = $this->findForUser($userId, $folderId);
$folder = $this->find($userId, $folderId);
$folder->setOpened($open);
return $this->mapper->update($folder);
}
/**
* Delete all folders of a user
*
* @param string $userId User ID/name
*/
public function deleteUser(string $userId): void
{
$folders = $this->findAllForUser($userId);
foreach ($folders as $folder) {
$this->mapper->delete($folder);
}
}
}

View File

@ -0,0 +1,125 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Service;
use \OCA\News\Db\Item;
use \OCA\News\Db\Feed;
use \Psr\Log\LoggerInterface;
use \HTMLPurifier;
/**
* Class ImportService
*
* @package OCA\News\Service
*/
class ImportService
{
/**
* Items service.
*
* @var ItemServiceV2
*/
protected $itemService;
/**
* Feeds service.
*
* @var FeedServiceV2
*/
protected $feedService;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var HTMLPurifier
*/
protected $purifier;
/**
* FeedService constructor.
*
* @param FeedServiceV2 $feedService Service for feeds
* @param ItemServiceV2 $itemService Service to manage items
* @param HTMLPurifier $purifier HTML Purifier
* @param LoggerInterface $logger Logger
*/
public function __construct(
FeedServiceV2 $feedService,
ItemServiceV2 $itemService,
HTMLPurifier $purifier,
LoggerInterface $logger
) {
$this->itemService = $itemService;
$this->feedService = $feedService;
$this->purifier = $purifier;
$this->logger = $logger;
}
/**
* @param string $userId
* @param array $json
*
* @return array|null
*/
public function importArticles(string $userId, array $json)
{
$url = 'http://nextcloud/nofeed';
// build assoc array for fast access
$feeds = $this->feedService->findAllForUser($userId);
$feedsDict = [];
foreach ($feeds as $feed) {
$feedsDict[$feed->getLink()] = $feed;
}
$createdFeed = false;
// loop over all items and get the corresponding feed
// if the feed does not exist, create a separate feed for them
foreach ($json as $entry) {
$item = Item::fromImport($entry);
$feedLink = $entry['feedLink']; // this is not set on the item yet
if (array_key_exists($feedLink, $feedsDict)) {
$feed = $feedsDict[$feedLink];
} else {
$createdFeed = true;
$feed = new Feed();
$feed->setUserId($userId)
->setUrlHash(md5($url))
->setLink($url)
->setUrl($url)
->setTitle('Articles without feed')
->setAdded(time())
->setFolderId(null)
->setPreventUpdate(true);
$feed = $this->feedService->insert($feed);
$feedsDict[$feed->getLink()] = $feed;
}
$item->setFeedId($feed->getId())
->setBody($this->purifier->purify($item->getBody()))
->generateSearchIndex();
$this->itemService->insertOrUpdate($item);
}
if (!$createdFeed) {
return null;
}
return $this->feedService->findByURL($userId, $url);
}
}

View File

@ -15,6 +15,7 @@ namespace OCA\News\Service;
use OCA\News\AppInfo\Application;
use OCA\News\Db\Item;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCP\AppFramework\Db\Entity;
use OCP\IConfig;
@ -34,12 +35,22 @@ use Psr\Log\LoggerInterface;
class ItemService extends Service
{
/**
* @var IConfig
*/
private $config;
/**
* @var Time
*/
private $timeFactory;
private $itemMapper;
/**
* @var ItemMapper
*/
private $oldItemMapper;
public function __construct(
ItemMapper $itemMapper,
ItemMapperV2 $itemMapper,
ItemMapper $oldItemMapper,
Time $timeFactory,
IConfig $config,
LoggerInterface $logger
@ -47,7 +58,7 @@ class ItemService extends Service
parent::__construct($itemMapper, $logger);
$this->config = $config;
$this->timeFactory = $timeFactory;
$this->itemMapper = $itemMapper;
$this->oldItemMapper = $oldItemMapper;
}
@ -68,21 +79,21 @@ class ItemService extends Service
{
switch ($type) {
case FeedType::FEED:
return $this->itemMapper->findAllNewFeed(
return $this->oldItemMapper->findAllNewFeed(
$id,
$updatedSince,
$showAll,
$userId
);
case FeedType::FOLDER:
return $this->itemMapper->findAllNewFolder(
return $this->oldItemMapper->findAllNewFolder(
$id,
$updatedSince,
$showAll,
$userId
);
default:
return $this->itemMapper->findAllNew(
return $this->oldItemMapper->findAllNew(
$updatedSince,
$type,
$showAll,
@ -120,7 +131,7 @@ class ItemService extends Service
) {
switch ($type) {
case FeedType::FEED:
return $this->itemMapper->findAllFeed(
return $this->oldItemMapper->findAllFeed(
$id,
$limit,
$offset,
@ -130,7 +141,7 @@ class ItemService extends Service
$search
);
case FeedType::FOLDER:
return $this->itemMapper->findAllFolder(
return $this->oldItemMapper->findAllFolder(
$id,
$limit,
$offset,
@ -140,7 +151,7 @@ class ItemService extends Service
$search
);
default:
return $this->itemMapper->findAllItems(
return $this->oldItemMapper->findAllItems(
$limit,
$offset,
$type,
@ -154,7 +165,7 @@ class ItemService extends Service
public function findAllForUser(string $userId, array $params = []): array
{
return $this->itemMapper->findAllFromUser($userId);
return $this->mapper->findAllFromUser($userId, $params);
}
@ -171,18 +182,11 @@ class ItemService extends Service
public function star($feedId, $guidHash, $isStarred, $userId)
{
try {
/**
* @var Item $item
*/
$item = $this->itemMapper->findByGuidHash(
$guidHash,
$feedId,
$userId
);
$item = $this->mapper->findByGuidHash($feedId, $guidHash);
$item->setStarred($isStarred);
$this->itemMapper->update($item);
$this->mapper->update($item);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
@ -202,7 +206,7 @@ class ItemService extends Service
{
try {
$lastModified = $this->timeFactory->getMicroTime();
$this->itemMapper->readItem($itemId, $isRead, $lastModified, $userId);
$this->oldItemMapper->readItem($itemId, $isRead, $lastModified, $userId);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
@ -220,7 +224,7 @@ class ItemService extends Service
public function readAll($highestItemId, $userId)
{
$time = $this->timeFactory->getMicroTime();
$this->itemMapper->readAll($highestItemId, $time, $userId);
$this->oldItemMapper->readAll($highestItemId, $time, $userId);
}
@ -236,7 +240,7 @@ class ItemService extends Service
public function readFolder(?int $folderId, $highestItemId, $userId)
{
$time = $this->timeFactory->getMicroTime();
$this->itemMapper->readFolder(
$this->oldItemMapper->readFolder(
$folderId,
$highestItemId,
$time,
@ -257,7 +261,7 @@ class ItemService extends Service
public function readFeed($feedId, $highestItemId, $userId)
{
$time = $this->timeFactory->getMicroTime();
$this->itemMapper->readFeed($feedId, $highestItemId, $time, $userId);
$this->oldItemMapper->readFeed($feedId, $highestItemId, $time, $userId);
}
@ -275,7 +279,7 @@ class ItemService extends Service
Application::DEFAULT_SETTINGS['autoPurgeCount']
);
if ($count >= 0) {
$this->itemMapper->deleteReadOlderThanThreshold($count);
$this->oldItemMapper->deleteReadOlderThanThreshold($count);
}
}
@ -290,7 +294,7 @@ class ItemService extends Service
public function getNewestItemId($userId)
{
try {
return $this->itemMapper->getNewestItemId($userId);
return $this->oldItemMapper->getNewestItemId($userId);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
@ -305,7 +309,7 @@ class ItemService extends Service
*/
public function starredCount($userId)
{
return $this->itemMapper->starredCount($userId);
return $this->oldItemMapper->starredCount($userId);
}
@ -315,18 +319,7 @@ class ItemService extends Service
*/
public function getUnreadOrStarred($userId)
{
return $this->itemMapper->findAllUnreadOrStarred($userId);
}
/**
* Deletes all items of a user
*
* @param string $userId the name of the user
*/
public function deleteUser($userId)
{
$this->itemMapper->deleteUser($userId);
return $this->oldItemMapper->findAllUnreadOrStarred($userId);
}
@ -335,7 +328,7 @@ class ItemService extends Service
*/
public function generateSearchIndices()
{
$this->itemMapper->updateSearchIndices();
$this->oldItemMapper->updateSearchIndices();
}
public function findAll(): array

View File

@ -16,6 +16,7 @@ use OCA\News\AppInfo\Application;
use OCA\News\Db\Item;
use OCA\News\Db\ItemMapperV2;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
@ -76,8 +77,10 @@ class ItemServiceV2 extends Service
* Insert an item or update.
*
* @param Item $item
*
* @return Entity|Item The updated/inserted item
*/
public function insertOrUpdate(Item $item)
public function insertOrUpdate(Item $item): Entity
{
try {
$db_item = $this->mapper->findByGuidHash($item->getFeedId(), $item->getGuidHash());
@ -94,17 +97,24 @@ class ItemServiceV2 extends Service
$item->resetUpdatedFields();
}
$this->mapper->update($item);
return $this->mapper->update($item);
} catch (DoesNotExistException $exception) {
$this->mapper->insert($item);
return $this->mapper->insert($item);
}
}
/**
* @param int $feedId
*
* @return array
*/
public function findAllForFeed(int $feedId): array
{
return $this->mapper->findAllForFeed($feedId);
}
public function purgeOverThreshold(int $threshold = null)
{
@ -120,4 +130,13 @@ class ItemServiceV2 extends Service
return $this->mapper->deleteOverThreshold($threshold);
}
/**
* @param int $feedId
* @param string $guidHash
*/
public function findForGuidHash(int $feedId, string $guidHash)
{
return $this->mapper->findByGuidHash($feedId, $guidHash);
}
}

View File

@ -29,11 +29,6 @@ class OpmlService
*/
private $feedService;
/**
* @var ItemService
*/
private $itemService;
/**
* @var OPMLExporter
*/
@ -42,12 +37,10 @@ class OpmlService
public function __construct(
FolderServiceV2 $folderService,
FeedServiceV2 $feedService,
ItemServiceV2 $itemService,
OPMLExporter $exporter
) {
$this->folderService = $folderService;
$this->feedService = $feedService;
$this->itemService = $itemService;
$this->exporter = $exporter;
}

View File

@ -43,7 +43,7 @@ abstract class Service
* @param NewsMapperV2 $mapper
* @param LoggerInterface $logger
*/
public function __construct($mapper, LoggerInterface $logger)
public function __construct(NewsMapperV2 $mapper, LoggerInterface $logger)
{
$this->mapper = $mapper;
$this->logger = $logger;
@ -76,11 +76,40 @@ abstract class Service
* @throws ServiceNotFoundException if the entity does not exist, or there
* are more than one of it
*/
public function delete(string $userId, int $id)
public function delete(string $userId, int $id): Entity
{
$entity = $this->find($userId, $id);
$this->mapper->delete($entity);
return $this->mapper->delete($entity);
}
/**
* Insert an entity
*
* @param Entity $entity The entity to insert
*
* @return Entity The inserted entity
*/
public function insert(Entity $entity): Entity
{
return $this->mapper->insert($entity);
}
/**
* Update an entity
*
* @param string $userId the name of the user for security reasons
* @param Entity $entity the entity
*
* @throws ServiceNotFoundException if the entity does not exist, or there
* are more than one of it
*/
public function update(string $userId, Entity $entity): Entity
{
$this->find($userId, $entity->getId());
return $this->mapper->update($entity);
}
@ -104,4 +133,17 @@ abstract class Service
throw new ServiceNotFoundException($ex->getMessage());
}
}
/**
* Delete all items of a user
*
* @param string $userId User ID/name
*/
public function deleteUser(string $userId): void
{
$items = $this->findAllForUser($userId);
foreach ($items as $item) {
$this->mapper->delete($item);
}
}
}

View File

@ -28,12 +28,11 @@ class StatusService
public function __construct(
IConfig $settings,
IDBConnection $connection,
string $AppName
IDBConnection $connection
) {
$this->settings = $settings;
$this->appName = $AppName;
$this->connection = $connection;
$this->appName = Application::NAME;
}
/**

View File

@ -11,6 +11,7 @@
<file>./lib/AppInfo/Application.php</file>
<file>./lib/Controller/JSONHttpErrorTrait.php</file>
<file>./lib/**Exception.php</file>
<file>./lib/Migration/**.php</file>
</exclude>
</whitelist>
</filter>

View File

@ -74,7 +74,7 @@ class UpdateFeedTest extends TestCase
->method('getLastUpdateError');
$this->service->expects($this->exactly(1))
->method('findForUser')
->method('find')
->with('admin', '1')
->willReturn($feed);
@ -108,7 +108,7 @@ class UpdateFeedTest extends TestCase
->willReturn('Problem');
$this->service->expects($this->exactly(1))
->method('findForUser')
->method('find')
->with('admin', '1')
->willReturn($feed);
@ -140,7 +140,7 @@ class UpdateFeedTest extends TestCase
$feed = $this->createMock(Feed::class);
$this->service->expects($this->exactly(1))
->method('findForUser')
->method('find')
->with('admin', '1')
->willReturn($feed);

View File

@ -1,144 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\EntityApiSerializer;
use \OCP\AppFramework\Http\Response;
use \OCP\AppFramework\Db\Entity;
use \OCA\News\Db\Item;
use PHPUnit\Framework\TestCase;
class TestEntity extends Entity
{
}
class EntityApiSerializerTest extends TestCase
{
public function testSerializeSingle()
{
$item = new Item();
$item->setUnread(true);
$item->setId(3);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$serializer = new EntityApiSerializer('items');
$result = $serializer->serialize($item);
$this->assertTrue($result['items'][0]['unread']);
}
public function testSerializeMultiple()
{
$item = new Item();
$item->setUnread(true);
$item->setId(3);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$item2 = new Item();
$item2->setUnread(false);
$item2->setId(5);
$item2->setGuid('guid');
$item2->setGuidHash('guidhash');
$item2->setFeedId(123);
$serializer = new EntityApiSerializer('items');
$result = $serializer->serialize([$item, $item2]);
$this->assertTrue($result['items'][0]['unread']);
$this->assertFalse($result['items'][1]['unread']);
}
public function testResponseNoChange()
{
$response = new Response();
$serializer = new EntityApiSerializer('items');
$result = $serializer->serialize($response);
$this->assertEquals($response, $result);
}
public function testCompleteArraysTransformed()
{
$item = new Item();
$item->setUnread(true);
$item->setId(3);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$item2 = new Item();
$item2->setUnread(false);
$item2->setId(5);
$item2->setGuid('guid');
$item2->setGuidHash('guidhash');
$item2->setFeedId(123);
$serializer = new EntityApiSerializer('items');
$in = [
'items' => [$item, $item2],
'test' => 1
];
$result = $serializer->serialize($in);
$this->assertTrue($result['items'][0]['unread']);
$this->assertFalse($result['items'][1]['unread']);
$this->assertEquals(1, $result['test']);
}
public function testNoEntityNoChange()
{
$serializer = new EntityApiSerializer('items');
$in = [
'items' => ['hi', '2'],
'test' => 1
];
$result = $serializer->serialize($in);
$this->assertEquals('hi', $result['items'][0]);
$this->assertEquals('2', $result['items'][1]);
$this->assertEquals(1, $result['test']);
}
public function testEntitiesNoChange()
{
$serializer = new EntityApiSerializer('items');
$in = [
'items' => [new TestEntity()]
];
$result = $serializer->serialize($in);
$this->assertEquals($in, $result);
}
}

View File

@ -15,18 +15,15 @@
namespace OCA\News\Tests\Unit\Controller;
use Exception;
use OCA\News\Controller\FeedApiController;
use OCA\News\Service\FeedService;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemService;
use OCA\News\Service\ItemServiceV2;
use OCA\News\Utility\PsrLogger;
use \OCP\AppFramework\Http;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
use \OCA\News\Service\Exceptions\ServiceConflictException;
use \OCA\News\Db\Feed;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
@ -36,10 +33,6 @@ use Psr\Log\LoggerInterface;
class FeedApiControllerTest extends TestCase
{
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedService
*/
private $oldFeedService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2
@ -57,7 +50,10 @@ class FeedApiControllerTest extends TestCase
private $logger;
private $class;
private $user;
/**
* @var string
*/
private $userID = '123';
private $msg;
protected function setUp(): void
@ -72,18 +68,15 @@ class FeedApiControllerTest extends TestCase
$userSession = $this->getMockBuilder(IUserSession::class)
->disableOriginalConstructor()
->getMock();
$this->user = $this->getMockBuilder(IUser::class)
$user = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$userSession->expects($this->any())
->method('getUser')
->will($this->returnValue($this->user));
$this->user->expects($this->any())
->will($this->returnValue($user));
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('123'));
$this->oldFeedService = $this->getMockBuilder(FeedService::class)
->disableOriginalConstructor()
->getMock();
->will($this->returnValue($this->userID));
$this->feedService = $this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
@ -93,7 +86,6 @@ class FeedApiControllerTest extends TestCase
$this->class = new FeedApiController(
$request,
$userSession,
$this->oldFeedService,
$this->feedService,
$this->itemService,
$this->logger
@ -110,25 +102,26 @@ class FeedApiControllerTest extends TestCase
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->returnValue($starredCount));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->returnValue($newestItemId));
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->returnValue($feeds));
$response = $this->class->index();
$this->assertEquals(
[
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
'newestItemId' => $newestItemId
], $response
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
'newestItemId' => $newestItemId
],
$response
);
}
@ -140,24 +133,25 @@ class FeedApiControllerTest extends TestCase
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->returnValue($starredCount));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->throwException(new ServiceNotFoundException('')));
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->user->getUID()))
->with($this->equalTo($this->userID))
->will($this->returnValue($feeds));
$response = $this->class->index();
$this->assertEquals(
[
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
], $response
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
],
$response
);
}
@ -167,7 +161,7 @@ class FeedApiControllerTest extends TestCase
$this->feedService->expects($this->once())
->method('delete')
->with(
$this->equalTo($this->user->getUID()),
$this->equalTo($this->userID),
$this->equalTo(2)
);
@ -202,7 +196,7 @@ class FeedApiControllerTest extends TestCase
$this->feedService->expects($this->once())
->method('create')
->with($this->user->getUID(), 'url', 3)
->with($this->userID, 'url', 3)
->will($this->returnValue($feeds[0]));
$this->itemService->expects($this->once())
->method('getNewestItemId')
@ -229,7 +223,7 @@ class FeedApiControllerTest extends TestCase
$this->feedService->expects($this->once())
->method('create')
->with($this->user->getUID(), 'ho', 3)
->with($this->userID, 'ho', 3)
->will($this->returnValue($feeds[0]));
$this->itemService->expects($this->once())
->method('getNewestItemId')
@ -239,8 +233,9 @@ class FeedApiControllerTest extends TestCase
$this->assertEquals(
[
'feeds' => [$feeds[0]->toAPI()]
], $response
'feeds' => [$feeds[0]->toAPI()]
],
$response
);
}
@ -288,7 +283,7 @@ class FeedApiControllerTest extends TestCase
->with(
$this->equalTo(3),
$this->equalTo(30),
$this->equalTo($this->user->getUID())
$this->equalTo($this->userID)
);
$this->class->read(3, 30);
@ -297,13 +292,18 @@ class FeedApiControllerTest extends TestCase
public function testMove()
{
$this->oldFeedService->expects($this->once())
->method('patch')
->with(
$this->equalTo(3),
$this->equalTo($this->user->getUID()),
$this->equalTo(['folderId' => 30])
);
$feed = $this->getMockBuilder(Feed::class)->getMock();
$feed->expects($this->once())
->method('setFolderId')
->with(30)
->will($this->returnSelf());
$this->feedService->expects($this->once())
->method('find')
->with($this->userID, 3)
->will($this->returnValue($feed));
$this->feedService->expects($this->once())
->method('update')
->with($this->userID, $feed);
$this->class->move(3, 30);
}
@ -311,8 +311,8 @@ class FeedApiControllerTest extends TestCase
public function testMoveDoesNotExist()
{
$this->oldFeedService->expects($this->once())
->method('patch')
$this->feedService->expects($this->once())
->method('update')
->will(
$this->throwException(new ServiceNotFoundException($this->msg))
);
@ -327,18 +327,20 @@ class FeedApiControllerTest extends TestCase
public function testRename()
{
$feedId = 3;
$feedTitle = 'test';
$feed = $this->getMockBuilder(Feed::class)->getMock();
$feed->expects($this->once())
->method('setTitle')
->with('test')
->will($this->returnSelf());
$this->feedService->expects($this->once())
->method('find')
->with($this->userID, 3)
->will($this->returnValue($feed));
$this->feedService->expects($this->once())
->method('update')
->with($this->userID, $feed);
$this->oldFeedService->expects($this->once())
->method('patch')
->with(
$this->equalTo($feedId),
$this->equalTo($this->user->getUID()),
$this->equalTo(['title' => $feedTitle])
);
$this->class->rename($feedId, $feedTitle);
$this->class->rename(3, 'test');
}
@ -347,13 +349,9 @@ class FeedApiControllerTest extends TestCase
$feedId = 3;
$feedTitle = 'test';
$this->oldFeedService->expects($this->once())
->method('patch')
->with(
$this->equalTo($feedId),
$this->equalTo($this->user->getUID()),
$this->equalTo(['title' => $feedTitle])
)
$this->feedService->expects($this->once())
->method('find')
->with($this->userID, 3)
->will($this->throwException(new ServiceNotFoundException('hi')));
$result = $this->class->rename($feedId, $feedTitle);
@ -365,7 +363,7 @@ class FeedApiControllerTest extends TestCase
}
public function testfromAllUsers()
public function testFromAllUsers()
{
$feed = new Feed();
$feed->setUrl(3);
@ -405,7 +403,7 @@ class FeedApiControllerTest extends TestCase
$userId = 'hi';
$this->feedService->expects($this->once())
->method('find')
->will($this->throwException(new \Exception($this->msg)));
->will($this->throwException(new Exception($this->msg)));
$this->logger->expects($this->once())
->method('debug')

View File

@ -14,8 +14,10 @@
namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\FeedController;
use OCA\News\Service\FeedService;
use OCA\News\Db\Folder;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCA\News\Service\ItemService;
use OCP\AppFramework\Http;
@ -45,10 +47,13 @@ class FeedControllerTest extends TestCase
*/
private $folderService;
/**
* TODO: Remove
* @var MockObject|FeedService
* @var MockObject|FeedServiceV2
*/
private $feedService;
/**
* @var MockObject|ImportService
*/
private $importService;
/**
* TODO: Remove
* @var MockObject|ItemService
@ -86,7 +91,11 @@ class FeedControllerTest extends TestCase
->disableOriginalConstructor()
->getMock();
$this->feedService = $this
->getMockBuilder(FeedService::class)
->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->importService = $this
->getMockBuilder(ImportService::class)
->disableOriginalConstructor()
->getMock();
$this->folderService = $this
@ -110,6 +119,7 @@ class FeedControllerTest extends TestCase
$this->folderService,
$this->feedService,
$this->itemService,
$this->importService,
$this->settings,
$this->userSession
);
@ -136,11 +146,11 @@ class FeedControllerTest extends TestCase
->will($this->returnValue($result['feeds']));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->throwException(new ServiceNotFoundException('')));
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue($result['starred']));
$response = $this->class->index();
@ -160,15 +170,15 @@ class FeedControllerTest extends TestCase
];
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue($result['feeds']));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue($result['newestItemId']));
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue($result['starred']));
$response = $this->class->index();
@ -229,6 +239,32 @@ class FeedControllerTest extends TestCase
}
public function testActiveFolder()
{
$type = FeedType::FOLDER;
$folder = new Folder();
$folder->setId(3);
$result = [
'activeFeed' => [
'id' => 3,
'type' => 1
]
];
$this->folderService->expects($this->once())
->method('find')
->with($this->uid, 3)
->will($this->returnValue($folder));
$this->activeInitMocks(3, $type);
$response = $this->class->active();
$this->assertEquals($result, $response);
}
public function testActiveFolderDoesNotExist()
{
$id = 3;
@ -276,15 +312,10 @@ class FeedControllerTest extends TestCase
->will($this->returnValue($result['newestItemId']));
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->equalTo($this->uid), $this->equalTo(false));
->with($this->uid, false);
$this->feedService->expects($this->once())
->method('create')
->with(
$this->equalTo('hi'),
$this->equalTo(4),
$this->equalTo($this->uid),
$this->equalTo('yo')
)
->with($this->uid, 'hi', 4, false, 'yo')
->will($this->returnValue($result['feeds'][0]));
$response = $this->class->create('hi', 4, 'yo');
@ -293,13 +324,37 @@ class FeedControllerTest extends TestCase
}
public function testCreateOldFolderId()
{
$result = [
'feeds' => [new Feed()],
'newestItemId' => 3
];
$this->itemService->expects($this->once())
->method('getNewestItemId')
->will($this->returnValue($result['newestItemId']));
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->uid, false);
$this->feedService->expects($this->once())
->method('create')
->with($this->uid, 'hi', null, false, 'yo')
->will($this->returnValue($result['feeds'][0]));
$response = $this->class->create('hi', 0, 'yo');
$this->assertEquals($result, $response);
}
public function testCreateNoItems()
{
$result = ['feeds' => [new Feed()]];
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->equalTo($this->uid), $this->equalTo(false));
->with($this->uid, false);
$this->itemService->expects($this->once())
->method('getNewestItemId')
@ -307,12 +362,7 @@ class FeedControllerTest extends TestCase
$this->feedService->expects($this->once())
->method('create')
->with(
$this->equalTo('hi'),
$this->equalTo(4),
$this->equalTo($this->uid),
$this->equalTo('yo')
)
->with($this->uid, 'hi', 4, false, 'yo')
->will($this->returnValue($result['feeds'][0]));
$response = $this->class->create('hi', 4, 'yo');
@ -327,7 +377,7 @@ class FeedControllerTest extends TestCase
$ex = new ServiceNotFoundException($msg);
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->equalTo($this->uid), $this->equalTo(false));
->with($this->uid, false);
$this->feedService->expects($this->once())
->method('create')
->will($this->throwException($ex));
@ -348,7 +398,7 @@ class FeedControllerTest extends TestCase
$ex = new ServiceConflictException($msg);
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->equalTo($this->uid), $this->equalTo(false));
->with($this->uid, false);
$this->feedService->expects($this->once())
->method('create')
->will($this->throwException($ex));
@ -363,9 +413,17 @@ class FeedControllerTest extends TestCase
public function testDelete()
{
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$feed->expects($this->once())
->method('setDeletedAt');
$this->feedService->expects($this->once())
->method('markDeleted')
->with($this->equalTo(4));
->method('find')
->with('jack', 4)
->willReturn($feed);
$this->feedService->expects($this->once())
->method('update')
->with('jack', $feed);
$this->class->delete(4);
}
@ -376,7 +434,7 @@ class FeedControllerTest extends TestCase
$msg = 'hehe';
$this->feedService->expects($this->once())
->method('markDeleted')
->method('find')
->will($this->throwException(new ServiceNotFoundException($msg)));
$response = $this->class->delete(4);
@ -402,8 +460,13 @@ class FeedControllerTest extends TestCase
];
$this->feedService->expects($this->once())
->method('update')
->with($this->equalTo($this->uid), $this->equalTo(4))
->method('find')
->with($this->uid, 4)
->will($this->returnValue($feed));
$this->feedService->expects($this->once())
->method('fetch')
->with($feed)
->will($this->returnValue($feed));
$response = $this->class->update(4);
@ -415,8 +478,8 @@ class FeedControllerTest extends TestCase
public function testUpdateReturnsJSONError()
{
$this->feedService->expects($this->once())
->method('update')
->with($this->equalTo($this->uid), $this->equalTo(4))
->method('find')
->with($this->uid, 4)
->will($this->throwException(new ServiceNotFoundException('NO!')));
$response = $this->class->update(4);
@ -436,17 +499,14 @@ class FeedControllerTest extends TestCase
'feeds' => [$feed]
];
$this->feedService->expects($this->once())
$this->importService->expects($this->once())
->method('importArticles')
->with(
$this->equalTo(['json']),
$this->equalTo($this->uid)
)
->with($this->uid, ['json'],)
->will($this->returnValue($feed));
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue(3));
$response = $this->class->import(['json']);
@ -457,17 +517,14 @@ class FeedControllerTest extends TestCase
public function testImportCreatesNoAdditionalFeed()
{
$this->feedService->expects($this->once())
$this->importService->expects($this->once())
->method('importArticles')
->with(
$this->equalTo(['json']),
$this->equalTo($this->uid)
)
->with($this->uid, ['json'])
->will($this->returnValue(null));
$this->itemService->expects($this->once())
->method('starredCount')
->with($this->equalTo($this->uid))
->with($this->uid)
->will($this->returnValue(3));
$response = $this->class->import(['json']);
@ -489,7 +546,7 @@ class FeedControllerTest extends TestCase
$this->itemService->expects($this->once())
->method('readFeed')
->with($this->equalTo(4), $this->equalTo(5), $this->uid);
->with(4, 5, $this->uid);
$response = $this->class->read(4, 5);
$this->assertEquals($expected, $response);
@ -498,9 +555,21 @@ class FeedControllerTest extends TestCase
public function testRestore()
{
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$feed->expects($this->once())
->method('setDeletedAt')
->with(null);
$this->feedService->expects($this->once())
->method('unmarkDeleted')
->with($this->equalTo(4));
->method('find')
->with($this->uid, 4)
->willReturn($feed);
$this->feedService->expects($this->once())
->method('update')
->with($this->uid, $feed);
$this->class->restore(4);
}
@ -511,7 +580,8 @@ class FeedControllerTest extends TestCase
$msg = 'hehe';
$this->feedService->expects($this->once())
->method('unmarkDeleted')
->method('find')
->with($this->uid, 4)
->will($this->throwException(new ServiceNotFoundException($msg)));
$response = $this->class->restore(4);
@ -523,17 +593,46 @@ class FeedControllerTest extends TestCase
public function testPatch()
{
$expected = [
'pinned' => true,
'fullTextEnabled' => true,
'updateMode' => 1
];
$this->feedService->expects($this->once())
->method('patch')
->with(4, $this->uid, $expected)
->will($this->returnValue(1));
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$this->class->patch(4, true, true, 1);
$feed->expects($this->once())
->method('setFolderId')
->with(null);
$feed->expects($this->once())
->method('setPinned')
->with(true);
$feed->expects($this->once())
->method('setFullTextEnabled')
->with(false);
$feed->expects($this->once())
->method('setUpdateMode')
->with(1);
$feed->expects($this->never())
->method('setOrdering')
->with(true);
$feed->expects($this->never())
->method('setTitle')
->with(true);
$this->feedService->expects($this->once())
->method('find')
->with($this->uid, 4)
->will($this->returnValue($feed));
$this->feedService->expects($this->once())
->method('update')
->with($this->uid, $feed);
$this->class->patch(4, true, false, 1);
}
public function testPatchDoesNotExist()
@ -541,7 +640,8 @@ class FeedControllerTest extends TestCase
$msg = 'hehe';
$this->feedService->expects($this->once())
->method('patch')
->method('find')
->with($this->uid, 4)
->will($this->throwException(new ServiceNotFoundException($msg)));
$response = $this->class->patch(4, 2);
@ -551,5 +651,54 @@ class FeedControllerTest extends TestCase
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
}
public function testPatchDoesNotExistOnUpdate()
{
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$feed->expects($this->once())
->method('setFolderId')
->with(1);
$feed->expects($this->once())
->method('setPinned')
->with(true);
$feed->expects($this->once())
->method('setFullTextEnabled')
->with(false);
$feed->expects($this->once())
->method('setUpdateMode')
->with(1);
$feed->expects($this->once())
->method('setOrdering')
->with(1);
$feed->expects($this->once())
->method('setTitle')
->with('title');
$this->feedService->expects($this->once())
->method('find')
->with($this->uid, 4)
->will($this->returnValue($feed));
$this->feedService->expects($this->once())
->method('update')
->with($this->uid, $feed)
->will($this->throwException(new ServiceNotFoundException('test')));
$response = $this->class->patch(4, 2, false, 1, 1, 1, 'title');
$params = json_decode($response->render(), true);
$this->assertEquals('test', $params['message']);
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
}
}

View File

@ -14,7 +14,7 @@
namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\FolderController;
use OCA\News\Service\FeedService;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ItemService;
use \OCP\AppFramework\Http;
@ -39,6 +39,9 @@ class FolderControllerTest extends TestCase
*/
private $folderService;
private $itemService;
/**
* @var MockObject|FeedServiceV2
*/
private $feedService;
/**
* @var MockObject|IUser
@ -60,7 +63,7 @@ class FolderControllerTest extends TestCase
$this->folderService = $this->getMockBuilder(FolderServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->feedService = $this->getMockBuilder(FeedService::class)
$this->feedService = $this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->itemService = $this->getMockBuilder(ItemService::class)

View File

@ -14,7 +14,7 @@
namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\ItemController;
use OCA\News\Service\FeedService;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemService;
use \OCP\AppFramework\Http;
@ -34,9 +34,21 @@ class ItemControllerTest extends TestCase
{
private $appName;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IConfig
*/
private $settings;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemService
*/
private $itemService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2
*/
private $feedService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IRequest
*/
private $request;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IUser
@ -64,7 +76,7 @@ class ItemControllerTest extends TestCase
->disableOriginalConstructor()
->getMock();
$this->feedService =
$this->getMockBuilder(FeedService::class)
$this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->request = $this->getMockBuilder(IRequest::class)

View File

@ -0,0 +1,451 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Tests\Unit\Db;
use OCA\News\Db\Feed;
use OCA\News\Db\FeedMapperV2;
use OCA\News\Db\Folder;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IFunctionBuilder;
class FeedMapperTest extends MapperTestUtility
{
/** @var FeedMapperV2 */
private $class;
/** @var Feeds[] */
private $feeds;
/**
* @covers \OCA\News\Db\FeedMapperV2::__construct
*/
protected function setUp(): void
{
parent::setUp();
$this->class = new FeedMapperV2($this->db, new Time());
// create mock folders
$feed1 = new Feed();
$feed1->setId(4);
$feed1->resetUpdatedFields();
$feed2 = new Feed();
$feed2->setId(5);
$feed2->resetUpdatedFields();
$this->feeds = [$feed1, $feed2];
}
/**
* @covers \OCA\News\Db\FeedMapperV2::__construct
*/
public function testSetUpSuccess(): void
{
$this->assertEquals('news_feeds', $this->class->getTableName());
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findAllFromUser
*/
public function testFindAllFromUser()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$funcbuilder = $this->getMockBuilder(IFunctionBuilder::class)
->getMock();
$funcbuilder->expects($this->once())
->method('count')
->with('items.id', 'unreadCount')
->will($this->returnValue('COUNT_FUNC'));
$this->builder->expects($this->once())
->method('func')
->will($this->returnValue($funcbuilder));
$this->builder->expects($this->once())
->method('select')
->with('feeds.*', 'COUNT_FUNC')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds', 'feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('leftJoin')
->with('feeds', 'news_items', 'items', 'items.feed_id = feeds.id AND items.unread = :unread')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('feeds.user_id = :user_id')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('andWhere')
->with('feeds.deleted_at = 0')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('groupby')
->with('feeds.id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':unread', true], [':user_id', 'jack'])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(3))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
['id' => 5],
null
);
$result = $this->class->findAllFromUser('jack', []);
$this->assertEquals($this->feeds, $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findFromUser
*/
public function testFindFromUser()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('user_id = :user_id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('andWhere')
->withConsecutive(['id = :id'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':user_id', 'jack'], [':id', 1])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(2))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
false
);
$result = $this->class->findFromUser('jack', 1);
$this->assertEquals($this->feeds[0], $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findFromUser
*/
public function testFindFromUserEmpty()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('user_id = :user_id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('andWhere')
->withConsecutive(['id = :id'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':user_id', 'jack'], [':id', 1])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(1))
->method('fetch')
->willReturnOnConsecutiveCalls(
false
);
$this->expectException(DoesNotExistException::class);
$this->class->findFromUser('jack', 1);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findByURL
*/
public function testFindByUrl()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('user_id = :user_id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('andWhere')
->withConsecutive(['url = :url'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':user_id', 'jack'], [':url', 'https://url.com'])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(2))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
false
);
$result = $this->class->findByURL('jack', 'https://url.com');
$this->assertEquals($this->feeds[0], $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findFromUser
*/
public function testFindFromUserDuplicate()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('user_id = :user_id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('andWhere')
->withConsecutive(['id = :id'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':user_id', 'jack'], [':id', 1])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(2))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 1],
['id' => 2]
);
$this->expectException(MultipleObjectsReturnedException::class);
$this->class->findFromUser('jack', 1);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findAll
*/
public function testFindAll()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('deleted_at = 0')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(3))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
['id' => 5],
null
);
$result = $this->class->findAll();
$this->assertEquals($this->feeds, $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findAllFromFolder
*/
public function testFindAllFromFolder()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('folder_id = :folder_id')
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('setParameter')
->withConsecutive([':folder_id', 1])
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(3))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
['id' => 5],
null
);
$result = $this->class->findAllFromFolder(1);
$this->assertEquals($this->feeds, $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::findAllFromFolder
*/
public function testFindAllFromRootFolder()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('select')
->with('*')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('from')
->with('news_feeds')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('where')
->with('folder_id IS NULL')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('execute')
->will($this->returnValue($this->cursor));
$this->cursor->expects($this->exactly(3))
->method('fetch')
->willReturnOnConsecutiveCalls(
['id' => 4],
['id' => 5],
null
);
$result = $this->class->findAllFromFolder(null);
$this->assertEquals($this->feeds, $result);
}
}

View File

@ -0,0 +1,270 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Tests\Unit\Db;
use OCA\News\Db\Feed;
use OCA\News\Db\FeedMapperV2;
use OCA\News\Db\Folder;
use OCA\News\Db\NewsMapperV2;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IFunctionBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Test\TestCase;
abstract class TmpNewsMapper extends NewsMapperV2 {
const TABLE_NAME = 'NAME';
}
class NewsMapperTest extends TestCase
{
/** @var IDBConnection */
private $db;
/** @var Time */
private $time;
/** @var NewsMapperV2 */
private $class;
/**
* @covers \OCA\News\Db\NewsMapperV2::__construct
*/
protected function setUp(): void
{
$this->db = $this->getMockBuilder(IDBConnection::class)
->getMock();
$this->time = $this->getMockBuilder(Time::class)
->getMock();
$this->class = $this->getMockBuilder(TmpNewsMapper::class)
->setConstructorArgs([$this->db, $this->time, 'entity'])
->getMockForAbstractClass();
}
/**
* @covers \OCA\News\Db\NewsMapperV2::__construct
*/
public function testSetUpSuccess(): void
{
$this->assertEquals('NAME', $this->class->getTableName());
}
/**
* @covers \OCA\News\Db\NewsMapperV2::update
*/
public function testUpdateNoChange()
{
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$this->time->expects($this->never())
->method('getMicroTime')
->willReturn('1');
$feed->expects($this->never())
->method('setLastModified')
->with('1');
$feed->expects($this->exactly(2))
->method('getUpdatedFields')
->willReturn([]);
$result = $this->class->update($feed);
$this->assertEquals($feed, $result);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::update
*/
public function testUpdateChange()
{
$this->expectException('InvalidArgumentException');
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$this->time->expects($this->once())
->method('getMicroTime')
->willReturn('1');
$feed->expects($this->once())
->method('setLastModified')
->with('1');
$feed->expects($this->exactly(2))
->method('getUpdatedFields')
->willReturn(['a' => 'b']);
$result = $this->class->update($feed);
$this->assertEquals($feed, $result);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::insert
*/
public function testInsert()
{
$feed = $this->getMockBuilder(Feed::class)
->getMock();
$qb = $this->getMockBuilder(IQueryBuilder::class)
->getMock();
$this->time->expects($this->once())
->method('getMicroTime')
->willReturn('1');
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$feed->expects($this->once())
->method('setLastModified')
->with('1');
$feed->expects($this->once())
->method('getUpdatedFields')
->willReturn([]);
$result = $this->class->insert($feed);
$this->assertEquals($feed, $result);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::purgeDeleted
*/
public function testPurgeEmptyAll()
{
$qb = $this->getMockBuilder(IQueryBuilder::class)
->getMock();
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$qb->expects($this->once())
->method('delete')
->with('NAME')
->will($this->returnSelf());
$qb->expects($this->once())
->method('andWhere')
->with('deleted_at != 0')
->will($this->returnSelf());
$qb->expects($this->once())
->method('execute');
$result = $this->class->purgeDeleted(null, null);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::purgeDeleted
*/
public function testPurgeUser()
{
$qb = $this->getMockBuilder(IQueryBuilder::class)
->getMock();
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$qb->expects($this->once())
->method('delete')
->with('NAME')
->will($this->returnSelf());
$qb->expects($this->exactly(2))
->method('andWhere')
->withConsecutive(['deleted_at != 0'], ['user_id = :user_id'])
->will($this->returnSelf());
$qb->expects($this->once())
->method('setParameter')
->with(':user_id', 'jack')
->will($this->returnSelf());
$qb->expects($this->once())
->method('execute');
$result = $this->class->purgeDeleted('jack', null);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::purgeDeleted
*/
public function testPurgeTime()
{
$qb = $this->getMockBuilder(IQueryBuilder::class)
->getMock();
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$qb->expects($this->once())
->method('delete')
->with('NAME')
->will($this->returnSelf());
$qb->expects($this->exactly(2))
->method('andWhere')
->withConsecutive(['deleted_at != 0'], ['deleted_at < :deleted_at'])
->will($this->returnSelf());
$qb->expects($this->once())
->method('setParameter')
->with(':deleted_at', 1)
->will($this->returnSelf());
$qb->expects($this->once())
->method('execute');
$result = $this->class->purgeDeleted(null, 1);
}
/**
* @covers \OCA\News\Db\NewsMapperV2::purgeDeleted
*/
public function testPurgeBoth()
{
$qb = $this->getMockBuilder(IQueryBuilder::class)
->getMock();
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$qb->expects($this->once())
->method('delete')
->with('NAME')
->will($this->returnSelf());
$qb->expects($this->exactly(3))
->method('andWhere')
->withConsecutive(['deleted_at != 0'], ['user_id = :user_id'], ['deleted_at < :deleted_at'])
->will($this->returnSelf());
$qb->expects($this->exactly(2))
->method('setParameter')
->withConsecutive([':user_id', 'jack'], [':deleted_at', 1])
->will($this->returnSelf());
$qb->expects($this->once())
->method('execute');
$result = $this->class->purgeDeleted('jack', 1);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -132,45 +132,6 @@ class FolderServiceTest extends TestCase
$this->assertEquals($result[0]->feeds, $feeds);
}
public function testFindForUser()
{
$return = new Folder();
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 1)
->will($this->returnValue($return));
$result = $this->class->findForUser('jack', 1);
$this->assertEquals($return, $result);
}
public function testFindForUserEmpty()
{
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage('Folder not found');
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 1)
->will($this->throwException(new DoesNotExistException('')));
$this->class->findForUser('jack', 1);
}
public function testFindForUserDupe()
{
$this->expectException(ServiceConflictException::class);
$this->expectExceptionMessage('Multiple folders found');
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 1)
->will($this->throwException(new MultipleObjectsReturnedException('')));
$this->class->findForUser('jack', 1);
}
public function testCreate()
{

View File

@ -0,0 +1,232 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @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\ImportService;
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 ImportServiceTest extends TestCase
{
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2
*/
private $itemService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2
*/
private $feedService;
/** @var ImportService */
private $class;
/**
* @var string
*/
private $uid;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface
*/
private $logger;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|\HTMLPurifier
*/
private $purifier;
protected function setUp(): void
{
$this->logger = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor()
->getMock();
$this->itemService = $this
->getMockBuilder(ItemServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->feedService = $this
->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->purifier = $this
->getMockBuilder(\HTMLPurifier::class)
->disableOriginalConstructor()
->getMock();
$this->time = 333333;
$this->class = new ImportService(
$this->feedService,
$this->itemService,
$this->purifier,
$this->logger
);
$this->uid = 'jack';
}
public function testImportArticles()
{
$url = 'http://nextcloud/nofeed';
$feed = new Feed();
$feed->setId(3);
$feed->setUserId($this->uid);
$feed->setUrl($url);
$feed->setLink($url);
$feed->setTitle('Articles without feed');
$feed->setAdded($this->time);
$feed->setFolderId(0);
$feed->setPreventUpdate(true);
$feeds = [$feed];
$item = new Item();
$item->setFeedId(3);
$item->setAuthor('john');
$item->setGuid('s');
$item->setGuidHash('03c7c0ace395d80182db07ae2c30f034');
$item->setTitle('hey');
$item->setPubDate(333);
$item->setBody('come over');
$item->setEnclosureMime('mime');
$item->setEnclosureLink('lin');
$item->setUnread(true);
$item->setStarred(false);
$item->generateSearchIndex();
$json = $item->toExport(['feed3' => $feed]);
$items = [$json];
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->uid))
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('insertOrUpdate')
->with($item);
$this->purifier->expects($this->once())
->method('purify')
->with($this->equalTo($item->getBody()))
->will($this->returnValue($item->getBody()));
$result = $this->class->importArticles($this->uid, $items);
$this->assertEquals(null, $result);
}
public function testImportArticlesCreatesOwnFeedWhenNotFound()
{
$url = 'http://nextcloud/args';
$feed = new Feed();
$feed->setId(3);
$feed->setUserId($this->uid);
$feed->setUrl($url);
$feed->setLink($url);
$feed->setTitle('Articles without feed');
$feed->setAdded($this->time);
$feed->setFolderId(0);
$feed->setPreventUpdate(true);
$feeds = [$feed];
$item = new Item();
$item->setFeedId(3);
$item->setAuthor('john');
$item->setGuid('s');
$item->setGuidHash('03c7c0ace395d80182db07ae2c30f034');
$item->setTitle('hey');
$item->setPubDate(333);
$item->setBody('come over');
$item->setEnclosureMime('mime');
$item->setEnclosureLink('lin');
$item->setUnread(true);
$item->setStarred(false);
$item->generateSearchIndex();
$json = $item->toExport(['feed3' => $feed]);
$json2 = $json;
// believe it or not this copies stuff :D
$json2['feedLink'] = 'http://test.com';
$items = [$json, $json2];
$insertFeed = new Feed();
$insertFeed->setLink('http://nextcloud/nofeed');
$insertFeed->setUrl('http://nextcloud/nofeed');
$insertFeed->setUserId($this->uid);
$insertFeed->setTitle('Articles without feed');
$insertFeed->setAdded($this->time);
$insertFeed->setPreventUpdate(true);
$insertFeed->setFolderId(null);
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->uid))
->will($this->returnValue($feeds));
$this->feedService->expects($this->once())
->method('insert')
->will(
$this->returnCallback(
function () use ($insertFeed) {
$insertFeed->setId(3);
return $insertFeed;
}
)
);
$this->itemService->expects($this->exactly(2))
->method('insertOrUpdate')
->withConsecutive([$item]);
$this->purifier->expects($this->exactly(2))
->method('purify')
->with($this->equalTo($item->getBody()))
->will($this->returnValue($item->getBody()));
$this->feedService->expects($this->once())
->method('findByUrl')
->will($this->returnValue($feed));
$result = $this->class->importArticles($this->uid, $items);
$this->assertEquals($feed, $result);
}
}

View File

@ -15,6 +15,7 @@ namespace OCA\News\Tests\Unit\Service;
use OC\Log;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\ItemService;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Utility\PsrLogger;
@ -35,6 +36,11 @@ class ItemServiceTest extends TestCase
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapper
*/
private $oldItemMapper;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapperV2
*/
private $mapper;
/**
* @var ItemService
@ -79,7 +85,10 @@ class ItemServiceTest extends TestCase
$this->timeFactory->expects($this->any())
->method('getMicroTime')
->will($this->returnValue($this->time));
$this->mapper = $this->getMockBuilder(ItemMapper::class)
$this->mapper = $this->getMockBuilder(ItemMapperV2::class)
->disableOriginalConstructor()
->getMock();
$this->oldItemMapper = $this->getMockBuilder(ItemMapper::class)
->disableOriginalConstructor()
->getMock();
$this->config = $this->getMockBuilder(IConfig::class)
@ -92,6 +101,7 @@ class ItemServiceTest extends TestCase
$this->itemService = new ItemService(
$this->mapper,
$this->oldItemMapper,
$this->timeFactory,
$this->config,
$this->logger
@ -109,7 +119,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNewFeed()
{
$type = FeedType::FEED;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllNewFeed')
->with(
$this->equalTo(3),
@ -127,7 +137,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNewFolder()
{
$type = FeedType::FOLDER;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllNewFolder')
->with(
$this->equalTo(3),
@ -145,7 +155,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNew()
{
$type = FeedType::STARRED;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllNew')
->with(
$this->equalTo(20333),
@ -166,7 +176,7 @@ class ItemServiceTest extends TestCase
public function testFindAllFeed()
{
$type = FeedType::FEED;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllFeed')
->with(
$this->equalTo(3),
@ -190,7 +200,7 @@ class ItemServiceTest extends TestCase
public function testFindAllFolder()
{
$type = FeedType::FOLDER;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllFolder')
->with(
$this->equalTo(3),
@ -214,7 +224,7 @@ class ItemServiceTest extends TestCase
public function testFindAll()
{
$type = FeedType::STARRED;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(20),
@ -240,7 +250,7 @@ class ItemServiceTest extends TestCase
$type = FeedType::STARRED;
$search = ['test'];
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(20),
@ -269,22 +279,16 @@ class ItemServiceTest extends TestCase
$guidHash = md5('hihi');
$item = new Item();
$item->setStatus(128);
$item->setId($itemId);
$item->setStarred(false);
$expectedItem = new Item();
$expectedItem->setStatus(128);
$expectedItem->setStarred(true);
$expectedItem->setId($itemId);
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(
$this->equalTo($guidHash),
$this->equalTo($feedId),
$this->equalTo('jack')
)
->with($feedId, $guidHash)
->will($this->returnValue($item));
$this->mapper->expects($this->once())
@ -316,11 +320,7 @@ class ItemServiceTest extends TestCase
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(
$this->equalTo($guidHash),
$this->equalTo($feedId),
$this->equalTo('jack')
)
->with($feedId, $guidHash)
->will($this->returnValue($item));
$this->mapper->expects($this->once())
@ -346,7 +346,7 @@ class ItemServiceTest extends TestCase
$expectedItem->setId($itemId);
$expectedItem->setLastModified($this->time);
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('readItem')
->with(
$this->equalTo($itemId),
@ -364,7 +364,7 @@ class ItemServiceTest extends TestCase
{
$this->expectException(ServiceNotFoundException::class);
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('readItem')
->will($this->throwException(new DoesNotExistException('')));
@ -387,7 +387,7 @@ class ItemServiceTest extends TestCase
{
$highestItemId = 6;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('readAll')
->with(
$this->equalTo($highestItemId),
@ -404,7 +404,7 @@ class ItemServiceTest extends TestCase
$folderId = 3;
$highestItemId = 6;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('readFolder')
->with(
$this->equalTo($folderId),
@ -422,7 +422,7 @@ class ItemServiceTest extends TestCase
$feedId = 3;
$highestItemId = 6;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('readFeed')
->with(
$this->equalTo($feedId),
@ -441,7 +441,7 @@ class ItemServiceTest extends TestCase
->method('getAppValue')
->with('news', 'autoPurgeCount')
->will($this->returnValue(2));
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('deleteReadOlderThanThreshold')
->with($this->equalTo(2));
@ -454,7 +454,7 @@ class ItemServiceTest extends TestCase
->method('getAppValue')
->with('news', 'autoPurgeCount')
->will($this->returnValue(-1));
$this->mapper->expects($this->never())
$this->oldItemMapper->expects($this->never())
->method('deleteReadOlderThanThreshold');
$this->itemService->autoPurgeOld();
@ -463,7 +463,7 @@ class ItemServiceTest extends TestCase
public function testGetNewestItemId()
{
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo('jack'))
->will($this->returnValue(12));
@ -475,7 +475,7 @@ class ItemServiceTest extends TestCase
public function testGetNewestItemIdDoesNotExist()
{
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('getNewestItemId')
->with($this->equalTo('jack'))
->will(
@ -493,7 +493,7 @@ class ItemServiceTest extends TestCase
{
$star = 18;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('starredCount')
->with($this->equalTo('jack'))
->will($this->returnValue($star));
@ -508,7 +508,7 @@ class ItemServiceTest extends TestCase
{
$star = 18;
$this->mapper->expects($this->once())
$this->oldItemMapper->expects($this->once())
->method('findAllUnreadOrStarred')
->with($this->equalTo('jack'))
->will($this->returnValue($star));
@ -519,15 +519,5 @@ class ItemServiceTest extends TestCase
}
public function testDeleteUser()
{
$this->mapper->expects($this->once())
->method('deleteUser')
->will($this->returnValue('jack'));
$this->itemService->deleteUser('jack');
}
}

View File

@ -0,0 +1,143 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @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\Db\Folder;
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\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCA\News\Service\ItemServiceV2;
use OCA\News\Service\OpmlService;
use OCA\News\Utility\OPMLExporter;
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 OPMLServiceTest extends TestCase
{
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FolderServiceV2
*/
private $folderService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2
*/
private $feedService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|OPMLExporter
*/
private $exporter;
/** @var OpmlService */
private $class;
/**
* @var string
*/
private $uid;
protected function setUp(): void
{
$this->exporter = $this->getMockBuilder(OPMLExporter::class)
->disableOriginalConstructor()
->getMock();
$this->folderService = $this
->getMockBuilder(FolderServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->feedService = $this
->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->time = 333333;
$this->class = new OpmlService(
$this->folderService,
$this->feedService,
$this->exporter
);
$this->uid = 'jack';
}
public function testExportEmpty()
{
$this->feedService->expects($this->once())
->method('findAllForUser')
->will($this->returnValue([]));
$this->folderService->expects($this->once())
->method('findAllForUser')
->will($this->returnValue([]));
$domdoc = $this->getMockBuilder(\DOMDocument::class)
->getMock();
$this->exporter->expects($this->once())
->method('build')
->with([], [])
->will($this->returnValue($domdoc));
$domdoc->expects($this->once())
->method('saveXML')
->will($this->returnValue('doc'));
$this->assertEquals('doc', $this->class->export('jack'));
}
public function testExportSuccess()
{
$feed = Feed::fromParams(['id' => 1]);
$folder = Folder::fromParams(['id' => 1]);
$this->feedService->expects($this->once())
->method('findAllForUser')
->will($this->returnValue([$feed]));
$this->folderService->expects($this->once())
->method('findAllForUser')
->will($this->returnValue([$folder]));
$domdoc = $this->getMockBuilder(\DOMDocument::class)
->getMock();
$this->exporter->expects($this->once())
->method('build')
->with([$folder], [$feed])
->will($this->returnValue($domdoc));
$domdoc->expects($this->once())
->method('saveXML')
->will($this->returnValue('doc'));
$this->assertEquals('doc', $this->class->export('jack'));
}
}

View File

@ -15,6 +15,7 @@ namespace OCA\News\Tests\Unit\Service;
use OCA\News\Db\Feed;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\Service;
use \OCP\AppFramework\Db\DoesNotExistException;
@ -24,41 +25,24 @@ use \OCA\News\Db\Folder;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class TestLegacyService extends Service
{
public function __construct($mapper, $logger)
{
parent::__construct($mapper, $logger);
}
public function findAllForUser(string $userId, array $params = []): array
{
// TODO: Implement findAllForUser() method.
}
public function findAll(): array
{
// TODO: Implement findAll() method.
}
}
class ServiceTest extends TestCase
{
protected $mapper;
protected $logger;
protected $newsService;
protected $class;
protected function setUp(): void
{
$this->mapper = $this->getMockBuilder(ItemMapper::class)
$this->mapper = $this->getMockBuilder(ItemMapperV2::class)
->disableOriginalConstructor()
->getMock();
$this->logger = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor()
->getMock();
$this->newsService = new TestLegacyService($this->mapper, $this->logger);
$this->class = $this->getMockBuilder(Service::class)
->setConstructorArgs([$this->mapper, $this->logger])
->getMockForAbstractClass();
}
@ -77,7 +61,19 @@ class ServiceTest extends TestCase
->with($this->equalTo($user), $this->equalTo($id))
->will($this->returnValue($folder));
$this->newsService->delete($user, $id);
$this->class->delete($user, $id);
}
public function testInsert()
{
$folder = new Folder();
$this->mapper->expects($this->once())
->method('insert')
->with($this->equalTo($folder));
$this->class->insert($folder);
}
@ -91,7 +87,7 @@ class ServiceTest extends TestCase
->with($this->equalTo($user), $this->equalTo($id))
->will($this->returnValue(new Feed()));
$this->newsService->find($user, $id);
$this->class->find($user, $id);
}
@ -104,7 +100,7 @@ class ServiceTest extends TestCase
->will($this->throwException($ex));
$this->expectException(ServiceNotFoundException::class);
$this->newsService->find('', 1);
$this->class->find('', 1);
}
@ -117,7 +113,25 @@ class ServiceTest extends TestCase
->will($this->throwException($ex));
$this->expectException(ServiceNotFoundException::class);
$this->newsService->find('', 1);
$this->class->find('', 1);
}
public function testDeleteUser()
{
$feed1 = Feed::fromParams(['id' => 1]);
$feed2 = Feed::fromParams(['id' => 2]);
$this->class->expects($this->once())
->method('findAllForUser')
->with('')
->willReturn([$feed1, $feed2]);
$this->mapper->expects($this->exactly(2))
->method('delete')
->withConsecutive([$feed1], [$feed2]);
$this->class->deleteUser('');
}
}

View File

@ -45,12 +45,9 @@ class StatusServiceTest extends TestCase
$this->connection = $this->getMockBuilder(IDBConnection::class)
->disableOriginalConstructor()
->getMock();
$this->service = new StatusService($this->settings, $this->connection, 'news');
$this->service = new StatusService($this->settings, $this->connection);
}
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatus()
{
$this->settings->expects($this->exactly(3))
@ -81,9 +78,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response);
}
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusNoCorrectCronAjax()
{
$this->settings->expects($this->exactly(3))
@ -114,9 +108,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response);
}
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusNoCorrectCronTurnedOff()
{
$this->settings->expects($this->exactly(3))
@ -147,9 +138,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response);
}
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusReportsNon4ByteText()
{
$this->settings->expects($this->exactly(3))
@ -180,9 +168,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response);
}
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredNone()
{
$this->settings->expects($this->exactly(2))
@ -200,9 +185,6 @@ class StatusServiceTest extends TestCase
$this->assertFalse($response);
}
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredModeCronNoSystem()
{
$this->settings->expects($this->exactly(2))
@ -220,9 +202,6 @@ class StatusServiceTest extends TestCase
$this->assertTrue($response);
}
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredModeCron()
{
$this->settings->expects($this->exactly(2))