Browse Source

Implement TT-RSS API level 15

rpm
J. King 3 years ago
parent
commit
211cea648e
  1. 16
      lib/REST/TinyTinyRSS/API.php
  2. 87
      tests/cases/REST/TinyTinyRSS/TestAPI.php

16
lib/REST/TinyTinyRSS/API.php

@ -24,7 +24,7 @@ use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public const LEVEL = 14; // emulated API level
public const LEVEL = 15; // 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
@ -79,7 +79,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
'include_header' => ValueInfo::T_BOOL | ValueInfo::M_DROP, // whether to attach a header to the results of `getHeadlines`
'search' => ValueInfo::T_STRING, // search string for `getHeadlines`
'field' => ValueInfo::T_INT, // which state to change in `updateArticle`
'mode' => ValueInfo::T_INT, // whether to set, clear, or toggle the selected state in `updateArticle`
'mode' => ValueInfo::T_MIXED, // whether to set, clear, or toggle the selected state in `updateArticle` (integer), or whether to ignore a certain recent timeframe in `catchupFeed` (string)
'data' => ValueInfo::T_STRING, // note text in `updateArticle` if setting a note
];
protected const VIEW_MODES = ["all_articles", "adaptive", "unread", "marked", "has_note", "published"];
@ -1037,6 +1037,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function opCatchUpFeed(array $data): array {
$id = $data['feed_id'] ?? self::FEED_ARCHIVED;
$cat = $data['is_cat'] ?? false;
$mode = $data['mode'] ?? "all";
$out = ['status' => "OK"];
// first prepare the context; unsupported contexts simply return early
$c = (new Context)->hidden(false);
@ -1089,6 +1090,16 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
}
}
}
switch ($mode) {
case "2week":
$c->notModifiedSince(Date::sub("P2W", $this->now()));
break;
case "1week":
$c->notModifiedSince(Date::sub("P1W", $this->now()));
break;
case "1day":
$c->notModifiedSince(Date::sub("PT24H", $this->now()));
}
// perform the marking
try {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
@ -1102,6 +1113,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function opUpdateArticle(array $data): array {
// normalize input
$articles = array_filter(ValueInfo::normalize(explode(",", (string) $data['article_ids']), ValueInfo::T_INT | ValueInfo::M_ARRAY), [ValueInfo::class, "id"]);
$data['mode'] = ValueInfo::normalize($data['mode'], ValueInfo::T_INT);
if (!$articles) {
// if there are no valid articles this is an error
throw new Exception("INCORRECT_USAGE");

87
tests/cases/REST/TinyTinyRSS/TestAPI.php

@ -19,8 +19,8 @@ use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
protected const NOW = "2020-12-21T23:09:17.189065Z";
@ -1309,55 +1309,46 @@ LONG_STRING;
$this->assertMessage($this->respGood($exp), $this->req($in[1]));
}
public function testMarkFeedsAsRead(): void {
$in1 = [
// no-ops
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx"],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'is_cat' => true],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'is_cat' => true],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'is_cat' => true],
];
$in2 = [
// simple contexts
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true],
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true],
];
$in3 = [
// this one has a tricky time-based context
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
];
/** @dataProvider provideMassMarkings */
public function testMarkFeedsAsRead(array $in, ?Context $c): void {
$base = ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx"];
$in = array_merge($base, $in);
\Phake::when(Arsse::$db)->articleMark->thenThrow(new ExceptionInput("typeViolation"));
$exp = $this->respGood(['status' => "OK"]);
// verify the above are in fact no-ops
for ($a = 0; $a < sizeof($in1); $a++) {
$this->assertMessage($exp, $this->req($in1[$a]), "Test $a failed");
}
\Phake::verify(Arsse::$db, \Phake::times(0))->articleMark;
// verify the simple contexts
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)->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");
// create a mock-current time
\Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW));
// TT-RSS always responds the same regardless of success or failure
$this->assertMessage($this->respGood(['status' => "OK"]), $this->req($in));
if (isset($c)) {
\Phake::verify(Arsse::$db)->articleMark(Arsse::$user->id, ['read' => true], $c);
} else {
\Phake::verify(Arsse::$db, \Phake::times(0))->articleMark;
}
\Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], $this->equalTo((new Context)->hidden(false)->modifiedSince($t), 2)); // within two seconds
}
public function provideMassMarkings(): iterable {
$c = (new Context)->hidden(false);
return [
[[], null],
[['feed_id' => 0], null],
[['feed_id' => 0, 'is_cat' => true], (clone $c)->folderShallow(0)],
[['feed_id' => 0, 'is_cat' => true, 'mode' => "bogus"], (clone $c)->folderShallow(0)],
[['feed_id' => -1], (clone $c)->starred(true)],
[['feed_id' => -1, 'is_cat' => true], 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, 'is_cat' => true], null],
[['feed_id' => -2], null],
[['feed_id' => -2, 'is_cat' => true], (clone $c)->labelled(true)],
[['feed_id' => -2, 'is_cat' => true, 'mode' => "all"], (clone $c)->labelled(true)],
[['feed_id' => -4], $c],
[['feed_id' => -4, 'is_cat' => true], null],
[['feed_id' => -6], 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' => 2112], (clone $c)->subscription(2112)],
[['feed_id' => 2112, 'mode' => "2week"], (clone $c)->subscription(2112)->notModifiedSince(Date::sub("P2W", self::NOW))],
];
}
public function testRetrieveFeedList(): void {

Loading…
Cancel
Save