1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-23 06:04:53 +00:00

Replace Resquest objects with PSR-7 request messages; improves #53

This commit is contained in:
J. King 2018-01-04 23:08:53 -05:00
parent 9ad0b47201
commit 890f9b07d4
12 changed files with 327 additions and 329 deletions

View file

@ -8,10 +8,12 @@ namespace JKingWeb\Arsse\REST;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\ValueInfo; use JKingWeb\Arsse\Misc\ValueInfo;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
abstract class AbstractHandler implements Handler { abstract class AbstractHandler implements Handler {
abstract public function __construct(); abstract public function __construct();
abstract public function dispatch(Request $req): \Psr\Http\Message\ResponseInterface; abstract public function dispatch(ServerRequestInterface $req): ResponseInterface;
protected function fieldMapNames(array $data, array $map): array { protected function fieldMapNames(array $data, array $map): array {
$out = []; $out = [];

View file

@ -6,7 +6,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST; namespace JKingWeb\Arsse\REST;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
interface Handler { interface Handler {
public function __construct(); public function __construct();
public function dispatch(Request $req): \Psr\Http\Message\ResponseInterface; public function dispatch(ServerRequestInterface $req): ResponseInterface;
} }

View file

@ -15,7 +15,9 @@ use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use \Psr\Http\Message\ResponseInterface; use JKingWeb\Arsse\REST\Target;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
@ -43,53 +45,61 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
'items' => ValueInfo::T_MIXED | ValueInfo::M_ARRAY, 'items' => ValueInfo::T_MIXED | ValueInfo::M_ARRAY,
]; ];
protected $paths = [ protected $paths = [
'folders' => ['GET' => "folderList", 'POST' => "folderAdd"], '/folders' => ['GET' => "folderList", 'POST' => "folderAdd"],
'folders/1' => ['PUT' => "folderRename", 'DELETE' => "folderRemove"], '/folders/1' => ['PUT' => "folderRename", 'DELETE' => "folderRemove"],
'folders/1/read' => ['PUT' => "folderMarkRead"], '/folders/1/read' => ['PUT' => "folderMarkRead"],
'feeds' => ['GET' => "subscriptionList", 'POST' => "subscriptionAdd"], '/feeds' => ['GET' => "subscriptionList", 'POST' => "subscriptionAdd"],
'feeds/1' => ['DELETE' => "subscriptionRemove"], '/feeds/1' => ['DELETE' => "subscriptionRemove"],
'feeds/1/move' => ['PUT' => "subscriptionMove"], '/feeds/1/move' => ['PUT' => "subscriptionMove"],
'feeds/1/rename' => ['PUT' => "subscriptionRename"], '/feeds/1/rename' => ['PUT' => "subscriptionRename"],
'feeds/1/read' => ['PUT' => "subscriptionMarkRead"], '/feeds/1/read' => ['PUT' => "subscriptionMarkRead"],
'feeds/all' => ['GET' => "feedListStale"], '/feeds/all' => ['GET' => "feedListStale"],
'feeds/update' => ['GET' => "feedUpdate"], '/feeds/update' => ['GET' => "feedUpdate"],
'items' => ['GET' => "articleList"], '/items' => ['GET' => "articleList"],
'items/updated' => ['GET' => "articleList"], '/items/updated' => ['GET' => "articleList"],
'items/read' => ['PUT' => "articleMarkReadAll"], '/items/read' => ['PUT' => "articleMarkReadAll"],
'items/1/read' => ['PUT' => "articleMarkRead"], '/items/1/read' => ['PUT' => "articleMarkRead"],
'items/1/unread' => ['PUT' => "articleMarkRead"], '/items/1/unread' => ['PUT' => "articleMarkRead"],
'items/read/multiple' => ['PUT' => "articleMarkReadMulti"], '/items/read/multiple' => ['PUT' => "articleMarkReadMulti"],
'items/unread/multiple' => ['PUT' => "articleMarkReadMulti"], '/items/unread/multiple' => ['PUT' => "articleMarkReadMulti"],
'items/1/1/star' => ['PUT' => "articleMarkStarred"], '/items/1/1/star' => ['PUT' => "articleMarkStarred"],
'items/1/1/unstar' => ['PUT' => "articleMarkStarred"], '/items/1/1/unstar' => ['PUT' => "articleMarkStarred"],
'items/star/multiple' => ['PUT' => "articleMarkStarredMulti"], '/items/star/multiple' => ['PUT' => "articleMarkStarredMulti"],
'items/unstar/multiple' => ['PUT' => "articleMarkStarredMulti"], '/items/unstar/multiple' => ['PUT' => "articleMarkStarredMulti"],
'cleanup/before-update' => ['GET' => "cleanupBefore"], '/cleanup/before-update' => ['GET' => "cleanupBefore"],
'cleanup/after-update' => ['GET' => "cleanupAfter"], '/cleanup/after-update' => ['GET' => "cleanupAfter"],
'version' => ['GET' => "serverVersion"], '/version' => ['GET' => "serverVersion"],
'status' => ['GET' => "serverStatus"], '/status' => ['GET' => "serverStatus"],
'user' => ['GET' => "userStatus"], '/user' => ['GET' => "userStatus"],
]; ];
public function __construct() { public function __construct() {
} }
public function dispatch(\JKingWeb\Arsse\REST\Request $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
// try to authenticate // try to authenticate
if (!Arsse::$user->authHTTP()) { if (!Arsse::$user->authHTTP()) {
return new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.self::REALM.'"']); return new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.self::REALM.'"']);
} }
// explode and normalize the URL path
$target = new Target($req->getRequestTarget());
// handle HTTP OPTIONS requests // handle HTTP OPTIONS requests
if ($req->method=="OPTIONS") { if ($req->getMethod()=="OPTIONS") {
return $this->handleHTTPOptions($req->paths); return $this->handleHTTPOptions((string) $target);
} }
// normalize the input // normalize the input
if ($req->body) { $data = (string) $req->getBody();
$type = "";
if ($req->hasHeader("Content-Type")) {
$type = $req->getHeader("Content-Type");
$type = array_pop($type);
}
if ($data) {
// if the entity body is not JSON according to content type, return "415 Unsupported Media Type" // if the entity body is not JSON according to content type, return "415 Unsupported Media Type"
if (!preg_match("<^application/json\b|^$>", $req->type)) { if (!preg_match("<^application/json\b|^$>", $type)) {
return new EmptyResponse(415, ['Accept' => "application/json"]); return new EmptyResponse(415, ['Accept' => "application/json"]);
} }
$data = @json_decode($req->body, true); $data = @json_decode($data, true);
if (json_last_error() != \JSON_ERROR_NONE) { if (json_last_error() != \JSON_ERROR_NONE) {
// if the body could not be parsed as JSON, return "400 Bad Request" // if the body could not be parsed as JSON, return "400 Bad Request"
return new EmptyResponse(400); return new EmptyResponse(400);
@ -98,10 +108,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$data = []; $data = [];
} }
// FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ? // FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ?
$data = $this->normalizeInput(array_merge($data, $req->query), $this->validInput, "unix"); $data = $this->normalizeInput(array_merge($data, $req->getQueryParams()), $this->validInput, "unix");
// check to make sure the requested function is implemented // check to make sure the requested function is implemented
try { try {
$func = $this->chooseCall($req->paths, $req->method); $func = $this->chooseCall((string) $target, $req->getMethod());
} catch (Exception404 $e) { } catch (Exception404 $e) {
return new EmptyResponse(404); return new EmptyResponse(404);
} catch (Exception405 $e) { } catch (Exception405 $e) {
@ -112,7 +122,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
// dispatch // dispatch
try { try {
return $this->$func($req->paths, $data); return $this->$func($target->path, $data);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
// if there was a REST exception return 400 // if there was a REST exception return 400
@ -124,19 +134,24 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
protected function normalizePath(array $url): string { protected function normalizePathIds(string $url): string {
// any URL components which are database IDs (integers greater than zero) should be replaced with "1", for easier comparison (we don't care about the specific ID) // first parse the URL and perform syntactic normalization
for ($a = 0; $a < sizeof($url); $a++) { $target = new Target($url);
if (ValueInfo::id($url[$a])) { // any path components which are database IDs (integers greater than zero) should be replaced with "1", for easier comparison (we don't care about the specific ID)
$url[$a] = "1"; for ($a = 0; $a < sizeof($target->path); $a++) {
if (ValueInfo::id($target->path[$a])) {
$target->path[$a] = "1";
} }
} }
return implode("/", $url); // discard any fragment ID (there shouldn't be any) and query string (the query is available in the request itself)
$target->fragment = "";
$target->query = "";
return (string) $target;
} }
protected function chooseCall(array $url, string $method): string { protected function chooseCall(string $url, string $method): string {
// normalize the URL path // // normalize the URL path: change any IDs to 1 for easier comparison
$url = $this->normalizePath($url); $url = $this->normalizePathIds($url);
// normalize the HTTP method to uppercase // normalize the HTTP method to uppercase
$method = strtoupper($method); $method = strtoupper($method);
// we now evaluate the supplied URL against every supported path for the selected scope // we now evaluate the supplied URL against every supported path for the selected scope
@ -244,9 +259,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
return $article; return $article;
} }
protected function handleHTTPOptions(array $url): ResponseInterface { protected function handleHTTPOptions(string $url): ResponseInterface {
// normalize the URL path // normalize the URL path: change any IDs to 1 for easier comparison
$url = $this->normalizePath($url); $url = $this->normalizePathIDs($url);
if (isset($this->paths[$url])) { if (isset($this->paths[$url])) {
// if the path is supported, respond with the allowed methods and other metadata // if the path is supported, respond with the allowed methods and other metadata
$allowed = array_keys($this->paths[$url]); $allowed = array_keys($this->paths[$url]);

View file

@ -6,6 +6,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST\NextCloudNews; namespace JKingWeb\Arsse\REST\NextCloudNews;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
@ -13,24 +15,26 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
public function __construct() { public function __construct() {
} }
public function dispatch(\JKingWeb\Arsse\REST\Request $req): \Psr\Http\Message\ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^/?$>", $req->path)) { if (!preg_match("<^/?$>", $req->getRequestTarget())) {
// if the request path is an empty string or just a slash, the client is probably trying a version we don't support // if the request path is more than an empty string or a slash, the client is probably trying a version we don't support
return new EmptyResponse(404); return new EmptyResponse(404);
} elseif ($req->method=="OPTIONS") { }
// if the request method is OPTIONS, respond accordingly switch ($req->getMethod()) {
return new EmptyResponse(204, ['Allow' => "HEAD,GET"]); case "OPTIONS":
} elseif ($req->method != "GET") { // if the request method is OPTIONS, respond accordingly
// if a method other than GET was used, this is an error return new EmptyResponse(204, ['Allow' => "HEAD,GET"]);
return new EmptyResponse(405, ['Allow' => "HEAD,GET"]); case "GET":
} else { // otherwise return the supported versions
// otherwise return the supported versions $out = [
$out = [ 'apiLevels' => [
'apiLevels' => [ 'v1-2',
'v1-2', ]
] ];
]; return new Response($out);
return new Response($out); default:
// if any other method was used, this is an error
return new EmptyResponse(405, ['Allow' => "HEAD,GET"]);
} }
} }
} }

View file

@ -1,89 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\REST;
class Request {
public $method = "GET";
public $head = false;
public $url = "";
public $path ="";
public $paths = [];
public $query = "";
public $type ="";
public $body = "";
public function __construct(string $method = null, string $url = null, string $body = null, string $contentType = null) {
$method = $method ?? $_SERVER['REQUEST_METHOD'];
$url = $url ?? $_SERVER['REQUEST_URI'];
$body = $body ?? file_get_contents("php://input");
if (is_null($contentType)) {
if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
$contentType = $_SERVER['HTTP_CONTENT_TYPE'];
} else {
$contentType = "";
}
}
$this->method = strtoupper($method);
$this->url = $url;
$this->body = $body;
$this->type = $contentType;
if ($this->method=="HEAD") {
$this->head = true;
$this->method = "GET";
}
$this->refreshURL();
}
public function refreshURL() {
$url = $this->parseURL($this->url);
$this->path = $url['path'];
$this->paths = $url['paths'];
$this->query = $url['query'];
}
protected function parseURL(string $url): array {
// split the query string from the path
$parts = explode("?", $url);
$out = ['path' => $parts[0], 'paths' => [''], 'query' => []];
// if there is a query string, parse it
if (isset($parts[1])) {
// split along & to get key-value pairs
$query = explode("&", $parts[1]);
for ($a = 0; $a < sizeof($query); $a++) {
// split each pair, into no more than two parts
$data = explode("=", $query[$a], 2);
// decode the key
$key = rawurldecode($data[0]);
// decode the value if there is one
$value = "";
if (isset($data[1])) {
$value = rawurldecode($data[1]);
}
// add the pair to the query output, overwriting earlier values for the same key, is present
$out['query'][$key] = $value;
}
}
// also include the path as a set of decoded elements
// if the path is an empty string or just / nothing needs be done
if (!in_array($out['path'], ["/",""])) {
$paths = explode("/", $out['path']);
// remove the first and last empty elements, if present (they are artefacts of the splitting; others should remain)
if (!strlen($paths[0])) {
array_shift($paths);
}
if (!strlen($paths[sizeof($paths)-1])) {
array_pop($paths);
}
// %-decode each path element
$paths = array_map(function ($v) {
return rawurldecode($v);
}, $paths);
$out['paths'] = $paths;
}
return $out;
}
}

View file

@ -19,6 +19,8 @@ use JKingWeb\Arsse\ExceptionType;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ResultEmpty; use JKingWeb\Arsse\Db\ResultEmpty;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
@ -89,21 +91,22 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { public function __construct() {
} }
public function dispatch(\JKingWeb\Arsse\REST\Request $req): \Psr\Http\Message\ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^(?:/(?:index\.php)?)?$>", $req->path)) { if (!preg_match("<^(?:/(?:index\.php)?)?$>", $req->getRequestTarget())) {
// reject paths other than the index // reject paths other than the index
return new EmptyResponse(404); return new EmptyResponse(404);
} }
if ($req->method=="OPTIONS") { if ($req->getMethod()=="OPTIONS") {
// respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method // respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method
return new EmptyResponse(204, [ return new EmptyResponse(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/json, text/json", 'Accept' => "application/json, text/json",
]); ]);
} }
if ($req->body) { $data = (string) $req->getBody();
if ($data) {
// only JSON entities are allowed, but Content-Type is ignored, as is request method // only JSON entities are allowed, but Content-Type is ignored, as is request method
$data = @json_decode($req->body, true); $data = @json_decode($data, true);
if (json_last_error() != \JSON_ERROR_NONE || !is_array($data)) { if (json_last_error() != \JSON_ERROR_NONE || !is_array($data)) {
return new Response(self::FATAL_ERR); return new Response(self::FATAL_ERR);
} }

View file

@ -7,17 +7,19 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\TinyTinyRSS; namespace JKingWeb\Arsse\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\EmptyResponse as Response; use Zend\Diactoros\Response\EmptyResponse as Response;
class Icon extends \JKingWeb\Arsse\REST\AbstractHandler { class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { public function __construct() {
} }
public function dispatch(\JKingWeb\Arsse\REST\Request $req): \Psr\Http\Message\ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if ($req->method != "GET") { if ($req->getMethod() != "GET") {
// only GET requests are allowed // only GET requests are allowed
return new Response(405, ['Allow' => "GET"]); return new Response(405, ['Allow' => "GET"]);
} elseif (!preg_match("<^(\d+)\.ico$>", $req->url, $match) || !((int) $match[1])) { } elseif (!preg_match("<^(\d+)\.ico$>", $req->getRequestTarget(), $match) || !((int) $match[1])) {
return new Response(404); return new Response(404);
} }
$url = Arsse::$db->subscriptionFavicon((int) $match[1]); $url = Arsse::$db->subscriptionFavicon((int) $match[1]);

View file

@ -11,13 +11,14 @@ use JKingWeb\Arsse\Conf;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Service; use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\REST\Request;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\Context; use JKingWeb\Arsse\Misc\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\NextCloudNews\V1_2; use JKingWeb\Arsse\REST\NextCloudNews\V1_2;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
use Phake; use Phake;
@ -300,6 +301,40 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
]; ];
protected function req(string $method, string $target, string $data = "", array $headers = []): ResponseInterface {
$url = "/index.php/apps/news/api/v1-2".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
'PHP_AUTH_USER' => "john.doe@example.com",
'PHP_AUTH_PW' => "secret",
'REMOTE_USER' => "john.doe@example.com",
];
if (strlen($data)) {
$server['HTTP_CONTENT_TYPE'] = "application/json";
}
$req = new ServerRequest($server, [], $url, $method, "php://memory");
foreach($headers as $key => $value) {
if (!is_null($value)) {
$req = $req->withHeader($key, $value);
} else {
$req = $req->withoutHeader($key);
}
}
if (strlen($data)) {
$body = $req->getBody();
$body->write($data);
$req = $req->withBody($body);
}
$q = $req->getUri()->getQuery();
if (strlen($q)) {
parse_str($q, $q);
$req = $req->withQueryParams($q);
}
$req = $req->withRequestTarget($target);
return $this->h->dispatch($req);
}
public function setUp() { public function setUp() {
$this->clearData(); $this->clearData();
Arsse::$conf = new Conf(); Arsse::$conf = new Conf();
@ -321,7 +356,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testSendAuthenticationChallenge() { public function testSendAuthenticationChallenge() {
Phake::when(Arsse::$user)->authHTTP->thenReturn(false); Phake::when(Arsse::$user)->authHTTP->thenReturn(false);
$exp = new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.V1_2::REALM.'"']); $exp = new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.V1_2::REALM.'"']);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/"))); $this->assertResponse($exp, $this->req("GET", "/"));
} }
public function testRespondToInvalidPaths() { public function testRespondToInvalidPaths() {
@ -359,22 +394,23 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
foreach ($errs[404] as $req) { foreach ($errs[404] as $req) {
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
list($method, $path) = $req; list($method, $path) = $req;
$this->assertResponse($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404."); $this->assertResponse($exp, $this->req($method, $path), "$method call to $path did not return 404.");
} }
foreach ($errs[405] as $allow => $cases) { foreach ($errs[405] as $allow => $cases) {
$exp = new EmptyResponse(405, ['Allow' => $allow]); $exp = new EmptyResponse(405, ['Allow' => $allow]);
foreach ($cases as $req) { foreach ($cases as $req) {
list($method, $path) = $req; list($method, $path) = $req;
$this->assertResponse($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405."); $this->assertResponse($exp, $this->req($method, $path), "$method call to $path did not return 405.");
} }
} }
} }
public function testRespondToInvalidInputTypes() { public function testRespondToInvalidInputTypes() {
$exp = new EmptyResponse(415, ['Accept' => "application/json"]); $exp = new EmptyResponse(415, ['Accept' => "application/json"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", '<data/>', 'application/xml'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"]));
$exp = new EmptyResponse(400); $exp = new EmptyResponse(400);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", '<data/>', 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", '<data/>'));
$this->assertResponse($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null]));
} }
public function testRespondToOptionsRequests() { public function testRespondToOptionsRequests() {
@ -382,19 +418,19 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
'Allow' => "HEAD,GET,POST", 'Allow' => "HEAD,GET,POST",
'Accept' => "application/json", 'Accept' => "application/json",
]); ]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/feeds"))); $this->assertResponse($exp, $this->req("OPTIONS", "/feeds"));
$exp = new EmptyResponse(204, [ $exp = new EmptyResponse(204, [
'Allow' => "DELETE", 'Allow' => "DELETE",
'Accept' => "application/json", 'Accept' => "application/json",
]); ]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/feeds/2112"))); $this->assertResponse($exp, $this->req("OPTIONS", "/feeds/2112"));
$exp = new EmptyResponse(204, [ $exp = new EmptyResponse(204, [
'Allow' => "HEAD,GET", 'Allow' => "HEAD,GET",
'Accept' => "application/json", 'Accept' => "application/json",
]); ]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/user"))); $this->assertResponse($exp, $this->req("OPTIONS", "/user"));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/invalid/path"))); $this->assertResponse($exp, $this->req("OPTIONS", "/invalid/path"));
} }
public function testListFolders() { public function testListFolders() {
@ -408,9 +444,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
]; ];
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($list)); Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($list));
$exp = new Response(['folders' => []]); $exp = new Response(['folders' => []]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders"))); $this->assertResponse($exp, $this->req("GET", "/folders"));
$exp = new Response(['folders' => $out]); $exp = new Response(['folders' => $out]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders"))); $this->assertResponse($exp, $this->req("GET", "/folders"));
} }
public function testAddAFolder() { public function testAddAFolder() {
@ -438,33 +474,33 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => " "])->thenThrow(new ExceptionInput("whitespace")); Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => " "])->thenThrow(new ExceptionInput("whitespace"));
// correctly add two folders, using different means // correctly add two folders, using different means
$exp = new Response(['folders' => [$out[0]]]); $exp = new Response(['folders' => [$out[0]]]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/folders", json_encode($in[0])));
$exp = new Response(['folders' => [$out[1]]]); $exp = new Response(['folders' => [$out[1]]]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders?name=Hardware"))); $this->assertResponse($exp, $this->req("POST", "/folders?name=Hardware"));
Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[0]); Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[0]);
Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[1]); Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[1]);
Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 1); Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 1);
Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2); Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2);
// test bad folder names // test bad folder names
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders"))); $this->assertResponse($exp, $this->req("POST", "/folders"));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":""}', 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/folders", '{"name":""}'));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":" "}', 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/folders", '{"name":" "}'));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":{}}', 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/folders", '{"name":{}}'));
// try adding the same two folders again // try adding the same two folders again
$exp = new EmptyResponse(409); $exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software"))); $this->assertResponse($exp, $this->req("POST", "/folders?name=Software"));
$exp = new EmptyResponse(409); $exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/folders", json_encode($in[1])));
} }
public function testRemoveAFolder() { public function testRemoveAFolder() {
Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing")); Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/folders/1"))); $this->assertResponse($exp, $this->req("DELETE", "/folders/1"));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/folders/1"))); $this->assertResponse($exp, $this->req("DELETE", "/folders/1"));
Phake::verify(Arsse::$db, Phake::times(2))->folderRemove(Arsse::$user->id, 1); Phake::verify(Arsse::$db, Phake::times(2))->folderRemove(Arsse::$user->id, 1);
} }
@ -483,17 +519,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database
Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 3, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // folder ID 3 does not exist Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 3, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // folder ID 3 does not exist
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", json_encode($in[0])));
$exp = new EmptyResponse(409); $exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/2", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", json_encode($in[2])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", json_encode($in[3])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1", json_encode($in[4])));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/3", json_encode($in[0])));
} }
public function testRetrieveServerVersion() { public function testRetrieveServerVersion() {
@ -501,7 +537,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
'version' => V1_2::VERSION, 'version' => V1_2::VERSION,
'arsse_version' => Arsse::VERSION, 'arsse_version' => Arsse::VERSION,
]); ]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/version"))); $this->assertResponse($exp, $this->req("GET", "/version"));
} }
public function testListSubscriptions() { public function testListSubscriptions() {
@ -518,9 +554,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleStarred(Arsse::$user->id)->thenReturn(['total' => 0])->thenReturn(['total' => 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); Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id)->thenReturn(0)->thenReturn(4758915);
$exp = new Response($exp1); $exp = new Response($exp1);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds"))); $this->assertResponse($exp, $this->req("GET", "/feeds"));
$exp = new Response($exp2); $exp = new Response($exp2);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds"))); $this->assertResponse($exp, $this->req("GET", "/feeds"));
} }
public function testAddASubscription() { public function testAddASubscription() {
@ -553,31 +589,31 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.net/news.atom")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.net/news.atom", new \PicoFeed\Client\InvalidUrlException()))->thenReturn(47); Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.net/news.atom")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.net/news.atom", new \PicoFeed\Client\InvalidUrlException()))->thenReturn(47);
// add the subscriptions // add the subscriptions
$exp = new Response($out[0]); $exp = new Response($out[0]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[0])));
$exp = new Response($out[1]); $exp = new Response($out[1]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[1])));
// try to add them a second time // try to add them a second time
$exp = new EmptyResponse(409); $exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[0])));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[1])));
// try to add a bad feed // try to add a bad feed
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[2])));
// try again (this will succeed), with an invalid folder ID // try again (this will succeed), with an invalid folder ID
$exp = new Response($out[2]); $exp = new Response($out[2]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[3])));
// try to add no feed // try to add no feed
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[4]), 'application/json'))); $this->assertResponse($exp, $this->req("POST", "/feeds", json_encode($in[4])));
} }
public function testRemoveASubscription() { public function testRemoveASubscription() {
Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing")); Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1"))); $this->assertResponse($exp, $this->req("DELETE", "/feeds/1"));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1"))); $this->assertResponse($exp, $this->req("DELETE", "/feeds/1"));
Phake::verify(Arsse::$db, Phake::times(2))->subscriptionRemove(Arsse::$user->id, 1); Phake::verify(Arsse::$db, Phake::times(2))->subscriptionRemove(Arsse::$user->id, 1);
} }
@ -596,17 +632,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => -1])->thenThrow(new ExceptionInput("typeViolation")); // folder is invalid Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => -1])->thenThrow(new ExceptionInput("typeViolation")); // folder is invalid
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // subscription does not exist Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // subscription does not exist
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2])));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/move", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[4]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[5]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5])));
} }
public function testRenameASubscription() { public function testRenameASubscription() {
@ -626,17 +662,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, $this->identicalTo(['title' => false]))->thenThrow(new ExceptionInput("missing")); Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, $this->identicalTo(['title' => false]))->thenThrow(new ExceptionInput("missing"));
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3])));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/rename", json_encode($in[4]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[6]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6])));
} }
public function testListStaleFeeds() { public function testListStaleFeeds() {
@ -652,11 +688,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
]; ];
Phake::when(Arsse::$db)->feedListStale->thenReturn(array_column($out, "id")); Phake::when(Arsse::$db)->feedListStale->thenReturn(array_column($out, "id"));
$exp = new Response(['feeds' => $out]); $exp = new Response(['feeds' => $out]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all"))); $this->assertResponse($exp, $this->req("GET", "/feeds/all"));
// retrieving the list when not an admin fails // retrieving the list when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0); Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new EmptyResponse(403); $exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all"))); $this->assertResponse($exp, $this->req("GET", "/feeds/all"));
} }
public function testUpdateAFeed() { public function testUpdateAFeed() {
@ -671,17 +707,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->feedUpdate(2112)->thenThrow(new ExceptionInput("subjectMissing")); Phake::when(Arsse::$db)->feedUpdate(2112)->thenThrow(new ExceptionInput("subjectMissing"));
Phake::when(Arsse::$db)->feedUpdate($this->lessThan(1))->thenThrow(new ExceptionInput("typeViolation")); Phake::when(Arsse::$db)->feedUpdate($this->lessThan(1))->thenThrow(new ExceptionInput("typeViolation"));
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[0])));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[2])));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[3])));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[4]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[4])));
// updating a feed when not an admin fails // updating a feed when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0); Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new EmptyResponse(403); $exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/feeds/update", json_encode($in[0])));
} }
public function testListArticles() { public function testListArticles() {
@ -708,23 +744,23 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation")); Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation"));
$exp = new Response(['items' => $this->articles['rest']]); $exp = new Response(['items' => $this->articles['rest']]);
// check the contents of the response // check the contents of the response
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items"))); // first instance of base context $this->assertResponse($exp, $this->req("GET", "/items")); // first instance of base context
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items/updated"))); // second instance of base context $this->assertResponse($exp, $this->req("GET", "/items/updated")); // second instance of base context
// check error conditions // check error conditions
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[0]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/items", json_encode($in[0])));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[1]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/items", json_encode($in[1])));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[2]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/items", json_encode($in[2])));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[3]), 'application/json'))); $this->assertResponse($exp, $this->req("GET", "/items", json_encode($in[3])));
// simply run through the remainder of the input for later method verification // simply run through the remainder of the input for later method verification
$this->h->dispatch(new Request("GET", "/items", json_encode($in[4]), 'application/json')); $this->req("GET", "/items", json_encode($in[4]));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[5]), 'application/json')); // third instance of base context $this->req("GET", "/items", json_encode($in[5])); // third instance of base context
$this->h->dispatch(new Request("GET", "/items", json_encode($in[6]), 'application/json')); $this->req("GET", "/items", json_encode($in[6]));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[7]), 'application/json')); $this->req("GET", "/items", json_encode($in[7]));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[8]), 'application/json')); // fourth instance of base context $this->req("GET", "/items", json_encode($in[8])); // fourth instance of base context
$this->h->dispatch(new Request("GET", "/items", json_encode($in[9]), 'application/json')); $this->req("GET", "/items", json_encode($in[9]));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[10]), 'application/json')); $this->req("GET", "/items", json_encode($in[10]));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[11]), 'application/json')); $this->req("GET", "/items", json_encode($in[11]));
// perform method verifications // perform method verifications
Phake::verify(Arsse::$db, Phake::times(4))->articleList(Arsse::$user->id, (new Context)->reverse(true), Database::LIST_TYPICAL); Phake::verify(Arsse::$db, Phake::times(4))->articleList(Arsse::$user->id, (new Context)->reverse(true), Database::LIST_TYPICAL);
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42), Database::LIST_TYPICAL); Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42), Database::LIST_TYPICAL);
@ -745,13 +781,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(1)->latestEdition(2112))->thenReturn(42); Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(1)->latestEdition(2112))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // folder doesn't exist Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // folder doesn't exist
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read", $in, 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/1/read", $in));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read?newestItemId=2112"))); $this->assertResponse($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read"))); $this->assertResponse($exp, $this->req("PUT", "/folders/1/read"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read?newestItemId=ook"))); $this->assertResponse($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/42/read", $in, 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/folders/42/read", $in));
} }
public function testMarkASubscriptionRead() { public function testMarkASubscriptionRead() {
@ -760,13 +796,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(1)->latestEdition(2112))->thenReturn(42); Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(1)->latestEdition(2112))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // subscription doesn't exist Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // subscription doesn't exist
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read", $in, 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/read", $in));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read?newestItemId=2112"))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read"))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/read"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read?newestItemId=ook"))); $this->assertResponse($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/read", $in, 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/feeds/42/read", $in));
} }
public function testMarkAllItemsRead() { public function testMarkAllItemsRead() {
@ -774,11 +810,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->latestEdition(2112))->thenReturn(42); Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->latestEdition(2112))->thenReturn(42);
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read", $in, 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/read", $in));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read?newestItemId=2112"))); $this->assertResponse($exp, $this->req("PUT", "/items/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read"))); $this->assertResponse($exp, $this->req("PUT", "/items/read"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read?newestItemId=ook"))); $this->assertResponse($exp, $this->req("PUT", "/items/read?newestItemId=ook"));
} }
public function testChangeMarksOfASingleArticle() { public function testChangeMarksOfASingleArticle() {
@ -795,15 +831,15 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(4))->thenReturn(42); Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(4))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(1337))->thenThrow(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(1337))->thenThrow(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/read"))); $this->assertResponse($exp, $this->req("PUT", "/items/1/read"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/2/unread"))); $this->assertResponse($exp, $this->req("PUT", "/items/2/unread"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/3/star"))); $this->assertResponse($exp, $this->req("PUT", "/items/1/3/star"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/4400/4/unstar"))); $this->assertResponse($exp, $this->req("PUT", "/items/4400/4/unstar"));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/42/read"))); $this->assertResponse($exp, $this->req("PUT", "/items/42/read"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/47/unread"))); $this->assertResponse($exp, $this->req("PUT", "/items/47/unread"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/2112/star"))); $this->assertResponse($exp, $this->req("PUT", "/items/1/2112/star"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/4400/1337/unstar"))); $this->assertResponse($exp, $this->req("PUT", "/items/4400/1337/unstar"));
Phake::verify(Arsse::$db, Phake::times(8))->articleMark(Arsse::$user->id, $this->anything(), $this->anything()); Phake::verify(Arsse::$db, Phake::times(8))->articleMark(Arsse::$user->id, $this->anything(), $this->anything());
} }
@ -826,26 +862,26 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->editions([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->editions([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->articles([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->articles([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple"))); $this->assertResponse($exp, $this->req("PUT", "/items/read/multiple"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple"))); $this->assertResponse($exp, $this->req("PUT", "/items/unread/multiple"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple"))); $this->assertResponse($exp, $this->req("PUT", "/items/star/multiple"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unstar/multiple"))); $this->assertResponse($exp, $this->req("PUT", "/items/unstar/multiple"));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple", json_encode(['items' => "ook"]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/read/multiple", json_encode(['items' => "ook"])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple", json_encode(['items' => "ook"]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unread/multiple", json_encode(['items' => "ook"])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple", json_encode(['items' => "ook"]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/star/multiple", json_encode(['items' => "ook"])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unstar/multiple", json_encode(['items' => "ook"]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unstar/multiple", json_encode(['items' => "ook"])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple", json_encode(['items' => []]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/read/multiple", json_encode(['items' => []])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple", json_encode(['items' => []]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unread/multiple", json_encode(['items' => []])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple", json_encode(['items' => $in[0]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/read/multiple", json_encode(['items' => $in[0]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple", json_encode(['items' => $in[0]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unread/multiple", json_encode(['items' => $in[0]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple", json_encode(['items' => $in[1]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/read/multiple", json_encode(['items' => $in[1]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple", json_encode(['items' => $in[1]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unread/multiple", json_encode(['items' => $in[1]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple", json_encode(['items' => []]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/star/multiple", json_encode(['items' => []])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unstar/multiple", json_encode(['items' => []]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unstar/multiple", json_encode(['items' => []])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple", json_encode(['items' => $inStar[0]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/star/multiple", json_encode(['items' => $inStar[0]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unstar/multiple", json_encode(['items' => $inStar[0]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unstar/multiple", json_encode(['items' => $inStar[0]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple", json_encode(['items' => $inStar[1]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/star/multiple", json_encode(['items' => $inStar[1]])));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unstar/multiple", json_encode(['items' => $inStar[1]]), 'application/json'))); $this->assertResponse($exp, $this->req("PUT", "/items/unstar/multiple", json_encode(['items' => $inStar[1]])));
// ensure the data model was queried appropriately for read/unread // ensure the data model was queried appropriately for read/unread
Phake::verify(Arsse::$db, Phake::atLeast(1))->articleMark(Arsse::$user->id, $read, (new Context)->editions([])); Phake::verify(Arsse::$db, Phake::atLeast(1))->articleMark(Arsse::$user->id, $read, (new Context)->editions([]));
Phake::verify(Arsse::$db, Phake::atLeast(1))->articleMark(Arsse::$user->id, $read, (new Context)->editions($in[0])); Phake::verify(Arsse::$db, Phake::atLeast(1))->articleMark(Arsse::$user->id, $read, (new Context)->editions($in[0]));
@ -879,28 +915,28 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$arr2['warnings']['improperlyConfiguredCron'] = true; $arr2['warnings']['improperlyConfiguredCron'] = true;
$arr2['warnings']['incorrectDbCharset'] = true; $arr2['warnings']['incorrectDbCharset'] = true;
$exp = new Response($arr1); $exp = new Response($arr1);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/status"))); $this->assertResponse($exp, $this->req("GET", "/status"));
} }
public function testCleanUpBeforeUpdate() { public function testCleanUpBeforeUpdate() {
Phake::when(Arsse::$db)->feedCleanup()->thenReturn(true); Phake::when(Arsse::$db)->feedCleanup()->thenReturn(true);
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/before-update"))); $this->assertResponse($exp, $this->req("GET", "/cleanup/before-update"));
Phake::verify(Arsse::$db)->feedCleanup(); Phake::verify(Arsse::$db)->feedCleanup();
// performing a cleanup when not an admin fails // performing a cleanup when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0); Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new EmptyResponse(403); $exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/before-update"))); $this->assertResponse($exp, $this->req("GET", "/cleanup/before-update"));
} }
public function testCleanUpAfterUpdate() { public function testCleanUpAfterUpdate() {
Phake::when(Arsse::$db)->articleCleanup()->thenReturn(true); Phake::when(Arsse::$db)->articleCleanup()->thenReturn(true);
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/after-update"))); $this->assertResponse($exp, $this->req("GET", "/cleanup/after-update"));
Phake::verify(Arsse::$db)->articleCleanup(); Phake::verify(Arsse::$db)->articleCleanup();
// performing a cleanup when not an admin fails // performing a cleanup when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0); Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new EmptyResponse(403); $exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/after-update"))); $this->assertResponse($exp, $this->req("GET", "/cleanup/after-update"));
} }
} }

View file

@ -7,7 +7,8 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\NextCloudNews; namespace JKingWeb\Arsse\TestCase\REST\NextCloudNews;
use JKingWeb\Arsse\REST\NextCloudNews\Versions; use JKingWeb\Arsse\REST\NextCloudNews\Versions;
use JKingWeb\Arsse\REST\Request; use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
@ -17,44 +18,37 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
$this->clearData(); $this->clearData();
} }
protected function req(string $method, string $target): ResponseInterface {
$url = "/index.php/apps/news/api".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$req = $req->withRequestTarget($target);
return (new Versions)->dispatch($req);
}
public function testFetchVersionList() { public function testFetchVersionList() {
$exp = new Response(['apiLevels' => ['v1-2']]); $exp = new Response(['apiLevels' => ['v1-2']]);
$h = new Versions; $this->assertResponse($exp, $this->req("GET", "/"));
$req = new Request("GET", "/"); $this->assertResponse($exp, $this->req("GET", "/"));
$res = $h->dispatch($req); $this->assertResponse($exp, $this->req("GET", "/"));
$this->assertResponse($exp, $res);
$req = new Request("GET", "");
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
$req = new Request("GET", "/?id=1827");
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
} }
public function testRespondToOptionsRequest() { public function testRespondToOptionsRequest() {
$exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]); $exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]);
$h = new Versions; $this->assertResponse($exp, $this->req("OPTIONS", "/"));
$req = new Request("OPTIONS", "/");
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
} }
public function testUseIncorrectMethod() { public function testUseIncorrectMethod() {
$exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]); $exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]);
$h = new Versions; $this->assertResponse($exp, $this->req("POST", "/"));
$req = new Request("POST", "/");
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
} }
public function testUseIncorrectPath() { public function testUseIncorrectPath() {
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$h = new Versions; $this->assertResponse($exp, $this->req("GET", "/ook"));
$req = new Request("GET", "/ook"); $this->assertResponse($exp, $this->req("OPTIONS", "/ook"));
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
$req = new Request("OPTIONS", "/ook");
$res = $h->dispatch($req);
$this->assertResponse($exp, $res);
} }
} }

