mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Implement TTTRSS getCounters operation; fixes #79
This commit is contained in:
parent
c9c6891567
commit
20ff08a431
7 changed files with 243 additions and 93 deletions
|
@ -305,7 +305,8 @@ class Database {
|
|||
$q = new Query(
|
||||
"SELECT
|
||||
id,name,parent,
|
||||
(select count(*) from arsse_folders as parents where parents.parent is arsse_folders.id) as children
|
||||
(select count(*) from arsse_folders as parents where parents.parent is arsse_folders.id) as children,
|
||||
(select count(*) from arsse_subscriptions where folder is arsse_folders.id) as feeds
|
||||
FROM arsse_folders"
|
||||
);
|
||||
if (!$recursive) {
|
||||
|
@ -508,6 +509,7 @@ class Database {
|
|||
"SELECT
|
||||
arsse_subscriptions.id as id,
|
||||
feed,url,favicon,source,folder,pinned,err_count,err_msg,order_type,added,
|
||||
arsse_feeds.updated as updated,
|
||||
topmost.top as top_folder,
|
||||
coalesce(arsse_subscriptions.title, arsse_feeds.title) as title,
|
||||
(SELECT count(*) from arsse_articles where feed is arsse_subscriptions.feed) - (SELECT count(*) from arsse_marks where subscription is arsse_subscriptions.id and read is 1) as unread
|
||||
|
@ -1002,6 +1004,21 @@ class Database {
|
|||
return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue();
|
||||
}
|
||||
|
||||
public function articleStarred(string $user): array {
|
||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||
}
|
||||
return $this->db->prepare(
|
||||
"SELECT
|
||||
count(*) as total,
|
||||
coalesce(sum(not read),0) as unread,
|
||||
coalesce(sum(read),0) as read
|
||||
FROM (
|
||||
select read from arsse_marks where starred is 1 and subscription in (select id from arsse_subscriptions where owner is ?)
|
||||
)", "str"
|
||||
)->run($user)->getRow();
|
||||
}
|
||||
|
||||
public function articleCleanup(): bool {
|
||||
$query = $this->db->prepare(
|
||||
"WITH target_feed(id,subs) as (".
|
||||
|
|
|
@ -7,7 +7,6 @@ use JKingWeb\Arsse\Misc\Date;
|
|||
abstract class AbstractStatement implements Statement {
|
||||
protected $types = [];
|
||||
protected $isNullable = [];
|
||||
protected $values = ['pre' => [], 'post' => []];
|
||||
|
||||
abstract public function runArray(array $values = []): Result;
|
||||
|
||||
|
|
|
@ -395,7 +395,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
$out[] = $this->feedTranslate($sub);
|
||||
}
|
||||
$out = ['feeds' => $out];
|
||||
$out['starredCount'] = Arsse::$db->articleCount(Arsse::$user->id, (new Context)->starred(true));
|
||||
$out['starredCount'] = Arsse::$db->articleStarred(Arsse::$user->id)['total'];
|
||||
$newest = Arsse::$db->editionLatest(Arsse::$user->id);
|
||||
if ($newest) {
|
||||
$out['newestItemId'] = $newest;
|
||||
|
|
|
@ -17,13 +17,13 @@ use JKingWeb\Arsse\REST\Response;
|
|||
/*
|
||||
|
||||
Protocol difference so far:
|
||||
- handling of incorrect Content-Type and/or HTTP method is different
|
||||
- Handling of incorrect Content-Type and/or HTTP method is different
|
||||
- TT-RSS accepts whitespace-only names; we do not
|
||||
- TT-RSS allows two folders to share the same name under the same parent; we do not
|
||||
- Session lifetime is much shorter by default (does TT-RSS even expire sessions?)
|
||||
- Categories and feeds will always be sorted alphabetically (the protocol does not allow for clients to re-order)
|
||||
- Label IDs decrease from -11 instead of from -1025
|
||||
|
||||
- The "Archived" virtual feed is non-functional (the protocol does not allow archiving)
|
||||
- The "Published" virtual feed is non-functional (this will not be implemented in the near term)
|
||||
*/
|
||||
|
||||
|
||||
|
@ -31,14 +31,12 @@ Protocol difference so far:
|
|||
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||
const LEVEL = 14;
|
||||
const VERSION = "17.4";
|
||||
const LABEL_OFFSET = 1024;
|
||||
const FATAL_ERR = [
|
||||
'seq' => null,
|
||||
'status' => 1,
|
||||
'content' => ['error' => "NOT_LOGGED_IN"],
|
||||
];
|
||||
const OVERRIDE = [
|
||||
'auth' => ["login"],
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
@ -65,8 +63,8 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
'sid' => null,
|
||||
], $data);
|
||||
try {
|
||||
if (!in_array($data['op'], self::OVERRIDE['auth'])) {
|
||||
// unless otherwise specified, a session identifier is required
|
||||
if (strtolower((string) $data['op']) != "login") {
|
||||
// unless logging in, a session identifier is required
|
||||
$this->resumeSession($data['sid']);
|
||||
}
|
||||
$method = "op".ucfirst($data['op']);
|
||||
|
@ -148,19 +146,109 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
return ['status' => true];
|
||||
}
|
||||
|
||||
public function opGetConfig(array $data): array {
|
||||
return [
|
||||
'icons_dir' => "feed-icons",
|
||||
'icons_url' => "feed-icons",
|
||||
'daemon_is_running' => Service::hasCheckedIn(),
|
||||
'num_feeds' => Arsse::$db->subscriptionCount(Arsse::$user->id),
|
||||
];
|
||||
}
|
||||
|
||||
public function opGetUnread(array $data): array {
|
||||
// simply sum the unread count of each subscription
|
||||
$out = 0;
|
||||
foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $sub) {
|
||||
$out += $sub['unread'];
|
||||
}
|
||||
return ['unread' => $out];
|
||||
}
|
||||
|
||||
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")));
|
||||
$countAll = 0;
|
||||
$countSubs = 0;
|
||||
$feeds = [];
|
||||
$labels = [];
|
||||
// do a first pass on categories: add the ID to a lookup table and set the unread counter to zero
|
||||
$categories = Arsse::$db->folderList($user)->getAll();
|
||||
$catmap = [];
|
||||
for ($a = 0; $a < sizeof($categories); $a++) {
|
||||
$catmap[(int) $categories[$a]['id']] = $a;
|
||||
$categories[$a]['counter'] = 0;
|
||||
}
|
||||
// add the "Uncategorized" and "Labels" virtual categories to the list
|
||||
$catmap[0] = sizeof($categories);
|
||||
$categories[] = ['id' => 0, 'name' => Arsse::$lang->msg("API.TTRSS.Category.Uncategorized"), 'parent' => 0, 'children' => 0, 'counter' => 0];
|
||||
$catmap[-2] = sizeof($categories);
|
||||
$categories[] = ['id' => -2, 'name' => Arsse::$lang->msg("API.TTRSS.Category.Labels"), 'parent' => 0, 'children' => 0, 'counter' => 0];
|
||||
// prepare data for each subscription; we also add unread counts for their host categories
|
||||
foreach (Arsse::$db->subscriptionList($user) as $f) {
|
||||
if ($f['unread']) {
|
||||
// add the feed to the list of feeds
|
||||
$feeds[] = ['id' => $f['id'], 'updated' => Date::transform($f['updated'], "iso8601", "sql"),'counter' => $f['unread'], 'has_img' => (int) (strlen((string) $f['favicon']) > 0)];
|
||||
// add the feed's unread count to the global unread count
|
||||
$countAll += $f['unread'];
|
||||
// add the feed's unread count to its category unread count
|
||||
$categories[$catmap[(int) $f['folder']]]['counter'] += $f['unread'];
|
||||
}
|
||||
// increment the global feed count
|
||||
$countSubs += 1;
|
||||
}
|
||||
// prepare data for each non-empty label
|
||||
foreach (Arsse::$db->labelList($user, false) as $l) {
|
||||
$unread = $l['articles'] - $l['read'];
|
||||
$labels[] = ['id' => $this->labelOut($l['id']), 'counter' => $unread, 'auxcounter' => $l['articles']];
|
||||
$categories[$catmap[-2]]['counter'] += $unread;
|
||||
}
|
||||
// do a second pass on categories, summing descendant unread counts for ancestors, pruning categories with no unread, and building a final category list
|
||||
$cats = [];
|
||||
while ($categories) {
|
||||
foreach ($categories as $c) {
|
||||
if ($c['children']) {
|
||||
// only act on leaf nodes
|
||||
continue;
|
||||
}
|
||||
if ($c['parent']) {
|
||||
// if the category has a parent, add its counter to the parent's counter, and decrement the parent's child count
|
||||
$categories[$catmap[$c['parent']]]['counter'] += $c['counter'];
|
||||
$categories[$catmap[$c['parent']]]['children'] -= 1;
|
||||
}
|
||||
if ($c['counter']) {
|
||||
// if the category's counter is non-zero, add the category to the output list
|
||||
$cats[] = ['id' => $c['id'], 'kind' => "cat", 'counter' => $c['counter']];
|
||||
}
|
||||
// remove the category from the input list
|
||||
unset($categories[$catmap[$c['id']]]);
|
||||
}
|
||||
}
|
||||
// prepare data for the virtual feeds and other counters
|
||||
$special = [
|
||||
['id' => "global-unread", 'counter' => $countAll], //this should not count archived articles, but we do not have an archive
|
||||
['id' => "subscribed-feeds", 'counter' => $countSubs],
|
||||
['id' => 0, 'counter' => 0, 'auxcounter' => 0], // Archived articles
|
||||
['id' => -1, 'counter' => $starred['unread'], 'auxcounter' => $starred['total']], // Starred articles
|
||||
['id' => -2, 'counter' => 0, 'auxcounter' => 0], // Published articles
|
||||
['id' => -3, 'counter' => $fresh, 'auxcounter' => 0], // Fresh articles
|
||||
['id' => -4, 'counter' => $countAll, 'auxcounter' => 0], // All articles
|
||||
];
|
||||
return array_merge($special, $labels, $feeds, $cats);
|
||||
}
|
||||
|
||||
public function opGetCategories(array $data): array {
|
||||
// normalize input
|
||||
$all = isset($data['include_empty']) ? ValueInfo::bool($data['include_empty'], false) : false;
|
||||
$read = !(isset($data['unread_only']) ? ValueInfo::bool($data['unread_only'], false) : false);
|
||||
$deep = !(isset($data['enable_nested']) ? ValueInfo::bool($data['enable_nested'], false) : false);
|
||||
$user = Arsse::$user->id;
|
||||
// for each category, add the ID to a lookup table, set the number of unread and feeds to zero, and assign an increasing order index
|
||||
// for each category, add the ID to a lookup table, set the number of unread to zero, and assign an increasing order index
|
||||
$cats = Arsse::$db->folderList($user, null, $deep)->getAll();
|
||||
$map = [];
|
||||
for ($a = 0; $a < sizeof($cats); $a++) {
|
||||
$map[$cats[$a]['id']] = $a;
|
||||
$cats[$a]['unread'] = 0;
|
||||
$cats[$a]['feeds'] = 0;
|
||||
$cats[$a]['order'] = $a + 1;
|
||||
}
|
||||
// add the "Uncategorized", "Special", and "Labels" virtual categories to the list
|
||||
|
@ -176,7 +264,9 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
// note we use top_folder if we're in "nested" mode
|
||||
$f = $map[(int) ($deep ? $sub['folder'] : $sub['top_folder'])];
|
||||
$cats[$f]['unread'] += $sub['unread'];
|
||||
$cats[$f]['feeds'] += 1;
|
||||
if (!$cats[$f]['id']) {
|
||||
$cats[$f]['feeds'] += 1;
|
||||
}
|
||||
}
|
||||
// for each label, add the unread count to the labels category, and increment the labels category's feed count
|
||||
$labels = Arsse::$db->labelList($user);
|
||||
|
@ -188,7 +278,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
// get the unread counts for the special feeds
|
||||
// FIXME: this is pretty inefficient
|
||||
$f = $map[-1];
|
||||
$cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->starred(true)); // starred
|
||||
$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
|
||||
if (!$read) {
|
||||
// if we're only including unread entries, remove any categories with zero unread items (this will by definition also exclude empties)
|
||||
|
@ -439,24 +529,6 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function opGetUnread(array $data): array {
|
||||
// simply sum the unread count of each subscription
|
||||
$out = 0;
|
||||
foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $sub) {
|
||||
$out += $sub['unread'];
|
||||
}
|
||||
return ['unread' => $out];
|
||||
}
|
||||
|
||||
public function opGetConfig(array $data): array {
|
||||
return [
|
||||
'icons_dir' => "feed-icons",
|
||||
'icons_url' => "feed-icons",
|
||||
'daemon_is_running' => Service::hasCheckedIn(),
|
||||
'num_feeds' => Arsse::$db->subscriptionCount(Arsse::$user->id),
|
||||
];
|
||||
}
|
||||
|
||||
public function opUpdateFeed(array $data): array {
|
||||
if (!isset($data['feed_id']) || !ValueInfo::id($data['feed_id'])) {
|
||||
// if the feed is invalid, throw an error
|
||||
|
@ -471,14 +543,14 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
}
|
||||
|
||||
protected function labelIn($id): int {
|
||||
if (!(ValueInfo::int($id) & ValueInfo::NEG) || $id > -11) {
|
||||
if (!(ValueInfo::int($id) & ValueInfo::NEG) || $id > (-1 - self::LABEL_OFFSET)) {
|
||||
throw new Exception("INCORRECT_USAGE");
|
||||
}
|
||||
return (abs($id) - 10);
|
||||
return (abs($id) - self::LABEL_OFFSET);
|
||||
}
|
||||
|
||||
protected function labelOut(int $id): int {
|
||||
return ($id * -1 - 10);
|
||||
return ($id * -1 - self::LABEL_OFFSET);
|
||||
}
|
||||
|
||||
public function opAddLabel(array $data) {
|
||||
|
|
|
@ -475,7 +475,7 @@ class TestNCNV1_2 extends Test\AbstractTest {
|
|||
'newestItemId' => 4758915,
|
||||
];
|
||||
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([]))->thenReturn(new Result($this->feeds['db']));
|
||||
Phake::when(Arsse::$db)->articleCount(Arsse::$user->id, (new Context)->starred(true))->thenReturn(0)->thenReturn(5);
|
||||
Phake::when(Arsse::$db)->articleStarred(Arsse::$user->id)->thenReturn(['total' => 0])->thenReturn(['total' => 5]);
|
||||
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id)->thenReturn(0)->thenReturn(4758915);
|
||||
$exp = new Response(200, $exp1);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds")));
|
||||
|
|
|
@ -9,12 +9,43 @@ use JKingWeb\Arsse\Misc\Date;
|
|||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
use JKingWeb\Arsse\Db\Transaction;
|
||||
use JKingWeb\Arsse\REST\TinyTinyRSS\API;
|
||||
use Phake;
|
||||
|
||||
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
|
||||
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
|
||||
class TestTinyTinyAPI extends Test\AbstractTest {
|
||||
protected $h;
|
||||
protected $folders = [
|
||||
['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"],
|
||||
['id' => 6, 'parent' => 3, 'children' => 0, 'feeds' => 2, 'name' => "National"],
|
||||
['id' => 4, 'parent' => null, 'children' => 0, 'feeds' => 0, 'name' => "Photography"],
|
||||
['id' => 3, 'parent' => null, 'children' => 2, 'feeds' => 0, 'name' => "Politics"],
|
||||
['id' => 2, 'parent' => 1, 'children' => 0, 'feeds' => 1, 'name' => "Rocketry"],
|
||||
['id' => 1, 'parent' => null, 'children' => 1, 'feeds' => 1, 'name' => "Science"],
|
||||
];
|
||||
protected $topFolders = [
|
||||
['id' => 4, 'parent' => null, 'children' => 0, 'feeds' => 0, 'name' => "Photography"],
|
||||
['id' => 3, 'parent' => null, 'children' => 2, 'feeds' => 0, 'name' => "Politics"],
|
||||
['id' => 1, 'parent' => null, 'children' => 1, 'feeds' => 1, 'name' => "Science"],
|
||||
];
|
||||
protected $subscriptions = [
|
||||
['id' => 6, 'folder' => null, 'top_folder' => null, 'unread' => 0, 'updated' => "2010-02-12 20:08:47", 'favicon' => 'http://example.com/6.png'],
|
||||
['id' => 3, 'folder' => 1, 'top_folder' => 1, 'unread' => 2, 'updated' => "2016-05-23 06:40:02", 'favicon' => 'http://example.com/3.png'],
|
||||
['id' => 1, 'folder' => 2, 'top_folder' => 1, 'unread' => 5, 'updated' => "2017-09-15 22:54:16", 'favicon' => null],
|
||||
['id' => 2, 'folder' => 5, 'top_folder' => 3, 'unread' => 10, 'updated' => "2011-11-11 11:11:11", 'favicon' => 'http://example.com/2.png'],
|
||||
['id' => 5, 'folder' => 6, 'top_folder' => 3, 'unread' => 12, 'updated' => "2017-07-07 17:07:17", 'favicon' => ''],
|
||||
['id' => 4, 'folder' => 6, 'top_folder' => 3, 'unread' => 6, 'updated' => "2017-10-09 15:58:34", 'favicon' => 'http://example.com/4.png'],
|
||||
];
|
||||
protected $labels = [
|
||||
['id' => 5, 'articles' => 0, 'read' => 0],
|
||||
['id' => 3, 'articles' => 100, 'read' => 94],
|
||||
['id' => 1, 'articles' => 2, 'read' => 0],
|
||||
];
|
||||
protected $usedLabels = [
|
||||
['id' => 3, 'articles' => 100, 'read' => 94],
|
||||
['id' => 1, 'articles' => 2, 'read' => 0],
|
||||
];
|
||||
|
||||
protected function respGood($content = null, $seq = 0): Response {
|
||||
return new Response(200, [
|
||||
|
@ -33,6 +64,19 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
]);
|
||||
}
|
||||
|
||||
protected function assertResponse(Response $exp, Response $act, string $text = null) {
|
||||
if ($exp->payload['status']) {
|
||||
// if the expectation is an error response, do a straight object comparison
|
||||
$this->assertEquals($exp, $act, $text);
|
||||
} else {
|
||||
// otherwise just compare their content
|
||||
foreach ($act->payload['content'] as $record) {
|
||||
$this->assertContains($record, $exp->payload['content'], $text);
|
||||
}
|
||||
$this->assertCount(sizeof($exp->payload['content']), $act->payload['content'], $text);
|
||||
}
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
$this->clearData();
|
||||
Arsse::$conf = new Conf();
|
||||
|
@ -529,14 +573,14 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
|
||||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => " "])->thenThrow(new ExceptionInput("whitespace"));
|
||||
// correctly add two labels
|
||||
$exp = $this->respGood(-12);
|
||||
$exp = $this->respGood((-1 * API::LABEL_OFFSET) - 2);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[0]))));
|
||||
$exp = $this->respGood(-13);
|
||||
$exp = $this->respGood((-1 * API::LABEL_OFFSET) - 3);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[1]))));
|
||||
// attempt to add the two labels again
|
||||
$exp = $this->respGood(-12);
|
||||
$exp = $this->respGood((-1 * API::LABEL_OFFSET) - 2);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[0]))));
|
||||
$exp = $this->respGood(-13);
|
||||
$exp = $this->respGood((-1 * API::LABEL_OFFSET) - 3);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[1]))));
|
||||
Phake::verify(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Software", true);
|
||||
Phake::verify(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Hardware", true);
|
||||
|
@ -549,14 +593,14 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
|
||||
public function testRemoveALabel() {
|
||||
$in = [
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42],
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042],
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112],
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 1],
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 0],
|
||||
['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -10],
|
||||
];
|
||||
Phake::when(Arsse::$db)->labelRemove(Arsse::$user->id, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
|
||||
Phake::when(Arsse::$db)->labelRemove(Arsse::$user->id, 32)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
|
||||
Phake::when(Arsse::$db)->labelRemove(Arsse::$user->id, 18)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
|
||||
// succefully delete a label
|
||||
$exp = $this->respGood();
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[0]))));
|
||||
|
@ -571,29 +615,29 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[2]))));
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[3]))));
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "", json_encode($in[4]))));
|
||||
Phake::verify(Arsse::$db, Phake::times(2))->labelRemove(Arsse::$user->id, 32);
|
||||
Phake::verify(Arsse::$db)->labelRemove(Arsse::$user->id, 2102);
|
||||
Phake::verify(Arsse::$db, Phake::times(2))->labelRemove(Arsse::$user->id, 18);
|
||||
Phake::verify(Arsse::$db)->labelRemove(Arsse::$user->id, 1088);
|
||||
}
|
||||
|
||||
public function testRenameALabel() {
|
||||
$in = [
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42, 'caption' => "Ook"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => "Ook"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112, 'caption' => "Eek"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42, 'caption' => "Eek"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42, 'caption' => ""],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42, 'caption' => " "],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => "Eek"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => ""],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => " "],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1, 'caption' => "Ook"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'caption' => "Ook"],
|
||||
['op' => "renameLabel", 'sid' => "PriestsOfSyrinx"],
|
||||
];
|
||||
$db = [
|
||||
[Arsse::$user->id, 32, ['name' => "Ook"]],
|
||||
[Arsse::$user->id, 2102, ['name' => "Eek"]],
|
||||
[Arsse::$user->id, 32, ['name' => "Eek"]],
|
||||
[Arsse::$user->id, 32, ['name' => ""]],
|
||||
[Arsse::$user->id, 32, ['name' => " "]],
|
||||
[Arsse::$user->id, 32, ['name' => ""]],
|
||||
[Arsse::$user->id, 18, ['name' => "Ook"]],
|
||||
[Arsse::$user->id, 1088, ['name' => "Eek"]],
|
||||
[Arsse::$user->id, 18, ['name' => "Eek"]],
|
||||
[Arsse::$user->id, 18, ['name' => ""]],
|
||||
[Arsse::$user->id, 18, ['name' => " "]],
|
||||
[Arsse::$user->id, 18, ['name' => ""]],
|
||||
];
|
||||
Phake::when(Arsse::$db)->labelPropertiesSet(...$db[0])->thenReturn(true);
|
||||
Phake::when(Arsse::$db)->labelPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
|
||||
|
@ -622,38 +666,6 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
}
|
||||
|
||||
public function testRetrieveCategoryLists() {
|
||||
$folders = [
|
||||
['id' => 5, 'parent' => 3, 'children' => 0, 'name' => "Local"],
|
||||
['id' => 6, 'parent' => 3, 'children' => 0, 'name' => "National"],
|
||||
['id' => 4, 'parent' => null, 'children' => 0, 'name' => "Photography"],
|
||||
['id' => 3, 'parent' => null, 'children' => 2, 'name' => "Politics"],
|
||||
['id' => 2, 'parent' => 1, 'children' => 0, 'name' => "Rocketry"],
|
||||
['id' => 1, 'parent' => null, 'children' => 1, 'name' => "Science"],
|
||||
];
|
||||
$topFolders = [
|
||||
['id' => 4, 'parent' => null, 'children' => 0, 'name' => "Photography"],
|
||||
['id' => 3, 'parent' => null, 'children' => 2, 'name' => "Politics"],
|
||||
['id' => 1, 'parent' => null, 'children' => 1, 'name' => "Science"],
|
||||
];
|
||||
$subscriptions = [
|
||||
['folder' => null, 'top_folder' => null, 'unread' => 0],
|
||||
['folder' => 1, 'top_folder' => 1, 'unread' => 2],
|
||||
['folder' => 2, 'top_folder' => 1, 'unread' => 5],
|
||||
['folder' => 5, 'top_folder' => 3, 'unread' => 10],
|
||||
['folder' => 6, 'top_folder' => 3, 'unread' => 12],
|
||||
['folder' => 6, 'top_folder' => 3, 'unread' => 6],
|
||||
];
|
||||
$labels = [
|
||||
['articles' => 0, 'read' => 0],
|
||||
['articles' => 100, 'read' => 94],
|
||||
['articles' => 2, 'read' => 0],
|
||||
];
|
||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($folders));
|
||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($topFolders));
|
||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($subscriptions));
|
||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($labels));
|
||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->starred(true))->thenReturn(4);
|
||||
$in = [
|
||||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
|
||||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx"],
|
||||
|
@ -662,6 +674,12 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true],
|
||||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true, 'unread_only' => true],
|
||||
];
|
||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->folders));
|
||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->topFolders));
|
||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->subscriptions));
|
||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn(['total' => 10, 'unread' => 4, 'read' => 6]);
|
||||
$exp = [
|
||||
[
|
||||
['id' => 5, 'title' => "Local", 'unread' => 10, 'order_id' => 1],
|
||||
|
@ -718,4 +736,36 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
|||
$this->assertEquals($this->respGood($exp[$a]), $this->h->dispatch(new Request("POST", "", json_encode($in[$a]))), "Test $a failed");
|
||||
}
|
||||
}
|
||||
|
||||
public function testRetrieveCounterList() {
|
||||
$in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"];
|
||||
Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->folders));
|
||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->subscriptions));
|
||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn(['total' => 10, 'unread' => 4, 'read' => 6]);
|
||||
$exp = [
|
||||
['id' => "global-unread", 'counter' => 35],
|
||||
['id' => "subscribed-feeds", 'counter' => 6],
|
||||
['id' => 0, 'counter' => 0, 'auxcounter' => 0],
|
||||
['id' => -1, 'counter' => 4, 'auxcounter' => 10],
|
||||
['id' => -2, 'counter' => 0, 'auxcounter' => 0],
|
||||
['id' => -3, 'counter' => 7, 'auxcounter' => 0],
|
||||
['id' => -4, 'counter' => 35, 'auxcounter' => 0],
|
||||
['id' => -1027, 'counter' => 6, 'auxcounter' => 100],
|
||||
['id' => -1025, 'counter' => 2, 'auxcounter' => 2],
|
||||
['id' => 3, 'has_img' => 1, 'counter' => 2, 'updated' => "2016-05-23T06:40:02"],
|
||||
['id' => 1, 'has_img' => 0, 'counter' => 5, 'updated' => "2017-09-15T22:54:16"],
|
||||
['id' => 2, 'has_img' => 1, 'counter' => 10, 'updated' => "2011-11-11T11:11:11"],
|
||||
['id' => 5, 'has_img' => 0, 'counter' => 12, 'updated' => "2017-07-07T17:07:17"],
|
||||
['id' => 4, 'has_img' => 1, 'counter' => 6, 'updated' => "2017-10-09T15:58:34"],
|
||||
['id' => 5, 'kind' => "cat", 'counter' => 10],
|
||||
['id' => 6, 'kind' => "cat", 'counter' => 18],
|
||||
['id' => 3, 'kind' => "cat", 'counter' => 28],
|
||||
['id' => 2, 'kind' => "cat", 'counter' => 5],
|
||||
['id' => 1, 'kind' => "cat", 'counter' => 7],
|
||||
['id' => -2, 'kind' => "cat", 'counter' => 8],
|
||||
];
|
||||
$this->assertResponse($this->respGood($exp), $this->h->dispatch(new Request("POST", "", json_encode($in))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -730,17 +730,29 @@ trait SeriesArticle {
|
|||
Arsse::$db->articleMark($this->user, ['read'=>false]);
|
||||
}
|
||||
|
||||
public function testCountStarredArticles() {
|
||||
public function testCountArticles() {
|
||||
$this->assertSame(2, Arsse::$db->articleCount("john.doe@example.com", (new Context)->starred(true)));
|
||||
$this->assertSame(2, Arsse::$db->articleCount("john.doe@example.org", (new Context)->starred(true)));
|
||||
$this->assertSame(2, Arsse::$db->articleCount("john.doe@example.net", (new Context)->starred(true)));
|
||||
$this->assertSame(4, Arsse::$db->articleCount("john.doe@example.com", (new Context)->folder(1)));
|
||||
$this->assertSame(0, Arsse::$db->articleCount("jane.doe@example.com", (new Context)->starred(true)));
|
||||
}
|
||||
|
||||
public function testCountStarredArticlesWithoutAuthority() {
|
||||
public function testCountArticlesWithoutAuthority() {
|
||||
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||
Arsse::$db->articleCount($this->user, (new Context)->starred(true));
|
||||
Arsse::$db->articleCount($this->user);
|
||||
}
|
||||
|
||||
public function testFetchStarredCounts() {
|
||||
$exp1 = ['total' => 2, 'unread' => 1, 'read' => 1];
|
||||
$exp2 = ['total' => 0, 'unread' => 0, 'read' => 0];
|
||||
$this->assertSame($exp1, Arsse::$db->articleStarred("john.doe@example.com"));
|
||||
$this->assertSame($exp2, Arsse::$db->articleStarred("jane.doe@example.com"));
|
||||
}
|
||||
|
||||
public function testFetchStarredCountsWithoutAuthority() {
|
||||
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||
Arsse::$db->articleStarred($this->user);
|
||||
}
|
||||
|
||||
public function testFetchLatestEdition() {
|
||||
|
|
Loading…
Reference in a new issue