From 7e30451d02a026a0d5f4ac10226ed4737fc75052 Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Sat, 2 Jan 2021 17:57:17 +0100 Subject: [PATCH] Remove V1 item API Signed-off-by: Sean Molenaar --- CHANGELOG.md | 3 + lib/AppInfo/Application.php | 8 - lib/Command/Config/FolderDelete.php | 4 +- lib/Controller/ApiController.php | 2 +- lib/Controller/FeedApiController.php | 32 +- lib/Controller/FeedController.php | 18 +- lib/Controller/FolderApiController.php | 25 +- lib/Controller/FolderController.php | 35 +- lib/Controller/ItemApiController.php | 160 +- lib/Controller/ItemController.php | 164 +- lib/Controller/PageController.php | 2 +- lib/Db/FeedMapperV2.php | 25 + lib/Db/FeedType.php | 5 + lib/Db/FolderMapperV2.php | 26 + lib/Db/ItemMapper.php | 586 ------ lib/Db/ItemMapperV2.php | 357 +++- lib/Db/MapperFactory.php | 54 - lib/Db/Mysql/ItemMapper.php | 98 - lib/DependencyInjection/IFactory.php | 25 - .../RecommendedSiteNotFoundException.php | 2 +- lib/Explore/RecommendedSites.php | 2 + lib/Fetcher/FetcherException.php | 28 - .../Exceptions/ServiceConflictException.php | 16 +- lib/Service/Exceptions/ServiceException.php | 25 +- .../Exceptions/ServiceNotFoundException.php | 17 +- .../Exceptions/ServiceValidationException.php | 16 +- lib/Service/FeedServiceV2.php | 20 +- lib/Service/FolderServiceV2.php | 17 + lib/Service/ItemService.php | 352 ---- lib/Service/ItemServiceV2.php | 282 ++- lib/Service/Service.php | 12 +- phpunit.xml | 6 +- tests/Unit/Command/FolderDeleteTest.php | 2 +- .../Unit/Controller/FeedApiControllerTest.php | 46 +- tests/Unit/Controller/FeedControllerTest.php | 73 +- .../Controller/FolderApiControllerTest.php | 50 +- .../Unit/Controller/FolderControllerTest.php | 74 +- .../Unit/Controller/ItemApiControllerTest.php | 288 +-- tests/Unit/Controller/ItemControllerTest.php | 246 ++- tests/Unit/Controller/PageControllerTest.php | 22 +- tests/Unit/Db/FeedMapperTest.php | 92 +- tests/Unit/Db/FolderMapperTest.php | 90 + tests/Unit/Db/ItemMapperTest.php | 1724 +++++++++++++++++ tests/Unit/Db/MapperFactoryTest.php | 59 - tests/Unit/Db/MapperTestUtility.php | 2 + tests/Unit/Service/FeedServiceTest.php | 21 +- tests/Unit/Service/FolderServiceTest.php | 17 + tests/Unit/Service/ItemServiceTest.php | 626 +++--- tests/Unit/Service/ServiceTest.php | 4 +- 49 files changed, 3883 insertions(+), 1977 deletions(-) delete mode 100644 lib/Db/ItemMapper.php delete mode 100644 lib/Db/MapperFactory.php delete mode 100644 lib/Db/Mysql/ItemMapper.php delete mode 100644 lib/DependencyInjection/IFactory.php rename lib/Explore/{ => Exceptions}/RecommendedSiteNotFoundException.php (91%) delete mode 100644 lib/Fetcher/FetcherException.php delete mode 100644 lib/Service/ItemService.php create mode 100644 tests/Unit/Db/ItemMapperTest.php delete mode 100644 tests/Unit/Db/MapperFactoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c7495949..24d3c390a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1 ## [Unreleased] ### Changed +- Remove outdated item DB code. +- Stop returning all feeds after marking folder as read. ### Fixed @@ -20,6 +22,7 @@ The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1 ## [15.2.0-beta1] - 2021-01-11 ### Changed + - Remove outdated feed DB code - add background & hover for entries - Improve spacing of open articles in compact mode (nextcloud/news#1017) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index a262e2a031..079b32fe8f 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,9 +31,6 @@ use OCP\Files\IRootFolder; use OCP\Files\Node; - -use OCA\News\Db\MapperFactory; -use OCA\News\Db\ItemMapper; use OCA\News\Fetcher\FeedFetcher; use OCA\News\Fetcher\Fetcher; use OCP\User\Events\BeforeUserDeletedEvent; @@ -90,11 +87,6 @@ public function register(IRegistrationContext $context): void $context->registerParameter('exploreDir', __DIR__ . '/../Explore/feeds'); $context->registerParameter('configFile', 'config.ini'); - // factories - $context->registerService(ItemMapper::class, function (ContainerInterface $c): ItemMapper { - return $c->get(MapperFactory::class)->build(); - }); - $context->registerService(HTMLPurifier::class, function (ContainerInterface $c): HTMLPurifier { $directory = $c->get(ITempManager::class)->getTempBaseDir() . '/news/cache/purifier'; diff --git a/lib/Command/Config/FolderDelete.php b/lib/Command/Config/FolderDelete.php index a80875682e..8d7722b3be 100644 --- a/lib/Command/Config/FolderDelete.php +++ b/lib/Command/Config/FolderDelete.php @@ -2,7 +2,7 @@ namespace OCA\News\Command\Config; -use OCA\News\Service\Exceptions\ServiceException; +use OCA\News\Service\Exceptions\ServiceValidationException; use OCA\News\Service\FolderServiceV2; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -50,7 +50,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $id = $input->getArgument('folder-id'); if ($id === null) { - throw new ServiceException('Can not remove root folder!'); + throw new ServiceValidationException('Can not remove root folder!'); } $this->folderService->delete($user, intval($id)); diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index a434f8de77..e6a83b21a1 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -77,7 +77,7 @@ protected function getUserId() * * @return array */ - public function index() + public function index(): array { return [ 'apiLevels' => ['v1-2'] diff --git a/lib/Controller/FeedApiController.php b/lib/Controller/FeedApiController.php index 29f79b3ec5..43d92b7cae 100644 --- a/lib/Controller/FeedApiController.php +++ b/lib/Controller/FeedApiController.php @@ -19,12 +19,12 @@ use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\FeedServiceV2; +use OCA\News\Service\ItemServiceV2; use OCP\AppFramework\Http\JSONResponse; use \OCP\IRequest; use \OCP\IUserSession; use \OCP\AppFramework\Http; -use \OCA\News\Service\ItemService; use Psr\Log\LoggerInterface; class FeedApiController extends ApiController @@ -32,10 +32,9 @@ class FeedApiController extends ApiController use JSONHttpErrorTrait, ApiPayloadTrait; /** - * TODO: Remove - * @var ItemService + * @var ItemServiceV2 */ - private $oldItemService; + private $itemService; /** * @var FeedServiceV2 @@ -47,21 +46,16 @@ class FeedApiController extends ApiController */ private $logger; - /** - * @var EntityApiSerializer - */ - private $serializer; - public function __construct( IRequest $request, ?IUserSession $userSession, FeedServiceV2 $feedService, - ItemService $oldItemService, + ItemServiceV2 $itemService, LoggerInterface $logger ) { parent::__construct($request, $userSession); $this->feedService = $feedService; - $this->oldItemService = $oldItemService; + $this->itemService = $itemService; $this->logger = $logger; } @@ -75,12 +69,12 @@ public function index(): array { $result = [ - 'starredCount' => $this->oldItemService->starredCount($this->getUserId()), + 'starredCount' => count($this->itemService->starred($this->getUserId())), 'feeds' => $this->serialize($this->feedService->findAllForUser($this->getUserId())) ]; try { - $result['newestItemId'] = $this->oldItemService->getNewestItemId($this->getUserId()); + $result['newestItemId'] = $this->itemService->newest($this->getUserId())->getId(); } catch (ServiceNotFoundException $ex) { // in case there are no items, ignore } @@ -101,9 +95,7 @@ public function index(): array */ public function create(string $url, ?int $folderId = null) { - if ($folderId === 0) { - $folderId = null; - } + $folderId = $folderId === 0 ? null : $folderId; try { $this->feedService->purgeDeleted($this->getUserId(), time() - 600); @@ -114,7 +106,7 @@ public function create(string $url, ?int $folderId = null) $this->feedService->fetch($feed); try { - $result['newestItemId'] = $this->oldItemService->getNewestItemId($this->getUserId()); + $result['newestItemId'] = $this->itemService->newest($this->getUserId())->getId(); } catch (ServiceNotFoundException $ex) { // in case there are no items, ignore } @@ -159,7 +151,7 @@ public function delete(int $feedId) */ public function read(int $feedId, int $newestItemId): void { - $this->oldItemService->readFeed($feedId, $newestItemId, $this->getUserId()); + $this->itemService->read($this->getUserId(), $feedId, $newestItemId); } @@ -175,9 +167,7 @@ public function read(int $feedId, int $newestItemId): void */ public function move(int $feedId, ?int $folderId) { - if ($folderId === 0) { - $folderId = null; - } + $folderId = $folderId === 0 ? null : $folderId; try { $feed = $this->feedService->find($this->getUserId(), $feedId); diff --git a/lib/Controller/FeedController.php b/lib/Controller/FeedController.php index 9f7c9b0d6f..681dda4bc9 100644 --- a/lib/Controller/FeedController.php +++ b/lib/Controller/FeedController.php @@ -18,12 +18,12 @@ use OCA\News\Service\FeedServiceV2; use OCA\News\Service\FolderServiceV2; use OCA\News\Service\ImportService; +use OCA\News\Service\ItemServiceV2; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; use OCP\IConfig; use OCP\AppFramework\Http; -use OCA\News\Service\ItemService; use OCA\News\Db\FeedType; use OCP\IUserSession; @@ -35,7 +35,9 @@ class FeedController extends Controller * @var FeedServiceV2 */ private $feedService; - //TODO: Remove + /** + * @var ItemServiceV2 + */ private $itemService; /** * @var FolderServiceV2 @@ -54,7 +56,7 @@ public function __construct( IRequest $request, FolderServiceV2 $folderService, FeedServiceV2 $feedService, - ItemService $itemService, + ItemServiceV2 $itemService, ImportService $importService, IConfig $settings, ?IUserSession $userSession @@ -79,11 +81,11 @@ public function index(): array // item id which will be used for marking feeds read $params = [ 'feeds' => $this->feedService->findAllForUser($this->getUserId()), - 'starred' => $this->itemService->starredCount($this->getUserId()) + 'starred' => count($this->itemService->starred($this->getUserId())) ]; try { - $id = $this->itemService->getNewestItemId($this->getUserId()); + $id = $this->itemService->newest($this->getUserId())->getId(); // An exception occurs if there is a newest item. If there is none, // simply ignore it and do not add the newestItemId @@ -183,7 +185,7 @@ public function create( $this->feedService->fetch($feed); try { - $id = $this->itemService->getNewestItemId($this->getUserId()); + $id = $this->itemService->newest($this->getUserId())->getId(); // An exception occurs if there is a newest item. If there is none, // simply ignore it and do not add the newestItemId $params['newestItemId'] = $id; @@ -261,7 +263,7 @@ public function import(array $json): array $feed = $this->importService->importArticles($this->getUserId(), $json); $params = [ - 'starred' => $this->itemService->starredCount($this->getUserId()) + 'starred' => count($this->itemService->starred($this->getUserId())) ]; if ($feed) { @@ -281,7 +283,7 @@ public function import(array $json): array */ public function read(int $feedId, int $highestItemId): array { - $this->itemService->readFeed($feedId, $highestItemId, $this->getUserId()); + $this->feedService->read($this->getUserId(), $feedId, $highestItemId); return [ 'feeds' => [ diff --git a/lib/Controller/FolderApiController.php b/lib/Controller/FolderApiController.php index 8de4b9e695..71fc503e20 100644 --- a/lib/Controller/FolderApiController.php +++ b/lib/Controller/FolderApiController.php @@ -20,7 +20,6 @@ use \OCP\IUserSession; use \OCP\AppFramework\Http; -use \OCA\News\Service\ItemService; use \OCA\News\Service\FolderServiceV2; use \OCA\News\Service\Exceptions\ServiceNotFoundException; use \OCA\News\Service\Exceptions\ServiceConflictException; @@ -30,20 +29,19 @@ class FolderApiController extends ApiController { use JSONHttpErrorTrait, ApiPayloadTrait; + /** + * @var FolderServiceV2 + */ private $folderService; - //TODO: Remove - private $itemService; public function __construct( IRequest $request, ?IUserSession $userSession, - FolderServiceV2 $folderService, - ItemService $itemService + FolderServiceV2 $folderService ) { parent::__construct($request, $userSession); $this->folderService = $folderService; - $this->itemService = $itemService; } @@ -52,7 +50,7 @@ public function __construct( * @NoCSRFRequired * @CORS */ - public function index() + public function index(): array { $folders = $this->folderService->findAllForUser($this->getUserId()); return ['folders' => $this->serialize($folders)]; @@ -142,14 +140,13 @@ public function update(?int $folderId, string $name) * @NoCSRFRequired * @CORS * - * @param int|null $folderId - * @param int $newestItemId + * @param int|null $folderId ID of the folder + * @param int $maxItemId The newest read item */ - public function read(?int $folderId, int $newestItemId): void + public function read(?int $folderId, int $maxItemId): void { - if ($folderId === 0) { - $folderId = null; - } - $this->itemService->readFolder($folderId, $newestItemId, $this->getUserId()); + $folderId = $folderId === 0 ? null : $folderId; + + $this->folderService->read($this->getUserId(), $folderId, $maxItemId); } } diff --git a/lib/Controller/FolderController.php b/lib/Controller/FolderController.php index 9dc13b309a..da03f9863c 100644 --- a/lib/Controller/FolderController.php +++ b/lib/Controller/FolderController.php @@ -14,13 +14,11 @@ namespace OCA\News\Controller; use OCA\News\Service\Exceptions\ServiceException; -use OCA\News\Service\FeedServiceV2; use OCP\AppFramework\Http\JSONResponse; use \OCP\IRequest; use \OCP\AppFramework\Http; use \OCA\News\Service\FolderServiceV2; -use \OCA\News\Service\ItemService; use \OCA\News\Service\Exceptions\ServiceNotFoundException; use \OCA\News\Service\Exceptions\ServiceConflictException; use OCP\IUserSession; @@ -33,24 +31,14 @@ class FolderController extends Controller * @var FolderServiceV2 */ private $folderService; - /** - * @var FeedServiceV2 - */ - private $feedService; - //TODO: Remove - private $itemService; public function __construct( IRequest $request, FolderServiceV2 $folderService, - FeedServiceV2 $feedService, - ItemService $itemService, ?IUserSession $userSession ) { parent::__construct($request, $userSession); $this->folderService = $folderService; - $this->feedService = $feedService; - $this->itemService = $itemService; } @@ -134,12 +122,12 @@ public function delete(?int $folderId) /** * @NoAdminRequired * - * @param string $folderName - * @param int|null $folderId + * @param int|null $folderId The ID of the folder + * @param string $folderName The new name of the folder * * @return array|JSONResponse */ - public function rename(string $folderName, ?int $folderId) + public function rename(?int $folderId, string $folderName) { if (empty($folderId)) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); @@ -159,21 +147,18 @@ public function rename(string $folderName, ?int $folderId) * @NoAdminRequired * * @param int|null $folderId - * @param int $highestItemId + * @param int $maxItemId + * + * @return void * - * @return array + * @throws ServiceConflictException + * @throws ServiceNotFoundException */ - public function read(?int $folderId, int $highestItemId): array + public function read(?int $folderId, int $maxItemId): void { $folderId = $folderId === 0 ? null : $folderId; - $this->itemService->readFolder( - $folderId, - $highestItemId, - $this->getUserId() - ); - $feeds = $this->feedService->findAllForUser($this->getUserId()); - return ['feeds' => $this->serialize($feeds)]; + $this->folderService->read($this->getUserId(), $folderId, $maxItemId); } diff --git a/lib/Controller/ItemApiController.php b/lib/Controller/ItemApiController.php index af36b87f0a..a269f04be0 100644 --- a/lib/Controller/ItemApiController.php +++ b/lib/Controller/ItemApiController.php @@ -15,7 +15,9 @@ namespace OCA\News\Controller; -use OCA\News\Service\ItemService; +use OCA\News\Db\FeedType; +use OCA\News\Service\Exceptions\ServiceConflictException; +use OCA\News\Service\Exceptions\ServiceValidationException; use OCA\News\Service\ItemServiceV2; use OCP\AppFramework\Http\JSONResponse; use \OCP\IRequest; @@ -24,22 +26,27 @@ use \OCA\News\Service\Exceptions\ServiceNotFoundException; +/** + * Class ItemApiController + * + * @package OCA\News\Controller + */ class ItemApiController extends ApiController { use JSONHttpErrorTrait, ApiPayloadTrait; - private $oldItemService; + /** + * @var ItemServiceV2 + */ private $itemService; public function __construct( IRequest $request, ?IUserSession $userSession, - ItemService $oldItemService, ItemServiceV2 $itemService ) { parent::__construct($request, $userSession); - $this->oldItemService = $oldItemService; $this->itemService = $itemService; } @@ -64,16 +71,38 @@ public function index( int $batchSize = -1, int $offset = 0, bool $oldestFirst = false - ) { - $items = $this->oldItemService->findAllItems( - $id, - $type, - $batchSize, - $offset, - $getRead, - $oldestFirst, - $this->getUserId() - ); + ): array { + switch ($type) { + case FeedType::FEED: + $items = $this->itemService->findAllInFeedWithFilters( + $this->getUserId(), + $id, + $batchSize, + $offset, + !$getRead, + $oldestFirst + ); + break; + case FeedType::FOLDER: + $items = $this->itemService->findAllInFolderWithFilters( + $this->getUserId(), + $id, + $batchSize, + $offset, + !$getRead, + $oldestFirst + ); + break; + default: + $items = $this->itemService->findAllWithFilters( + $this->getUserId(), + $type, + $batchSize, + $offset, + $oldestFirst + ); + break; + } return ['items' => $this->serialize($items)]; } @@ -87,9 +116,12 @@ public function index( * @param int $type * @param int $id * @param int $lastModified - * @return array|mixed + * + * @return array + * + * @throws ServiceValidationException */ - public function updated(int $type = 3, int $id = 0, int $lastModified = 0) + public function updated(int $type = 3, int $id = 0, int $lastModified = 0): array { // needs to be turned into a millisecond timestamp to work properly if (strlen((string) $lastModified) <= 10) { @@ -97,27 +129,33 @@ public function updated(int $type = 3, int $id = 0, int $lastModified = 0) } else { $paddedLastModified = $lastModified; } - $items = $this->oldItemService->findAllNew( - $id, - $type, - (int) $paddedLastModified, - true, - $this->getUserId() - ); + + switch ($type) { + case FeedType::FEED: + $items = $this->itemService->findAllInFeedAfter($this->getUserId(), $id, $paddedLastModified, false); + break; + case FeedType::FOLDER: + $items = $this->itemService->findAllInFolderAfter($this->getUserId(), $id, $paddedLastModified, false); + break; + default: + $items = $this->itemService->findAllAfter($this->getUserId(), $type, $paddedLastModified); + break; + } return ['items' => $this->serialize($items)]; } - /** - * @return JSONResponse|array + * @param int $itemId + * @param bool $isRead * - * @psalm-return JSONResponse|array + * @return array|JSONResponse + * @throws ServiceConflictException */ - private function setRead(bool $isRead, int $itemId) + private function setRead(int $itemId, bool $isRead) { try { - $this->oldItemService->read($itemId, $isRead, $this->getUserId()); + $this->itemService->read($this->getUserId(), $itemId, $isRead); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -134,10 +172,11 @@ private function setRead(bool $isRead, int $itemId) * @param int $itemId * * @return array|JSONResponse + * @throws ServiceConflictException */ public function read(int $itemId) { - return $this->setRead(true, $itemId); + return $this->setRead($itemId, true); } @@ -149,27 +188,25 @@ public function read(int $itemId) * @param int $itemId * * @return array|JSONResponse + * @throws ServiceConflictException */ public function unread(int $itemId) { - return $this->setRead(false, $itemId); + return $this->setRead($itemId, false); } - /** - * @return JSONResponse|array + * @param int $feedId + * @param string $guidHash + * @param bool $isStarred * - * @psalm-return JSONResponse|array + * @return array|JSONResponse + * @throws ServiceConflictException */ - private function setStarred(bool $isStarred, int $feedId, string $guidHash) + private function setStarred(int $feedId, string $guidHash, bool $isStarred) { try { - $this->oldItemService->star( - $feedId, - $guidHash, - $isStarred, - $this->getUserId() - ); + $this->itemService->starByGuid($this->getUserId(), $feedId, $guidHash, $isStarred); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -187,10 +224,11 @@ private function setStarred(bool $isStarred, int $feedId, string $guidHash) * @param string $guidHash * * @return array|JSONResponse + * @throws ServiceConflictException */ public function star(int $feedId, string $guidHash) { - return $this->setStarred(true, $feedId, $guidHash); + return $this->setStarred($feedId, $guidHash, true); } @@ -203,10 +241,11 @@ public function star(int $feedId, string $guidHash) * @param string $guidHash * * @return array|JSONResponse + * @throws ServiceConflictException */ public function unstar(int $feedId, string $guidHash) { - return $this->setStarred(false, $feedId, $guidHash); + return $this->setStarred($feedId, $guidHash, false); } @@ -223,15 +262,20 @@ public function unstar(int $feedId, string $guidHash) */ public function readAll(int $newestItemId): void { - $this->oldItemService->readAll($newestItemId, $this->getUserId()); + $this->itemService->readAll($this->getUserId(), $newestItemId); } - - private function setMultipleRead(bool $isRead, array $items): void + /** + * @param array $items + * @param bool $isRead + * + * @throws ServiceConflictException + */ + private function setMultipleRead(array $items, bool $isRead): void { foreach ($items as $id) { try { - $this->oldItemService->read($id, $isRead, $this->getUserId()); + $this->itemService->read($this->getUserId(), $id, $isRead); } catch (ServiceNotFoundException $ex) { continue; } @@ -249,10 +293,12 @@ private function setMultipleRead(bool $isRead, array $items): void * @param int[] $items item ids * * @return void + * + * @throws ServiceConflictException */ public function readMultiple(array $items): void { - $this->setMultipleRead(true, $items); + $this->setMultipleRead($items, true); } @@ -266,30 +312,32 @@ public function readMultiple(array $items): void * @param int[] $items item ids * * @return void + * + * @throws ServiceConflictException */ public function unreadMultiple(array $items): void { - $this->setMultipleRead(false, $items); + $this->setMultipleRead($items, false); } /** - * @param bool $isStarred * @param array $items + * @param bool $isStarred * * @return void */ - private function setMultipleStarred(bool $isStarred, array $items): void + private function setMultipleStarred(array $items, bool $isStarred): void { foreach ($items as $item) { try { - $this->oldItemService->star( + $this->itemService->starByGuid( + $this->getUserId(), $item['feedId'], $item['guidHash'], - $isStarred, - $this->getUserId() + $isStarred ); - } catch (ServiceNotFoundException $ex) { + } catch (ServiceNotFoundException | ServiceConflictException $ex) { continue; } } @@ -309,7 +357,7 @@ private function setMultipleStarred(bool $isStarred, array $items): void */ public function starMultiple(array $items): void { - $this->setMultipleStarred(true, $items); + $this->setMultipleStarred($items, true); } @@ -326,6 +374,6 @@ public function starMultiple(array $items): void */ public function unstarMultiple(array $items): void { - $this->setMultipleStarred(false, $items); + $this->setMultipleStarred($items, false); } } diff --git a/lib/Controller/ItemController.php b/lib/Controller/ItemController.php index 96ebcbaec7..02a308d872 100644 --- a/lib/Controller/ItemController.php +++ b/lib/Controller/ItemController.php @@ -13,20 +13,31 @@ namespace OCA\News\Controller; +use OCA\News\Db\FeedType; +use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\FeedServiceV2; +use OCP\AppFramework\Http\JSONResponse; use \OCP\IRequest; use \OCP\IConfig; use \OCP\AppFramework\Http; use \OCA\News\Service\Exceptions\ServiceException; use \OCA\News\Service\Exceptions\ServiceNotFoundException; -use \OCA\News\Service\ItemService; +use \OCA\News\Service\ItemServiceV2; use OCP\IUserSession; +/** + * Class ItemController + * + * @package OCA\News\Controller + */ class ItemController extends Controller { use JSONHttpErrorTrait; + /** + * @var ItemServiceV2 + */ private $itemService; /** * @var FeedServiceV2 @@ -40,7 +51,7 @@ class ItemController extends Controller public function __construct( IRequest $request, FeedServiceV2 $feedService, - ItemService $itemService, + ItemServiceV2 $itemService, IConfig $settings, ?IUserSession $userSession ) { @@ -71,7 +82,7 @@ public function index( ?bool $showAll = null, ?bool $oldestFirst = null, string $search = '' - ) { + ): array { // in case this is called directly and not from the website use the // internal state @@ -104,15 +115,14 @@ public function index( $type ); - $params = []; + $return = []; // split search parameter on url space - $search = trim(urldecode($search)); - $search = preg_replace('/\s+/', ' ', $search); // remove multiple ws - if ($search === '') { - $search = []; - } else { - $search = explode(' ', $search); + $search_string = trim(urldecode($search)); + $search_string = preg_replace('/\s+/', ' ', $search_string); // remove multiple ws + $search_items = []; + if ($search !== '') { + $search_items = explode(' ', $search_string); } try { @@ -120,30 +130,54 @@ public function index( // we need to pass the newest feeds to not let the unread count get // out of sync if ($offset === 0) { - $params['newestItemId'] = - $this->itemService->getNewestItemId($this->getUserId()); - $params['feeds'] = $this->feedService->findAllForUser($this->getUserId()); - $params['starred'] = - $this->itemService->starredCount($this->getUserId()); + $return['newestItemId'] = $this->itemService->newest($this->getUserId())->getId(); + $return['feeds'] = $this->feedService->findAllForUser($this->getUserId()); + $return['starred'] = count($this->itemService->starred($this->getUserId())); } - $params['items'] = $this->itemService->findAllItems( - $id, - $type, - $limit, - $offset, - $showAll, - $oldestFirst, - $this->getUserId(), - $search - ); + switch ($type) { + case FeedType::FEED: + $items = $this->itemService->findAllInFeedWithFilters( + $this->getUserId(), + $id, + $limit, + $offset, + !$showAll, + $oldestFirst, + $search_items + ); + break; + case FeedType::FOLDER: + $items = $this->itemService->findAllInFolderWithFilters( + $this->getUserId(), + $id, + $limit, + $offset, + !$showAll, + $oldestFirst, + $search_items + ); + break; + default: + $items = $this->itemService->findAllWithFilters( + $this->getUserId(), + $type, + $limit, + $offset, + $oldestFirst, + $search_items + ); + break; + } + $return['items'] = $items; // this gets thrown if there are no items // in that case just return an empty array } catch (ServiceException $ex) { + //NO-OP } - return $params; + return $return; } @@ -155,7 +189,7 @@ public function index( * @param int $lastModified * @return array */ - public function newItems($type, $id, $lastModified = 0) + public function newItems(int $type, int $id, $lastModified = 0): array { $showAll = $this->settings->getUserValue( $this->getUserId(), @@ -163,28 +197,47 @@ public function newItems($type, $id, $lastModified = 0) 'showAll' ) === '1'; - $params = []; + $return = []; try { - $params['newestItemId'] = - $this->itemService->getNewestItemId($this->getUserId()); - $params['feeds'] = $this->feedService->findAllForUser($this->getUserId()); - $params['starred'] = - $this->itemService->starredCount($this->getUserId()); - $params['items'] = $this->itemService->findAllNew( - $id, - $type, - $lastModified, - $showAll, - $this->getUserId() - ); + switch ($type) { + case FeedType::FEED: + $items = $this->itemService->findAllInFeedAfter( + $this->getUserId(), + $id, + $lastModified, + !$showAll + ); + break; + case FeedType::FOLDER: + $items = $this->itemService->findAllInFolderAfter( + $this->getUserId(), + $id, + $lastModified, + !$showAll + ); + break; + default: + $items = $this->itemService->findAllAfter( + $this->getUserId(), + $type, + $lastModified + ); + break; + } + + $return['newestItemId'] = $this->itemService->newest($this->getUserId())->getId(); + $return['feeds'] = $this->feedService->findAllForUser($this->getUserId()); + $return['starred'] = count($this->itemService->starred($this->getUserId())); + $return['items'] = $items; // this gets thrown if there are no items // in that case just return an empty array } catch (ServiceException $ex) { + //NO-OP } - return $params; + return $return; } @@ -194,16 +247,17 @@ public function newItems($type, $id, $lastModified = 0) * @param int $feedId * @param string $guidHash * @param bool $isStarred - * @return array|\OCP\AppFramework\Http\JSONResponse + * + * @return array|JSONResponse */ - public function star($feedId, $guidHash, $isStarred) + public function star(int $feedId, string $guidHash, bool $isStarred) { try { - $this->itemService->star( + $this->itemService->starByGuid( + $this->getUserId(), $feedId, $guidHash, - $isStarred, - $this->getUserId() + $isStarred ); } catch (ServiceException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); @@ -218,12 +272,13 @@ public function star($feedId, $guidHash, $isStarred) * * @param int $itemId * @param bool $isRead - * @return array|\OCP\AppFramework\Http\JSONResponse + * + * @return array|JSONResponse */ - public function read($itemId, $isRead = true) + public function read(int $itemId, $isRead = true) { try { - $this->itemService->read($itemId, $isRead, $this->getUserId()); + $this->itemService->read($this->getUserId(), $itemId, $isRead); } catch (ServiceException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -236,11 +291,12 @@ public function read($itemId, $isRead = true) * @NoAdminRequired * * @param int $highestItemId + * * @return array */ - public function readAll($highestItemId) + public function readAll(int $highestItemId): array { - $this->itemService->readAll($highestItemId, $this->getUserId()); + $this->itemService->readAll($this->getUserId(), $highestItemId); return ['feeds' => $this->feedService->findAllForUser($this->getUserId())]; } @@ -252,12 +308,12 @@ public function readAll($highestItemId) * * @return void */ - public function readMultiple($itemIds): void + public function readMultiple(array $itemIds): void { foreach ($itemIds as $id) { try { - $this->itemService->read($id, true, $this->getUserId()); - } catch (ServiceNotFoundException $ex) { + $this->itemService->read($this->getUserId(), $id, true); + } catch (ServiceNotFoundException | ServiceConflictException $ex) { continue; } } diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 35924d10cb..87ed91c734 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -14,6 +14,7 @@ namespace OCA\News\Controller; use OCA\News\AppInfo\Application; +use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException; use OCP\IRequest; use OCP\IConfig; use OCP\IL10N; @@ -24,7 +25,6 @@ use OCA\News\Service\StatusService; use OCA\News\Explore\RecommendedSites; -use OCA\News\Explore\RecommendedSiteNotFoundException; use OCA\News\Db\FeedType; use OCP\IUserSession; diff --git a/lib/Db/FeedMapperV2.php b/lib/Db/FeedMapperV2.php index b3d8879f3d..5cc537fe7d 100644 --- a/lib/Db/FeedMapperV2.php +++ b/lib/Db/FeedMapperV2.php @@ -153,4 +153,29 @@ public function findAllFromFolder(?int $id): array return $this->findEntities($builder); } + + /** + * @param string $userId + * @param int $id + * @param int|null $maxItemID + */ + public function read(string $userId, int $id, ?int $maxItemID = null): void + { + $builder = $this->db->getQueryBuilder(); + $builder->update(ItemMapperV2::TABLE_NAME, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->setValue('unread', 0) + ->andWhere('feeds.user_id = :userId') + ->andWhere('feeds.id = :feedId') + ->setParameter('userId', $userId) + ->setParameter('feedId', $id); + + if ($maxItemID !== null) { + $builder->andWhere('items.id =< :maxItemId') + ->setParameter('maxItemId', $maxItemID); + } + + $this->db->executeUpdate($builder->getSQL()); + } + } diff --git a/lib/Db/FeedType.php b/lib/Db/FeedType.php index bf487992c4..1ccd592a8d 100644 --- a/lib/Db/FeedType.php +++ b/lib/Db/FeedType.php @@ -13,6 +13,11 @@ namespace OCA\News\Db; +/** + * Enum FeedType + * + * @package OCA\News\Db + */ class FeedType { const FEED = 0; diff --git a/lib/Db/FolderMapperV2.php b/lib/Db/FolderMapperV2.php index 85e07c07f4..12fa268875 100644 --- a/lib/Db/FolderMapperV2.php +++ b/lib/Db/FolderMapperV2.php @@ -95,4 +95,30 @@ public function findFromUser(string $userId, int $id): Entity return $this->findEntity($builder); } + + /** + * @param string $userId + * @param int $id + * @param int|null $maxItemID + * + * @return void + */ + public function read(string $userId, int $id, ?int $maxItemID = null): void + { + $builder = $this->db->getQueryBuilder(); + $builder->update(ItemMapperV2::TABLE_NAME, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->setValue('unread', 0) + ->andWhere('feeds.user_id = :userId') + ->andWhere('feeds.folder_id = :folderId') + ->setParameter('userId', $userId) + ->setParameter('folderId', $id); + + if ($maxItemID !== null) { + $builder->andWhere('items.id =< :maxItemId') + ->setParameter('maxItemId', $maxItemID); + } + + $this->db->executeUpdate($builder->getSQL()); + } } diff --git a/lib/Db/ItemMapper.php b/lib/Db/ItemMapper.php deleted file mode 100644 index 17f6638def..0000000000 --- a/lib/Db/ItemMapper.php +++ /dev/null @@ -1,586 +0,0 @@ - - * @author Bernhard Posselt - * @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\Entity; -use OCP\AppFramework\Db\Mapper; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; -use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\IDBConnection; - -/** - * Class LegacyItemMapper - * - * @package OCA\News\Db - * @deprecated use ItemMapper - */ -class ItemMapper extends Mapper -{ - - const TABLE_NAME = 'news_items'; - /** - * @var Time - */ - private $time; - - /** - * NewsMapper constructor. - * - * @param IDBConnection $db Database connection - * @param Time $time Time class - */ - public function __construct(IDBConnection $db, Time $time) - { - parent::__construct($db, static::TABLE_NAME, Item::class); - $this->time = $time; - } - - private function makeSelectQuery( - string $prependTo = '', - bool $oldestFirst = false, - bool $distinctFingerprint = false - ): string { - if ($oldestFirst) { - $ordering = 'ASC'; - } else { - $ordering = 'DESC'; - } - - return 'SELECT `items`.* FROM `*PREFIX*news_items` `items` ' . - 'JOIN `*PREFIX*news_feeds` `feeds` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'AND `feeds`.`user_id` = ? ' . - $prependTo . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . - 'ON `folders`.`id` = `feeds`.`folder_id` ' . - 'WHERE `feeds`.`folder_id` IS NULL ' . - 'OR `folders`.`deleted_at` = 0 ' . - 'ORDER BY `items`.`id` ' . $ordering; - } - - /** - * check if type is feed or all items should be shown - * - * @param bool $showAll - * @param int|null $type - * @return string - */ - private function buildStatusQueryPart($showAll, $type = null) - { - $sql = ''; - - if (isset($type) && $type === FeedType::STARRED) { - $sql = 'AND `items`.`starred` = '; - $sql .= $this->db->quote(true, IQueryBuilder::PARAM_BOOL) . ' '; - } elseif (!$showAll || $type === FeedType::UNREAD) { - $sql .= 'AND `items`.`unread` = '; - $sql .= $this->db->quote(true, IQueryBuilder::PARAM_BOOL) . ' '; - } - - return $sql; - } - - private function buildSearchQueryPart(array $search = []): string - { - return str_repeat('AND `items`.`search_index` LIKE ? ', count($search)); - } - - /** - * wrap and escape search parameters in a like statement - * - * @param string[] $search an array of strings that should be searched - * @return array with like parameters - */ - private function buildLikeParameters($search = []) - { - return array_map( - function ($param) { - $param = addcslashes($param, '\\_%'); - return '%' . mb_strtolower($param, 'UTF-8') . '%'; - }, - $search - ); - } - - /** - * @param int $id - * @param string $userId - * @return \OCA\News\Db\Item|Entity - */ - public function find(string $userId, int $id) - { - $sql = $this->makeSelectQuery('AND `items`.`id` = ? '); - return $this->findEntity($sql, [$userId, $id]); - } - - public function starredCount(string $userId): int - { - $sql = 'SELECT COUNT(*) AS size FROM `*PREFIX*news_items` `items` ' . - 'JOIN `*PREFIX*news_feeds` `feeds` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'AND `feeds`.`user_id` = ? ' . - 'AND `items`.`starred` = ? ' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . - 'ON `folders`.`id` = `feeds`.`folder_id` ' . - 'WHERE `feeds`.`folder_id` IS NULL ' . - 'OR `folders`.`deleted_at` = 0'; - - $params = [$userId, true]; - - $result = $this->execute($sql, $params)->fetch(); - - return (int)$result['size']; - } - - - public function readAll(int $highestItemId, string $time, string $userId): void - { - $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET unread = ? ' . - ', `last_modified` = ? ' . - 'WHERE `feed_id` IN (' . - 'SELECT `id` FROM `*PREFIX*news_feeds` ' . - 'WHERE `user_id` = ? ' . - ') ' . - 'AND `id` <= ?'; - $params = [false, $time, $userId, $highestItemId]; - $this->execute($sql, $params); - } - - - public function readFolder(?int $folderId, int $highestItemId, string $time, string $userId): void - { - $folderWhere = is_null($folderId) ? 'IS' : '='; - $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET unread = ? ' . - ', `last_modified` = ? ' . - 'WHERE `feed_id` IN (' . - 'SELECT `id` FROM `*PREFIX*news_feeds` ' . - "WHERE `folder_id` ${folderWhere} ? " . - 'AND `user_id` = ? ' . - ') ' . - 'AND `id` <= ?'; - $params = [false, $time, $folderId, $userId, - $highestItemId]; - $this->execute($sql, $params); - } - - - public function readFeed(int $feedId, int $highestItemId, string $time, string $userId): void - { - $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET unread = ? ' . - ', `last_modified` = ? ' . - 'WHERE `feed_id` = ? ' . - 'AND `id` <= ? ' . - 'AND EXISTS (' . - 'SELECT * FROM `*PREFIX*news_feeds` ' . - 'WHERE `user_id` = ? ' . - 'AND `id` = ? ) '; - $params = [false, $time, $feedId, $highestItemId, - $userId, $feedId]; - - $this->execute($sql, $params); - } - - - private function getOperator(bool $oldestFirst): string - { - if ($oldestFirst) { - return '>'; - } else { - return '<'; - } - } - - - public function findAllNew(int $updatedSince, int $type, bool $showAll, string $userId): array - { - $sql = $this->buildStatusQueryPart($showAll, $type); - - $sql .= 'AND `items`.`last_modified` >= ? '; - $sql = $this->makeSelectQuery($sql); - $params = [$userId, $updatedSince]; - return $this->findEntities($sql, $params); - } - - - public function findAllNewFolder(?int $id, int $updatedSince, bool $showAll, string $userId): array - { - $sql = $this->buildStatusQueryPart($showAll); - - $folderWhere = is_null($id) ? 'IS' : '='; - $sql .= "AND `feeds`.`folder_id` ${folderWhere} ? " . - 'AND `items`.`last_modified` >= ? '; - $sql = $this->makeSelectQuery($sql); - $params = [$userId, $id, $updatedSince]; - return $this->findEntities($sql, $params); - } - - - public function findAllNewFeed(?int $id, int $updatedSince, bool $showAll, string $userId): array - { - $sql = $this->buildStatusQueryPart($showAll); - - $sql .= 'AND `items`.`feed_id` = ? ' . - 'AND `items`.`last_modified` >= ? '; - $sql = $this->makeSelectQuery($sql); - $params = [$userId, $id, $updatedSince]; - return $this->findEntities($sql, $params); - } - - - /** - * @param (int|mixed|null)[] $params - */ - private function findEntitiesIgnoringNegativeLimit(string $sql, array $params, int $limit): array - { - // ignore limit if negative to offer a way to return all feeds - if ($limit >= 0) { - return $this->findEntities($sql, $params, $limit); - } else { - return $this->findEntities($sql, $params); - } - } - - - public function findAllFeed( - ?int $id, - int $limit, - int $offset, - bool $showAll, - bool $oldestFirst, - string $userId, - array $search = [] - ): array { - $params = [$userId]; - $params = array_merge($params, $this->buildLikeParameters($search)); - $params[] = $id; - - $sql = $this->buildStatusQueryPart($showAll); - $sql .= $this->buildSearchQueryPart($search); - - $sql .= 'AND `items`.`feed_id` = ? '; - if ($offset !== 0) { - $sql .= 'AND `items`.`id` ' . - $this->getOperator($oldestFirst) . ' ? '; - $params[] = $offset; - } - $sql = $this->makeSelectQuery($sql, $oldestFirst); - return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); - } - - - public function findAllFolder( - ?int $id, - int $limit, - int $offset, - bool $showAll, - bool $oldestFirst, - string $userId, - array $search = [] - ): array { - $params = [$userId]; - $params = array_merge($params, $this->buildLikeParameters($search)); - $params[] = $id; - - $sql = $this->buildStatusQueryPart($showAll); - $sql .= $this->buildSearchQueryPart($search); - - $folderWhere = is_null($id) ? 'IS' : '='; - $sql .= "AND `feeds`.`folder_id` ${folderWhere} ? "; - if ($offset !== 0) { - $sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? '; - $params[] = $offset; - } - $sql = $this->makeSelectQuery($sql, $oldestFirst); - return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); - } - - - /** - * @param string[] $search - */ - public function findAllItems( - int $limit, - int $offset, - int $type, - bool $showAll, - bool $oldestFirst, - string $userId, - array $search = [] - ): array { - $params = [$userId]; - $params = array_merge($params, $this->buildLikeParameters($search)); - $sql = $this->buildStatusQueryPart($showAll, $type); - $sql .= $this->buildSearchQueryPart($search); - - if ($offset !== 0) { - $sql .= 'AND `items`.`id` ' . - $this->getOperator($oldestFirst) . ' ? '; - $params[] = $offset; - } - - $sql = $this->makeSelectQuery($sql, $oldestFirst); - - return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); - } - - - public function findAllUnreadOrStarred(string $userId): array - { - $params = [$userId, true, true]; - $sql = 'AND (`items`.`unread` = ? OR `items`.`starred` = ?) '; - $sql = $this->makeSelectQuery($sql); - return $this->findEntities($sql, $params); - } - - /** - * @param $guidHash - * @param $feedId - * @param $userId - * - * @return Entity|Item - * @throws DoesNotExistException - * @throws MultipleObjectsReturnedException - */ - public function findByGuidHash($guidHash, $feedId, $userId) - { - $sql = $this->makeSelectQuery( - 'AND `items`.`guid_hash` = ? ' . - 'AND `feeds`.`id` = ? ' - ); - - return $this->findEntity($sql, [$userId, $guidHash, $feedId]); - } - - - /** - * Delete all items for feeds that have over $threshold unread and not - * starred items - * - * @param int $threshold the number of items that should be deleted - * - * @return void - */ - public function deleteReadOlderThanThreshold($threshold) - { - $params = [false, false, $threshold]; - - $sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' . - '`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' . - 'FROM `*PREFIX*news_items` `items` ' . - 'JOIN `*PREFIX*news_feeds` `feeds` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND `items`.`unread` = ? ' . - 'AND `items`.`starred` = ? ' . - 'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' . - 'HAVING COUNT(*) > ?'; - - $result = $this->execute($sql, $params); - - while ($row = $result->fetch()) { - $size = (int)$row['size']; - $limit = $size - $threshold; - $feed_id = $row['feed_id']; - - if ($limit > 0) { - $params = [false, false, $feed_id, $limit]; - $sql = 'SELECT `id` FROM `*PREFIX*news_items` ' . - 'WHERE `unread` = ? ' . - 'AND `starred` = ? ' . - 'AND `feed_id` = ? ' . - 'ORDER BY `id` ASC ' . - 'LIMIT 1 ' . - 'OFFSET ? '; - } - $limit_result = $this->execute($sql, $params); - if ($limit_row = $limit_result->fetch()) { - $limit_id = (int)$limit_row['id']; - $params = [false, false, $feed_id, $limit_id]; - $sql = 'DELETE FROM `*PREFIX*news_items` ' . - 'WHERE `unread` = ? ' . - 'AND `starred` = ? ' . - 'AND `feed_id` = ? ' . - 'AND `id` < ? '; - $this->execute($sql, $params); - } - } - } - - - public function getNewestItemId(string $userId): int - { - $sql = 'SELECT MAX(`items`.`id`) AS `max_id` ' . - 'FROM `*PREFIX*news_items` `items` ' . - 'JOIN `*PREFIX*news_feeds` `feeds` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND `feeds`.`user_id` = ?'; - $params = [$userId]; - - $result = $this->findOneQuery($sql, $params); - - return (int)$result['max_id']; - } - - - /** - * Returns a list of ids and userid of all items - * - * @param int|null $limit - * @param int|null $offset - * - * @return array|false - */ - public function findAllIds(?int $limit = null, ?int $offset = null) - { - $sql = 'SELECT `id` FROM `*PREFIX*news_items`'; - return $this->execute($sql, [], $limit, $offset)->fetchAll(); - } - - /** - * Update search indices of all items - * - * @return void - */ - public function updateSearchIndices(): void - { - // update indices in steps to prevent memory issues on larger systems - $step = 1000; // update 1000 items at a time - $itemCount = 1; - $offset = 0; - - // stop condition if there are no previously fetched items - while ($itemCount > 0) { - $items = $this->findAllIds($step, $offset); - $itemCount = count($items); - $this->updateSearchIndex($items); - $offset += $step; - } - } - - private function updateSearchIndex(array $items = []): void - { - foreach ($items as $row) { - $sql = 'SELECT * FROM `*PREFIX*news_items` WHERE `id` = ?'; - $params = [$row['id']]; - $item = $this->findEntity($sql, $params); - $item->generateSearchIndex(); - $this->update($item); - } - } - - /** - * @return void - */ - public function readItem(int $itemId, bool $isRead, string $lastModified, string $userId) - { - $item = $this->find($userId, $itemId); - - // reading an item should set all of the same items as read, whereas - // marking an item as unread should only mark the selected instance - // as unread - if ($isRead) { - $sql = 'UPDATE `*PREFIX*news_items` - SET `unread` = ?, - `last_modified` = ? - WHERE `fingerprint` = ? - AND `feed_id` IN ( - SELECT `f`.`id` FROM `*PREFIX*news_feeds` AS `f` - WHERE `f`.`user_id` = ? - )'; - $params = [false, $lastModified, $item->getFingerprint(), $userId]; - $this->execute($sql, $params); - } else { - $item->setLastModified($lastModified); - $item->setUnread(true); - $this->update($item); - } - } - - 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(); - } - /** - * Performs a SELECT query with all arguments appended to the WHERE clause - * The SELECT will be performed on the current table and takes 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); - } -} diff --git a/lib/Db/ItemMapperV2.php b/lib/Db/ItemMapperV2.php index 0c3191540b..ffaa5b6272 100644 --- a/lib/Db/ItemMapperV2.php +++ b/lib/Db/ItemMapperV2.php @@ -12,6 +12,7 @@ namespace OCA\News\Db; +use OCA\News\Service\Exceptions\ServiceValidationException; use OCA\News\Utility\Time; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; @@ -54,12 +55,11 @@ public function findAllFromUser(string $userId, array $params = []): array ->from($this->tableName, 'items') ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') ->where('feeds.user_id = :user_id') - ->andWhere('deleted_at = 0') + ->andWhere('feeds.deleted_at = 0') ->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR); foreach ($params as $key => $value) { - $builder->andWhere("${key} = :${key}") - ->setParameter($key, $value); + $builder->andWhere("${key} = " . $builder->createNamedParameter($value)); } return $this->findEntities($builder); @@ -73,13 +73,17 @@ public function findAllFromUser(string $userId, array $params = []): array public function findAll(): array { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') + $builder->select('*') ->from($this->tableName) - ->andWhere('deleted_at = 0'); + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('feeds.deleted_at = 0'); return $this->findEntities($builder); } + /** + * @inheritDoc + */ public function findFromUser(string $userId, int $id): Entity { $builder = $this->db->getQueryBuilder(); @@ -88,9 +92,9 @@ public function findFromUser(string $userId, int $id): Entity ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') ->where('feeds.user_id = :user_id') ->andWhere('items.id = :item_id') - ->andWhere('deleted_at = 0') + ->andWhere('feeds.deleted_at = 0') ->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR) - ->setParameter('item_id', $id, IQueryBuilder::PARAM_STR); + ->setParameter('item_id', $id, IQueryBuilder::PARAM_INT); return $this->findEntity($builder); } @@ -101,15 +105,15 @@ public function findFromUser(string $userId, int $id): Entity * @param int $feedId ID of the feed * @param string $guidHash hash to find with * - * @return Item + * @return Item|Entity * * @throws DoesNotExistException * @throws MultipleObjectsReturnedException */ - public function findByGuidHash(int $feedId, string $guidHash): Item + public function findByGuidHash(int $feedId, string $guidHash): Entity { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') + $builder->select('*') ->from($this->tableName) ->andWhere('feed_id = :feed_id') ->andWhere('guid_hash = :guid_hash') @@ -119,6 +123,34 @@ public function findByGuidHash(int $feedId, string $guidHash): Item return $this->findEntity($builder); } + /** + * Find a user item by a GUID hash. + * + * @param string $userId + * @param int $feedId ID of the feed + * @param string $guidHash hash to find with + * + * @return Item|Entity + * + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + */ + public function findForUserByGuidHash(string $userId, int $feedId, string $guidHash): Item + { + $builder = $this->db->getQueryBuilder(); + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('feeds.user_id = :user_id') + ->andWhere('feeds.id = :feed_id') + ->andWhere('items.guid_hash = :guid_hash') + ->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR) + ->setParameter('feed_id', $feedId, IQueryBuilder::PARAM_INT) + ->setParameter('guid_hash', $guidHash, IQueryBuilder::PARAM_STR); + + return $this->findEntity($builder); + } + /** * @param int $feedId * @@ -127,10 +159,10 @@ public function findByGuidHash(int $feedId, string $guidHash): Item public function findAllForFeed(int $feedId): array { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') + $builder->select('*') ->from($this->tableName) - ->andWhere('feed_id = :feed_id') - ->setParameter('feed_id', $feedId, IQueryBuilder::PARAM_INT); + ->where('feed_id = :feed_identifier') + ->setParameter('feed_identifier', $feedId, IQueryBuilder::PARAM_INT); return $this->findEntities($builder); } @@ -164,4 +196,303 @@ public function purgeDeleted(?string $userID, ?int $oldestDelete): void { //NO-OP } + + + /** + * @param string $userId + * @param int $maxItemId + */ + public function readAll(string $userId, int $maxItemId): void + { + $builder = $this->db->getQueryBuilder(); + + $builder->update($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->setValue('unread', 0) + ->andWhere('items.id =< :maxItemId') + ->andWhere('feeds.user_id = :userId') + ->setParameter('maxItemId', $maxItemId) + ->setParameter('userId', $userId); + + $this->db->executeUpdate($builder->getSQL()); + } + + /** + * @param string $userId + * + * @return Entity|Item + * + * @throws DoesNotExistException The item is not found + * @throws MultipleObjectsReturnedException Multiple items found + */ + public function newest(string $userId): Entity + { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->orderBy('items.updated_date', 'DESC') + ->where('feeds.user_id = :userId') + ->setParameter('userId', $userId) + ->setMaxResults(1); + + return $this->findEntity($builder); + } + + /** + * @param string $userId + * @param int $feedId + * @param int $updatedSince + * @param bool $hideRead + * + * @return Item[] + */ + public function findAllInFeedAfter( + string $userId, + int $feedId, + int $updatedSince, + bool $hideRead + ): array { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('items.updated_date >= :updatedSince') + ->andWhere('feeds.user_id = :userId') + ->andWhere('feeds.id = :feedId') + ->setParameters([ + 'updatedSince' => $updatedSince, + 'feedId' => $feedId, + 'userId'=> $userId, + ]) + ->orderBy('items.updated_date', 'DESC'); + + if ($hideRead === true) { + $builder->andWhere('items.unread = 1'); + } + + return $this->findEntities($builder); + } + + /** + * @param string $userId + * @param int|null $folderId + * @param int $updatedSince + * @param bool $hideRead + * + * @return Item[] + */ + public function findAllInFolderAfter( + string $userId, + ?int $folderId, + int $updatedSince, + bool $hideRead + ): array { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->innerJoin('feeds', FolderMapperV2::TABLE_NAME, 'folders', 'feeds.folder_id = folders.id') + ->andWhere('items.updated_date >= :updatedSince') + ->andWhere('feeds.user_id = :userId') + ->andWhere('folders.id = :folderId') + ->setParameters(['updatedSince' => $updatedSince, 'folderId' => $folderId, 'userId' => $userId]) + ->orderBy('items.updated_date', 'DESC'); + + if ($hideRead === true) { + $builder->andWhere('items.unread = 1'); + } + + return $this->findEntities($builder); + } + + /** + * @param string $userId + * @param int $updatedSince + * @param int $feedType + * + * @return Item[]|Entity[] + * @throws ServiceValidationException + */ + public function findAllAfter(string $userId, int $feedType, int $updatedSince): array + { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('items.updated_date >= :updatedSince') + ->andWhere('feeds.user_id = :userId') + ->setParameters(['updatedSince' => $updatedSince, 'userId' => $userId]) + ->orderBy('items.updated_date', 'DESC'); + + switch ($feedType) { + case FeedType::STARRED: + $builder->andWhere('items.starred = 1'); + break; + case FeedType::UNREAD: + $builder->andWhere('items.unread = 1'); + break; + default: + throw new ServiceValidationException('Unexpected Feed type in call'); + } + + return $this->findEntities($builder); + } + + /** + * @param string $userId + * @param int $feedId + * @param int $limit + * @param int $offset + * @param bool $hideRead + * @param bool $oldestFirst + * @param array $search + * + * @return Item[] + */ + public function findAllFeed( + string $userId, + int $feedId, + int $limit, + int $offset, + bool $hideRead, + bool $oldestFirst, + array $search + ): array { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('feeds.user_id = :userId') + ->andWhere('items.feed_id = :feedId') + ->setParameter('userId', $userId) + ->setParameter('feedId', $feedId) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC')); + + if ($search !== []) { + foreach ($search as $key => $term) { + $term = $this->db->escapeLikeParameter($term); + $builder->andWhere("items.search_index LIKE :term${key}") + ->setParameter("term${key}", "%$term%"); + } + } + + if ($hideRead === true) { + $builder->andWhere('items.unread = 1'); + } + + return $this->findEntities($builder); + } + + /** + * @param string $userId + * @param int|null $folderId + * @param int $limit + * @param int $offset + * @param bool $hideRead + * @param bool $oldestFirst + * @param array $search + * + * @return Item[] + */ + public function findAllFolder( + string $userId, + ?int $folderId, + int $limit, + int $offset, + bool $hideRead, + bool $oldestFirst, + array $search + ): array { + $builder = $this->db->getQueryBuilder(); + + if ($folderId === null) { + $folderWhere = $builder->expr()->isNull('feeds.folder_id'); + } else { + $folderWhere = $builder->expr()->eq('feeds.folder_id', $folderId); + } + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('feeds.user_id = :userId') + ->andWhere($folderWhere) + ->setParameter('userId', $userId) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC')); + + if ($search !== []) { + foreach ($search as $key => $term) { + $term = $this->db->escapeLikeParameter($term); + $builder->andWhere("items.search_index LIKE :term${key}") + ->setParameter("term${key}", "%$term%"); + } + } + + if ($hideRead === true) { + $builder->andWhere('items.unread = 1'); + } + + return $this->findEntities($builder); + } + + /** + * @param string $userId + * @param int $type + * @param int $limit + * @param int $offset + * @param bool $oldestFirst + * @param array $search + * + * @return Item[] + * @throws ServiceValidationException + */ + public function findAllItems( + string $userId, + int $type, + int $limit, + int $offset, + bool $oldestFirst, + array $search + ): array { + $builder = $this->db->getQueryBuilder(); + + $builder->select('items.*') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->andWhere('feeds.user_id = :userId') + ->setParameter('userId', $userId) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC')); + + if ($search !== []) { + foreach ($search as $key => $term) { + $term = $this->db->escapeLikeParameter($term); + $builder->andWhere("items.search_index LIKE :term${key}") + ->setParameter("term${key}", "%$term%"); + } + } + + switch ($type) { + case FeedType::STARRED: + $builder->andWhere('items.starred = 1'); + break; + case FeedType::UNREAD: + $builder->andWhere('items.unread = 1'); + break; + default: + throw new ServiceValidationException('Unexpected Feed type in call'); + } + + return $this->findEntities($builder); + } } diff --git a/lib/Db/MapperFactory.php b/lib/Db/MapperFactory.php deleted file mode 100644 index 35d587a6b5..0000000000 --- a/lib/Db/MapperFactory.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Db; - -use OCA\News\Utility\Time; -use OCP\IDBConnection; - -use OCA\News\Db\Mysql\ItemMapper as MysqlItemMapper; -use OCA\News\DependencyInjection\IFactory; - -/** - * Class LegacyMapperFactory - * - * @package OCA\News\Db - * @deprecated not needed in modern system - */ -class MapperFactory implements IFactory -{ - - private $dbType; - private $db; - /** - * @var Time - */ - private $time; - - public function __construct(IDBConnection $db, $databaseType, Time $time) - { - $this->dbType = $databaseType; - $this->db = $db; - $this->time = $time; - } - - public function build() - { - switch ($this->dbType) { - case 'mysql': - return new MysqlItemMapper($this->db, $this->time); - default: - return new ItemMapper($this->db, $this->time); - } - } -} diff --git a/lib/Db/Mysql/ItemMapper.php b/lib/Db/Mysql/ItemMapper.php deleted file mode 100644 index 5ad3baf79a..0000000000 --- a/lib/Db/Mysql/ItemMapper.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Db\Mysql; - -use OCA\News\Utility\Time; -use OCP\IDBConnection; - -/** - * Class LegacyItemMapper - * - * @package OCA\News\Db\Mysql - * @deprecated use normal ItemMapper - */ -class ItemMapper extends \OCA\News\Db\ItemMapper -{ - - public function __construct(IDBConnection $db, Time $time) - { - parent::__construct($db, $time); - } - - - /** - * Delete all items for feeds that have over $threshold unread and not - * starred items - * - * @param int $threshold the number of items that should be deleted - * - * @return void - */ - public function deleteReadOlderThanThreshold($threshold) - { - $sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' . - '`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' . - 'FROM `*PREFIX*news_items` `items` ' . - 'JOIN `*PREFIX*news_feeds` `feeds` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND `items`.`unread` = ? ' . - 'AND `items`.`starred` = ? ' . - 'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' . - 'HAVING COUNT(*) > ?'; - $params = [false, false, $threshold]; - $result = $this->execute($sql, $params); - - while ($row = $result->fetch()) { - $size = (int) $row['size']; - $limit = $size - $threshold; - - if ($limit > 0) { - $params = [false, false, $row['feed_id'], $limit]; - - $sql = 'DELETE FROM `*PREFIX*news_items` ' . - 'WHERE `unread` = ? ' . - 'AND `starred` = ? ' . - 'AND `feed_id` = ? ' . - 'ORDER BY `id` ASC ' . - 'LIMIT ?'; - - $this->execute($sql, $params); - } - } - } - - /** - * @return void - */ - public function readItem($itemId, $isRead, $lastModified, $userId) - { - $item = $this->find($itemId, $userId); - - if ($isRead) { - $sql = 'UPDATE `*PREFIX*news_items` `items` - JOIN `*PREFIX*news_feeds` `feeds` - ON `feeds`.`id` = `items`.`feed_id` - SET `items`.`unread` = ?, - `items`.`last_modified` = ? - WHERE `items`.`fingerprint` = ? - AND `feeds`.`user_id` = ?'; - $params = [false, $lastModified, $item->getFingerprint(), $userId]; - $this->execute($sql, $params); - } else { - $item->setLastModified($lastModified); - $item->setUnread(true); - $this->update($item); - } - } -} diff --git a/lib/DependencyInjection/IFactory.php b/lib/DependencyInjection/IFactory.php deleted file mode 100644 index 73c2833801..0000000000 --- a/lib/DependencyInjection/IFactory.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\DependencyInjection; - -interface IFactory -{ - - /** - * Method that constructs the object - * - * @return mixed the constructed object - */ - public function build(); -} diff --git a/lib/Explore/RecommendedSiteNotFoundException.php b/lib/Explore/Exceptions/RecommendedSiteNotFoundException.php similarity index 91% rename from lib/Explore/RecommendedSiteNotFoundException.php rename to lib/Explore/Exceptions/RecommendedSiteNotFoundException.php index 9d3e717c7e..444bec23b6 100644 --- a/lib/Explore/RecommendedSiteNotFoundException.php +++ b/lib/Explore/Exceptions/RecommendedSiteNotFoundException.php @@ -11,7 +11,7 @@ * @copyright 2012-2014 Bernhard Posselt */ -namespace OCA\News\Explore; +namespace OCA\News\Explore\Exceptions; use Exception; diff --git a/lib/Explore/RecommendedSites.php b/lib/Explore/RecommendedSites.php index 59ca3f337b..f483e64cb9 100644 --- a/lib/Explore/RecommendedSites.php +++ b/lib/Explore/RecommendedSites.php @@ -13,6 +13,8 @@ namespace OCA\News\Explore; +use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException; + class RecommendedSites { diff --git a/lib/Fetcher/FetcherException.php b/lib/Fetcher/FetcherException.php deleted file mode 100644 index 263b542393..0000000000 --- a/lib/Fetcher/FetcherException.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Fetcher; - -class FetcherException extends \Exception -{ - - /** - * Constructor - * - * @param string $msg the error message - */ - public function __construct(string $msg) - { - parent::__construct($msg); - } -} diff --git a/lib/Service/Exceptions/ServiceConflictException.php b/lib/Service/Exceptions/ServiceConflictException.php index dd6ba03c9d..ed3d3a54c1 100644 --- a/lib/Service/Exceptions/ServiceConflictException.php +++ b/lib/Service/Exceptions/ServiceConflictException.php @@ -13,16 +13,22 @@ namespace OCA\News\Service\Exceptions; +use Exception; +use OCP\AppFramework\Db\IMapperException; + +/** + * Class ServiceConflictException + * + * @package OCA\News\Service\Exceptions + */ class ServiceConflictException extends ServiceException { /** - * Constructor - * - * @param string $msg the error message + * @inheritDoc */ - public function __construct(string $msg) + public static function from(IMapperException $exception): ServiceException { - parent::__construct($msg); + return new self($exception->getMessage(), $exception->getCode(), $exception); } } diff --git a/lib/Service/Exceptions/ServiceException.php b/lib/Service/Exceptions/ServiceException.php index c5ddddbd98..69b963ab64 100644 --- a/lib/Service/Exceptions/ServiceException.php +++ b/lib/Service/Exceptions/ServiceException.php @@ -13,16 +13,35 @@ namespace OCA\News\Service\Exceptions; -class ServiceException extends \Exception +use Exception; +use OCP\AppFramework\Db\IMapperException; + +/** + * Class ServiceException + * + * @package OCA\News\Service\Exceptions + */ +abstract class ServiceException extends Exception { /** * Constructor * * @param string $msg the error message + * @param int $code + * @param Exception|null $previous */ - public function __construct(string $msg) + final public function __construct(string $msg, int $code = 0, ?Exception $previous = null) { - parent::__construct($msg); + parent::__construct($msg, $code, $previous); } + + /** + * Create exception from Mapper exception. + * + * @param IMapperException|Exception $exception Existing exception + * + * @return static + */ + abstract public static function from(IMapperException $exception): ServiceException; } diff --git a/lib/Service/Exceptions/ServiceNotFoundException.php b/lib/Service/Exceptions/ServiceNotFoundException.php index 6c68ea6b29..f70706506e 100644 --- a/lib/Service/Exceptions/ServiceNotFoundException.php +++ b/lib/Service/Exceptions/ServiceNotFoundException.php @@ -13,16 +13,21 @@ namespace OCA\News\Service\Exceptions; +use Exception; +use OCP\AppFramework\Db\IMapperException; + +/** + * Class ServiceNotFoundException + * + * @package OCA\News\Service\Exceptions + */ class ServiceNotFoundException extends ServiceException { - /** - * Constructor - * - * @param string $msg the error message + * @inheritDoc */ - public function __construct(string $msg) + public static function from(IMapperException $exception): ServiceException { - parent::__construct($msg); + return new self($exception->getMessage(), $exception->getCode(), $exception); } } diff --git a/lib/Service/Exceptions/ServiceValidationException.php b/lib/Service/Exceptions/ServiceValidationException.php index 8e9dc9fee2..c41ce4aace 100644 --- a/lib/Service/Exceptions/ServiceValidationException.php +++ b/lib/Service/Exceptions/ServiceValidationException.php @@ -13,16 +13,22 @@ namespace OCA\News\Service\Exceptions; +use Exception; +use OCP\AppFramework\Db\IMapperException; + +/** + * Class ServiceValidationException + * + * @package OCA\News\Service\Exceptions + */ class ServiceValidationException extends ServiceException { /** - * Constructor - * - * @param string $msg the error message + * @inheritDoc */ - public function __construct(string $msg) + public static function from(IMapperException $exception): ServiceException { - parent::__construct($msg); + return new self($exception->getMessage(), $exception->getCode(), $exception); } } diff --git a/lib/Service/FeedServiceV2.php b/lib/Service/FeedServiceV2.php index 00511beb6e..5c9fed4854 100644 --- a/lib/Service/FeedServiceV2.php +++ b/lib/Service/FeedServiceV2.php @@ -18,6 +18,7 @@ use HTMLPurifier; 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; @@ -119,7 +120,7 @@ public function findAllForUserRecursive(string $userId): array $feeds = $this->mapper->findAllFromUser($userId); foreach ($feeds as &$feed) { - $items = $this->itemService->findAllForFeed($feed->getId()); + $items = $this->itemService->findAllInFeed($userId, $feed->getId()); $feed->items = $items; } return $feeds; @@ -342,4 +343,21 @@ public function fetchAll(): void $this->fetch($feed); } } + + /** + * Mark a feed as read + * + * @param string $userId Feed owner + * @param int $id Feed ID + * @param int|null $maxItemID Highest item ID to mark as read + * + * @throws ServiceConflictException + * @throws ServiceNotFoundException + */ + public function read(string $userId, int $id, ?int $maxItemID = null): void + { + $feed = $this->find($userId, $id); + + $this->mapper->read($userId, $feed->getId(), $maxItemID); + } } diff --git a/lib/Service/FolderServiceV2.php b/lib/Service/FolderServiceV2.php index ae8d378165..d13b4afc07 100644 --- a/lib/Service/FolderServiceV2.php +++ b/lib/Service/FolderServiceV2.php @@ -178,4 +178,21 @@ public function open(string $userId, ?int $folderId, bool $open): Entity $folder->setOpened($open); return $this->mapper->update($folder); } + + /** + * Mark a folder as read + * + * @param string $userId Folder owner + * @param int $id Folder ID + * @param int|null $maxItemID Highest item ID to mark as read + * + * @throws ServiceConflictException + * @throws ServiceNotFoundException + */ + public function read(string $userId, int $id, ?int $maxItemID = null): void + { + $folder = $this->find($userId, $id); + + $this->mapper->read($userId, $folder->getId(), $maxItemID); + } } diff --git a/lib/Service/ItemService.php b/lib/Service/ItemService.php deleted file mode 100644 index 8ba0a4b733..0000000000 --- a/lib/Service/ItemService.php +++ /dev/null @@ -1,352 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Service; - -use OCA\News\AppInfo\Application; -use OCA\News\Db\ItemMapperV2; -use OCA\News\Service\Exceptions\ServiceNotFoundException; -use OCP\IConfig; -use OCP\AppFramework\Db\DoesNotExistException; - -use OCA\News\Db\ItemMapper; -use OCA\News\Db\FeedType; -use OCA\News\Utility\Time; -use Psr\Log\LoggerInterface; - -/** - * Class LegacyItemService - * - * @package OCA\News\Service - * @deprecated use ItemServiceV2 - */ -class ItemService extends Service -{ - - /** - * @var IConfig - */ - private $config; - /** - * @var Time - */ - private $timeFactory; - /** - * @var ItemMapper - */ - private $oldItemMapper; - - public function __construct( - ItemMapperV2 $itemMapper, - ItemMapper $oldItemMapper, - Time $timeFactory, - IConfig $config, - LoggerInterface $logger - ) { - parent::__construct($itemMapper, $logger); - $this->config = $config; - $this->timeFactory = $timeFactory; - $this->oldItemMapper = $oldItemMapper; - } - - - /** - * Returns all new items - * - * @param int|null $id the id of the feed, 0 for starred or all items - * @param int $type the type of the feed - * @param int $updatedSince a timestamp with the last modification date - * returns only items with a >= modified - * timestamp - * @param boolean $showAll if unread items should also be returned - * @param string $userId the name of the user - * - * @return array of items - */ - public function findAllNew(?int $id, $type, int $updatedSince, bool $showAll, string $userId) - { - switch ($type) { - case FeedType::FEED: - return $this->oldItemMapper->findAllNewFeed( - $id, - $updatedSince, - $showAll, - $userId - ); - case FeedType::FOLDER: - return $this->oldItemMapper->findAllNewFolder( - $id, - $updatedSince, - $showAll, - $userId - ); - default: - return $this->oldItemMapper->findAllNew( - $updatedSince, - $type, - $showAll, - $userId - ); - } - } - - - /** - * Returns all items - * - * @param int|null $id the id of the feed, 0 for starred or all items - * @param int $type the type of the feed - * @param int $limit how many items should be returned - * @param int $offset the offset - * @param boolean $showAll if unread items should also be returned - * @param boolean $oldestFirst if it should be ordered by oldest first - * @param string $userId the name of the user - * @param string[] $search an array of keywords that the result should - * contain in either the author, title, link - * or body - * - * @return array of items - */ - public function findAllItems( - ?int $id, - $type, - $limit, - $offset, - $showAll, - $oldestFirst, - $userId, - $search = [] - ) { - switch ($type) { - case FeedType::FEED: - return $this->oldItemMapper->findAllFeed( - $id, - $limit, - $offset, - $showAll, - $oldestFirst, - $userId, - $search - ); - case FeedType::FOLDER: - return $this->oldItemMapper->findAllFolder( - $id, - $limit, - $offset, - $showAll, - $oldestFirst, - $userId, - $search - ); - default: - return $this->oldItemMapper->findAllItems( - $limit, - $offset, - $type, - $showAll, - $oldestFirst, - $userId, - $search - ); - } - } - - public function findAllForUser(string $userId, array $params = []): array - { - return $this->mapper->findAllFromUser($userId, $params); - } - - - /** - * Star or unstar an item - * - * @param int $feedId the id of the item's feed that should be starred - * @param string $guidHash the guidHash of the item that should be starred - * @param boolean $isStarred if true the item will be marked as starred, - * if false unstar - * @param string $userId the name of the user for security reasons - * - * @throws ServiceNotFoundException if the item does not exist - * - * @return void - */ - public function star($feedId, $guidHash, $isStarred, $userId): void - { - try { - $item = $this->mapper->findByGuidHash($feedId, $guidHash); - - $item->setStarred($isStarred); - - $this->mapper->update($item); - } catch (DoesNotExistException $ex) { - throw new ServiceNotFoundException($ex->getMessage()); - } - } - - - /** - * Read or unread an item - * - * @param int $itemId the id of the item that should be read - * @param boolean $isRead if true the item will be marked as read, - * if false unread - * @param string $userId the name of the user for security reasons - * - * @throws ServiceNotFoundException if the item does not exist - * - * @return void - */ - public function read($itemId, $isRead, $userId): void - { - try { - $lastModified = $this->timeFactory->getMicroTime(); - $this->oldItemMapper->readItem($itemId, $isRead, $lastModified, $userId); - } catch (DoesNotExistException $ex) { - throw new ServiceNotFoundException($ex->getMessage()); - } - } - - - /** - * Set all items read - * - * @param int $highestItemId all items below that are marked read. This is - * used to prevent marking items as read that - * the users hasn't seen yet - * @param string $userId the name of the user - * - * @return void - */ - public function readAll($highestItemId, $userId): void - { - $time = $this->timeFactory->getMicroTime(); - $this->oldItemMapper->readAll($highestItemId, $time, $userId); - } - - - /** - * Set a folder read - * - * @param int|null $folderId the id of the folder that should be marked read - * @param int $highestItemId all items below that are marked read. This is - * used to prevent marking items as read that - * the users hasn't seen yet - * @param string $userId the name of the user - * - * @return void - */ - public function readFolder(?int $folderId, $highestItemId, $userId): void - { - $time = $this->timeFactory->getMicroTime(); - $this->oldItemMapper->readFolder( - $folderId, - $highestItemId, - $time, - $userId - ); - } - - - /** - * Set a feed read - * - * @param int $feedId the id of the feed that should be marked read - * @param int $highestItemId all items below that are marked read. This is - * used to prevent marking items as read that - * the users hasn't seen yet - * @param string $userId the name of the user - * - * @return void - */ - public function readFeed($feedId, $highestItemId, $userId): void - { - $time = $this->timeFactory->getMicroTime(); - $this->oldItemMapper->readFeed($feedId, $highestItemId, $time, $userId); - } - - - /** - * This method deletes all unread feeds that are not starred and over the - * count of $this->autoPurgeCount starting by the oldest. This is to clean - * up the database so that old entries don't spam your db. As criteria for - * old, the id is taken - * - * @return void - */ - public function autoPurgeOld(): void - { - $count = $this->config->getAppValue( - Application::NAME, - 'autoPurgeCount', - Application::DEFAULT_SETTINGS['autoPurgeCount'] - ); - if ($count >= 0) { - $this->oldItemMapper->deleteReadOlderThanThreshold($count); - } - } - - - /** - * Returns the newest item id, use this for marking feeds read - * - * @param string $userId the name of the user - * @throws ServiceNotFoundException if there is no newest item - * @return int - */ - public function getNewestItemId($userId) - { - try { - return $this->oldItemMapper->getNewestItemId($userId); - } catch (DoesNotExistException $ex) { - throw new ServiceNotFoundException($ex->getMessage()); - } - } - - - /** - * Returns the starred count - * - * @param string $userId the name of the user - * @return int the count - */ - public function starredCount($userId) - { - return $this->oldItemMapper->starredCount($userId); - } - - - /** - * @param string $userId from which user the items should be taken - * @return array of items which are starred or unread - */ - public function getUnreadOrStarred($userId): array - { - return $this->oldItemMapper->findAllUnreadOrStarred($userId); - } - - - /** - * Regenerates the search index for all items - * - * @return void - */ - public function generateSearchIndices(): void - { - $this->oldItemMapper->updateSearchIndices(); - } - - public function findAll(): array - { - return $this->mapper->findAll(); - } -} diff --git a/lib/Service/ItemServiceV2.php b/lib/Service/ItemServiceV2.php index 54cefa1973..a8b6048ba7 100644 --- a/lib/Service/ItemServiceV2.php +++ b/lib/Service/ItemServiceV2.php @@ -13,10 +13,16 @@ namespace OCA\News\Service; use OCA\News\AppInfo\Application; +use OCA\News\Db\Feed; +use OCA\News\Db\FeedType; use OCA\News\Db\Item; use OCA\News\Db\ItemMapperV2; +use OCA\News\Service\Exceptions\ServiceConflictException; +use OCA\News\Service\Exceptions\ServiceNotFoundException; +use OCA\News\Service\Exceptions\ServiceValidationException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\IConfig; use Psr\Log\LoggerInterface; @@ -83,14 +89,15 @@ public function findAll(): array public function insertOrUpdate(Item $item): Entity { try { - $db_item = $this->mapper->findByGuidHash($item->getFeedId(), $item->getGuidHash()); + $db_item = $this->findByGuidHash($item->getFeedId(), $item->getGuidHash()); // Transfer user modifications $item->setUnread($db_item->isUnread()) ->setStarred($db_item->isStarred()) ->setId($db_item->getId()); - $item->generateSearchIndex(); + $item->generateSearchIndex();//generates fingerprint + // We don't want to update the database record if there is no // change in the fetched item if ($db_item->getFingerprint() === $item->getFingerprint()) { @@ -104,39 +111,282 @@ public function insertOrUpdate(Item $item): Entity } /** - * @param int $feedId + * @param int|null $threshold * - * @return array + * @return string */ - public function findAllForFeed(int $feedId): array + public function purgeOverThreshold(?int $threshold = null) { - return $this->mapper->findAllForFeed($feedId); + if ($threshold === null) { + $threshold = $this->config->getAppValue( + Application::NAME, + 'autoPurgeCount', + Application::DEFAULT_SETTINGS['autoPurgeCount'] + ); + } + + return $this->mapper->deleteOverThreshold(intval($threshold)); } + /** + * Return all starred items + * + * @param string $userId + * + * @return Item[] + */ + public function starred(string $userId): array + { + return $this->findAllForUser($userId, ['starred' => 1]); + } + /** + * Mark an item as read + * + * @param string $userId Item owner + * @param int $id Item ID + * @param bool $read + * + * @return Item + * @throws ServiceNotFoundException + * @throws ServiceConflictException + */ + public function read(string $userId, int $id, bool $read): Entity + { + /** @var Item $item */ + $item = $this->find($userId, $id); + + $item->setUnread(!$read); + + return $this->mapper->update($item); + } - public function purgeOverThreshold(int $threshold = null) + /** + * Mark an item as starred + * + * @param string $userId Item owner + * @param int $id Item ID + * @param bool $starred + * + * @return Item + * @throws ServiceNotFoundException|ServiceConflictException + */ + public function star(string $userId, int $id, bool $starred): Entity { + /** @var Item $item */ + $item = $this->find($userId, $id); + + $item->setStarred($starred); - $threshold = (int) $threshold ?? $this->config->getAppValue( - Application::NAME, - 'autoPurgeCount', - Application::DEFAULT_SETTINGS['autoPurgeCount'] - ); + return $this->mapper->update($item); + } - if ($threshold === 0) { - return ''; + /** + * Mark an item as starred by GUID hash + * + * @param string $userId Item owner + * @param int $feedId Item ID + * @param string $guidHash + * @param bool $starred + * + * @return Item + * @throws ServiceConflictException + * @throws ServiceNotFoundException + */ + public function starByGuid(string $userId, int $feedId, string $guidHash, bool $starred): Entity + { + try { + $item = $this->mapper->findForUserByGuidHash($userId, $feedId, $guidHash); + } catch (DoesNotExistException $ex) { + throw ServiceNotFoundException::from($ex); + } catch (MultipleObjectsReturnedException $ex) { + throw ServiceConflictException::from($ex); } - return $this->mapper->deleteOverThreshold($threshold); + $item->setStarred($starred); + + return $this->mapper->update($item); + } + + /** + * Mark all items as read + * + * @param string $userId Item owner + * @param int $maxItemId + * + * @return void + */ + public function readAll(string $userId, int $maxItemId): void + { + $this->mapper->readAll($userId, $maxItemId); + } + + /** + * @param string $userId + * + * @return Item + */ + public function newest(string $userId): Entity + { + try { + return $this->mapper->newest($userId); + } catch (DoesNotExistException $e) { + throw ServiceNotFoundException::from($e); + } catch (MultipleObjectsReturnedException $e) { + throw ServiceConflictException::from($e); + } } /** * @param int $feedId * @param string $guidHash + * + * @return Item|Entity + * + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException */ - public function findForGuidHash(int $feedId, string $guidHash) + public function findByGuidHash(int $feedId, string $guidHash): Entity { return $this->mapper->findByGuidHash($feedId, $guidHash); } + + /** + * Convenience method to find all items in a feed. + * + * @param string $userId + * @param int $feedId + * + * @return array + */ + public function findAllInFeed(string $userId, int $feedId): array + { + return $this->findAllInFeedAfter($userId, $feedId, PHP_INT_MIN, false); + } + + /** + * Returns all new items in a feed + * @param string $userId the name of the user + * @param int $feedId the id of the feed + * @param int $updatedSince a timestamp with the minimal modification date + * @param boolean $hideRead if unread items should also be returned + * + * @return array of items + */ + public function findAllInFeedAfter(string $userId, int $feedId, int $updatedSince, bool $hideRead): array + { + return $this->mapper->findAllInFeedAfter($userId, $feedId, $updatedSince, $hideRead); + } + + /** + * Returns all new items in a folder + * @param string $userId the name of the user + * @param int|null $folderId the id of the folder + * @param int $updatedSince a timestamp with the minimal modification date + * @param boolean $hideRead if unread items should also be returned + * + * @return array of items + */ + public function findAllInFolderAfter(string $userId, ?int $folderId, int $updatedSince, bool $hideRead): array + { + return $this->mapper->findAllInFolderAfter($userId, $folderId, $updatedSince, $hideRead); + } + + /** + * Returns all new items of a type + * + * @param string $userId the name of the user + * @param int $feedType the type of feed items to fetch. (starred || unread) + * @param int $updatedSince a timestamp with the minimal modification date + * + * @return array of items + * + * @throws ServiceValidationException + */ + public function findAllAfter(string $userId, int $feedType, int $updatedSince): array + { + if (!in_array($feedType, [FeedType::STARRED, FeedType::UNREAD])) { + throw new ServiceValidationException('Trying to find in unknown type'); + } + + return $this->mapper->findAllAfter($userId, $feedType, $updatedSince); + } + + + /** + * Returns all items + * + * @param int $feedId the id of the feed, 0 for starred or all items + * @param int $limit how many items should be returned + * @param int $offset the offset + * @param boolean $hideRead if unread items should also be returned + * @param boolean $oldestFirst if it should be ordered by oldest first + * @param string $userId the name of the user + * @param string[] $search an array of keywords that the result should + * contain in either the author, title, link + * or body + * + * @return array of items + */ + public function findAllInFeedWithFilters( + string $userId, + int $feedId, + int $limit, + int $offset, + bool $hideRead, + bool $oldestFirst, + array $search = [] + ): array { + return $this->mapper->findAllFeed($userId, $feedId, $limit, $offset, $hideRead, $oldestFirst, $search); + } + /** + * Returns all items + * + * @param int|null $folderId the id of the feed, 0 for starred or all items + * @param int $limit how many items should be returned + * @param int $offset the offset + * @param boolean $hideRead if unread items should also be returned + * @param boolean $oldestFirst if it should be ordered by oldest first + * @param string $userId the name of the user + * @param string[] $search an array of keywords that the result should + * contain in either the author, title, link + * or body + * + * @return array of items + */ + public function findAllInFolderWithFilters( + string $userId, + ?int $folderId, + int $limit, + int $offset, + bool $hideRead, + bool $oldestFirst, + array $search = [] + ): array { + return $this->mapper->findAllFolder($userId, $folderId, $limit, $offset, $hideRead, $oldestFirst, $search); + } + /** + * Returns all items + * + * @param int $type the type of the feed + * @param int $limit how many items should be returned + * @param int $offset the offset + * @param boolean $oldestFirst if it should be ordered by oldest first + * @param string $userId the name of the user + * @param string[] $search an array of keywords that the result should + * contain in either the author, title, link + * or body + * + * @return array of items + */ + public function findAllWithFilters( + string $userId, + int $type, + int $limit, + int $offset, + bool $oldestFirst, + array $search = [] + ): array { + return $this->mapper->findAllItems($userId, $type, $limit, $offset, $oldestFirst, $search); + } } diff --git a/lib/Service/Service.php b/lib/Service/Service.php index 1d7fdffb65..951d460f6c 100644 --- a/lib/Service/Service.php +++ b/lib/Service/Service.php @@ -14,6 +14,7 @@ namespace OCA\News\Service; use OCA\News\Db\NewsMapperV2; +use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; @@ -65,14 +66,13 @@ abstract public function findAllForUser(string $userId, array $params = []): arr */ abstract public function findAll(): array; - /** * Delete an entity * * @param int $id the id of the entity * @param string $userId the name of the user for security reasons * - * @throws ServiceNotFoundException if the entity does not exist, or there + * @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there * are more than one of it */ public function delete(string $userId, int $id): Entity @@ -102,7 +102,7 @@ public function insert(Entity $entity): 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 + * @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there * are more than one of it */ public function update(string $userId, Entity $entity): Entity @@ -119,7 +119,7 @@ public function update(string $userId, Entity $entity): Entity * @param string $userId the name of the user for security reasons * * @return Entity the entity - * @throws ServiceNotFoundException if the entity does not exist, or there + * @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there * are more than one of it */ public function find(string $userId, int $id): Entity @@ -127,9 +127,9 @@ public function find(string $userId, int $id): Entity try { return $this->mapper->findFromUser($userId, $id); } catch (DoesNotExistException $ex) { - throw new ServiceNotFoundException($ex->getMessage()); + throw ServiceNotFoundException::from($ex); } catch (MultipleObjectsReturnedException $ex) { - throw new ServiceNotFoundException($ex->getMessage()); + throw ServiceConflictException::from($ex); } } diff --git a/phpunit.xml b/phpunit.xml index 6a2f2d01bb..8d21b4051e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,7 @@ ./tests/Unit + ./lib/ @@ -13,7 +14,10 @@ ./lib/AppInfo/Application.php ./lib/Controller/JSONHttpErrorTrait.php - ./lib/**Exception.php + ./lib/*/Exceptions + ./lib/Migration + ./lib/Db/FeedType.php + ./lib/Db/IAPI.php diff --git a/tests/Unit/Command/FolderDeleteTest.php b/tests/Unit/Command/FolderDeleteTest.php index c87b05acd0..e9076d9ba6 100644 --- a/tests/Unit/Command/FolderDeleteTest.php +++ b/tests/Unit/Command/FolderDeleteTest.php @@ -82,7 +82,7 @@ public function testValid() */ public function testInValid() { - $this->expectException('OCA\News\Service\Exceptions\ServiceException'); + $this->expectException('OCA\News\Service\Exceptions\ServiceValidationException'); $this->expectExceptionMessage('Can not remove root folder!'); $this->consoleInput->expects($this->exactly(2)) diff --git a/tests/Unit/Controller/FeedApiControllerTest.php b/tests/Unit/Controller/FeedApiControllerTest.php index a6a1db548e..4e6f5f1f30 100644 --- a/tests/Unit/Controller/FeedApiControllerTest.php +++ b/tests/Unit/Controller/FeedApiControllerTest.php @@ -18,7 +18,7 @@ use Exception; use OCA\News\Controller\FeedApiController; use OCA\News\Service\FeedServiceV2; -use OCA\News\Service\ItemService; +use OCA\News\Service\ItemServiceV2; use \OCP\AppFramework\Http; use \OCA\News\Service\Exceptions\ServiceNotFoundException; @@ -40,7 +40,7 @@ class FeedApiControllerTest extends TestCase private $feedService; /** - * @var \PHPUnit\Framework\MockObject\MockObject|ItemService + * @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2 */ private $itemService; @@ -80,7 +80,7 @@ protected function setUp(): void $this->feedService = $this->getMockBuilder(FeedServiceV2::class) ->disableOriginalConstructor() ->getMock(); - $this->itemService = $this->getMockBuilder(ItemService::class) + $this->itemService = $this->getMockBuilder(ItemServiceV2::class) ->disableOriginalConstructor() ->getMock(); $this->class = new FeedApiController( @@ -96,18 +96,17 @@ protected function setUp(): void public function testIndex() { - $feeds = [new Feed()]; - $starredCount = 3; - $newestItemId = 2; + $feed = Feed::fromParams(['id' => 5]); + $feeds = [$feed]; $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->equalTo($this->userID)) - ->will($this->returnValue($starredCount)); + ->will($this->returnValue([1, 2, 3])); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with($this->equalTo($this->userID)) - ->will($this->returnValue($newestItemId)); + ->will($this->returnValue($feeds[0])); $this->feedService->expects($this->once()) ->method('findAllForUser') ->with($this->equalTo($this->userID)) @@ -118,8 +117,8 @@ public function testIndex() $this->assertEquals( [ 'feeds' => [$feeds[0]->toAPI()], - 'starredCount' => $starredCount, - 'newestItemId' => $newestItemId + 'starredCount' => 3, + 'newestItemId' => 5 ], $response ); @@ -129,14 +128,13 @@ public function testIndex() public function testIndexNoNewestItemId() { $feeds = [new Feed()]; - $starredCount = 3; $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->equalTo($this->userID)) - ->will($this->returnValue($starredCount)); + ->will($this->returnValue([1, 2, 3])); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with($this->equalTo($this->userID)) ->will($this->throwException(new ServiceNotFoundException(''))); $this->feedService->expects($this->once()) @@ -149,7 +147,7 @@ public function testIndexNoNewestItemId() $this->assertEquals( [ 'feeds' => [$feeds[0]->toAPI()], - 'starredCount' => $starredCount, + 'starredCount' => 3, ], $response ); @@ -203,8 +201,8 @@ public function testCreate() ->method('fetch') ->with($feeds[0]); $this->itemService->expects($this->once()) - ->method('getNewestItemId') - ->will($this->returnValue(3)); + ->method('newest') + ->will($this->returnValue(Feed::fromParams(['id' => 3]))); $response = $this->class->create('url', 3); @@ -234,7 +232,7 @@ public function testCreateNoItems() ->method('fetch') ->with($feeds[0]); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->will($this->throwException(new ServiceNotFoundException(''))); $response = $this->class->create('ho', 3); @@ -287,12 +285,8 @@ public function testCreateError() public function testRead() { $this->itemService->expects($this->once()) - ->method('readFeed') - ->with( - $this->equalTo(3), - $this->equalTo(30), - $this->equalTo($this->userID) - ); + ->method('read') + ->with($this->userID,3,30); $this->class->read(3, 30); } diff --git a/tests/Unit/Controller/FeedControllerTest.php b/tests/Unit/Controller/FeedControllerTest.php index 871b781e00..b8929d3731 100644 --- a/tests/Unit/Controller/FeedControllerTest.php +++ b/tests/Unit/Controller/FeedControllerTest.php @@ -18,7 +18,7 @@ use OCA\News\Service\FeedServiceV2; use OCA\News\Service\FolderServiceV2; use OCA\News\Service\ImportService; -use OCA\News\Service\ItemService; +use OCA\News\Service\ItemServiceV2; use OCP\AppFramework\Http; use OCA\News\Db\Feed; @@ -55,8 +55,7 @@ class FeedControllerTest extends TestCase */ private $importService; /** - * TODO: Remove - * @var MockObject|ItemService + * @var MockObject|ItemServiceV2 */ private $itemService; @@ -87,7 +86,7 @@ public function setUp(): void ->disableOriginalConstructor() ->getMock(); $this->itemService = $this - ->getMockBuilder(ItemService::class) + ->getMockBuilder(ItemServiceV2::class) ->disableOriginalConstructor() ->getMock(); $this->feedService = $this @@ -138,20 +137,20 @@ public function testIndex() 'feeds' => [ ['a feed'], ], - 'starred' => 13 + 'starred' => 4 ]; $this->feedService->expects($this->once()) ->method('findAllForUser') ->with($this->uid) ->will($this->returnValue($result['feeds'])); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with($this->uid) ->will($this->throwException(new ServiceNotFoundException(''))); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->uid) - ->will($this->returnValue($result['starred'])); + ->will($this->returnValue([1, 2, 3, 4])); $response = $this->class->index(); @@ -165,7 +164,7 @@ public function testIndexHighestItemIdExists() 'feeds' => [ ['a feed'], ], - 'starred' => 13, + 'starred' => 2, 'newestItemId' => 5 ]; $this->feedService->expects($this->once()) @@ -173,13 +172,13 @@ public function testIndexHighestItemIdExists() ->with($this->uid) ->will($this->returnValue($result['feeds'])); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with($this->uid) - ->will($this->returnValue($result['newestItemId'])); + ->will($this->returnValue(Feed::fromParams(['id' => 5]))); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->uid) - ->will($this->returnValue($result['starred'])); + ->will($this->returnValue([1, 2])); $response = $this->class->index(); @@ -224,6 +223,30 @@ public function testActive() } + public function testActiveFeed() + { + $id = 3; + $type = FeedType::FEED; + $result = [ + 'activeFeed' => [ + 'id' => $id, + 'type' => $type + ] + ]; + + $this->feedService->expects($this->once()) + ->method('find') + ->with($this->uid, $id) + ->will($this->returnValue(new Feed())); + + $this->activeInitMocks($id, $type); + + $response = $this->class->active(); + + $this->assertEquals($result, $response); + } + + public function testActiveFeedDoesNotExist() { $id = 3; @@ -313,8 +336,8 @@ public function testCreate() ]; $this->itemService->expects($this->once()) - ->method('getNewestItemId') - ->will($this->returnValue($result['newestItemId'])); + ->method('newest') + ->will($this->returnValue(Feed::fromParams(['id' => 3]))); $this->feedService->expects($this->once()) ->method('purgeDeleted') ->with($this->uid, false); @@ -341,8 +364,8 @@ public function testCreateOldFolderId() ]; $this->itemService->expects($this->once()) - ->method('getNewestItemId') - ->will($this->returnValue($result['newestItemId'])); + ->method('newest') + ->will($this->returnValue(Feed::fromParams(['id' => 3]))); $this->feedService->expects($this->once()) ->method('purgeDeleted') ->with($this->uid, false); @@ -370,7 +393,7 @@ public function testCreateNoItems() ->with($this->uid, false); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->will($this->throwException(new ServiceNotFoundException(''))); $this->feedService->expects($this->once()) @@ -522,9 +545,9 @@ public function testImport() ->will($this->returnValue($feed)); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->uid) - ->will($this->returnValue(3)); + ->will($this->returnValue([1, 2, 3])); $response = $this->class->import(['json']); @@ -540,9 +563,9 @@ public function testImportCreatesNoAdditionalFeed() ->will($this->returnValue(null)); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with($this->uid) - ->will($this->returnValue(3)); + ->will($this->returnValue([1, 2, 3])); $response = $this->class->import(['json']); @@ -561,9 +584,9 @@ public function testReadFeed() ] ]; - $this->itemService->expects($this->once()) - ->method('readFeed') - ->with(4, 5, $this->uid); + $this->feedService->expects($this->once()) + ->method('read') + ->with($this->uid, 4, 5); $response = $this->class->read(4, 5); $this->assertEquals($expected, $response); diff --git a/tests/Unit/Controller/FolderApiControllerTest.php b/tests/Unit/Controller/FolderApiControllerTest.php index 858e7ff9e2..1964f7b473 100644 --- a/tests/Unit/Controller/FolderApiControllerTest.php +++ b/tests/Unit/Controller/FolderApiControllerTest.php @@ -37,7 +37,7 @@ class FolderApiControllerTest extends TestCase private $folderService; private $itemService; - private $folderAPI; + private $class; private $user; private $msg; @@ -65,7 +65,7 @@ protected function setUp(): void $this->itemService = $this->getMockBuilder(ItemService::class) ->disableOriginalConstructor() ->getMock(); - $this->folderAPI = new FolderApiController( + $this->class = new FolderApiController( $request, $userSession, $this->folderService, @@ -84,7 +84,7 @@ public function testIndex() ->with($this->equalTo($this->user->getUID())) ->will($this->returnValue($folders)); - $response = $this->folderAPI->index(); + $response = $this->class->index(); $this->assertEquals( [ @@ -108,7 +108,7 @@ public function testCreate() ->with($this->user->getUID(), $folderName) ->will($this->returnValue($folder)); - $response = $this->folderAPI->create($folderName); + $response = $this->class->create($folderName); $this->assertEquals( [ @@ -129,7 +129,7 @@ public function testCreateAlreadyExists() ->method('create') ->will($this->throwException(new ServiceConflictException($msg))); - $response = $this->folderAPI->create('hi'); + $response = $this->class->create('hi'); $data = $response->getData(); $this->assertEquals($msg, $data['message']); @@ -148,7 +148,7 @@ public function testCreateInvalidFolderName() ->method('create') ->will($this->throwException(new ServiceValidationException($msg))); - $response = $this->folderAPI->create('hi'); + $response = $this->class->create('hi'); $data = $response->getData(); $this->assertEquals($msg, $data['message']); @@ -164,7 +164,7 @@ public function testDelete() ->method('delete') ->with($this->user->getUID(), 23); - $this->folderAPI->delete(23); + $this->class->delete(23); } @@ -180,7 +180,7 @@ public function testDeleteDoesNotExist() ) ); - $response = $this->folderAPI->delete($folderId); + $response = $this->class->delete($folderId); $data = $response->getData(); $this->assertEquals($this->msg, $data['message']); @@ -197,7 +197,7 @@ public function testUpdate() ->method('rename') ->with($this->user->getUID(), $folderId, $folderName); - $this->folderAPI->update($folderId, $folderName); + $this->class->update($folderId, $folderName); } public function testUpdateDoesNotExist() @@ -213,7 +213,7 @@ public function testUpdateDoesNotExist() ) ); - $response = $this->folderAPI->update($folderId, $folderName); + $response = $this->class->update($folderId, $folderName); $data = $response->getData(); $this->assertEquals($this->msg, $data['message']); @@ -234,7 +234,7 @@ public function testUpdateExists() ) ); - $response = $this->folderAPI->update($folderId, $folderName); + $response = $this->class->update($folderId, $folderName); $data = $response->getData(); $this->assertEquals($this->msg, $data['message']); @@ -255,7 +255,7 @@ public function testUpdateInvalidFolderName() ) ); - $response = $this->folderAPI->update($folderId, $folderName); + $response = $this->class->update($folderId, $folderName); $data = $response->getData(); $this->assertEquals($this->msg, $data['message']); @@ -267,15 +267,25 @@ public function testUpdateInvalidFolderName() public function testRead() { - $this->itemService->expects($this->once()) - ->method('readFolder') - ->with( - $this->equalTo(3), - $this->equalTo(30), - $this->equalTo($this->user->getUID()) - ); + $this->folderService->expects($this->once()) + ->method('read') + ->with($this->user->getUID(), 3, 30); + + $this->class->read(3, 30); + } + + public function testUpdateRoot() + { + $response = $this->class->update(null, ''); + $this->assertSame(400, $response->getStatus()); + + } + + public function testDeleteRoot() + { + $response = $this->class->delete(null); + $this->assertSame(400, $response->getStatus()); - $this->folderAPI->read(3, 30); } diff --git a/tests/Unit/Controller/FolderControllerTest.php b/tests/Unit/Controller/FolderControllerTest.php index 18f4114b6d..4f46e12117 100644 --- a/tests/Unit/Controller/FolderControllerTest.php +++ b/tests/Unit/Controller/FolderControllerTest.php @@ -84,8 +84,6 @@ public function setUp(): void $this->class = new FolderController( $request, $this->folderService, - $this->feedService, - $this->itemService, $this->userSession ); $this->msg = 'ron'; @@ -162,6 +160,16 @@ public function testDelete() $this->class->delete(5); } + public function testDeleteRoot() + { + $this->folderService->expects($this->never()) + ->method('markDelete') + ->with('jack', 5, true); + + $response = $this->class->delete(null); + $this->assertEquals(400, $response->getStatus()); + } + public function testDeleteDoesNotExist() { $this->folderService->expects($this->once()) @@ -176,6 +184,20 @@ public function testDeleteDoesNotExist() $this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND); } + public function testDeleteConflict() + { + $this->folderService->expects($this->once()) + ->method('markDelete') + ->will($this->throwException(new ServiceConflictException($this->msg))); + + $response = $this->class->delete(5); + + $params = json_decode($response->render(), true); + + $this->assertEquals($this->msg, $params['message']); + $this->assertEquals($response->getStatus(), Http::STATUS_CONFLICT); + } + public function testRename() { $folder = new Folder(); @@ -186,11 +208,21 @@ public function testRename() ->with('jack', 4, 'tech') ->will($this->returnValue($folder)); - $response = $this->class->rename('tech', 4); + $response = $this->class->rename(4, 'tech'); $this->assertEquals($result, $response); } + public function testRenameRoot() + { + $this->folderService->expects($this->never()) + ->method('rename'); + + $response = $this->class->rename(null, 'tech'); + + $this->assertEquals(400, $response->getStatus()); + } + public function testRenameDoesNotExist() { $msg = 'except'; @@ -199,7 +231,7 @@ public function testRenameDoesNotExist() ->method('rename') ->will($this->throwException($ex)); - $response = $this->class->rename('tech', 5); + $response = $this->class->rename(5, 'tech'); $params = json_decode($response->render(), true); $this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND); @@ -215,7 +247,7 @@ public function testRenameReturnsErrorForDuplicateCreate() ->method('rename') ->will($this->throwException($ex)); - $response = $this->class->rename('tech', 1); + $response = $this->class->rename(1, 'tech'); $params = json_decode($response->render(), true); $this->assertEquals($response->getStatus(), Http::STATUS_CONFLICT); @@ -226,19 +258,11 @@ public function testRenameReturnsErrorForDuplicateCreate() public function testRead() { - $feed = new Feed(); - $expected = ['feeds' => [$feed->toAPI()]]; - - $this->itemService->expects($this->once()) - ->method('readFolder') - ->with(4, 5, 'jack'); - $this->feedService->expects($this->once()) - ->method('findAllForUser') - ->with('jack') - ->will($this->returnValue([$feed])); + $this->folderService->expects($this->once()) + ->method('read') + ->with('jack', 4, 5); - $response = $this->class->read(4, 5); - $this->assertEquals($expected, $response); + $this->class->read(4, 5); } @@ -267,4 +291,20 @@ public function testRestoreDoesNotExist() $this->assertEquals($this->msg, $params['message']); } + + public function testRestoreConflict() + { + $this->folderService->expects($this->once()) + ->method('markDelete') + ->with('jack', 5, false) + ->will($this->throwException(new ServiceConflictException($this->msg))); + + $response = $this->class->restore(5); + + $params = json_decode($response->render(), true); + + $this->assertEquals(Http::STATUS_CONFLICT, $response->getStatus()); + $this->assertEquals($this->msg, $params['message']); + } + } \ No newline at end of file diff --git a/tests/Unit/Controller/ItemApiControllerTest.php b/tests/Unit/Controller/ItemApiControllerTest.php index 91181333a2..85e3597059 100644 --- a/tests/Unit/Controller/ItemApiControllerTest.php +++ b/tests/Unit/Controller/ItemApiControllerTest.php @@ -16,7 +16,6 @@ namespace OCA\News\Tests\Unit\Controller; use OCA\News\Controller\ItemApiController; -use OCA\News\Service\ItemService; use OCA\News\Service\ItemServiceV2; use \OCP\AppFramework\Http; @@ -31,18 +30,28 @@ class ItemApiControllerTest extends TestCase { - + /** + * @var ItemServiceV2|\PHPUnit\Framework\MockObject\MockObject + */ private $itemService; - private $oldItemService; - private $class; + /** + * @var IUserSession|\PHPUnit\Framework\MockObject\MockObject + */ private $userSession; + /** + * @var IUser|\PHPUnit\Framework\MockObject\MockObject + */ private $user; + /** + * @var IRequest|\PHPUnit\Framework\MockObject\MockObject + */ private $request; private $msg; + private $uid = 'tom'; + private $class; protected function setUp(): void { - $this->user = 'tom'; $this->appName = 'news'; $this->request = $this->getMockBuilder(IRequest::class) ->disableOriginalConstructor() @@ -58,24 +67,20 @@ protected function setUp(): void ->will($this->returnValue($this->user)); $this->user->expects($this->any()) ->method('getUID') - ->will($this->returnValue('123')); - $this->oldItemService = $this->getMockBuilder(ItemService::class) - ->disableOriginalConstructor() - ->getMock(); + ->will($this->returnValue($this->uid)); $this->itemService = $this->getMockBuilder(ItemServiceV2::class) ->disableOriginalConstructor() ->getMock(); $this->class = new ItemApiController( $this->request, $this->userSession, - $this->oldItemService, $this->itemService ); $this->msg = 'hi'; } - public function testIndex() + public function testIndexForFeed() { $item = new Item(); $item->setId(5); @@ -83,26 +88,52 @@ public function testIndex() $item->setGuidHash('guidhash'); $item->setFeedId(123); - $this->oldItemService->expects($this->once()) - ->method('findAllItems') - ->with( - $this->equalTo(2), - $this->equalTo(1), - $this->equalTo(30), - $this->equalTo(20), - $this->equalTo(true), - $this->equalTo(true), - $this->equalTo($this->user->getUID()) - ) + $this->itemService->expects($this->once()) + ->method('findAllInFeedWithFilters') + ->with($this->uid, 2, 30, 20, false, true) + ->will($this->returnValue([$item])); + + $response = $this->class->index(0, 2, true, 30, 20, true); + + $this->assertEquals(['items' => [$item->toApi()]], $response); + } + + + public function testIndexForFolder() + { + $item = new Item(); + $item->setId(5); + $item->setGuid('guid'); + $item->setGuidHash('guidhash'); + $item->setFeedId(123); + + $this->itemService->expects($this->once()) + ->method('findAllInFolderWithFilters') + ->with($this->uid, 2, 30, 20, false, true) ->will($this->returnValue([$item])); $response = $this->class->index(1, 2, true, 30, 20, true); - $this->assertEquals( - [ - 'items' => [$item->toApi()] - ], $response - ); + $this->assertEquals(['items' => [$item->toApi()]], $response); + } + + + public function testIndexForItems() + { + $item = new Item(); + $item->setId(5); + $item->setGuid('guid'); + $item->setGuidHash('guidhash'); + $item->setFeedId(123); + + $this->itemService->expects($this->once()) + ->method('findAllWithFilters') + ->with($this->uid, 3, 30, 20, true) + ->will($this->returnValue([$item])); + + $response = $this->class->index(3, 2, true, 30, 20, true); + + $this->assertEquals(['items' => [$item->toApi()]], $response); } @@ -114,30 +145,18 @@ public function testIndexDefaultBatchSize() $item->setGuidHash('guidhash'); $item->setFeedId(123); - $this->oldItemService->expects($this->once()) - ->method('findAllItems') - ->with( - $this->equalTo(2), - $this->equalTo(1), - $this->equalTo(-1), - $this->equalTo(0), - $this->equalTo(false), - $this->equalTo(false), - $this->equalTo($this->user->getUID()) - ) + $this->itemService->expects($this->once()) + ->method('findAllInFolderWithFilters') + ->with($this->uid, 2, -1, 0, true, false) ->will($this->returnValue([$item])); $response = $this->class->index(1, 2, false); - $this->assertEquals( - [ - 'items' => [$item->toApi()] - ], $response - ); + $this->assertEquals(['items' => [$item->toApi()]], $response); } - public function testUpdated() + public function testUpdatedFeed() { $item = new Item(); $item->setId(5); @@ -145,36 +164,78 @@ public function testUpdated() $item->setGuidHash('guidhash'); $item->setFeedId(123); - $this->oldItemService->expects($this->once()) - ->method('findAllNew') - ->with( - $this->equalTo(2), - $this->equalTo(1), - $this->equalTo(30000000), - $this->equalTo(true), - $this->equalTo($this->user->getUID()) - ) + $this->itemService->expects($this->once()) + ->method('findAllInFeedAfter') + ->with($this->uid, 2, 30000000, false) + ->will($this->returnValue([$item])); + + $response = $this->class->updated(0, 2, 30); + + $this->assertEquals(['items' => [$item->toApi()]], $response); + } + + + public function testUpdatedFolder() + { + $item = new Item(); + $item->setId(5); + $item->setGuid('guid'); + $item->setGuidHash('guidhash'); + $item->setFeedId(123); + + $this->itemService->expects($this->once()) + ->method('findAllInFolderAfter') + ->with($this->uid, 2, 30000000, false) ->will($this->returnValue([$item])); $response = $this->class->updated(1, 2, 30); - $this->assertEquals( - [ - 'items' => [$item->toApi()] - ], $response - ); + $this->assertEquals(['items' => [$item->toApi()]], $response); + } + + + public function testUpdatedItems() + { + $item = new Item(); + $item->setId(5); + $item->setGuid('guid'); + $item->setGuidHash('guidhash'); + $item->setFeedId(123); + + $this->itemService->expects($this->once()) + ->method('findAllAfter') + ->with($this->uid, 3, 30000000) + ->will($this->returnValue([$item])); + + $response = $this->class->updated(3, 2, 30); + + $this->assertEquals(['items' => [$item->toApi()]], $response); + } + + public function testUpdatedFeedFullTimestamp() + { + $item = new Item(); + $item->setId(5); + $item->setGuid('guid'); + $item->setGuidHash('guidhash'); + $item->setFeedId(123); + + $this->itemService->expects($this->once()) + ->method('findAllInFeedAfter') + ->with($this->uid, 2, 1609598359000000, false) + ->will($this->returnValue([$item])); + + $response = $this->class->updated(0, 2, '1609598359000000'); + + $this->assertEquals(['items' => [$item->toApi()]], $response); } public function testRead() { - $this->oldItemService->expects($this->once()) + $this->itemService->expects($this->once()) ->method('read') - ->with( - $this->equalTo(2), - $this->equalTo(true), - $this->equalTo($this->user->getUID()) - ); + ->with($this->user->getUID(), 2, true); $this->class->read(2); } @@ -182,7 +243,7 @@ public function testRead() public function testReadDoesNotExist() { - $this->oldItemService->expects($this->once()) + $this->itemService->expects($this->once()) ->method('read') ->will( $this->throwException( @@ -200,12 +261,12 @@ public function testReadDoesNotExist() public function testUnread() { - $this->oldItemService->expects($this->once()) + $this->itemService->expects($this->once()) ->method('read') ->with( + $this->equalTo($this->user->getUID()), $this->equalTo(2), - $this->equalTo(false), - $this->equalTo($this->user->getUID()) + $this->equalTo(false) ); $this->class->unread(2); @@ -214,7 +275,7 @@ public function testUnread() public function testUnreadDoesNotExist() { - $this->oldItemService->expects($this->once()) + $this->itemService->expects($this->once()) ->method('read') ->will( $this->throwException( @@ -232,14 +293,9 @@ public function testUnreadDoesNotExist() public function testStar() { - $this->oldItemService->expects($this->once()) - ->method('star') - ->with( - $this->equalTo(2), - $this->equalTo('hash'), - $this->equalTo(true), - $this->equalTo($this->user->getUID()) - ); + $this->itemService->expects($this->once()) + ->method('starByGuid') + ->with('tom', 2, 'hash', true); $this->class->star(2, 'hash'); } @@ -247,13 +303,9 @@ public function testStar() public function testStarDoesNotExist() { - $this->oldItemService->expects($this->once()) - ->method('star') - ->will( - $this->throwException( - new ServiceNotFoundException($this->msg) - ) - ); + $this->itemService->expects($this->once()) + ->method('starByGuid') + ->will($this->throwException(new ServiceNotFoundException($this->msg))); $response = $this->class->star(2, 'test'); @@ -265,14 +317,9 @@ public function testStarDoesNotExist() public function testUnstar() { - $this->oldItemService->expects($this->once()) - ->method('star') - ->with( - $this->equalTo(2), - $this->equalTo('hash'), - $this->equalTo(false), - $this->equalTo($this->user->getUID()) - ); + $this->itemService->expects($this->once()) + ->method('starByGuid') + ->with($this->uid, 2, 'hash', false); $this->class->unstar(2, 'hash'); } @@ -280,8 +327,8 @@ public function testUnstar() public function testUnstarDoesNotExist() { - $this->oldItemService->expects($this->once()) - ->method('star') + $this->itemService->expects($this->once()) + ->method('starByGuid') ->will( $this->throwException( new ServiceNotFoundException($this->msg) @@ -298,12 +345,9 @@ public function testUnstarDoesNotExist() public function testReadAll() { - $this->oldItemService->expects($this->once()) + $this->itemService->expects($this->once()) ->method('readAll') - ->with( - $this->equalTo(30), - $this->equalTo($this->user->getUID()) - ); + ->with($this->user->getUID(), 30); $this->class->readAll(30); } @@ -312,11 +356,11 @@ public function testReadAll() public function testReadMultiple() { - $this->oldItemService->expects($this->exactly(2)) + $this->itemService->expects($this->exactly(2)) ->method('read') ->withConsecutive( - [2, true, $this->user->getUID()], - [4, true, $this->user->getUID()] + [$this->user->getUID(), 2, true], + [$this->user->getUID(), 4, true] ); $this->class->readMultiple([2, 4]); } @@ -324,24 +368,24 @@ public function testReadMultiple() public function testReadMultipleDoesntCareAboutException() { - $this->oldItemService->expects($this->exactly(2)) + $this->itemService->expects($this->exactly(2)) ->method('read') ->withConsecutive( - [2, true, $this->user->getUID()], - [4, true, $this->user->getUID()] + [$this->user->getUID(), 2, true], + [$this->user->getUID(), 4, true] ) - ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), null); + ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), new Item()); $this->class->readMultiple([2, 4]); } public function testUnreadMultiple() { - $this->oldItemService->expects($this->exactly(2)) + $this->itemService->expects($this->exactly(2)) ->method('read') ->withConsecutive( - [2, false, $this->user->getUID()], - [4, false, $this->user->getUID()] + [$this->user->getUID(), 2, false], + [$this->user->getUID(), 4, false] ); $this->class->unreadMultiple([2, 4]); } @@ -360,11 +404,11 @@ public function testStarMultiple() ] ]; - $this->oldItemService->expects($this->exactly(2)) - ->method('star') + $this->itemService->expects($this->exactly(2)) + ->method('starByGuid') ->withConsecutive( - [2, 'a', true, $this->user->getUID()], - [4, 'b', true, $this->user->getUID()] + [$this->user->getUID(), 2, 'a', true], + [$this->user->getUID(), 4, 'b', true] ); $this->class->starMultiple($ids); } @@ -383,13 +427,13 @@ public function testStarMultipleDoesntCareAboutException() ] ]; - $this->oldItemService->expects($this->exactly(2)) - ->method('star') + $this->itemService->expects($this->exactly(2)) + ->method('starByGuid') ->withConsecutive( - [2, 'a', true, $this->user->getUID()], - [4, 'b', true, $this->user->getUID()] + [$this->user->getUID(), 2, 'a', true], + [$this->user->getUID(), 4, 'b', true] ) - ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), null); + ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), new Item()); $this->class->starMultiple($ids); } @@ -408,11 +452,11 @@ public function testUnstarMultiple() ] ]; - $this->oldItemService->expects($this->exactly(2)) - ->method('star') + $this->itemService->expects($this->exactly(2)) + ->method('starByGuid') ->withConsecutive( - [2, 'a', false, $this->user->getUID()], - [4, 'b', false, $this->user->getUID()] + [$this->user->getUID(), 2, 'a', false], + [$this->user->getUID(), 4, 'b', false] ); $this->class->unstarMultiple($ids); diff --git a/tests/Unit/Controller/ItemControllerTest.php b/tests/Unit/Controller/ItemControllerTest.php index 3048f62f9d..546b392786 100644 --- a/tests/Unit/Controller/ItemControllerTest.php +++ b/tests/Unit/Controller/ItemControllerTest.php @@ -15,7 +15,7 @@ use OCA\News\Controller\ItemController; use OCA\News\Service\FeedServiceV2; -use OCA\News\Service\ItemService; +use OCA\News\Service\ItemServiceV2; use \OCP\AppFramework\Http; use \OCA\News\Db\Item; @@ -39,7 +39,7 @@ class ItemControllerTest extends TestCase */ private $settings; /** - * @var \PHPUnit\Framework\MockObject\MockObject|ItemService + * @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2 */ private $itemService; /** @@ -72,7 +72,7 @@ public function setUp(): void ->disableOriginalConstructor() ->getMock(); $this->itemService = - $this->getMockBuilder(ItemService::class) + $this->getMockBuilder(ItemServiceV2::class) ->disableOriginalConstructor() ->getMock(); $this->feedService = @@ -106,7 +106,7 @@ public function testRead() { $this->itemService->expects($this->once()) ->method('read') - ->with(4, true, 'user'); + ->with('user', 4, true); $this->controller->read(4, true); } @@ -133,8 +133,8 @@ public function testReadMultiple() $this->itemService->expects($this->exactly(2)) ->method('read') ->withConsecutive( - [2, true, 'user'], - [4, true, 'user'] + ['user', 2, true], + ['user', 4, true] ); $this->controller->readMultiple([2, 4]); @@ -147,10 +147,10 @@ public function testReadMultipleDontStopOnException() $this->itemService->expects($this->exactly(2)) ->method('read') ->withConsecutive( - [2, true, 'user'], - [4, true, 'user'] + ['user', 2, true], + ['user', 4, true] ) - ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('yo')), null); + ->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('yo')), new Item()); $this->controller->readMultiple([2, 4]); } @@ -158,8 +158,8 @@ public function testReadMultipleDontStopOnException() public function testStar() { $this->itemService->expects($this->once()) - ->method('star') - ->with(4, 'test', true, 'user'); + ->method('starByGuid') + ->with('user', 4, 'test', true); $this->controller->star(4, 'test', true); } @@ -170,7 +170,7 @@ public function testStarDoesNotExist() $msg = 'ho'; $this->itemService->expects($this->once()) - ->method('star') + ->method('starByGuid') ->will($this->throwException(new ServiceNotFoundException($msg))); $response = $this->controller->star(4, 'test', false); @@ -189,7 +189,7 @@ public function testReadAll() $this->itemService->expects($this->once()) ->method('readAll') - ->with(5, 'user'); + ->with('user', 5); $this->feedService->expects($this->once()) ->method('findAllForUser') ->with('user') @@ -199,8 +199,14 @@ public function testReadAll() $this->assertEquals($expected, $response); } - - private function itemsApiExpects($id, $type, $oldestFirst = '1') + /** + * Setup expectations for settings + * + * @param $id + * @param $type + * @param string $oldestFirst + */ + private function itemsApiExpects($id, $type, $oldestFirst = '1'): void { $this->settings->expects($this->exactly(2)) ->method('getUserValue') @@ -218,14 +224,14 @@ private function itemsApiExpects($id, $type, $oldestFirst = '1') } - public function testIndex() + public function testIndexForFeed() { $feeds = [new Feed()]; $result = [ 'items' => [new Item()], 'feeds' => $feeds, 'newestItemId' => $this->newestItemId, - 'starred' => 3111 + 'starred' => 3 ]; $this->itemsApiExpects(2, FeedType::FEED, '0'); @@ -236,18 +242,18 @@ public function testIndex() ->will($this->returnValue($feeds)); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with('user') - ->will($this->returnValue($this->newestItemId)); + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with('user') - ->will($this->returnValue(3111)); + ->will($this->returnValue([1, 2, 3])); $this->itemService->expects($this->once()) - ->method('findAllItems') - ->with(2, FeedType::FEED, 3, 0, true, false, 'user', []) + ->method('findAllInFeedWithFilters') + ->with('user', 2, 3, 0, false, false, []) ->will($this->returnValue($result['items'])); $response = $this->controller->index(FeedType::FEED, 2, 3); @@ -255,14 +261,88 @@ public function testIndex() } - public function testIndexSearch() + public function testIndexForFolder() + { + $feeds = [new Feed()]; + $result = [ + 'items' => [new Item()], + 'feeds' => $feeds, + 'newestItemId' => $this->newestItemId, + 'starred' => 3 + ]; + + $this->itemsApiExpects(2, FeedType::FOLDER, '0'); + + $this->feedService->expects($this->once()) + ->method('findAllForUser') + ->with('user') + ->will($this->returnValue($feeds)); + + $this->itemService->expects($this->once()) + ->method('newest') + ->with('user') + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); + + $this->itemService->expects($this->once()) + ->method('starred') + ->with('user') + ->will($this->returnValue([1, 2, 3])); + + $this->itemService->expects($this->once()) + ->method('findAllInFolderWithFilters') + ->with('user', 2, 3, 0, false, false, []) + ->will($this->returnValue($result['items'])); + + $response = $this->controller->index(FeedType::FOLDER, 2, 3); + $this->assertEquals($result, $response); + } + + + public function testIndexForOther() { $feeds = [new Feed()]; $result = [ 'items' => [new Item()], 'feeds' => $feeds, 'newestItemId' => $this->newestItemId, - 'starred' => 3111 + 'starred' => 3 + ]; + + $this->itemsApiExpects(2, FeedType::STARRED, '0'); + + $this->feedService->expects($this->once()) + ->method('findAllForUser') + ->with('user') + ->will($this->returnValue($feeds)); + + $this->itemService->expects($this->once()) + ->method('newest') + ->with('user') + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); + + $this->itemService->expects($this->once()) + ->method('starred') + ->with('user') + ->will($this->returnValue([1, 2, 3])); + + $this->itemService->expects($this->once()) + ->method('findAllWithFilters') + ->with('user', 2, 3, 0, false, []) + ->will($this->returnValue($result['items'])); + + $response = $this->controller->index(FeedType::STARRED, 2, 3); + $this->assertEquals($result, $response); + } + + + public function testIndexSearchFeed() + { + $feeds = [new Feed()]; + $result = [ + 'items' => [new Item()], + 'feeds' => $feeds, + 'newestItemId' => $this->newestItemId, + 'starred' => 3 ]; $this->itemsApiExpects(2, FeedType::FEED, '0'); @@ -273,18 +353,18 @@ public function testIndexSearch() ->will($this->returnValue($feeds)); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with('user') - ->will($this->returnValue($this->newestItemId)); + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with('user') - ->will($this->returnValue(3111)); + ->will($this->returnValue([1, 2, 3])); $this->itemService->expects($this->once()) - ->method('findAllItems') - ->with(2, FeedType::FEED, 3, 0, true, false, 'user', ['test', 'search']) + ->method('findAllInFeedWithFilters') + ->with('user', 2, 3, 0, false, false, ['test', 'search']) ->will($this->returnValue($result['items'])); $response = $this->controller->index(FeedType::FEED, 2, 3, 0, null, null, 'test%20%20search%20'); @@ -299,8 +379,8 @@ public function testItemsOffsetNotZero() $this->itemsApiExpects(2, FeedType::FEED); $this->itemService->expects($this->once()) - ->method('findAllItems') - ->with(2, FeedType::FEED, 3, 10, true, true, 'user') + ->method('findAllInFeedWithFilters') + ->with('user', 2, 3, 10, false, true) ->will($this->returnValue($result['items'])); $this->feedService->expects($this->never()) @@ -316,7 +396,7 @@ public function testGetItemsNoNewestItemsId() $this->itemsApiExpects(2, FeedType::FEED); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with('user') ->will($this->throwException(new ServiceNotFoundException(''))); @@ -325,14 +405,14 @@ public function testGetItemsNoNewestItemsId() } - public function testNewItems() + public function testNewItemsFeed() { $feeds = [new Feed()]; $result = [ 'items' => [new Item()], 'feeds' => $feeds, 'newestItemId' => $this->newestItemId, - 'starred' => 3111 + 'starred' => 3 ]; $this->settings->expects($this->once()) @@ -346,18 +426,18 @@ public function testNewItems() ->will($this->returnValue($feeds)); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with('user') - ->will($this->returnValue($this->newestItemId)); + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); $this->itemService->expects($this->once()) - ->method('starredCount') + ->method('starred') ->with('user') - ->will($this->returnValue(3111)); + ->will($this->returnValue([1, 2, 3])); $this->itemService->expects($this->once()) - ->method('findAllNew') - ->with(2, FeedType::FEED, 3, true, 'user') + ->method('findAllInFeedAfter') + ->with('user', 2, 3, false) ->will($this->returnValue($result['items'])); $response = $this->controller->newItems(FeedType::FEED, 2, 3); @@ -365,6 +445,86 @@ public function testNewItems() } + public function testNewItemsFolder() + { + $feeds = [new Feed()]; + $result = [ + 'items' => [new Item()], + 'feeds' => $feeds, + 'newestItemId' => $this->newestItemId, + 'starred' => 3 + ]; + + $this->settings->expects($this->once()) + ->method('getUserValue') + ->with('user', $this->appName, 'showAll') + ->will($this->returnValue('1')); + + $this->feedService->expects($this->once()) + ->method('findAllForUser') + ->with('user') + ->will($this->returnValue($feeds)); + + $this->itemService->expects($this->once()) + ->method('newest') + ->with('user') + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); + + $this->itemService->expects($this->once()) + ->method('starred') + ->with('user') + ->will($this->returnValue([1, 2, 3])); + + $this->itemService->expects($this->once()) + ->method('findAllInFolderAfter') + ->with('user', 2, 3, false) + ->will($this->returnValue($result['items'])); + + $response = $this->controller->newItems(FeedType::FOLDER, 2, 3); + $this->assertEquals($result, $response); + } + + + public function testNewItemsOther() + { + $feeds = [new Feed()]; + $result = [ + 'items' => [new Item()], + 'feeds' => $feeds, + 'newestItemId' => $this->newestItemId, + 'starred' => 3 + ]; + + $this->settings->expects($this->once()) + ->method('getUserValue') + ->with('user', $this->appName, 'showAll') + ->will($this->returnValue('1')); + + $this->feedService->expects($this->once()) + ->method('findAllForUser') + ->with('user') + ->will($this->returnValue($feeds)); + + $this->itemService->expects($this->once()) + ->method('newest') + ->with('user') + ->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId]))); + + $this->itemService->expects($this->once()) + ->method('starred') + ->with('user') + ->will($this->returnValue([1, 2, 3])); + + $this->itemService->expects($this->once()) + ->method('findAllAfter') + ->with('user', 6, 3) + ->will($this->returnValue($result['items'])); + + $response = $this->controller->newItems(FeedType::UNREAD, 2, 3); + $this->assertEquals($result, $response); + } + + public function testGetNewItemsNoNewestItemsId() { $this->settings->expects($this->once()) @@ -373,7 +533,7 @@ public function testGetNewItemsNoNewestItemsId() ->will($this->returnValue('1')); $this->itemService->expects($this->once()) - ->method('getNewestItemId') + ->method('newest') ->with('user') ->will($this->throwException(new ServiceNotFoundException(''))); diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php index 2f259b316d..5f62bfe9fc 100644 --- a/tests/Unit/Controller/PageControllerTest.php +++ b/tests/Unit/Controller/PageControllerTest.php @@ -16,6 +16,7 @@ use OC\L10N\L10N; use OCA\News\Controller\PageController; use \OCA\News\Db\FeedType; +use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException; use OCA\News\Explore\RecommendedSites; use OCA\News\Service\StatusService; use OCP\IConfig; @@ -26,7 +27,6 @@ use OCP\IUserSession; use PHPUnit\Framework\TestCase; - class PageControllerTest extends TestCase { @@ -278,4 +278,24 @@ public function testExplore() } + public function testExploreError() + { + $this->settings->expects($this->exactly(2)) + ->method('setUserValue') + ->withConsecutive( + ['becka', 'news', 'lastViewedFeedId', 0], + ['becka', 'news', 'lastViewedFeedType', FeedType::EXPLORE] + ); + + $this->recommended->expects($this->once()) + ->method('forLanguage') + ->with('nl') + ->will($this->throwException(new RecommendedSiteNotFoundException('error'))); + + $out = $this->controller->explore('nl'); + + $this->assertEquals(404, $out->getStatus()); + + } + } diff --git a/tests/Unit/Db/FeedMapperTest.php b/tests/Unit/Db/FeedMapperTest.php index c14b8995f6..780bfbc386 100644 --- a/tests/Unit/Db/FeedMapperTest.php +++ b/tests/Unit/Db/FeedMapperTest.php @@ -69,7 +69,7 @@ public function testFindAllFromUser() ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) - ->getMock(); + ->getMock(); $funcbuilder->expects($this->once()) ->method('count') @@ -451,4 +451,94 @@ public function testFindAllFromRootFolder() $result = $this->class->findAllFromFolder(null); $this->assertEquals($this->feeds, $result); } + + /** + * @covers \OCA\News\Db\FeedMapperV2::read + */ + public function testRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['feedId', 1]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1); + } + + /** + * @covers \OCA\News\Db\FeedMapperV2::read + */ + public function testReadWithMaxId() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId'], ['items.id =< :maxItemId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['feedId', 1], ['maxItemId', 4]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1, 4); + } } \ No newline at end of file diff --git a/tests/Unit/Db/FolderMapperTest.php b/tests/Unit/Db/FolderMapperTest.php index dd87b22b57..026c16bc68 100644 --- a/tests/Unit/Db/FolderMapperTest.php +++ b/tests/Unit/Db/FolderMapperTest.php @@ -279,4 +279,94 @@ public function testFindAll() $result = $this->class->findAll(); $this->assertEquals($this->folders, $result); } + + /** + * @covers \OCA\News\Db\FolderMapperV2::read + */ + public function testRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['folderId', 1]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1); + } + + /** + * @covers \OCA\News\Db\FolderMapperV2::read + */ + public function testReadWithMaxId() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId'], ['items.id =< :maxItemId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['folderId', 1], ['maxItemId', 4]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1, 4); + } } \ No newline at end of file diff --git a/tests/Unit/Db/ItemMapperTest.php b/tests/Unit/Db/ItemMapperTest.php new file mode 100644 index 0000000000..ee5680f4e5 --- /dev/null +++ b/tests/Unit/Db/ItemMapperTest.php @@ -0,0 +1,1724 @@ + + * @author Bernhard Posselt + * @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\Item; +use OCA\News\Db\ItemMapperV2; +use OCA\News\Db\NewsMapperV2; +use OCA\News\Service\Exceptions\ServiceValidationException; +use OCA\News\Utility\Time; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\DB\QueryBuilder\IExpressionBuilder; +use OCP\DB\QueryBuilder\IFunctionBuilder; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; +use OCP\IDBConnection; +use Test\TestCase; + +class ItemMapperTest extends MapperTestUtility +{ + + /** @var Time */ + private $time; + /** @var ItemMapperV2 */ + private $class; + + /** + * @covers \OCA\News\Db\ItemMapperV2::__construct + */ + protected function setUp(): void + { + parent::setUp(); + $this->time = $this->getMockBuilder(Time::class) + ->getMock(); + + $this->class = new ItemMapperV2($this->db, $this->time); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::__construct + */ + public function testSetUpSuccess(): void + { + $this->assertEquals('news_items', $this->class->getTableName()); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllFromUser + */ + public function testFindAllFromUser() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->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->exactly(1)) + ->method('setParameter') + ->withConsecutive(['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([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllFromUser + */ + public function testFindAllFromUserWithParams() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('createNamedParameter') + ->with('val') + ->will($this->returnValue(':val')); + + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feeds.user_id = :user_id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.deleted_at = 0'], ['key = :val']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['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', ['key' => 'val']); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::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_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.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('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([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllForFeed + */ + public function testFindAllForFeed() + { + $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_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feed_id = :feed_identifier') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setParameter') + ->with('feed_identifier', 4) + ->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->findAllForFeed(4); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + public function testFindFromUser() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feeds.user_id = :user_id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['items.id = :item_id'], ['feeds.deleted_at = 0']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack'], ['item_id', 4]) + ->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', 4); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindByGUIDHash() + { + $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_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feed_id = :feed_id'], ['guid_hash = :guid_hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['feed_id', 4], ['guid_hash', 'hash']) + ->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->findByGuidHash(4, 'hash'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindForUserByGUIDHash() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :user_id'], ['feeds.id = :feed_id'], ['items.guid_hash = :guid_hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack'], ['feed_id', 4], ['guid_hash', 'hash']) + ->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->findForUserByGuidHash('jack', 4, 'hash'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testNewest() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('where') + ->withConsecutive(['feeds.user_id = :userId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(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->newest('jack'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindAllInFeedAfter() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['feeds.id = :feedId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'feedId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllInFeedAfter('jack', 4, 1610903351, false); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllInFeedAfterHideRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['feeds.id = :feedId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'feedId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllInFeedAfter('jack', 4, 1610903351, true); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllInFolderAfter() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('innerJoin') + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['feeds', 'news_folders', 'folders', 'feeds.folder_id = folders.id'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['folders.id = :folderId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'folderId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllInFolderAfter('jack', 4, 1610903351, false); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllInFolderAfterHideRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('innerJoin') + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['feeds', 'news_folders', 'folders', 'feeds.folder_id = folders.id'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['folders.id = :folderId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'folderId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllInFolderAfter('jack', 4, 1610903351, true); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllAfterUnread() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllAfter('jack', 6, 1610903351); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllAfterStarred() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['items.starred = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->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->findAllAfter('jack', 2, 1610903351); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllAfterInvalid() + { + $this->expectException(ServiceValidationException::class); + $this->expectExceptionMessage('Unexpected Feed type in call'); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->never()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->never()) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllAfter('jack', 232, 1610903351); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllItemsInvalid() + { + $this->expectException(ServiceValidationException::class); + $this->expectExceptionMessage('Unexpected Feed type in call'); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->with('userId', 'jack') + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->never()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->never()) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $this->class->findAllItems('jack', 232, 10, 10, false, []); + } + + public function testFindAllItemsUnread() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->with('userId', 'jack') + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllItems('jack', 6, 10, 10, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllItemsStarred() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.starred = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->with('userId', 'jack') + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllItems('jack', 2, 10, 10, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllItemsStarredSearch() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + $this->db->expects($this->exactly(2)) + ->method('escapeLikeParameter') + ->will($this->returnArgument(0)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.search_index LIKE :term0'], + ['items.search_index LIKE :term1'], + ['items.starred = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack'], ['term0', '%key%'], ['term1', '%word%']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllItems('jack', 2, 10, 10, false, ['key', 'word']); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFeed() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.feed_id = :feedId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack'], ['feedId', 2]) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFeed('jack', 2, 10, 10, false, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFeedHideRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.feed_id = :feedId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack'], ['feedId', 2]) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFeed('jack', 2, 10, 10, true, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFeedSearch() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + $this->db->expects($this->exactly(2)) + ->method('escapeLikeParameter') + ->will($this->returnArgument(0)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['items.feed_id = :feedId'], + ['items.search_index LIKE :term0'], + ['items.search_index LIKE :term1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack'], ['feedId', 2], ['term0', '%key%'], ['term1', '%word%']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFeed('jack', 2, 10, 10, false, false, ['key', 'word']); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFolderIdNull() + { + $expr = $this->getMockBuilder(IExpressionBuilder::class) + ->getMock(); + + $expr->expects($this->once()) + ->method('isNull') + ->with('feeds.folder_id') + ->will($this->returnValue('x IS NULL')); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->exactly(1)) + ->method('expr') + ->will($this->returnValue($expr)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['x IS NULL'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFolder('jack', null, 10, 10, false, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFolderHideRead() + { + $expr = $this->getMockBuilder(IExpressionBuilder::class) + ->getMock(); + + $expr->expects($this->once()) + ->method('isNull') + ->with('feeds.folder_id') + ->will($this->returnValue('x IS NULL')); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->exactly(1)) + ->method('expr') + ->will($this->returnValue($expr)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['x IS NULL'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFolder('jack', null, 10, 10, true, false, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFolderHideReadInvertOrder() + { + $expr = $this->getMockBuilder(IExpressionBuilder::class) + ->getMock(); + + $expr->expects($this->once()) + ->method('isNull') + ->with('feeds.folder_id') + ->will($this->returnValue('x IS NULL')); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->exactly(1)) + ->method('expr') + ->will($this->returnValue($expr)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['x IS NULL'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'ASC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFolder('jack', null, 10, 10, true, true, []); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllFolderSearchId() + { + $expr = $this->getMockBuilder(IExpressionBuilder::class) + ->getMock(); + + $expr->expects($this->once()) + ->method('eq') + ->with('feeds.folder_id', 2) + ->will($this->returnValue('x = y')); + + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + $this->db->expects($this->exactly(2)) + ->method('escapeLikeParameter') + ->will($this->returnArgument(0)); + + $this->builder->expects($this->exactly(1)) + ->method('expr') + ->will($this->returnValue($expr)); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('innerJoin') + ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['feeds.user_id = :userId'], + ['x = y'], + ['items.search_index LIKE :term0'], + ['items.search_index LIKE :term1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack'], ['term0', '%key%'], ['term1', '%word%']) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(10) + ->will($this->returnSelf()); + + + $this->builder->expects($this->exactly(1)) + ->method('setFirstResult') + ->with(10) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllFolder('jack', 2, 10, 10, false, false, ['key', 'word']); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testReadAll() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['items.id =< :maxItemId'], ['feeds.user_id = :userId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['maxItemId', 4], ['userId', 'jack']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->once()) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->readAll('jack', 4); + } + + public function testPurgeDeletedEmpty() + { + $this->db->expects($this->never()) + ->method('getQueryBuilder'); + + $this->class->purgeDeleted('jack', 4); + } + +} \ No newline at end of file diff --git a/tests/Unit/Db/MapperFactoryTest.php b/tests/Unit/Db/MapperFactoryTest.php deleted file mode 100644 index 1c4e2f4b62..0000000000 --- a/tests/Unit/Db/MapperFactoryTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Tests\Unit\Db; - -use OCA\News\Db\ItemMapper; -use OCA\News\Db\MapperFactory; -use OCA\News\Utility\Time; -use PHPUnit\Framework\TestCase; - -use OCP\IDBConnection; - -use OCA\News\Db\Mysql\ItemMapper as MysqlMapper; - - -class MapperFactoryTest extends TestCase -{ - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|IDBConnection - */ - private $db; - - public function setUp(): void - { - $this->db = $this->getMockBuilder(IDBConnection::class) - ->disableOriginalConstructor() - ->getMock(); - } - - public function testGetItemMapperSqlite() - { - $factory = new MapperFactory($this->db, 'sqlite', new Time()); - $this->assertTrue($factory->build() instanceof ItemMapper); - } - - public function testGetItemMapperPostgres() - { - $factory = new MapperFactory($this->db, 'pgsql', new Time()); - $this->assertTrue($factory->build() instanceof ItemMapper); - } - - public function testGetItemMapperMysql() - { - $factory = new MapperFactory($this->db, 'mysql', new Time()); - $this->assertTrue($factory->build() instanceof MysqlMapper); - } - -} diff --git a/tests/Unit/Db/MapperTestUtility.php b/tests/Unit/Db/MapperTestUtility.php index 3aa1d8aed9..4a875fde5a 100644 --- a/tests/Unit/Db/MapperTestUtility.php +++ b/tests/Unit/Db/MapperTestUtility.php @@ -24,6 +24,7 @@ namespace OCA\News\Tests\Unit\Db; use Doctrine\DBAL\Driver\Statement; +use OC\DB\QueryBuilder\QueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -74,6 +75,7 @@ protected function setUp(): void ->getMock(); $this->builder = $this->getMockBuilder(IQueryBuilder::class) + ->disableOriginalConstructor() ->getMock(); $this->cursor = $this->getMockBuilder(Statement::class) diff --git a/tests/Unit/Service/FeedServiceTest.php b/tests/Unit/Service/FeedServiceTest.php index 2d45cea705..b0bfa65975 100644 --- a/tests/Unit/Service/FeedServiceTest.php +++ b/tests/Unit/Service/FeedServiceTest.php @@ -817,8 +817,8 @@ public function testFindAllFromUserRecursive() ->will($this->returnValue([$feed1, $feed2])); $this->itemService->expects($this->exactly(2)) - ->method('findAllForFeed') - ->withConsecutive([1], [2]) + ->method('findAllInFeed') + ->withConsecutive(['jack', 1], ['jack', 2]) ->willReturn(['a']); $feeds = $this->class->findAllForUserRecursive($this->uid); @@ -826,4 +826,21 @@ public function testFindAllFromUserRecursive() $this->assertEquals(['a'], $feeds[1]->items); } + public function testRead() + { + $feed1 = new Feed(); + $feed1->setId(1); + + $this->mapper->expects($this->once()) + ->method('findFromUser') + ->with($this->uid, 1) + ->will($this->returnValue($feed1)); + + $this->mapper->expects($this->exactly(1)) + ->method('read') + ->withConsecutive(['jack', 1, null]); + + $this->class->read($this->uid, 1); + } + } diff --git a/tests/Unit/Service/FolderServiceTest.php b/tests/Unit/Service/FolderServiceTest.php index 2b55ee01a6..3e7e980415 100644 --- a/tests/Unit/Service/FolderServiceTest.php +++ b/tests/Unit/Service/FolderServiceTest.php @@ -262,4 +262,21 @@ public function testDeleteUser() } + public function testRead() + { + $folder = new Folder(); + $folder->setId(1); + + $this->mapper->expects($this->once()) + ->method('findFromUser') + ->with('jack', 1) + ->will($this->returnValue($folder)); + + $this->mapper->expects($this->exactly(1)) + ->method('read') + ->withConsecutive(['jack', 1, null]); + + $this->class->read('jack', 1); + } + } diff --git a/tests/Unit/Service/ItemServiceTest.php b/tests/Unit/Service/ItemServiceTest.php index 1ab9a7b259..0c699d7283 100644 --- a/tests/Unit/Service/ItemServiceTest.php +++ b/tests/Unit/Service/ItemServiceTest.php @@ -13,84 +13,63 @@ namespace OCA\News\Tests\Unit\Service; -use OC\Log; -use OCA\News\Db\ItemMapper; use OCA\News\Db\ItemMapperV2; -use OCA\News\Service\ItemService; +use OCA\News\Service\Exceptions\ServiceConflictException; +use OCA\News\Service\Exceptions\ServiceValidationException; use OCA\News\Service\Exceptions\ServiceNotFoundException; -use OCA\News\Utility\PsrLogger; -use OCA\News\Utility\Time; +use OCA\News\Service\ItemServiceV2; use \OCP\AppFramework\Db\DoesNotExistException; use \OCA\News\Db\Item; use \OCA\News\Db\FeedType; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; - +/** + * Class ItemServiceTest + * + * @package OCA\News\Tests\Unit\Service + */ class ItemServiceTest extends TestCase { /** - * @var \PHPUnit\Framework\MockObject\MockObject|ItemMapper - */ - private $oldItemMapper; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|ItemMapperV2 + * @var MockObject|ItemMapperV2 */ private $mapper; /** - * @var ItemService + * @var ItemServiceV2 */ - private $itemService; + private $class; /** - * @var \PHPUnit\Framework\MockObject\MockObject|IConfig + * @var MockObject|IConfig */ private $config; /** - * @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface + * @var MockObject|LoggerInterface */ private $logger; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|Time - */ - private $timeFactory; - /** * @var int */ private $newestItemId; - /** * @var string */ - private $time; + private $user; protected function setUp(): void { - $this->time = '222'; - $this->timeFactory = $this->getMockBuilder(Time::class) - ->disableOriginalConstructor() - ->getMock(); - $this->timeFactory->expects($this->any()) - ->method('getTime') - ->will($this->returnValue($this->time)); - $this->timeFactory->expects($this->any()) - ->method('getMicroTime') - ->will($this->returnValue($this->time)); $this->mapper = $this->getMockBuilder(ItemMapperV2::class) ->disableOriginalConstructor() ->getMock(); - $this->oldItemMapper = $this->getMockBuilder(ItemMapper::class) - ->disableOriginalConstructor() - ->getMock(); $this->config = $this->getMockBuilder(IConfig::class) ->disableOriginalConstructor() ->getMock(); @@ -99,10 +78,8 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMock(); - $this->itemService = new ItemService( + $this->class = new ItemServiceV2( $this->mapper, - $this->oldItemMapper, - $this->timeFactory, $this->config, $this->logger ); @@ -115,164 +92,126 @@ protected function setUp(): void $this->newestItemId = 4; } - public function testFindAllNewFeed() { - $type = FeedType::FEED; - $this->oldItemMapper->expects($this->once()) - ->method('findAllNewFeed') - ->with( - $this->equalTo(3), - $this->equalTo(20333), - $this->equalTo(true), - $this->equalTo('jack') - ) + $this->mapper->expects($this->once()) + ->method('findAllInFeedAfter') + ->with('jack', 2, 20333, true) ->will($this->returnValue([])); - $result = $this->itemService->findAllNew(3, $type, 20333, true, 'jack'); + $result = $this->class->findAllInFeedAfter($this->user, 2, 20333, true); $this->assertEquals([], $result); } - public function testFindAllNewFolder() { - $type = FeedType::FOLDER; - $this->oldItemMapper->expects($this->once()) - ->method('findAllNewFolder') - ->with( - $this->equalTo(3), - $this->equalTo(20333), - $this->equalTo(true), - $this->equalTo('jack') - ) - ->will($this->returnValue(['val'])); + $this->mapper->expects($this->once()) + ->method('findAllInFolderAfter') + ->with('jack', 2, 20333, true) + ->will($this->returnValue([])); - $result = $this->itemService->findAllNew(3, $type, 20333, true, 'jack'); - $this->assertEquals(['val'], $result); + $result = $this->class->findAllInFolderAfter($this->user, 2, 20333, true); + $this->assertEquals([], $result); } - - public function testFindAllNew() + public function testFindAllNewItem() { - $type = FeedType::STARRED; - $this->oldItemMapper->expects($this->once()) - ->method('findAllNew') - ->with( - $this->equalTo(20333), - $this->equalTo($type), - $this->equalTo(true), - $this->equalTo('jack') - ) - ->will($this->returnValue(['val'])); + $this->mapper->expects($this->once()) + ->method('findAllAfter') + ->with('jack', 2, 20333) + ->will($this->returnValue([])); - $result = $this->itemService->findAllNew( - 3, $type, 20333, true, - 'jack' - ); - $this->assertEquals(['val'], $result); + $result = $this->class->findAllAfter($this->user, 2, 20333); + $this->assertEquals([], $result); } + public function testFindAllNewItemWrongType() + { + $this->expectException(ServiceValidationException::class); + $this->expectExceptionMessage('Trying to find in unknown type'); + + $this->mapper->expects($this->never()) + ->method('findAllAfter'); + + $result = $this->class->findAllAfter($this->user, 3, 20333); + $this->assertEquals([], $result); + } public function testFindAllFeed() { - $type = FeedType::FEED; - $this->oldItemMapper->expects($this->once()) + $this->mapper->expects($this->once()) ->method('findAllFeed') - ->with( - $this->equalTo(3), - $this->equalTo(20), - $this->equalTo(5), - $this->equalTo(true), - $this->equalTo(false), - $this->equalTo('jack'), - $this->equalTo([]) - ) + ->with('jack', 3, 20, 5, true, false, []) ->will($this->returnValue(['val'])); - $result = $this->itemService->findAllItems( - 3, $type, 20, 5, - true, false, 'jack' + $result = $this->class->findAllInFeedWithFilters( + 'jack', + 3, + 20, + 5, + true, + false ); $this->assertEquals(['val'], $result); } - public function testFindAllFolder() { - $type = FeedType::FOLDER; - $this->oldItemMapper->expects($this->once()) + $this->mapper->expects($this->once()) ->method('findAllFolder') - ->with( - $this->equalTo(3), - $this->equalTo(20), - $this->equalTo(5), - $this->equalTo(true), - $this->equalTo(true), - $this->equalTo('jack'), - $this->equalTo([]) - ) + ->with('jack', 3, 20, 5, true, true, []) ->will($this->returnValue(['val'])); - $result = $this->itemService->findAllItems( - 3, $type, 20, 5, - true, true, 'jack' + $result = $this->class->findAllInFolderWithFilters( + 'jack', + 3, + 20, + 5, + true, + true, + [] ); $this->assertEquals(['val'], $result); } - - public function testFindAll() + public function testFindAllItems() { $type = FeedType::STARRED; - $this->oldItemMapper->expects($this->once()) + $this->mapper->expects($this->once()) ->method('findAllItems') - ->with( - $this->equalTo(20), - $this->equalTo(5), - $this->equalTo($type), - $this->equalTo(true), - $this->equalTo(true), - $this->equalTo('jack'), - $this->equalTo([]) - ) + ->with('jack', $type, 20, 5, true, []) ->will($this->returnValue(['val'])); - $result = $this->itemService->findAllItems( - 3, $type, 20, 5, - true, true, 'jack' - ); + $result = $this->class->findAllWithFilters('jack', $type, 20, 5, true); $this->assertEquals(['val'], $result); } - public function testFindAllSearch() { $type = FeedType::STARRED; $search = ['test']; - $this->oldItemMapper->expects($this->once()) + + $this->mapper->expects($this->once()) ->method('findAllItems') - ->with( - $this->equalTo(20), - $this->equalTo(5), - $this->equalTo($type), - $this->equalTo(true), - $this->equalTo(true), - $this->equalTo('jack'), - $this->equalTo($search) - ) + ->with('jack', $type, 20, 5, true, $search) ->will($this->returnValue(['val'])); - $result = $this->itemService->findAllItems( - 3, $type, 20, 5, - true, true, 'jack', $search - ); + $result = $this->class->findAllWithFilters('jack', $type, 20, 5, true, $search); $this->assertEquals(['val'], $result); } + public function testFindAll() + { + $this->mapper->expects($this->once()) + ->method('findAll') + ->will($this->returnValue(['val'])); + $result = $this->class->findAll(); + $this->assertEquals(['val'], $result); + } - public function testStar() + public function testStarByGuid() { $itemId = 3; $feedId = 5; @@ -287,20 +226,19 @@ public function testStar() $expectedItem->setId($itemId); $this->mapper->expects($this->once()) - ->method('findByGuidHash') - ->with($feedId, $guidHash) + ->method('findForUserByGuidHash') + ->with('jack', $feedId, $guidHash) ->will($this->returnValue($item)); $this->mapper->expects($this->once()) ->method('update') ->with($this->equalTo($expectedItem)); - $this->itemService->star($feedId, $guidHash, true, 'jack'); + $this->class->starByGuid('jack', $feedId, $guidHash, true); $this->assertTrue($item->isStarred()); } - public function testUnstar() { $itemId = 3; @@ -308,214 +246,366 @@ public function testUnstar() $guidHash = md5('hihi'); $item = new Item(); - $item->setStatus(128); $item->setId($itemId); $item->setStarred(true); $expectedItem = new Item(); - $expectedItem->setStatus(128); $expectedItem->setStarred(true); //workaround to set starred as updated field $expectedItem->setStarred(false); $expectedItem->setId($itemId); $this->mapper->expects($this->once()) - ->method('findByGuidHash') - ->with($feedId, $guidHash) + ->method('findForUserByGuidHash') + ->with('jack', $feedId, $guidHash) ->will($this->returnValue($item)); $this->mapper->expects($this->once()) ->method('update') ->with($this->equalTo($expectedItem)); - $this->itemService->star($feedId, $guidHash, false, 'jack'); + $this->class->starByGuid('jack', $feedId, $guidHash, false); $this->assertFalse($item->isStarred()); } public function testRead() { - $itemId = 3; - $item = new Item(); - $item->setStatus(128); - $item->setId($itemId); - $item->setUnread(true); + $item = $this->getMockBuilder(Item::class) + ->getMock(); - $expectedItem = new Item(); - $expectedItem->setStatus(128); - $expectedItem->setUnread(false); - $expectedItem->setId($itemId); - $expectedItem->setLastModified($this->time); - - $this->oldItemMapper->expects($this->once()) - ->method('readItem') - ->with( - $this->equalTo($itemId), - $this->equalTo(true), - $this->equalTo($this->time), - $this->equalTo('jack') - ) + $item->expects($this->once()) + ->method('setUnread') + ->with(false); + + $this->mapper->expects($this->once()) + ->method('findFromUser') + ->with('jack', 3) ->will($this->returnValue($item)); - $this->itemService->read($itemId, true, 'jack'); - } + $this->mapper->expects($this->once()) + ->method('update') + ->with($item) + ->will($this->returnValue($item)); + $this->class->read('jack', 3, true); + } - public function testReadDoesNotExist() + public function testStar() { + $item = $this->getMockBuilder(Item::class) + ->getMock(); - $this->expectException(ServiceNotFoundException::class); - $this->oldItemMapper->expects($this->once()) - ->method('readItem') - ->will($this->throwException(new DoesNotExistException(''))); + $item->expects($this->once()) + ->method('setStarred') + ->with(true); + + $this->mapper->expects($this->once()) + ->method('findFromUser') + ->with('jack', 3) + ->will($this->returnValue($item)); - $this->itemService->read(1, true, 'jack'); + $this->mapper->expects($this->once()) + ->method('update') + ->with($item) + ->will($this->returnValue($item)); + + $this->class->star('jack', 3, true); } - public function testStarDoesNotExist() + public function testStarByGuidDoesNotExist() { $this->expectException(ServiceNotFoundException::class); $this->mapper->expects($this->once()) - ->method('findByGuidHash') + ->method('findForUserByGuidHash') ->will($this->throwException(new DoesNotExistException(''))); - $this->itemService->star(1, 'hash', true, 'jack'); + $this->class->starByGuid('jack', 1, 'hash', true); } + public function testStarByGuidDuplicate() + { + + $this->expectException(ServiceConflictException::class); + $this->mapper->expects($this->once()) + ->method('findForUserByGuidHash') + ->will($this->throwException(new MultipleObjectsReturnedException(''))); + + $this->class->starByGuid('jack', 1, 'hash', true); + } public function testReadAll() { $highestItemId = 6; - $this->oldItemMapper->expects($this->once()) + $this->mapper->expects($this->once()) ->method('readAll') - ->with( - $this->equalTo($highestItemId), - $this->equalTo($this->time), - $this->equalTo('jack') - ); + ->with('jack', $highestItemId); - $this->itemService->readAll($highestItemId, 'jack'); + $this->class->readAll('jack', $highestItemId); } - - public function testReadFolder() + public function testGetNewestItemId() { - $folderId = 3; - $highestItemId = 6; + $this->mapper->expects($this->once()) + ->method('newest') + ->with($this->equalTo('jack')) + ->will($this->returnValue(Item::fromParams(['id' => 12]))); + + $result = $this->class->newest('jack'); + $this->assertEquals(12, $result->getId()); + } - $this->oldItemMapper->expects($this->once()) - ->method('readFolder') - ->with( - $this->equalTo($folderId), - $this->equalTo($highestItemId), - $this->equalTo($this->time), - $this->equalTo('jack') + public function testGetNewestItemIdDoesNotExist() + { + $this->mapper->expects($this->once()) + ->method('newest') + ->with($this->equalTo('jack')) + ->will( + $this->throwException( + new DoesNotExistException('There are no items') + ) ); - $this->itemService->readFolder($folderId, $highestItemId, 'jack'); + $this->expectException(ServiceNotFoundException::class); + $this->class->newest('jack'); } - - public function testReadFeed() + public function testGetNewestItemDuplicate() { - $feedId = 3; - $highestItemId = 6; - - $this->oldItemMapper->expects($this->once()) - ->method('readFeed') - ->with( - $this->equalTo($feedId), - $this->equalTo($highestItemId), - $this->equalTo($this->time), - $this->equalTo('jack') + $this->mapper->expects($this->once()) + ->method('newest') + ->with($this->equalTo('jack')) + ->will( + $this->throwException( + new MultipleObjectsReturnedException('There are no items') + ) ); - $this->itemService->readFeed($feedId, $highestItemId, 'jack'); + $this->expectException(ServiceConflictException::class); + $this->class->newest('jack'); } - - public function testAutoPurgeOldWillPurgeOld() + public function testStarredCount() { - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('news', 'autoPurgeCount') - ->will($this->returnValue(2)); - $this->oldItemMapper->expects($this->once()) - ->method('deleteReadOlderThanThreshold') - ->with($this->equalTo(2)); + $this->mapper->expects($this->once()) + ->method('findAllFromUser') + ->with('jack', ['starred' => 1]) + ->will($this->returnValue([new Item(), new Item()])); + + $result = $this->class->starred('jack'); - $this->itemService->autoPurgeOld(); + $this->assertEquals(2, count($result)); } - public function testAutoPurgeOldWontPurgeOld() + public function testInsertOrUpdateInserts() { - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('news', 'autoPurgeCount') - ->will($this->returnValue(-1)); - $this->oldItemMapper->expects($this->never()) - ->method('deleteReadOlderThanThreshold'); + $item = $this->getMockBuilder(Item::class) + ->getMock(); - $this->itemService->autoPurgeOld(); - } + $item->expects($this->once()) + ->method('getFeedId') + ->will($this->returnValue(1)); + $item->expects($this->once()) + ->method('getGuidHash') + ->will($this->returnValue('hash')); - public function testGetNewestItemId() - { - $this->oldItemMapper->expects($this->once()) - ->method('getNewestItemId') - ->with($this->equalTo('jack')) - ->will($this->returnValue(12)); + $this->mapper->expects($this->once()) + ->method('findByGuidHash') + ->with(1, 'hash') + ->will($this->throwException(new DoesNotExistException('exception'))); - $result = $this->itemService->getNewestItemId('jack'); - $this->assertEquals(12, $result); - } + $this->mapper->expects($this->once()) + ->method('insert') + ->with($item) + ->will($this->returnValue($item)); + $result = $this->class->insertOrUpdate($item); - public function testGetNewestItemIdDoesNotExist() + $this->assertEquals($item, $result); + } + + public function testInsertOrUpdateUpdates() { - $this->oldItemMapper->expects($this->once()) - ->method('getNewestItemId') - ->with($this->equalTo('jack')) - ->will( - $this->throwException( - new DoesNotExistException('There are no items') - ) - ); + $item = $this->getMockBuilder(Item::class) + ->getMock(); + $db_item = $this->getMockBuilder(Item::class) + ->getMock(); - $this->expectException(ServiceNotFoundException::class); - $this->itemService->getNewestItemId('jack'); - } + $item->expects($this->once()) + ->method('getFeedId') + ->will($this->returnValue(1)); + $item->expects($this->once()) + ->method('getGuidHash') + ->will($this->returnValue('hash')); - public function testStarredCount() + $item->expects($this->once()) + ->method('setUnread') + ->with(true) + ->will($this->returnSelf()); + + $db_item->expects($this->once()) + ->method('isUnread') + ->will($this->returnValue(true)); + + $item->expects($this->once()) + ->method('setStarred') + ->with(true) + ->will($this->returnSelf()); + + $db_item->expects($this->once()) + ->method('isStarred') + ->will($this->returnValue(true)); + + $item->expects($this->once()) + ->method('generateSearchIndex') + ->will($this->returnSelf()); + + $item->expects($this->once()) + ->method('getFingerprint') + ->will($this->returnValue('fingerA')); + + $db_item->expects($this->once()) + ->method('getFingerprint') + ->will($this->returnValue('fingerB')); + + $item->expects($this->never()) + ->method('resetUpdatedFields'); + + $this->mapper->expects($this->once()) + ->method('findByGuidHash') + ->with(1, 'hash') + ->will($this->returnValue($db_item)); + + $this->mapper->expects($this->once()) + ->method('update') + ->with($item) + ->will($this->returnValue($item)); + + $result = $this->class->insertOrUpdate($item); + + $this->assertEquals($item, $result); + } + + public function testInsertOrUpdateSkipsSame() { - $star = 18; + $item = $this->getMockBuilder(Item::class) + ->getMock(); + $db_item = $this->getMockBuilder(Item::class) + ->getMock(); - $this->oldItemMapper->expects($this->once()) - ->method('starredCount') - ->with($this->equalTo('jack')) - ->will($this->returnValue($star)); + $item->expects($this->once()) + ->method('getFeedId') + ->will($this->returnValue(1)); + + $item->expects($this->once()) + ->method('getGuidHash') + ->will($this->returnValue('hash')); + + $item->expects($this->once()) + ->method('setUnread') + ->with(true) + ->will($this->returnSelf()); + + $db_item->expects($this->once()) + ->method('isUnread') + ->will($this->returnValue(true)); + + $item->expects($this->once()) + ->method('setStarred') + ->with(true) + ->will($this->returnSelf()); + + $db_item->expects($this->once()) + ->method('isStarred') + ->will($this->returnValue(true)); + + $item->expects($this->once()) + ->method('generateSearchIndex') + ->will($this->returnSelf()); + + $item->expects($this->once()) + ->method('getFingerprint') + ->will($this->returnValue('fingerA')); + + $db_item->expects($this->once()) + ->method('getFingerprint') + ->will($this->returnValue('fingerA')); + + $item->expects($this->once()) + ->method('resetUpdatedFields'); + + $this->mapper->expects($this->once()) + ->method('findByGuidHash') + ->with(1, 'hash') + ->will($this->returnValue($db_item)); + + $this->mapper->expects($this->once()) + ->method('update') + ->with($item) + ->will($this->returnValue($item)); + + $result = $this->class->insertOrUpdate($item); - $result = $this->itemService->starredCount('jack'); + $this->assertEquals($item, $result); + } + + public function testPurgeOverThresholdNull() + { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('news', 'autoPurgeCount', 200) + ->will($this->returnValue(200)); + + $this->mapper->expects($this->once()) + ->method('deleteOverThreshold') + ->with(200); - $this->assertEquals($star, $result); + $this->class->purgeOverThreshold(); } + public function testPurgeOverThresholdSet() + { + $this->config->expects($this->never()) + ->method('getAppValue') + ->with('news', 'autoPurgeCount', 200); + + $this->mapper->expects($this->once()) + ->method('deleteOverThreshold') + ->with(5); + + $this->class->purgeOverThreshold(5); + } - public function testGetUnreadOrStarred() + public function testFindByGuidHash() { - $this->oldItemMapper->expects($this->once()) - ->method('findAllUnreadOrStarred') - ->with($this->equalTo('jack')) - ->will($this->returnValue([])); + $item = $this->getMockBuilder(Item::class) + ->getMock(); - $result = $this->itemService->getUnreadOrStarred('jack'); + $this->mapper->expects($this->once()) + ->method('findByGuidHash') + ->with(1, 'a') + ->will($this->returnValue($item)); - $this->assertEquals([], $result); + $result = $this->class->findByGuidHash(1, 'a'); + + $this->assertEquals($item, $result); } + public function testFindAllInFeed() + { + $items = [new Item(), new Item()]; + + $this->mapper->expects($this->once()) + ->method('findAllInFeedAfter') + ->with('jack', 1, PHP_INT_MIN, false) + ->will($this->returnValue($items)); + + $result = $this->class->findAllInFeed('jack', 1); + $this->assertEquals($items, $result); + } } diff --git a/tests/Unit/Service/ServiceTest.php b/tests/Unit/Service/ServiceTest.php index cfaf82c95e..cc4e2604b0 100644 --- a/tests/Unit/Service/ServiceTest.php +++ b/tests/Unit/Service/ServiceTest.php @@ -14,8 +14,8 @@ namespace OCA\News\Tests\Unit\Service; use OCA\News\Db\Feed; -use OCA\News\Db\ItemMapper; use OCA\News\Db\ItemMapperV2; +use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCA\News\Service\Service; use \OCP\AppFramework\Db\DoesNotExistException; @@ -112,7 +112,7 @@ public function testFindMultiple() ->method('findFromUser') ->will($this->throwException($ex)); - $this->expectException(ServiceNotFoundException::class); + $this->expectException(ServiceConflictException::class); $this->class->find('', 1); }