View file

@ -19,6 +19,7 @@ use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\TinyTinyRSS\API; use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
use Phake; use Phake;
@ -124,8 +125,22 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
</section> </section>
LONG_STRING; LONG_STRING;
protected function req($data): ResponseInterface { protected function req($data, string $method = "POST", string $target = "", string $strData = null): ResponseInterface {
return $this->h->dispatch(new Request("POST", "", json_encode($data))); $url = "/tt-rss/api".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
'HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded",
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$body = $req->getBody();
if (!is_null($strData)) {
$body->write($strData);
} else {
$body->write(json_encode($data));
}
$req = $req->withBody($body)->withRequestTarget($target);
return $this->h->dispatch($req);
} }
protected function respGood($content = null, $seq = 0): Response { protected function respGood($content = null, $seq = 0): Response {
@ -172,11 +187,11 @@ LONG_STRING;
public function testHandleInvalidPaths() { public function testHandleInvalidPaths() {
$exp = $this->respErr("MALFORMED_INPUT", [], null); $exp = $this->respErr("MALFORMED_INPUT", [], null);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", ""))); $this->assertResponse($exp, $this->req(null, "POST", "", ""));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/", ""))); $this->assertResponse($exp, $this->req(null, "POST", "/", ""));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/index.php", ""))); $this->assertResponse($exp, $this->req(null, "POST", "/index.php", ""));
$exp = new EmptyResponse(404); $exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/bad/path", ""))); $this->assertResponse($exp, $this->req(null, "POST", "/bad/path", ""));
} }
public function testHandleOptionsRequest() { public function testHandleOptionsRequest() {
@ -184,13 +199,13 @@ LONG_STRING;
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/json, text/json", 'Accept' => "application/json, text/json",
]); ]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", ""))); $this->assertResponse($exp, $this->req(null, "OPTIONS", "", ""));
} }
public function testHandleInvalidData() { public function testHandleInvalidData() {
$exp = $this->respErr("MALFORMED_INPUT", [], null); $exp = $this->respErr("MALFORMED_INPUT", [], null);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", "This is not valid JSON data"))); $this->assertResponse($exp, $this->req(null, "POST", "", "This is not valid JSON data"));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", ""))); // lack of data is also an error $this->assertResponse($exp, $this->req(null, "POST", "", "")); // lack of data is also an error
} }
public function testLogIn() { public function testLogIn() {

View file

@ -12,6 +12,8 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\REST\TinyTinyRSS\Icon; use JKingWeb\Arsse\REST\TinyTinyRSS\Icon;
use JKingWeb\Arsse\REST\Request; use JKingWeb\Arsse\REST\Request;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response\EmptyResponse as Response; use Zend\Diactoros\Response\EmptyResponse as Response;
use Phake; use Phake;
@ -32,6 +34,17 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
$this->clearData(); $this->clearData();
} }
protected function req(string $target, $method = "GET"): ResponseInterface {
$url = "/tt-rss/feed-icons/".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$req = $req->withRequestTarget($target);
return $this->h->dispatch($req);
}
public function testRetrieveFavion() { public function testRetrieveFavion() {
Phake::when(Arsse::$db)->subscriptionFavicon->thenReturn(""); Phake::when(Arsse::$db)->subscriptionFavicon->thenReturn("");
Phake::when(Arsse::$db)->subscriptionFavicon(42)->thenReturn("http://example.com/favicon.ico"); Phake::when(Arsse::$db)->subscriptionFavicon(42)->thenReturn("http://example.com/favicon.ico");
@ -39,19 +52,19 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionFavicon(1337)->thenReturn("http://example.org/icon.gif\r\nLocation: http://bad.example.com/"); Phake::when(Arsse::$db)->subscriptionFavicon(1337)->thenReturn("http://example.org/icon.gif\r\nLocation: http://bad.example.com/");
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]); $exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "42.ico"))); $this->assertResponse($exp, $this->req("42.ico"));
$exp = new Response(301, ['Location' => "http://example.net/logo.png"]); $exp = new Response(301, ['Location' => "http://example.net/logo.png"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "2112.ico"))); $this->assertResponse($exp, $this->req("2112.ico"));
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = new Response(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "1337.ico"))); $this->assertResponse($exp, $this->req("1337.ico"));
// these requests should fail // these requests should fail
$exp = new Response(404); $exp = new Response(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "ook.ico"))); $this->assertResponse($exp, $this->req("ook.ico"));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "ook"))); $this->assertResponse($exp, $this->req("ook"));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "47.ico"))); $this->assertResponse($exp, $this->req("47.ico"));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "2112.png"))); $this->assertResponse($exp, $this->req("2112.png"));
// only GET is allowed // only GET is allowed
$exp = new Response(405, ['Allow' => "GET"]); $exp = new Response(405, ['Allow' => "GET"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "2112.ico"))); $this->assertResponse($exp, $this->req("2112.ico", "PUT"));
} }
} }

View file

@ -33,13 +33,13 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
} }
protected function assertResponse(ResponseInterface $exp, ResponseInterface $act, string $text = null) { protected function assertResponse(ResponseInterface $exp, ResponseInterface $act, string $text = null) {
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
$this->assertEquals($exp->getStatusCode(), $act->getStatusCode(), $text); $this->assertEquals($exp->getStatusCode(), $act->getStatusCode(), $text);
$this->assertInstanceOf(get_class($exp), $act); $this->assertInstanceOf(get_class($exp), $act);
if ($exp instanceof JsonResponse) { if ($exp instanceof JsonResponse) {
$this->assertEquals($exp->getPayload(), $act->getPayload(), $text); $this->assertEquals($exp->getPayload(), $act->getPayload(), $text);
$this->assertSame($exp->getPayload(), $act->getPayload(), $text); $this->assertSame($exp->getPayload(), $act->getPayload(), $text);
} }
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
} }
public function approximateTime($exp, $act) { public function approximateTime($exp, $act) {