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

Replace instances of Diactoros' EmptyResponse

This commit is contained in:
J. King 2022-08-04 22:04:39 -04:00
parent 560d4db139
commit 6c0183faea
16 changed files with 231 additions and 233 deletions

View file

@ -7,11 +7,11 @@ declare(strict_types=1);
namespace JKingWeb\Arsse; namespace JKingWeb\Arsse;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Response\EmptyResponse;
class REST { class REST {
public const API_LIST = [ public const API_LIST = [
@ -101,7 +101,7 @@ class REST {
$res = $drv->dispatch($req); $res = $drv->dispatch($req);
} }
} catch (REST\Exception501 $e) { } catch (REST\Exception501 $e) {
$res = new EmptyResponse(501); $res = HTTP::respEmpty(501);
} }
// modify the response so that it has all the required metadata // modify the response so that it has all the required metadata
return $this->normalizeResponse($res, $req); return $this->normalizeResponse($res, $req);
@ -180,7 +180,7 @@ class REST {
} }
// if the response is to a HEAD request, the body should be omitted // if the response is to a HEAD request, the body should be omitted
if ($req && $req->getMethod() === "HEAD") { if ($req && $req->getMethod() === "HEAD") {
$res = new EmptyResponse($res->getStatusCode(), $res->getHeaders()); $res = HTTP::respEmpty($res->getStatusCode(), $res->getHeaders());
} }
// if an Allow header field is present, normalize it // if an Allow header field is present, normalize it
if ($res->hasHeader("Allow")) { if ($res->hasHeader("Allow")) {

View file

@ -10,13 +10,12 @@ use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\XmlResponse; use Laminas\Diactoros\Response\XmlResponse;
use Laminas\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler { class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public const LEVEL = 3; public const LEVEL = 3;
@ -62,11 +61,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$P = $this->normalizeInputPost($req->getParsedBody() ?? []); $P = $this->normalizeInputPost($req->getParsedBody() ?? []);
if (!isset($G['api'])) { if (!isset($G['api'])) {
// 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 HTTP::respEmpty(404);
} }
switch ($req->getMethod()) { switch ($req->getMethod()) {
case "OPTIONS": case "OPTIONS":
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => implode(", ", self::ACCEPTED_TYPES), 'Accept' => implode(", ", self::ACCEPTED_TYPES),
]); ]);
@ -82,7 +81,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$out['auth'] = 1; $out['auth'] = 1;
} elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) {
// 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 HTTP::respEmpty(401);
} }
// produce a full response if authenticated or a basic response otherwise // produce a full response if authenticated or a basic response otherwise
if ($this->logIn(strtolower($P['api_key'] ?? ""))) { if ($this->logIn(strtolower($P['api_key'] ?? ""))) {
@ -93,7 +92,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// return the result, possibly formatted as XML // return the result, possibly formatted as XML
return $this->formatResponse($out, ($G['api'] === "xml")); return $this->formatResponse($out, ($G['api'] === "xml"));
default: default:
return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]); return HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"]);
} }
} }

View file

