From 459e44e04159a67898f28fb9bd4ca1353aa4743a Mon Sep 17 00:00:00 2001 From: "J. King" Date: Sat, 6 Aug 2022 16:03:50 -0400 Subject: [PATCH] Address remaining errors Still many failures to fix --- lib/REST/Miniflux/ErrorResponse.php | 19 -- lib/REST/Miniflux/V1.php | 132 ++++----- tests/cases/REST/Fever/TestAPI.php | 3 +- .../cases/REST/Miniflux/TestErrorResponse.php | 22 -- tests/cases/REST/Miniflux/TestStatus.php | 5 +- tests/cases/REST/Miniflux/TestV1.php | 255 +++++++++--------- tests/cases/REST/TestREST.php | 6 +- tests/cases/REST/TinyTinyRSS/TestAPI.php | 32 +-- tests/lib/AbstractTest.php | 19 +- tests/phpunit.dist.xml | 1 - 10 files changed, 233 insertions(+), 261 deletions(-) delete mode 100644 lib/REST/Miniflux/ErrorResponse.php delete mode 100644 tests/cases/REST/Miniflux/TestErrorResponse.php diff --git a/lib/REST/Miniflux/ErrorResponse.php b/lib/REST/Miniflux/ErrorResponse.php deleted file mode 100644 index 1cf467ee..00000000 --- a/lib/REST/Miniflux/ErrorResponse.php +++ /dev/null @@ -1,19 +0,0 @@ - Arsse::$lang->msg("API.Miniflux.Error.".$msg, $data)]; - parent::__construct($data, $status, $headers, $encodingOptions); - } -} diff --git a/lib/REST/Miniflux/V1.php b/lib/REST/Miniflux/V1.php index 7ff9df24..b1fb512f 100644 --- a/lib/REST/Miniflux/V1.php +++ b/lib/REST/Miniflux/V1.php @@ -212,6 +212,14 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { public function __construct() { } + + public static function respError($data, int $status = 400, array $headers = []): ResponseInterface { + assert(isset(Arsse::$lang) && Arsse::$lang instanceof \JKingWeb\Arsse\Lang, new \Exception("Language database must be initialized before use")); + $data = (array) $data; + $msg = array_shift($data); + $data = ["error_message" => Arsse::$lang->msg("API.Miniflux.Error.".$msg, $data)]; + return HTTP::respJson($data, $status, $headers); + } protected function authenticate(ServerRequestInterface $req): bool { // first check any tokens; this is what Miniflux does @@ -245,7 +253,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } // try to authenticate if (!$this->authenticate($req)) { - return new ErrorResponse("401", 401); + return self::respError("401", 401); } $func = $this->chooseCall($target, $method); if ($func instanceof ResponseInterface) { @@ -254,7 +262,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { [$func, $reqAdmin, $reqPath, $reqBody, $reqQuery, $reqFields] = $func; } if ($reqAdmin && !$this->isAdmin()) { - return new ErrorResponse("403", 403); + return self::respError("403", 403); } $args = []; if ($reqPath) { @@ -269,7 +277,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $data = @json_decode($data, true); if (json_last_error() !== \JSON_ERROR_NONE) { // if the body could not be parsed as JSON, return "400 Bad Request" - return new ErrorResponse(["InvalidBodyJSON", json_last_error_msg()], 400); + return self::respError(["InvalidBodyJSON", json_last_error_msg()], 400); } } else { $data = []; @@ -344,20 +352,20 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { if (!isset($body[$k])) { $body[$k] = null; } elseif (gettype($body[$k]) !== $t) { - return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); + return self::respError(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); } elseif ( (in_array($k, ["keeplist_rules", "blocklist_rules"]) && !Rule::validate($body[$k])) || (in_array($k, ["url", "feed_url"]) && !URL::absolute($body[$k])) || ($k === "category_id" && $body[$k] < 1) || ($k === "status" && !in_array($body[$k], ["read", "unread", "removed"])) ) { - return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); + return self::respError(["InvalidInputValue", 'field' => $k], 422); } elseif ($k === "entry_ids") { foreach ($body[$k] as $v) { if (gettype($v) !== "integer") { - return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => "integer", 'actual' => gettype($v)], 422); + return self::respError(["InvalidInputType", 'field' => $k, 'expected' => "integer", 'actual' => gettype($v)], 422); } elseif ($v < 1) { - return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); + return self::respError(["InvalidInputValue", 'field' => $k], 422); } } } @@ -369,16 +377,16 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $body[$k] = null; } elseif ($k === "entry_sorting_direction") { if (!in_array($body[$k], ["asc", "desc"])) { - return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); + return self::respError(["InvalidInputValue", 'field' => $k], 422); } } elseif (gettype($body[$k]) !== $t) { - return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); + return self::respError(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); } } // check for any missing required values foreach ($req as $k) { if (!isset($body[$k]) || (is_array($body[$k]) && !$body[$k])) { - return new ErrorResponse(["MissingInputValue", 'field' => $k], 422); + return self::respError(["MissingInputValue", 'field' => $k], 422); } } return $body; @@ -407,7 +415,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { if ($seen[$k] && !$a) { // if the key has already been seen and it's not an array field, bail // NOTE: Miniflux itself simply ignores duplicates entirely - return new ErrorResponse(["DuplicateInputValue", 'field' => $k], 400); + return self::respError(["DuplicateInputValue", 'field' => $k], 400); } $seen[$k] = true; if ($k === "starred") { @@ -423,7 +431,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $out[$k] = V::normalize($v, $t + V::M_STRICT, "unix"); } } catch (ExceptionType $e) { - return new ErrorResponse(["InvalidInputValue", 'field' => $k], 400); + return self::respError(["InvalidInputValue", 'field' => $k], 400); } // perform additional validation if ( @@ -433,7 +441,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { || ($k === "order" && !in_array($v, ["id", "status", "published_at", "category_title", "category_id"])) || ($k === "status" && !in_array($v, ["read", "unread", "removed"])) ) { - return new ErrorResponse(["InvalidInputValue", 'field' => $k], 400); + return self::respError(["InvalidInputValue", 'field' => $k], 400); } } return $out; @@ -525,7 +533,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { 10507 => "Fetch401", 10521 => "Fetch404", ][$e->getCode()] ?? "FetchOther"; - return new ErrorResponse($msg, 502); + return self::respError($msg, 502); } $out = []; foreach ($list as $url) { @@ -544,7 +552,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->listUsers([$path[1]], true)[0] ?? new \stdClass); } catch (UserException $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -553,7 +561,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $user = Arsse::$user->lookup((int) $path[1]); return HTTP::respJson($this->listUsers([$user], true)[0] ?? new \stdClass); } catch (UserException $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -570,13 +578,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (UserException $e) { switch ($e->getCode()) { case 10403: - return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409); + return self::respError(["DuplicateUser", 'user' => $data['username']], 409); case 10441: - return new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422); + return self::respError(["InvalidInputValue", 'field' => "timezone"], 422); case 10443: - return new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422); + return self::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422); case 10444: - return new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422); + return self::respError(["InvalidInputValue", 'field' => "username"], 422); } throw $e; // @codeCoverageIgnore } @@ -589,16 +597,16 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { if (((int) $path[1]) === $user['num']) { if ($data['is_admin'] && !$user['admin']) { // non-admins should not be able to set themselves as admin - return new ErrorResponse("InvalidElevation", 403); + return self::respError("InvalidElevation", 403); } $user = Arsse::$user->id; } elseif (!$user['admin']) { - return new ErrorResponse("403", 403); + return self::respError("403", 403); } else { try { $user = Arsse::$user->lookup((int) $path[1]); } catch (ExceptionConflict $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } // make any requested changes @@ -616,13 +624,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (UserException $e) { switch ($e->getCode()) { case 10403: - return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409); + return self::respError(["DuplicateUser", 'user' => $data['username']], 409); case 10441: - return new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422); + return self::respError(["InvalidInputValue", 'field' => "timezone"], 422); case 10443: - return new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422); + return self::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422); case 10444: - return new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422); + return self::respError(["InvalidInputValue", 'field' => "username"], 422); } throw $e; // @codeCoverageIgnore } @@ -633,7 +641,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { Arsse::$user->remove(Arsse::$user->lookup((int) $path[1])); } catch (ExceptionConflict $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -673,9 +681,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $id = Arsse::$db->folderAdd(Arsse::$user->id, ['name' => (string) $data['title']]); } catch (ExceptionInput $e) { if ($e->getCode() === 10236) { - return new ErrorResponse(["DuplicateCategory", 'title' => $data['title']], 409); + return self::respError(["DuplicateCategory", 'title' => $data['title']], 409); } else { - return new ErrorResponse(["InvalidCategory", 'title' => $data['title']], 422); + return self::respError(["InvalidCategory", 'title' => $data['title']], 422); } } $meta = Arsse::$user->propertiesGet(Arsse::$user->id, false); @@ -698,11 +706,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } } catch (ExceptionInput $e) { if ($e->getCode() === 10236) { - return new ErrorResponse(["DuplicateCategory", 'title' => $title], 409); + return self::respError(["DuplicateCategory", 'title' => $title], 409); } elseif (in_array($e->getCode(), [10237, 10239])) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } else { - return new ErrorResponse(["InvalidCategory", 'title' => $title], 422); + return self::respError(["InvalidCategory", 'title' => $title], 422); } } $meta = Arsse::$user->propertiesGet(Arsse::$user->id, false); @@ -724,7 +732,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $tr->commit(); } } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -788,7 +796,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } } catch (ExceptionInput $e) { // the folder does not exist - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respJson($out); } @@ -800,7 +808,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $sub = Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); return HTTP::respJson($this->transformFeed($sub, $meta['num'], $meta['root'], $meta['tz'])); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -823,13 +831,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { 10521 => "Fetch404", 10522 => "FetchFormat", ][$e->getCode()] ?? "FetchOther"; - return new ErrorResponse($msg, 502); + return self::respError($msg, 502); } catch (ExceptionInput $e) { switch ($e->getCode()) { case 10235: - return new ErrorResponse("MissingCategory", 422); + return self::respError("MissingCategory", 422); case 10236: - return new ErrorResponse("DuplicateFeed", 409); + return self::respError("DuplicateFeed", 409); } } return HTTP::respJson(['feed_id' => $id], 201); @@ -851,11 +859,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { switch ($e->getCode()) { case 10231: case 10232: - return new ErrorResponse("InvalidTitle", 422); + return self::respError("InvalidTitle", 422); case 10235: - return new ErrorResponse("MissingCategory", 422); + return self::respError("MissingCategory", 422); case 10239: - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } return $this->getFeed($path)->withStatus(201); @@ -866,7 +874,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]); return HTTP::respEmpty(204); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -874,10 +882,10 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { $icon = Arsse::$db->subscriptionIcon(Arsse::$user->id, (int) $path[1]); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } if (!$icon || !$icon['type'] || !$icon['data']) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respJson([ 'id' => (int) $icon['id'], @@ -1038,7 +1046,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->listEntries($query, new Context)); } catch (ExceptionInput $e) { - return new ErrorResponse("MissingCategory", 400); + return self::respError("MissingCategory", 400); } } @@ -1048,7 +1056,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { return HTTP::respJson($this->listEntries($query, $c)); } catch (ExceptionInput $e) { // FIXME: this should differentiate between a missing feed and a missing category, but doesn't - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1057,7 +1065,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->listEntries($query, new Context)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1065,7 +1073,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->findEntry((int) $path[1])); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1074,7 +1082,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->findEntry((int) $path[3], $c)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1088,7 +1096,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { return HTTP::respJson($this->findEntry((int) $path[3], $c)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1113,7 +1121,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { // this function is restricted to the logged-in user $user = Arsse::$user->propertiesGet(Arsse::$user->id, false); if (((int) $path[1]) !== $user['num']) { - return new ErrorResponse("403", 403); + return self::respError("403", 403); } $this->massRead(new Context); return HTTP::respEmpty(204); @@ -1123,7 +1131,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { $this->massRead((new Context)->subscription((int) $path[1])); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -1140,7 +1148,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { $this->massRead($c); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -1158,7 +1166,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } $tr->commit(); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -1168,7 +1176,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { try { Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } return HTTP::respEmpty(204); } @@ -1185,18 +1193,18 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (ImportException $e) { switch ($e->getCode()) { case 10611: - return new ErrorResponse("InvalidBodyXML", 400); + return self::respError("InvalidBodyXML", 400); case 10612: - return new ErrorResponse("InvalidBodyOPML", 422); + return self::respError("InvalidBodyOPML", 422); case 10613: - return new ErrorResponse("InvalidImportCategory", 422); + return self::respError("InvalidImportCategory", 422); case 10614: - return new ErrorResponse("DuplicateImportCategory", 422); + return self::respError("DuplicateImportCategory", 422); case 10615: - return new ErrorResponse("InvalidImportLabel", 422); + return self::respError("InvalidImportLabel", 422); } } catch (FeedException $e) { - return new ErrorResponse(["FailedImportFeed", 'url' => $e->getParams()['url'], 'code' => $e->getCode()], 502); + return self::respError(["FailedImportFeed", 'url' => $e->getParams()['url'], 'code' => $e->getCode()], 502); } return HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")]); } diff --git a/tests/cases/REST/Fever/TestAPI.php b/tests/cases/REST/Fever/TestAPI.php index 2f624da1..ff809647 100644 --- a/tests/cases/REST/Fever/TestAPI.php +++ b/tests/cases/REST/Fever/TestAPI.php @@ -16,7 +16,6 @@ use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\REST\Fever\API; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\XmlResponse; /** @covers \JKingWeb\Arsse\REST\Fever\API */ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { @@ -472,7 +471,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { 'items' => $this->articles['rest'], 'total_items' => 1024, ]); - $exp = new XmlResponse("1018Article title 1<p>Article content 1</p>http://example.com/1009466848001028Article title 2<p>Article content 2</p>http://example.com/2019467712001039Article title 3<p>Article content 3</p>http://example.com/3109468576001049Article title 4<p>Article content 4</p>http://example.com/41194694400010510Article title 5<p>Article content 5</p>http://example.com/5009470304001024"); + $exp = HTTP::respXml("1018Article title 1<p>Article content 1</p>http://example.com/1009466848001028Article title 2<p>Article content 2</p>http://example.com/2019467712001039Article title 3<p>Article content 3</p>http://example.com/3109468576001049Article title 4<p>Article content 4</p>http://example.com/41194694400010510Article title 5<p>Article content 5</p>http://example.com/5009470304001024"); $this->assertMessage($exp, $this->req("api=xml")); } diff --git a/tests/cases/REST/Miniflux/TestErrorResponse.php b/tests/cases/REST/Miniflux/TestErrorResponse.php deleted file mode 100644 index 5852b4d0..00000000 --- a/tests/cases/REST/Miniflux/TestErrorResponse.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame('{"error_message":"Access Unauthorized"}', (string) $act->getBody()); - } - - public function testCreateVariableResponse(): void { - $act = new ErrorResponse(["InvalidBodyJSON", "Doh!"], 401); - $this->assertSame('{"error_message":"Invalid JSON payload: Doh!"}', (string) $act->getBody()); - } -} diff --git a/tests/cases/REST/Miniflux/TestStatus.php b/tests/cases/REST/Miniflux/TestStatus.php index cf573e3e..4975d4af 100644 --- a/tests/cases/REST/Miniflux/TestStatus.php +++ b/tests/cases/REST/Miniflux/TestStatus.php @@ -10,7 +10,6 @@ use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\REST\Miniflux\Status; use JKingWeb\Arsse\REST\Miniflux\V1; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\TextResponse; /** @covers \JKingWeb\Arsse\REST\Miniflux\Status */ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest { @@ -22,10 +21,10 @@ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest { public function provideRequests(): iterable { return [ - ["/version", "GET", new TextResponse(V1::VERSION)], + ["/version", "GET", HTTP::respText(V1::VERSION)], ["/version", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])], ["/version", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])], - ["/healthcheck", "GET", new TextResponse("OK")], + ["/healthcheck", "GET", HTTP::respText("OK")], ["/healthcheck", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])], ["/healthcheck", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])], ["/version/", "GET", HTTP::respEmpty(404)], diff --git a/tests/cases/REST/Miniflux/TestV1.php b/tests/cases/REST/Miniflux/TestV1.php index b1dec747..ca4ae1fa 100644 --- a/tests/cases/REST/Miniflux/TestV1.php +++ b/tests/cases/REST/Miniflux/TestV1.php @@ -26,7 +26,6 @@ use JKingWeb\Arsse\User\ExceptionConflict; use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput; use JKingWeb\Arsse\Test\Result; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\TextResponse; /** @covers \JKingWeb\Arsse\REST\Miniflux\V1 */ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { @@ -101,7 +100,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideAuthResponses */ public function testAuthenticateAUser($token, bool $auth, bool $success): void { - $exp = $success ? HTTP::respEmpty(404) : new ErrorResponse("401", 401); + $exp = $success ? HTTP::respEmpty(404) : V1::respError("401", 401); $user = "john.doe@example.com"; if ($token !== null) { $headers = ['X-Auth-Token' => $token]; @@ -165,7 +164,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { } public function testRejectBadlyTypedData(): void { - $exp = new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422); + $exp = V1::respError(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422); $this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112])); } @@ -183,10 +182,10 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { return [ ["http://localhost:8000/Feed/Discovery/Valid", HTTP::respJson($discovered)], ["http://localhost:8000/Feed/Discovery/Invalid", HTTP::respJson([])], - ["http://localhost:8000/Feed/Discovery/Missing", new ErrorResponse("Fetch404", 502)], - [1, new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)], - ["Not a URL", new ErrorResponse(["InvalidInputValue", 'field' => "url"], 422)], - [null, new ErrorResponse(["MissingInputValue", 'field' => "url"], 422)], + ["http://localhost:8000/Feed/Discovery/Missing", V1::respError("Fetch404", 502)], + [1, V1::respError(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)], + ["Not a URL", V1::respError(["InvalidInputValue", 'field' => "url"], 422)], + [null, V1::respError(["MissingInputValue", 'field' => "url"], 422)], ]; } @@ -231,16 +230,16 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { [true, "/users/1", HTTP::respJson(self::USERS[0])], [true, "/users/jane.doe@example.com", HTTP::respJson(self::USERS[1])], [true, "/users/2", HTTP::respJson(self::USERS[1])], - [true, "/users/jack.doe@example.com", new ErrorResponse("404", 404)], - [true, "/users/47", new ErrorResponse("404", 404)], - [false, "/users", new ErrorResponse("403", 403)], + [true, "/users/jack.doe@example.com", V1::respError("404", 404)], + [true, "/users/47", V1::respError("404", 404)], + [false, "/users", V1::respError("403", 403)], [false, "/me", HTTP::respJson(self::USERS[1])], - [false, "/users/john.doe@example.com", new ErrorResponse("403", 403)], - [false, "/users/1", new ErrorResponse("403", 403)], - [false, "/users/jane.doe@example.com", new ErrorResponse("403", 403)], - [false, "/users/2", new ErrorResponse("403", 403)], - [false, "/users/jack.doe@example.com", new ErrorResponse("403", 403)], - [false, "/users/47", new ErrorResponse("403", 403)], + [false, "/users/john.doe@example.com", V1::respError("403", 403)], + [false, "/users/1", V1::respError("403", 403)], + [false, "/users/jane.doe@example.com", V1::respError("403", 403)], + [false, "/users/2", V1::respError("403", 403)], + [false, "/users/jack.doe@example.com", V1::respError("403", 403)], + [false, "/users/47", V1::respError("403", 403)], ]; } @@ -305,21 +304,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $resp1 = array_merge(self::USERS[1], ['username' => "john.doe@example.com"]); $resp2 = array_merge(self::USERS[1], ['id' => 1, 'is_admin' => true]); return [ - [false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, new ErrorResponse(["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)], - [false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)], - [false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse("403", 403)], - [false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, new ErrorResponse("InvalidElevation", 403)], + [false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, V1::respError(["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)], + [false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)], + [false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, V1::respError("403", 403)], + [false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, V1::respError("InvalidElevation", 403)], [false, "/users/2", ['language' => "fr_CA"], null, null, null, null, ['lang' => "fr_CA"], $out1, HTTP::respJson($resp1, 201)], [false, "/users/2", ['entry_sorting_direction' => "asc"], null, null, null, null, ['sort_asc' => true], $out1, HTTP::respJson($resp1, 201)], [false, "/users/2", ['entry_sorting_direction' => "desc"], null, null, null, null, ['sort_asc' => false], $out1, HTTP::respJson($resp1, 201)], - [false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422)], - [false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422)], - [false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422)], - [false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, new ErrorResponse(["DuplicateUser", 'user' => "ook"], 409)], + [false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422)], + [false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), V1::respError(["InvalidInputValue", 'field' => "timezone"], 422)], + [false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "username"], 422)], + [false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, V1::respError(["DuplicateUser", 'user' => "ook"], 409)], [false, "/users/2", ['password' => "ook"], null, null, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['password' => "ook"]), 201)], [false, "/users/2", ['username' => "ook", 'password' => "ook"], "ook", true, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['username' => "ook", 'password' => "ook"]), 201)], [true, "/users/1", ['theme' => "stark"], null, null, null, null, ['theme' => "stark"], $out2, HTTP::respJson($resp2, 201)], - [true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse("404", 404)], + [true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, V1::respError("404", 404)], ]; } @@ -360,18 +359,18 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideUserAdditions(): iterable { $resp1 = array_merge(self::USERS[1], ['username' => "ook", 'password' => "eek"]); return [ - [[], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "username"], 422)], - [['username' => "ook"], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "password"], 422)], - [['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, new ErrorResponse(["DuplicateUser", 'user' => "ook"], 409)], - [['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422)], - [['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422)], - [['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422)], + [[], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "username"], 422)], + [['username' => "ook"], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "password"], 422)], + [['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, V1::respError(["DuplicateUser", 'user' => "ook"], 409)], + [['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, V1::respError(["InvalidInputValue", 'field' => "username"], 422)], + [['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), V1::respError(["InvalidInputValue", 'field' => "timezone"], 422)], + [['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422)], [['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], HTTP::respJson($resp1, 201)], ]; } public function testAddAUserWithoutAuthority(): void { - $this->assertMessage(new ErrorResponse("403", 403), $this->req("POST", "/users", [])); + $this->assertMessage(V1::respError("403", 403), $this->req("POST", "/users", [])); } public function testDeleteAUser(): void { @@ -391,13 +390,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { Arsse::$user->method("remove")->willReturn(true); Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112); Arsse::$user->expects($this->exactly(0))->method("remove"); - $this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/users/2112")); + $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/users/2112")); } public function testDeleteAUserWithoutAuthority(): void { Arsse::$user->expects($this->exactly(0))->method("lookup"); Arsse::$user->expects($this->exactly(0))->method("remove"); - $this->assertMessage(new ErrorResponse("403", 403), $this->req("DELETE", "/users/2112")); + $this->assertMessage(V1::respError("403", 403), $this->req("DELETE", "/users/2112")); } public function testListCategories(): void { @@ -440,11 +439,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideCategoryAdditions(): iterable { return [ ["New", HTTP::respJson(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)], - ["Duplicate", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 409)], - ["", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], - [" ", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], - [null, new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], - [false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], + ["Duplicate", V1::respError(["DuplicateCategory", 'title' => "Duplicate"], 409)], + ["", V1::respError(["InvalidCategory", 'title' => ""], 422)], + [" ", V1::respError(["InvalidCategory", 'title' => " "], 422)], + [null, V1::respError(["MissingInputValue", 'field' => "title"], 422)], + [false, V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], ]; } @@ -465,19 +464,19 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideCategoryUpdates(): iterable { return [ - [3, "New", "subjectMissing", new ErrorResponse("404", 404)], + [3, "New", "subjectMissing", V1::respError("404", 404)], [2, "New", true, HTTP::respJson(['id' => 2, 'title' => "New", 'user_id' => 42], 201)], - [2, "Duplicate", "constraintViolation", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 409)], - [2, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], - [2, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], - [2, null, "missing", new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], - [2, false, "subjectMissing", new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], + [2, "Duplicate", "constraintViolation", V1::respError(["DuplicateCategory", 'title' => "Duplicate"], 409)], + [2, "", "missing", V1::respError(["InvalidCategory", 'title' => ""], 422)], + [2, " ", "whitespace", V1::respError(["InvalidCategory", 'title' => " "], 422)], + [2, null, "missing", V1::respError(["MissingInputValue", 'field' => "title"], 422)], + [2, false, "subjectMissing", V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], [1, "New", true, HTTP::respJson(['id' => 1, 'title' => "New", 'user_id' => 42], 201)], [1, "Duplicate", "constraintViolation", HTTP::respJson(['id' => 1, 'title' => "Duplicate", 'user_id' => 42], 201)], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used - [1, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], - [1, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], - [1, null, "missing", new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], - [1, false, false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], + [1, "", "missing", V1::respError(["InvalidCategory", 'title' => ""], 422)], + [1, " ", "whitespace", V1::respError(["InvalidCategory", 'title' => " "], 422)], + [1, null, "missing", V1::respError(["MissingInputValue", 'field' => "title"], 422)], + [1, false, false, V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], ]; } @@ -485,7 +484,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/2112")); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111); - $this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/categories/47")); + $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/categories/47")); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 46); } @@ -529,7 +528,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testListFeedsOfAMissingCategory(): void { $this->dbMock->subscriptionList->throws(new ExceptionInput("idMissing")); - $exp = new ErrorResponse("404", 404); + $exp = V1::respError("404", 404); $this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds")); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true); } @@ -544,7 +543,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testGetAMissingFeed(): void { $this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing")); - $this->assertMessage(new ErrorResponse("404", 404), $this->req("GET", "/feeds/1")); + $this->assertMessage(V1::respError("404", 404), $this->req("GET", "/feeds/1")); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1); } @@ -610,29 +609,29 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideFeedCreations(): iterable { self::clearData(); return [ - [['category_id' => 1], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "feed_url"], 422)], - [['feed_url' => "http://example.com/"], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "category_id"], 422)], - [['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, new ErrorResponse(["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)], - [['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "feed_url"], 422)], - [['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "category_id"], 422)], - [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "keeplist_rules"], 422)], - [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "blocklist_rules"], 422)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, new ErrorResponse("Fetch404", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, new ErrorResponse("Fetch403", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, new ErrorResponse("Fetch401", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, new ErrorResponse("FetchOther", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, new ErrorResponse("Fetch404", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, new ErrorResponse("FetchFormat", 502)], - [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, new ErrorResponse("DuplicateFeed", 409)], - [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, new ErrorResponse("MissingCategory", 422)], + [['category_id' => 1], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "feed_url"], 422)], + [['feed_url' => "http://example.com/"], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "category_id"], 422)], + [['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, V1::respError(["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)], + [['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "feed_url"], 422)], + [['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "category_id"], 422)], + [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "keeplist_rules"], 422)], + [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "blocklist_rules"], 422)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, V1::respError("Fetch404", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, V1::respError("Fetch403", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, V1::respError("Fetch401", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, V1::respError("FetchOther", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, V1::respError("Fetch404", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, V1::respError("FetchFormat", 502)], + [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, V1::respError("DuplicateFeed", 409)], + [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, V1::respError("MissingCategory", 422)], [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, true, null, HTTP::respJson(['feed_id' => 44], 201)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "^A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)], @@ -657,11 +656,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $success = HTTP::respJson(self::FEEDS_OUT[0], 201); return [ [[], [], true, $success], - [[], [], new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], - [['title' => ""], ['title' => ""], new ExceptionInput("missing"), new ErrorResponse("InvalidTitle", 422)], - [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse("InvalidTitle", 422)], - [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse("InvalidTitle", 422)], - [['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), new ErrorResponse("MissingCategory", 422)], + [[], [], new ExceptionInput("subjectMissing"), V1::respError("404", 404)], + [['title' => ""], ['title' => ""], new ExceptionInput("missing"), V1::respError("InvalidTitle", 422)], + [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError("InvalidTitle", 422)], + [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError("InvalidTitle", 422)], + [['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), V1::respError("MissingCategory", 422)], [['crawler' => false], ['scrape' => false], true, $success], [['keeplist_rules' => ""], ['keep_rule' => ""], true, $success], [['blocklist_rules' => "ook"], ['block_rule' => "ook"], true, $success], @@ -685,7 +684,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testDeleteAMissingFeed(): void { $this->dbMock->subscriptionRemove->throws(new ExceptionInput("subjectMissing")); - $this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/feeds/2112")); + $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/feeds/2112")); $this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112); } @@ -703,11 +702,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideIcons(): iterable { return [ [['id' => 44, 'type' => "image/svg+xml", 'data' => ""], HTTP::respJson(['id' => 44, 'data' => "image/svg+xml;base64,PHN2Zy8+", 'mime_type' => "image/svg+xml"])], - [['id' => 47, 'type' => "", 'data' => ""], new ErrorResponse("404", 404)], - [['id' => 47, 'type' => null, 'data' => ""], new ErrorResponse("404", 404)], - [['id' => 47, 'type' => null, 'data' => null], new ErrorResponse("404", 404)], - [null, new ErrorResponse("404", 404)], - [new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], + [['id' => 47, 'type' => "", 'data' => ""], V1::respError("404", 404)], + [['id' => 47, 'type' => null, 'data' => ""], V1::respError("404", 404)], + [['id' => 47, 'type' => null, 'data' => null], V1::respError("404", 404)], + [null, V1::respError("404", 404)], + [new ExceptionInput("subjectMissing"), V1::respError("404", 404)], ]; } @@ -743,17 +742,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $c = (new Context)->limit(100); $o = ["modified_date"]; // the default sort order return [ - ["/entries?after=A", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "after"], 400)], - ["/entries?before=B", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "before"], 400)], - ["/entries?category_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "category_id"], 400)], - ["/entries?after_entry_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "after_entry_id"], 400)], - ["/entries?before_entry_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "before_entry_id"], 400)], - ["/entries?limit=-1", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "limit"], 400)], - ["/entries?offset=-1", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "offset"], 400)], - ["/entries?direction=sideways", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "direction"], 400)], - ["/entries?order=false", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "order"], 400)], - ["/entries?starred&starred", null, null, [], false, new ErrorResponse(["DuplicateInputValue", 'field' => "starred"], 400)], - ["/entries?after&after=0", null, null, [], false, new ErrorResponse(["DuplicateInputValue", 'field' => "after"], 400)], + ["/entries?after=A", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "after"], 400)], + ["/entries?before=B", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "before"], 400)], + ["/entries?category_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "category_id"], 400)], + ["/entries?after_entry_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "after_entry_id"], 400)], + ["/entries?before_entry_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "before_entry_id"], 400)], + ["/entries?limit=-1", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "limit"], 400)], + ["/entries?offset=-1", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "offset"], 400)], + ["/entries?direction=sideways", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "direction"], 400)], + ["/entries?order=false", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "order"], 400)], + ["/entries?starred&starred", null, null, [], false, V1::respError(["DuplicateInputValue", 'field' => "starred"], 400)], + ["/entries?after&after=0", null, null, [], false, V1::respError(["DuplicateInputValue", 'field' => "after"], 400)], ["/entries", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?category_id=47", (clone $c)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?category_id=1", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], @@ -790,15 +789,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ["/entries?order=category_id&direction=desc", $c, ["top_folder desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=category_title&direction=desc", $c, ["top_folder_name desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=status&direction=desc", $c, ["hidden desc", "unread"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("MissingCategory")], + ["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError("MissingCategory")], ["/feeds/42/entries", (clone $c)->subscription(42), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/feeds/42/entries?category_id=47", (clone $c)->subscription(42)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("404", 404)], + ["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, V1::respError("404", 404)], ["/categories/42/entries", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/42/entries?category_id=47", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/42/entries?starred", (clone $c)->folder(41)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/1/entries", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("404", 404)], + ["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError("404", 404)], ]; } @@ -828,15 +827,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $c = new Context; return [ ["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])], - ["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], + ["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)], ["/feeds/47/entries/42", (clone $c)->subscription(47)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])], - ["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], new ErrorResponse("404", 404)], - ["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], - ["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], + ["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], V1::respError("404", 404)], + ["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)], + ["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), V1::respError("404", 404)], ["/categories/47/entries/42", (clone $c)->folder(46)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])], - ["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], new ErrorResponse("404", 404)], - ["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], - ["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], + ["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], V1::respError("404", 404)], + ["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)], + ["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), V1::respError("404", 404)], ["/categories/1/entries/42", (clone $c)->folderShallow(0)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])], ]; } @@ -855,14 +854,14 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideEntryMarkings(): iterable { self::clearData(); return [ - [['status' => "read"], null, new ErrorResponse(["MissingInputValue", 'field' => "entry_ids"], 422)], - [['entry_ids' => [1]], null, new ErrorResponse(["MissingInputValue", 'field' => "status"], 422)], - [['entry_ids' => [], 'status' => "read"], null, new ErrorResponse(["MissingInputValue", 'field' => "entry_ids"], 422)], - [['entry_ids' => 1, 'status' => "read"], null, new ErrorResponse(["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)], - [['entry_ids' => ["1"], 'status' => "read"], null, new ErrorResponse(["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 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' => [1], 'status' => "reread"], null, new ErrorResponse(["InvalidInputValue", 'field' => "status"], 422)], + [['status' => "read"], null, V1::respError(["MissingInputValue", 'field' => "entry_ids"], 422)], + [['entry_ids' => [1]], null, V1::respError(["MissingInputValue", 'field' => "status"], 422)], + [['entry_ids' => [], 'status' => "read"], null, V1::respError(["MissingInputValue", 'field' => "entry_ids"], 422)], + [['entry_ids' => 1, 'status' => "read"], null, V1::respError(["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)], + [['entry_ids' => ["1"], 'status' => "read"], null, V1::respError(["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 422)], + [['entry_ids' => [1], 'status' => 1], null, V1::respError(["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)], + [['entry_ids' => [0], 'status' => "read"], null, V1::respError(["InvalidInputValue", 'field' => "entry_ids"], 422)], + [['entry_ids' => [1], 'status' => "reread"], null, V1::respError(["InvalidInputValue", 'field' => "status"], 422)], [['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], HTTP::respEmpty(204)], [['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], HTTP::respEmpty(204)], [['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], HTTP::respEmpty(204)], @@ -889,11 +888,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { $c = (new Context)->hidden(false); return [ ["/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, V1::respError("403", 403)], ["/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"), V1::respError("404", 404)], ["/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"), V1::respError("404", 404)], ["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, HTTP::respEmpty(204)], ]; } @@ -930,7 +929,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { return [ [1, true, HTTP::respEmpty(204)], [0, false, HTTP::respEmpty(204)], - [new ExceptionInput("subjectMissing"), null, new ErrorResponse("404", 404)], + [new ExceptionInput("subjectMissing"), null, V1::respError("404", 404)], ]; } @@ -942,7 +941,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testRefreshAMissingFeed(): void { $this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing")); - $this->assertMessage(new ErrorResponse("404", 404), $this->req("PUT", "/feeds/2112/refresh")); + $this->assertMessage(V1::respError("404", 404), $this->req("PUT", "/feeds/2112/refresh")); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 2112); } @@ -963,12 +962,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideImports(): iterable { self::clearData(); return [ - [new ImportException("invalidSyntax"), new ErrorResponse("InvalidBodyXML", 400)], - [new ImportException("invalidSemantics"), new ErrorResponse("InvalidBodyOPML", 422)], - [new ImportException("invalidFolderName"), new ErrorResponse("InvalidImportCategory", 422)], - [new ImportException("invalidFolderCopy"), new ErrorResponse("DuplicateImportCategory", 422)], - [new ImportException("invalidTagName"), new ErrorResponse("InvalidImportLabel", 422)], - [new FeedException("invalidUrl", ['url' => "http://example.com/"]), new ErrorResponse(["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)], + [new ImportException("invalidSyntax"), V1::respError("InvalidBodyXML", 400)], + [new ImportException("invalidSemantics"), V1::respError("InvalidBodyOPML", 422)], + [new ImportException("invalidFolderName"), V1::respError("InvalidImportCategory", 422)], + [new ImportException("invalidFolderCopy"), V1::respError("DuplicateImportCategory", 422)], + [new ImportException("invalidTagName"), V1::respError("InvalidImportLabel", 422)], + [new FeedException("invalidUrl", ['url' => "http://example.com/"]), V1::respError(["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)], [true, HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")])], ]; } @@ -976,8 +975,8 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testExport(): void { $opml = $this->mock(OPML::class); $this->objMock->get->with(OPML::class)->returns($opml); - $opml->export->returns("EXPORT DATA"); - $this->assertMessage(new TextResponse("EXPORT DATA", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export")); + $opml->export->returns(""); + $this->assertMessage(HTTP::respText("", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export")); $opml->export->calledWith(Arsse::$user->id); } } diff --git a/tests/cases/REST/TestREST.php b/tests/cases/REST/TestREST.php index 58dfa524..9dd98612 100644 --- a/tests/cases/REST/TestREST.php +++ b/tests/cases/REST/TestREST.php @@ -242,7 +242,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { ["OPTIONS", ['Origin' => "http://example", 'Access-Control-Request-Headers' => ["Content-Type", "If-None-Match"]], [], [ 'Access-Control-Allow-Origin' => "http://example", 'Access-Control-Allow-Credentials' => "true", - 'Access-Control-Allow-Headers' => "Content-Type,If-None-Match", + 'Access-Control-Allow-Headers' => "Content-Type, If-None-Match", 'Access-Control-Max-Age' => (string) (60 * 60 * 24), 'Vary' => "Origin", ]], @@ -280,8 +280,8 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { [HTTP::respText("", 200), HTTP::respText("", 200, ['Content-Length' => "0"])], [HTTP::respText("ook", 404), HTTP::respText("ook", 404, ['Content-Length' => "3"])], [HTTP::respText("", 404), HTTP::respText("", 404)], - [new Response(200, [], $stream), new Response(200, ['Content-Length' => "3"], $stream), new Request("", "GET")], - [new Response(200, [], $stream), HTTP::respEmpty(200, ['Content-Length' => "3"]), new Request("", "HEAD")], + [new Response(200, [], $stream), new Response(200, ['Content-Length' => "3"], $stream), new Request("GET", "")], + [new Response(200, [], $stream), HTTP::respEmpty(200, ['Content-Length' => "3"]), new Request("HEAD", "")], ]; } diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index 96d50c30..c31a6655 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -1685,10 +1685,10 @@ LONG_STRING; $this->assertMessage($this->outputHeadlines(1), $test); // test 'show_content' $test = $this->req($in[1]); - $this->assertArrayHasKey("content", $test->getPayload()['content'][0]); - $this->assertArrayHasKey("content", $test->getPayload()['content'][1]); + $this->assertArrayHasKey("content", $this->extractMessageJson($test)['content'][0]); + $this->assertArrayHasKey("content", $this->extractMessageJson($test)['content'][1]); foreach ($this->generateHeadlines(1) as $key => $row) { - $this->assertSame($row['content'], $test->getPayload()['content'][$key]['content']); + $this->assertSame($row['content'], $this->extractMessageJson($test)['content'][$key]['content']); } // test 'include_attachments' $test = $this->req($in[2]); @@ -1704,22 +1704,22 @@ LONG_STRING; 'post_id' => "2112", ], ]; - $this->assertArrayHasKey("attachments", $test->getPayload()['content'][0]); - $this->assertArrayHasKey("attachments", $test->getPayload()['content'][1]); - $this->assertSame([], $test->getPayload()['content'][0]['attachments']); - $this->assertSame($exp, $test->getPayload()['content'][1]['attachments']); + $this->assertArrayHasKey("attachments", $this->extractMessageJson($test)['content'][0]); + $this->assertArrayHasKey("attachments", $this->extractMessageJson($test)['content'][1]); + $this->assertSame([], $this->extractMessageJson($test)['content'][0]['attachments']); + $this->assertSame($exp, $this->extractMessageJson($test)['content'][1]['attachments']); // test 'include_header' $test = $this->req($in[3]); $exp = $this->respGood([ ['id' => -4, 'is_cat' => false, 'first_id' => 1], - $this->outputHeadlines(1)->getPayload()['content'], + $this->extractMessageJson($this->outputHeadlines(1))['content'], ]); $this->assertMessage($exp, $test); // test 'include_header' with a category $test = $this->req($in[4]); $exp = $this->respGood([ ['id' => -3, 'is_cat' => true, 'first_id' => 1], - $this->outputHeadlines(1)->getPayload()['content'], + $this->extractMessageJson($this->outputHeadlines(1))['content'], ]); $this->assertMessage($exp, $test); // test 'include_header' with an empty result @@ -1741,7 +1741,7 @@ LONG_STRING; $test = $this->req($in[7]); $exp = $this->respGood([ ['id' => -4, 'is_cat' => false, 'first_id' => 0], - $this->outputHeadlines(1)->getPayload()['content'], + $this->extractMessageJson($this->outputHeadlines(1))['content'], ]); $this->assertMessage($exp, $test); // test 'include_header' with skip @@ -1749,24 +1749,24 @@ LONG_STRING; $test = $this->req($in[8]); $exp = $this->respGood([ ['id' => 42, 'is_cat' => false, 'first_id' => 1867], - $this->outputHeadlines(1)->getPayload()['content'], + $this->extractMessageJson($this->outputHeadlines(1))['content'], ]); $this->assertMessage($exp, $test); // test 'include_header' with skip and ascending order $test = $this->req($in[9]); $exp = $this->respGood([ ['id' => 42, 'is_cat' => false, 'first_id' => 0], - $this->outputHeadlines(1)->getPayload()['content'], + $this->extractMessageJson($this->outputHeadlines(1))['content'], ]); $this->assertMessage($exp, $test); // test 'show_excerpt' $exp1 = "“This & that, you know‽”"; $exp2 = "Pour vous faire mieux connaitre d’ou\u{300} vient l’erreur de ceux qui bla\u{302}ment la volupte\u{301}, et qui louent en…"; $test = $this->req($in[10]); - $this->assertArrayHasKey("excerpt", $test->getPayload()['content'][0]); - $this->assertArrayHasKey("excerpt", $test->getPayload()['content'][1]); - $this->assertSame($exp1, $test->getPayload()['content'][0]['excerpt']); - $this->assertSame($exp2, $test->getPayload()['content'][1]['excerpt']); + $this->assertArrayHasKey("excerpt", $this->extractMessageJson($test)['content'][0]); + $this->assertArrayHasKey("excerpt", $this->extractMessageJson($test)['content'][1]); + $this->assertSame($exp1, $this->extractMessageJson($test)['content'][0]['excerpt']); + $this->assertSame($exp2, $this->extractMessageJson($test)['content'][1]['excerpt']); } protected function generateHeadlines(int $id): Result { diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php index c155a192..77653ee8 100644 --- a/tests/lib/AbstractTest.php +++ b/tests/lib/AbstractTest.php @@ -24,8 +24,7 @@ use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\ServerRequest; -use Laminas\Diactoros\Response\XmlResponse; +use GuzzleHttp\Psr7\ServerRequest; /** @coversNothing */ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { @@ -112,7 +111,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { } } $server = array_merge($server, $vars); - $req = new ServerRequest($server, [], $url, $method, "php://memory", [], [], $params, $parsedBody); + $req = new ServerRequest($method, $url, $headers, $body, "1.1", $server); if (isset($user)) { if (strlen($user)) { $req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user); @@ -192,8 +191,8 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { $this->assertSame($exp->getRequestTarget(), $act->getRequestTarget(), $text); } if ($exp instanceof ResponseInterface && HTTP::matchType($exp, "application/json", "text/json", "+json")) { - $expBody = json_decode((string) $exp->getBody(), true); - $actBody = json_decode((string) $act->getBody(), true); + $expBody = @json_decode((string) $exp->getBody(), true); + $actBody = @json_decode((string) $act->getBody(), true); $this->assertSame(\JSON_ERROR_NONE, json_last_error(), "Response body is not valid JSON"); $this->assertEquals($expBody, $actBody, $text); $this->assertSame($expBody, $actBody, $text); @@ -205,6 +204,16 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { $this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text); } + protected function extractMessageJson(MessageInterface $msg) { + if (HTTP::matchType($msg, "application/json", "text/json", "+json")) { + $json = @json_decode((string) $msg->getBody(), true); + if (json_last_error() === \JSON_ERROR_NONE) { + return $json; + } + } + return null; + } + public function assertTime($exp, $test, string $msg = ''): void { $test = $this->approximateTime($exp, $test); $exp = Date::transform($exp, "iso8601"); diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index bd01e8f6..f50257c5 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -115,7 +115,6 @@ cases/REST/TestREST.php - cases/REST/Miniflux/TestErrorResponse.php cases/REST/Miniflux/TestStatus.php cases/REST/Miniflux/TestV1.php cases/REST/Miniflux/TestToken.php