Browse Source

Address remaining errors

Still many failures to fix
arch
J. King 2 years ago
parent
commit
459e44e041
  1. 19
      lib/REST/Miniflux/ErrorResponse.php
  2. 132
      lib/REST/Miniflux/V1.php
  3. 3
      tests/cases/REST/Fever/TestAPI.php
  4. 22
      tests/cases/REST/Miniflux/TestErrorResponse.php
  5. 5
      tests/cases/REST/Miniflux/TestStatus.php
  6. 255
      tests/cases/REST/Miniflux/TestV1.php
  7. 6
      tests/cases/REST/TestREST.php
  8. 32
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  9. 19
      tests/lib/AbstractTest.php
  10. 1
      tests/phpunit.dist.xml

19
lib/REST/Miniflux/ErrorResponse.php

@ -1,19 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\REST\Miniflux;
use JKingWeb\Arsse\Arsse;
class ErrorResponse extends \Laminas\Diactoros\Response\JsonResponse {
public function __construct($data, int $status = 400, array $headers = [], int $encodingOptions = self::DEFAULT_JSON_FLAGS) {
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)];
parent::__construct($data, $status, $headers, $encodingOptions);
}
}

132
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")]);
}

3
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<extended> */
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("<response><items><item><id>101</id><feed_id>8</feed_id><title>Article title 1</title><author></author><html>&lt;p&gt;Article content 1&lt;/p&gt;</html><url>http://example.com/1</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>946684800</created_on_time></item><item><id>102</id><feed_id>8</feed_id><title>Article title 2</title><author></author><html>&lt;p&gt;Article content 2&lt;/p&gt;</html><url>http://example.com/2</url><is_saved>0</is_saved><is_read>1</is_read><created_on_time>946771200</created_on_time></item><item><id>103</id><feed_id>9</feed_id><title>Article title 3</title><author></author><html>&lt;p&gt;Article content 3&lt;/p&gt;</html><url>http://example.com/3</url><is_saved>1</is_saved><is_read>0</is_read><created_on_time>946857600</created_on_time></item><item><id>104</id><feed_id>9</feed_id><title>Article title 4</title><author></author><html>&lt;p&gt;Article content 4&lt;/p&gt;</html><url>http://example.com/4</url><is_saved>1</is_saved><is_read>1</is_read><created_on_time>946944000</created_on_time></item><item><id>105</id><feed_id>10</feed_id><title>Article title 5</title><author></author><html>&lt;p&gt;Article content 5&lt;/p&gt;</html><url>http://example.com/5</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>947030400</created_on_time></item></items><total_items>1024</total_items></response>");
$exp = HTTP::respXml("<response><items><item><id>101</id><feed_id>8</feed_id><title>Article title 1</title><author></author><html>&lt;p&gt;Article content 1&lt;/p&gt;</html><url>http://example.com/1</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>946684800</created_on_time></item><item><id>102</id><feed_id>8</feed_id><title>Article title 2</title><author></author><html>&lt;p&gt;Article content 2&lt;/p&gt;</html><url>http://example.com/2</url><is_saved>0</is_saved><is_read>1</is_read><created_on_time>946771200</created_on_time></item><item><id>103</id><feed_id>9</feed_id><title>Article title 3</title><author></author><html>&lt;p&gt;Article content 3&lt;/p&gt;</html><url>http://example.com/3</url><is_saved>1</is_saved><is_read>0</is_read><created_on_time>946857600</created_on_time></item><item><id>104</id><feed_id>9</feed_id><title>Article title 4</title><author></author><html>&lt;p&gt;Article content 4&lt;/p&gt;</html><url>http://example.com/4</url><is_saved>1</is_saved><is_read>1</is_read><created_on_time>946944000</created_on_time></item><item><id>105</id><feed_id>10</feed_id><title>Article title 5</title><author></author><html>&lt;p&gt;Article content 5&lt;/p&gt;</html><url>http://example.com/5</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>947030400</created_on_time></item></items><total_items>1024</total_items></response>");
$this->assertMessage($exp, $this->req("api=xml"));
}

22
tests/cases/REST/Miniflux/TestErrorResponse.php

@ -1,22 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\ErrorResponse */
class TestErrorResponse extends \JKingWeb\Arsse\Test\AbstractTest {
public function testCreateConstantResponse(): void {
$act = new ErrorResponse("401", 401);
$this->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());
}
}

5
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)],

255
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<extended> */
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' => "<svg/>"], HTTP::respJson(['id' => 44, 'data' => "image/svg+xml;base64,PHN2Zy8+", 'mime_type' => "image/svg+xml"])],
[['id' => 47, 'type' => "", 'data' => "<svg/>"], new ErrorResponse("404", 404)],
[['id' => 47, 'type' => null, 'data' => "<svg/>"], 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' => "<svg/>"], V1::respError("404", 404)],
[['id' => 47, 'type' => null, 'data' => "<svg/>"], 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("<EXPORT_DATA/>");
$this->assertMessage(HTTP::respText("<EXPORT_DATA/>", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export"));
$opml->export->calledWith(Arsse::$user->id);
}
}

6
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", "")],
];
}

32
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 {

19
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");

1
tests/phpunit.dist.xml

@ -115,7 +115,6 @@
<file>cases/REST/TestREST.php</file>
</testsuite>
<testsuite name="Miniflux">
<file>cases/REST/Miniflux/TestErrorResponse.php</file>
<file>cases/REST/Miniflux/TestStatus.php</file>
<file>cases/REST/Miniflux/TestV1.php</file>
<file>cases/REST/Miniflux/TestToken.php</file>

Loading…
Cancel
Save