@ -6,9 +6,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST\Miniflux; namespace JKingWeb\Arsse\REST\Miniflux;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse; use Laminas\Diactoros\Response\TextResponse;
class Status extends \JKingWeb\Arsse\REST\AbstractHandler { class Status extends \JKingWeb\Arsse\REST\AbstractHandler {
@ -18,13 +18,13 @@ class Status extends \JKingWeb\Arsse\REST\AbstractHandler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
$target = parse_url($req->getRequestTarget())['path'] ?? ""; $target = parse_url($req->getRequestTarget())['path'] ?? "";
if (!in_array($target, ["/version", "/healthcheck"])) { if (!in_array($target, ["/version", "/healthcheck"])) {
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
$method = $req->getMethod(); $method = $req->getMethod();
if ($method === "OPTIONS") { if ($method === "OPTIONS") {
return new EmptyResponse(204, ['Allow' => "HEAD, GET"]); return HTTP::respEmpty(204, ['Allow' => "HEAD, GET"]);
} elseif ($method !== "GET") { } elseif ($method !== "GET") {
return new EmptyResponse(405, ['Allow' => "HEAD, GET"]); return HTTP::respEmpty(405, ['Allow' => "HEAD, GET"]);
} }
$out = ""; $out = "";
if ($target === "/version") { if ($target === "/version") {

View file

@ -19,6 +19,7 @@ use JKingWeb\Arsse\ImportExport\OPML;
use JKingWeb\Arsse\ImportExport\Exception as ImportException; use JKingWeb\Arsse\ImportExport\Exception as ImportException;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\REST\Exception; use JKingWeb\Arsse\REST\Exception;
use JKingWeb\Arsse\Rule\Rule; use JKingWeb\Arsse\Rule\Rule;
@ -26,7 +27,6 @@ use JKingWeb\Arsse\User\ExceptionConflict;
use JKingWeb\Arsse\User\Exception as UserException; use JKingWeb\Arsse\User\Exception as UserException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\TextResponse as GenericResponse; use Laminas\Diactoros\Response\TextResponse as GenericResponse;
use Laminas\Diactoros\Uri; use Laminas\Diactoros\Uri;
@ -295,10 +295,10 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
// if there was a REST exception return 400 // if there was a REST exception return 400
return new EmptyResponse(400); return HTTP::respEmpty(400);
} catch (AbstractException $e) { } catch (AbstractException $e) {
// if there was any other Arsse exception return 500 // if there was any other Arsse exception return 500
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
@ -317,11 +317,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return self::CALLS[$url][$method]; return self::CALLS[$url][$method];
} else { } else {
// otherwise return 405 // otherwise return 405
return new EmptyResponse(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]); return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]);
} }
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -451,13 +451,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (in_array("GET", $allowed)) { if (in_array("GET", $allowed)) {
array_unshift($allowed, "HEAD"); array_unshift($allowed, "HEAD");
} }
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => implode(", ", $allowed), 'Allow' => implode(", ", $allowed),
'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON), 'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON),
]); ]);
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -637,7 +637,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionConflict $e) { } catch (ExceptionConflict $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
/** Returns a useful subset of user metadata /** Returns a useful subset of user metadata
@ -728,7 +728,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function transformFeed(array $sub, int $uid, string $rootName, \DateTimeZone $tz): array { protected function transformFeed(array $sub, int $uid, string $rootName, \DateTimeZone $tz): array {
@ -866,7 +866,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function deleteFeed(array $path): ResponseInterface { protected function deleteFeed(array $path): ResponseInterface {
try { try {
Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]); Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
@ -1104,7 +1104,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
assert(isset($in), new \Exception("Unknown status specified")); assert(isset($in), new \Exception("Unknown status specified"));
Arsse::$db->articleMark(Arsse::$user->id, $in, (new Context)->articles($data['entry_ids'])); Arsse::$db->articleMark(Arsse::$user->id, $in, (new Context)->articles($data['entry_ids']));
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function massRead(Context $c): void { protected function massRead(Context $c): void {
@ -1118,7 +1118,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return new ErrorResponse("403", 403); return new ErrorResponse("403", 403);
} }
$this->massRead(new Context); $this->massRead(new Context);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function markFeed(array $path): ResponseInterface { protected function markFeed(array $path): ResponseInterface {
@ -1127,7 +1127,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function markCategory(array $path): ResponseInterface { protected function markCategory(array $path): ResponseInterface {
@ -1144,7 +1144,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function toggleEntryBookmark(array $path): ResponseInterface { protected function toggleEntryBookmark(array $path): ResponseInterface {
@ -1162,7 +1162,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function refreshFeed(array $path): ResponseInterface { protected function refreshFeed(array $path): ResponseInterface {
@ -1172,13 +1172,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function refreshAllFeeds(): ResponseInterface { protected function refreshAllFeeds(): ResponseInterface {
// NOTE: This is a no-op // NOTE: This is a no-op
// It could be implemented, but the need is considered low since we use a dynamic schedule always // It could be implemented, but the need is considered low since we use a dynamic schedule always
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function opmlImport(string $data): ResponseInterface { protected function opmlImport(string $data): ResponseInterface {

View file

@ -18,7 +18,6 @@ use JKingWeb\Arsse\REST\Exception;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
public const VERSION = "11.0.5"; public const VERSION = "11.0.5";
@ -86,19 +85,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($req->getAttribute("authenticated", false)) { if ($req->getAttribute("authenticated", false)) {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} else { } else {
return new EmptyResponse(401); return HTTP::respEmpty(401);
} }
// normalize the input // normalize the input
$data = (string) $req->getBody(); $data = (string) $req->getBody();
if ($data) { 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 (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) { if (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) {
return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); return HTTP::respEmpty(415, ['Accept' => self::ACCEPTED_TYPE]);
} }
$data = @json_decode($data, 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 HTTP::respEmpty(400);
} }
} else { } else {
$data = []; $data = [];
@ -117,10 +116,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
// if there was a REST exception return 400 // if there was a REST exception return 400
return new EmptyResponse(400); return HTTP::respEmpty(400);
} catch (AbstractException $e) { } catch (AbstractException $e) {
// if there was any other Arsse exception return 500 // if there was any other Arsse exception return 500
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
@ -162,11 +161,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
return $this->paths[$url][$method]; return $this->paths[$url][$method];
} else { } else {
// otherwise return 405 // otherwise return 405
return new EmptyResponse(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]); return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]);
} }
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -268,13 +267,13 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (in_array("GET", $allowed)) { if (in_array("GET", $allowed)) {
array_unshift($allowed, "HEAD"); array_unshift($allowed, "HEAD");
} }
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => implode(",", $allowed), 'Allow' => implode(",", $allowed),
'Accept' => self::ACCEPTED_TYPE, 'Accept' => self::ACCEPTED_TYPE,
]); ]);
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -294,12 +293,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// folder already exists // folder already exists
case 10236: return new EmptyResponse(409); case 10236: return HTTP::respEmpty(409);
// folder name not acceptable // folder name not acceptable
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
$folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder)); $folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder));
@ -313,9 +312,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]); Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// folder does not exist // folder does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// rename a folder (also supports moving nesting folders, but this is not a feature of the API) // rename a folder (also supports moving nesting folders, but this is not a feature of the API)
@ -325,24 +324,24 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// folder does not exist // folder does not exist
case 10239: return new EmptyResponse(404); case 10239: return HTTP::respEmpty(404);
// folder already exists // folder already exists
case 10236: return new EmptyResponse(409); case 10236: return HTTP::respEmpty(409);
// folder name not acceptable // folder name not acceptable
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark all articles associated with a folder as read // mark all articles associated with a folder as read
protected function folderMarkRead(array $url, array $data): ResponseInterface { protected function folderMarkRead(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
@ -353,15 +352,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// folder does not exist // folder does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// return list of feeds which should be refreshed // return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): ResponseInterface { protected function feedListStale(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
// list stale feeds which should be checked for updates // list stale feeds which should be checked for updates
$feeds = Arsse::$db->feedListStale(); $feeds = Arsse::$db->feedListStale();
@ -376,21 +375,21 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// refresh a feed // refresh a feed
protected function feedUpdate(array $url, array $data): ResponseInterface { protected function feedUpdate(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
try { try {
Arsse::$db->feedUpdate($data['feedId']); Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10239: // feed does not exist case 10239: // feed does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
case 10237: // feed ID invalid case 10237: // feed ID invalid
return new EmptyResponse(422); return HTTP::respEmpty(422);
default: // other errors related to input default: // other errors related to input
return new EmptyResponse(400); // @codeCoverageIgnore return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// add a new feed // add a new feed
@ -401,10 +400,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']); $id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// feed already exists // feed already exists
return new EmptyResponse(409); return HTTP::respEmpty(409);
} catch (FeedException $e) { } catch (FeedException $e) {
// feed could not be retrieved // feed could not be retrieved
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// if a folder was specified, move the feed to the correct folder; silently ignore errors // if a folder was specified, move the feed to the correct folder; silently ignore errors
if ($data['folderId']) { if ($data['folderId']) {
@ -447,9 +446,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]); Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// feed does not exist // feed does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// rename a feed // rename a feed
@ -459,22 +458,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// subscription does not exist // subscription does not exist
case 10239: return new EmptyResponse(404); case 10239: return HTTP::respEmpty(404);
// name is invalid // name is invalid
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// move a feed to a folder // move a feed to a folder
protected function subscriptionMove(array $url, array $data): ResponseInterface { protected function subscriptionMove(array $url, array $data): ResponseInterface {
// if no folder is specified this is an error // if no folder is specified this is an error
if (!isset($data['folderId'])) { if (!isset($data['folderId'])) {
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// perform the move // perform the move
try { try {
@ -482,22 +481,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10239: // subscription does not exist case 10239: // subscription does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
case 10235: // folder does not exist case 10235: // folder does not exist
case 10237: // folder ID is invalid case 10237: // folder ID is invalid
return new EmptyResponse(422); return HTTP::respEmpty(422);
default: // other errors related to input default: // other errors related to input
return new EmptyResponse(400); // @codeCoverageIgnore return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark all articles associated with a subscription as read // mark all articles associated with a subscription as read
protected function subscriptionMarkRead(array $url, array $data): ResponseInterface { protected function subscriptionMarkRead(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
@ -508,9 +507,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// subscription does not exist // subscription does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// list articles and their properties // list articles and their properties
@ -579,7 +578,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
], [$reverse ? "edition desc" : "edition"]); ], [$reverse ? "edition desc" : "edition"]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID of subscription or folder is not valid // ID of subscription or folder is not valid
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
$out = []; $out = [];
foreach ($items as $item) { foreach ($items as $item) {
@ -593,14 +592,14 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function articleMarkReadAll(array $url, array $data): ResponseInterface { protected function articleMarkReadAll(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
$c->editionRange(null, (int) $data['newestItemId']); $c->editionRange(null, (int) $data['newestItemId']);
// perform the operation // perform the operation
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark a single article as read // mark a single article as read
@ -614,9 +613,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID is not valid // ID is not valid
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark a single article as read // mark a single article as read
@ -630,9 +629,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID is not valid // ID is not valid
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark an array of articles as read // mark an array of articles as read
@ -646,7 +645,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark an array of articles as starred // mark an array of articles as starred
@ -660,7 +659,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function userStatus(array $url, array $data): ResponseInterface { protected function userStatus(array $url, array $data): ResponseInterface {
@ -674,18 +673,18 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function cleanupBefore(array $url, array $data): ResponseInterface { protected function cleanupBefore(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
Service::cleanupPre(); Service::cleanupPre();
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function cleanupAfter(array $url, array $data): ResponseInterface { protected function cleanupAfter(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
Service::cleanupPost(); Service::cleanupPost();
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// return the server version // return the server version

View file

@ -6,10 +6,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST\NextcloudNews; namespace JKingWeb\Arsse\REST\NextcloudNews;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class Versions implements \JKingWeb\Arsse\REST\Handler { class Versions implements \JKingWeb\Arsse\REST\Handler {
public function __construct() { public function __construct() {
@ -18,12 +18,12 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^/?$>D", $req->getRequestTarget())) { if (!preg_match("<^/?$>D", $req->getRequestTarget())) {
// if the request path is more than an empty string or 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 HTTP::respEmpty(404);
} }
switch ($req->getMethod()) { switch ($req->getMethod()) {
case "OPTIONS": case "OPTIONS":
// if the request method is OPTIONS, respond accordingly // if the request method is OPTIONS, respond accordingly
return new EmptyResponse(204, ['Allow' => "HEAD,GET"]); return HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]);
case "GET": case "GET":
// otherwise return the supported versions // otherwise return the supported versions
$out = [ $out = [
@ -34,7 +34,7 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
return new Response($out); return new Response($out);
default: default:
// if any other method was used, this is an error // if any other method was used, this is an error
return new EmptyResponse(405, ['Allow' => "HEAD,GET"]); return HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]);
} }
} }
} }

View file

@ -12,6 +12,7 @@ use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\ExceptionType; use JKingWeb\Arsse\ExceptionType;
@ -21,7 +22,6 @@ use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler { class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public const LEVEL = 15; // emulated API level public const LEVEL = 15; // emulated API level
@ -96,11 +96,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^(?:/(?:index\.php)?)?$>D", $req->getRequestTarget())) { if (!preg_match("<^(?:/(?:index\.php)?)?$>D", $req->getRequestTarget())) {
// reject paths other than the index // reject paths other than the index
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
if ($req->getMethod() === "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 HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => implode(", ", self::ACCEPTED_TYPES), 'Accept' => implode(", ", self::ACCEPTED_TYPES),
]); ]);
@ -125,7 +125,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) {
// 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 HTTP::respEmpty(401);
} }
if (strtolower((string) $data['op']) !== "login") { if (strtolower((string) $data['op']) !== "login") {
// unless logging in, a session identifier is required // unless logging in, a session identifier is required
@ -148,7 +148,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
'content' => $e->getData(), 'content' => $e->getData(),
]); ]);
} catch (AbstractException $e) { } catch (AbstractException $e) {
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
} else { } else {
// absence of a request body indicates an error // absence of a request body indicates an error

View file

@ -7,10 +7,10 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\TinyTinyRSS; namespace JKingWeb\Arsse\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\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() {
@ -22,25 +22,25 @@ class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} elseif ($req->getAttribute("authenticationFailed", false) || Arsse::$conf->userHTTPAuthRequired) { } elseif ($req->getAttribute("authenticationFailed", false) || Arsse::$conf->userHTTPAuthRequired) {
// otherwise if HTTP authentication failed or did not occur when it is required, deny access at the HTTP level // otherwise if HTTP authentication failed or did not occur when it is required, deny access at the HTTP level
return new Response(401); return HTTP::respEmpty(401);
} }
if ($req->getMethod() !== "GET") { if ($req->getMethod() !== "GET") {
// only GET requests are allowed // only GET requests are allowed
return new Response(405, ['Allow' => "GET"]); return HTTP::respEmpty(405, ['Allow' => "GET"]);
} elseif (!preg_match("<^(\d+)\.ico$>D", $req->getRequestTarget(), $match) || !((int) $match[1])) { } elseif (!preg_match("<^(\d+)\.ico$>D", $req->getRequestTarget(), $match) || !((int) $match[1])) {
return new Response(404); return HTTP::respEmpty(404);
} }
try { try {
$url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'] ?? null; $url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'] ?? null;
if (!$url) { if (!$url) {
return new Response(404); return HTTP::respEmpty(404);
} }
if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) { if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) {
$url = substr($url, 0, $pos); $url = substr($url, 0, $pos);
} }
return new Response(301, ['Location' => $url]); return HTTP::respEmpty(301, ['Location' => $url]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new Response(404); return HTTP::respEmpty(404);
} }
} }
} }

View file

@ -9,6 +9,7 @@ namespace JKingWeb\Arsse\TestCase\REST\Fever;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
@ -17,7 +18,6 @@ use JKingWeb\Arsse\REST\Fever\API;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\XmlResponse; use Laminas\Diactoros\Response\XmlResponse;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\Fever\API<extended> */ /** @covers \JKingWeb\Arsse\REST\Fever\API<extended> */
class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
@ -194,7 +194,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideTokenAuthenticationRequests(): iterable { public function provideTokenAuthenticationRequests(): iterable {
$success = new JsonResponse(['auth' => 1]); $success = new JsonResponse(['auth' => 1]);
$failure = new JsonResponse(['auth' => 0]); $failure = new JsonResponse(['auth' => 0]);
$denied = new EmptyResponse(401); $denied = HTTP::respEmpty(401);
return [ return [
[false, true, null, [], ['api' => null], $failure], [false, true, null, [], ['api' => null], $failure],
[false, false, null, [], ['api' => null], $failure], [false, false, null, [], ['api' => null], $failure],
@ -421,8 +421,8 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideInvalidRequests(): iterable { public function provideInvalidRequests(): iterable {
return [ return [
'Not an API request' => ["", "", "POST", null, new EmptyResponse(404)], 'Not an API request' => ["", "", "POST", null, HTTP::respEmpty(404)],
'Wrong method' => ["api", "", "PUT", null, new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])], 'Wrong method' => ["api", "", "PUT", null, HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"])],
'Non-standard method' => ["api", "", "GET", null, new JsonResponse([])], 'Non-standard method' => ["api", "", "GET", null, new JsonResponse([])],
'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this 'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this
'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this 'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this
@ -494,7 +494,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testAnswerOptionsRequest(): void { public function testAnswerOptionsRequest(): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/x-www-form-urlencoded, multipart/form-data", 'Accept' => "application/x-www-form-urlencoded, multipart/form-data",
]); ]);

View file

@ -6,10 +6,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\Miniflux; namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\REST\Miniflux\Status; use JKingWeb\Arsse\REST\Miniflux\Status;
use JKingWeb\Arsse\REST\Miniflux\V1; use JKingWeb\Arsse\REST\Miniflux\V1;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse; use Laminas\Diactoros\Response\TextResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\Status */ /** @covers \JKingWeb\Arsse\REST\Miniflux\Status */
@ -23,12 +23,12 @@ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideRequests(): iterable { public function provideRequests(): iterable {
return [ return [
["/version", "GET", new TextResponse(V1::VERSION)], ["/version", "GET", new TextResponse(V1::VERSION)],
["/version", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], ["/version", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])],
["/version", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], ["/version", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])],
["/healthcheck", "GET", new TextResponse("OK")], ["/healthcheck", "GET", new TextResponse("OK")],
["/healthcheck", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], ["/healthcheck", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])],
["/healthcheck", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], ["/healthcheck", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])],
["/version/", "GET", new EmptyResponse(404)], ["/version/", "GET", HTTP::respEmpty(404)],
]; ];
} }
} }

