diff --git a/CHANGELOG b/CHANGELOG index f6263f2e..6995ad0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -Version 0.1?.? (2022-??-??) +Version 0.10.3 (2022-09-14) =========================== Bug fixes: @@ -6,6 +6,7 @@ Bug fixes: - Allow multiple date ranges in search strings in Tiny Tiny RSS - Honour user time zone when interpreting search strings in Tiny Tiny RSS - Perform MySQL table maintenance more reliably +- Address CVE-2022-31090, CVE-2022-31091, CVE-2022-29248, and CVE-2022-31109 Version 0.10.2 (2022-04-04) =========================== diff --git a/RoboFile.php b/RoboFile.php index 4010e875..5d7c46fe 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -164,7 +164,7 @@ class RoboFile extends \Robo\Tasks { if ( (IS_WIN && (!exec(escapeshellarg($bin)." --help $blackhole", $junk, $status) || $status)) || (!IS_WIN && (!exec("which ".escapeshellarg($bin)." $blackhole", $junk, $status) || $status)) - ) { + ) { return false; } } diff --git a/UPGRADING b/UPGRADING index f6dcfff6..0ca27113 100644 --- a/UPGRADING +++ b/UPGRADING @@ -11,6 +11,13 @@ usually prudent: `composer install -o --no-dev` +Upgrading from 0.10.2 to 0.10.3 +============================= + +- The following Composer dependencies have been removed: + - laminas/laminas-diactoros + + Upgrading from 0.8.5 to 0.9.0 ============================= diff --git a/composer.json b/composer.json index 97aedd38..38952d20 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "hosteurope/password-generator": "1.*", "docopt/docopt": "1.*", "jkingweb/druuid": "3.*", - "laminas/laminas-diactoros": "2.*", + "guzzlehttp/psr7": "1.*", "laminas/laminas-httphandlerrunner": "1.*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 21480c42..972f8e87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c658930fbc56b2b2cf646e34c6a8d8d3", + "content-hash": "2671d9010a4ac73e877838baf3586df2", "packages": [ { "name": "docopt/docopt", @@ -58,24 +58,24 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.5.6", + "version": "6.5.8", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "f092dd734083473658de3ee4bef093ed77d2689c" + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f092dd734083473658de3ee4bef093ed77d2689c", - "reference": "f092dd734083473658de3ee4bef093ed77d2689c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.6.1", + "guzzlehttp/psr7": "^1.9", "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.17.0" + "symfony/polyfill-intl-idn": "^1.17" }, "require-dev": { "ext-curl": "*", @@ -153,7 +153,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/6.5.6" + "source": "https://github.com/guzzle/guzzle/tree/6.5.8" }, "funding": [ { @@ -169,20 +169,20 @@ "type": "tidelift" } ], - "time": "2022-05-25T13:19:12+00:00" + "time": "2022-06-20T22:16:07+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + "reference": "b94b2807d85443f9719887892882d0329d1e2598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", "shasum": "" }, "require": { @@ -237,7 +237,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.1" + "source": "https://github.com/guzzle/promises/tree/1.5.2" }, "funding": [ { @@ -253,20 +253,20 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:56:57+00:00" + "time": "2022-08-28T14:55:35+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.5", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268" + "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/337e3ad8e5716c15f9657bd214d16cc5e69df268", - "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", "shasum": "" }, "require": { @@ -287,7 +287,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -347,7 +347,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.5" + "source": "https://github.com/guzzle/psr7/tree/1.9.0" }, "funding": [ { @@ -363,7 +363,7 @@ "type": "tidelift" } ], - "time": "2022-03-20T21:51:18+00:00" + "time": "2022-06-20T21:43:03+00:00" }, { "name": "hosteurope/password-generator", @@ -537,105 +537,6 @@ }, "time": "2017-08-17T12:23:43+00:00" }, - { - "name": "laminas/laminas-diactoros", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "36ef09b73e884135d2059cc498c938e90821bb57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/36ef09b73e884135d2059cc498c938e90821bb57", - "reference": "36ef09b73e884135d2059cc498c938e90821bb57", - "shasum": "" - }, - "require": { - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^7.1", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "replace": { - "zendframework/zend-diactoros": "^2.2.1" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.5.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.0", - "phpunit/phpunit": "^7.5.18" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2020-09-03T14:29:41+00:00" - }, { "name": "laminas/laminas-httphandlerrunner", "version": "1.2.0", @@ -892,61 +793,6 @@ }, "time": "2020-09-15T07:28:23+00:00" }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, { "name": "psr/http-message", "version": "1.0.1", @@ -1153,16 +999,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "shasum": "" }, "require": { @@ -1176,7 +1022,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1220,7 +1066,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" }, "funding": [ { @@ -1236,20 +1082,20 @@ "type": "tidelift" } ], - "time": "2021-09-14T14:02:44+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -1261,7 +1107,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1304,7 +1150,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -1320,20 +1166,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { @@ -1342,7 +1188,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1380,7 +1226,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -1396,7 +1242,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-05-24T11:49:31+00:00" } ], "packages-dev": [ diff --git a/lib/Arsse.php b/lib/Arsse.php index 604bccd1..9d5a57b7 100644 --- a/lib/Arsse.php +++ b/lib/Arsse.php @@ -7,7 +7,7 @@ declare(strict_types=1); namespace JKingWeb\Arsse; class Arsse { - public const VERSION = "0.10.2"; + public const VERSION = "0.10.3"; public const REQUIRED_EXTENSIONS = [ "intl", // as this extension is required to prepare formatted messages, its absence will throw a distinct English-only exception "dom", diff --git a/lib/Db/MySQL/ExceptionBuilder.php b/lib/Db/MySQL/ExceptionBuilder.php index 8f5be9c3..dfe01ae5 100644 --- a/lib/Db/MySQL/ExceptionBuilder.php +++ b/lib/Db/MySQL/ExceptionBuilder.php @@ -27,7 +27,7 @@ trait ExceptionBuilder { public static function buildConnectionException($code, string $msg): array { switch ($code) { case 1045: - // @codeCoverageIgnoreStart + // @codeCoverageIgnoreStart case 1043: case 1044: case 1046: @@ -48,7 +48,7 @@ trait ExceptionBuilder { case 2018: case 2026: case 2028: - // @codeCoverageIgnoreEnd + // @codeCoverageIgnoreEnd return [Exception::class, 'connectionFailure', ['engine' => "MySQL", 'message' => $msg]]; default: return [Exception::class, 'engineErrorGeneral', $msg]; // @codeCoverageIgnore diff --git a/lib/Misc/HTTP.php b/lib/Misc/HTTP.php index ac415062..b772ad2e 100644 --- a/lib/Misc/HTTP.php +++ b/lib/Misc/HTTP.php @@ -7,16 +7,41 @@ declare(strict_types=1); namespace JKingWeb\Arsse\Misc; use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\ResponseInterface; +use GuzzleHttp\Psr7\Response; class HTTP { public static function matchType(MessageInterface $msg, string ...$type): bool { $header = $msg->getHeaderLine("Content-Type") ?? ""; foreach ($type as $t) { - $pattern = "/^".preg_quote(trim($t), "/")."\s*($|;|,)/Di"; + if (($t[0] ?? "") === "+") { + $pattern = "/^[^+;,\s]*".preg_quote(trim($t), "/")."\s*($|;|,)/Di"; + } else { + $pattern = "/^".preg_quote(trim($t), "/")."\s*($|;|,)/Di"; + } if (preg_match($pattern, $header)) { return true; } } return false; } + + public static function respEmpty(int $status, ?array $headers = []): ResponseInterface { + return new Response($status, $headers ?? []); + } + + public static function respJson($body, int $status = 200, ?array $headers = []): ResponseInterface { + $headers = ($headers ?? []) + ['Content-Type' => "application/json"]; + return new Response($status, $headers, json_encode($body, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + public static function respText(string $body, int $status = 200, ?array $headers = []): ResponseInterface { + $headers = ($headers ?? []) + ['Content-Type' => "text/plain; charset=UTF-8"]; + return new Response($status, $headers, $body); + } + + public static function respXml(string $body, int $status = 200, ?array $headers = []): ResponseInterface { + $headers = ($headers ?? []) + ['Content-Type' => "application/xml; charset=UTF-8"]; + return new Response($status, $headers, $body); + } } diff --git a/lib/REST.php b/lib/REST.php index adc56ac5..8cae6ea2 100644 --- a/lib/REST.php +++ b/lib/REST.php @@ -7,11 +7,11 @@ declare(strict_types=1); namespace JKingWeb\Arsse; use JKingWeb\Arsse\Misc\URL; +use JKingWeb\Arsse\Misc\HTTP; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\ServerRequestFactory; -use Laminas\Diactoros\Response\EmptyResponse; +use GuzzleHttp\Psr7\ServerRequest; class REST { public const API_LIST = [ @@ -84,7 +84,7 @@ class REST { // ensure the require extensions are loaded Arsse::checkExtensions(...Arsse::REQUIRED_EXTENSIONS); // create a request object if not provided - $req = $req ?? ServerRequestFactory::fromGlobals(); + $req = $req ?? ServerRequest::fromGlobals(); // find the API to handle [, $target, $class] = $this->apiMatch($req->getRequestTarget(), $this->apis); // authenticate the request pre-emptively @@ -101,7 +101,7 @@ class REST { $res = $drv->dispatch($req); } } catch (REST\Exception501 $e) { - $res = new EmptyResponse(501); + $res = HTTP::respEmpty(501); } // modify the response so that it has all the required metadata return $this->normalizeResponse($res, $req); @@ -180,7 +180,7 @@ class REST { } // if the response is to a HEAD request, the body should be omitted if ($req && $req->getMethod() === "HEAD") { - $res = new EmptyResponse($res->getStatusCode(), $res->getHeaders()); + $res = HTTP::respEmpty($res->getStatusCode(), $res->getHeaders()); } // if an Allow header field is present, normalize it if ($res->hasHeader("Allow")) { diff --git a/lib/REST/Fever/API.php b/lib/REST/Fever/API.php index 7ad69ba5..9b243129 100644 --- a/lib/REST/Fever/API.php +++ b/lib/REST/Fever/API.php @@ -10,13 +10,10 @@ use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\Date; -use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Misc\HTTP; +use JKingWeb\Arsse\Db\ExceptionInput; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse; -use Laminas\Diactoros\Response\XmlResponse; -use Laminas\Diactoros\Response\EmptyResponse; class API extends \JKingWeb\Arsse\REST\AbstractHandler { public const LEVEL = 3; @@ -62,11 +59,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $P = $this->normalizeInputPost($req->getParsedBody() ?? []); if (!isset($G['api'])) { // the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404 - return new EmptyResponse(404); + return HTTP::respEmpty(404); } switch ($req->getMethod()) { case "OPTIONS": - return new EmptyResponse(204, [ + return HTTP::respEmpty(204, [ 'Allow' => "POST", 'Accept' => implode(", ", self::ACCEPTED_TYPES), ]); @@ -82,7 +79,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { $out['auth'] = 1; } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { // otherwise if HTTP authentication failed or is required, deny access at the HTTP level - return new EmptyResponse(401); + return HTTP::respEmpty(401); } // produce a full response if authenticated or a basic response otherwise if ($this->logIn(strtolower($P['api_key'] ?? ""))) { @@ -93,7 +90,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // return the result, possibly formatted as XML return $this->formatResponse($out, ($G['api'] === "xml")); default: - return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]); + return HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"]); } } @@ -182,9 +179,9 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { if ($xml) { $d = new \DOMDocument("1.0", "utf-8"); $d->appendChild($this->makeXMLAssoc($data, $d->createElement("response"))); - return new XmlResponse($d->saveXML()); + return HTTP::respXml($d->saveXML()); } else { - return new JsonResponse($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + return HTTP::respJson($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); } } 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/Status.php b/lib/REST/Miniflux/Status.php index 367a7a65..b84f4a13 100644 --- a/lib/REST/Miniflux/Status.php +++ b/lib/REST/Miniflux/Status.php @@ -6,10 +6,9 @@ declare(strict_types=1); namespace JKingWeb\Arsse\REST\Miniflux; +use JKingWeb\Arsse\Misc\HTTP; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\EmptyResponse; -use Laminas\Diactoros\Response\TextResponse; class Status extends \JKingWeb\Arsse\REST\AbstractHandler { public function __construct() { @@ -18,13 +17,13 @@ class Status extends \JKingWeb\Arsse\REST\AbstractHandler { public function dispatch(ServerRequestInterface $req): ResponseInterface { $target = parse_url($req->getRequestTarget())['path'] ?? ""; if (!in_array($target, ["/version", "/healthcheck"])) { - return new EmptyResponse(404); + return HTTP::respEmpty(404); } $method = $req->getMethod(); if ($method === "OPTIONS") { - return new EmptyResponse(204, ['Allow' => "HEAD, GET"]); + return HTTP::respEmpty(204, ['Allow' => "HEAD, GET"]); } elseif ($method !== "GET") { - return new EmptyResponse(405, ['Allow' => "HEAD, GET"]); + return HTTP::respEmpty(405, ['Allow' => "HEAD, GET"]); } $out = ""; if ($target === "/version") { @@ -32,6 +31,6 @@ class Status extends \JKingWeb\Arsse\REST\AbstractHandler { } elseif ($target === "/healthcheck") { $out = "OK"; } - return new TextResponse($out); + return HTTP::respText($out); } } diff --git a/lib/REST/Miniflux/V1.php b/lib/REST/Miniflux/V1.php index 6897472f..2b981c59 100644 --- a/lib/REST/Miniflux/V1.php +++ b/lib/REST/Miniflux/V1.php @@ -19,6 +19,7 @@ use JKingWeb\Arsse\ImportExport\OPML; use JKingWeb\Arsse\ImportExport\Exception as ImportException; use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\URL; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\REST\Exception; use JKingWeb\Arsse\Rule\Rule; @@ -26,10 +27,7 @@ use JKingWeb\Arsse\User\ExceptionConflict; use JKingWeb\Arsse\User\Exception as UserException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\EmptyResponse; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\TextResponse as GenericResponse; -use Laminas\Diactoros\Uri; +use GuzzleHttp\Psr7\Uri; class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { public const VERSION = "2.0.28"; @@ -215,6 +213,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 if ($req->hasHeader("X-Auth-Token")) { @@ -247,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) { @@ -256,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) { @@ -271,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 = []; @@ -295,10 +301,10 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { // @codeCoverageIgnoreStart } catch (Exception $e) { // if there was a REST exception return 400 - return new EmptyResponse(400); + return HTTP::respEmpty(400); } catch (AbstractException $e) { // if there was any other Arsse exception return 500 - return new EmptyResponse(500); + return HTTP::respEmpty(500); } // @codeCoverageIgnoreEnd } @@ -317,11 +323,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { return self::CALLS[$url][$method]; } else { // otherwise return 405 - return new EmptyResponse(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]); + return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]); } } else { // if the path is not supported, return 404 - return new EmptyResponse(404); + return HTTP::respEmpty(404); } } @@ -346,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); } } } @@ -371,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; @@ -409,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") { @@ -425,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 ( @@ -435,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; @@ -451,13 +457,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { if (in_array("GET", $allowed)) { array_unshift($allowed, "HEAD"); } - return new EmptyResponse(204, [ + return HTTP::respEmpty(204, [ 'Allow' => implode(", ", $allowed), 'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON), ]); } else { // if the path is not supported, return 404 - return new EmptyResponse(404); + return HTTP::respEmpty(404); } } @@ -527,40 +533,40 @@ 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) { // TODO: This needs to be refined once PicoFeed is replaced $out[] = ['title' => "Feed", 'type' => "rss", 'url' => $url]; } - return new Response($out); + return HTTP::respJson($out); } protected function getUsers(): ResponseInterface { $tr = Arsse::$user->begin(); - return new Response($this->listUsers(Arsse::$user->list(), false)); + return HTTP::respJson($this->listUsers(Arsse::$user->list(), false)); } protected function getUserById(array $path): ResponseInterface { try { - return new Response($this->listUsers([$path[1]], true)[0] ?? new \stdClass); + return HTTP::respJson($this->listUsers([$path[1]], true)[0] ?? new \stdClass); } catch (UserException $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } protected function getUserByNum(array $path): ResponseInterface { try { $user = Arsse::$user->lookup((int) $path[1]); - return new Response($this->listUsers([$user], true)[0] ?? new \stdClass); + return HTTP::respJson($this->listUsers([$user], true)[0] ?? new \stdClass); } catch (UserException $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } protected function getCurrentUser(): ResponseInterface { - return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass); + return HTTP::respJson($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass); } protected function createUser(array $data): ResponseInterface { @@ -572,17 +578,17 @@ 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 } - return new Response($out, 201); + return HTTP::respJson($out, 201); } protected function updateUserByNum(array $path, array $data): ResponseInterface { @@ -591,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 @@ -618,26 +624,26 @@ 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 } - return new Response($out, 201); + return HTTP::respJson($out, 201); } protected function deleteUserByNum(array $path): ResponseInterface { try { Arsse::$user->remove(Arsse::$user->lookup((int) $path[1])); } catch (ExceptionConflict $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } /** Returns a useful subset of user metadata @@ -667,7 +673,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { // always add 1 to the ID since the root folder will always be 1 instead of 0. $out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $meta['num']]; } - return new Response($out); + return HTTP::respJson($out); } protected function createCategory(array $data): ResponseInterface { @@ -675,13 +681,13 @@ 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); - return new Response(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']], 201); + return HTTP::respJson(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']], 201); } protected function updateCategory(array $path, array $data): ResponseInterface { @@ -700,15 +706,15 @@ 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); - return new Response(['id' => (int) $path[1], 'title' => $title, 'user_id' => $meta['num']], 201); + return HTTP::respJson(['id' => (int) $path[1], 'title' => $title, 'user_id' => $meta['num']], 201); } protected function deleteCategory(array $path): ResponseInterface { @@ -726,9 +732,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $tr->commit(); } } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function transformFeed(array $sub, int $uid, string $rootName, \DateTimeZone $tz): array { @@ -772,7 +778,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $r) { $out[] = $this->transformFeed($r, $meta['num'], $meta['root'], $meta['tz']); } - return new Response($out); + return HTTP::respJson($out); } protected function getCategoryFeeds(array $path): ResponseInterface { @@ -790,9 +796,9 @@ 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 new Response($out); + return HTTP::respJson($out); } protected function getFeed(array $path): ResponseInterface { @@ -800,9 +806,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $meta = $this->userMeta(Arsse::$user->id); try { $sub = Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); - return new Response($this->transformFeed($sub, $meta['num'], $meta['root'], $meta['tz'])); + 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); } } @@ -825,16 +831,16 @@ 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 new Response(['feed_id' => $id], 201); + return HTTP::respJson(['feed_id' => $id], 201); } protected function updateFeed(array $path, array $data): ResponseInterface { @@ -853,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,9 +872,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { protected function deleteFeed(array $path): ResponseInterface { try { Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]); - return new EmptyResponse(204); + return HTTP::respEmpty(204); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -876,12 +882,12 @@ 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 new Response([ + return HTTP::respJson([ 'id' => (int) $icon['id'], 'data' => $icon['type'].";base64,".base64_encode($icon['data']), 'mime_type' => $icon['type'], @@ -1038,45 +1044,45 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { protected function getEntries(array $query): ResponseInterface { try { - return new Response($this->listEntries($query, new Context)); + return HTTP::respJson($this->listEntries($query, new Context)); } catch (ExceptionInput $e) { - return new ErrorResponse("MissingCategory", 400); + return self::respError("MissingCategory", 400); } } protected function getFeedEntries(array $path, array $query): ResponseInterface { $c = (new Context)->subscription((int) $path[1]); try { - return new Response($this->listEntries($query, $c)); + 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); } } protected function getCategoryEntries(array $path, array $query): ResponseInterface { $query['category_id'] = (int) $path[1]; try { - return new Response($this->listEntries($query, new Context)); + return HTTP::respJson($this->listEntries($query, new Context)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } protected function getEntry(array $path): ResponseInterface { try { - return new Response($this->findEntry((int) $path[1])); + return HTTP::respJson($this->findEntry((int) $path[1])); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } protected function getFeedEntry(array $path): ResponseInterface { $c = (new Context)->subscription((int) $path[1]); try { - return new Response($this->findEntry((int) $path[3], $c)); + return HTTP::respJson($this->findEntry((int) $path[3], $c)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1088,9 +1094,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $c->folder((int) $path[1] - 1); } try { - return new Response($this->findEntry((int) $path[3], $c)); + return HTTP::respJson($this->findEntry((int) $path[3], $c)); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } } @@ -1104,7 +1110,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } assert(isset($in), new \Exception("Unknown status specified")); Arsse::$db->articleMark(Arsse::$user->id, $in, (new Context)->articles($data['entry_ids'])); - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function massRead(Context $c): void { @@ -1115,19 +1121,19 @@ 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 new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function markFeed(array $path): ResponseInterface { try { $this->massRead((new Context)->subscription((int) $path[1])); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function markCategory(array $path): ResponseInterface { @@ -1142,9 +1148,9 @@ 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 new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function toggleEntryBookmark(array $path): ResponseInterface { @@ -1160,9 +1166,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } $tr->commit(); } catch (ExceptionInput $e) { - return new ErrorResponse("404", 404); + return self::respError("404", 404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function refreshFeed(array $path): ResponseInterface { @@ -1170,15 +1176,15 @@ 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 new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function refreshAllFeeds(): ResponseInterface { // NOTE: This is a no-op // It could be implemented, but the need is considered low since we use a dynamic schedule always - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function opmlImport(string $data): ResponseInterface { @@ -1187,23 +1193,23 @@ 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 new Response(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")]); + return HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")]); } protected function opmlExport(): ResponseInterface { - return new GenericResponse(Arsse::$obj->get(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]); + return HTTP::respText(Arsse::$obj->get(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]); } } diff --git a/lib/REST/NextcloudNews/V1_2.php b/lib/REST/NextcloudNews/V1_2.php index 7ec195cc..bfcf80f8 100644 --- a/lib/REST/NextcloudNews/V1_2.php +++ b/lib/REST/NextcloudNews/V1_2.php @@ -17,8 +17,6 @@ use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\REST\Exception; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { public const VERSION = "11.0.5"; @@ -86,19 +84,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { if ($req->getAttribute("authenticated", false)) { Arsse::$user->id = $req->getAttribute("authenticatedUser"); } else { - return new EmptyResponse(401); + return HTTP::respEmpty(401); } // normalize the input $data = (string) $req->getBody(); if ($data) { // if the entity body is not JSON according to content type, return "415 Unsupported Media Type" if (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) { - return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); + return HTTP::respEmpty(415, ['Accept' => self::ACCEPTED_TYPE]); } $data = @json_decode($data, true); if (json_last_error() !== \JSON_ERROR_NONE) { // if the body could not be parsed as JSON, return "400 Bad Request" - return new EmptyResponse(400); + return HTTP::respEmpty(400); } } else { $data = []; @@ -117,10 +115,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { // @codeCoverageIgnoreStart } catch (Exception $e) { // if there was a REST exception return 400 - return new EmptyResponse(400); + return HTTP::respEmpty(400); } catch (AbstractException $e) { // if there was any other Arsse exception return 500 - return new EmptyResponse(500); + return HTTP::respEmpty(500); } // @codeCoverageIgnoreEnd } @@ -162,11 +160,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { return $this->paths[$url][$method]; } else { // otherwise return 405 - return new EmptyResponse(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]); + return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]); } } else { // if the path is not supported, return 404 - return new EmptyResponse(404); + return HTTP::respEmpty(404); } } @@ -268,13 +266,13 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { if (in_array("GET", $allowed)) { array_unshift($allowed, "HEAD"); } - return new EmptyResponse(204, [ + return HTTP::respEmpty(204, [ 'Allow' => implode(",", $allowed), 'Accept' => self::ACCEPTED_TYPE, ]); } else { // if the path is not supported, return 404 - return new EmptyResponse(404); + return HTTP::respEmpty(404); } } @@ -284,7 +282,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $folder) { $folders[] = $this->folderTranslate($folder); } - return new Response(['folders' => $folders]); + return HTTP::respJson(['folders' => $folders]); } // create a folder @@ -294,16 +292,16 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (ExceptionInput $e) { switch ($e->getCode()) { // folder already exists - case 10236: return new EmptyResponse(409); - // folder name not acceptable + case 10236: return HTTP::respEmpty(409); + // folder name not acceptable case 10231: - case 10232: return new EmptyResponse(422); + case 10232: return HTTP::respEmpty(422); // other errors related to input - default: return new EmptyResponse(400); // @codeCoverageIgnore + default: return HTTP::respEmpty(400); // @codeCoverageIgnore } } $folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder)); - return new Response(['folders' => [$folder]]); + return HTTP::respJson(['folders' => [$folder]]); } // delete a folder @@ -313,9 +311,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]); } catch (ExceptionInput $e) { // folder does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // rename a folder (also supports moving nesting folders, but this is not a feature of the API) @@ -325,24 +323,24 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (ExceptionInput $e) { switch ($e->getCode()) { // folder does not exist - case 10239: return new EmptyResponse(404); - // folder already exists - case 10236: return new EmptyResponse(409); - // folder name not acceptable + case 10239: return HTTP::respEmpty(404); + // folder already exists + case 10236: return HTTP::respEmpty(409); + // folder name not acceptable case 10231: - case 10232: return new EmptyResponse(422); + case 10232: return HTTP::respEmpty(422); // other errors related to input - default: return new EmptyResponse(400); // @codeCoverageIgnore + default: return HTTP::respEmpty(400); // @codeCoverageIgnore } } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark all articles associated with a folder as read protected function folderMarkRead(array $url, array $data): ResponseInterface { if (!ValueInfo::id($data['newestItemId'])) { // if the item ID is invalid (i.e. not a positive integer), this is an error - return new EmptyResponse(422); + return HTTP::respEmpty(422); } // build the context $c = (new Context)->hidden(false); @@ -353,15 +351,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); } catch (ExceptionInput $e) { // folder does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // return list of feeds which should be refreshed protected function feedListStale(array $url, array $data): ResponseInterface { if (!$this->isAdmin()) { - return new EmptyResponse(403); + return HTTP::respEmpty(403); } // list stale feeds which should be checked for updates $feeds = Arsse::$db->feedListStale(); @@ -370,27 +368,27 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { // since in our implementation feeds don't belong the users, the 'userId' field will always be an empty string $out[] = ['id' => (int) $feed, 'userId' => ""]; } - return new Response(['feeds' => $out]); + return HTTP::respJson(['feeds' => $out]); } // refresh a feed protected function feedUpdate(array $url, array $data): ResponseInterface { if (!$this->isAdmin()) { - return new EmptyResponse(403); + return HTTP::respEmpty(403); } try { Arsse::$db->feedUpdate($data['feedId']); } catch (ExceptionInput $e) { switch ($e->getCode()) { case 10239: // feed does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); case 10237: // feed ID invalid - return new EmptyResponse(422); + return HTTP::respEmpty(422); default: // other errors related to input - return new EmptyResponse(400); // @codeCoverageIgnore + return HTTP::respEmpty(400); // @codeCoverageIgnore } } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // add a new feed @@ -401,10 +399,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { $id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']); } catch (ExceptionInput $e) { // feed already exists - return new EmptyResponse(409); + return HTTP::respEmpty(409); } catch (FeedException $e) { // feed could not be retrieved - return new EmptyResponse(422); + return HTTP::respEmpty(422); } // if a folder was specified, move the feed to the correct folder; silently ignore errors if ($data['folderId']) { @@ -422,7 +420,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { if ($newest) { $out['newestItemId'] = $newest; } - return new Response($out); + return HTTP::respJson($out); } // return list of feeds for the logged-in user @@ -438,7 +436,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { if ($newest) { $out['newestItemId'] = $newest; } - return new Response($out); + return HTTP::respJson($out); } // delete a feed @@ -447,9 +445,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]); } catch (ExceptionInput $e) { // feed does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // rename a feed @@ -459,22 +457,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (ExceptionInput $e) { switch ($e->getCode()) { // subscription does not exist - case 10239: return new EmptyResponse(404); - // name is invalid + case 10239: return HTTP::respEmpty(404); + // name is invalid case 10231: - case 10232: return new EmptyResponse(422); + case 10232: return HTTP::respEmpty(422); // other errors related to input - default: return new EmptyResponse(400); // @codeCoverageIgnore + default: return HTTP::respEmpty(400); // @codeCoverageIgnore } } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // move a feed to a folder protected function subscriptionMove(array $url, array $data): ResponseInterface { // if no folder is specified this is an error if (!isset($data['folderId'])) { - return new EmptyResponse(422); + return HTTP::respEmpty(422); } // perform the move try { @@ -482,22 +480,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } catch (ExceptionInput $e) { switch ($e->getCode()) { case 10239: // subscription does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); case 10235: // folder does not exist case 10237: // folder ID is invalid - return new EmptyResponse(422); + return HTTP::respEmpty(422); default: // other errors related to input - return new EmptyResponse(400); // @codeCoverageIgnore + return HTTP::respEmpty(400); // @codeCoverageIgnore } } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark all articles associated with a subscription as read protected function subscriptionMarkRead(array $url, array $data): ResponseInterface { if (!ValueInfo::id($data['newestItemId'])) { // if the item ID is invalid (i.e. not a positive integer), this is an error - return new EmptyResponse(422); + return HTTP::respEmpty(422); } // build the context $c = (new Context)->hidden(false); @@ -508,9 +506,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); } catch (ExceptionInput $e) { // subscription does not exist - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // list articles and their properties @@ -579,28 +577,28 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { ], [$reverse ? "edition desc" : "edition"]); } catch (ExceptionInput $e) { // ID of subscription or folder is not valid - return new EmptyResponse(422); + return HTTP::respEmpty(422); } $out = []; foreach ($items as $item) { $out[] = $this->articleTranslate($item); } $out = ['items' => $out]; - return new Response($out); + return HTTP::respJson($out); } // mark all articles as read protected function articleMarkReadAll(array $url, array $data): ResponseInterface { if (!ValueInfo::id($data['newestItemId'])) { // if the item ID is invalid (i.e. not a positive integer), this is an error - return new EmptyResponse(422); + return HTTP::respEmpty(422); } // build the context $c = (new Context)->hidden(false); $c->editionRange(null, (int) $data['newestItemId']); // perform the operation Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark a single article as read @@ -614,9 +612,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); } catch (ExceptionInput $e) { // ID is not valid - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark a single article as read @@ -630,9 +628,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); } catch (ExceptionInput $e) { // ID is not valid - return new EmptyResponse(404); + return HTTP::respEmpty(404); } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark an array of articles as read @@ -646,7 +644,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); } catch (ExceptionInput $e) { } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // mark an array of articles as starred @@ -660,11 +658,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); } catch (ExceptionInput $e) { } - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function userStatus(array $url, array $data): ResponseInterface { - return new Response([ + return HTTP::respJson([ 'userId' => (string) Arsse::$user->id, 'displayName' => (string) Arsse::$user->id, 'lastLoginTimestamp' => time(), @@ -674,30 +672,30 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { protected function cleanupBefore(array $url, array $data): ResponseInterface { if (!$this->isAdmin()) { - return new EmptyResponse(403); + return HTTP::respEmpty(403); } Service::cleanupPre(); - return new EmptyResponse(204); + return HTTP::respEmpty(204); } protected function cleanupAfter(array $url, array $data): ResponseInterface { if (!$this->isAdmin()) { - return new EmptyResponse(403); + return HTTP::respEmpty(403); } Service::cleanupPost(); - return new EmptyResponse(204); + return HTTP::respEmpty(204); } // return the server version protected function serverVersion(array $url, array $data): ResponseInterface { - return new Response([ + return HTTP::respJson([ 'version' => self::VERSION, 'arsse_version' => Arsse::VERSION, ]); } protected function serverStatus(array $url, array $data): ResponseInterface { - return new Response([ + return HTTP::respJson([ 'version' => self::VERSION, 'arsse_version' => Arsse::VERSION, 'warnings' => [ diff --git a/lib/REST/NextcloudNews/Versions.php b/lib/REST/NextcloudNews/Versions.php index 95ee0bf3..0a3a6f6f 100644 --- a/lib/REST/NextcloudNews/Versions.php +++ b/lib/REST/NextcloudNews/Versions.php @@ -6,10 +6,9 @@ declare(strict_types=1); namespace JKingWeb\Arsse\REST\NextcloudNews; +use JKingWeb\Arsse\Misc\HTTP; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; class Versions implements \JKingWeb\Arsse\REST\Handler { public function __construct() { @@ -18,12 +17,12 @@ class Versions implements \JKingWeb\Arsse\REST\Handler { public function dispatch(ServerRequestInterface $req): ResponseInterface { if (!preg_match("<^/?$>D", $req->getRequestTarget())) { // if the request path is more than an empty string or a slash, the client is probably trying a version we don't support - return new EmptyResponse(404); + return HTTP::respEmpty(404); } switch ($req->getMethod()) { case "OPTIONS": // if the request method is OPTIONS, respond accordingly - return new EmptyResponse(204, ['Allow' => "HEAD,GET"]); + return HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]); case "GET": // otherwise return the supported versions $out = [ @@ -31,10 +30,10 @@ class Versions implements \JKingWeb\Arsse\REST\Handler { 'v1-2', ], ]; - return new Response($out); + return HTTP::respJson($out); default: // if any other method was used, this is an error - return new EmptyResponse(405, ['Allow' => "HEAD,GET"]); + return HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]); } } } diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php index e167fa4b..71261000 100644 --- a/lib/REST/TinyTinyRSS/API.php +++ b/lib/REST/TinyTinyRSS/API.php @@ -12,6 +12,7 @@ use JKingWeb\Arsse\Service; use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Misc\Date; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\ExceptionType; @@ -20,8 +21,6 @@ use JKingWeb\Arsse\Db\ResultEmpty; use JKingWeb\Arsse\Feed\Exception as FeedException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; class API extends \JKingWeb\Arsse\REST\AbstractHandler { public const LEVEL = 15; // emulated API level @@ -96,11 +95,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { public function dispatch(ServerRequestInterface $req): ResponseInterface { if (!preg_match("<^(?:/(?:index\.php)?)?$>D", $req->getRequestTarget())) { // reject paths other than the index - return new EmptyResponse(404); + return HTTP::respEmpty(404); } if ($req->getMethod() === "OPTIONS") { // respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method - return new EmptyResponse(204, [ + return HTTP::respEmpty(204, [ 'Allow' => "POST", 'Accept' => implode(", ", self::ACCEPTED_TYPES), ]); @@ -110,7 +109,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // only JSON entities are allowed, but Content-Type is ignored, as is request method $data = @json_decode($data, true); if (json_last_error() !== \JSON_ERROR_NONE || !is_array($data)) { - return new Response(self::FATAL_ERR); + return HTTP::respJson(self::FATAL_ERR); } try { // normalize input @@ -125,7 +124,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$user->id = $req->getAttribute("authenticatedUser"); } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { // otherwise if HTTP authentication failed or is required, deny access at the HTTP level - return new EmptyResponse(401); + return HTTP::respEmpty(401); } if (strtolower((string) $data['op']) !== "login") { // unless logging in, a session identifier is required @@ -136,23 +135,23 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // TT-RSS operations are case-insensitive by dint of PHP method names being case-insensitive; this will only trigger if the method really doesn't exist throw new Exception("UNKNOWN_METHOD", ['method' => $data['op']]); } - return new Response([ + return HTTP::respJson([ 'seq' => $data['seq'], 'status' => 0, 'content' => $this->$method($data), ]); } catch (Exception $e) { - return new Response([ + return HTTP::respJson([ 'seq' => $data['seq'], 'status' => 1, 'content' => $e->getData(), ]); } catch (AbstractException $e) { - return new EmptyResponse(500); + return HTTP::respEmpty(500); } } else { // absence of a request body indicates an error - return new Response(self::FATAL_ERR); + return HTTP::respJson(self::FATAL_ERR); } } @@ -1000,7 +999,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { switch ($e->getCode()) { case 10236: // label already exists // retrieve the ID of the existing label; duplicating a label silently returns the existing one - return $this->labelOut(Arsse::$db->labelPropertiesGet(Arsse::$user->id, $in['name'], true)['id']); + return $this->labelOut(Arsse::$db->labelPropertiesGet(Arsse::$user->id, $in['name'], true)['id']); default: // other errors related to input throw new Exception("INCORRECT_USAGE"); } diff --git a/lib/REST/TinyTinyRSS/Icon.php b/lib/REST/TinyTinyRSS/Icon.php index dd718bcb..37f2ce89 100644 --- a/lib/REST/TinyTinyRSS/Icon.php +++ b/lib/REST/TinyTinyRSS/Icon.php @@ -7,10 +7,10 @@ declare(strict_types=1); namespace JKingWeb\Arsse\REST\TinyTinyRSS; use JKingWeb\Arsse\Arsse; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Db\ExceptionInput; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\EmptyResponse as Response; class Icon extends \JKingWeb\Arsse\REST\AbstractHandler { public function __construct() { @@ -22,25 +22,25 @@ class Icon extends \JKingWeb\Arsse\REST\AbstractHandler { Arsse::$user->id = $req->getAttribute("authenticatedUser"); } elseif ($req->getAttribute("authenticationFailed", false) || Arsse::$conf->userHTTPAuthRequired) { // otherwise if HTTP authentication failed or did not occur when it is required, deny access at the HTTP level - return new Response(401); + return HTTP::respEmpty(401); } if ($req->getMethod() !== "GET") { // only GET requests are allowed - return new Response(405, ['Allow' => "GET"]); + return HTTP::respEmpty(405, ['Allow' => "GET"]); } elseif (!preg_match("<^(\d+)\.ico$>D", $req->getRequestTarget(), $match) || !((int) $match[1])) { - return new Response(404); + return HTTP::respEmpty(404); } try { $url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'] ?? null; if (!$url) { - return new Response(404); + return HTTP::respEmpty(404); } if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) { $url = substr($url, 0, $pos); } - return new Response(301, ['Location' => $url]); + return HTTP::respEmpty(301, ['Location' => $url]); } catch (ExceptionInput $e) { - return new Response(404); + return HTTP::respEmpty(404); } } } diff --git a/locale/en.php b/locale/en.php index a439a45b..c9546472 100644 --- a/locale/en.php +++ b/locale/en.php @@ -34,7 +34,7 @@ return [ 'API.Miniflux.Error.InvalidTitle' => 'Invalid feed title', 'API.Miniflux.Error.InvalidImportCategory' => 'Payload contains an invalid category name', 'API.Miniflux.Error.DuplicateImportCategory' => 'Payload contains the same category name twice', - 'API.Miniflux.Error.FailedImportFeed' => 'Unable to import feed at URL "{url}" (code {code}', + 'API.Miniflux.Error.FailedImportFeed' => 'Unable to import feed at URL "{url}" (code {code})', 'API.Miniflux.Error.InvalidImportLabel' => 'Payload contains an invalid label name', 'API.TTRSS.Category.Uncategorized' => 'Uncategorized', diff --git a/tests/cases/Database/SeriesArticle.php b/tests/cases/Database/SeriesArticle.php index 4dea4baf..15f851ee 100644 --- a/tests/cases/Database/SeriesArticle.php +++ b/tests/cases/Database/SeriesArticle.php @@ -19,7 +19,7 @@ trait SeriesArticle { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "", 1], ["john.doe@example.com", "", 2], ["john.doe@example.org", "", 3], @@ -29,7 +29,7 @@ trait SeriesArticle { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/1", "Feed 1"], [2,"http://example.com/2", "Feed 2"], [3,"http://example.com/3", "Feed 3"], @@ -47,7 +47,7 @@ trait SeriesArticle { ], 'arsse_folders' => [ 'columns' => ["id", "owner", "parent", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", null, "Technology"], [2, "john.doe@example.com", 1, "Software"], [3, "john.doe@example.com", 1, "Rocketry"], @@ -61,7 +61,7 @@ trait SeriesArticle { ], 'arsse_tags' => [ 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", "Technology"], [2, "john.doe@example.com", "Software"], [3, "john.doe@example.com", "Rocketry"], @@ -74,7 +74,7 @@ trait SeriesArticle { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "folder", "title", "scrape"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com",1, null,"Subscription 1", 0], [2, "john.doe@example.com",2, null,null, 0], [3, "john.doe@example.com",3, 1,"Subscription 3", 0], @@ -94,7 +94,7 @@ trait SeriesArticle { ], 'arsse_tag_members' => [ 'columns' => ["tag", "subscription", "assigned"], - 'rows' => [ + 'rows' => [ [1,3,1], [1,4,1], [2,4,1], @@ -109,8 +109,8 @@ trait SeriesArticle { ], 'arsse_articles' => [ 'columns' => [ - "id", "feed", "url", "title", "author", "published", "edited", "content", "guid", - "url_title_hash", "url_content_hash", "title_content_hash", "modified", "content_scraped" + "id", "feed", "url", "title", "author", "published", "edited", "content", "guid", + "url_title_hash", "url_content_hash", "title_content_hash", "modified", "content_scraped", ], 'rows' => [ [1,1,null,"Title one", null,null,null,"First article", null,"","","","2000-01-01T00:00:00Z",null], @@ -142,7 +142,7 @@ trait SeriesArticle { ], 'arsse_enclosures' => [ 'columns' => ["article", "url", "type"], - 'rows' => [ + 'rows' => [ [102,"http://example.com/text","text/plain"], [103,"http://example.com/video","video/webm"], [104,"http://example.com/image","image/svg+xml"], @@ -152,7 +152,7 @@ trait SeriesArticle { ], 'arsse_editions' => [ 'columns' => ["id", "article"], - 'rows' => [ + 'rows' => [ [1,1], [2,2], [3,3], @@ -188,7 +188,7 @@ trait SeriesArticle { ], 'arsse_marks' => [ 'columns' => ["subscription", "article", "read", "starred", "modified", "note", "hidden"], - 'rows' => [ + 'rows' => [ [1, 1,1,1,'2000-01-01 00:00:00','',0], [5, 19,1,0,'2016-01-01 00:00:00','',0], [5, 20,0,1,'2005-01-01 00:00:00','',0], @@ -209,7 +209,7 @@ trait SeriesArticle { ], 'arsse_categories' => [ // author-supplied categories 'columns' => ["article", "name"], - 'rows' => [ + 'rows' => [ [19,"Fascinating"], [19,"Logical"], [20,"Interesting"], @@ -218,7 +218,7 @@ trait SeriesArticle { ], 'arsse_labels' => [ // labels applied to articles 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com","Interesting"], [2,"john.doe@example.com","Fascinating"], [3,"jane.doe@example.com","Boring"], @@ -227,7 +227,7 @@ trait SeriesArticle { ], 'arsse_label_members' => [ 'columns' => ["label", "article", "subscription", "assigned", "modified"], - 'rows' => [ + 'rows' => [ [1, 1,1,1,'2000-01-01 00:00:00'], [2, 1,1,1,'2000-01-01 00:00:00'], [1,19,5,1,'2000-01-01 00:00:00'], diff --git a/tests/cases/Database/SeriesCleanup.php b/tests/cases/Database/SeriesCleanup.php index 8850ecd2..483edfd1 100644 --- a/tests/cases/Database/SeriesCleanup.php +++ b/tests/cases/Database/SeriesCleanup.php @@ -28,14 +28,14 @@ trait SeriesCleanup { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], ], 'arsse_sessions' => [ 'columns' => ["id", "created", "expires", "user"], - 'rows' => [ + 'rows' => [ ["a", $nowish, $faroff, "jane.doe@example.com"], // not expired and recently created, thus kept ["b", $nowish, $soon, "jane.doe@example.com"], // not expired and recently created, thus kept ["c", $daysago, $soon, "jane.doe@example.com"], // created more than a day ago, thus deleted @@ -45,7 +45,7 @@ trait SeriesCleanup { ], 'arsse_tokens' => [ 'columns' => ["id", "class", "user", "expires"], - 'rows' => [ + 'rows' => [ ["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff], ["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired ["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null], @@ -54,7 +54,7 @@ trait SeriesCleanup { ], 'arsse_icons' => [ 'columns' => ["id", "url", "orphaned"], - 'rows' => [ + 'rows' => [ [1,'http://localhost:8000/Icon/PNG',$daybefore], [2,'http://localhost:8000/Icon/GIF',$daybefore], [3,'http://localhost:8000/Icon/SVG1',null], @@ -62,7 +62,7 @@ trait SeriesCleanup { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title", "orphaned", "size", "icon"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/1","",$daybefore,2,null], //latest two articles should be kept [2,"http://example.com/2","",$yesterday,0,2], [3,"http://example.com/3","",null,0,1], @@ -71,7 +71,7 @@ trait SeriesCleanup { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed"], - 'rows' => [ + 'rows' => [ // one feed previously marked for deletion has a subscription again, and so should not be deleted [1,'jane.doe@example.com',1], // other subscriptions exist for article cleanup tests @@ -80,7 +80,7 @@ trait SeriesCleanup { ], 'arsse_articles' => [ 'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], - 'rows' => [ + 'rows' => [ [1,1,"","","",$weeksago], // is the latest article, thus is kept [2,1,"","","",$weeksago], // is the second latest article, thus is kept [3,1,"","","",$weeksago], // is starred by one user, thus is kept @@ -94,7 +94,7 @@ trait SeriesCleanup { ], 'arsse_editions' => [ 'columns' => ["id", "article"], - 'rows' => [ + 'rows' => [ [1,1], [2,2], [3,3], @@ -105,7 +105,7 @@ trait SeriesCleanup { ], 'arsse_marks' => [ 'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"], - 'rows' => [ + 'rows' => [ [3,1,0,1,0,$weeksago], [4,1,1,0,0,$daysago], [6,1,1,0,0,$nowish], diff --git a/tests/cases/Database/SeriesFeed.php b/tests/cases/Database/SeriesFeed.php index 67fb77a6..2a428cf3 100644 --- a/tests/cases/Database/SeriesFeed.php +++ b/tests/cases/Database/SeriesFeed.php @@ -18,14 +18,14 @@ trait SeriesFeed { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], ], 'arsse_icons' => [ 'columns' => ["id", "url", "type", "data"], - 'rows' => [ + 'rows' => [ [1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")], [2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], // this actually contains the data of SVG2, which will lead to a row update when retieved @@ -34,7 +34,7 @@ trait SeriesFeed { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"], - 'rows' => [ + 'rows' => [ [1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,null], [2,"http://localhost:8000/Feed/Matching/1","Eek",5,"There was an error last time",$past,$future,0,null], [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,null], @@ -49,7 +49,7 @@ trait SeriesFeed { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "keep_rule", "block_rule"], - 'rows' => [ + 'rows' => [ [1,'john.doe@example.com',1,null,'^Sport$'], [2,'john.doe@example.com',2,"",null], [3,'john.doe@example.com',3,'\w+',null], @@ -60,7 +60,7 @@ trait SeriesFeed { ], 'arsse_articles' => [ 'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], - 'rows' => [ + 'rows' => [ [1,1,'http://example.com/1','Article title 1','','2000-01-01 00:00:00','2000-01-01 00:00:00','

Article content 1

','e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda','f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6','fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4','18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',$past], [2,1,'http://example.com/2','Article title 2','','2000-01-02 00:00:00','2000-01-02 00:00:00','

Article content 2

','5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7','0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153','13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9','2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',$past], [3,1,'http://example.com/3','Article title 3','','2000-01-03 00:00:00','2000-01-03 00:00:00','

Article content 3

','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b',$past], @@ -72,7 +72,7 @@ trait SeriesFeed { ], 'arsse_editions' => [ 'columns' => ["id", "article", "modified"], - 'rows' => [ + 'rows' => [ [1,1,$past], [2,2,$past], [3,3,$past], @@ -82,7 +82,7 @@ trait SeriesFeed { ], 'arsse_marks' => [ 'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"], - 'rows' => [ + 'rows' => [ // Jane's marks [1,6,1,0,0,$past], [2,6,1,0,0,$past], @@ -97,13 +97,13 @@ trait SeriesFeed { ], 'arsse_enclosures' => [ 'columns' => ["article", "url", "type"], - 'rows' => [ + 'rows' => [ [7,'http://example.com/png','image/png'], ], ], 'arsse_categories' => [ 'columns' => ["article", "name"], - 'rows' => [ + 'rows' => [ [7,'Syrinx'], ], ], diff --git a/tests/cases/Database/SeriesFolder.php b/tests/cases/Database/SeriesFolder.php index 4e0eec4b..9c3147f5 100644 --- a/tests/cases/Database/SeriesFolder.php +++ b/tests/cases/Database/SeriesFolder.php @@ -13,7 +13,7 @@ trait SeriesFolder { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], @@ -41,7 +41,7 @@ trait SeriesFolder { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/1", "Feed 1"], [2,"http://example.com/2", "Feed 2"], [3,"http://example.com/3", "Feed 3"], @@ -59,7 +59,7 @@ trait SeriesFolder { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "folder"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com",1, null], [2, "john.doe@example.com",2, null], [3, "john.doe@example.com",3, 1], diff --git a/tests/cases/Database/SeriesIcon.php b/tests/cases/Database/SeriesIcon.php index 73b6cf4e..58d43780 100644 --- a/tests/cases/Database/SeriesIcon.php +++ b/tests/cases/Database/SeriesIcon.php @@ -17,14 +17,14 @@ trait SeriesIcon { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], ], 'arsse_icons' => [ 'columns' => ["id", "url", "type", "data"], - 'rows' => [ + 'rows' => [ [1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")], [2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], [3,'http://localhost:8000/Icon/SVG1','image/svg+xml',''], @@ -33,7 +33,7 @@ trait SeriesIcon { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"], - 'rows' => [ + 'rows' => [ [1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,1], [2,"http://localhost:8000/Feed/Matching/1","Eek",5,"There was an error last time",$past,$future,0,2], [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,3], @@ -43,7 +43,7 @@ trait SeriesIcon { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed"], - 'rows' => [ + 'rows' => [ [1,'john.doe@example.com',1], [2,'john.doe@example.com',2], [3,'john.doe@example.com',3], diff --git a/tests/cases/Database/SeriesLabel.php b/tests/cases/Database/SeriesLabel.php index d4ffa721..72d3281b 100644 --- a/tests/cases/Database/SeriesLabel.php +++ b/tests/cases/Database/SeriesLabel.php @@ -15,7 +15,7 @@ trait SeriesLabel { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ["john.doe@example.org", "",3], @@ -24,7 +24,7 @@ trait SeriesLabel { ], 'arsse_folders' => [ 'columns' => ["id", "owner", "parent", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", null, "Technology"], [2, "john.doe@example.com", 1, "Software"], [3, "john.doe@example.com", 1, "Rocketry"], @@ -38,7 +38,7 @@ trait SeriesLabel { ], 'arsse_feeds' => [ 'columns' => ["id", "url"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/1"], [2,"http://example.com/2"], [3,"http://example.com/3"], @@ -56,7 +56,7 @@ trait SeriesLabel { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "folder"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com",1,null], [2,"john.doe@example.com",2,null], [3,"john.doe@example.com",3,1], @@ -75,7 +75,7 @@ trait SeriesLabel { ], 'arsse_articles' => [ 'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], - 'rows' => [ + 'rows' => [ [1,1,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"], [2,1,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"], [3,2,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"], @@ -105,7 +105,7 @@ trait SeriesLabel { ], 'arsse_enclosures' => [ 'columns' => ["article", "url", "type"], - 'rows' => [ + 'rows' => [ [102,"http://example.com/text","text/plain"], [103,"http://example.com/video","video/webm"], [104,"http://example.com/image","image/svg+xml"], @@ -115,7 +115,7 @@ trait SeriesLabel { ], 'arsse_editions' => [ 'columns' => ["id", "article"], - 'rows' => [ + 'rows' => [ [1,1], [2,2], [3,3], @@ -151,7 +151,7 @@ trait SeriesLabel { ], 'arsse_marks' => [ 'columns' => ["subscription", "article", "read", "starred", "modified", "hidden"], - 'rows' => [ + 'rows' => [ [1, 1,1,1,'2000-01-01 00:00:00',0], [5, 19,1,0,'2000-01-01 00:00:00',0], [5, 20,0,1,'2010-01-01 00:00:00',0], @@ -169,7 +169,7 @@ trait SeriesLabel { ], 'arsse_labels' => [ 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com","Interesting"], [2,"john.doe@example.com","Fascinating"], [3,"jane.doe@example.com","Boring"], @@ -178,7 +178,7 @@ trait SeriesLabel { ], 'arsse_label_members' => [ 'columns' => ["label", "article", "subscription", "assigned"], - 'rows' => [ + 'rows' => [ [1, 1,1,1], [2, 1,1,1], [1,19,5,1], diff --git a/tests/cases/Database/SeriesMeta.php b/tests/cases/Database/SeriesMeta.php index aeac6b79..9f25f698 100644 --- a/tests/cases/Database/SeriesMeta.php +++ b/tests/cases/Database/SeriesMeta.php @@ -14,7 +14,7 @@ trait SeriesMeta { $dataBare = [ 'arsse_meta' => [ 'columns' => ["key", "value"], - 'rows' => [ + 'rows' => [ //['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION], ['album',"A Farewell to Kings"], ], diff --git a/tests/cases/Database/SeriesSession.php b/tests/cases/Database/SeriesSession.php index ffbba56e..b025b922 100644 --- a/tests/cases/Database/SeriesSession.php +++ b/tests/cases/Database/SeriesSession.php @@ -24,14 +24,14 @@ trait SeriesSession { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], ], 'arsse_sessions' => [ 'columns' => ["id", "user", "created", "expires"], - 'rows' => [ + 'rows' => [ ["80fa94c1a11f11e78667001e673b2560", "jane.doe@example.com", $past, $faroff], ["27c6de8da13311e78667001e673b2560", "jane.doe@example.com", $past, $past], // expired ["ab3b3eb8a13311e78667001e673b2560", "jane.doe@example.com", $old, $future], // too old diff --git a/tests/cases/Database/SeriesSubscription.php b/tests/cases/Database/SeriesSubscription.php index 0b279702..4b3143b9 100644 --- a/tests/cases/Database/SeriesSubscription.php +++ b/tests/cases/Database/SeriesSubscription.php @@ -16,7 +16,7 @@ trait SeriesSubscription { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "", 1], ["john.doe@example.com", "", 2], ["jill.doe@example.com", "", 3], @@ -25,7 +25,7 @@ trait SeriesSubscription { ], 'arsse_folders' => [ 'columns' => ["id", "owner", "parent", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", null, "Technology"], [2, "john.doe@example.com", 1, "Software"], [3, "john.doe@example.com", 1, "Rocketry"], @@ -36,14 +36,14 @@ trait SeriesSubscription { ], 'arsse_icons' => [ 'columns' => ["id", "url", "data"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/favicon.ico", "ICON DATA"], [2,"http://example.net/favicon.ico", null], ], ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title", "username", "password", "updated", "next_fetch", "icon"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/feed1", "Ook", "", "",strtotime("now"),strtotime("now"),null], [2,"http://example.com/feed2", "eek", "", "",strtotime("now - 1 hour"),strtotime("now - 1 hour"),1], [3,"http://example.com/feed3", "Ack", "", "",strtotime("now + 1 hour"),strtotime("now + 1 hour"),2], @@ -52,7 +52,7 @@ trait SeriesSubscription { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "title", "folder", "pinned", "order_type", "keep_rule", "block_rule", "scrape"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com",2,null,null,1,2,null,null,0], [2,"jane.doe@example.com",2,null,null,0,0,null,null,0], [3,"john.doe@example.com",3,"Ook",2,0,1,null,null,0], @@ -63,7 +63,7 @@ trait SeriesSubscription { ], 'arsse_tags' => [ 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com","Interesting"], [2,"john.doe@example.com","Fascinating"], [3,"jane.doe@example.com","Boring"], @@ -72,7 +72,7 @@ trait SeriesSubscription { ], 'arsse_tag_members' => [ 'columns' => ["tag", "subscription", "assigned"], - 'rows' => [ + 'rows' => [ [1,1,1], [1,3,0], [2,1,1], @@ -82,7 +82,7 @@ trait SeriesSubscription { ], 'arsse_articles' => [ 'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "title"], - 'rows' => [ + 'rows' => [ [1,2,"","","","Title 1"], [2,2,"","","","Title 2"], [3,2,"","","","Title 3"], @@ -95,7 +95,7 @@ trait SeriesSubscription { ], 'arsse_editions' => [ 'columns' => ["id", "article"], - 'rows' => [ + 'rows' => [ [1,1], [2,2], [3,3], @@ -108,7 +108,7 @@ trait SeriesSubscription { ], 'arsse_categories' => [ 'columns' => ["article", "name"], - 'rows' => [ + 'rows' => [ [1,"A"], [2,"B"], [4,"D"], @@ -120,7 +120,7 @@ trait SeriesSubscription { ], 'arsse_marks' => [ 'columns' => ["article", "subscription", "read", "starred", "hidden"], - 'rows' => [ + 'rows' => [ [1,2,1,0,0], [2,2,1,0,0], [3,2,1,0,0], diff --git a/tests/cases/Database/SeriesTag.php b/tests/cases/Database/SeriesTag.php index 47c9fa7c..ef695706 100644 --- a/tests/cases/Database/SeriesTag.php +++ b/tests/cases/Database/SeriesTag.php @@ -14,7 +14,7 @@ trait SeriesTag { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ["john.doe@example.org", "",3], @@ -23,7 +23,7 @@ trait SeriesTag { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title"], - 'rows' => [ + 'rows' => [ [1,"http://example.com/1",""], [2,"http://example.com/2",""], [3,"http://example.com/3","Feed Title"], @@ -41,7 +41,7 @@ trait SeriesTag { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "feed", "title"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", 1,"Lord of Carrots"], [2, "john.doe@example.com", 2,null], [3, "john.doe@example.com", 3,"Subscription Title"], @@ -60,7 +60,7 @@ trait SeriesTag { ], 'arsse_tags' => [ 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1,"john.doe@example.com","Interesting"], [2,"john.doe@example.com","Fascinating"], [3,"jane.doe@example.com","Boring"], @@ -69,7 +69,7 @@ trait SeriesTag { ], 'arsse_tag_members' => [ 'columns' => ["tag", "subscription", "assigned"], - 'rows' => [ + 'rows' => [ [1,1,1], [1,3,0], [1,5,1], diff --git a/tests/cases/Database/SeriesToken.php b/tests/cases/Database/SeriesToken.php index ab90244e..6d2fb6e8 100644 --- a/tests/cases/Database/SeriesToken.php +++ b/tests/cases/Database/SeriesToken.php @@ -18,14 +18,14 @@ trait SeriesToken { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["jane.doe@example.com", "",1], ["john.doe@example.com", "",2], ], ], 'arsse_tokens' => [ 'columns' => ["id", "class", "user", "expires", "data"], - 'rows' => [ + 'rows' => [ ["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff, null], ["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past, null], // expired ["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null, null], diff --git a/tests/cases/Database/SeriesUser.php b/tests/cases/Database/SeriesUser.php index 2caba860..0053d7a1 100644 --- a/tests/cases/Database/SeriesUser.php +++ b/tests/cases/Database/SeriesUser.php @@ -13,7 +13,7 @@ trait SeriesUser { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num", "admin"], - 'rows' => [ + 'rows' => [ ["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', 1, 1], // password is hash of "secret" ["jane.doe@example.com", "", 2, 0], ["john.doe@example.com", "", 3, 0], @@ -21,7 +21,7 @@ trait SeriesUser { ], 'arsse_user_meta' => [ 'columns' => ["owner", "key", "value"], - 'rows' => [ + 'rows' => [ ["admin@example.net", "lang", "en"], ["admin@example.net", "tz", "America/Toronto"], ["admin@example.net", "sort_asc", "0"], diff --git a/tests/cases/ImportExport/TestImportExport.php b/tests/cases/ImportExport/TestImportExport.php index a6d6194b..e6eb36ee 100644 --- a/tests/cases/ImportExport/TestImportExport.php +++ b/tests/cases/ImportExport/TestImportExport.php @@ -42,14 +42,14 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest { $this->data = [ 'arsse_users' => [ 'columns' => ["id", "password", "num"], - 'rows' => [ + 'rows' => [ ["john.doe@example.com", "", 1], ["jane.doe@example.com", "", 2], ], ], 'arsse_folders' => [ 'columns' => ["id", "owner", "parent", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", null, "Science"], [2, "john.doe@example.com", 1, "Rocketry"], [3, "john.doe@example.com", null, "Politics"], @@ -60,7 +60,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest { ], 'arsse_feeds' => [ 'columns' => ["id", "url", "title"], - 'rows' => [ + 'rows' => [ [1, "http://localhost:8000/Import/nasa-jpl", "NASA JPL"], [2, "http://localhost:8000/Import/torstar", "Toronto Star"], [3, "http://localhost:8000/Import/ars", "Ars Technica"], @@ -71,7 +71,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest { ], 'arsse_subscriptions' => [ 'columns' => ["id", "owner", "folder", "feed", "title"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", 2, 1, "NASA JPL"], [2, "john.doe@example.com", 5, 2, "Toronto Star"], [3, "john.doe@example.com", 1, 3, "Ars Technica"], @@ -82,7 +82,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest { ], 'arsse_tags' => [ 'columns' => ["id", "owner", "name"], - 'rows' => [ + 'rows' => [ [1, "john.doe@example.com", "canada"], [2, "john.doe@example.com", "frequent"], [3, "john.doe@example.com", "gaming"], @@ -93,7 +93,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest { ], 'arsse_tag_members' => [ 'columns' => ["tag", "subscription", "assigned"], - 'rows' => [ + 'rows' => [ [1, 2, 1], [1, 4, 1], [1, 5, 1], diff --git a/tests/cases/Misc/TestHTTP.php b/tests/cases/Misc/TestHTTP.php index 9bffe073..fee9a67a 100644 --- a/tests/cases/Misc/TestHTTP.php +++ b/tests/cases/Misc/TestHTTP.php @@ -7,14 +7,17 @@ declare(strict_types=1); namespace JKingWeb\Arsse\TestCase\Misc; use JKingWeb\Arsse\Misc\HTTP; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\ResponseInterface; /** @covers \JKingWeb\Arsse\Misc\HTTP */ class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideMediaTypes */ public function testMatchMediaType(string $header, array $types, bool $exp): void { - $msg = (new \Laminas\Diactoros\Request)->withHeader("Content-Type", $header); + $msg = (new Request("POST", "/"))->withHeader("Content-Type", $header); $this->assertSame($exp, HTTP::matchType($msg, ...$types)); - $msg = (new \Laminas\Diactoros\Response)->withHeader("Content-Type", $header); + $msg = (new Response)->withHeader("Content-Type", $header); $this->assertSame($exp, HTTP::matchType($msg, ...$types)); } @@ -27,6 +30,26 @@ class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest { ["", ["application/json"], false], ["", ["application/json", ""], true], ["application/json ;", ["application/json"], true], + ["application/feed+json", ["application/json", "+json"], true], + ["application/xhtml+xml", ["application/json", "+json"], false], + ]; + } + + /** @dataProvider provideTypedMessages */ + public function testCreateResponses(string $type, array $params, ResponseInterface $exp): void { + $act = call_user_func(["JKingWeb\\Arsse\\Misc\\HTTP", $type], ...$params); + $this->assertMessage($exp, $act); + } + + public function provideTypedMessages(): iterable { + return [ + ["respEmpty", [422, ['Content-Length' => "0"]], new Response(422, ['Content-Length' => "0"])], + ["respText", ["OOK"], new Response(200, ['Content-Type' => "text/plain; charset=UTF-8"], "OOK")], + ["respText", ["OOK", 201, ['Content-Type' => "application/octet-stream"]], new Response(201, ['Content-Type' => "application/octet-stream"], "OOK")], + ["respJson", [['ook' => "eek"]], new Response(200, ['Content-Type' => "application/json"], '{"ook":"eek"}')], + ["respJson", [['ook' => "eek"], 400, ['Content-Type' => "application/feed+json"]], new Response(400, ['Content-Type' => "application/feed+json"], '{"ook":"eek"}')], + ["respXml", [""], new Response(200, ['Content-Type' => "application/xml; charset=UTF-8"], "")], + ["respXml", ["", 451, ['Content-Type' => "text/plain", 'Vary' => "ETag"]], new Response(451, ['Content-Type' => "text/plain", 'Vary' => "ETag"], "")], ]; } } diff --git a/tests/cases/REST/Fever/TestAPI.php b/tests/cases/REST/Fever/TestAPI.php index 6a618b68..ff809647 100644 --- a/tests/cases/REST/Fever/TestAPI.php +++ b/tests/cases/REST/Fever/TestAPI.php @@ -9,15 +9,13 @@ namespace JKingWeb\Arsse\TestCase\REST\Fever; use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\User; use JKingWeb\Arsse\Database; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Context\Context; 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\JsonResponse; -use Laminas\Diactoros\Response\XmlResponse; -use Laminas\Diactoros\Response\EmptyResponse; /** @covers \JKingWeb\Arsse\REST\Fever\API */ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { @@ -192,9 +190,9 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { } public function provideTokenAuthenticationRequests(): iterable { - $success = new JsonResponse(['auth' => 1]); - $failure = new JsonResponse(['auth' => 0]); - $denied = new EmptyResponse(401); + $success = HTTP::respJson(['auth' => 1]); + $failure = HTTP::respJson(['auth' => 0]); + $denied = HTTP::respEmpty(401); return [ [false, true, null, [], ['api' => null], $failure], [false, false, null, [], ['api' => null], $failure], @@ -255,7 +253,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 2, 'name' => "Interesting", 'subscription' => 1], ['id' => 2, 'name' => "Interesting", 'subscription' => 3], ])); - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'groups' => [ ['id' => 1, 'title' => "Fascinating"], ['id' => 2, 'title' => "Interesting"], @@ -281,7 +279,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 2, 'name' => "Interesting", 'subscription' => 1], ['id' => 2, 'name' => "Interesting", 'subscription' => 3], ])); - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'feeds' => [ ['id' => 1, 'favicon_id' => 42, 'title' => "Ankh-Morpork News", 'url' => "http://example.com/feed", 'site_url' => "http://example.com/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("2019-01-01T21:12:00Z")], ['id' => 2, 'favicon_id' => 0, 'title' => "Ook, Ook Eek Ook!", 'url' => "http://example.net/feed", 'site_url' => "http://example.net/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("1988-06-24T12:21:00Z")], @@ -301,7 +299,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $order = [$desc ? "id desc" : "id"]; $this->dbMock->articleList->returns(new Result($this->articles['db'])); $this->dbMock->articleCount->with($this->userId, (new Context)->hidden(false))->returns(1024); - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'items' => $this->articles['rest'], 'total_items' => 1024, ]); @@ -330,15 +328,15 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $unread = [['id' => 4],['id' => 5],['id' => 6]]; $this->dbMock->articleList->with($this->userId, (new Context)->starred(true)->hidden(false))->returns(new Result($saved)); $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); - $exp = new JsonResponse(['saved_item_ids' => "1,2,3"]); + $exp = HTTP::respJson(['saved_item_ids' => "1,2,3"]); $this->assertMessage($exp, $this->req("api&saved_item_ids")); - $exp = new JsonResponse(['unread_item_ids' => "4,5,6"]); + $exp = HTTP::respJson(['unread_item_ids' => "4,5,6"]); $this->assertMessage($exp, $this->req("api&unread_item_ids")); } public function testListHotLinks(): void { // hot links are not actually implemented, so an empty array should be all we get - $exp = new JsonResponse(['links' => []]); + $exp = HTTP::respJson(['links' => []]); $this->assertMessage($exp, $this->req("api&links")); } @@ -350,7 +348,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); $this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing")); - $exp = new JsonResponse($out); + $exp = HTTP::respJson($out); $this->assertMessage($exp, $this->req("api", $post)); if ($c && $data) { $this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c)); @@ -367,7 +365,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); $this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing")); - $exp = new JsonResponse($out); + $exp = HTTP::respJson($out); $this->assertMessage($exp, $this->req("api&$get")); if ($c && $data) { $this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c)); @@ -421,11 +419,11 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { public function provideInvalidRequests(): iterable { return [ - 'Not an API request' => ["", "", "POST", null, new EmptyResponse(404)], - 'Wrong method' => ["api", "", "PUT", null, new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])], - 'Non-standard method' => ["api", "", "GET", null, new JsonResponse([])], - 'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this - 'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this + 'Not an API request' => ["", "", "POST", null, HTTP::respEmpty(404)], + 'Wrong method' => ["api", "", "PUT", null, HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"])], + 'Non-standard method' => ["api", "", "GET", null, HTTP::respJson([])], + 'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", HTTP::respJson([])], // some clients send nonsensical content types; Fever seems to have allowed this + 'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", HTTP::respJson([])], // some clients send nonsensical content types; Fever seems to have allowed this ]; } @@ -433,21 +431,21 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $this->hMock->baseResponse->forwards(); $this->hMock->logIn->returns(true); $this->dbMock->subscriptionRefreshed->with($this->userId)->returns(new \DateTimeImmutable("2000-01-01T00:00:00Z")); - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'api_version' => API::LEVEL, 'auth' => 1, 'last_refreshed_on_time' => 946684800, ]); $this->assertMessage($exp, $this->req("api")); $this->dbMock->subscriptionRefreshed->with($this->userId)->returns(null); // no subscriptions - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'api_version' => API::LEVEL, 'auth' => 1, 'last_refreshed_on_time' => null, ]); $this->assertMessage($exp, $this->req("api")); $this->hMock->logIn->returns(false); - $exp = new JsonResponse([ + $exp = HTTP::respJson([ 'api_version' => API::LEVEL, 'auth' => 0, ]); @@ -460,7 +458,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->limit(1)->hidden(false)), ["marked_date"], ["marked_date desc"])->returns(new Result([['marked_date' => "2000-01-01 00:00:00"]])); $this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(new Result($unread)); $this->dbMock->articleMark->returns(0); - $exp = new JsonResponse($out); + $exp = HTTP::respJson($out); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); $this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedRange("1999-12-31T23:59:45Z", null)->hidden(false))); $this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([])); @@ -473,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")); } @@ -485,7 +483,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 44, 'type' => null, 'data' => "IMAGE DATA"], ['id' => 47, 'type' => null, 'data' => null], ]))); - $exp = new JsonResponse(['favicons' => [ + $exp = HTTP::respJson(['favicons' => [ ['id' => 0, 'data' => $iconType.",".$iconData], ['id' => 42, 'data' => "image/svg+xml;base64,PHN2Zy8+"], ['id' => 44, 'data' => "application/octet-stream;base64,SU1BR0UgREFUQQ=="], @@ -494,7 +492,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { } public function testAnswerOptionsRequest(): void { - $exp = new EmptyResponse(204, [ + $exp = HTTP::respEmpty(204, [ 'Allow' => "POST", 'Accept' => "application/x-www-form-urlencoded, multipart/form-data", ]); 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 bcf81d18..4975d4af 100644 --- a/tests/cases/REST/Miniflux/TestStatus.php +++ b/tests/cases/REST/Miniflux/TestStatus.php @@ -6,11 +6,10 @@ declare(strict_types=1); namespace JKingWeb\Arsse\TestCase\REST\Miniflux; +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\EmptyResponse; -use Laminas\Diactoros\Response\TextResponse; /** @covers \JKingWeb\Arsse\REST\Miniflux\Status */ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest { @@ -22,13 +21,13 @@ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest { public function provideRequests(): iterable { return [ - ["/version", "GET", new TextResponse(V1::VERSION)], - ["/version", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], - ["/version", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], - ["/healthcheck", "GET", new TextResponse("OK")], - ["/healthcheck", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], - ["/healthcheck", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], - ["/version/", "GET", new EmptyResponse(404)], + ["/version", "GET", HTTP::respText(V1::VERSION)], + ["/version", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])], + ["/version", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])], + ["/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 3bae78e8..759f7835 100644 --- a/tests/cases/REST/Miniflux/TestV1.php +++ b/tests/cases/REST/Miniflux/TestV1.php @@ -14,10 +14,10 @@ use JKingWeb\Arsse\Context\RootContext; use JKingWeb\Arsse\Context\UnionContext; use JKingWeb\Arsse\User; use JKingWeb\Arsse\Database; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\REST\Miniflux\V1; -use JKingWeb\Arsse\REST\Miniflux\ErrorResponse; use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\ImportExport\Exception as ImportException; use JKingWeb\Arsse\ImportExport\OPML; @@ -25,9 +25,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\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; -use Laminas\Diactoros\Response\TextResponse; /** @covers \JKingWeb\Arsse\REST\Miniflux\V1 */ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { @@ -100,9 +97,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { return $value; } + public function testGenerateErrorResponse() { + $act = V1::respError(["DuplicateUser", 'user' => "john.doe"], 409, ['Cache-Control' => "no-store"]); + $exp = HTTP::respJson(['error_message' => 'The user name "john.doe" already exists'], 409, ['Cache-Control' => "no-store"]); + $this->assertMessage($exp, $act); + } + /** @dataProvider provideAuthResponses */ public function testAuthenticateAUser($token, bool $auth, bool $success): void { - $exp = $success ? new EmptyResponse(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]; @@ -133,7 +136,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideInvalidPaths */ public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { - $exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); + $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []); $this->assertMessage($exp, $this->req($method, $path)); } @@ -148,7 +151,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideOptionsRequests */ public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { - $exp = new EmptyResponse(204, [ + $exp = HTTP::respEmpty(204, [ 'Allow' => $allow, 'Accept' => $accept, ]); @@ -166,7 +169,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])); } @@ -182,12 +185,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ['title' => "Feed", 'type' => "rss", 'url' => "http://localhost:8000/Feed/Discovery/Missing"], ]; return [ - ["http://localhost:8000/Feed/Discovery/Valid", new Response($discovered)], - ["http://localhost:8000/Feed/Discovery/Invalid", new Response([])], - ["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/Valid", HTTP::respJson($discovered)], + ["http://localhost:8000/Feed/Discovery/Invalid", HTTP::respJson([])], + ["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)], ]; } @@ -226,22 +229,22 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideUserQueries(): iterable { self::clearData(); return [ - [true, "/users", new Response(self::USERS)], - [true, "/me", new Response(self::USERS[0])], - [true, "/users/john.doe@example.com", new Response(self::USERS[0])], - [true, "/users/1", new Response(self::USERS[0])], - [true, "/users/jane.doe@example.com", new Response(self::USERS[1])], - [true, "/users/2", new Response(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)], - [false, "/me", new Response(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)], + [true, "/users", HTTP::respJson(self::USERS)], + [true, "/me", HTTP::respJson(self::USERS[0])], + [true, "/users/john.doe@example.com", HTTP::respJson(self::USERS[0])], + [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", 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", 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)], ]; } @@ -306,21 +309,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/2", ['language' => "fr_CA"], null, null, null, null, ['lang' => "fr_CA"], $out1, new Response($resp1, 201)], - [false, "/users/2", ['entry_sorting_direction' => "asc"], null, null, null, null, ['sort_asc' => true], $out1, new Response($resp1, 201)], - [false, "/users/2", ['entry_sorting_direction' => "desc"], null, null, null, null, ['sort_asc' => false], $out1, new Response($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", ['password' => "ook"], null, null, "ook", "ook", null, null, new Response(array_merge($resp1, ['password' => "ook"]), 201)], - [false, "/users/2", ['username' => "ook", 'password' => "ook"], "ook", true, "ook", "ook", null, null, new Response(array_merge($resp1, ['username' => "ook", 'password' => "ook"]), 201)], - [true, "/users/1", ['theme' => "stark"], null, null, null, null, ['theme' => "stark"], $out2, new Response($resp2, 201)], - [true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse("404", 404)], + [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"), 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, V1::respError("404", 404)], ]; } @@ -361,18 +364,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)], - [['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], new Response($resp1, 201)], + [[], 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 { @@ -382,7 +385,7 @@ 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(1))->method("remove")->with("john.doe@example.com"); - $this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/users/2112")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/users/2112")); } public function testDeleteAMissingUser(): void { @@ -392,13 +395,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 { @@ -406,7 +409,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 1, 'name' => "Science"], ['id' => 20, 'name' => "Technology"], ]))); - $exp = new Response([ + $exp = HTTP::respJson([ ['id' => 1, 'title' => "All", 'user_id' => 42], ['id' => 2, 'title' => "Science", 'user_id' => 42], ['id' => 21, 'title' => "Technology", 'user_id' => 42], @@ -416,7 +419,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { // run test again with a renamed root folder Arsse::$user = $this->createMock(User::class); Arsse::$user->method("propertiesGet")->willReturn(['num' => 47, 'admin' => false, 'root_folder_name' => "Uncategorized"]); - $exp = new Response([ + $exp = HTTP::respJson([ ['id' => 1, 'title' => "Uncategorized", 'user_id' => 47], ['id' => 2, 'title' => "Science", 'user_id' => 47], ['id' => 21, 'title' => "Technology", 'user_id' => 47], @@ -440,12 +443,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideCategoryAdditions(): iterable { return [ - ["New", new Response(['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)], + ["New", HTTP::respJson(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)], + ["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)], ]; } @@ -466,27 +469,27 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideCategoryUpdates(): iterable { return [ - [3, "New", "subjectMissing", new ErrorResponse("404", 404)], - [2, "New", true, new Response(['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)], - [1, "New", true, new Response(['id' => 1, 'title' => "New", 'user_id' => 42], 201)], - [1, "Duplicate", "constraintViolation", new Response(['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)], + [3, "New", "subjectMissing", V1::respError("404", 404)], + [2, "New", true, HTTP::respJson(['id' => 2, 'title' => "New", 'user_id' => 42], 201)], + [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", 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)], ]; } public function testDeleteARealCategory(): void { $this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing")); - $this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/2112")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/2112")); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111); - $this->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); } @@ -497,7 +500,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 2112], ]))); $this->dbMock->subscriptionRemove->returns(true); - $this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/1")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/1")); Phony::inOrder( $this->dbMock->begin->calledWith(), $this->dbMock->subscriptionList->calledWith("john.doe@example.com", null, false), @@ -510,42 +513,42 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testListFeeds(): void { $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); - $exp = new Response(self::FEEDS_OUT); + $exp = HTTP::respJson(self::FEEDS_OUT); $this->assertMessage($exp, $this->req("GET", "/feeds")); } public function testListFeedsOfACategory(): void { $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); - $exp = new Response(self::FEEDS_OUT); + $exp = HTTP::respJson(self::FEEDS_OUT); $this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds")); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true); } public function testListFeedsOfTheRootCategory(): void { $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); - $exp = new Response(self::FEEDS_OUT); + $exp = HTTP::respJson(self::FEEDS_OUT); $this->assertMessage($exp, $this->req("GET", "/categories/1/feeds")); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 0, false); } 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); } public function testGetAFeed(): void { $this->dbMock->subscriptionPropertiesGet->returns($this->v(self::FEEDS[0]))->returns($this->v(self::FEEDS[1])); - $this->assertMessage(new Response(self::FEEDS_OUT[0]), $this->req("GET", "/feeds/1")); + $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[0]), $this->req("GET", "/feeds/1")); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1); - $this->assertMessage(new Response(self::FEEDS_OUT[1]), $this->req("GET", "/feeds/55")); + $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[1]), $this->req("GET", "/feeds/55")); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 55); } 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); } @@ -611,39 +614,39 @@ 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)], - [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, true, null, new Response(['feed_id' => 44], 201)], - [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "^A"], 2112, 44, true, true, new Response(['feed_id' => 44], 201)], - [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "A"], 2112, 44, true, true, new Response(['feed_id' => 44], 201)], + [['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)], ]; } /** @dataProvider provideFeedModifications */ public function testModifyAFeed(array $in, array $data, $out, ResponseInterface $exp): void { $this->h = $this->partialMock(V1::class); - $this->h->getFeed->returns(new Response(self::FEEDS_OUT[0])); + $this->h->getFeed->returns(HTTP::respJson(self::FEEDS_OUT[0])); if ($out instanceof \Exception) { $this->dbMock->subscriptionPropertiesSet->throws($out); } else { @@ -655,14 +658,14 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideFeedModifications(): iterable { self::clearData(); - $success = new Response(self::FEEDS_OUT[0], 201); + $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], @@ -672,21 +675,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testModifyAFeedWithNoBody(): void { $this->h = $this->partialMock(V1::class); - $this->h->getFeed->returns(new Response(self::FEEDS_OUT[0])); + $this->h->getFeed->returns(HTTP::respJson(self::FEEDS_OUT[0])); $this->dbMock->subscriptionPropertiesSet->returns(true); - $this->assertMessage(new Response(self::FEEDS_OUT[0], 201), $this->req("PUT", "/feeds/2112", "")); + $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[0], 201), $this->req("PUT", "/feeds/2112", "")); $this->dbMock->subscriptionPropertiesSet->calledWith(Arsse::$user->id, 2112, []); } public function testDeleteAFeed(): void { $this->dbMock->subscriptionRemove->returns(true); - $this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/feeds/2112")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/feeds/2112")); $this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112); } 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,12 +706,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideIcons(): iterable { return [ - [['id' => 44, 'type' => "image/svg+xml", 'data' => ""], new Response(['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' => 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' => ""], 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)], ]; } @@ -744,62 +747,62 @@ 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", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?category_id=47", (clone $c)->folder(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?category_id=1", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=unread", (clone $c)->unread(true)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=read", (clone $c)->unread(false)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=removed", (clone $c)->hidden(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=unread&status=read", (clone $c)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=unread&status=removed", new UnionContext((clone $c)->unread(true), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=removed&status=read", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=removed&status=read&status=removed", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?status=removed&status=read&status=unread", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?starred", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?before=0", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?after_entry_id=42", (clone $c)->articleRange(43, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?before_entry_id=47", (clone $c)->articleRange(null, 46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?limit=4", (clone $c)->limit(4), $o, self::ENTRIES, true, new Response(['total' => 2112, 'entries' => self::ENTRIES_OUT])], - ["/entries?offset=20", (clone $c)->offset(20), $o, [], true, new Response(['total' => 2112, 'entries' => []])], - ["/entries?direction=asc", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=id", $c, ["id"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=published_at", $c, ["modified_date"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=category_id", $c, ["top_folder"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=category_title", $c, ["top_folder_name"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=status", $c, ["hidden", "unread desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=id&direction=desc", $c, ["id desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=published_at&direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=category_id&direction=desc", $c, ["top_folder desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=category_title&direction=desc", $c, ["top_folder_name desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/entries?order=status&direction=desc", $c, ["hidden desc", "unread"], self::ENTRIES, false, new Response(['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")], - ["/feeds/42/entries", (clone $c)->subscription(42), $o, self::ENTRIES, false, new Response(['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, new Response(['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)], - ["/categories/42/entries", (clone $c)->folder(41), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/categories/42/entries?category_id=47", (clone $c)->folder(41), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/categories/42/entries?starred", (clone $c)->folder(41)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], - ["/categories/1/entries", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, new Response(['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)], + ["/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])], + ["/entries?status=unread", (clone $c)->unread(true)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=read", (clone $c)->unread(false)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=removed", (clone $c)->hidden(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=unread&status=read", (clone $c)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=unread&status=removed", new UnionContext((clone $c)->unread(true), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=removed&status=read", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=removed&status=read&status=removed", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?status=removed&status=read&status=unread", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?starred", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before=0", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?after_entry_id=42", (clone $c)->articleRange(43, null), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?before_entry_id=47", (clone $c)->articleRange(null, 46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?limit=4", (clone $c)->limit(4), $o, self::ENTRIES, true, HTTP::respJson(['total' => 2112, 'entries' => self::ENTRIES_OUT])], + ["/entries?offset=20", (clone $c)->offset(20), $o, [], true, HTTP::respJson(['total' => 2112, 'entries' => []])], + ["/entries?direction=asc", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=id", $c, ["id"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=published_at", $c, ["modified_date"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=category_id", $c, ["top_folder"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=category_title", $c, ["top_folder_name"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=status", $c, ["hidden", "unread desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=id&direction=desc", $c, ["id desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/entries?order=published_at&direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], + ["/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, 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, 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, V1::respError("404", 404)], ]; } @@ -828,17 +831,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { self::clearData(); $c = new Context; return [ - ["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], - ["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], - ["/feeds/47/entries/42", (clone $c)->subscription(47)->article(42), [self::ENTRIES[1]], new Response(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)], - ["/categories/47/entries/42", (clone $c)->folder(46)->article(42), [self::ENTRIES[1]], new Response(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/1/entries/42", (clone $c)->folderShallow(0)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], + ["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])], + ["/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), [], 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), [], 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])], ]; } @@ -856,17 +859,17 @@ 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)], - [['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], new EmptyResponse(204)], - [['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], new EmptyResponse(204)], - [['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], new EmptyResponse(204)], + [['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,13 +892,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { self::clearData(); $c = (new Context)->hidden(false); return [ - ["/users/42/mark-all-as-read", $c, 1123, new EmptyResponse(204)], - ["/users/2112/mark-all-as-read", $c, null, new ErrorResponse("403", 403)], - ["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, new EmptyResponse(204)], - ["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], - ["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, new EmptyResponse(204)], - ["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], - ["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, new EmptyResponse(204)], + ["/users/42/mark-all-as-read", $c, 1123, HTTP::respEmpty(204)], + ["/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"), 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"), V1::respError("404", 404)], + ["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, HTTP::respEmpty(204)], ]; } @@ -929,26 +932,26 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideBookmarkTogglings(): iterable { self::clearData(); return [ - [1, true, new EmptyResponse(204)], - [0, false, new EmptyResponse(204)], - [new ExceptionInput("subjectMissing"), null, new ErrorResponse("404", 404)], + [1, true, HTTP::respEmpty(204)], + [0, false, HTTP::respEmpty(204)], + [new ExceptionInput("subjectMissing"), null, V1::respError("404", 404)], ]; } public function testRefreshAFeed(): void { $this->dbMock->subscriptionPropertiesGet->returns([]); - $this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/47/refresh")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/47/refresh")); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 47); } 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); } public function testRefreshAllFeeds(): void { - $this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/refresh")); + $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/refresh")); } /** @dataProvider provideImports */ @@ -964,21 +967,21 @@ 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)], - [true, new Response(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")])], + [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")])], ]; } 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/NextcloudNews/TestV1_2.php b/tests/cases/REST/NextcloudNews/TestV1_2.php index 34720e31..8b7f4d8a 100644 --- a/tests/cases/REST/NextcloudNews/TestV1_2.php +++ b/tests/cases/REST/NextcloudNews/TestV1_2.php @@ -11,13 +11,12 @@ use JKingWeb\Arsse\User; use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Misc\Date; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\REST\NextcloudNews\V1_2; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; /** @covers \JKingWeb\Arsse\REST\NextcloudNews\V1_2 */ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { @@ -336,13 +335,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { } public function testSendAuthenticationChallenge(): void { - $exp = new EmptyResponse(401); + $exp = HTTP::respEmpty(401); $this->assertMessage($exp, $this->req("GET", "/", "", [], false)); } /** @dataProvider provideInvalidPaths */ public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { - $exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); + $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []); $this->assertMessage($exp, $this->req($method, $path)); } @@ -374,16 +373,16 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { } public function testRespondToInvalidInputTypes(): void { - $exp = new EmptyResponse(415, ['Accept' => "application/json"]); + $exp = HTTP::respEmpty(415, ['Accept' => "application/json"]); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '', ['Content-Type' => "application/xml"])); - $exp = new EmptyResponse(400); + $exp = HTTP::respEmpty(400); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '')); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '', ['Content-Type' => null])); } /** @dataProvider provideOptionsRequests */ public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { - $exp = new EmptyResponse(204, [ + $exp = HTTP::respEmpty(204, [ 'Allow' => $allow, 'Accept' => $accept, ]); @@ -408,7 +407,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { ['id' => 12, 'name' => "Hardware"], ]; $this->dbMock->folderList->with($this->userId, null, false)->returns(new Result($this->v($list))); - $exp = new Response(['folders' => $out]); + $exp = HTTP::respJson(['folders' => $out]); $this->assertMessage($exp, $this->req("GET", "/folders")); } @@ -432,23 +431,23 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideFolderCreations(): array { return [ - [['name' => "Software"], true, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])], - [['name' => "Software"], false, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])], - [['name' => "Hardware"], true, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], - [['name' => "Hardware"], false, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], - [['name' => "Software"], true, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], - [['name' => ""], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], - [['name' => " "], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], - [['name' => null], true, new ExceptionInput("missing"), new EmptyResponse(422)], + [['name' => "Software"], true, 1, HTTP::respJson(['folders' => [['id' => 1, 'name' => "Software"]]])], + [['name' => "Software"], false, 1, HTTP::respJson(['folders' => [['id' => 1, 'name' => "Software"]]])], + [['name' => "Hardware"], true, "2", HTTP::respJson(['folders' => [['id' => 2, 'name' => "Hardware"]]])], + [['name' => "Hardware"], false, "2", HTTP::respJson(['folders' => [['id' => 2, 'name' => "Hardware"]]])], + [['name' => "Software"], true, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)], + [['name' => ""], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)], + [['name' => " "], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)], + [['name' => null], true, new ExceptionInput("missing"), HTTP::respEmpty(422)], ]; } public function testRemoveAFolder(): void { $this->dbMock->folderRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("DELETE", "/folders/1")); // fail on the second invocation because it no longer exists - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("DELETE", "/folders/1")); $this->dbMock->folderRemove->times(2)->calledWith($this->userId, 1); } @@ -467,17 +466,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideFolderRenamings(): array { return [ - [['name' => "Software"], 1, true, new EmptyResponse(204)], - [['name' => "Software"], 2, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], - [['name' => "Software"], 3, new ExceptionInput("subjectMissing"), new EmptyResponse(404)], - [['name' => ""], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], - [['name' => " "], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], - [['name' => null], 2, new ExceptionInput("missing"), new EmptyResponse(422)], + [['name' => "Software"], 1, true, HTTP::respEmpty(204)], + [['name' => "Software"], 2, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)], + [['name' => "Software"], 3, new ExceptionInput("subjectMissing"), HTTP::respEmpty(404)], + [['name' => ""], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)], + [['name' => " "], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)], + [['name' => null], 2, new ExceptionInput("missing"), HTTP::respEmpty(422)], ]; } public function testRetrieveServerVersion(): void { - $exp = new Response([ + $exp = HTTP::respJson([ 'version' => V1_2::VERSION, 'arsse_version' => Arsse::VERSION, ]); @@ -497,9 +496,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->subscriptionList->with($this->userId)->returns(new Result([]))->returns(new Result($this->v($this->feeds['db']))); $this->dbMock->articleStarred->with($this->userId)->returns($this->v(['total' => 0]))->returns($this->v(['total' => 5])); $this->dbMock->editionLatest->with($this->userId)->returns(0)->returns(4758915); - $exp = new Response($exp1); + $exp = HTTP::respJson($exp1); $this->assertMessage($exp, $this->req("GET", "/feeds")); - $exp = new Response($exp2); + $exp = HTTP::respJson($exp2); $this->assertMessage($exp, $this->req("GET", "/feeds")); } @@ -538,21 +537,21 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { public function provideNewSubscriptions(): array { $feedException = new \JKingWeb\Arsse\Feed\Exception("", [], new \PicoFeed\Reader\SubscriptionNotFoundException); return [ - [['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new Response(['feeds' => [$this->feeds['rest'][0]]])], - [['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, new Response(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])], - [['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new EmptyResponse(409)], - [['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, new EmptyResponse(409)], - [[], $feedException, 0, [], false, new EmptyResponse(422)], - [['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), new Response(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])], + [['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), HTTP::respJson(['feeds' => [$this->feeds['rest'][0]]])], + [['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, HTTP::respJson(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])], + [['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), HTTP::respEmpty(409)], + [['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, HTTP::respEmpty(409)], + [[], $feedException, 0, [], false, HTTP::respEmpty(422)], + [['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), HTTP::respJson(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])], ]; } public function testRemoveASubscription(): void { $this->dbMock->subscriptionRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); // fail on the second invocation because it no longer exists - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); $this->dbMock->subscriptionRemove->times(2)->calledWith($this->userId, 1); } @@ -571,17 +570,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => 2112])->throws(new ExceptionInput("idMissing")); // folder does not exist $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => -1])->throws(new ExceptionInput("typeViolation")); // folder is invalid $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); // subscription does not exist - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0]))); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2]))); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5]))); } @@ -601,17 +600,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => ""]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => false]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0]))); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3]))); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6]))); } @@ -627,13 +626,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { ], ]; $this->dbMock->feedListStale->returns($this->v(array_column($out, "id"))); - $exp = new Response(['feeds' => $out]); + $exp = HTTP::respJson(['feeds' => $out]); $this->assertMessage($exp, $this->req("GET", "/feeds/all")); } public function testListStaleFeedsWithoutAuthority(): void { $this->userMock->propertiesGet->returns(['admin' => false]); - $exp = new EmptyResponse(403); + $exp = HTTP::respEmpty(403); $this->assertMessage($exp, $this->req("GET", "/feeds/all")); $this->dbMock->feedListStale->never()->called(); } @@ -649,11 +648,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->feedUpdate->with(42)->returns(true); $this->dbMock->feedUpdate->with(2112)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->feedUpdate->with($this->lessThan(1))->throws(new ExceptionInput("typeViolation")); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0]))); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[1]))); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4]))); @@ -661,7 +660,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { public function testUpdateAFeedWithoutAuthority(): void { $this->userMock->propertiesGet->returns(['admin' => false]); - $exp = new EmptyResponse(403); + $exp = HTTP::respEmpty(403); $this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42])); $this->dbMock->feedUpdate->never()->called(); } @@ -683,8 +682,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $c = (new Context)->hidden(false); $t = Date::normalize(time()); $out = new Result($this->v($this->articles['db'])); - $r200 = new Response(['items' => $this->articles['rest']]); - $r422 = new EmptyResponse(422); + $r200 = HTTP::respJson(['items' => $this->articles['rest']]); + $r422 = HTTP::respEmpty(422); return [ ["/items", [], clone $c, $out, $r200], ["/items", ['type' => 0, 'id' => 42], (clone $c)->subscription(42), new ExceptionInput("idMissing"), $r422], @@ -720,13 +719,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $in = json_encode(['newestItemId' => 2112]); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // folder doesn't exist - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112")); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook")); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("PUT", "/folders/42/read", $in)); } @@ -735,13 +734,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $in = json_encode(['newestItemId' => 2112]); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // subscription doesn't exist - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112")); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook")); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/read", $in)); } @@ -749,10 +748,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $read = ['read' => true]; $in = json_encode(['newestItemId' => 2112]); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->editionRange(null, 2112)))->returns(42); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/items/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=2112")); - $exp = new EmptyResponse(422); + $exp = HTTP::respEmpty(422); $this->assertMessage($exp, $this->req("PUT", "/items/read")); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=ook")); } @@ -770,12 +769,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleMark->with($this->userId, $star, $this->equalTo((new Context)->article(2112)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(4)))->returns(42); $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(1337)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/items/1/read")); $this->assertMessage($exp, $this->req("PUT", "/items/2/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/1/3/star")); $this->assertMessage($exp, $this->req("PUT", "/items/4400/4/unstar")); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("PUT", "/items/42/read")); $this->assertMessage($exp, $this->req("PUT", "/items/47/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/1/2112/star")); @@ -801,7 +800,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->anything())->returns(42); $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->editions([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->articles([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("PUT", "/items/read/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/unread/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/star/multiple")); @@ -854,44 +853,44 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { ]; $arr2['warnings']['improperlyConfiguredCron'] = true; $arr2['warnings']['incorrectDbCharset'] = true; - $exp = new Response($arr1); + $exp = HTTP::respJson($arr1); $this->assertMessage($exp, $this->req("GET", "/status")); } public function testCleanUpBeforeUpdate(): void { $this->dbMock->feedCleanup->with()->returns(true); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->dbMock->feedCleanup->calledWith(); } public function testCleanUpBeforeUpdateWithoutAuthority(): void { $this->userMock->propertiesGet->returns(['admin' => false]); - $exp = new EmptyResponse(403); + $exp = HTTP::respEmpty(403); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->dbMock->feedCleanup->never()->called(); } public function testCleanUpAfterUpdate(): void { $this->dbMock->articleCleanup->with()->returns(true); - $exp = new EmptyResponse(204); + $exp = HTTP::respEmpty(204); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->dbMock->articleCleanup->calledWith(); } public function testCleanUpAfterUpdateWithoutAuthority(): void { $this->userMock->propertiesGet->returns(['admin' => false]); - $exp = new EmptyResponse(403); + $exp = HTTP::respEmpty(403); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->dbMock->feedCleanup->never()->called(); } public function testQueryTheUserStatus(): void { $act = $this->req("GET", "/user"); - $exp = new Response([ + $exp = HTTP::respJson([ 'userId' => $this->userId, 'displayName' => $this->userId, - 'lastLoginTimestamp' => $this->approximateTime($act->getPayload()['lastLoginTimestamp'], new \DateTimeImmutable), + 'lastLoginTimestamp' => $this->approximateTime(json_decode((string) $act->getBody(), true)['lastLoginTimestamp'], new \DateTimeImmutable), 'avatar' => null, ]); $this->assertMessage($exp, $act); @@ -906,8 +905,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->folderAdd->with($this->anything(), $in)->returns(1); $this->dbMock->folderPropertiesGet->with($this->userId, 1)->returns($this->v($out1)); $this->dbMock->folderPropertiesGet->with($this->userId, 2)->returns($this->v($out2)); - $exp = new Response(['folders' => [$out1]]); - $this->assertMessage($exp, $this->req("POST", "/folders?name=Hardware", json_encode($in))); + $exp = HTTP::respJson(['folders' => [$out1]]); + $this->assertMessage($exp, $this->req("POST", $url, json_encode($in))); } public function testMeldJsonAndQueryParameters(): void { diff --git a/tests/cases/REST/NextcloudNews/TestVersions.php b/tests/cases/REST/NextcloudNews/TestVersions.php index b5d5679d..472b808f 100644 --- a/tests/cases/REST/NextcloudNews/TestVersions.php +++ b/tests/cases/REST/NextcloudNews/TestVersions.php @@ -6,10 +6,9 @@ declare(strict_types=1); namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\REST\NextcloudNews\Versions; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; /** @covers \JKingWeb\Arsse\REST\NextcloudNews\Versions */ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest { @@ -25,24 +24,24 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest { } public function testFetchVersionList(): void { - $exp = new Response(['apiLevels' => ['v1-2']]); + $exp = HTTP::respJson(['apiLevels' => ['v1-2']]); $this->assertMessage($exp, $this->req("GET", "/")); $this->assertMessage($exp, $this->req("GET", "/")); $this->assertMessage($exp, $this->req("GET", "/")); } public function testRespondToOptionsRequest(): void { - $exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]); + $exp = HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]); $this->assertMessage($exp, $this->req("OPTIONS", "/")); } public function testUseIncorrectMethod(): void { - $exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]); + $exp = HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]); $this->assertMessage($exp, $this->req("POST", "/")); } public function testUseIncorrectPath(): void { - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("GET", "/ook")); $this->assertMessage($exp, $this->req("OPTIONS", "/ook")); } diff --git a/tests/cases/REST/TestREST.php b/tests/cases/REST/TestREST.php index 347ab7fe..b9736f32 100644 --- a/tests/cases/REST/TestREST.php +++ b/tests/cases/REST/TestREST.php @@ -12,13 +12,12 @@ use JKingWeb\Arsse\REST; use JKingWeb\Arsse\REST\Exception501; use JKingWeb\Arsse\REST\NextcloudNews\V1_2 as NCN; use JKingWeb\Arsse\REST\TinyTinyRSS\API as TTRSS; +use JKingWeb\Arsse\Misc\HTTP; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Request; -use Laminas\Diactoros\Response; -use Laminas\Diactoros\ServerRequest; -use Laminas\Diactoros\Response\TextResponse; -use Laminas\Diactoros\Response\EmptyResponse; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\ServerRequest; /** @covers \JKingWeb\Arsse\REST */ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { @@ -69,7 +68,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { $this->userMock->auth->with("someone.else@example.com", "")->returns(true); Arsse::$user = $this->userMock->get(); // create an input server request - $req = new ServerRequest($serverParams); + $req = new ServerRequest("GET", "/", [], null, "1.1", $serverParams); // create the expected output $exp = $req; foreach ($expAttr as $key => $value) { @@ -95,7 +94,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { public function testSendAuthenticationChallenges(): void { self::setConf(); $r = new REST; - $in = new EmptyResponse(401); + $in = HTTP::respEmpty(401); $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"'); $act = $r->challenge($in, "OOK"); $this->assertMessage($exp, $act); @@ -155,7 +154,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { return $origin; }); $headers = isset($origin) ? ['Origin' => $origin] : []; - $req = new Request("", "GET", "php://memory", $headers); + $req = new Request("GET", "", $headers); $act = $rMock->get()->corsNegotiate($req, $allowed, $denied); $this->assertSame($exp, $act); } @@ -188,9 +187,9 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideCorsHeaders */ public function testAddCorsHeaders(string $reqMethod, array $reqHeaders, array $resHeaders, array $expHeaders): void { $r = new REST; - $req = new Request("", $reqMethod, "php://memory", $reqHeaders); - $res = new EmptyResponse(204, $resHeaders); - $exp = new EmptyResponse(204, $expHeaders); + $req = new Request($reqMethod, "php://memory", $reqHeaders); + $res = HTTP::respEmpty(204, $resHeaders); + $exp = HTTP::respEmpty(204, $expHeaders); $act = $r->corsApply($res, $req); $this->assertMessage($exp, $act); } @@ -242,7 +241,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", ]], @@ -267,21 +266,21 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { $stream = fopen("php://memory", "w+b"); fwrite($stream, "ook"); return [ - [new EmptyResponse(204), new EmptyResponse(204)], - [new EmptyResponse(401), new EmptyResponse(401, ['WWW-Authenticate' => "Fake Value"])], - [new EmptyResponse(204, ['Allow' => "PUT"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => "PUT,OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => ["PUT", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, DELETE, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => "HEAD,GET"]), new EmptyResponse(204, ['Allow' => "HEAD, GET, OPTIONS"])], - [new EmptyResponse(204, ['Allow' => "GET"]), new EmptyResponse(204, ['Allow' => "GET, HEAD, OPTIONS"])], - [new TextResponse("ook", 200), new TextResponse("ook", 200, ['Content-Length' => "3"])], - [new TextResponse("", 200), new TextResponse("", 200, ['Content-Length' => "0"])], - [new TextResponse("ook", 404), new TextResponse("ook", 404, ['Content-Length' => "3"])], - [new TextResponse("", 404), new TextResponse("", 404)], - [new Response($stream, 200), new Response($stream, 200, ['Content-Length' => "3"]), new Request("", "GET")], - [new Response($stream, 200), new EmptyResponse(200, ['Content-Length' => "3"]), new Request("", "HEAD")], + [HTTP::respEmpty(204), HTTP::respEmpty(204)], + [HTTP::respEmpty(401), HTTP::respEmpty(401, ['WWW-Authenticate' => "Fake Value"])], + [HTTP::respEmpty(204, ['Allow' => "PUT"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => "PUT,OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => ["PUT", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, DELETE, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]), HTTP::respEmpty(204, ['Allow' => "HEAD, GET, OPTIONS"])], + [HTTP::respEmpty(204, ['Allow' => "GET"]), HTTP::respEmpty(204, ['Allow' => "GET, HEAD, OPTIONS"])], + [HTTP::respText("ook", 200), HTTP::respText("ook", 200, ['Content-Length' => "3"])], + [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", "")], ]; } @@ -296,7 +295,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { }); if ($called) { $hMock = $this->mock($class); - $hMock->dispatch->returns(new EmptyResponse(204)); + $hMock->dispatch->returns(HTTP::respEmpty(204)); $this->objMock->get->with($class)->returns($hMock); Arsse::$obj = $this->objMock->get(); } @@ -317,13 +316,13 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { public function provideMockRequests(): iterable { return [ - [new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "GET"), "GET", true, NCN::class, "/feeds"], - [new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "HEAD"), "GET", true, NCN::class, "/feeds"], - [new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "get"), "GET", true, NCN::class, "/feeds"], - [new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "head"), "GET", true, NCN::class, "/feeds"], - [new ServerRequest([], [], "/tt-rss/api/", "POST"), "POST", true, TTRSS::class, "/"], - [new ServerRequest([], [], "/no/such/api/", "HEAD"), "GET", false], - [new ServerRequest([], [], "/no/such/api/", "GET"), "GET", false], + [new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"], + [new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"], + [new ServerRequest("get", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"], + [new ServerRequest("head", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"], + [new ServerRequest("POST", "/tt-rss/api/"), "POST", true, TTRSS::class, "/"], + [new ServerRequest("HEAD", "/no/such/api/"), "GET", false], + [new ServerRequest("GET", "/no/such/api/"), "GET", false], ]; } } diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index 9a10d6a2..99ca29d4 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -11,14 +11,13 @@ use JKingWeb\Arsse\User; use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Misc\Date; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\REST\TinyTinyRSS\API; use JKingWeb\Arsse\Feed\Exception as FeedException; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\JsonResponse as Response; -use Laminas\Diactoros\Response\EmptyResponse; /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API * @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */ @@ -166,17 +165,17 @@ LONG_STRING; return $this->req($data, "POST", "", null, $user); } - protected function respGood($content = null, $seq = 0): Response { - return new Response([ + protected function respGood($content = null, $seq = 0): ResponseInterface { + return HTTP::respJson([ 'seq' => $seq, 'status' => 0, 'content' => $content, ]); } - protected function respErr(string $msg, $content = [], $seq = 0): Response { + protected function respErr(string $msg, $content = [], $seq = 0): ResponseInterface { $err = ['error' => $msg]; - return new Response([ + return HTTP::respJson([ 'seq' => $seq, 'status' => 1, 'content' => array_merge($err, $content, $err), @@ -188,12 +187,12 @@ LONG_STRING; $this->assertMessage($exp, $this->req(null, "POST", "", "")); $this->assertMessage($exp, $this->req(null, "POST", "/", "")); $this->assertMessage($exp, $this->req(null, "POST", "/index.php", "")); - $exp = new EmptyResponse(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req(null, "POST", "/bad/path", "")); } public function testHandleOptionsRequest(): void { - $exp = new EmptyResponse(204, [ + $exp = HTTP::respEmpty(204, [ 'Allow' => "POST", 'Accept' => "application/json, text/json", ]); @@ -215,7 +214,7 @@ LONG_STRING; $this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true); $this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation"); $this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold"); - if ($sessions instanceof EmptyResponse) { + if ($sessions instanceof ResponseInterface) { $exp1 = $sessions; $exp2 = $sessions; } elseif ($sessions) { @@ -260,7 +259,7 @@ LONG_STRING; 'op' => "isLoggedIn", 'sid' => $data, ]; - if ($result instanceof EmptyResponse) { + if ($result instanceof ResponseInterface) { $exp1 = $result; $exp2 = null; } elseif ($result) { @@ -333,7 +332,7 @@ LONG_STRING; 'userHTTPAuthRequired' => true, 'userSessionEnforced' => false, ]; - $http401 = new EmptyResponse(401); + $http401 = HTTP::respEmpty(401); if ($type === "login") { return [ // conf, user, data, result @@ -532,7 +531,7 @@ LONG_STRING; 'user' => $this->userId, 'password' => "secret", ]; - $exp = new EmptyResponse(500); + $exp = HTTP::respEmpty(500); $this->assertMessage($exp, $this->req($data)); } @@ -1686,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]); @@ -1705,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 @@ -1742,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 @@ -1750,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 { @@ -1815,7 +1814,7 @@ LONG_STRING; ])); } - protected function outputHeadlines(int $id): Response { + protected function outputHeadlines(int $id): ResponseInterface { return $this->respGood([ [ 'id' => $id, diff --git a/tests/cases/REST/TinyTinyRSS/TestIcon.php b/tests/cases/REST/TinyTinyRSS/TestIcon.php index f9d4b277..ab41dc05 100644 --- a/tests/cases/REST/TinyTinyRSS/TestIcon.php +++ b/tests/cases/REST/TinyTinyRSS/TestIcon.php @@ -9,10 +9,10 @@ namespace JKingWeb\Arsse\TestCase\REST\TinyTinyRSS; use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\User; use JKingWeb\Arsse\Database; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\REST\TinyTinyRSS\Icon; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\Response\EmptyResponse as Response; /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon */ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest { @@ -51,21 +51,21 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->subscriptionIcon->with($this->anything(), 2112, false)->returns(['url' => "http://example.net/logo.png"]); $this->dbMock->subscriptionIcon->with($this->anything(), 1337, false)->returns(['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"]); // these requests should succeed - $exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]); + $exp = HTTP::respEmpty(301, ['Location' => "http://example.com/favicon.ico"]); $this->assertMessage($exp, $this->req("42.ico")); - $exp = new Response(301, ['Location' => "http://example.net/logo.png"]); + $exp = HTTP::respEmpty(301, ['Location' => "http://example.net/logo.png"]); $this->assertMessage($exp, $this->req("2112.ico")); - $exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); + $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]); $this->assertMessage($exp, $this->req("1337.ico")); // these requests should fail - $exp = new Response(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->req("ook.ico")); $this->assertMessage($exp, $this->req("ook")); $this->assertMessage($exp, $this->req("47.ico")); $this->assertMessage($exp, $this->req("2112.png")); $this->assertMessage($exp, $this->req("1123.ico")); // only GET is allowed - $exp = new Response(405, ['Allow' => "GET"]); + $exp = HTTP::respEmpty(405, ['Allow' => "GET"]); $this->assertMessage($exp, $this->req("2112.ico", "PUT")); } @@ -79,32 +79,32 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest { $this->dbMock->subscriptionIcon->with(null, 2112, false)->returns($url); $this->dbMock->subscriptionIcon->with(null, 1337, false)->returns($url); // these requests should succeed - $exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); + $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]); $this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico")); // these requests should fail - $exp = new Response(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->reqAuth("2112.ico")); - $exp = new Response(401); + $exp = HTTP::respEmpty(401); $this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); // with HTTP auth required, only authenticated requests should succeed self::setConf(['userHTTPAuthRequired' => true]); - $exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); + $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]); $this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico")); // anything else should fail - $exp = new Response(401); + $exp = HTTP::respEmpty(401); $this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); // resources for the wrtong user should still fail, too - $exp = new Response(404); + $exp = HTTP::respEmpty(404); $this->assertMessage($exp, $this->reqAuth("2112.ico")); } } diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php index 56e8a59c..05f5fb2e 100644 --- a/tests/lib/AbstractTest.php +++ b/tests/lib/AbstractTest.php @@ -17,15 +17,13 @@ use JKingWeb\Arsse\Db\Driver; use JKingWeb\Arsse\Db\Result; use JKingWeb\Arsse\Factory; use JKingWeb\Arsse\Misc\Date; -use JKingWeb\Arsse\Misc\ValueInfo; use JKingWeb\Arsse\Misc\URL; +use JKingWeb\Arsse\Misc\HTTP; 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\JsonResponse; -use Laminas\Diactoros\Response\XmlResponse; +use GuzzleHttp\Psr7\ServerRequest; /** @coversNothing */ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { @@ -258,7 +256,8 @@ 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); + $req = $req->withParsedBody($parsedBody)->withQueryParams($params); if (isset($user)) { if (strlen($user)) { $req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user); @@ -337,12 +336,13 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { $this->assertSame($exp->getMethod(), $act->getMethod(), $text); $this->assertSame($exp->getRequestTarget(), $act->getRequestTarget(), $text); } - if ($exp instanceof JsonResponse) { - $this->assertInstanceOf(JsonResponse::class, $act, $text); - $this->assertEquals($exp->getPayload(), $act->getPayload(), $text); - $this->assertSame($exp->getPayload(), $act->getPayload(), $text); - } elseif ($exp instanceof XmlResponse) { - $this->assertInstanceOf(XmlResponse::class, $act, $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); + $this->assertSame(\JSON_ERROR_NONE, json_last_error(), "Response body is not valid JSON"); + $this->assertEquals($expBody, $actBody, $text); + $this->assertSame($expBody, $actBody, $text); + } elseif ($exp instanceof ResponseInterface && HTTP::matchType($exp, "application/xml", "text/xml", "+xml")) { $this->assertXmlStringEqualsXmlString((string) $exp->getBody(), (string) $act->getBody(), $text); } else { $this->assertSame((string) $exp->getBody(), (string) $act->getBody(), $text); @@ -350,6 +350,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"); @@ -388,7 +398,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { } /** Inserts into the database test data in the following format: - * + * * ```php * $data = [ * 'some_table' => [ @@ -482,7 +492,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { // now search for the actual output row in the expected output $found = array_keys($exp, $row, true); foreach ($found as $k) { - if(!isset($act[$k])) { + if (!isset($act[$k])) { $act[$k] = $row; // skip to the next row continue 2; 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 diff --git a/vendor-bin/csfixer/composer.lock b/vendor-bin/csfixer/composer.lock index f3e938b5..7de608b9 100644 --- a/vendor-bin/csfixer/composer.lock +++ b/vendor-bin/csfixer/composer.lock @@ -227,16 +227,16 @@ }, { "name": "doctrine/annotations", - "version": "1.13.2", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08" + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", "shasum": "" }, "require": { @@ -248,9 +248,10 @@ "require-dev": { "doctrine/cache": "^1.11 || ^2.0", "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" }, "type": "library", "autoload": { @@ -293,9 +294,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.2" + "source": "https://github.com/doctrine/annotations/tree/1.13.3" }, - "time": "2021-08-05T19:00:23+00:00" + "time": "2022-07-02T10:48:51+00:00" }, { "name": "doctrine/lexer", @@ -375,16 +376,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.8.0", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7dcdea3f2f5f473464e835be9be55283ff8cfdc3", + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3", "shasum": "" }, "require": { @@ -394,7 +395,7 @@ "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", - "php-cs-fixer/diff": "^2.0", + "sebastian/diff": "^4.0", "symfony/console": "^5.4 || ^6.0", "symfony/event-dispatcher": "^5.4 || ^6.0", "symfony/filesystem": "^5.4 || ^6.0", @@ -452,7 +453,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.11.0" }, "funding": [ { @@ -460,59 +461,7 @@ "type": "github" } ], - "time": "2022-03-18T17:20:59+00:00" - }, - { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" - }, - "time": "2020-10-14T08:32:19+00:00" + "time": "2022-09-01T18:24:51+00:00" }, { "name": "psr/cache", @@ -717,17 +666,83 @@ "time": "2021-07-14T16:46:02+00:00" }, { - "name": "symfony/console", - "version": "v6.1.0", + "name": "sebastian/diff", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170", - "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "symfony/console", + "version": "v6.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d", + "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d", "shasum": "" }, "require": { @@ -794,7 +809,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.1.0" + "source": "https://github.com/symfony/console/tree/v6.1.4" }, "funding": [ { @@ -810,11 +825,11 @@ "type": "tidelift" } ], - "time": "2022-05-27T06:34:22+00:00" + "time": "2022-08-26T10:32:31+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -861,7 +876,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" }, "funding": [ { @@ -964,7 +979,7 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -1023,7 +1038,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" }, "funding": [ { @@ -1043,16 +1058,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d" + "reference": "3f39c04d2630c34019907b02f85672dac99f8659" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d", - "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659", + "reference": "3f39c04d2630c34019907b02f85672dac99f8659", "shasum": "" }, "require": { @@ -1086,7 +1101,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.1.0" + "source": "https://github.com/symfony/filesystem/tree/v6.1.4" }, "funding": [ { @@ -1102,20 +1117,20 @@ "type": "tidelift" } ], - "time": "2022-05-21T13:34:40+00:00" + "time": "2022-08-02T16:17:38+00:00" }, { "name": "symfony/finder", - "version": "v6.1.0", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f" + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f", - "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f", + "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", "shasum": "" }, "require": { @@ -1150,7 +1165,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.1.0" + "source": "https://github.com/symfony/finder/tree/v6.1.3" }, "funding": [ { @@ -1166,7 +1181,7 @@ "type": "tidelift" } ], - "time": "2022-04-15T08:08:08+00:00" + "time": "2022-07-29T07:42:06+00:00" }, { "name": "symfony/options-resolver", @@ -1237,16 +1252,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { @@ -1261,7 +1276,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1299,7 +1314,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -1315,20 +1330,20 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -1340,7 +1355,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1380,7 +1395,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -1396,20 +1411,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T21:10:46+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -1421,7 +1436,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1464,7 +1479,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -1480,20 +1495,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { @@ -1508,7 +1523,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1547,7 +1562,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -1563,20 +1578,20 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -1585,7 +1600,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1630,7 +1645,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" }, "funding": [ { @@ -1646,20 +1661,20 @@ "type": "tidelift" } ], - "time": "2022-03-04T08:16:47+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "shasum": "" }, "require": { @@ -1668,7 +1683,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1709,7 +1724,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, "funding": [ { @@ -1725,20 +1740,20 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:11+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/process", - "version": "v6.1.0", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "318718453c2be58266f1a9e74063d13cb8dd4165" + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165", - "reference": "318718453c2be58266f1a9e74063d13cb8dd4165", + "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292", + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292", "shasum": "" }, "require": { @@ -1770,7 +1785,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.1.0" + "source": "https://github.com/symfony/process/tree/v6.1.3" }, "funding": [ { @@ -1786,20 +1801,20 @@ "type": "tidelift" } ], - "time": "2022-05-11T12:12:29+00:00" + "time": "2022-06-27T17:24:16+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957" + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", "shasum": "" }, "require": { @@ -1855,7 +1870,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" }, "funding": [ { @@ -1871,7 +1886,7 @@ "type": "tidelift" } ], - "time": "2022-05-07T08:07:09+00:00" + "time": "2022-05-30T19:18:58+00:00" }, { "name": "symfony/stopwatch", @@ -1937,16 +1952,16 @@ }, { "name": "symfony/string", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", + "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c", + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c", "shasum": "" }, "require": { @@ -2002,7 +2017,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.0" + "source": "https://github.com/symfony/string/tree/v6.1.4" }, "funding": [ { @@ -2018,7 +2033,7 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:18:23+00:00" + "time": "2022-08-12T18:05:43+00:00" } ], "aliases": [], diff --git a/vendor-bin/daux/composer.lock b/vendor-bin/daux/composer.lock index 85825bfe..5f917835 100644 --- a/vendor-bin/daux/composer.lock +++ b/vendor-bin/daux/composer.lock @@ -83,22 +83,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.3", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab" + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", - "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "guzzlehttp/psr7": "^1.9 || ^2.4", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -107,10 +107,10 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.1", "ext-curl": "*", "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -120,8 +120,12 @@ }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -187,7 +191,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.3" + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" }, "funding": [ { @@ -203,20 +207,20 @@ "type": "tidelift" } ], - "time": "2022-05-25T13:24:33+00:00" + "time": "2022-08-28T15:39:27+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + "reference": "b94b2807d85443f9719887892882d0329d1e2598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", "shasum": "" }, "require": { @@ -271,7 +275,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.1" + "source": "https://github.com/guzzle/promises/tree/1.5.2" }, "funding": [ { @@ -287,20 +291,20 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:56:57+00:00" + "time": "2022-08-28T14:55:35+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.2.1", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", - "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", "shasum": "" }, "require": { @@ -314,17 +318,21 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.1", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.8 || ^9.3.10" + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.4-dev" } }, "autoload": { @@ -386,7 +394,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.2.1" + "source": "https://github.com/guzzle/psr7/tree/2.4.1" }, "funding": [ { @@ -402,7 +410,7 @@ "type": "tidelift" } ], - "time": "2022-03-20T21:55:58+00:00" + "time": "2022-08-28T14:45:39+00:00" }, { "name": "league/commonmark", @@ -898,16 +906,16 @@ }, { "name": "symfony/console", - "version": "v5.4.9", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", + "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", "shasum": "" }, "require": { @@ -977,7 +985,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.9" + "source": "https://github.com/symfony/console/tree/v5.4.12" }, "funding": [ { @@ -993,11 +1001,11 @@ "type": "tidelift" } ], - "time": "2022-05-18T06:17:34+00:00" + "time": "2022-08-17T13:18:05+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -1044,7 +1052,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" }, "funding": [ { @@ -1064,16 +1072,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.9", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" + "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4bfe9611b113b15d98a43da68ec9b5a00d56791", + "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791", "shasum": "" }, "require": { @@ -1085,8 +1093,11 @@ "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0" + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" @@ -1117,7 +1128,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.9" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.12" }, "funding": [ { @@ -1133,20 +1144,20 @@ "type": "tidelift" } ], - "time": "2022-05-17T15:07:29+00:00" + "time": "2022-08-19T07:33:17+00:00" }, { "name": "symfony/mime", - "version": "v5.4.9", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" + "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", + "url": "https://api.github.com/repos/symfony/mime/zipball/03876e9c5a36f5b45e7d9a381edda5421eff8a90", + "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90", "shasum": "" }, "require": { @@ -1200,7 +1211,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.9" + "source": "https://github.com/symfony/mime/tree/v5.4.12" }, "funding": [ { @@ -1216,20 +1227,20 @@ "type": "tidelift" } ], - "time": "2022-05-21T10:24:18+00:00" + "time": "2022-08-19T14:24:03+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { @@ -1244,7 +1255,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1282,7 +1293,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -1298,20 +1309,20 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -1323,7 +1334,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1363,7 +1374,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -1379,20 +1390,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T21:10:46+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "c023a439b8551e320cc3c8433b198e408a623af1" + "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/c023a439b8551e320cc3c8433b198e408a623af1", - "reference": "c023a439b8551e320cc3c8433b198e408a623af1", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e407643d610e5f2c8a4b14189150f68934bf5e48", + "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48", "shasum": "" }, "require": { @@ -1404,7 +1415,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1450,7 +1461,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.26.0" }, "funding": [ { @@ -1466,20 +1477,20 @@ "type": "tidelift" } ], - "time": "2021-10-26T17:16:04+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "shasum": "" }, "require": { @@ -1493,7 +1504,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1537,7 +1548,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" }, "funding": [ { @@ -1553,20 +1564,20 @@ "type": "tidelift" } ], - "time": "2021-09-14T14:02:44+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -1578,7 +1589,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1621,7 +1632,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -1637,20 +1648,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { @@ -1665,7 +1676,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1704,7 +1715,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -1720,20 +1731,20 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { @@ -1742,7 +1753,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1780,7 +1791,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -1796,20 +1807,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { @@ -1818,7 +1829,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1859,7 +1870,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" }, "funding": [ { @@ -1875,20 +1886,20 @@ "type": "tidelift" } ], - "time": "2021-06-05T21:20:04+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -1897,7 +1908,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1942,7 +1953,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" }, "funding": [ { @@ -1958,20 +1969,20 @@ "type": "tidelift" } ], - "time": "2022-03-04T08:16:47+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { "name": "symfony/process", - "version": "v5.4.8", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", - "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", "shasum": "" }, "require": { @@ -2004,7 +2015,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.8" + "source": "https://github.com/symfony/process/tree/v5.4.11" }, "funding": [ { @@ -2020,20 +2031,20 @@ "type": "tidelift" } ], - "time": "2022-04-08T05:07:18+00:00" + "time": "2022-06-27T16:58:25+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957" + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", "shasum": "" }, "require": { @@ -2089,7 +2100,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" }, "funding": [ { @@ -2105,20 +2116,20 @@ "type": "tidelift" } ], - "time": "2022-05-07T08:07:09+00:00" + "time": "2022-05-30T19:18:58+00:00" }, { "name": "symfony/string", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", + "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c", + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c", "shasum": "" }, "require": { @@ -2174,7 +2185,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.0" + "source": "https://github.com/symfony/string/tree/v6.1.4" }, "funding": [ { @@ -2190,20 +2201,20 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:18:23+00:00" + "time": "2022-08-12T18:05:43+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.3", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e80f87d2c9495966768310fc531b487ce64237a2" + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e80f87d2c9495966768310fc531b487ce64237a2", - "reference": "e80f87d2c9495966768310fc531b487ce64237a2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", "shasum": "" }, "require": { @@ -2249,7 +2260,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.3" + "source": "https://github.com/symfony/yaml/tree/v5.4.12" }, "funding": [ { @@ -2265,7 +2276,7 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:32:32+00:00" + "time": "2022-08-02T15:52:22+00:00" }, { "name": "webuni/front-matter", diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index 838d5501..0ff5f3ec 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -338,16 +338,16 @@ }, { "name": "mikey179/vfsstream", - "version": "v1.6.10", + "version": "v1.6.11", "source": { "type": "git", "url": "https://github.com/bovigo/vfsStream.git", - "reference": "250c0825537d501e327df879fb3d4cd751933b85" + "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/250c0825537d501e327df879fb3d4cd751933b85", - "reference": "250c0825537d501e327df879fb3d4cd751933b85", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", + "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", "shasum": "" }, "require": { @@ -385,7 +385,7 @@ "source": "https://github.com/bovigo/vfsStream/tree/master", "wiki": "https://github.com/bovigo/vfsStream/wiki" }, - "time": "2021-09-25T08:05:01+00:00" + "time": "2022-02-23T02:02:42+00:00" }, { "name": "myclabs/deep-copy", @@ -448,16 +448,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -498,9 +498,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2022-05-31T20:59:12+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { "name": "phar-io/manifest", @@ -613,252 +613,25 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" - }, - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -907,7 +680,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" }, "funding": [ { @@ -915,7 +688,7 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1160,16 +933,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.20", + "version": "9.5.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", "shasum": "" }, "require": { @@ -1184,7 +957,6 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", @@ -1199,13 +971,9 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "sebastian/type": "^3.1", "sebastian/version": "^3.0.2" }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" @@ -1247,7 +1015,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" }, "funding": [ { @@ -1259,7 +1027,7 @@ "type": "github" } ], - "time": "2022-04-01T12:37:26+00:00" + "time": "2022-08-30T07:42:16+00:00" }, { "name": "sebastian/cli-parser", @@ -1430,16 +1198,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -1492,7 +1260,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -1500,7 +1268,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -1690,16 +1458,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -1755,7 +1523,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -1763,7 +1531,7 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -2118,16 +1886,16 @@ }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -2139,7 +1907,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -2162,7 +1930,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -2170,7 +1938,7 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -2225,88 +1993,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.1", @@ -2357,64 +2043,6 @@ ], "time": "2021-07-28T10:34:58+00:00" }, - { - "name": "webmozart/assert", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" - }, - "time": "2021-03-09T10:59:23+00:00" - }, { "name": "webmozart/glob", "version": "4.6.0", diff --git a/vendor-bin/robo/composer.lock b/vendor-bin/robo/composer.lock index 97b62cd4..dfef7873 100644 --- a/vendor-bin/robo/composer.lock +++ b/vendor-bin/robo/composer.lock @@ -90,16 +90,16 @@ }, { "name": "consolidation/annotated-command", - "version": "4.5.5", + "version": "4.5.6", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "67cea8e8e7656b74da651ea6f49321853996c0fd" + "reference": "3968070538761628546270935f0733a0cc408e1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/67cea8e8e7656b74da651ea6f49321853996c0fd", - "reference": "67cea8e8e7656b74da651ea6f49321853996c0fd", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/3968070538761628546270935f0733a0cc408e1f", + "reference": "3968070538761628546270935f0733a0cc408e1f", "shasum": "" }, "require": { @@ -140,22 +140,22 @@ "description": "Initialize Symfony Console commands from annotated command class methods.", "support": { "issues": "https://github.com/consolidation/annotated-command/issues", - "source": "https://github.com/consolidation/annotated-command/tree/4.5.5" + "source": "https://github.com/consolidation/annotated-command/tree/4.5.6" }, - "time": "2022-04-26T16:18:25+00:00" + "time": "2022-06-22T20:17:12+00:00" }, { "name": "consolidation/config", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/config.git", - "reference": "0c15841b2bf60d9af1ce29884673e7d9d50c3b75" + "reference": "dae810c162f0e799ea3f35cc2f40b0797b6e4d26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/0c15841b2bf60d9af1ce29884673e7d9d50c3b75", - "reference": "0c15841b2bf60d9af1ce29884673e7d9d50c3b75", + "url": "https://api.github.com/repos/consolidation/config/zipball/dae810c162f0e799ea3f35cc2f40b0797b6e4d26", + "reference": "dae810c162f0e799ea3f35cc2f40b0797b6e4d26", "shasum": "" }, "require": { @@ -200,9 +200,9 @@ "description": "Provide configuration services for a commandline tool.", "support": { "issues": "https://github.com/consolidation/config/issues", - "source": "https://github.com/consolidation/config/tree/2.1.0" + "source": "https://github.com/consolidation/config/tree/2.1.1" }, - "time": "2022-02-24T00:32:42+00:00" + "time": "2022-06-22T19:59:34+00:00" }, { "name": "consolidation/log", @@ -1070,16 +1070,16 @@ }, { "name": "symfony/console", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170" + "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170", - "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170", + "url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d", + "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d", "shasum": "" }, "require": { @@ -1146,7 +1146,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.1.0" + "source": "https://github.com/symfony/console/tree/v6.1.4" }, "funding": [ { @@ -1162,11 +1162,11 @@ "type": "tidelift" } ], - "time": "2022-05-27T06:34:22+00:00" + "time": "2022-08-26T10:32:31+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -1213,7 +1213,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" }, "funding": [ { @@ -1316,7 +1316,7 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -1375,7 +1375,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" }, "funding": [ { @@ -1395,16 +1395,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d" + "reference": "3f39c04d2630c34019907b02f85672dac99f8659" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d", - "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659", + "reference": "3f39c04d2630c34019907b02f85672dac99f8659", "shasum": "" }, "require": { @@ -1438,7 +1438,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.1.0" + "source": "https://github.com/symfony/filesystem/tree/v6.1.4" }, "funding": [ { @@ -1454,20 +1454,20 @@ "type": "tidelift" } ], - "time": "2022-05-21T13:34:40+00:00" + "time": "2022-08-02T16:17:38+00:00" }, { "name": "symfony/finder", - "version": "v6.1.0", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f" + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f", - "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f", + "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", "shasum": "" }, "require": { @@ -1502,7 +1502,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.1.0" + "source": "https://github.com/symfony/finder/tree/v6.1.3" }, "funding": [ { @@ -1518,20 +1518,20 @@ "type": "tidelift" } ], - "time": "2022-04-15T08:08:08+00:00" + "time": "2022-07-29T07:42:06+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { @@ -1546,7 +1546,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1584,7 +1584,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -1600,20 +1600,20 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -1625,7 +1625,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1665,7 +1665,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -1681,20 +1681,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T21:10:46+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -1706,7 +1706,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1749,7 +1749,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -1765,20 +1765,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { @@ -1793,7 +1793,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1832,7 +1832,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -1848,20 +1848,20 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/process", - "version": "v6.1.0", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "318718453c2be58266f1a9e74063d13cb8dd4165" + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165", - "reference": "318718453c2be58266f1a9e74063d13cb8dd4165", + "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292", + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292", "shasum": "" }, "require": { @@ -1893,7 +1893,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.1.0" + "source": "https://github.com/symfony/process/tree/v6.1.3" }, "funding": [ { @@ -1909,20 +1909,20 @@ "type": "tidelift" } ], - "time": "2022-05-11T12:12:29+00:00" + "time": "2022-06-27T17:24:16+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957" + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", - "reference": "d66cd8ab656780f62c4215b903a420eb86358957", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", "shasum": "" }, "require": { @@ -1978,7 +1978,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" }, "funding": [ { @@ -1994,20 +1994,20 @@ "type": "tidelift" } ], - "time": "2022-05-07T08:07:09+00:00" + "time": "2022-05-30T19:18:58+00:00" }, { "name": "symfony/string", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", - "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", + "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c", + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c", "shasum": "" }, "require": { @@ -2063,7 +2063,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.0" + "source": "https://github.com/symfony/string/tree/v6.1.4" }, "funding": [ { @@ -2079,20 +2079,20 @@ "type": "tidelift" } ], - "time": "2022-04-22T08:18:23+00:00" + "time": "2022-08-12T18:05:43+00:00" }, { "name": "symfony/yaml", - "version": "v6.1.0", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2" + "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/84ce4f9d2d68f306f971a39d949d8f4b5550dba2", - "reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/86ee4d8fa594ed45e40d86eedfda1bcb66c8d919", + "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919", "shasum": "" }, "require": { @@ -2137,7 +2137,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.1.0" + "source": "https://github.com/symfony/yaml/tree/v6.1.4" }, "funding": [ { @@ -2153,7 +2153,7 @@ "type": "tidelift" } ], - "time": "2022-04-15T14:25:02+00:00" + "time": "2022-08-02T16:17:38+00:00" } ], "aliases": [],