diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php index 2df402a..9f8ea59 100644 --- a/lib/REST/TinyTinyRSS/API.php +++ b/lib/REST/TinyTinyRSS/API.php @@ -26,6 +26,7 @@ use Laminas\Diactoros\Response\EmptyResponse; class API extends \JKingWeb\Arsse\REST\AbstractHandler { public const LEVEL = 14; // emulated API level public const VERSION = "17.4"; // emulated TT-RSS version + protected const LABEL_OFFSET = 1024; // offset below zero at which labels begin, counting down protected const LIMIT_ARTICLES = 200; // maximum number of articles returned by getHeadlines protected const LIMIT_EXCERPT = 100; // maximum length of excerpts in getHeadlines, counted in grapheme units @@ -81,6 +82,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { 'mode' => ValueInfo::T_INT, // whether to set, clear, or toggle the selected state in `updateArticle` 'data' => ValueInfo::T_STRING, // note text in `updateArticle` if setting a note ]; + protected const VIEW_MODES = ["all_articles", "adaptive", "unread", "marked", "has_note", "published"]; // generic error construct protected const FATAL_ERR = [ 'seq' => null, @@ -234,7 +236,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { public function opGetCounters(array $data): array { $user = Arsse::$user->id; $starred = Arsse::$db->articleStarred($user); - $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))); + $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); $countAll = 0; $countSubs = 0; $feeds = []; @@ -339,7 +341,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { 'id' => "FEED:".self::FEED_FRESH, 'bare_id' => self::FEED_FRESH, 'icon' => "images/fresh.png", - 'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))), + 'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)), ], $tSpecial), array_merge([ // Starred articles 'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"), @@ -391,7 +393,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { ]; $unread += ($l['articles'] - $l['read']); } - // if there are labels, all the label category, + // if there are labels, add the "Labels" category, if ($items) { $out[] = [ 'name' => Arsse::$lang->msg("API.TTRSS.Category.Labels"), @@ -523,7 +525,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // FIXME: this is pretty inefficient $f = $map[self::CAT_SPECIAL]; $cats[$f]['unread'] += Arsse::$db->articleStarred($user)['unread']; // starred - $cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))); // fresh + $cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); // fresh if (!$read) { // if we're only including unread entries, remove any categories with zero unread items (this will by definition also exclude empties) $count = sizeof($cats); @@ -675,8 +677,8 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { if ($cat == self::CAT_ALL || $cat == self::CAT_SPECIAL) { // gather some statistics $starred = Arsse::$db->articleStarred($user)['unread']; - $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))); - $global = Arsse::$db->articleCount($user, (new Context)->unread(true)); + $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); + $global = Arsse::$db->articleCount($user, (new Context)->unread(true)->hidden(false)); $published = 0; // TODO: if the Published feed is implemented, the getFeeds method needs to be adjusted accordingly $archived = 0; // the archived feed is non-functional in the TT-RSS protocol itself // build the list; exclude anything with zero unread if requested @@ -737,7 +739,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // NOTE: the list is a flat one: it includes children, but not other descendents foreach (Arsse::$db->folderList($user, $cat, false) as $c) { // get the number of unread for the category and its descendents; those with zero unread are excluded in "unread-only" mode - $count = Arsse::$db->articleCount($user, (new Context)->unread(true)->folder((int) $c['id'])); + $count = Arsse::$db->articleCount($user, (new Context)->unread(true)->folder((int) $c['id'])->hidden(false)); if (!$unread || $count) { $out[] = [ 'id' => (int) $c['id'], @@ -1037,7 +1039,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $cat = $data['is_cat'] ?? false; $out = ['status' => "OK"]; // first prepare the context; unsupported contexts simply return early - $c = new Context; + $c = (new Context)->hidden(false); if ($cat) { // categories switch ($id) { case self::CAT_SPECIAL: @@ -1073,7 +1075,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // TODO: if the Published feed is implemented, the catchup function needs to be modified accordingly return $out; case self::FEED_FRESH: - $c->modifiedSince(Date::sub("PT24H")); + $c->modifiedSince(Date::sub("PT24H", $this->now())); break; case self::FEED_ALL: // no context needed here @@ -1188,6 +1190,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { "id", "guid", "title", + "author", "url", "unread", "starred", @@ -1296,10 +1299,14 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { "subscription", "subscription_title", "note", - ($data['show_content'] || $data['show_excerpt']) ? "content" : "", - ($data['include_attachments']) ? "media_url": "", - ($data['include_attachments']) ? "media_type": "", ]; + if ($data['show_content'] || $data['show_excerpt']) { + $columns[] = "content"; + } + if ($data['include_attachments']) { + $columns[] = "media_url"; + $columns[] = "media_type"; + } foreach ($this->fetchArticles($data, $columns) as $article) { $row = [ 'id' => (int) $article['id'], @@ -1387,9 +1394,10 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $id = $data['feed_id']; $cat = $data['is_cat'] ?? false; $shallow = !($data['include_nested'] ?? false); - $viewMode = in_array($data['view_mode'], ["all_articles", "adaptive", "unread", "marked", "has_note", "published"]) ? $data['view_mode'] : "all_articles"; + $viewMode = in_array($data['view_mode'], self::VIEW_MODES) ? $data['view_mode'] : "all_articles"; + assert(in_array($viewMode, self::VIEW_MODES), new \JKingWeb\Arsse\Exception("constantUnknown", $viewMode)); // prepare the context; unsupported, invalid, or inherently empty contexts return synthetic empty result sets - $c = new Context; + $c = (new Context)->hidden(false); $tr = Arsse::$db->begin(); // start with the feed or category ID if ($cat) { // categories @@ -1433,13 +1441,13 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // TODO: if the Published feed is implemented, the headline function needs to be modified accordingly return new ResultEmpty; case self::FEED_FRESH: - $c->modifiedSince(Date::sub("PT24H"))->unread(true); + $c->modifiedSince(Date::sub("PT24H", $this->now()))->unread(true); break; case self::FEED_ALL: // no context needed here break; case self::FEED_READ: - $c->markedSince(Date::sub("PT24H"))->unread(false); // FIXME: this selects any recently touched (read, starred, annotated) article which is read, not necessarily a recently read one + $c->markedSince(Date::sub("PT24H", $this->now()))->unread(false); // FIXME: this selects any recently touched (read, starred, annotated) article which is read, not necessarily a recently read one break; default: // any actual feed @@ -1477,8 +1485,6 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // not implemented // TODO: if the Published feed is implemented, the headline function needs to be modified accordingly return new ResultEmpty; - default: - throw new \JKingWeb\Arsse\Exception("constantUnknown", $viewMode); // @codeCoverageIgnore } // handle the search string, if any if (isset($data['search'])) { diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index d5ca279..abcbdd9 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -23,6 +23,8 @@ use Laminas\Diactoros\Response\EmptyResponse; /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API * @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { + protected const NOW = "2020-12-21T23:09:17.189065Z"; + protected $h; protected $folders = [ ['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"], @@ -1113,7 +1115,7 @@ LONG_STRING; \Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->topFolders))); \Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions))); \Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels))); - \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); + \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); \Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); $exp = [ [ @@ -1177,7 +1179,7 @@ LONG_STRING; \Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders))); \Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions))); \Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); - \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); + \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); \Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); $exp = [ ['id' => "global-unread", 'counter' => 35], @@ -1298,7 +1300,7 @@ LONG_STRING; \Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders))); \Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions))); \Phake::when(Arsse::$db)->labelList($this->anything(), true)->thenReturn(new Result($this->v($this->labels))); - \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); + \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); \Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); // the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them $exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Photography','id' => 'CAT:4','bare_id' => 4,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(0 feeds)','items' => []],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]]; @@ -1343,19 +1345,19 @@ LONG_STRING; for ($a = 0; $a < sizeof($in2); $a++) { $this->assertMessage($exp, $this->req($in2[$a]), "Test $a failed"); } - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], new Context); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->starred(true)); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1088)); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(2112)); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folder(42)); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folderShallow(0)); - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->labelled(true)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->starred(true)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1088)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(2112)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folder(42)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folderShallow(0)->hidden(false)); + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->labelled(true)->hidden(false)); // verify the time-based mock $t = Date::sub("PT24H"); for ($a = 0; $a < sizeof($in3); $a++) { $this->assertMessage($exp, $this->req($in3[$a]), "Test $a failed"); } - \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], $this->equalTo((new Context)->modifiedSince($t), 2)); // within two seconds + \Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], $this->equalTo((new Context)->hidden(false)->modifiedSince($t), 2)); // within two seconds } public function testRetrieveFeedList(): void { @@ -1385,8 +1387,8 @@ LONG_STRING; ]; // statistical mocks \Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); - \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); - \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(35); + \Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->unread(true)->hidden(false)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); + \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->hidden(false))->thenReturn(35); // label mocks \Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels))); \Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); @@ -1400,7 +1402,7 @@ LONG_STRING; \Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->filterFolders(null)))); foreach ($this->folders as $f) { \Phake::when(Arsse::$db)->folderList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterFolders($f['id'])))); - \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->folder($f['id']))->thenReturn($this->reduceFolders($f['id'])); + \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->hidden(false)->folder($f['id']))->thenReturn($this->reduceFolders($f['id'])); \Phake::when(Arsse::$db)->subscriptionList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterSubs($f['id'])))); } $exp = [ @@ -1694,208 +1696,101 @@ LONG_STRING; $this->assertMessage($this->respGood([$exp[0]]), $this->req($in[5])); } - public function testRetrieveCompactHeadlines(): void { - $in1 = [ - // erroneous input - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"], - // empty results - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true], // is_cat is not used in getCompactHeadlines - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "published"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "unread"], - // non-empty results - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "adaptive"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "adaptive"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "unread"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "marked"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "has_note"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'skip' => 2], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5, 'skip' => 2], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'since_id' => 47], - ]; - $in2 = [ - // time-based contexts, handled separately - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "adaptive"], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3], - ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"], - ]; - \Phake::when(Arsse::$db)->articleList->thenReturn(new Result($this->v([['id' => 0]]))); - \Phake::when(Arsse::$db)->articleCount->thenReturn(0); - \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1); - $c = (new Context); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), ["id"], ["edited_date desc"])->thenThrow(new ExceptionInput("subjectMissing")); - \Phake::when(Arsse::$db)->articleList($this->anything(), $c, ["id"], ["edited_date desc"])->thenReturn(new Result($this->v($this->articles))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), ["id"], ["marked_date desc"])->thenReturn(new Result($this->v([['id' => 1]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 2]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 3]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 4]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 5]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 6]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 7]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 8]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 9]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 10]]))); - $out1 = [ - $this->respErr("INCORRECT_USAGE"), - $this->respGood([]), - $this->respGood([]), - $this->respGood([]), - $this->respGood([]), - $this->respGood([]), - $this->respGood([]), - $this->respGood([['id' => 101],['id' => 102]]), - $this->respGood([['id' => 1]]), - $this->respGood([['id' => 2]]), - $this->respGood([['id' => 3]]), - $this->respGood([['id' => 2]]), // the result is 2 rather than 4 because there are no unread, so the unread context is not used - $this->respGood([['id' => 4]]), - $this->respGood([['id' => 5]]), - $this->respGood([['id' => 6]]), - $this->respGood([['id' => 7]]), - $this->respGood([['id' => 8]]), - $this->respGood([['id' => 9]]), - $this->respGood([['id' => 10]]), - ]; - $out2 = [ - $this->respGood([['id' => 1001]]), - $this->respGood([['id' => 1001]]), - $this->respGood([['id' => 1002]]), - $this->respGood([['id' => 1003]]), - ]; - for ($a = 0; $a < sizeof($in1); $a++) { - $this->assertMessage($out1[$a], $this->req($in1[$a]), "Test $a failed"); - } - for ($a = 0; $a < sizeof($in2); $a++) { - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(false)->markedSince(Date::sub("PT24H")), 2), ["id"], ["marked_date desc"])->thenReturn(new Result($this->v([['id' => 1001]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), 2), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 1002]]))); - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), 2), ["id"], ["edited_date desc"])->thenReturn(new Result($this->v([['id' => 1003]]))); - $this->assertMessage($out2[$a], $this->req($in2[$a]), "Test $a failed"); - } - } - - public function testRetrieveFullHeadlines(): void { - $in1 = [ - // empty results - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "published"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "unread"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "unread", 'search' => "unread:false"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'search' => "pub:true"], - ]; - $in2 = [ - // simple context tests - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "adaptive"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "adaptive"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "unread"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "marked"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "has_note"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'skip' => 2], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5, 'skip' => 2], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'since_id' => 47], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true, 'include_nested' => true], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'order_by' => "feed_dates"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'order_by' => "date_reverse"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'search' => "interesting"], - ]; - $in3 = [ - // time-based context tests - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "adaptive"], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3], - ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"], - ]; - \Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels))); + /** @dataProvider provideHeadlines */ + public function testRetrieveHeadlines(bool $full, array $in, $out, Context $c, array $fields, array $order, ResponseInterface $exp): void { + $base = ['op' => $full ? "getHeadlines" : "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"]; + $in = array_merge($base, $in); + $this->h = \Phake::partialMock(API::class); + \Phake::when($this->h)->now->thenReturn(Date::normalize(self::NOW)); + \Phake::when(Arsse::$db)->labelList->thenReturn(new Result($this->v($this->labels))); \Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); \Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]); \Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3])); \Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]); \Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]); - \Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(0)); - \Phake::when(Arsse::$db)->articleCount->thenReturn(0); - \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1); - $c = (new Context)->limit(200); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), $this->anything(), ["edited_date desc"])->thenThrow(new ExceptionInput("subjectMissing")); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), $this->anything(), ["marked_date desc"])->thenReturn($this->generateHeadlines(1)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(2)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(3)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(4)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(5)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(6)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(7)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(8)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(9)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(10)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(11)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->labelled(true), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(12)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folderShallow(0), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(13)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folderShallow(42), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(14)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folder(42), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(15)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c), $this->anything(), ["edited_date"])->thenReturn($this->generateHeadlines(16)); - \Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->searchTerms(["interesting"]), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(17)); - $out2 = [ - $this->respErr("INCORRECT_USAGE"), - $this->outputHeadlines(11), - $this->outputHeadlines(1), - $this->outputHeadlines(2), - $this->outputHeadlines(3), - $this->outputHeadlines(2), // the result is 2 rather than 4 because there are no unread, so the unread context is not used - $this->outputHeadlines(4), - $this->outputHeadlines(5), - $this->outputHeadlines(6), - $this->outputHeadlines(7), - $this->outputHeadlines(8), - $this->outputHeadlines(9), - $this->outputHeadlines(10), - $this->outputHeadlines(11), - $this->outputHeadlines(11), - $this->outputHeadlines(12), - $this->outputHeadlines(13), - $this->outputHeadlines(14), - $this->outputHeadlines(15), - $this->outputHeadlines(11), // defaulting sorting is not fully implemented - $this->outputHeadlines(16), - $this->outputHeadlines(17), - ]; - $out3 = [ - $this->outputHeadlines(1001), - $this->outputHeadlines(1001), - $this->outputHeadlines(1002), - $this->outputHeadlines(1003), - ]; - for ($a = 0; $a < sizeof($in1); $a++) { - $this->assertMessage($this->respGood([]), $this->req($in1[$a]), "Test $a failed"); - } - for ($a = 0; $a < sizeof($in2); $a++) { - $this->assertMessage($out2[$a], $this->req($in2[$a]), "Test $a failed"); + \Phake::when(Arsse::$db)->articleCount->thenReturn(2); + if ($out instanceof \Exception) { + \Phake::when(Arsse::$db)->articleList->thenThrow($out); + } else { + \Phake::when(Arsse::$db)->articleList->thenReturn($out); } - for ($a = 0; $a < sizeof($in3); $a++) { - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(false)->markedSince(Date::sub("PT24H")), 2), $this->anything(), ["marked_date desc"])->thenReturn($this->generateHeadlines(1001)); - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), 2), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(1002)); - \Phake::when(Arsse::$db)->articleList($this->anything(), $this->equalTo((clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), 2), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(1003)); - $this->assertMessage($out3[$a], $this->req($in3[$a]), "Test $a failed"); + $this->assertMessage($exp, $this->req($in)); + if ($out) { + \Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, $c, $fields, $order); + } else { + \Phake::verify(Arsse::$db, \Phake::times(0))->articleList; } } + public function provideHeadlines(): iterable { + $t = Date::normalize(self::NOW); + $c = (new Context)->hidden(false)->limit(200); + $out = $this->generateHeadlines(47); + $gone = new ExceptionInput("idMissing"); + $comp = new Result($this->v([['id' => 47], ['id' => 2112]])); + $expFull = $this->outputHeadlines(47); + $expComp = $this->respGood([['id' => 47], ['id' => 2112]]); + $fields = ["id", "guid", "title", "author", "url", "unread", "starred", "edited_date", "published_date", "subscription", "subscription_title", "note"]; + $sort = ["edited_date desc"]; + return [ + [true, [], null, $c, [], [], $this->respErr("INCORRECT_USAGE")], + [true, ['feed_id' => 0], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => -1], $out, (clone $c)->starred(true), $fields, ["marked_date desc"], $expFull], + [true, ['feed_id' => -2], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => -4], $out, $c, $fields, $sort, $expFull], + [true, ['feed_id' => 2112], $gone, (clone $c)->subscription(2112), $fields, $sort, $this->respGood([])], + [true, ['feed_id' => -2112], $out, (clone $c)->label(1088), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'view_mode' => "adaptive"], $out, (clone $c)->unread(true), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'view_mode' => "published"], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => -2112, 'view_mode' => "adaptive"], $out, (clone $c)->label(1088)->unread(true), $fields, $sort, $expFull], + [true, ['feed_id' => -2112, 'view_mode' => "unread"], $out, (clone $c)->label(1088)->unread(true), $fields, $sort, $expFull], + [true, ['feed_id' => 42, 'view_mode' => "marked"], $out, (clone $c)->subscription(42)->starred(true), $fields, $sort, $expFull], + [true, ['feed_id' => 42, 'view_mode' => "has_note"], $out, (clone $c)->subscription(42)->annotated(true), $fields, $sort, $expFull], + [true, ['feed_id' => 42, 'view_mode' => "unread", 'search' => "unread:false"], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => 42, 'search' => "pub:true"], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => -4, 'limit' => 5], $out, (clone $c)->limit(5), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'skip' => 2], $out, (clone $c)->offset(2), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'limit' => 5, 'skip' => 2], $out, (clone $c)->limit(5)->offset(2), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'since_id' => 47], $out, (clone $c)->oldestArticle(48), $fields, $sort, $expFull], + [true, ['feed_id' => -3, 'is_cat' => true], $out, $c, $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'is_cat' => true], $out, $c, $fields, $sort, $expFull], + [true, ['feed_id' => -2, 'is_cat' => true], $out, (clone $c)->labelled(true), $fields, $sort, $expFull], + [true, ['feed_id' => -1, 'is_cat' => true], null, $c, [], [], $this->respGood([])], + [true, ['feed_id' => 0, 'is_cat' => true], $out, (clone $c)->folderShallow(0), $fields, $sort, $expFull], + [true, ['feed_id' => 0, 'is_cat' => true, 'include_nested' => true], $out, (clone $c)->folderShallow(0), $fields, $sort, $expFull], + [true, ['feed_id' => 42, 'is_cat' => true], $out, (clone $c)->folderShallow(42), $fields, $sort, $expFull], + [true, ['feed_id' => 42, 'is_cat' => true, 'include_nested' => true], $out, (clone $c)->folder(42), $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'order_by' => "feed_dates"], $out, $c, $fields, $sort, $expFull], + [true, ['feed_id' => -4, 'order_by' => "date_reverse"], $out, $c, $fields, ["edited_date"], $expFull], + [true, ['feed_id' => 42, 'search' => "interesting"], $out, (clone $c)->subscription(42)->searchTerms(["interesting"]), $fields, $sort, $expFull], + [true, ['feed_id' => -6], $out, (clone $c)->unread(false)->markedSince(Date::sub("PT24H", $t)), $fields, ["marked_date desc"], $expFull], + [true, ['feed_id' => -6, 'view_mode' => "unread"], null, $c, $fields, $sort, $this->respGood([])], + [true, ['feed_id' => -3], $out, (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H", $t)), $fields, $sort, $expFull], + [true, ['feed_id' => -3, 'view_mode' => "marked"], $out, (clone $c)->unread(true)->starred(true)->modifiedSince(Date::sub("PT24H", $t)), $fields, $sort, $expFull], + [false, [], null, (clone $c)->limit(null), [], [], $this->respErr("INCORRECT_USAGE")], + [false, ['feed_id' => 0], null, (clone $c)->limit(null), [], [], $this->respGood([])], + [false, ['feed_id' => -1], $comp, (clone $c)->limit(null)->starred(true), ["id"], ["marked_date desc"], $expComp], + [false, ['feed_id' => -2], null, (clone $c)->limit(null), [], [], $this->respGood([])], + [false, ['feed_id' => -4], $comp, (clone $c)->limit(null), ["id"], $sort, $expComp], + [false, ['feed_id' => 2112], $gone, (clone $c)->limit(null)->subscription(2112), ["id"], $sort, $this->respGood([])], + [false, ['feed_id' => -2112], $comp, (clone $c)->limit(null)->label(1088), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'view_mode' => "adaptive"], $comp, (clone $c)->limit(null)->unread(true), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'view_mode' => "published"], null, (clone $c)->limit(null), [], [], $this->respGood([])], + [false, ['feed_id' => -2112, 'view_mode' => "adaptive"], $comp, (clone $c)->limit(null)->label(1088)->unread(true), ["id"], $sort, $expComp], + [false, ['feed_id' => -2112, 'view_mode' => "unread"], $comp, (clone $c)->limit(null)->label(1088)->unread(true), ["id"], $sort, $expComp], + [false, ['feed_id' => 42, 'view_mode' => "marked"], $comp, (clone $c)->limit(null)->subscription(42)->starred(true), ["id"], $sort, $expComp], + [false, ['feed_id' => 42, 'view_mode' => "has_note"], $comp, (clone $c)->limit(null)->subscription(42)->annotated(true), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'limit' => 5], $comp, (clone $c)->limit(5), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'skip' => 2], $comp, (clone $c)->limit(null)->offset(2), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'limit' => 5, 'skip' => 2], $comp, (clone $c)->limit(5)->offset(2), ["id"], $sort, $expComp], + [false, ['feed_id' => -4, 'since_id' => 47], $comp, (clone $c)->limit(null)->oldestArticle(48), ["id"], $sort, $expComp], + [false, ['feed_id' => -6], $comp, (clone $c)->limit(null)->unread(false)->markedSince(Date::sub("PT24H", $t)), ["id"], ["marked_date desc"], $expComp], + [false, ['feed_id' => -6, 'view_mode' => "unread"], null, (clone $c)->limit(null), ["id"], $sort, $this->respGood([])], + [false, ['feed_id' => -3], $comp, (clone $c)->limit(null)->unread(true)->modifiedSince(Date::sub("PT24H", $t)), ["id"], $sort, $expComp], + [false, ['feed_id' => -3, 'view_mode' => "marked"], $comp, (clone $c)->limit(null)->unread(true)->starred(true)->modifiedSince(Date::sub("PT24H", $t)), ["id"], $sort, $expComp], + ]; + } + public function testRetrieveFullHeadlinesCheckingExtraFields(): void { $in = [ // empty results @@ -1919,7 +1814,7 @@ LONG_STRING; \Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]); \Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(1)); \Phake::when(Arsse::$db)->articleCount->thenReturn(0); - \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1); + \Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->hidden(false))->thenReturn(1); // sanity check; this makes sure extra fields are not included in default situations $test = $this->req($in[0]); $this->assertMessage($this->outputHeadlines(1), $test); @@ -1970,7 +1865,7 @@ LONG_STRING; ]); $this->assertMessage($exp, $test); // test 'include_header' with an erroneous result - \Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(200)->subscription(2112), $this->anything(), ["edited_date desc"])->thenThrow(new ExceptionInput("subjectMissing")); + \Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(200)->subscription(2112)->hidden(false), $this->anything(), ["edited_date desc"])->thenThrow(new ExceptionInput("subjectMissing")); $test = $this->req($in[6]); $exp = $this->respGood([ ['id' => 2112, 'is_cat' => false, 'first_id' => 0], @@ -1985,7 +1880,7 @@ LONG_STRING; ]); $this->assertMessage($exp, $test); // test 'include_header' with skip - \Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(1)->subscription(42), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(1867)); + \Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(1)->subscription(42)->hidden(false), $this->anything(), ["edited_date desc"])->thenReturn($this->generateHeadlines(1867)); $test = $this->req($in[8]); $exp = $this->respGood([ ['id' => 42, 'is_cat' => false, 'first_id' => 1867],