View file

@ -14,6 +14,7 @@ use JKingWeb\Arsse\Context\RootContext;
use JKingWeb\Arsse\Context\UnionContext; use JKingWeb\Arsse\Context\UnionContext;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\REST\Miniflux\V1; use JKingWeb\Arsse\REST\Miniflux\V1;
@ -26,7 +27,6 @@ use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse; use Laminas\Diactoros\Response\TextResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\V1<extended> */ /** @covers \JKingWeb\Arsse\REST\Miniflux\V1<extended> */
@ -102,7 +102,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideAuthResponses */ /** @dataProvider provideAuthResponses */
public function testAuthenticateAUser($token, bool $auth, bool $success): void { public function testAuthenticateAUser($token, bool $auth, bool $success): void {
$exp = $success ? new EmptyResponse(404) : new ErrorResponse("401", 401); $exp = $success ? HTTP::respEmpty(404) : new ErrorResponse("401", 401);
$user = "john.doe@example.com"; $user = "john.doe@example.com";
if ($token !== null) { if ($token !== null) {
$headers = ['X-Auth-Token' => $token]; $headers = ['X-Auth-Token' => $token];
@ -133,7 +133,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideInvalidPaths */ /** @dataProvider provideInvalidPaths */
public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void {
$exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []);
$this->assertMessage($exp, $this->req($method, $path)); $this->assertMessage($exp, $this->req($method, $path));
} }
@ -148,7 +148,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideOptionsRequests */ /** @dataProvider provideOptionsRequests */
public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => $allow, 'Allow' => $allow,
'Accept' => $accept, 'Accept' => $accept,
]); ]);
@ -382,7 +382,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->method("remove")->willReturn(true); Arsse::$user->method("remove")->willReturn(true);
Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112); Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112);
Arsse::$user->expects($this->exactly(1))->method("remove")->with("john.doe@example.com"); Arsse::$user->expects($this->exactly(1))->method("remove")->with("john.doe@example.com");
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/users/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/users/2112"));
} }
public function testDeleteAMissingUser(): void { public function testDeleteAMissingUser(): void {
@ -484,7 +484,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testDeleteARealCategory(): void { public function testDeleteARealCategory(): void {
$this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/2112"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111);
$this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/categories/47")); $this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/categories/47"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 46); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 46);
@ -497,7 +497,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 2112], ['id' => 2112],
]))); ])));
$this->dbMock->subscriptionRemove->returns(true); $this->dbMock->subscriptionRemove->returns(true);
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/1")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/1"));
Phony::inOrder( Phony::inOrder(
$this->dbMock->begin->calledWith(), $this->dbMock->begin->calledWith(),
$this->dbMock->subscriptionList->calledWith("john.doe@example.com", null, false), $this->dbMock->subscriptionList->calledWith("john.doe@example.com", null, false),
@ -680,7 +680,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testDeleteAFeed(): void { public function testDeleteAFeed(): void {
$this->dbMock->subscriptionRemove->returns(true); $this->dbMock->subscriptionRemove->returns(true);
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/feeds/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/feeds/2112"));
$this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112); $this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112);
} }
@ -864,9 +864,9 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
[['entry_ids' => [1], 'status' => 1], null, new ErrorResponse(["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)], [['entry_ids' => [1], 'status' => 1], null, new ErrorResponse(["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)],
[['entry_ids' => [0], 'status' => "read"], null, new ErrorResponse(["InvalidInputValue", 'field' => "entry_ids"], 422)], [['entry_ids' => [0], 'status' => "read"], null, new ErrorResponse(["InvalidInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1], 'status' => "reread"], null, new ErrorResponse(["InvalidInputValue", 'field' => "status"], 422)], [['entry_ids' => [1], 'status' => "reread"], null, new ErrorResponse(["InvalidInputValue", 'field' => "status"], 422)],
[['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], HTTP::respEmpty(204)],
]; ];
} }
@ -889,13 +889,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
self::clearData(); self::clearData();
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
return [ return [
["/users/42/mark-all-as-read", $c, 1123, new EmptyResponse(204)], ["/users/42/mark-all-as-read", $c, 1123, HTTP::respEmpty(204)],
["/users/2112/mark-all-as-read", $c, null, new ErrorResponse("403", 403)], ["/users/2112/mark-all-as-read", $c, null, new ErrorResponse("403", 403)],
["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, new EmptyResponse(204)], ["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, HTTP::respEmpty(204)],
["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)],
["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, new EmptyResponse(204)], ["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, HTTP::respEmpty(204)],
["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)],
["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, new EmptyResponse(204)], ["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, HTTP::respEmpty(204)],
]; ];
} }
@ -929,15 +929,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideBookmarkTogglings(): iterable { public function provideBookmarkTogglings(): iterable {
self::clearData(); self::clearData();
return [ return [
[1, true, new EmptyResponse(204)], [1, true, HTTP::respEmpty(204)],
[0, false, new EmptyResponse(204)], [0, false, HTTP::respEmpty(204)],
[new ExceptionInput("subjectMissing"), null, new ErrorResponse("404", 404)], [new ExceptionInput("subjectMissing"), null, new ErrorResponse("404", 404)],
]; ];
} }
public function testRefreshAFeed(): void { public function testRefreshAFeed(): void {
$this->dbMock->subscriptionPropertiesGet->returns([]); $this->dbMock->subscriptionPropertiesGet->returns([]);
$this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/47/refresh")); $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/47/refresh"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 47); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 47);
} }
@ -948,7 +948,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRefreshAllFeeds(): void { public function testRefreshAllFeeds(): void {
$this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/refresh")); $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/refresh"));
} }
/** @dataProvider provideImports */ /** @dataProvider provideImports */

