diff --git a/lib/Context/ExclusionMethods.php b/lib/Context/ExclusionMethods.php index 7b7ecc4a..917326ed 100644 --- a/lib/Context/ExclusionMethods.php +++ b/lib/Context/ExclusionMethods.php @@ -164,39 +164,39 @@ trait ExclusionMethods { return $this->act(__FUNCTION__, func_num_args(), $spec); } - public function latestArticle(int $spec = null) { + public function articleRange(?int $start = null, ?int $end = null) { + if ($start === null && $end === null) { + $spec = null; + } else { + $spec = [$start, $end]; + } return $this->act(__FUNCTION__, func_num_args(), $spec); } - public function oldestArticle(int $spec = null) { + public function editionRange(?int $start = null, ?int $end = null) { + if ($start === null && $end === null) { + $spec = null; + } else { + $spec = [$start, $end]; + } return $this->act(__FUNCTION__, func_num_args(), $spec); } - public function latestEdition(int $spec = null) { + public function modifiedRange($start = null, $end = null) { + if ($start === null && $end === null) { + $spec = null; + } else { + $spec = [Date::normalize($start), Date::normalize($end)]; + } return $this->act(__FUNCTION__, func_num_args(), $spec); } - public function oldestEdition(int $spec = null) { - return $this->act(__FUNCTION__, func_num_args(), $spec); - } - - public function modifiedSince($spec = null) { - $spec = Date::normalize($spec); - return $this->act(__FUNCTION__, func_num_args(), $spec); - } - - public function notModifiedSince($spec = null) { - $spec = Date::normalize($spec); - return $this->act(__FUNCTION__, func_num_args(), $spec); - } - - public function markedSince($spec = null) { - $spec = Date::normalize($spec); - return $this->act(__FUNCTION__, func_num_args(), $spec); - } - - public function notMarkedSince($spec = null) { - $spec = Date::normalize($spec); + public function markedRange($start = null, $end = null) { + if ($start === null && $end === null) { + $spec = null; + } else { + $spec = [Date::normalize($start), Date::normalize($end)]; + } return $this->act(__FUNCTION__, func_num_args(), $spec); } } diff --git a/lib/Context/ExclusionProperties.php b/lib/Context/ExclusionProperties.php index 426adb27..8b0b63b8 100644 --- a/lib/Context/ExclusionProperties.php +++ b/lib/Context/ExclusionProperties.php @@ -29,12 +29,8 @@ trait ExclusionProperties { public $searchTerms = null; public $titleTerms = null; public $authorTerms = null; - public $oldestArticle = null; - public $latestArticle = null; - public $oldestEdition = null; - public $latestEdition = null; - public $modifiedSince = null; - public $notModifiedSince = null; - public $markedSince = null; - public $notMarkedSince = null; + public $articleRange = [null, null]; + public $editionRange = [null, null]; + public $modifiedRange = [null, null]; + public $markedRange = [null, null]; } diff --git a/lib/Database.php b/lib/Database.php index f3320ce2..6f63395d 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -1556,31 +1556,30 @@ class Database { $q->setLimit($context->limit, $context->offset); // handle the simple context options $options = [ - // each context array consists of a column identifier (see $colDefs above), a comparison operator, a data type, and an option to pair with for BETWEEN evaluation - "edition" => ["edition", "=", "int", ""], - "editions" => ["edition", "in", "int", ""], - "article" => ["id", "=", "int", ""], - "articles" => ["id", "in", "int", ""], - "oldestArticle" => ["id", ">=", "int", "latestArticle"], - "latestArticle" => ["id", "<=", "int", "oldestArticle"], - "oldestEdition" => ["edition", ">=", "int", "latestEdition"], - "latestEdition" => ["edition", "<=", "int", "oldestEdition"], - "modifiedSince" => ["modified_date", ">=", "datetime", "notModifiedSince"], - "notModifiedSince" => ["modified_date", "<=", "datetime", "modifiedSince"], - "markedSince" => ["marked_date", ">=", "datetime", "notMarkedSince"], - "notMarkedSince" => ["marked_date", "<=", "datetime", "markedSince"], - "folderShallow" => ["folder", "=", "int", ""], - "foldersShallow" => ["folder", "in", "int", ""], - "subscription" => ["subscription", "=", "int", ""], - "subscriptions" => ["subscription", "in", "int", ""], - "unread" => ["unread", "=", "bool", ""], - "starred" => ["starred", "=", "bool", ""], - "hidden" => ["hidden", "=", "bool", ""], + // each context array consists of a column identifier (see $colDefs above), a comparison operator, and a data type; the "between" operator has special handling + "edition" => ["edition", "=", "int"], + "editions" => ["edition", "in", "int"], + "article" => ["id", "=", "int"], + "articles" => ["id", "in", "int"], + "articleRange" => ["id", "between", "int"], + "editionRange" => ["edition", "between", "int"], + "modifiedRange" => ["modified_date", "between", "datetime"], + "markedRange" => ["marked_date", "between", "datetime"], + "folderShallow" => ["folder", "=", "int"], + "foldersShallow" => ["folder", "in", "int"], + "subscription" => ["subscription", "=", "int"], + "subscriptions" => ["subscription", "in", "int"], + "unread" => ["unread", "=", "bool"], + "starred" => ["starred", "=", "bool"], + "hidden" => ["hidden", "=", "bool"], ]; - foreach ($options as $m => [$col, $op, $type, $pair]) { + foreach ($options as $m => [$col, $op, $type]) { if (!$context->$m()) { // context is not being used continue; + } elseif ($op === "between") { + // option is a range + $q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], $context->$m); } elseif (is_array($context->$m)) { // context option is an array of values if (!$context->$m) { @@ -1588,23 +1587,18 @@ class Database { } [$clause, $types, $values] = $this->generateIn($context->$m, $type); $q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values); - } elseif ($pair && $context->$pair()) { - // option is paired with another which is also being used - if ($op === ">=") { - $q->setWhere("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], [$context->$m, $context->$pair]); - } else { - // option has already been paired - continue; - } } else { $q->setWhere("{$colDefs[$col]} $op ?", $type, $context->$m); } } // further handle exclusionary options if specified - foreach ($options as $m => [$col, $op, $type, $pair]) { + foreach ($options as $m => [$col, $op, $type]) { if (!method_exists($context->not, $m) || !$context->not->$m()) { // context option is not being used continue; + } elseif ($op === "between") { + // option is a range + $q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], $context->not->$m); } elseif (is_array($context->not->$m)) { if (!$context->not->$m) { // for exclusions we don't care if the array is empty @@ -1612,14 +1606,6 @@ class Database { } [$clause, $types, $values] = $this->generateIn($context->not->$m, $type); $q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values); - } elseif ($pair && $context->not->$pair()) { - // option is paired with another which is also being used - if ($op === ">=") { - $q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], [$context->not->$m, $context->not->$pair]); - } else { - // option has already been paired - continue; - } } else { $q->setWhereNot("{$colDefs[$col]} $op ?", $type, $context->not->$m); } diff --git a/lib/REST/Fever/API.php b/lib/REST/Fever/API.php index 20e6c356..c581d881 100644 --- a/lib/REST/Fever/API.php +++ b/lib/REST/Fever/API.php @@ -244,7 +244,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $c = new Context; $id = $P['id']; if ($P['before']) { - $c->notMarkedSince($P['before']); + $c->markedRange(null, $P['before']); } switch ($P['mark']) { case "item": @@ -310,7 +310,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $c = (new Context)->hidden(false); $lastUnread = Date::normalize($lastUnread, "sql"); $since = Date::sub("PT15S", $lastUnread); - $c->unread(false)->markedSince($since); + $c->unread(false)->markedRange($since, null); Arsse::$db->articleMark(Arsse::$user->id, ['read' => false], $c); } diff --git a/lib/REST/Miniflux/V1.php b/lib/REST/Miniflux/V1.php index 7cba4061..ca8535c6 100644 --- a/lib/REST/Miniflux/V1.php +++ b/lib/REST/Miniflux/V1.php @@ -893,8 +893,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { ->limit($query['limit'] ?? self::DEFAULT_ENTRY_LIMIT) // NOTE: This does not honour user preferences ->offset($query['offset']) ->starred($query['starred']) - ->modifiedSince($query['after']) // FIXME: This may not be the correct date field - ->notModifiedSince($query['before']) + ->modifiedRange($query['after'], $query['before']) // FIXME: This may not be the correct date field ->oldestArticle($query['after_entry_id'] ? $query['after_entry_id'] + 1 : null) // FIXME: This might be edition ->latestArticle($query['before_entry_id'] ? $query['before_entry_id'] - 1 : null) ->searchTerms(strlen($query['search'] ?? "") ? preg_split("/\s+/", $query['search']) : null); // NOTE: Miniflux matches only whole words; we match simple substrings diff --git a/lib/REST/NextcloudNews/V1_2.php b/lib/REST/NextcloudNews/V1_2.php index 111cf2f0..21bc6fb0 100644 --- a/lib/REST/NextcloudNews/V1_2.php +++ b/lib/REST/NextcloudNews/V1_2.php @@ -556,7 +556,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } // whether to return only updated items if ($data['lastModified']) { - $c->markedSince($data['lastModified']); + $c->markedRange($data['lastModified'], null); } // perform the fetch try { diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php index 74f315a5..757d476b 100644 --- a/lib/REST/TinyTinyRSS/API.php +++ b/lib/REST/TinyTinyRSS/API.php @@ -256,7 +256,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", $this->now()))->hidden(false)); + $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false)); $countAll = 0; $countSubs = 0; $feeds = []; @@ -361,7 +361,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", $this->now()))->hidden(false)), + 'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false)), ], $tSpecial), array_merge([ // Starred articles 'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"), @@ -545,7 +545,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", $this->now()))->hidden(false)); // fresh + $cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->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); @@ -697,7 +697,7 @@ 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", $this->now()))->hidden(false)); + $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->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 @@ -1096,7 +1096,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", $this->now())); + $c->modifiedRange(Date::sub("PT24H", $this->now()), null); break; case self::FEED_ALL: // no context needed here @@ -1112,13 +1112,13 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { } switch ($mode) { case "2week": - $c->notModifiedSince(Date::sub("P2W", $this->now())); + $c->modifiedRange($c->modifiedRange[0], Date::sub("P2W", $this->now())); break; case "1week": - $c->notModifiedSince(Date::sub("P1W", $this->now())); + $c->modifiedRange($c->modifiedRange[0], Date::sub("P1W", $this->now())); break; case "1day": - $c->notModifiedSince(Date::sub("PT24H", $this->now())); + $c->modifiedRange($c->modifiedRange[0], Date::sub("PT24H", $this->now())); } // perform the marking try { @@ -1473,13 +1473,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", $this->now()))->unread(true); + $c->modifiedRange(Date::sub("PT24H", $this->now()), null)->unread(true); break; case self::FEED_ALL: // no context needed here break; case self::FEED_READ: - $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 + $c->markedRange(Date::sub("PT24H", $this->now()), null)->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 diff --git a/lib/REST/TinyTinyRSS/Search.php b/lib/REST/TinyTinyRSS/Search.php index ea3dbe65..966ea205 100644 --- a/lib/REST/TinyTinyRSS/Search.php +++ b/lib/REST/TinyTinyRSS/Search.php @@ -320,16 +320,15 @@ class Search { $end = $day."T23:59:59+00:00"; // if a date is already set, the same date is a no-op; anything else is a contradiction $cc = $neg ? $c->not : $c; - if ($cc->modifiedSince() || $cc->notModifiedSince()) { - if (!$cc->modifiedSince() || !$cc->notModifiedSince() || $cc->modifiedSince->format("c") !== $start || $cc->notModifiedSince->format("c") !== $end) { + if ($cc->modifiedRange()) { + if (!$cc->modifiedRange[0] || !$cc->modifiedRange[1] || $cc->modifiedRange[0]->format("c") !== $start || $cc->modifiedRange[1]->format("c") !== $end) { // FIXME: multiple negative dates should be allowed, but the design of the Context class does not support this throw new Exception; } else { return $c; } } - $cc->modifiedSince($start); - $cc->notModifiedSince($end); + $cc->modifiedRange($start, $end); return $c; } diff --git a/tests/cases/Database/SeriesArticle.php b/tests/cases/Database/SeriesArticle.php index eace73af..82bcef82 100644 --- a/tests/cases/Database/SeriesArticle.php +++ b/tests/cases/Database/SeriesArticle.php @@ -462,16 +462,16 @@ trait SeriesArticle { 'Not before edition 1001' => [(new Context)->subscription(5)->oldestEdition(1001), [20]], 'Not after article 3' => [(new Context)->latestArticle(3), [1,2,3]], 'Not before article 19' => [(new Context)->oldestArticle(19), [19,20]], - 'Modified by author since 2005' => [(new Context)->modifiedSince("2005-01-01T00:00:00Z"), [2,4,6,8,20]], - 'Modified by author since 2010' => [(new Context)->modifiedSince("2010-01-01T00:00:00Z"), [2,4,6,8,20]], - 'Not modified by author since 2005' => [(new Context)->notModifiedSince("2005-01-01T00:00:00Z"), [1,3,5,7,19]], - 'Not modified by author since 2000' => [(new Context)->notModifiedSince("2000-01-01T00:00:00Z"), [1,3,5,7,19]], - 'Marked or labelled since 2014' => [(new Context)->markedSince("2014-01-01T00:00:00Z"), [8,19]], - 'Marked or labelled since 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z"), [2,4,6,8,19,20]], - 'Not marked or labelled since 2014' => [(new Context)->notMarkedSince("2014-01-01T00:00:00Z"), [1,2,3,4,5,6,7,20]], - 'Not marked or labelled since 2005' => [(new Context)->notMarkedSince("2005-01-01T00:00:00Z"), [1,3,5,7]], - 'Marked or labelled between 2000 and 2015' => [(new Context)->markedSince("2000-01-01T00:00:00Z")->notMarkedSince("2015-12-31T23:59:59Z"), [1,2,3,4,5,6,7,8,20]], - 'Marked or labelled in 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z")->notMarkedSince("2010-12-31T23:59:59Z"), [2,4,6,20]], + 'Modified by author since 2005' => [(new Context)->modifiedRange("2005-01-01T00:00:00Z", null), [2,4,6,8,20]], + 'Modified by author since 2010' => [(new Context)->modifiedRange("2010-01-01T00:00:00Z", null), [2,4,6,8,20]], + 'Not modified by author since 2005' => [(new Context)->modifiedRange(null, "2005-01-01T00:00:00Z"), [1,3,5,7,19]], + 'Not modified by author since 2000' => [(new Context)->modifiedRange(null, "2000-01-01T00:00:00Z"), [1,3,5,7,19]], + 'Marked or labelled since 2014' => [(new Context)->markedRange("2014-01-01T00:00:00Z", null), [8,19]], + 'Marked or labelled since 2010' => [(new Context)->markedRange("2010-01-01T00:00:00Z", null), [2,4,6,8,19,20]], + 'Not marked or labelled since 2014' => [(new Context)->markedRange(null, "2014-01-01T00:00:00Z"), [1,2,3,4,5,6,7,20]], + 'Not marked or labelled since 2005' => [(new Context)->markedRange(null, "2005-01-01T00:00:00Z"), [1,3,5,7]], + 'Marked or labelled between 2000 and 2015' => [(new Context)->markedRange("2000-01-01T00:00:00Z", "2015-12-31T23:59:59Z"), [1,2,3,4,5,6,7,8,20]], + 'Marked or labelled in 2010' => [(new Context)->markedRange("2010-01-01T00:00:00Z", "2010-12-31T23:59:59Z"), [2,4,6,20]], 'Paged results' => [(new Context)->limit(2)->oldestEdition(4), [4,5]], 'With label ID 1' => [(new Context)->label(1), [1,19]], 'With label ID 2' => [(new Context)->label(2), [1,5,20]], @@ -505,7 +505,7 @@ trait SeriesArticle { 'Folder tree 1 excluding subscription 4' => [(new Context)->not->subscription(4)->folder(1), [5,6]], 'Folder tree 1 excluding articles 7 and 8' => [(new Context)->folder(1)->not->articles([7,8]), [5,6]], 'Folder tree 1 excluding no articles' => [(new Context)->folder(1)->not->articles([]), [5,6,7,8]], - 'Marked or labelled between 2000 and 2015 excluding in 2010' => [(new Context)->markedSince("2000-01-01T00:00:00Z")->notMarkedSince("2015-12-31T23:59:59")->not->markedSince("2010-01-01T00:00:00Z")->not->notMarkedSince("2010-12-31T23:59:59Z"), [1,3,5,7,8]], + 'Marked or labelled between 2000 and 2015 excluding in 2010' => [(new Context)->markedRange("2000-01-01T00:00:00Z", "2015-12-31T23:59:59")->not->markedRange("2010-01-01T00:00:00Z", "2010-12-31T23:59:59Z"), [1,3,5,7,8]], 'Search with exclusion' => [(new Context)->searchTerms(["Article"])->not->searchTerms(["one", "two"]), [3]], 'Excluded folder tree' => [(new Context)->not->folder(1), [1,2,3,4,19,20]], 'Excluding label ID 2' => [(new Context)->not->label(2), [2,3,4,6,7,8,19]], @@ -953,7 +953,7 @@ trait SeriesArticle { } public function testMarkByLastMarked(): void { - Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedSince('2017-01-01T00:00:00Z')); + Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedRange('2017-01-01T00:00:00Z', null)); $now = Date::transform(time(), "sql"); $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][8][3] = 1; @@ -964,7 +964,7 @@ trait SeriesArticle { } public function testMarkByNotLastMarked(): void { - Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->notMarkedSince('2000-01-01T00:00:00Z')); + Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedRange(null, '2000-01-01T00:00:00Z')); $now = Date::transform(time(), "sql"); $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][] = [13,5,0,1,$now,'',0]; diff --git a/tests/cases/Misc/TestContext.php b/tests/cases/Misc/TestContext.php index 46ecaaff..7e1d6af1 100644 --- a/tests/cases/Misc/TestContext.php +++ b/tests/cases/Misc/TestContext.php @@ -47,10 +47,6 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest { 'unread' => true, 'starred' => true, 'hidden' => true, - 'modifiedSince' => new \DateTime(), - 'notModifiedSince' => new \DateTime(), - 'markedSince' => new \DateTime(), - 'notMarkedSince' => new \DateTime(), 'editions' => [1,2], 'articles' => [1,2], 'label' => 2112, @@ -65,21 +61,17 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest { 'authorTerms' => ["foo", "bar"], 'not' => (new Context)->subscription(5), ]; - $times = ['modifiedSince','notModifiedSince','markedSince','notMarkedSince']; + $ranges = ['modifiedRange', 'markedRange', 'articleRange', 'editionRange']; $c = new Context; foreach ((new \ReflectionObject($c))->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { - if ($m->isStatic() || strpos($m->name, "__") === 0) { + if ($m->isStatic() || strpos($m->name, "__") === 0 || in_array($m->name, $ranges)) { continue; } $method = $m->name; $this->assertArrayHasKey($method, $v, "Context method $method not included in test"); $this->assertInstanceOf(Context::class, $c->$method($v[$method])); $this->assertTrue($c->$method()); - if (in_array($method, $times)) { - $this->assertTime($c->$method, $v[$method], "Context method $method did not return the expected results"); - } else { - $this->assertSame($c->$method, $v[$method], "Context method $method did not return the expected results"); - } + $this->assertSame($c->$method, $v[$method], "Context method $method did not return the expected results"); // clear the context option $c->$method(null); $this->assertFalse($c->$method()); diff --git a/tests/cases/REST/Fever/TestAPI.php b/tests/cases/REST/Fever/TestAPI.php index a9896c73..4dfaec8e 100644 --- a/tests/cases/REST/Fever/TestAPI.php +++ b/tests/cases/REST/Fever/TestAPI.php @@ -407,7 +407,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { ["mark=group&as=unread&id=-1", (new Context)->not->folder(0), $markUnread, $listUnread], ["mark=group&as=saved&id=-1", (new Context)->not->folder(0), $markSaved, $listSaved], ["mark=group&as=unsaved&id=-1", (new Context)->not->folder(0), $markUnsaved, $listSaved], - ["mark=group&as=read&id=-1&before=946684800", (new Context)->not->folder(0)->notMarkedSince("2000-01-01T00:00:00Z"), $markRead, $listUnread], + ["mark=group&as=read&id=-1&before=946684800", (new Context)->not->folder(0)->markedRange(null, "2000-01-01T00:00:00Z"), $markRead, $listUnread], ["mark=item&as=unread", new Context, [], []], ["mark=item&id=6", new Context, [], []], ["as=unread&id=6", new Context, [], []], @@ -462,7 +462,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleMark->returns(0); $exp = new JsonResponse($out); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); - $this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedSince("1999-12-31T23:59:45Z")->hidden(false))); + $this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedRange("1999-12-31T23:59:45Z", null)->hidden(false))); $this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([])); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); $this->dbMock->articleMark->once()->called(); // only called one time, above diff --git a/tests/cases/REST/Miniflux/TestV1.php b/tests/cases/REST/Miniflux/TestV1.php index f1dd8d33..a87daf6f 100644 --- a/tests/cases/REST/Miniflux/TestV1.php +++ b/tests/cases/REST/Miniflux/TestV1.php @@ -768,9 +768,10 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?after=0", (clone $c)->modifiedSince(0), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=0", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?before=1", (clone $c)->notModifiedSince(1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?after_entry_id=42", (clone $c)->oldestArticle(43), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before_entry_id=47", (clone $c)->latestArticle(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], diff --git a/tests/cases/REST/NextcloudNews/TestV1_2.php b/tests/cases/REST/NextcloudNews/TestV1_2.php index 9e980e99..b637b0fe 100644 --- a/tests/cases/REST/NextcloudNews/TestV1_2.php +++ b/tests/cases/REST/NextcloudNews/TestV1_2.php @@ -695,7 +695,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { ["/items", ['type' => 3, 'id' => 0], clone $c, $out, $r200], ["/items", ['getRead' => true], clone $c, $out, $r200], ["/items", ['getRead' => false], (clone $c)->unread(true), $out, $r200], - ["/items", ['lastModified' => $t->getTimestamp()], (clone $c)->markedSince($t), $out, $r200], + ["/items", ['lastModified' => $t->getTimestamp()], (clone $c)->markedRange($t, null), $out, $r200], ["/items", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200], ["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200], ["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200], @@ -708,7 +708,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { ["/items/updated", ['type' => 3, 'id' => 0], clone $c, $out, $r200], ["/items/updated", ['getRead' => true], clone $c, $out, $r200], ["/items/updated", ['getRead' => false], (clone $c)->unread(true), $out, $r200], - ["/items/updated", ['lastModified' => $t->getTimestamp()], (clone $c)->markedSince($t), $out, $r200], + ["/items/updated", ['lastModified' => $t->getTimestamp()], (clone $c)->markedRange($t, null), $out, $r200], ["/items/updated", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200], ["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200], ["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200], diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index 74a12b95..e9ed0e57 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -959,7 +959,7 @@ LONG_STRING; $this->dbMock->folderList->with("~", null, false)->returns(new Result($this->v($this->topFolders))); $this->dbMock->subscriptionList->returns(new Result($this->v($this->subscriptions))); $this->dbMock->labelList->returns(new Result($this->v($this->labels))); - $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW))))->returns(7); + $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null)))->returns(7); $this->dbMock->articleStarred->returns($this->v($this->starred)); $this->assertMessage($exp, $this->req($in)); } @@ -1060,7 +1060,7 @@ LONG_STRING; ['id' => -2, 'kind' => "cat", 'counter' => 6], ]; $this->assertMessage($this->respGood($exp), $this->req($in)); - $this->dbMock->articleCount->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW)))); + $this->dbMock->articleCount->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null))); } /** @dataProvider provideLabelListings */ @@ -1152,7 +1152,7 @@ LONG_STRING; $this->assertMessage($this->respGood($exp), $this->req($in[0])); $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' => '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]]]]]]; $this->assertMessage($this->respGood($exp), $this->req($in[1])); - $this->dbMock->articleCount->twice()->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW)))); + $this->dbMock->articleCount->twice()->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null))); } /** @dataProvider provideMassMarkings */ @@ -1180,8 +1180,8 @@ LONG_STRING; [['feed_id' => 0, 'is_cat' => true, 'mode' => "bogus"], (clone $c)->folderShallow(0)], [['feed_id' => -1], (clone $c)->starred(true)], [['feed_id' => -1, 'is_cat' => "t"], null], - [['feed_id' => -3], (clone $c)->modifiedSince(Date::sub("PT24H", self::NOW))], - [['feed_id' => -3, 'mode' => "1day"], (clone $c)->modifiedSince(Date::sub("PT24H", self::NOW))->notModifiedSince(Date::sub("PT24H", self::NOW))], // this is a nonsense query, but it's what TT-RSS appearsto do + [['feed_id' => -3], (clone $c)->modifiedRange(Date::sub("PT24H", self::NOW), null)], + [['feed_id' => -3, 'mode' => "1day"], (clone $c)->modifiedRange(Date::sub("PT24H", self::NOW), Date::sub("PT24H", self::NOW))], // this is a nonsense query, but it's what TT-RSS appearsto do [['feed_id' => -3, 'is_cat' => true], null], [['feed_id' => -2], null], [['feed_id' => -2, 'is_cat' => true], (clone $c)->labelled(true)], @@ -1191,9 +1191,9 @@ LONG_STRING; [['feed_id' => -6, 'is_cat' => "f"], null], [['feed_id' => -2112], (clone $c)->label(1088)], [['feed_id' => 42, 'is_cat' => true], (clone $c)->folder(42)], - [['feed_id' => 42, 'is_cat' => true, 'mode' => "1week"], (clone $c)->folder(42)->notModifiedSince(Date::sub("P1W", self::NOW))], + [['feed_id' => 42, 'is_cat' => true, 'mode' => "1week"], (clone $c)->folder(42)->modifiedRange(null, Date::sub("P1W", self::NOW))], [['feed_id' => 2112], (clone $c)->subscription(2112)], - [['feed_id' => 2112, 'mode' => "2week"], (clone $c)->subscription(2112)->notModifiedSince(Date::sub("P2W", self::NOW))], + [['feed_id' => 2112, 'mode' => "2week"], (clone $c)->subscription(2112)->modifiedRange(null, Date::sub("P2W", self::NOW))], ]; } @@ -1202,7 +1202,7 @@ LONG_STRING; $in = array_merge(['op' => "getFeeds", 'sid' => "PriestsOfSyrinx"], $in); // statistical mocks $this->dbMock->articleStarred->returns($this->v($this->starred)); - $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)->modifiedSince(Date::sub("PT24H", self::NOW))))->returns(7); + $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)->modifiedRange(Date::sub("PT24H", self::NOW), null)))->returns(7); $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(35); // label mocks $this->dbMock->labelList->returns(new Result($this->v($this->labels))); @@ -1521,61 +1521,61 @@ LONG_STRING; $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], + [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)->markedRange(Date::sub("PT24H", $t), null), $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)->modifiedRange(Date::sub("PT24H", $t), null), $fields, $sort, $expFull], + [true, ['feed_id' => -3, 'view_mode' => "marked"], $out, (clone $c)->unread(true)->starred(true)->modifiedRange(Date::sub("PT24H", $t), null), $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)->markedRange(Date::sub("PT24H", $t), null), ["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)->modifiedRange(Date::sub("PT24H", $t), null), ["id"], $sort, $expComp], + [false, ['feed_id' => -3, 'view_mode' => "marked"], $comp, (clone $c)->limit(null)->unread(true)->starred(true)->modifiedRange(Date::sub("PT24H", $t), null), ["id"], $sort, $expComp], ]; } diff --git a/tests/cases/REST/TinyTinyRSS/TestSearch.php b/tests/cases/REST/TinyTinyRSS/TestSearch.php index 6999b0d6..84ca2005 100644 --- a/tests/cases/REST/TinyTinyRSS/TestSearch.php +++ b/tests/cases/REST/TinyTinyRSS/TestSearch.php @@ -101,10 +101,10 @@ class TestSearch extends \JKingWeb\Arsse\Test\AbstractTest { 'Doubled boolean' => ['unread:true unread:true', (new Context)->unread(true)], 'Bare blank date' => ['@', new Context], 'Quoted blank date' => ['"@"', new Context], - 'Bare ISO date' => ['@2019-03-01', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], - 'Quoted ISO date' => ['"@March 1st, 2019"', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], - 'Bare negative ISO date' => ['-@2019-03-01', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], - 'Quoted negative English date' => ['"-@March 1st, 2019"', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], + 'Bare ISO date' => ['@2019-03-01', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], + 'Quoted ISO date' => ['"@March 1st, 2019"', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], + 'Bare negative ISO date' => ['-@2019-03-01', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], + 'Quoted negative English date' => ['"-@March 1st, 2019"', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], 'Invalid date' => ['@Bugaboo', new Context], 'Escaped quoted date 1' => ['"@""Yesterday" and today', (new Context)->searchTerms(["and", "today"])], 'Escaped quoted date 2' => ['"@\\"Yesterday" and today', (new Context)->searchTerms(["and", "today"])], @@ -112,8 +112,8 @@ class TestSearch extends \JKingWeb\Arsse\Test\AbstractTest { 'Escaped quoted date 4' => ['"@Yesterday\\and today', new Context], 'Escaped quoted date 5' => ['"@Yesterday"and today', (new Context)->searchTerms(["today"])], 'Contradictory dates' => ['@Yesterday @Today', null], - 'Doubled date' => ['"@March 1st, 2019" @2019-03-01', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], - 'Doubled negative date' => ['"-@March 1st, 2019" -@2019-03-01', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], + 'Doubled date' => ['"@March 1st, 2019" @2019-03-01', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], + 'Doubled negative date' => ['"-@March 1st, 2019" -@2019-03-01', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")], ]; }