From a97dd58e3b499b60ac8b37786d402d7f2e371a88 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Mon, 14 Aug 2017 10:34:53 +0200 Subject: Split binary to booleans (#203) * replaced old status with 2 flags for unread and starred * add fields to db, replace int(1,0) with booleans in sql queries, removed StatusFlags class + refactor code relying to it * add repair step for migration * again use integer(1,0) instead of bool in sql queries, because of sqlite doesn't support true/false * add/fix unit tests for new boolean status * set unread/starred flags as statements in sql * fixed mysql unknown column items.unread, fixed marking of read items on repair step * remove unnecessary bool casts * add empty checks to Items::is* methods * update migration to use native sql instead of the querybuilder * don't cast the flags manually, let the api do the work --- appinfo/database.xml | 12 + appinfo/info.xml | 8 +- lib/Db/FeedMapper.php | 20 +- lib/Db/Item.php | 55 +-- lib/Db/ItemMapper.php | 125 +++-- lib/Db/Mysql/ItemMapper.php | 23 +- lib/Db/StatusFlag.php | 47 -- lib/Fetcher/FeedFetcher.php | 2 +- lib/Migration/MigrateStatusFlags.php | 56 +++ lib/Service/FeedService.php | 2 +- lib/Service/ItemService.php | 29 +- tests/Integration/Db/FeedMapperTest.php | 192 +++++++- tests/Integration/Db/ItemMapperTest.php | 13 +- tests/Integration/Fixtures/ItemFixture.php | 3 +- tests/Integration/Fixtures/data/default.php | 14 +- tests/Integration/Fixtures/data/readitem.php | 12 +- tests/Integration/IntegrationTest.php | 5 +- tests/Unit/Controller/EntityApiSerializerTest.php | 10 +- tests/Unit/Db/FeedMapperTest.php | 303 ------------ tests/Unit/Db/ItemMapperTest.php | 550 ---------------------- tests/Unit/Db/ItemTest.php | 31 +- tests/Unit/Db/Mysql/ItemMapperTest.php | 119 ----- tests/Unit/Fetcher/FeedFetcherTest.php | 2 +- tests/Unit/Migration/MigrateStatusFlagsTest.php | 99 ++++ tests/Unit/Service/FeedServiceTest.php | 14 +- tests/Unit/Service/ItemServiceTest.php | 44 +- tests/Unit/Service/StatusFlagTest.php | 57 --- 27 files changed, 548 insertions(+), 1299 deletions(-) delete mode 100644 lib/Db/StatusFlag.php create mode 100644 lib/Migration/MigrateStatusFlags.php delete mode 100644 tests/Unit/Db/FeedMapperTest.php delete mode 100644 tests/Unit/Db/ItemMapperTest.php delete mode 100644 tests/Unit/Db/Mysql/ItemMapperTest.php create mode 100644 tests/Unit/Migration/MigrateStatusFlagsTest.php delete mode 100644 tests/Unit/Service/StatusFlagTest.php diff --git a/appinfo/database.xml b/appinfo/database.xml index 1044208cd..e9c65a947 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -363,6 +363,18 @@ 0 true + + unread + boolean + false + true + + + starred + boolean + false + true + last_modified integer diff --git a/appinfo/info.xml b/appinfo/info.xml index 6f97c052f..099f3d824 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -8,7 +8,7 @@ Before you update to a new version, [check the changelog](https://github.com/nextcloud/news/blob/master/CHANGELOG.md) to avoid surprises. **Important**: To enable feed updates you will need to enable either [Nextcloud system cron](https://docs.nextcloud.com/server/10/admin_manual/configuration_server/background_jobs_configuration.html#cron) or use [an updater](https://github.com/nextcloud/news-updater) which uses the built in update API and disable cron updates. More information can be found [in the README](https://github.com/nextcloud/news).]]> - 11.0.5 + 11.0.6 agpl Bernhard Posselt Alessandro Cosentino @@ -42,6 +42,12 @@ Before you update to a new version, [check the changelog](https://github.com/nex OCA\News\Cron\Updater + + + OCA\News\Migration\MigrateStatusFlags + + + OCA\News\Settings\Admin OCA\News\Settings\Section diff --git a/lib/Db/FeedMapper.php b/lib/Db/FeedMapper.php index 056296e49..ad54ffc37 100644 --- a/lib/Db/FeedMapper.php +++ b/lib/Db/FeedMapper.php @@ -35,12 +35,11 @@ class FeedMapper extends NewsMapper { // work because prepared statements dont work. This is a // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. // think twice when changing this - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . + 'AND unread = ? ' . 'WHERE `feeds`.`id` = ? ' . 'AND `feeds`.`user_id` = ? ' . 'GROUP BY `feeds`.`id`'; - $params = [$id, $userId]; + $params = [true, $id, $userId]; return $this->findEntity($sql, $params); } @@ -57,15 +56,14 @@ class FeedMapper extends NewsMapper { // work because prepared statements dont work. This is a // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. // think twice when changing this - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . + 'AND unread = ? ' . 'WHERE `feeds`.`user_id` = ? ' . 'AND (`feeds`.`folder_id` = 0 ' . 'OR `folders`.`deleted_at` = 0' . ')' . 'AND `feeds`.`deleted_at` = 0 ' . 'GROUP BY `feeds`.`id`'; - $params = [$userId]; + $params = [true, $userId]; return $this->findEntities($sql, $params); } @@ -82,15 +80,14 @@ class FeedMapper extends NewsMapper { // work because prepared statements dont work. This is a // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. // think twice when changing this - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . + 'AND unread = ? ' . 'WHERE (`feeds`.`folder_id` = 0 ' . 'OR `folders`.`deleted_at` = 0' . ')' . 'AND `feeds`.`deleted_at` = 0 ' . 'GROUP BY `feeds`.`id`'; - return $this->findEntities($sql); + return $this->findEntities($sql, [true]); } @@ -103,12 +100,11 @@ class FeedMapper extends NewsMapper { // work because prepared statements dont work. This is a // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. // think twice when changing this - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . + 'AND unread = ? ' . 'WHERE `feeds`.`url_hash` = ? ' . 'AND `feeds`.`user_id` = ? ' . 'GROUP BY `feeds`.`id`'; - $params = [$hash, $userId]; + $params = [true, $hash, $userId]; return $this->findEntity($sql, $params); } diff --git a/lib/Db/Item.php b/lib/Db/Item.php index f19843c8e..a781048fd 100644 --- a/lib/Db/Item.php +++ b/lib/Db/Item.php @@ -37,14 +37,14 @@ use \OCP\AppFramework\Db\Entity; * @method void setEnclosureLink(string $value) * @method integer getFeedId() * @method void setFeedId(integer $value) - * @method integer getStatus() - * @method void setStatus(integer $value) * @method void setRtl(boolean $value) * @method string getLastModified() * @method void setLastModified(string $value) * @method void setFingerprint(string $value) * @method void setContentHash(string $value) * @method void setSearchIndex(string $value) + * @method void setUnread(bool $value) + * @method void setStarred(bool $value) */ class Item extends Entity implements IAPI, \JsonSerializable { @@ -67,53 +67,28 @@ class Item extends Entity implements IAPI, \JsonSerializable { protected $searchIndex; protected $rtl; protected $fingerprint; + protected $unread = false; + protected $starred = false; public function __construct() { $this->addType('pubDate', 'integer'); $this->addType('updatedDate', 'integer'); $this->addType('feedId', 'integer'); - $this->addType('status', 'integer'); $this->addType('rtl', 'boolean'); - } - - public function setRead() { - $this->markFieldUpdated('status'); - $this->status &= ~StatusFlag::UNREAD; - } - - public function isRead() { - return !(($this->status & StatusFlag::UNREAD) === StatusFlag::UNREAD); - } - - public function setUnread() { - $this->markFieldUpdated('status'); - $this->status |= StatusFlag::UNREAD; + $this->addType('unread', 'boolean'); + $this->addType('starred', 'boolean'); } public function isUnread() { - return !$this->isRead(); - } - - public function setStarred() { - $this->markFieldUpdated('status'); - $this->status |= StatusFlag::STARRED; + return $this->unread; } public function isStarred() { - return ($this->status & StatusFlag::STARRED) === StatusFlag::STARRED; - } - - public function setUnstarred() { - $this->markFieldUpdated('status'); - $this->status &= ~StatusFlag::STARRED; - } - - public function isUnstarred() { - return !$this->isStarred(); + return $this->starred; } /** - * Turns entitie attributes into an array + * Turns entity attributes into an array */ public function jsonSerialize() { return [ @@ -196,16 +171,8 @@ class Item extends Entity implements IAPI, \JsonSerializable { $item->setEnclosureMime($import['enclosureMime']); $item->setEnclosureLink($import['enclosureLink']); $item->setRtl($import['rtl']); - if ($import['unread']) { - $item->setUnread(); - } else { - $item->setRead(); - } - if ($import['starred']) { - $item->setStarred(); - } else { - $item->setUnstarred(); - } + $item->setUnread($import['unread']); + $item->setStarred($import['starred']); return $item; } diff --git a/lib/Db/ItemMapper.php b/lib/Db/ItemMapper.php index b49cdd5ea..777d0d392 100644 --- a/lib/Db/ItemMapper.php +++ b/lib/Db/ItemMapper.php @@ -15,6 +15,7 @@ namespace OCA\News\Db; use Exception; use OCA\News\Utility\Time; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -45,18 +46,28 @@ class ItemMapper extends NewsMapper { 'ORDER BY `items`.`id` ' . $ordering; } - private function makeSelectQueryStatus($prependTo, $status, - $oldestFirst = false, $search = [], - $distinctFingerprint = false) { - $status = (int)$status; - $count = count($search); + /** + * 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) { + $sql .= 'AND `items`.`unread` = '; + $sql .= $this->db->quote(true, IQueryBuilder::PARAM_BOOL) . ' '; + } - // WARNING: Potential SQL injection if you change this carelessly - $sql = 'AND ((`items`.`status` & ' . $status . ') = ' . $status . ') '; - $sql .= str_repeat('AND `items`.`search_index` LIKE ? ', $count); - $sql .= $prependTo; + return $sql; + } - return $this->makeSelectQuery($sql, $oldestFirst, $distinctFingerprint); + private function buildSearchQueryPart(array $search = []) { + return str_repeat('AND `items`.`search_index` LIKE ? ', count($search)); } /** @@ -88,14 +99,13 @@ class ItemMapper extends NewsMapper { 'ON `feeds`.`id` = `items`.`feed_id` ' . 'AND `feeds`.`deleted_at` = 0 ' . 'AND `feeds`.`user_id` = ? ' . - 'AND ((`items`.`status` & ' . StatusFlag::STARRED . ') = ' . - StatusFlag::STARRED . ')' . + 'AND `items`.`starred` = ? ' . 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . 'ON `folders`.`id` = `feeds`.`folder_id` ' . 'WHERE `feeds`.`folder_id` = 0 ' . 'OR `folders`.`deleted_at` = 0'; - $params = [$userId]; + $params = [$userId, true]; $result = $this->execute($sql, $params)->fetch(); @@ -105,21 +115,21 @@ class ItemMapper extends NewsMapper { public function readAll($highestItemId, $time, $userId) { $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET `status` = `status` & ? ' . + 'SET unread = ? ' . ', `last_modified` = ? ' . 'WHERE `feed_id` IN (' . 'SELECT `id` FROM `*PREFIX*news_feeds` ' . 'WHERE `user_id` = ? ' . ') ' . 'AND `id` <= ?'; - $params = [~StatusFlag::UNREAD, $time, $userId, $highestItemId]; + $params = [false, $time, $userId, $highestItemId]; $this->execute($sql, $params); } public function readFolder($folderId, $highestItemId, $time, $userId) { $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET `status` = `status` & ? ' . + 'SET unread = ? ' . ', `last_modified` = ? ' . 'WHERE `feed_id` IN (' . 'SELECT `id` FROM `*PREFIX*news_feeds` ' . @@ -127,7 +137,7 @@ class ItemMapper extends NewsMapper { 'AND `user_id` = ? ' . ') ' . 'AND `id` <= ?'; - $params = [~StatusFlag::UNREAD, $time, $folderId, $userId, + $params = [false, $time, $folderId, $userId, $highestItemId]; $this->execute($sql, $params); } @@ -135,7 +145,7 @@ class ItemMapper extends NewsMapper { public function readFeed($feedId, $highestItemId, $time, $userId) { $sql = 'UPDATE `*PREFIX*news_items` ' . - 'SET `status` = `status` & ? ' . + 'SET unread = ? ' . ', `last_modified` = ? ' . 'WHERE `feed_id` = ? ' . 'AND `id` <= ? ' . @@ -143,7 +153,7 @@ class ItemMapper extends NewsMapper { 'SELECT * FROM `*PREFIX*news_feeds` ' . 'WHERE `user_id` = ? ' . 'AND `id` = ? ) '; - $params = [~StatusFlag::UNREAD, $time, $feedId, $highestItemId, + $params = [false, $time, $feedId, $highestItemId, $userId, $feedId]; $this->execute($sql, $params); @@ -159,27 +169,33 @@ class ItemMapper extends NewsMapper { } - public function findAllNew($updatedSince, $status, $userId) { - $sql = $this->makeSelectQueryStatus( - 'AND `items`.`last_modified` >= ? ', $status); + public function findAllNew($updatedSince, $type, $showAll, $userId) { + $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($id, $updatedSince, $status, $userId) { - $sql = 'AND `feeds`.`folder_id` = ? ' . + public function findAllNewFolder($id, $updatedSince, $showAll, $userId) { + $sql = $this->buildStatusQueryPart($showAll); + + $sql .= 'AND `feeds`.`folder_id` = ? ' . 'AND `items`.`last_modified` >= ? '; - $sql = $this->makeSelectQueryStatus($sql, $status); + $sql = $this->makeSelectQuery($sql); $params = [$userId, $id, $updatedSince]; return $this->findEntities($sql, $params); } - public function findAllNewFeed($id, $updatedSince, $status, $userId) { - $sql = 'AND `items`.`feed_id` = ? ' . + public function findAllNewFeed($id, $updatedSince, $showAll, $userId) { + $sql = $this->buildStatusQueryPart($showAll); + + $sql .= 'AND `items`.`feed_id` = ? ' . 'AND `items`.`last_modified` >= ? '; - $sql = $this->makeSelectQueryStatus($sql, $status); + $sql = $this->makeSelectQuery($sql); $params = [$userId, $id, $updatedSince]; return $this->findEntities($sql, $params); } @@ -195,63 +211,68 @@ class ItemMapper extends NewsMapper { } - public function findAllFeed($id, $limit, $offset, $status, $oldestFirst, + public function findAllFeed($id, $limit, $offset, $showAll, $oldestFirst, $userId, $search = []) { $params = [$userId]; $params = array_merge($params, $this->buildLikeParameters($search)); $params[] = $id; - $sql = 'AND `items`.`feed_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->makeSelectQueryStatus($sql, $status, $oldestFirst, - $search); + $sql = $this->makeSelectQuery($sql, $oldestFirst, $search); return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); } - public function findAllFolder($id, $limit, $offset, $status, $oldestFirst, + public function findAllFolder($id, $limit, $offset, $showAll, $oldestFirst, $userId, $search = []) { $params = [$userId]; $params = array_merge($params, $this->buildLikeParameters($search)); $params[] = $id; - $sql = 'AND `feeds`.`folder_id` = ? '; + $sql = $this->buildStatusQueryPart($showAll); + $sql .= $this->buildSearchQueryPart($search); + + $sql .= 'AND `feeds`.`folder_id` = ? '; if ($offset !== 0) { $sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? '; $params[] = $offset; } - $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst, - $search); + $sql = $this->makeSelectQuery($sql, $oldestFirst, $search); return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); } - public function findAll($limit, $offset, $status, $oldestFirst, $userId, + public function findAll($limit, $offset, $type, $showAll, $oldestFirst, $userId, $search = []) { $params = [$userId]; $params = array_merge($params, $this->buildLikeParameters($search)); - $sql = ''; + $sql = $this->buildStatusQueryPart($showAll, $type); + $sql .= $this->buildSearchQueryPart($search); + if ($offset !== 0) { $sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? '; $params[] = $offset; } - $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst, - $search); + + $sql = $this->makeSelectQuery($sql, $oldestFirst); return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit); } public function findAllUnreadOrStarred($userId) { - $params = [$userId]; - $status = StatusFlag::UNREAD | StatusFlag::STARRED; - $sql = 'AND ((`items`.`status` & ' . $status . ') > 0) '; + $params = [$userId, true, true]; + $sql = 'AND (`items`.`unread` = ? OR `items`.`starred` = ?) '; $sql = $this->makeSelectQuery($sql); return $this->findEntities($sql, $params); } @@ -272,15 +293,15 @@ class ItemMapper extends NewsMapper { * @param int $threshold the number of items that should be deleted */ public function deleteReadOlderThanThreshold($threshold) { - $status = StatusFlag::STARRED | StatusFlag::UNREAD; - $params = [$status, $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 NOT ((`items`.`status` & ?) > 0) ' . + 'AND `items`.`unread` = ? ' . + 'AND `items`.`starred` = ? ' . 'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' . 'HAVING COUNT(*) > ?'; @@ -292,12 +313,13 @@ class ItemMapper extends NewsMapper { $limit = $size - $threshold; if ($limit > 0) { - $params = [$status, $row['feed_id'], $limit]; + $params = [false, false, $row['feed_id'], $limit]; $sql = 'DELETE FROM `*PREFIX*news_items` ' . 'WHERE `id` IN (' . 'SELECT `id` FROM `*PREFIX*news_items` ' . - 'WHERE NOT ((`status` & ?) > 0) ' . + 'WHERE `unread` = ? ' . + 'AND `starred` = ? ' . 'AND `feed_id` = ? ' . 'ORDER BY `id` ASC ' . 'LIMIT ?' . @@ -383,19 +405,18 @@ class ItemMapper extends NewsMapper { // as unread if ($isRead) { $sql = 'UPDATE `*PREFIX*news_items` - SET `status` = `status` & ?, + SET `unread` = ?, `last_modified` = ? WHERE `fingerprint` = ? AND `feed_id` IN ( SELECT `f`.`id` FROM `*PREFIX*news_feeds` AS `f` WHERE `f`.`user_id` = ? )'; - $params = [~StatusFlag::UNREAD, $lastModified, - $item->getFingerprint(), $userId]; + $params = [false, $lastModified, $item->getFingerprint(), $userId]; $this->execute($sql, $params); } else { $item->setLastModified($lastModified); - $item->setUnread(); + $item->setUnread(true); $this->update($item); } } diff --git a/lib/Db/Mysql/ItemMapper.php b/lib/Db/Mysql/ItemMapper.php index 8053c3d87..ad373eafb 100644 --- a/lib/Db/Mysql/ItemMapper.php +++ b/lib/Db/Mysql/ItemMapper.php @@ -16,9 +16,6 @@ namespace OCA\News\Db\Mysql; use OCA\News\Utility\Time; use OCP\IDBConnection; -use OCA\News\Db\StatusFlag; - - class ItemMapper extends \OCA\News\Db\ItemMapper { public function __construct(IDBConnection $db, Time $time){ @@ -29,19 +26,19 @@ class ItemMapper extends \OCA\News\Db\ItemMapper { /** * 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 + * @param int $threshold the number of items that should be deleted */ public function deleteReadOlderThanThreshold($threshold){ - $status = StatusFlag::STARRED | StatusFlag::UNREAD; $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 NOT ((`items`.`status` & ?) > 0) ' . + 'AND `items`.`unread` = ? ' . + 'AND `items`.`starred` = ? ' . 'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' . 'HAVING COUNT(*) > ?'; - $params = [$status, $threshold]; + $params = [false, false, $threshold]; $result = $this->execute($sql, $params); while($row = $result->fetch()) { @@ -50,10 +47,11 @@ class ItemMapper extends \OCA\News\Db\ItemMapper { $limit = $size - $threshold; if($limit > 0) { - $params = [$status, $row['feed_id'], $limit]; + $params = [false, false, $row['feed_id'], $limit]; $sql = 'DELETE FROM `*PREFIX*news_items` ' . - 'WHERE NOT ((`status` & ?) > 0) ' . + 'WHERE `unread` = ? ' . + 'AND `starred` = ? ' . 'AND `feed_id` = ? ' . 'ORDER BY `id` ASC ' . 'LIMIT ?'; @@ -71,16 +69,15 @@ class ItemMapper extends \OCA\News\Db\ItemMapper { $sql = 'UPDATE `*PREFIX*news_items` `items` JOIN `*PREFIX*news_feeds` `feeds` ON `feeds`.`id` = `items`.`feed_id` - SET `items`.`status` = `items`.`status` & ?, + SET `items`.`unread` = ?, `items`.`last_modified` = ? WHERE `items`.`fingerprint` = ? AND `feeds`.`user_id` = ?'; - $params = [~StatusFlag::UNREAD, $lastModified, - $item->getFingerprint(), $userId]; + $params = [false, $lastModified, $item->getFingerprint(), $userId]; $this->execute($sql, $params); } else { $item->setLastModified($lastModified); - $item->setUnread(); + $item->setUnread(true); $this->update($item); } } diff --git a/lib/Db/StatusFlag.php b/lib/Db/StatusFlag.php deleted file mode 100644 index 314a81d01..000000000 --- a/lib/Db/StatusFlag.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @author Bernhard Posselt - * @copyright Alessandro Cosentino 2012 - * @copyright Bernhard Posselt 2012, 2014 - */ - -namespace OCA\News\Db; - -class StatusFlag { - const UNREAD = 0x02; - const STARRED = 0x04; - const DELETED = 0x08; - const UPDATED = 0x16; - - - /** - * Get status for query - * - * @param int $type the type that should be turned into the status - * @param bool $showAll true if it should return all read items - * @return int the status for the database - */ - public function typeToStatus($type, $showAll){ - if($type === FeedType::STARRED){ - return self::STARRED; - } else { - $status = 0; - } - - if($showAll){ - $status &= ~self::UNREAD; - } else { - $status |= self::UNREAD; - } - - return $status; - } - - -} \ No newline at end of file diff --git a/lib/Fetcher/FeedFetcher.php b/lib/Fetcher/FeedFetcher.php index b87001723..e63db2772 100644 --- a/lib/Fetcher/FeedFetcher.php +++ b/lib/Fetcher/FeedFetcher.php @@ -242,7 +242,7 @@ class FeedFetcher implements IFeedFetcher { protected function buildItem($parsedItem, $parsedFeed) { $item = new Item(); - $item->setUnread(); + $item->setUnread(true); $item->setUrl($parsedItem->getUrl()); $item->setGuid($parsedItem->getId()); $item->setGuidHash($item->getGuid()); diff --git a/lib/Migration/MigrateStatusFlags.php b/lib/Migration/MigrateStatusFlags.php new file mode 100644 index 000000000..acf53ebba --- /dev/null +++ b/lib/Migration/MigrateStatusFlags.php @@ -0,0 +1,56 @@ + + * @copyright Daniel Opitz 2017 + */ + +namespace OCA\News\Migration; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IRepairStep; +use OCP\Migration\IOutput; + +class MigrateStatusFlags implements IRepairStep { + + /** @var IDBConnection */ + private $db; + + /** @var IConfig */ + private $config; + + /** + * @param IDBConnection $db + * @param IConfig $config + */ + public function __construct(IDBConnection $db, IConfig $config) { + $this->db = $db; + $this->config = $config; + } + + public function getName() { + return 'Migrate binary status into separate boolean fields'; + } + + public function run(IOutput $output) { + $version = $this->config->getAppValue('news', 'installed_version', '0.0.0'); + if (version_compare($version, '11.0.6', '>=')) { + return; + } + + $sql = 'UPDATE `*PREFIX*news_items` ' + . 'SET `unread` = ((`status` & 2) = 2), ' + . '`starred` = ((`status` & 4) = 4)'; + $query = $this->db->prepare($sql); + + if (!$query->execute()) { + throw new \Exception('Could not migrate status'); + } + } +} \ No newline at end of file diff --git a/lib/Service/FeedService.php b/lib/Service/FeedService.php index f17b759e3..d24fd259e 100644 --- a/lib/Service/FeedService.php +++ b/lib/Service/FeedService.php @@ -264,7 +264,7 @@ class FeedService extends Service { // update modes: 0 nothing, 1 set unread if ($existingFeed->getUpdateMode() === 1) { - $dbItem->setUnread(); + $dbItem->setUnread(true); } $this->itemMapper->update($dbItem); diff --git a/lib/Service/ItemService.php b/lib/Service/ItemService.php index f657b3eff..fee00ffc2 100644 --- a/lib/Service/ItemService.php +++ b/lib/Service/ItemService.php @@ -13,11 +13,11 @@ namespace OCA\News\Service; +use OCA\News\Db\Item; use OCP\IConfig; use OCP\AppFramework\Db\DoesNotExistException; use OCA\News\Db\ItemMapper; -use OCA\News\Db\StatusFlag; use OCA\News\Db\FeedType; use OCA\News\Config\Config; use OCA\News\Utility\Time; @@ -25,19 +25,16 @@ use OCA\News\Utility\Time; class ItemService extends Service { - private $statusFlag; private $config; private $timeFactory; private $itemMapper; private $systemConfig; public function __construct(ItemMapper $itemMapper, - StatusFlag $statusFlag, Time $timeFactory, Config $config, IConfig $systemConfig){ parent::__construct($itemMapper); - $this->statusFlag = $statusFlag; $this->config = $config; $this->timeFactory = $timeFactory; $this->itemMapper = $itemMapper; @@ -56,20 +53,18 @@ class ItemService extends Service { * @return array of items */ public function findAllNew($id, $type, $updatedSince, $showAll, $userId){ - $status = $this->statusFlag->typeToStatus($type, $showAll); - switch($type){ case FeedType::FEED: return $this->itemMapper->findAllNewFeed( - $id, $updatedSince, $status, $userId + $id, $updatedSince, $showAll, $userId ); case FeedType::FOLDER: return $this->itemMapper->findAllNewFolder( - $id, $updatedSince, $status, $userId + $id, $updatedSince, $showAll, $userId ); default: return $this->itemMapper->findAllNew( - $updatedSince, $status, $userId + $updatedSince, $type, $showAll, $userId ); } } @@ -90,22 +85,20 @@ class ItemService extends Service { */ public function findAll($id, $type, $limit, $offset, $showAll, $oldestFirst, $userId, $search=[]){ - $status = $this->statusFlag->typeToStatus($type, $showAll); - switch($type){ case FeedType::FEED: return $this->itemMapper->findAllFeed( - $id, $limit, $offset, $status, $oldestFirst, $userId, + $id, $limit, $offset, $showAll, $oldestFirst, $userId, $search ); case FeedType::FOLDER: return $this->itemMapper->findAllFolder( - $id, $limit, $offset, $status, $oldestFirst, $userId, + $id, $limit, $offset, $showAll, $oldestFirst, $userId, $search ); default: return $this->itemMapper->findAll( - $limit, $offset, $status, $oldestFirst, $userId, $search + $limit, $offset, $type, $showAll, $oldestFirst, $userId, $search ); } } @@ -122,15 +115,13 @@ class ItemService extends Service { */ public function star($feedId, $guidHash, $isStarred, $userId){ try { + /** @var Item $item */ $item = $this->itemMapper->findByGuidHash( $guidHash, $feedId, $userId ); - if($isStarred){ - $item->setStarred(); - } else { - $item->setUnstarred(); - } + $item->setStarred($isStarred); + $this->itemMapper->update($item); } catch(DoesNotExistException $ex) { throw new ServiceNotFoundException($ex->getMessage()); diff --git a/tests/Integration/Db/FeedMapperTest.php b/tests/Integration/Db/FeedMapperTest.php index 0c13280f5..b429149a4 100644 --- a/tests/Integration/Db/FeedMapperTest.php +++ b/tests/Integration/Db/FeedMapperTest.php @@ -6,49 +6,231 @@ * later. See the COPYING file. * * @author Bernhard Posselt + * @author Daniel Opitz * @copyright Bernhard Posselt 2015 + * @copyright Daniel Opitz 2017 */ namespace OCA\News\Db; -use \OCA\News\Tests\Integration\IntegrationTest; +use OCA\News\Tests\Integration\Fixtures\FeedFixture; +use OCA\News\Tests\Integration\IntegrationTest; class FeedMapperTest extends IntegrationTest { - public function testFind () { + $feed = new FeedFixture(); + $feed = $this->feedMapper->insert($feed); + + $fetched = $this->feedMapper->find($feed->getId(), $this->user); + + $this->assertInstanceOf(Feed::class, $fetched); + $this->assertEquals($feed->getLink(), $fetched->getLink()); + } + + /** + * @expectedException OCP\AppFramework\Db\DoesNotExistException + */ + public function testFindNotExisting () { + $this->feedMapper->find(0, $this->user); } - /* TBD public function testFindAll () { + $feeds = [ + [ + 'userId' => $this->user, + 'items' => [] + ], + [ + 'userId' => 'john', + 'items' => [] + ] + ]; + $this->loadFeedFixtures($feeds); + + $fetched = $this->feedMapper->findAll(); + + $this->assertInternalType('array', $fetched); + $this->assertCount(2, $fetched); + $this->assertContainsOnlyInstancesOf(Feed::class, $fetched); + + $this->tearDownUser('john'); + } + public function testFindAllEmpty () { + $feeds = $this->feedMapper->findAll(); + + $this->assertInternalType('array', $feeds); + $this->assertCount(0, $feeds); } public function testFindAllFromUser () { + $feeds = [ + [ + 'userId' => $this->user, + 'items' => [] + ], + [ + 'userId' => 'john', + 'items' => [] + ] + ]; + $this->loadFeedFixtures($feeds); + + $fetched = $this->feedMapper->findAllFromUser($this->user); + + $this->assertInternalType('array', $fetched); + $this->assertCount(1, $fetched); + $this->assertContainsOnlyInstancesOf(Feed::class, $fetched); + + $this->tearDownUser('john'); + } + + public function testFindAllFromUserNotExisting () { + $fetched = $this->feedMapper->findAllFromUser('notexistinguser'); + + $this->assertInternalType('array', $fetched); + $this->assertCount(0, $fetched); } public function testFindByUrlHash () { + $feed = new FeedFixture([ + 'urlHash' => 'someTestHash', + 'title' => 'Some Test Title' + ]); + $feed = $this->feedMapper->insert($feed); + + $fetched = $this->feedMapper->findByUrlHash($feed->getUrlHash(), $this->user); + $this->assertInstanceOf(Feed::class, $fetched); + $this->assertEquals($feed->getTitle(), $fetched->getTitle()); } + /** + * @expectedException OCP\AppFramework\Db\MultipleObjectsReturnedException + */ + public function testFindByUrlHashMoreThanOneResult () { + $feed1 = $this->feedMapper->insert(new FeedFixture([ + 'urlHash' => 'someTestHash' + ])); + $feed2 = $this->feedMapper->insert(new FeedFixture([ + 'urlHash' => 'someTestHash' + ])); + + $this->feedMapper->findByUrlHash($feed1->getUrlHash(), $this->user); + } - public function testDelete () { + /** + * @expectedException OCP\AppFramework\Db\DoesNotExistException + */ + public function testFindByUrlHashNotExisting () { + $this->feedMapper->findByUrlHash('some random hash', $this->user); } + public function testDelete () { + $this->loadFixtures('default'); + + $feeds = $this->feedMapper->findAllFromUser($this->user); + $this->assertCount(4, $feeds); + + $feed = reset($feeds); + + $items = $this->itemMapper->findAllFeed( + $feed->getId(), 100, 0, true, false, $this->user + ); + $this->assertCount(7, $items); + + $this->feedMapper->delete($feed); + + $this->assertCount(3, $this->feedMapper->findAllFromUser($this->user)); + + $items = $this->itemMapper->findAllFeed( + $feed->getId(), 100, 0, true, false, $this->user + ); + $this->assertCount(0, $items); + } + public function testGetToDelete () { + $this->loadFeedFixtures([ + ['deletedAt' => 1], + ['deletedAt' => 0], + ['deletedAt' => 1, 'userId' => 'john'], + ['deletedAt' => 1000] + ]); + + $fetched = $this->feedMapper->getToDelete(); + + $this->assertInternalType('array', $fetched); + $this->assertCount(3, $fetched); + $this->assertContainsOnlyInstancesOf(Feed::class, $fetched); + + $this->tearDownUser('john'); + } + + public function testGetToDeleteOlderThan () { + $this->loadFeedFixtures([ + ['deletedAt' => 1], + ['deletedAt' => 0], + ['deletedAt' => 1, 'userId' => 'john'], + ['deletedAt' => 1000] + ]); + + $fetched = $this->feedMapper->getToDelete(1000); + + $this->assertInternalType('array', $fetched); + $this->assertCount(2, $fetched); + $this->assertContainsOnlyInstancesOf(Feed::class, $fetched); + + $this->tearDownUser('john'); + } + public function testGetToDeleteUser () { + $this->loadFeedFixtures([ + ['deletedAt' => 1], + ['deletedAt' => 0], + ['deletedAt' => 1, 'userId' => 'john'], + ['deletedAt' => 1000] + ]); + + $fetched = $this->feedMapper->getToDelete(2000, $this->user); + + $this->assertInternalType('array', $fetched); + $this->assertCount(2, $fetched); + $this->assertContainsOnlyInstancesOf(Feed::class, $fetched); + + $this->tearDownUser('john'); } + public function testGetToDeleteEmpty () { + $fetched = $this->feedMapper->getToDelete(); + + $this->assertInternalType('array', $fetched); + $this->assertCount(0, $fetched); + } public function testDeleteUser () { + $this->loadFixtures('default'); - }*/ + $this->assertCount(4, $this->feedMapper->findAllFromUser($this->user)); + $items = $this->itemMapper->findAll(100, 0, 0, true, false, $this->user); + $this->assertCount(9, $items); + $this->feedMapper->deleteUser($this->user); + + $this->assertCount(0, $this->feedMapper->findAllFromUser($this->user)); + + $items = $this->itemMapper->findAll(100, 0, 0, true, false, $this->user); + $this->assertCount(0, $items); + } + + public function testDeleteUserNotExisting () { + $this->feedMapper->deleteUser('notexistinguser'); + } } diff --git a/tests/Integration/Db/ItemMapperTest.php b/tests/Integration/Db/ItemMapperTest.php index 6b621e070..fa9cc7d25 100644 --- a/tests/Integration/Db/ItemMapperTest.php +++ b/tests/Integration/Db/ItemMapperTest.php @@ -112,9 +112,8 @@ class ItemMapperTest extends IntegrationTest { $this->itemMapper->readAll(PHP_INT_MAX, 10, $this->user); - $status = StatusFlag::UNREAD; $items = $this->itemMapper->findAll( - 30, 0, $status, false, $this->user + 30, 0, 0, false, false, $this->user ); $this->assertEquals(0, count($items)); @@ -144,9 +143,8 @@ class ItemMapperTest extends IntegrationTest { $folderId, PHP_INT_MAX, 10, $this->user ); - $status = StatusFlag::UNREAD; $items = $this->itemMapper->findAll( - 30, 0, $status, false, $this->user + 30, 0, 0, false, false, $this->user ); $this->assertEquals(1, count($items)); @@ -176,9 +174,8 @@ class ItemMapperTest extends IntegrationTest { $feedId, PHP_INT_MAX, 10, $this->user ); - $status = StatusFlag::UNREAD; $items = $this->itemMapper->findAll( - 30, 0, $status, false, $this->user + 30, 0, 0, false, false, $this->user ); $this->assertEquals(2, count($items)); @@ -246,7 +243,7 @@ class ItemMapperTest extends IntegrationTest { // assert that all test user's same items are read $items = $this->itemMapper->where(['feedId' => $feed->getId(), 'title' => 'blubb']); foreach ($items as $item) { - $this->assertTrue($item->isRead()); + $this->assertFalse($item->isUnread()); } // assert that a different item is not read @@ -277,7 +274,7 @@ class ItemMapperTest extends IntegrationTest { if ($item->getId() === $duplicateItem->getId()) { $this->assertTrue($item->isUnread()); } else { - $this->assertTrue($item->isRead()); + $this->assertFalse($item->isUnread()); } } diff --git a/tests/Integration/Fixtures/ItemFixture.php b/tests/Integration/Fixtures/ItemFixture.php index 2dfe79c28..0832b0ef4 100644 --- a/tests/Integration/Fixtures/ItemFixture.php +++ b/tests/Integration/Fixtures/ItemFixture.php @@ -29,7 +29,8 @@ class ItemFixture extends Item { 'enclosureMime' => 'video/mpeg', 'enclosureLink' => 'http://google.de/web.webm', 'feedId' => 0, - 'status' => 2, + 'unread' => true, + 'starred' => false, 'lastModified' => 113, 'rtl' => false, ], $defaults); diff --git a/tests/Integration/Fixtures/data/default.php b/tests/Integration/Fixtures/data/default.php index d87bb1e6f..862515b12 100644 --- a/tests/Integration/Fixtures/data/default.php +++ b/tests/Integration/Fixtures/data/default.php @@ -20,12 +20,12 @@ return [ 'articlesPerUpdate' => 1, 'items' => [ ['title' => 'a title1', 'guid' => 'abc'], - ['title' => 'a title2', 'status' => 4, 'guid' => 'def'], - ['title' => 'a title3', 'status' => 6, 'guid' => 'gih'], - ['title' => 'del1', 'status' => 0], - ['title' => 'del2', 'status' => 0], - ['title' => 'del3', 'status' => 0], - ['title' => 'del4', 'status' => 0] + ['title' => 'a title2', 'unread' => false, 'starred' => true, 'guid' => 'def'], + ['title' => 'a title3', 'unread' => true, 'starred' => true, 'guid' => 'gih'], + ['title' => 'del1', 'unread' => false, 'starred' => false], + ['title' => 'del2', 'unread' => false, 'starred' => false], + ['title' => 'del3', 'unread' => false, 'starred' => false], + ['title' => 'del4', 'unread' => false, 'starred' => false] ] ], [ @@ -69,7 +69,7 @@ return [ 'title' => 'fourth feed', 'url' => 'http://blog.fefe.de', 'items' => [ - ['title' => 'no folder', 'status' => 0] + ['title' => 'no folder', 'unread' => false, 'starred' => false] ] ] ] diff --git a/tests/Integration/Fixtures/data/readitem.php b/tests/Integration/Fixtures/data/readitem.php index 0a587bad7..8f953a845 100644 --- a/tests/Integration/Fixtures/data/readitem.php +++ b/tests/Integration/Fixtures/data/readitem.php @@ -15,18 +15,18 @@ return [ 'title' => 'john feed', 'userId' => 'john', 'items' => [ - ['title' => 'blubb', 'status' => 2], - ['title' => 'blubb', 'status' => 2] + ['title' => 'blubb', 'unread' => true, 'starred' => false], + ['title' => 'blubb', 'unread' => true, 'starred' => false] ] ], [ 'title' => 'test feed', 'userId' => 'test', 'items' => [ - ['title' => 'blubb', 'status' => 2], - ['title' => 'blubbs', 'status' => 2], - ['title' => 'blubb', 'status' => 2], - ['title' => 'blubb', 'status' => 2] + ['title' => 'blubb', 'unread' => true, 'starred' => false], + ['title' => 'blubbs', 'unread' => true, 'starred' => false], + ['title' => 'blubb', 'unread' => true, 'starred' => false], + ['title' => 'blubb', 'unread' => true, 'starred' => false] ] ] ] diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php index c14d3007a..f14a1263b 100644 --- a/tests/Integration/IntegrationTest.php +++ b/tests/Integration/IntegrationTest.php @@ -124,7 +124,10 @@ abstract class IntegrationTest extends \Test\TestCase { $feed = new FeedFixture($feedFixture); $feed->setFolderId($folderId); $feedId = $this->loadFixture($feed); - $this->loadItemFixtures($feedFixture['items'], $feedId); + + if (!empty($feedFixture['items'])) { + $this->loadItemFixtures($feedFixture['items'], $feedId); + } } } diff --git a/tests/Unit/Controller/EntityApiSerializerTest.php b/tests/Unit/Controller/EntityApiSerializerTest.php index ec357e7f2..63de1ed7e 100644 --- a/tests/Unit/Controller/EntityApiSerializerTest.php +++ b/tests/Unit/Controller/EntityApiSerializerTest.php @@ -29,7 +29,7 @@ class EntityApiSerializerTest extends \PHPUnit_Framework_TestCase { public function testSerializeSingle() { $item = new Item(); - $item->setUnread(); + $item->setUnread(true); $serializer = new EntityApiSerializer('items'); $result = $serializer->serialize($item); @@ -40,10 +40,10 @@ class EntityApiSerializerTest extends \PHPUnit_Framework_TestCase { public function testSerializeMultiple() { $item = new Item(); - $item->setUnread(); + $item->setUnread(true); $item2 = new Item(); - $item2->setRead(); + $item2->setUnread(false); $serializer = new EntityApiSerializer('items'); @@ -66,10 +66,10 @@ class EntityApiSerializerTest extends \PHPUnit_Framework_TestCase { public function testCompleteArraysTransformed() { $item = new Item(); - $item->setUnread(); + $item->setUnread(true); $item2 = new Item(); - $item2->setRead(); + $item2->setUnread(false); $serializer = new EntityApiSerializer('items'); diff --git a/tests/Unit/Db/FeedMapperTest.php b/tests/Unit/Db/FeedMapperTest.php deleted file mode 100644 index ed74b235e..000000000 --- a/tests/Unit/Db/FeedMapperTest.php +++ /dev/null @@ -1,303 +0,0 @@ - - * @author Bernhard Posselt - * @copyright Alessandro Cosentino 2012 - * @copyright Bernhard Posselt 2012, 2014 - */ - -namespace OCA\News\Db; - - -use OCA\News\Utility\Time; - -class FeedMapperTest extends \OCA\News\Tests\Unit\Db\MapperTestUtility { - - private $mapper; - private $feeds; - - protected function setUp(){ - parent::setUp(); - - $this->mapper = new FeedMapper($this->db, new Time()); - - // create mock feeds - $feed1 = new Feed(); - $feed2 = new Feed(); - - $this->feeds = [$feed1, $feed2]; - $this->user = 'herman'; - } - - - public function testFind(){ - $userId = 'john'; - $id = 3; - $rows = [ - ['id' => $this->feeds[0]->getId()], - ]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`id` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $params = [$id, $userId]; - $this->setMapperResult($sql, $params, $rows); - - $result = $this->mapper->find($id, $userId); - $this->assertEquals($this->feeds[0], $result); - - } - - - public function testFindNotFound(){ - $userId = 'john'; - $id = 3; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`id` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $params = [$id, $userId]; - $this->setMapperResult($sql, $params); - - $this->setExpectedException( - '\OCP\AppFramework\Db\DoesNotExistException' - ); - $this->mapper->find($id, $userId); - } - - - public function testFindMoreThanOneResultFound(){ - $userId = 'john'; - $id = 3; - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`id` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $params = [$id, $userId]; - $this->setMapperResult($sql, $params, $rows); - - $this->setExpectedException( - '\OCP\AppFramework\Db\MultipleObjectsReturnedException' - ); - $this->mapper->find($id, $userId); - } - - - public function testFindAll(){ - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` '. - 'ON `feeds`.`folder_id` = `folders`.`id` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE (`feeds`.`folder_id` = 0 ' . - 'OR `folders`.`deleted_at` = 0' . - ')' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'GROUP BY `feeds`.`id`'; - - $this->setMapperResult($sql, [], $rows); - - $result = $this->mapper->findAll(); - $this->assertEquals($this->feeds, $result); - } - - - public function testFindAllFromUser(){ - $userId = 'john'; - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` '. - 'ON `feeds`.`folder_id` = `folders`.`id` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`user_id` = ? ' . - 'AND (`feeds`.`folder_id` = 0 ' . - 'OR `folders`.`deleted_at` = 0' . - ')' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'GROUP BY `feeds`.`id`'; - $this->setMapperResult($sql, [$userId], $rows); - - $result = $this->mapper->findAllFromUser($userId); - $this->assertEquals($this->feeds, $result); - } - - - public function testFindByUrlHash(){ - $urlHash = md5('hihi'); - $row = [['id' => $this->feeds[0]->getId()]]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`url_hash` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $this->setMapperResult($sql, [$urlHash, $this->user], $row); - - $result = $this->mapper->findByUrlHash($urlHash, $this->user); - $this->assertEquals($this->feeds[0], $result); - } - - - public function testFindByUrlHashNotFound(){ - $urlHash = md5('hihi'); - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`url_hash` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $this->setMapperResult($sql, [$urlHash, $this->user]); - - $this->setExpectedException( - '\OCP\AppFramework\Db\DoesNotExistException' - ); - $this->mapper->findByUrlHash($urlHash, $this->user); - } - - - public function testFindByUrlHashMoreThanOneResultFound(){ - $urlHash = md5('hihi'); - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $sql = 'SELECT `feeds`.*, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - 'AND (`items`.`status` & ' . StatusFlag::UNREAD . ') = ' . - StatusFlag::UNREAD . ' ' . - 'WHERE `feeds`.`url_hash` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id`'; - $this->setMapperResult($sql, [$urlHash, $this->user], $rows); - - $this->setExpectedException( - '\OCP\AppFramework\Db\MultipleObjectsReturnedException' - ); - $this->mapper->findByUrlHash($urlHash, $this->user); - } - - - public function testDelete(){ - $feed = new Feed(); - $feed->setId(3); - - $sql = 'DELETE FROM `*PREFIX*news_feeds` WHERE `id` = ?'; - $arguments = [$feed->getId()]; - - $sql2 = 'DELETE FROM `*PREFIX*news_items` WHERE `feed_id` = ?'; - $arguments2 = [$feed->getId()]; - - $this->setMapperResult($sql, $arguments); - $this->setMapperResult($sql2, $arguments2); - - $this->mapper->delete($feed); - - } - - - public function testGetPurgeDeleted(){ - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $deleteOlderThan = 110; - $sql = 'SELECT * FROM `*PREFIX*news_feeds` ' . - 'WHERE `deleted_at` > 0 ' . - 'AND `deleted_at` < ? '; - $this->setMapperResult($sql, [$deleteOlderThan], $rows); - $result = $this->mapper->getToDelete($deleteOlderThan); - - $this->assertEquals($this->feeds, $result); - } - - - public function testGetPurgeDeletedFromUser(){ - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - $deleteOlderThan = 110; - $sql = 'SELECT * FROM `*PREFIX*news_feeds` ' . - 'WHERE `deleted_at` > 0 ' . - 'AND `deleted_at` < ? ' . - 'AND `user_id` = ?'; - $this->setMapperResult($sql, [$deleteOlderThan, $this->user], $rows); - $result = $this->mapper->getToDelete($deleteOlderThan, $this->user); - - $this->assertEquals($this->feeds, $result); - } - - - public function testGetAllPurgeDeletedFromUser(){ - $rows = [ - ['id' => $this->feeds[0]->getId()], - ['id' => $this->feeds[1]->getId()] - ]; - - $sql = 'SELECT * FROM `*PREFIX*news_feeds` ' . - 'WHERE `deleted_at` > 0 ' . - 'AND `user_id` = ?'; - $this->setMapperResult($sql, [$this->user], $rows); - $result = $this->mapper->getToDelete(null, $this->user); - - $this->assertEquals($this->feeds, $result); - } - - - public function testDeleteFromUser(){ - $userId = 'john'; - $sql = 'DELETE FROM `*PREFIX*news_feeds` WHERE `user_id` = ?'; - - $this->setMapperResult($sql, [$userId]); - - $this->mapper->deleteUser($userId); - } - - -} diff --git a/tests/Unit/Db/ItemMapperTest.php b/tests/Unit/Db/ItemMapperTest.php deleted file mode 100644 index 082ff6ff9..000000000 --- a/tests/Unit/Db/ItemMapperTest.php +++ /dev/null @@ -1,550 +0,0 @@ - - * @author Bernhard Posselt - * @copyright Alessandro Cosentino 2012 - * @copyright Bernhard Posselt 2012, 2014 - */ - -namespace OCA\News\Db; - - -use OCA\News\Utility\Time; - -class ItemMapperTest extends \OCA\News\Tests\Unit\Db\MapperTestUtility { - - private $mapper; - private $items; - private $newestItemId; - private $limit; - private $user; - private $offset; - private $updatedSince; - private $status; - - - public function setUp() { - parent::setup(); - - $this->mapper = new ItemMapper($this->db, new Time()); - - // create mock items - $item1 = new Item(); - $item2 = new Item(); - - $this->items = [ - $item1, - $item2 - ]; - - $this->userId = 'john'; - $this->id = 3; - $this->folderId = 2; - - $this->row = [ - ['id' => $this->items[0]->getId()], - ]; - - $this->rows = [ - ['id' => $this->items[0]->getId()], - ['id' => $this->items[1]->getId()] - ]; - - $this->user = 'john'; - $this->limit = 10; - $this->offset = 3; - $this->id = 11; - $this->status = 333; - $this->updatedSince = 323; - $this->newestItemId = 2; - - } - - - private function makeSelectQuery($prependTo, $oldestFirst=false){ - 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` = 0 ' . - 'OR `folders`.`deleted_at` = 0 ' . - 'ORDER BY `items`.`id` ' . $ordering; - } - - private function makeSelectQueryStatus($prependTo, $status, - $oldestFirst=false, $search=[]) { - $status = (int) $status; - - // WARNING: Potential SQL injection if you change this carelessly - $sql = 'AND ((`items`.`status` & ' . $status . ') = ' . $status . ') '; - - foreach ($search as $_) { - $sql .= 'AND `items`.`search_index` LIKE ? '; - } - - $sql .= $prependTo; - - return $this->makeSelectQuery($sql, $oldestFirst); - } - - - public function testFind(){ - $sql = $this->makeSelectQuery('AND `items`.`id` = ? '); - - $this->setMapperResult( - $sql, [$this->userId, $this->id], $this->row - ); - - $result = $this->mapper->find($this->id, $this->userId); - $this->assertEquals($this->items[0], $result); - } - - - public function testGetStarredCount(){ - $userId = 'john'; - $row = [['size' => 9]]; - $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`.`status` & ' . StatusFlag::STARRED . ') = ' . - StatusFlag::STARRED . ')' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . - 'ON `folders`.`id` = `feeds`.`folder_id` ' . - 'WHERE `feeds`.`folder_id` = 0 ' . - 'OR `folders`.`deleted_at` = 0'; - - $this->setMapperResult($sql, [$userId], $row); - - $result = $this->mapper->starredCount($userId); - $this->assertEquals($row[0]['size'], $result); - } - - - public function te