View file

@ -11,13 +11,13 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\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 Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\NextcloudNews\V1_2<extended> */ /** @covers \JKingWeb\Arsse\REST\NextcloudNews\V1_2<extended> */
class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
@ -336,13 +336,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testSendAuthenticationChallenge(): void { public function testSendAuthenticationChallenge(): void {
$exp = new EmptyResponse(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->req("GET", "/", "", [], false)); $this->assertMessage($exp, $this->req("GET", "/", "", [], false));
} }
/** @dataProvider provideInvalidPaths */ /** @dataProvider provideInvalidPaths */
public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void {
$exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []);
$this->assertMessage($exp, $this->req($method, $path)); $this->assertMessage($exp, $this->req($method, $path));
} }
@ -374,16 +374,16 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRespondToInvalidInputTypes(): void { public function testRespondToInvalidInputTypes(): void {
$exp = new EmptyResponse(415, ['Accept' => "application/json"]); $exp = HTTP::respEmpty(415, ['Accept' => "application/json"]);
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"])); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"]));
$exp = new EmptyResponse(400); $exp = HTTP::respEmpty(400);
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>')); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>'));
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null])); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null]));
} }
/** @dataProvider provideOptionsRequests */ /** @dataProvider provideOptionsRequests */
public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => $allow, 'Allow' => $allow,
'Accept' => $accept, 'Accept' => $accept,
]); ]);
@ -436,19 +436,19 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
[['name' => "Software"], false, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])], [['name' => "Software"], false, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])],
[['name' => "Hardware"], true, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], [['name' => "Hardware"], true, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])],
[['name' => "Hardware"], false, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], [['name' => "Hardware"], false, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])],
[['name' => "Software"], true, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], [['name' => "Software"], true, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)],
[['name' => ""], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => ""], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => " "], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => " "], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => null], true, new ExceptionInput("missing"), new EmptyResponse(422)], [['name' => null], true, new ExceptionInput("missing"), HTTP::respEmpty(422)],
]; ];
} }
public function testRemoveAFolder(): void { public function testRemoveAFolder(): void {
$this->dbMock->folderRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->folderRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("DELETE", "/folders/1")); $this->assertMessage($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 = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("DELETE", "/folders/1")); $this->assertMessage($exp, $this->req("DELETE", "/folders/1"));
$this->dbMock->folderRemove->times(2)->calledWith($this->userId, 1); $this->dbMock->folderRemove->times(2)->calledWith($this->userId, 1);
} }
@ -467,12 +467,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFolderRenamings(): array { public function provideFolderRenamings(): array {
return [ return [
[['name' => "Software"], 1, true, new EmptyResponse(204)], [['name' => "Software"], 1, true, HTTP::respEmpty(204)],
[['name' => "Software"], 2, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], [['name' => "Software"], 2, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)],
[['name' => "Software"], 3, new ExceptionInput("subjectMissing"), new EmptyResponse(404)], [['name' => "Software"], 3, new ExceptionInput("subjectMissing"), HTTP::respEmpty(404)],
[['name' => ""], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => ""], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => " "], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => " "], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => null], 2, new ExceptionInput("missing"), new EmptyResponse(422)], [['name' => null], 2, new ExceptionInput("missing"), HTTP::respEmpty(422)],
]; ];
} }
@ -540,19 +540,19 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
return [ return [
[['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new Response(['feeds' => [$this->feeds['rest'][0]]])], [['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new Response(['feeds' => [$this->feeds['rest'][0]]])],
[['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, new Response(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])], [['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, new Response(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])],
[['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new EmptyResponse(409)], [['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), HTTP::respEmpty(409)],
[['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, new EmptyResponse(409)], [['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, HTTP::respEmpty(409)],
[[], $feedException, 0, [], false, new EmptyResponse(422)], [[], $feedException, 0, [], false, HTTP::respEmpty(422)],
[['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), new Response(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])], [['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), new Response(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])],
]; ];
} }
public function testRemoveASubscription(): void { public function testRemoveASubscription(): void {
$this->dbMock->subscriptionRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); $this->assertMessage($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 = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); $this->assertMessage($exp, $this->req("DELETE", "/feeds/1"));
$this->dbMock->subscriptionRemove->times(2)->calledWith($this->userId, 1); $this->dbMock->subscriptionRemove->times(2)->calledWith($this->userId, 1);
} }
@ -571,17 +571,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => 2112])->throws(new ExceptionInput("idMissing")); // folder does not exist $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => 2112])->throws(new ExceptionInput("idMissing")); // folder does not exist
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => -1])->throws(new ExceptionInput("typeViolation")); // folder is invalid $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => -1])->throws(new ExceptionInput("typeViolation")); // folder is invalid
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); // subscription does not exist $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); // subscription does not exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5])));
} }
@ -601,17 +601,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => ""]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => ""]))->throws(new ExceptionInput("missing"));
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => false]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => false]))->throws(new ExceptionInput("missing"));
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6])));
} }
@ -633,7 +633,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testListStaleFeedsWithoutAuthority(): void { public function testListStaleFeedsWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/all")); $this->assertMessage($exp, $this->req("GET", "/feeds/all"));
$this->dbMock->feedListStale->never()->called(); $this->dbMock->feedListStale->never()->called();
} }
@ -649,11 +649,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->feedUpdate->with(42)->returns(true); $this->dbMock->feedUpdate->with(42)->returns(true);
$this->dbMock->feedUpdate->with(2112)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->feedUpdate->with(2112)->throws(new ExceptionInput("subjectMissing"));
$this->dbMock->feedUpdate->with($this->lessThan(1))->throws(new ExceptionInput("typeViolation")); $this->dbMock->feedUpdate->with($this->lessThan(1))->throws(new ExceptionInput("typeViolation"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[1]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4])));
@ -661,7 +661,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testUpdateAFeedWithoutAuthority(): void { public function testUpdateAFeedWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42])); $this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42]));
$this->dbMock->feedUpdate->never()->called(); $this->dbMock->feedUpdate->never()->called();
} }
@ -684,7 +684,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$t = Date::normalize(time()); $t = Date::normalize(time());
$out = new Result($this->v($this->articles['db'])); $out = new Result($this->v($this->articles['db']));
$r200 = new Response(['items' => $this->articles['rest']]); $r200 = new Response(['items' => $this->articles['rest']]);
$r422 = new EmptyResponse(422); $r422 = HTTP::respEmpty(422);
return [ return [
["/items", [], clone $c, $out, $r200], ["/items", [], clone $c, $out, $r200],
["/items", ['type' => 0, 'id' => 42], (clone $c)->subscription(42), new ExceptionInput("idMissing"), $r422], ["/items", ['type' => 0, 'id' => 42], (clone $c)->subscription(42), new ExceptionInput("idMissing"), $r422],
@ -720,13 +720,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(1)->editionRange(null, 2112)->hidden(false)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // folder doesn't exist $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // folder doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/folders/42/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/folders/42/read", $in));
} }
@ -735,13 +735,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(1)->editionRange(null, 2112)->hidden(false)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // subscription doesn't exist $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // subscription doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/read", $in));
} }
@ -749,10 +749,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$read = ['read' => true]; $read = ['read' => true];
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->editionRange(null, 2112)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->editionRange(null, 2112)))->returns(42);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/items/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/items/read")); $this->assertMessage($exp, $this->req("PUT", "/items/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=ook"));
} }
@ -770,12 +770,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleMark->with($this->userId, $star, $this->equalTo((new Context)->article(2112)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist $this->dbMock->articleMark->with($this->userId, $star, $this->equalTo((new Context)->article(2112)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(4)))->returns(42); $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(4)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(1337)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(1337)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/1/read")); $this->assertMessage($exp, $this->req("PUT", "/items/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/2/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/2/unread"));
$this->assertMessage($exp, $this->req("PUT", "/items/1/3/star")); $this->assertMessage($exp, $this->req("PUT", "/items/1/3/star"));
$this->assertMessage($exp, $this->req("PUT", "/items/4400/4/unstar")); $this->assertMessage($exp, $this->req("PUT", "/items/4400/4/unstar"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/items/42/read")); $this->assertMessage($exp, $this->req("PUT", "/items/42/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/47/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/47/unread"));
$this->assertMessage($exp, $this->req("PUT", "/items/1/2112/star")); $this->assertMessage($exp, $this->req("PUT", "/items/1/2112/star"));
@ -801,7 +801,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->anything())->returns(42); $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->anything())->returns(42);
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->editions([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->editions([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->articles([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->articles([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/read/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/read/multiple"));
$this->assertMessage($exp, $this->req("PUT", "/items/unread/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/unread/multiple"));
$this->assertMessage($exp, $this->req("PUT", "/items/star/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/star/multiple"));
@ -860,28 +860,28 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testCleanUpBeforeUpdate(): void { public function testCleanUpBeforeUpdate(): void {
$this->dbMock->feedCleanup->with()->returns(true); $this->dbMock->feedCleanup->with()->returns(true);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
$this->dbMock->feedCleanup->calledWith(); $this->dbMock->feedCleanup->calledWith();
} }
public function testCleanUpBeforeUpdateWithoutAuthority(): void { public function testCleanUpBeforeUpdateWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
$this->dbMock->feedCleanup->never()->called(); $this->dbMock->feedCleanup->never()->called();
} }
public function testCleanUpAfterUpdate(): void { public function testCleanUpAfterUpdate(): void {
$this->dbMock->articleCleanup->with()->returns(true); $this->dbMock->articleCleanup->with()->returns(true);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
$this->dbMock->articleCleanup->calledWith(); $this->dbMock->articleCleanup->calledWith();
} }
public function testCleanUpAfterUpdateWithoutAuthority(): void { public function testCleanUpAfterUpdateWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
$this->dbMock->feedCleanup->never()->called(); $this->dbMock->feedCleanup->never()->called();
} }

View file

@ -6,10 +6,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews; namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\REST\NextcloudNews\Versions; use JKingWeb\Arsse\REST\NextcloudNews\Versions;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\NextcloudNews\Versions */ /** @covers \JKingWeb\Arsse\REST\NextcloudNews\Versions */
class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest { class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
@ -32,17 +32,17 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRespondToOptionsRequest(): void { public function testRespondToOptionsRequest(): void {
$exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]); $exp = HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]);
$this->assertMessage($exp, $this->req("OPTIONS", "/")); $this->assertMessage($exp, $this->req("OPTIONS", "/"));
} }
public function testUseIncorrectMethod(): void { public function testUseIncorrectMethod(): void {
$exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]); $exp = HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]);
$this->assertMessage($exp, $this->req("POST", "/")); $this->assertMessage($exp, $this->req("POST", "/"));
} }
public function testUseIncorrectPath(): void { public function testUseIncorrectPath(): void {
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("GET", "/ook")); $this->assertMessage($exp, $this->req("GET", "/ook"));
$this->assertMessage($exp, $this->req("OPTIONS", "/ook")); $this->assertMessage($exp, $this->req("OPTIONS", "/ook"));
} }

