1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Tests and fixed for Fever feeds and groups

This commit is contained in:
J. King 2019-03-27 15:09:04 -04:00
parent 7faec3b0db
commit de615c671a
2 changed files with 96 additions and 28 deletions

View file

@ -30,8 +30,8 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
$inR = $req->getQueryParams(); $inR = $req->getQueryParams() ?? [];
$inW = $req->getParsedBody(); $inW = $req->getParsedBody() ?? [];
if (!array_key_exists("api", $inR)) { if (!array_key_exists("api", $inR)) {
// the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404 // the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404
return new EmptyResponse(404); return new EmptyResponse(404);
@ -57,14 +57,13 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// otherwise if HTTP authentication failed or is required, deny access at the HTTP level // otherwise if HTTP authentication failed or is required, deny access at the HTTP level
return new EmptyResponse(401); return new EmptyResponse(401);
} }
// check that the user specified credentials // produce a full response if authenticated or a basic response otherwise
if ($this->logIn(strtolower($inW['api_key'] ?? ""))) { if ($this->logIn(strtolower($inW['api_key'] ?? ""))) {
$out['auth'] = 1; $out = $this->processRequest($this->baseResponse(true), $inR, $inW);
$out = $this->processRequest($out, $inR, $inW);
} else { } else {
$out['auth'] = 0; $out = $this->baseResponse(false);
} }
// return the result // return the result, possibly formatted as XML
return $this->formatResponse($out, $xml); return $this->formatResponse($out, $xml);
break; break;
default: default:
@ -73,9 +72,6 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
protected function processRequest(array $out, array $G, array $P): array { protected function processRequest(array $out, array $G, array $P): array {
// add base metadata
$out['last_refreshed_on_time'] = Date::transform(Arsse::$db->subscriptionRefreshed(Arsse::$user->id), "unix");
// handle each possible parameter
if (array_key_exists("feeds", $G) || array_key_exists("groups", $G)) { if (array_key_exists("feeds", $G) || array_key_exists("groups", $G)) {
if (array_key_exists("groups", $G)) { if (array_key_exists("groups", $G)) {
$out['groups'] = $this->getGroups(); $out['groups'] = $this->getGroups();
@ -91,6 +87,18 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
return $out; return $out;
} }
protected function baseResponse(bool $authenticated): array {
$out = [
'api_version' => self::LEVEL,
'auth' => (int) $authenticated,
];
if ($authenticated) {
// authenticated requests always include the most recent feed refresh
$out['last_refreshed_on_time'] = $this->getRefreshTime();
}
return $out;
}
protected function formatResponse(array $data, bool $xml): ResponseInterface { protected function formatResponse(array $data, bool $xml): ResponseInterface {
if ($xml) { if ($xml) {
throw \Exception("Not implemented yet"); throw \Exception("Not implemented yet");
@ -115,6 +123,10 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
return true; return true;
} }
protected function getRefreshTime() {
return Date::transform(Arsse::$db->subscriptionRefreshed(Arsse::$user->id), "unix");
}
protected function getFeeds(): array { protected function getFeeds(): array {
$out = []; $out = [];
foreach (arsse::$db->subscriptionList(Arsse::$user->id) as $sub) { foreach (arsse::$db->subscriptionList(Arsse::$user->id) as $sub) {
@ -125,7 +137,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
'url' => $sub['url'], 'url' => $sub['url'],
'site_url' => $sub['source'], 'site_url' => $sub['source'],
'is_spark' => 0, 'is_spark' => 0,
'lat_updated_on_time' => Date::transform($sub['edited'], "unix", "sql"), 'last_updated_on_time' => Date::transform($sub['edited'], "unix", "sql"),
]; ];
} }
return $out; return $out;

View file

@ -31,7 +31,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
return $value; return $value;
} }
protected function req($dataGet, $dataPost, string $method = "POST", string $type = null, string $url = "", string $user = null): ResponseInterface { protected function req($dataGet, $dataPost = "", string $method = "POST", string $type = null, string $url = "", string $user = null): ResponseInterface {
$url = "/fever/".$url; $url = "/fever/".$url;
$server = [ $server = [
'REQUEST_METHOD' => $method, 'REQUEST_METHOD' => $method,
@ -39,11 +39,10 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
'HTTP_CONTENT_TYPE' => $type ?? "application/x-www-form-urlencoded", 'HTTP_CONTENT_TYPE' => $type ?? "application/x-www-form-urlencoded",
]; ];
$req = new ServerRequest($server, [], $url, $method, "php://memory"); $req = new ServerRequest($server, [], $url, $method, "php://memory");
if (is_array($dataGet)) { if (!is_array($dataGet)) {
$req = $req->withRequestTarget($url)->withQueryParams($dataGet); parse_str($dataGet, $dataGet);
} else {
$req = $req->withRequestTarget($url."?".http_build_query((string) $dataGet, "", "&", \PHP_QUERY_RFC3986));
} }
$req = $req->withRequestTarget($url)->withQueryParams($dataGet);
if (is_array($dataPost)) { if (is_array($dataPost)) {
$req = $req->withParsedBody($dataPost); $req = $req->withParsedBody($dataPost);
} else { } else {
@ -72,8 +71,9 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$db = \Phake::mock(Database::class); Arsse::$db = \Phake::mock(Database::class);
\Phake::when(Arsse::$db)->begin->thenReturn(\Phake::mock(Transaction::class)); \Phake::when(Arsse::$db)->begin->thenReturn(\Phake::mock(Transaction::class));
\Phake::when(Arsse::$db)->tokenLookup->thenReturn(['user' => "john.doe@example.com"]); \Phake::when(Arsse::$db)->tokenLookup->thenReturn(['user' => "john.doe@example.com"]);
// instantiate the handler // instantiate the handler as a partial mock to simplify testing
$this->h = new API(); $this->h = \Phake::partialMock(API::class);
\Phake::when($this->h)->baseResponse->thenReturn([]);
} }
public function tearDown() { public function tearDown() {
@ -89,8 +89,10 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->id = null; Arsse::$user->id = null;
\Phake::when(Arsse::$db)->tokenLookup->thenThrow(new ExceptionInput("subjectMissing")); \Phake::when(Arsse::$db)->tokenLookup->thenThrow(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->tokenLookup("fever.login", "validtoken")->thenReturn(['user' => "jane.doe@example.com"]); \Phake::when(Arsse::$db)->tokenLookup("fever.login", "validtoken")->thenReturn(['user' => "jane.doe@example.com"]);
// use a partial mock to test only the authentication process // test only the authentication process
$this->h = \Phake::partialMock(API::class); \Phake::when($this->h)->baseResponse->thenReturnCallback(function(bool $authenticated) {
return ['auth' => (int) $authenticated];
});
\Phake::when($this->h)->processRequest->thenReturnCallback(function($out, $G, $P) { \Phake::when($this->h)->processRequest->thenReturnCallback(function($out, $G, $P) {
return $out; return $out;
}); });
@ -99,8 +101,8 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function provideTokenAuthenticationRequests() { public function provideTokenAuthenticationRequests() {
$success = new JsonResponse(['api_version' => API::LEVEL, 'auth' => 1]); $success = new JsonResponse(['auth' => 1]);
$failure = new JsonResponse(['api_version' => API::LEVEL, 'auth' => 0]); $failure = new JsonResponse(['auth' => 0]);
$denied = new EmptyResponse(401); $denied = new EmptyResponse(401);
return [ return [
[false, true, null, [], ['api' => null], $failure], [false, true, null, [], ['api' => null], $failure],
@ -149,4 +151,58 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
[true, false, "validUser", ['api_key' => "invalidToken"], ['api' => null], $success], [true, false, "validUser", ['api_key' => "invalidToken"], ['api' => null], $success],
]; ];
} }
public function testListGroups() {
\Phake::when(Arsse::$db)->tagList(Arsse::$user->id)->thenReturn(new Result([
['id' => 1, 'name' => "Fascinating", 'subscriptions' => 2],
['id' => 2, 'name' => "Interesting", 'subscriptions' => 2],
['id' => 3, 'name' => "Boring", 'subscriptions' => 0],
]));
\Phake::when(Arsse::$db)->tagSummarize(Arsse::$user->id)->thenReturn(new Result([
['id' => 1, 'name' => "Fascinating", 'subscription' => 1],
['id' => 1, 'name' => "Fascinating", 'subscription' => 2],
['id' => 2, 'name' => "Interesting", 'subscription' => 1],
['id' => 2, 'name' => "Interesting", 'subscription' => 3],
]));
$exp = new JsonResponse([
'groups' => [
['id' => 1, 'title' => "Fascinating"],
['id' => 2, 'title' => "Interesting"],
['id' => 3, 'title' => "Boring"],
],
'feeds_groups' => [
['group_id' => 1, 'feed_ids' => "1,2"],
['group_id' => 2, 'feed_ids' => "1,3"],
],
]);
$act = $this->req("api&groups");
$this->assertMessage($exp, $act);
}
public function testListFeeds() {
\Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([
['id' => 1, 'feed' => 5, 'title' => "Ankh-Morpork News", 'url' => "http://example.com/feed", 'source' => "http://example.com/", 'edited' => "2019-01-01 21:12:00", 'favicon' => "http://example.com/favicon.ico"],
['id' => 2, 'feed' => 9, 'title' => "Ook, Ook Eek Ook!", 'url' => "http://example.net/feed", 'source' => "http://example.net/", 'edited' => "1988-06-24 12:21:00", 'favicon' => ""],
['id' => 3, 'feed' => 1, 'title' => "The Last Soul", 'url' => "http://example.org/feed", 'source' => "http://example.org/", 'edited' => "1991-08-12 03:22:00", 'favicon' => "http://example.org/favicon.ico"],
]));
\Phake::when(Arsse::$db)->tagSummarize(Arsse::$user->id)->thenReturn(new Result([
['id' => 1, 'name' => "Fascinating", 'subscription' => 1],
['id' => 1, 'name' => "Fascinating", 'subscription' => 2],
['id' => 2, 'name' => "Interesting", 'subscription' => 1],
['id' => 2, 'name' => "Interesting", 'subscription' => 3],
]));
$exp = new JsonResponse([
'feeds' => [
['id' => 1, 'favicon_id' => 5, 'title' => "Ankh-Morpork News", 'url' => "http://example.com/feed", 'site_url' => "http://example.com/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("2019-01-01T21:12:00Z")],
['id' => 2, 'favicon_id' => 0, 'title' => "Ook, Ook Eek Ook!", 'url' => "http://example.net/feed", 'site_url' => "http://example.net/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("1988-06-24T12:21:00Z")],
['id' => 3, 'favicon_id' => 1, 'title' => "The Last Soul", 'url' => "http://example.org/feed", 'site_url' => "http://example.org/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("1991-08-12T03:22:00Z")],
],
'feeds_groups' => [
['group_id' => 1, 'feed_ids' => "1,2"],
['group_id' => 2, 'feed_ids' => "1,3"],
],
]);
$act = $this->req("api&feeds");
$this->assertMessage($exp, $act);
}
} }