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

View File

@ -51,7 +51,7 @@ class UpdateFeed extends Command
$feedId = $input->getArgument('feed-id'); $feedId = $input->getArgument('feed-id');
$userId = $input->getArgument('user-id'); $userId = $input->getArgument('user-id');
try { try {
$feed = $this->feedService->findForUser($userId, $feedId); $feed = $this->feedService->find($userId, $feedId);
$updated_feed = $this->feedService->fetch($feed); $updated_feed = $this->feedService->fetch($feed);
} catch (\Exception $e) { } catch (\Exception $e) {
$output->writeln( $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\IUserSession;
use \OCP\AppFramework\Http; use \OCP\AppFramework\Http;
use \OCA\News\Service\FeedService;
use \OCA\News\Service\ItemService; use \OCA\News\Service\ItemService;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use function GuzzleHttp\Psr7\uri_for;
class FeedApiController extends ApiController class FeedApiController extends ApiController
{ {
@ -45,12 +43,6 @@ class FeedApiController extends ApiController
*/ */
private $feedService; private $feedService;
/**
* TODO: Remove
* @var FeedService
*/
private $oldFeedService;
/** /**
* @var LoggerInterface * @var LoggerInterface
*/ */
@ -64,14 +56,12 @@ class FeedApiController extends ApiController
public function __construct( public function __construct(
IRequest $request, IRequest $request,
?IUserSession $userSession, ?IUserSession $userSession,
FeedService $oldFeedService,
FeedServiceV2 $feedService, FeedServiceV2 $feedService,
ItemService $oldItemService, ItemService $oldItemService,
LoggerInterface $logger LoggerInterface $logger
) { ) {
parent::__construct($request, $userSession); parent::__construct($request, $userSession);
$this->feedService = $feedService; $this->feedService = $feedService;
$this->oldFeedService = $oldFeedService;
$this->oldItemService = $oldItemService; $this->oldItemService = $oldItemService;
$this->logger = $logger; $this->logger = $logger;
} }
@ -189,11 +179,9 @@ class FeedApiController extends ApiController
} }
try { try {
$this->oldFeedService->patch( $feed = $this->feedService->find($this->getUserId(), $feedId);
$feedId, $feed->setFolderId($folderId);
$this->getUserId(), $this->feedService->update($this->getUserId(), $feed);
['folderId' => $folderId]
);
} catch (ServiceNotFoundException $ex) { } catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND); return $this->error($ex, Http::STATUS_NOT_FOUND);
} }
@ -215,11 +203,9 @@ class FeedApiController extends ApiController
public function rename(int $feedId, string $feedTitle) public function rename(int $feedId, string $feedTitle)
{ {
try { try {
$this->oldFeedService->patch( $feed = $this->feedService->find($this->getUserId(), $feedId);
$feedId, $feed->setTitle($feedTitle);
$this->getUserId(), $this->feedService->update($this->getUserId(), $feed);
['title' => $feedTitle]
);
} catch (ServiceNotFoundException $ex) { } catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND); 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\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2; use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest; use OCP\IRequest;
use OCP\IConfig; use OCP\IConfig;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCA\News\Service\ItemService; use OCA\News\Service\ItemService;
use OCA\News\Service\FeedService;
use OCA\News\Db\FeedType; use OCA\News\Db\FeedType;
use OCP\IUserSession; use OCP\IUserSession;
@ -30,7 +31,9 @@ class FeedController extends Controller
{ {
use JSONHttpErrorTrait; use JSONHttpErrorTrait;
//TODO: Remove /**
* @var FeedServiceV2
*/
private $feedService; private $feedService;
//TODO: Remove //TODO: Remove
private $itemService; private $itemService;
@ -38,6 +41,10 @@ class FeedController extends Controller
* @var FolderServiceV2 * @var FolderServiceV2
*/ */
private $folderService; private $folderService;
/**
* @var ImportService
*/
private $importService;
/** /**
* @var IConfig * @var IConfig
*/ */
@ -46,8 +53,9 @@ class FeedController extends Controller
public function __construct( public function __construct(
IRequest $request, IRequest $request,
FolderServiceV2 $folderService, FolderServiceV2 $folderService,
FeedService $feedService, FeedServiceV2 $feedService,
ItemService $itemService, ItemService $itemService,
ImportService $importService,
IConfig $settings, IConfig $settings,
?IUserSession $userSession ?IUserSession $userSession
) { ) {
@ -55,6 +63,7 @@ class FeedController extends Controller
$this->folderService = $folderService; $this->folderService = $folderService;
$this->feedService = $feedService; $this->feedService = $feedService;
$this->itemService = $itemService; $this->itemService = $itemService;
$this->importService = $importService;
$this->settings = $settings; $this->settings = $settings;
} }
@ -74,12 +83,13 @@ class FeedController extends Controller
]; ];
try { try {
$params['newestItemId'] = $id = $this->itemService->getNewestItemId($this->getUserId());
$this->itemService->getNewestItemId($this->getUserId());
// An exception occurs if there is a newest item. If there is none, // An exception occurs if there is a newest item. If there is none,
// simply ignore it and do not add the newestItemId // simply ignore it and do not add the newestItemId
$params['newestItemId'] = $id;
} catch (ServiceNotFoundException $ex) { } catch (ServiceNotFoundException $ex) {
//NO-OP
} }
return $params; return $params;
@ -102,24 +112,22 @@ class FeedController extends Controller
'lastViewedFeedType' 'lastViewedFeedType'
); );
// cast from null to int is 0
if ($feedType !== null) {
$feedType = (int) $feedType;
}
// check if feed or folder exists // check if feed or folder exists
try { try {
if ($feedType === FeedType::FOLDER) { if ($feedType === null) {
if ($feedId === 0) { throw new ServiceNotFoundException('First launch');
$feedId = null; }
}
$this->folderService->find($this->getUserId(), $feedId);
} elseif ($feedType === FeedType::FEED) {
$this->feedService->find($this->getUserId(), $feedId);
// if its the first launch, those values will be null $feedType = intval($feedType);
} elseif ($feedType === null) { switch ($feedType) {
throw new ServiceNotFoundException(''); 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) { } catch (ServiceNotFoundException $ex) {
$feedId = 0; $feedId = 0;
@ -162,9 +170,10 @@ class FeedController extends Controller
$this->feedService->purgeDeleted($this->getUserId(), false); $this->feedService->purgeDeleted($this->getUserId(), false);
$feed = $this->feedService->create( $feed = $this->feedService->create(
$this->getUserId(),
$url, $url,
$parentFolderId, $parentFolderId,
$this->getUserId(), false,
$title, $title,
$user, $user,
$password $password
@ -172,12 +181,12 @@ class FeedController extends Controller
$params = ['feeds' => [$feed]]; $params = ['feeds' => [$feed]];
try { try {
$params['newestItemId'] = $id = $this->itemService->getNewestItemId($this->getUserId());
$this->itemService->getNewestItemId($this->getUserId());
// An exception occurs if there is a newest item. If there is none, // An exception occurs if there is a newest item. If there is none,
// simply ignore it and do not add the newestItemId // simply ignore it and do not add the newestItemId
$params['newestItemId'] = $id;
} catch (ServiceNotFoundException $ex) { } catch (ServiceNotFoundException $ex) {
//NO-OP
} }
return $params; return $params;
@ -199,7 +208,9 @@ class FeedController extends Controller
public function delete(int $feedId) public function delete(int $feedId)
{ {
try { 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) { } catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND); return $this->error($ex, Http::STATUS_NOT_FOUND);
} }
@ -218,7 +229,8 @@ class FeedController extends Controller
public function update(int $feedId) public function update(int $feedId)
{ {
try { try {
$feed = $this->feedService->update($this->getUserId(), $feedId); $old_feed = $this->feedService->find($this->getUserId(), $feedId);
$feed = $this->feedService->fetch($old_feed);
return [ return [
'feeds' => [ 'feeds' => [
@ -244,7 +256,7 @@ class FeedController extends Controller
*/ */
public function import(array $json): array public function import(array $json): array
{ {
$feed = $this->feedService->importArticles($json, $this->getUserId()); $feed = $this->importService->importArticles($this->getUserId(), $json);
$params = [ $params = [
'starred' => $this->itemService->starredCount($this->getUserId()) 'starred' => $this->itemService->starredCount($this->getUserId())
@ -290,7 +302,9 @@ class FeedController extends Controller
public function restore(int $feedId) public function restore(int $feedId)
{ {
try { 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) { } catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND); return $this->error($ex, Http::STATUS_NOT_FOUND);
} }
@ -320,24 +334,32 @@ class FeedController extends Controller
?int $folderId = null, ?int $folderId = null,
?string $title = null ?string $title = null
) { ) {
$attributes = [ try {
'pinned' => $pinned, $feed = $this->feedService->find($this->getUserId(), $feedId);
'fullTextEnabled' => $fullTextEnabled, } catch (ServiceNotFoundException $ex) {
'updateMode' => $updateMode, return $this->error($ex, Http::STATUS_NOT_FOUND);
'ordering' => $ordering, }
'title' => $title,
'folderId' => $folderId === 0 ? null : $folderId
];
$diff = array_filter( $fId = $folderId === 0 ? null : $folderId;
$attributes, $feed->setFolderId($fId);
function ($value) { if ($pinned !== null) {
return $value !== 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 { try {
$this->feedService->patch($feedId, $this->getUserId(), $diff); $this->feedService->update($this->getUserId(), $feed);
} catch (ServiceNotFoundException $ex) { } catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND); return $this->error($ex, Http::STATUS_NOT_FOUND);
} }

View File

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

View File

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

View File

@ -25,6 +25,16 @@ class Feed extends Entity implements IAPI, \JsonSerializable
{ {
use EntityJSONSerializer; use EntityJSONSerializer;
/**
* Silently import new items
*/
const UPDATE_MODE_SILENT = 0;
/**
* Mark new items as unread.
*/
const UPDATE_MODE_NORMAL = 1;
/** @var string */ /** @var string */
protected $userId = ''; protected $userId = '';
/** @var string */ /** @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 = $this->db->getQueryBuilder();
$builder->select('feeds.*', $builder->func()->count('items.id', 'unreadCount')) $builder->select('feeds.*', $builder->func()->count('items.id', 'unreadCount'))
->from($this->tableName, 'feeds') ->from(static::TABLE_NAME, 'feeds')
->leftJoin( ->leftJoin(
'feeds', 'feeds',
ItemMapperV2::TABLE_NAME, ItemMapperV2::TABLE_NAME,
@ -82,8 +82,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findFromUser(string $userId, int $id): Entity public function findFromUser(string $userId, int $id): Entity
{ {
$builder = $this->db->getQueryBuilder(); $builder = $this->db->getQueryBuilder();
$builder->addSelect('*') $builder->select('*')
->from($this->tableName) ->from(static::TABLE_NAME)
->where('user_id = :user_id') ->where('user_id = :user_id')
->andWhere('id = :id') ->andWhere('id = :id')
->setParameter(':user_id', $userId) ->setParameter(':user_id', $userId)
@ -101,7 +101,7 @@ class FeedMapperV2 extends NewsMapperV2
{ {
$builder = $this->db->getQueryBuilder(); $builder = $this->db->getQueryBuilder();
$builder->select('*') $builder->select('*')
->from($this->tableName) ->from(static::TABLE_NAME)
->where('deleted_at = 0'); ->where('deleted_at = 0');
return $this->findEntities($builder); return $this->findEntities($builder);
@ -121,8 +121,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findByURL(string $userId, string $url): Entity public function findByURL(string $userId, string $url): Entity
{ {
$builder = $this->db->getQueryBuilder(); $builder = $this->db->getQueryBuilder();
$builder->addSelect('*') $builder->select('*')
->from($this->tableName) ->from(static::TABLE_NAME)
->where('user_id = :user_id') ->where('user_id = :user_id')
->andWhere('url = :url') ->andWhere('url = :url')
->setParameter(':user_id', $userId) ->setParameter(':user_id', $userId)
@ -141,8 +141,8 @@ class FeedMapperV2 extends NewsMapperV2
public function findAllFromFolder(?int $id): array public function findAllFromFolder(?int $id): array
{ {
$builder = $this->db->getQueryBuilder(); $builder = $this->db->getQueryBuilder();
$builder->addSelect('*') $builder->select('*')
->from($this->tableName); ->from(static::TABLE_NAME);
if (is_null($id)) { if (is_null($id)) {
$builder->where('folder_id IS NULL'); $builder->where('folder_id IS NULL');

View File

@ -17,6 +17,7 @@ use Exception;
use OCA\News\Utility\Time; use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\Mapper;
use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
@ -27,13 +28,25 @@ use OCP\IDBConnection;
* @package OCA\News\Db * @package OCA\News\Db
* @deprecated use ItemMapper * @deprecated use ItemMapper
*/ */
class ItemMapper extends NewsMapper class ItemMapper extends Mapper
{ {
const TABLE_NAME = 'news_items'; 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) 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( private function makeSelectQuery(
@ -107,7 +120,7 @@ class ItemMapper extends NewsMapper
/** /**
* @param int $id * @param int $id
* @param string $userId * @param string $userId
* @return \OCA\News\Db\Item * @return \OCA\News\Db\Item|Entity
*/ */
public function find(string $userId, int $id) public function find(string $userId, int $id)
{ {
@ -332,7 +345,15 @@ class ItemMapper extends NewsMapper
return $this->findEntities($sql, $params); return $this->findEntities($sql, $params);
} }
/**
* @param $guidHash
* @param $feedId
* @param $userId
*
* @return Entity|Item
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findByGuidHash($guidHash, $feedId, $userId) public function findByGuidHash($guidHash, $feedId, $userId)
{ {
$sql = $this->makeSelectQuery( $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 * Returns a list of ids and userid of all items
*/ */
@ -492,29 +496,74 @@ class ItemMapper extends NewsMapper
} }
} }
/** public function update(Entity $entity): Entity
* NO-OP
*
* @param string $userId
*
* @return array
*/
public function findAllFromUser(string $userId): array
{ {
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 * Remove deleted items.
* @return array *
* @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); return $this->findEntity($builder);
} }
/**
* @param int $feedId
*
* @return array
*/
public function findAllForFeed(int $feedId): array public function findAllForFeed(int $feedId): array
{ {
$builder = $this->db->getQueryBuilder(); $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; namespace OCA\News\Fetcher;
use FeedIo\Reader\ReadErrorException; use FeedIo\Reader\ReadErrorException;
use OCA\News\Db\Feed;
use OCA\News\Db\Item;
interface IFeedFetcher interface IFeedFetcher
{ {
@ -29,7 +31,7 @@ interface IFeedFetcher
* @param string|null $user if given, basic auth is set for this feed * @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 * @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 * element being the Feed and second element being an array of Items
* *
* @throws ReadErrorException if the Feed-IO fetcher encounters a problem * @throws ReadErrorException if the Feed-IO fetcher encounters a problem

View File

@ -14,9 +14,9 @@
namespace OCA\News\Hooks; namespace OCA\News\Hooks;
use OCA\News\AppInfo\Application; use OCA\News\AppInfo\Application;
use OCA\News\Service\ItemService; use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FeedService;
use OCA\News\Service\FolderServiceV2; use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ItemServiceV2;
use OCP\EventDispatcher\Event; use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener; use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\BeforeUserDeletedEvent;
@ -37,8 +37,8 @@ class UserDeleteHook implements IEventListener
$container = $app->getContainer(); $container = $app->getContainer();
// order is important! // order is important!
$container->get(ItemService::class)->deleteUser($userId); $container->get(ItemServiceV2::class)->deleteUser($userId);
$container->get(FeedService::class)->deleteUser($userId); $container->get(FeedServiceV2::class)->deleteUser($userId);
$container->get(FolderServiceV2::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\Feed;
use OCA\News\Db\Item; use OCA\News\Db\Item;
use OCA\News\Db\FeedMapper;
use OCA\News\Db\ItemMapper; use OCA\News\Db\ItemMapper;
use OCA\News\Fetcher\Fetcher; use OCA\News\Fetcher\Fetcher;
use OCA\News\Config\Config; use OCA\News\Config\Config;
@ -104,22 +103,6 @@ class FeedServiceV2 extends Service
return $this->mapper->findAllFromUser($userId, $params); 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 * @param int|null $id
* *
@ -139,6 +122,7 @@ class FeedServiceV2 extends Service
*/ */
public function findAllForUserRecursive(string $userId): array public function findAllForUserRecursive(string $userId): array
{ {
/** @var Feed[] $feeds */
$feeds = $this->mapper->findAllFromUser($userId); $feeds = $this->mapper->findAllFromUser($userId);
foreach ($feeds as &$feed) { foreach ($feeds as &$feed) {
@ -167,12 +151,24 @@ class FeedServiceV2 extends Service
* @return bool * @return bool
*/ */
public function existsForUser(string $userID, string $url): 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 { try {
$this->mapper->findByURL($userID, $url); return $this->mapper->findByURL($userID, $url);
return true;
} catch (DoesNotExistException $e) { } catch (DoesNotExistException $e) {
return false; return null;
} }
} }
@ -274,58 +270,59 @@ class FeedServiceV2 extends Service
$feed->getBasicAuthUser(), $feed->getBasicAuthUser(),
$feed->getBasicAuthPassword() $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) { } catch (ReadErrorException $ex) {
$feed->setUpdateErrorCount($feed->getUpdateErrorCount() + 1); $feed->setUpdateErrorCount($feed->getUpdateErrorCount() + 1);
$feed->setLastUpdateError($ex->getMessage()); $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 // update number of articles on every feed update
{ $itemCount = count($items);
$feed = $this->mapper->findFromUser($user, $id);
$this->mapper->delete($feed); // 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); $this->mapper->purgeDeleted($userID, $minTimestamp);
} }
/**
* Fetch all feeds.
*
* @see FeedServiceV2::fetch()
*/
public function fetchAll(): void public function fetchAll(): void
{ {
foreach ($this->findAll() as $feed) { foreach ($this->findAll() as $feed) {

View File

@ -63,28 +63,6 @@ class FolderServiceV2 extends Service
return $this->mapper->findAllFromUser($userId, $params); 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. * Find all folders and it's feeds.
* *
@ -133,23 +111,6 @@ class FolderServiceV2 extends Service
return $this->mapper->insert($folder); 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. * Purge all deleted folders.
* *
@ -174,8 +135,9 @@ class FolderServiceV2 extends Service
*/ */
public function rename(string $userId, int $folderId, string $newName): Entity public function rename(string $userId, int $folderId, string $newName): Entity
{ {
$folder = $this->findForUser($userId, $folderId); $folder = $this->find($userId, $folderId);
$folder->setName($newName); $folder->setName($newName);
return $this->mapper->update($folder); return $this->mapper->update($folder);
} }
@ -192,7 +154,7 @@ class FolderServiceV2 extends Service
*/ */
public function markDelete(string $userId, int $folderId, bool $mark): Entity 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; $time = $mark ? $this->timeFactory->getTime() : 0;
$folder->setDeletedAt($time); $folder->setDeletedAt($time);
@ -212,21 +174,8 @@ class FolderServiceV2 extends Service
*/ */
public function open(string $userId, ?int $folderId, bool $open): Entity public function open(string $userId, ?int $folderId, bool $open): Entity
{ {
$folder = $this->findForUser($userId, $folderId); $folder = $this->find($userId, $folderId);
$folder->setOpened($open); $folder->setOpened($open);
return $this->mapper->update($folder); 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\AppInfo\Application;
use OCA\News\Db\Item; use OCA\News\Db\Item;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCP\IConfig; use OCP\IConfig;
@ -34,12 +35,22 @@ use Psr\Log\LoggerInterface;
class ItemService extends Service class ItemService extends Service
{ {
/**
* @var IConfig
*/
private $config; private $config;
/**
* @var Time
*/
private $timeFactory; private $timeFactory;
private $itemMapper; /**
* @var ItemMapper
*/
private $oldItemMapper;
public function __construct( public function __construct(
ItemMapper $itemMapper, ItemMapperV2 $itemMapper,
ItemMapper $oldItemMapper,
Time $timeFactory, Time $timeFactory,
IConfig $config, IConfig $config,
LoggerInterface $logger LoggerInterface $logger
@ -47,7 +58,7 @@ class ItemService extends Service
parent::__construct($itemMapper, $logger); parent::__construct($itemMapper, $logger);
$this->config = $config; $this->config = $config;
$this->timeFactory = $timeFactory; $this->timeFactory = $timeFactory;
$this->itemMapper = $itemMapper; $this->oldItemMapper = $oldItemMapper;
} }
@ -68,21 +79,21 @@ class ItemService extends Service
{ {
switch ($type) { switch ($type) {
case FeedType::FEED: case FeedType::FEED:
return $this->itemMapper->findAllNewFeed( return $this->oldItemMapper->findAllNewFeed(
$id, $id,
$updatedSince, $updatedSince,
$showAll, $showAll,
$userId $userId
); );
case FeedType::FOLDER: case FeedType::FOLDER:
return $this->itemMapper->findAllNewFolder( return $this->oldItemMapper->findAllNewFolder(
$id, $id,
$updatedSince, $updatedSince,
$showAll, $showAll,
$userId $userId
); );
default: default:
return $this->itemMapper->findAllNew( return $this->oldItemMapper->findAllNew(
$updatedSince, $updatedSince,
$type, $type,
$showAll, $showAll,
@ -120,7 +131,7 @@ class ItemService extends Service
) { ) {
switch ($type) { switch ($type) {
case FeedType::FEED: case FeedType::FEED:
return $this->itemMapper->findAllFeed( return $this->oldItemMapper->findAllFeed(
$id, $id,
$limit, $limit,
$offset, $offset,
@ -130,7 +141,7 @@ class ItemService extends Service
$search $search
); );
case FeedType::FOLDER: case FeedType::FOLDER:
return $this->itemMapper->findAllFolder( return $this->oldItemMapper->findAllFolder(
$id, $id,
$limit, $limit,
$offset, $offset,
@ -140,7 +151,7 @@ class ItemService extends Service
$search $search
); );
default: default:
return $this->itemMapper->findAllItems( return $this->oldItemMapper->findAllItems(
$limit, $limit,
$offset, $offset,
$type, $type,
@ -154,7 +165,7 @@ class ItemService extends Service
public function findAllForUser(string $userId, array $params = []): array 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) public function star($feedId, $guidHash, $isStarred, $userId)
{ {
try { try {
/** $item = $this->mapper->findByGuidHash($feedId, $guidHash);
* @var Item $item
*/
$item = $this->itemMapper->findByGuidHash(
$guidHash,
$feedId,
$userId
);
$item->setStarred($isStarred); $item->setStarred($isStarred);
$this->itemMapper->update($item); $this->mapper->update($item);
} catch (DoesNotExistException $ex) { } catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage()); throw new ServiceNotFoundException($ex->getMessage());
} }
@ -202,7 +206,7 @@ class ItemService extends Service
{ {
try { try {
$lastModified = $this->timeFactory->getMicroTime(); $lastModified = $this->timeFactory->getMicroTime();
$this->itemMapper->readItem($itemId, $isRead, $lastModified, $userId); $this->oldItemMapper->readItem($itemId, $isRead, $lastModified, $userId);
} catch (DoesNotExistException $ex) { } catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage()); throw new ServiceNotFoundException($ex->getMessage());
} }
@ -220,7 +224,7 @@ class ItemService extends Service
public function readAll($highestItemId, $userId) public function readAll($highestItemId, $userId)
{ {
$time = $this->timeFactory->getMicroTime(); $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) public function readFolder(?int $folderId, $highestItemId, $userId)
{ {
$time = $this->timeFactory->getMicroTime(); $time = $this->timeFactory->getMicroTime();
$this->itemMapper->readFolder( $this->oldItemMapper->readFolder(
$folderId, $folderId,
$highestItemId, $highestItemId,
$time, $time,
@ -257,7 +261,7 @@ class ItemService extends Service
public function readFeed($feedId, $highestItemId, $userId) public function readFeed($feedId, $highestItemId, $userId)
{ {
$time = $this->timeFactory->getMicroTime(); $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'] Application::DEFAULT_SETTINGS['autoPurgeCount']
); );
if ($count >= 0) { if ($count >= 0) {
$this->itemMapper->deleteReadOlderThanThreshold($count); $this->oldItemMapper->deleteReadOlderThanThreshold($count);
} }
} }
@ -290,7 +294,7 @@ class ItemService extends Service
public function getNewestItemId($userId) public function getNewestItemId($userId)
{ {
try { try {
return $this->itemMapper->getNewestItemId($userId); return $this->oldItemMapper->getNewestItemId($userId);
} catch (DoesNotExistException $ex) { } catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage()); throw new ServiceNotFoundException($ex->getMessage());
} }
@ -305,7 +309,7 @@ class ItemService extends Service
*/ */
public function starredCount($userId) 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) public function getUnreadOrStarred($userId)
{ {
return $this->itemMapper->findAllUnreadOrStarred($userId); return $this->oldItemMapper->findAllUnreadOrStarred($userId);
}
/**
* Deletes all items of a user
*
* @param string $userId the name of the user
*/
public function deleteUser($userId)
{
$this->itemMapper->deleteUser($userId);
} }
@ -335,7 +328,7 @@ class ItemService extends Service
*/ */
public function generateSearchIndices() public function generateSearchIndices()
{ {
$this->itemMapper->updateSearchIndices(); $this->oldItemMapper->updateSearchIndices();
} }
public function findAll(): array public function findAll(): array

View File

@ -16,6 +16,7 @@ use OCA\News\AppInfo\Application;
use OCA\News\Db\Item; use OCA\News\Db\Item;
use OCA\News\Db\ItemMapperV2; use OCA\News\Db\ItemMapperV2;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\IConfig; use OCP\IConfig;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -76,8 +77,10 @@ class ItemServiceV2 extends Service
* Insert an item or update. * Insert an item or update.
* *
* @param Item $item * @param Item $item
*
* @return Entity|Item The updated/inserted item
*/ */
public function insertOrUpdate(Item $item) public function insertOrUpdate(Item $item): Entity
{ {
try { try {
$db_item = $this->mapper->findByGuidHash($item->getFeedId(), $item->getGuidHash()); $db_item = $this->mapper->findByGuidHash($item->getFeedId(), $item->getGuidHash());
@ -94,17 +97,24 @@ class ItemServiceV2 extends Service
$item->resetUpdatedFields(); $item->resetUpdatedFields();
} }
$this->mapper->update($item); return $this->mapper->update($item);
} catch (DoesNotExistException $exception) { } catch (DoesNotExistException $exception) {
$this->mapper->insert($item); return $this->mapper->insert($item);
} }
} }
/**
* @param int $feedId
*
* @return array
*/
public function findAllForFeed(int $feedId): array public function findAllForFeed(int $feedId): array
{ {
return $this->mapper->findAllForFeed($feedId); return $this->mapper->findAllForFeed($feedId);
} }
public function purgeOverThreshold(int $threshold = null) public function purgeOverThreshold(int $threshold = null)
{ {
@ -120,4 +130,13 @@ class ItemServiceV2 extends Service
return $this->mapper->deleteOverThreshold($threshold); 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; private $feedService;
/**
* @var ItemService
*/
private $itemService;
/** /**
* @var OPMLExporter * @var OPMLExporter
*/ */
@ -42,12 +37,10 @@ class OpmlService
public function __construct( public function __construct(
FolderServiceV2 $folderService, FolderServiceV2 $folderService,
FeedServiceV2 $feedService, FeedServiceV2 $feedService,
ItemServiceV2 $itemService,
OPMLExporter $exporter OPMLExporter $exporter
) { ) {
$this->folderService = $folderService; $this->folderService = $folderService;
$this->feedService = $feedService; $this->feedService = $feedService;
$this->itemService = $itemService;
$this->exporter = $exporter; $this->exporter = $exporter;
} }

View File

@ -43,7 +43,7 @@ abstract class Service
* @param NewsMapperV2 $mapper * @param NewsMapperV2 $mapper
* @param LoggerInterface $logger * @param LoggerInterface $logger
*/ */
public function __construct($mapper, LoggerInterface $logger) public function __construct(NewsMapperV2 $mapper, LoggerInterface $logger)
{ {
$this->mapper = $mapper; $this->mapper = $mapper;
$this->logger = $logger; $this->logger = $logger;
@ -76,11 +76,40 @@ abstract class Service
* @throws ServiceNotFoundException if the entity does not exist, or there * @throws ServiceNotFoundException if the entity does not exist, or there
* are more than one of it * 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); $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()); 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( public function __construct(
IConfig $settings, IConfig $settings,
IDBConnection $connection, IDBConnection $connection
string $AppName
) { ) {
$this->settings = $settings; $this->settings = $settings;
$this->appName = $AppName;
$this->connection = $connection; $this->connection = $connection;
$this->appName = Application::NAME;
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@
namespace OCA\News\Tests\Unit\Controller; namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\ItemController; use OCA\News\Controller\ItemController;
use OCA\News\Service\FeedService; use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemService; use OCA\News\Service\ItemService;
use \OCP\AppFramework\Http; use \OCP\AppFramework\Http;
@ -34,9 +34,21 @@ class ItemControllerTest extends TestCase
{ {
private $appName; private $appName;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IConfig
*/
private $settings; private $settings;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemService
*/
private $itemService; private $itemService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2
*/
private $feedService; private $feedService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IRequest
*/
private $request; private $request;
/** /**
* @var \PHPUnit\Framework\MockObject\MockObject|IUser * @var \PHPUnit\Framework\MockObject\MockObject|IUser
@ -64,7 +76,7 @@ class ItemControllerTest extends TestCase
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->feedService = $this->feedService =
$this->getMockBuilder(FeedService::class) $this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->request = $this->getMockBuilder(IRequest::class) $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); $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() 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 OC\Log;
use OCA\News\Db\ItemMapper; use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\ItemService; use OCA\News\Service\ItemService;
use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Utility\PsrLogger; use OCA\News\Utility\PsrLogger;
@ -35,6 +36,11 @@ class ItemServiceTest extends TestCase
/** /**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapper * @var \PHPUnit\Framework\MockObject\MockObject|ItemMapper
*/ */
private $oldItemMapper;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapperV2
*/
private $mapper; private $mapper;
/** /**
* @var ItemService * @var ItemService
@ -79,7 +85,10 @@ class ItemServiceTest extends TestCase
$this->timeFactory->expects($this->any()) $this->timeFactory->expects($this->any())
->method('getMicroTime') ->method('getMicroTime')
->will($this->returnValue($this->time)); ->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() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->config = $this->getMockBuilder(IConfig::class) $this->config = $this->getMockBuilder(IConfig::class)
@ -92,6 +101,7 @@ class ItemServiceTest extends TestCase
$this->itemService = new ItemService( $this->itemService = new ItemService(
$this->mapper, $this->mapper,
$this->oldItemMapper,
$this->timeFactory, $this->timeFactory,
$this->config, $this->config,
$this->logger $this->logger
@ -109,7 +119,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNewFeed() public function testFindAllNewFeed()
{ {
$type = FeedType::FEED; $type = FeedType::FEED;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllNewFeed') ->method('findAllNewFeed')
->with( ->with(
$this->equalTo(3), $this->equalTo(3),
@ -127,7 +137,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNewFolder() public function testFindAllNewFolder()
{ {
$type = FeedType::FOLDER; $type = FeedType::FOLDER;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllNewFolder') ->method('findAllNewFolder')
->with( ->with(
$this->equalTo(3), $this->equalTo(3),
@ -145,7 +155,7 @@ class ItemServiceTest extends TestCase
public function testFindAllNew() public function testFindAllNew()
{ {
$type = FeedType::STARRED; $type = FeedType::STARRED;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllNew') ->method('findAllNew')
->with( ->with(
$this->equalTo(20333), $this->equalTo(20333),
@ -166,7 +176,7 @@ class ItemServiceTest extends TestCase
public function testFindAllFeed() public function testFindAllFeed()
{ {
$type = FeedType::FEED; $type = FeedType::FEED;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllFeed') ->method('findAllFeed')
->with( ->with(
$this->equalTo(3), $this->equalTo(3),
@ -190,7 +200,7 @@ class ItemServiceTest extends TestCase
public function testFindAllFolder() public function testFindAllFolder()
{ {
$type = FeedType::FOLDER; $type = FeedType::FOLDER;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllFolder') ->method('findAllFolder')
->with( ->with(
$this->equalTo(3), $this->equalTo(3),
@ -214,7 +224,7 @@ class ItemServiceTest extends TestCase
public function testFindAll() public function testFindAll()
{ {
$type = FeedType::STARRED; $type = FeedType::STARRED;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllItems') ->method('findAllItems')
->with( ->with(
$this->equalTo(20), $this->equalTo(20),
@ -240,7 +250,7 @@ class ItemServiceTest extends TestCase
$type = FeedType::STARRED; $type = FeedType::STARRED;
$search = ['test']; $search = ['test'];
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllItems') ->method('findAllItems')
->with( ->with(
$this->equalTo(20), $this->equalTo(20),
@ -269,22 +279,16 @@ class ItemServiceTest extends TestCase
$guidHash = md5('hihi'); $guidHash = md5('hihi');
$item = new Item(); $item = new Item();
$item->setStatus(128);
$item->setId($itemId); $item->setId($itemId);
$item->setStarred(false); $item->setStarred(false);
$expectedItem = new Item(); $expectedItem = new Item();
$expectedItem->setStatus(128);
$expectedItem->setStarred(true); $expectedItem->setStarred(true);
$expectedItem->setId($itemId); $expectedItem->setId($itemId);
$this->mapper->expects($this->once()) $this->mapper->expects($this->once())
->method('findByGuidHash') ->method('findByGuidHash')
->with( ->with($feedId, $guidHash)
$this->equalTo($guidHash),
$this->equalTo($feedId),
$this->equalTo('jack')
)
->will($this->returnValue($item)); ->will($this->returnValue($item));
$this->mapper->expects($this->once()) $this->mapper->expects($this->once())
@ -316,11 +320,7 @@ class ItemServiceTest extends TestCase
$this->mapper->expects($this->once()) $this->mapper->expects($this->once())
->method('findByGuidHash') ->method('findByGuidHash')
->with( ->with($feedId, $guidHash)
$this->equalTo($guidHash),
$this->equalTo($feedId),
$this->equalTo('jack')
)
->will($this->returnValue($item)); ->will($this->returnValue($item));
$this->mapper->expects($this->once()) $this->mapper->expects($this->once())
@ -346,7 +346,7 @@ class ItemServiceTest extends TestCase
$expectedItem->setId($itemId); $expectedItem->setId($itemId);
$expectedItem->setLastModified($this->time); $expectedItem->setLastModified($this->time);
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('readItem') ->method('readItem')
->with( ->with(
$this->equalTo($itemId), $this->equalTo($itemId),
@ -364,7 +364,7 @@ class ItemServiceTest extends TestCase
{ {
$this->expectException(ServiceNotFoundException::class); $this->expectException(ServiceNotFoundException::class);
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('readItem') ->method('readItem')
->will($this->throwException(new DoesNotExistException(''))); ->will($this->throwException(new DoesNotExistException('')));
@ -387,7 +387,7 @@ class ItemServiceTest extends TestCase
{ {
$highestItemId = 6; $highestItemId = 6;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('readAll') ->method('readAll')
->with( ->with(
$this->equalTo($highestItemId), $this->equalTo($highestItemId),
@ -404,7 +404,7 @@ class ItemServiceTest extends TestCase
$folderId = 3; $folderId = 3;
$highestItemId = 6; $highestItemId = 6;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('readFolder') ->method('readFolder')
->with( ->with(
$this->equalTo($folderId), $this->equalTo($folderId),
@ -422,7 +422,7 @@ class ItemServiceTest extends TestCase
$feedId = 3; $feedId = 3;
$highestItemId = 6; $highestItemId = 6;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('readFeed') ->method('readFeed')
->with( ->with(
$this->equalTo($feedId), $this->equalTo($feedId),
@ -441,7 +441,7 @@ class ItemServiceTest extends TestCase
->method('getAppValue') ->method('getAppValue')
->with('news', 'autoPurgeCount') ->with('news', 'autoPurgeCount')
->will($this->returnValue(2)); ->will($this->returnValue(2));
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('deleteReadOlderThanThreshold') ->method('deleteReadOlderThanThreshold')
->with($this->equalTo(2)); ->with($this->equalTo(2));
@ -454,7 +454,7 @@ class ItemServiceTest extends TestCase
->method('getAppValue') ->method('getAppValue')
->with('news', 'autoPurgeCount') ->with('news', 'autoPurgeCount')
->will($this->returnValue(-1)); ->will($this->returnValue(-1));
$this->mapper->expects($this->never()) $this->oldItemMapper->expects($this->never())
->method('deleteReadOlderThanThreshold'); ->method('deleteReadOlderThanThreshold');
$this->itemService->autoPurgeOld(); $this->itemService->autoPurgeOld();
@ -463,7 +463,7 @@ class ItemServiceTest extends TestCase
public function testGetNewestItemId() public function testGetNewestItemId()
{ {
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('getNewestItemId') ->method('getNewestItemId')
->with($this->equalTo('jack')) ->with($this->equalTo('jack'))
->will($this->returnValue(12)); ->will($this->returnValue(12));
@ -475,7 +475,7 @@ class ItemServiceTest extends TestCase
public function testGetNewestItemIdDoesNotExist() public function testGetNewestItemIdDoesNotExist()
{ {
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('getNewestItemId') ->method('getNewestItemId')
->with($this->equalTo('jack')) ->with($this->equalTo('jack'))
->will( ->will(
@ -493,7 +493,7 @@ class ItemServiceTest extends TestCase
{ {
$star = 18; $star = 18;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('starredCount') ->method('starredCount')
->with($this->equalTo('jack')) ->with($this->equalTo('jack'))
->will($this->returnValue($star)); ->will($this->returnValue($star));
@ -508,7 +508,7 @@ class ItemServiceTest extends TestCase
{ {
$star = 18; $star = 18;
$this->mapper->expects($this->once()) $this->oldItemMapper->expects($this->once())
->method('findAllUnreadOrStarred') ->method('findAllUnreadOrStarred')
->with($this->equalTo('jack')) ->with($this->equalTo('jack'))
->will($this->returnValue($star)); ->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\Feed;
use OCA\News\Db\ItemMapper; use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\Service; use OCA\News\Service\Service;
use \OCP\AppFramework\Db\DoesNotExistException; use \OCP\AppFramework\Db\DoesNotExistException;
@ -24,41 +25,24 @@ use \OCA\News\Db\Folder;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface; 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 class ServiceTest extends TestCase
{ {
protected $mapper; protected $mapper;
protected $logger; protected $logger;
protected $newsService; protected $class;
protected function setUp(): void protected function setUp(): void
{ {
$this->mapper = $this->getMockBuilder(ItemMapper::class) $this->mapper = $this->getMockBuilder(ItemMapperV2::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->logger = $this->getMockBuilder(LoggerInterface::class) $this->logger = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->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)) ->with($this->equalTo($user), $this->equalTo($id))
->will($this->returnValue($folder)); ->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)) ->with($this->equalTo($user), $this->equalTo($id))
->will($this->returnValue(new Feed())); ->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)); ->will($this->throwException($ex));
$this->expectException(ServiceNotFoundException::class); $this->expectException(ServiceNotFoundException::class);
$this->newsService->find('', 1); $this->class->find('', 1);
} }
@ -117,7 +113,25 @@ class ServiceTest extends TestCase
->will($this->throwException($ex)); ->will($this->throwException($ex));
$this->expectException(ServiceNotFoundException::class); $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) $this->connection = $this->getMockBuilder(IDBConnection::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->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() public function testGetStatus()
{ {
$this->settings->expects($this->exactly(3)) $this->settings->expects($this->exactly(3))
@ -81,9 +78,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response); $this->assertEquals($expected, $response);
} }
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusNoCorrectCronAjax() public function testGetStatusNoCorrectCronAjax()
{ {
$this->settings->expects($this->exactly(3)) $this->settings->expects($this->exactly(3))
@ -114,9 +108,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response); $this->assertEquals($expected, $response);
} }
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusNoCorrectCronTurnedOff() public function testGetStatusNoCorrectCronTurnedOff()
{ {
$this->settings->expects($this->exactly(3)) $this->settings->expects($this->exactly(3))
@ -147,9 +138,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response); $this->assertEquals($expected, $response);
} }
/**
* @covers \OCA\News\Service\StatusService::getStatus
*/
public function testGetStatusReportsNon4ByteText() public function testGetStatusReportsNon4ByteText()
{ {
$this->settings->expects($this->exactly(3)) $this->settings->expects($this->exactly(3))
@ -180,9 +168,6 @@ class StatusServiceTest extends TestCase
$this->assertEquals($expected, $response); $this->assertEquals($expected, $response);
} }
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredNone() public function testIsProperlyConfiguredNone()
{ {
$this->settings->expects($this->exactly(2)) $this->settings->expects($this->exactly(2))
@ -200,9 +185,6 @@ class StatusServiceTest extends TestCase
$this->assertFalse($response); $this->assertFalse($response);
} }
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredModeCronNoSystem() public function testIsProperlyConfiguredModeCronNoSystem()
{ {
$this->settings->expects($this->exactly(2)) $this->settings->expects($this->exactly(2))
@ -220,9 +202,6 @@ class StatusServiceTest extends TestCase
$this->assertTrue($response); $this->assertTrue($response);
} }
/**
* @covers \OCA\News\Service\StatusService::isCronProperlyConfigured
*/
public function testIsProperlyConfiguredModeCron() public function testIsProperlyConfiguredModeCron()
{ {
$this->settings->expects($this->exactly(2)) $this->settings->expects($this->exactly(2))