View file

@ -12,13 +12,13 @@ use JKingWeb\Arsse\REST;
use JKingWeb\Arsse\REST\Exception501; use JKingWeb\Arsse\REST\Exception501;
use JKingWeb\Arsse\REST\NextcloudNews\V1_2 as NCN; use JKingWeb\Arsse\REST\NextcloudNews\V1_2 as NCN;
use JKingWeb\Arsse\REST\TinyTinyRSS\API as TTRSS; use JKingWeb\Arsse\REST\TinyTinyRSS\API as TTRSS;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Request; use Laminas\Diactoros\Request;
use Laminas\Diactoros\Response; use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\Response\TextResponse; use Laminas\Diactoros\Response\TextResponse;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST */ /** @covers \JKingWeb\Arsse\REST */
class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
@ -96,7 +96,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function testSendAuthenticationChallenges(): void { public function testSendAuthenticationChallenges(): void {
self::setConf(); self::setConf();
$r = new REST(); $r = new REST();
$in = new EmptyResponse(401); $in = HTTP::respEmpty(401);
$exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"'); $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"');
$act = $r->challenge($in, "OOK"); $act = $r->challenge($in, "OOK");
$this->assertMessage($exp, $act); $this->assertMessage($exp, $act);
@ -190,8 +190,8 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function testAddCorsHeaders(string $reqMethod, array $reqHeaders, array $resHeaders, array $expHeaders): void { public function testAddCorsHeaders(string $reqMethod, array $reqHeaders, array $resHeaders, array $expHeaders): void {
$r = new REST(); $r = new REST();
$req = new Request("", $reqMethod, "php://memory", $reqHeaders); $req = new Request("", $reqMethod, "php://memory", $reqHeaders);
$res = new EmptyResponse(204, $resHeaders); $res = HTTP::respEmpty(204, $resHeaders);
$exp = new EmptyResponse(204, $expHeaders); $exp = HTTP::respEmpty(204, $expHeaders);
$act = $r->corsApply($res, $req); $act = $r->corsApply($res, $req);
$this->assertMessage($exp, $act); $this->assertMessage($exp, $act);
} }
@ -268,21 +268,21 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
$stream = fopen("php://memory", "w+b"); $stream = fopen("php://memory", "w+b");
fwrite($stream, "ook"); fwrite($stream, "ook");
return [ return [
[new EmptyResponse(204), new EmptyResponse(204)], [HTTP::respEmpty(204), HTTP::respEmpty(204)],
[new EmptyResponse(401), new EmptyResponse(401, ['WWW-Authenticate' => "Fake Value"])], [HTTP::respEmpty(401), HTTP::respEmpty(401, ['WWW-Authenticate' => "Fake Value"])],
[new EmptyResponse(204, ['Allow' => "PUT"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "PUT,OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT,OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => ["PUT", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => ["PUT", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, DELETE, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, DELETE, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "HEAD,GET"]), new EmptyResponse(204, ['Allow' => "HEAD, GET, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]), HTTP::respEmpty(204, ['Allow' => "HEAD, GET, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "GET"]), new EmptyResponse(204, ['Allow' => "GET, HEAD, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "GET"]), HTTP::respEmpty(204, ['Allow' => "GET, HEAD, OPTIONS"])],
[new TextResponse("ook", 200), new TextResponse("ook", 200, ['Content-Length' => "3"])], [new TextResponse("ook", 200), new TextResponse("ook", 200, ['Content-Length' => "3"])],
[new TextResponse("", 200), new TextResponse("", 200, ['Content-Length' => "0"])], [new TextResponse("", 200), new TextResponse("", 200, ['Content-Length' => "0"])],
[new TextResponse("ook", 404), new TextResponse("ook", 404, ['Content-Length' => "3"])], [new TextResponse("ook", 404), new TextResponse("ook", 404, ['Content-Length' => "3"])],
[new TextResponse("", 404), new TextResponse("", 404)], [new TextResponse("", 404), new TextResponse("", 404)],
[new Response($stream, 200), new Response($stream, 200, ['Content-Length' => "3"]), new Request("", "GET")], [new Response($stream, 200), new Response($stream, 200, ['Content-Length' => "3"]), new Request("", "GET")],
[new Response($stream, 200), new EmptyResponse(200, ['Content-Length' => "3"]), new Request("", "HEAD")], [new Response($stream, 200), HTTP::respEmpty(200, ['Content-Length' => "3"]), new Request("", "HEAD")],
]; ];
} }
@ -297,7 +297,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
}); });
if ($called) { if ($called) {
$hMock = $this->mock($class); $hMock = $this->mock($class);
$hMock->dispatch->returns(new EmptyResponse(204)); $hMock->dispatch->returns(HTTP::respEmpty(204));
$this->objMock->get->with($class)->returns($hMock); $this->objMock->get->with($class)->returns($hMock);
Arsse::$obj = $this->objMock->get(); Arsse::$obj = $this->objMock->get();
} }

View file

@ -11,6 +11,7 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
@ -18,7 +19,6 @@ use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended> /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */ * @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
@ -188,12 +188,12 @@ LONG_STRING;
$this->assertMessage($exp, $this->req(null, "POST", "", "")); $this->assertMessage($exp, $this->req(null, "POST", "", ""));
$this->assertMessage($exp, $this->req(null, "POST", "/", "")); $this->assertMessage($exp, $this->req(null, "POST", "/", ""));
$this->assertMessage($exp, $this->req(null, "POST", "/index.php", "")); $this->assertMessage($exp, $this->req(null, "POST", "/index.php", ""));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req(null, "POST", "/bad/path", "")); $this->assertMessage($exp, $this->req(null, "POST", "/bad/path", ""));
} }
public function testHandleOptionsRequest(): void { public function testHandleOptionsRequest(): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/json, text/json", 'Accept' => "application/json, text/json",
]); ]);
@ -215,7 +215,7 @@ LONG_STRING;
$this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true); $this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true);
$this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation"); $this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation");
$this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold"); $this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold");
if ($sessions instanceof EmptyResponse) { if ($sessions instanceof ResponseInterface) {
$exp1 = $sessions; $exp1 = $sessions;
$exp2 = $sessions; $exp2 = $sessions;
} elseif ($sessions) { } elseif ($sessions) {
@ -260,7 +260,7 @@ LONG_STRING;
'op' => "isLoggedIn", 'op' => "isLoggedIn",
'sid' => $data, 'sid' => $data,
]; ];
if ($result instanceof EmptyResponse) { if ($result instanceof ResponseInterface) {
$exp1 = $result; $exp1 = $result;
$exp2 = null; $exp2 = null;
} elseif ($result) { } elseif ($result) {
@ -333,7 +333,7 @@ LONG_STRING;
'userHTTPAuthRequired' => true, 'userHTTPAuthRequired' => true,
'userSessionEnforced' => false, 'userSessionEnforced' => false,
]; ];
$http401 = new EmptyResponse(401); $http401 = HTTP::respEmpty(401);
if ($type === "login") { if ($type === "login") {
return [ return [
// conf, user, data, result // conf, user, data, result
@ -532,7 +532,7 @@ LONG_STRING;
'user' => $this->userId, 'user' => $this->userId,
'password' => "secret", 'password' => "secret",
]; ];
$exp = new EmptyResponse(500); $exp = HTTP::respEmpty(500);
$this->assertMessage($exp, $this->req($data)); $this->assertMessage($exp, $this->req($data));
} }

View file

@ -9,10 +9,10 @@ namespace JKingWeb\Arsse\TestCase\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\REST\TinyTinyRSS\Icon; use JKingWeb\Arsse\REST\TinyTinyRSS\Icon;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse as Response;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon<extended> */ /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon<extended> */
class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest { class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
@ -51,21 +51,21 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionIcon->with($this->anything(), 2112, false)->returns(['url' => "http://example.net/logo.png"]); $this->dbMock->subscriptionIcon->with($this->anything(), 2112, false)->returns(['url' => "http://example.net/logo.png"]);
$this->dbMock->subscriptionIcon->with($this->anything(), 1337, false)->returns(['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"]); $this->dbMock->subscriptionIcon->with($this->anything(), 1337, false)->returns(['url' => "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 = HTTP::respEmpty(301, ['Location' => "http://example.com/favicon.ico"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$exp = new Response(301, ['Location' => "http://example.net/logo.png"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.net/logo.png"]);
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
// these requests should fail // these requests should fail
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("ook.ico")); $this->assertMessage($exp, $this->req("ook.ico"));
$this->assertMessage($exp, $this->req("ook")); $this->assertMessage($exp, $this->req("ook"));
$this->assertMessage($exp, $this->req("47.ico")); $this->assertMessage($exp, $this->req("47.ico"));
$this->assertMessage($exp, $this->req("2112.png")); $this->assertMessage($exp, $this->req("2112.png"));
$this->assertMessage($exp, $this->req("1123.ico")); $this->assertMessage($exp, $this->req("1123.ico"));
// only GET is allowed // only GET is allowed
$exp = new Response(405, ['Allow' => "GET"]); $exp = HTTP::respEmpty(405, ['Allow' => "GET"]);
$this->assertMessage($exp, $this->req("2112.ico", "PUT")); $this->assertMessage($exp, $this->req("2112.ico", "PUT"));
} }
@ -79,32 +79,32 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionIcon->with(null, 2112, false)->returns($url); $this->dbMock->subscriptionIcon->with(null, 2112, false)->returns($url);
$this->dbMock->subscriptionIcon->with(null, 1337, false)->returns($url); $this->dbMock->subscriptionIcon->with(null, 1337, false)->returns($url);
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
$this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("42.ico"));
$this->assertMessage($exp, $this->reqAuth("1337.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico"));
// these requests should fail // these requests should fail
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->reqAuth("2112.ico")); $this->assertMessage($exp, $this->reqAuth("2112.ico"));
$exp = new Response(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("42.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico"));
// with HTTP auth required, only authenticated requests should succeed // with HTTP auth required, only authenticated requests should succeed
self::setConf(['userHTTPAuthRequired' => true]); self::setConf(['userHTTPAuthRequired' => true]);
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("42.ico"));
$this->assertMessage($exp, $this->reqAuth("1337.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico"));
// anything else should fail // anything else should fail
$exp = new Response(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("42.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico"));
// resources for the wrtong user should still fail, too // resources for the wrtong user should still fail, too
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->reqAuth("2112.ico")); $this->assertMessage($exp, $this->reqAuth("2112.ico"));
} }
} }