Sfoglia il codice sorgente

Replace Response objects with PSR-7 response messages; improves #53

While the test suite passes, this commit yields a broken server: replacing ad hoc request objectss with PSR-7 ones is still required, as is emission of PSR-7 responses. Both will come in subsequent commits, with tests

Diactoros was chosen specifically because it includes facilities for emitting responses, something which is awkward to test. The end of this refactoring should see both the Response and Request classes disappear, and the general REST class fully covered (as well as any speculative additions to AbstractHanlder).
microsub
J. King 6 anni fa
parent
commit
9eadd602bd
  1. 3
      composer.json
  2. 104
      composer.lock
  3. 2
      lib/REST.php
  4. 2
      lib/REST/AbstractHandler.php
  5. 2
      lib/REST/Handler.php
  6. 194
      lib/REST/NextCloudNews/V1_2.php
  7. 13
      lib/REST/NextCloudNews/Versions.php
  8. 23
      lib/REST/TinyTinyRSS/API.php
  9. 8
      lib/REST/TinyTinyRSS/Icon.php
  10. 162
      tests/cases/REST/NextCloudNews/TestV1_2.php
  11. 25
      tests/cases/REST/NextCloudNews/TestVersions.php
  12. 82
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  13. 26
      tests/cases/REST/TinyTinyRSS/TestIcon.php
  14. 13
      tests/lib/AbstractTest.php
  15. 20
      vendor-bin/phpunit/composer.lock
  16. 76
      vendor-bin/robo/composer.lock

3
composer.json

@ -25,7 +25,8 @@
"fguillot/picofeed": ">=0.1.31",
"hosteurope/password-generator": "^1.0",
"docopt/docopt": "^1.0",
"jkingweb/druuid": "^3.0"
"jkingweb/druuid": "^3.0",
"zendframework/zend-diactoros": "^1.6"
},
"require-dev": {
"bamarni/composer-bin-plugin": "*"

104
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "8a3c7ff23f125a5fa3dac2e6a7244a90",
"content-hash": "7d381fa958169b7079c1d3c5b911f3bd",
"packages": [
{
"name": "docopt/docopt",
@ -190,6 +190,108 @@
],
"time": "2017-02-09T14:17:01+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.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 interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "zendframework/zend-diactoros",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-diactoros.git",
"reference": "c8664b92a6d5bc229e48b0923486c097e45a7877"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/c8664b92a6d5bc229e48b0923486c097e45a7877",
"reference": "c8664b92a6d5bc229e48b0923486c097e45a7877",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0",
"psr/http-message": "^1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-dom": "*",
"ext-libxml": "*",
"phpunit/phpunit": "^5.7.16 || ^6.0.8",
"zendframework/zend-coding-standard": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6-dev",
"dev-develop": "1.7-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Diactoros\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"description": "PSR HTTP Message implementations",
"homepage": "https://github.com/zendframework/zend-diactoros",
"keywords": [
"http",
"psr",
"psr-7"
],
"time": "2017-10-12T15:24:51+00:00"
},
{
"name": "zendframework/zendxml",
"version": "1.0.2",

2
lib/REST.php

@ -48,7 +48,7 @@ class REST {
public function __construct() {
}
public function dispatch(REST\Request $req = null): REST\Response {
public function dispatch(REST\Request $req = null): \Psr\Http\Message\ResponseInterface {
if ($req===null) {
$req = new REST\Request();
}

2
lib/REST/AbstractHandler.php

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

2
lib/REST/Handler.php

@ -8,5 +8,5 @@ namespace JKingWeb\Arsse\REST;
interface Handler {
public function __construct();
public function dispatch(Request $req): Response;
public function dispatch(Request $req): \Psr\Http\Message\ResponseInterface;
}

194
lib/REST/NextCloudNews/V1_2.php

@ -15,7 +15,9 @@ use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\REST\Response;
use \Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse;
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
const REALM = "NextCloud News API v1-2";
@ -72,10 +74,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() {
}
public function dispatch(\JKingWeb\Arsse\REST\Request $req): Response {
public function dispatch(\JKingWeb\Arsse\REST\Request $req): ResponseInterface {
// try to authenticate
if (!Arsse::$user->authHTTP()) {
return new Response(401, "", "", ['WWW-Authenticate: Basic realm="'.self::REALM.'"']);
return new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.self::REALM.'"']);
}
// handle HTTP OPTIONS requests
if ($req->method=="OPTIONS") {
@ -85,12 +87,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($req->body) {
// if the entity body is not JSON according to content type, return "415 Unsupported Media Type"
if (!preg_match("<^application/json\b|^$>", $req->type)) {
return new Response(415, "", "", ['Accept: application/json']);
return new EmptyResponse(415, ['Accept' => "application/json"]);
}
$data = @json_decode($req->body, true);
if (json_last_error() != \JSON_ERROR_NONE) {
// if the body could not be parsed as JSON, return "400 Bad Request"
return new Response(400);
return new EmptyResponse(400);
}
} else {
$data = [];
@ -101,12 +103,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
try {
$func = $this->chooseCall($req->paths, $req->method);
} catch (Exception404 $e) {
return new Response(404);
return new EmptyResponse(404);
} catch (Exception405 $e) {
return new Response(405, "", "", ["Allow: ".$e->getMessage()]);
return new EmptyResponse(405, ['Allow' => $e->getMessage()]);
}
if (!method_exists($this, $func)) {
return new Response(501); // @codeCoverageIgnore
return new EmptyResponse(501); // @codeCoverageIgnore
}
// dispatch
try {
@ -114,10 +116,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreStart
} catch (Exception $e) {
// if there was a REST exception return 400
return new Response(400);
return new EmptyResponse(400);
} catch (AbstractException $e) {
// if there was any other Arsse exception return 500
return new Response(500);
return new EmptyResponse(500);
}
// @codeCoverageIgnoreEnd
}
@ -242,7 +244,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
return $article;
}
protected function handleHTTPOptions(array $url): Response {
protected function handleHTTPOptions(array $url): ResponseInterface {
// normalize the URL path
$url = $this->normalizePath($url);
if (isset($this->paths[$url])) {
@ -252,81 +254,81 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (in_array("GET", $allowed)) {
array_unshift($allowed, "HEAD");
}
return new Response(204, "", "", [
"Allow: ".implode(",", $allowed),
"Accept: application/json",
return new EmptyResponse(204, [
'Allow' => implode(",", $allowed),
'Accept' => "application/json",
]);
} else {
// if the path is not supported, return 404
return new Response(404);
return new EmptyResponse(404);
}
}
// list folders
protected function folderList(array $url, array $data): Response {
protected function folderList(array $url, array $data): ResponseInterface {
$folders = [];
foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $folder) {
$folders[] = $this->folderTranslate($folder);
}
return new Response(200, ['folders' => $folders]);
return new Response(['folders' => $folders]);
}
// create a folder
protected function folderAdd(array $url, array $data): Response {
protected function folderAdd(array $url, array $data): ResponseInterface {
try {
$folder = Arsse::$db->folderAdd(Arsse::$user->id, ['name' => $data['name']]);
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
// folder already exists
case 10236: return new Response(409);
case 10236: return new EmptyResponse(409);
// folder name not acceptable
case 10231:
case 10232: return new Response(422);
case 10232: return new EmptyResponse(422);
// other errors related to input
default: return new Response(400); // @codeCoverageIgnore
default: return new EmptyResponse(400); // @codeCoverageIgnore
}
}
$folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder));
return new Response(200, ['folders' => [$folder]]);
return new Response(['folders' => [$folder]]);
}
// delete a folder
protected function folderRemove(array $url, array $data): Response {
protected function folderRemove(array $url, array $data): ResponseInterface {
// perform the deletion
try {
Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) {
// folder does not exist
return new Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// rename a folder (also supports moving nesting folders, but this is not a feature of the API)
protected function folderRename(array $url, array $data): Response {
protected function folderRename(array $url, array $data): ResponseInterface {
try {
Arsse::$db->folderPropertiesSet(Arsse::$user->id, (int) $url[1], ['name' => $data['name']]);
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
// folder does not exist
case 10239: return new Response(404);
case 10239: return new EmptyResponse(404);
// folder already exists
case 10236: return new Response(409);
case 10236: return new EmptyResponse(409);
// folder name not acceptable
case 10231:
case 10232: return new Response(422);
case 10232: return new EmptyResponse(422);
// other errors related to input
default: return new Response(400); // @codeCoverageIgnore
default: return new EmptyResponse(400); // @codeCoverageIgnore
}
}
return new Response(204);
return new EmptyResponse(204);
}
// mark all articles associated with a folder as read
protected function folderMarkRead(array $url, array $data): Response {
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 Response(422);
return new EmptyResponse(422);
}
// build the context
$c = new Context;
@ -337,16 +339,16 @@ 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 Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): Response {
protected function feedListStale(array $url, array $data): ResponseInterface {
// function requires admin rights per spec
if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
return new Response(403);
return new EmptyResponse(403);
}
// list stale feeds which should be checked for updates
$feeds = Arsse::$db->feedListStale();
@ -355,42 +357,42 @@ 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' => $feed, 'userId' => ""];
}
return new Response(200, ['feeds' => $out]);
return new Response(['feeds' => $out]);
}
// refresh a feed
protected function feedUpdate(array $url, array $data): Response {
protected function feedUpdate(array $url, array $data): ResponseInterface {
// function requires admin rights per spec
if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
return new Response(403);
return new EmptyResponse(403);
}
try {
Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
case 10239: // feed does not exist
return new Response(404);
return new EmptyResponse(404);
case 10237: // feed ID invalid
return new Response(422);
return new EmptyResponse(422);
default: // other errors related to input
return new Response(400); // @codeCoverageIgnore
return new EmptyResponse(400); // @codeCoverageIgnore
}
}
return new Response(204);
return new EmptyResponse(204);
}
// add a new feed
protected function subscriptionAdd(array $url, array $data): Response {
protected function subscriptionAdd(array $url, array $data): ResponseInterface {
// try to add the feed
$tr = Arsse::$db->begin();
try {
$id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']);
} catch (ExceptionInput $e) {
// feed already exists
return new Response(409);
return new EmptyResponse(409);
} catch (FeedException $e) {
// feed could not be retrieved
return new Response(422);
return new EmptyResponse(422);
}
// if a folder was specified, move the feed to the correct folder; silently ignore errors
if ($data['folderId']) {
@ -408,11 +410,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($newest) {
$out['newestItemId'] = $newest;
}
return new Response(200, $out);
return new Response($out);
}
// return list of feeds for the logged-in user
protected function subscriptionList(array $url, array $data): Response {
protected function subscriptionList(array $url, array $data): ResponseInterface {
$subs = Arsse::$db->subscriptionList(Arsse::$user->id);
$out = [];
foreach ($subs as $sub) {
@ -424,43 +426,43 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($newest) {
$out['newestItemId'] = $newest;
}
return new Response(200, $out);
return new Response($out);
}
// delete a feed
protected function subscriptionRemove(array $url, array $data): Response {
protected function subscriptionRemove(array $url, array $data): ResponseInterface {
try {
Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) {
// feed does not exist
return new Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// rename a feed
protected function subscriptionRename(array $url, array $data): Response {
protected function subscriptionRename(array $url, array $data): ResponseInterface {
try {
Arsse::$db->subscriptionPropertiesSet(Arsse::$user->id, (int) $url[1], ['title' => (string) $data['feedTitle']]);
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
// subscription does not exist
case 10239: return new Response(404);
case 10239: return new EmptyResponse(404);
// name is invalid
case 10231:
case 10232: return new Response(422);
case 10232: return new EmptyResponse(422);
// other errors related to input
default: return new Response(400); // @codeCoverageIgnore
default: return new EmptyResponse(400); // @codeCoverageIgnore
}
}
return new Response(204);
return new EmptyResponse(204);
}
// move a feed to a folder
protected function subscriptionMove(array $url, array $data): Response {
protected function subscriptionMove(array $url, array $data): ResponseInterface {
// if no folder is specified this is an error
if (!isset($data['folderId'])) {
return new Response(422);
return new EmptyResponse(422);
}
// perform the move
try {
@ -468,22 +470,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
case 10239: // subscription does not exist
return new Response(404);
return new EmptyResponse(404);
case 10235: // folder does not exist
case 10237: // folder ID is invalid
return new Response(422);
return new EmptyResponse(422);
default: // other errors related to input
return new Response(400); // @codeCoverageIgnore
return new EmptyResponse(400); // @codeCoverageIgnore
}
}
return new Response(204);
return new EmptyResponse(204);
}
// mark all articles associated with a subscription as read
protected function subscriptionMarkRead(array $url, array $data): Response {
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 Response(422);
return new EmptyResponse(422);
}
// build the context
$c = new Context;
@ -494,13 +496,13 @@ 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 Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// list articles and their properties
protected function articleList(array $url, array $data): Response {
protected function articleList(array $url, array $data): ResponseInterface {
// set the context options supplied by the client
$c = new Context;
// set the batch size
@ -553,32 +555,32 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$items = Arsse::$db->articleList(Arsse::$user->id, $c, Database::LIST_TYPICAL);
} catch (ExceptionInput $e) {
// ID of subscription or folder is not valid
return new Response(422);
return new EmptyResponse(422);
}
$out = [];
foreach ($items as $item) {
$out[] = $this->articleTranslate($item);
}
$out = ['items' => $out];
return new Response(200, $out);
return new Response($out);
}
// mark all articles as read
protected function articleMarkReadAll(array $url, array $data): Response {
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 Response(422);
return new EmptyResponse(422);
}
// build the context
$c = new Context;
$c->latestEdition((int) $data['newestItemId']);
// perform the operation
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
return new Response(204);
return new EmptyResponse(204);
}
// mark a single article as read
protected function articleMarkRead(array $url, array $data): Response {
protected function articleMarkRead(array $url, array $data): ResponseInterface {
// initialize the matching context
$c = new Context;
$c->edition((int) $url[1]);
@ -588,13 +590,13 @@ 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 Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// mark a single article as read
protected function articleMarkStarred(array $url, array $data): Response {
protected function articleMarkStarred(array $url, array $data): ResponseInterface {
// initialize the matching context
$c = new Context;
$c->article((int) $url[2]);
@ -604,13 +606,13 @@ 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 Response(404);
return new EmptyResponse(404);
}
return new Response(204);
return new EmptyResponse(204);
}
// mark an array of articles as read
protected function articleMarkReadMulti(array $url, array $data): Response {
protected function articleMarkReadMulti(array $url, array $data): ResponseInterface {
// determine whether to mark read or unread
$set = ($url[1]=="read");
// initialize the matching context
@ -620,11 +622,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c);
} catch (ExceptionInput $e) {
}
return new Response(204);
return new EmptyResponse(204);
}
// mark an array of articles as starred
protected function articleMarkStarredMulti(array $url, array $data): Response {
protected function articleMarkStarredMulti(array $url, array $data): ResponseInterface {
// determine whether to mark starred or unstarred
$set = ($url[1]=="star");
// initialize the matching context
@ -634,10 +636,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
} catch (ExceptionInput $e) {
}
return new Response(204);
return new EmptyResponse(204);
}
protected function userStatus(array $url, array $data): Response {
protected function userStatus(array $url, array $data): ResponseInterface {
$data = Arsse::$user->propertiesGet(Arsse::$user->id, true);
// construct the avatar structure, if an image is available
if (isset($data['avatar'])) {
@ -655,37 +657,37 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
'lastLoginTimestamp' => time(),
'avatar' => $avatar,
];
return new Response(200, $out);
return new Response($out);
}
protected function cleanupBefore(array $url, array $data): Response {
protected function cleanupBefore(array $url, array $data): ResponseInterface {
// function requires admin rights per spec
if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
return new Response(403);
return new EmptyResponse(403);
}
Service::cleanupPre();
return new Response(204);
return new EmptyResponse(204);
}
protected function cleanupAfter(array $url, array $data): Response {
protected function cleanupAfter(array $url, array $data): ResponseInterface {
// function requires admin rights per spec
if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
return new Response(403);
return new EmptyResponse(403);
}
Service::cleanupPost();
return new Response(204);
return new EmptyResponse(204);
}
// return the server version
protected function serverVersion(array $url, array $data): Response {
return new Response(200, [
protected function serverVersion(array $url, array $data): ResponseInterface {
return new Response([
'version' => self::VERSION,
'arsse_version' => Arsse::VERSION,
]);
}
protected function serverStatus(array $url, array $data): Response {
return new Response(200, [
protected function serverStatus(array $url, array $data): ResponseInterface {
return new Response([
'version' => self::VERSION,
'arsse_version' => Arsse::VERSION,
'warnings' => [

13
lib/REST/NextCloudNews/Versions.php

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

23
lib/REST/TinyTinyRSS/API.php

@ -19,7 +19,8 @@ use JKingWeb\Arsse\ExceptionType;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ResultEmpty;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\REST\Response;
use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
const LEVEL = 14; // emulated API level
@ -88,23 +89,23 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() {
}
public function dispatch(\JKingWeb\Arsse\REST\Request $req): Response {
public function dispatch(\JKingWeb\Arsse\REST\Request $req): \Psr\Http\Message\ResponseInterface {
if (!preg_match("<^(?:/(?:index\.php)?)?$>", $req->path)) {
// reject paths other than the index
return new Response(404);
return new EmptyResponse(404);
}
if ($req->method=="OPTIONS") {
// respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method
return new Response(204, "", "", [
"Allow: POST",
"Accept: application/json, text/json",
return new EmptyResponse(204, [
'Allow' => "POST",
'Accept' => "application/json, text/json",
]);
}
if ($req->body) {
// only JSON entities are allowed, but Content-Type is ignored, as is request method
$data = @json_decode($req->body, true);
if (json_last_error() != \JSON_ERROR_NONE || !is_array($data)) {
return new Response(200, self::FATAL_ERR);
return new Response(self::FATAL_ERR);
}
try {
// normalize input
@ -123,23 +124,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(200, [
return new Response([
'seq' => $data['seq'],
'status' => 0,
'content' => $this->$method($data),
]);
} catch (Exception $e) {
return new Response(200, [
return new Response([
'seq' => $data['seq'],
'status' => 1,
'content' => $e->getData(),
]);
} catch (AbstractException $e) {
return new Response(500);
return new EmptyResponse(500);
}
} else {
// absence of a request body indicates an error
return new Response(200, self::FATAL_ERR);
return new Response(self::FATAL_ERR);
}
}

8
lib/REST/TinyTinyRSS/Icon.php

@ -7,16 +7,16 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\REST\Response;
use Zend\Diactoros\Response\EmptyResponse as Response;
class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() {
}
public function dispatch(\JKingWeb\Arsse\REST\Request $req): Response {
public function dispatch(\JKingWeb\Arsse\REST\Request $req): \Psr\Http\Message\ResponseInterface {
if ($req->method != "GET") {
// only GET requests are allowed
return new Response(405, "", "", ["Allow: GET"]);
return new Response(405, ['Allow' => "GET"]);
} elseif (!preg_match("<^(\d+)\.ico$>", $req->url, $match) || !((int) $match[1])) {
return new Response(404);
}
@ -26,7 +26,7 @@ class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) {
$url = substr($url, 0, $pos);
}
return new Response(301, "", "", ["Location: $url"]);
return new Response(301, ['Location' => $url]);
} else {
return new Response(404);
}

162
tests/cases/REST/NextCloudNews/TestV1_2.php

@ -12,13 +12,14 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\REST\Request;
use JKingWeb\Arsse\REST\Response;
use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\Context;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\NextCloudNews\V1_2;
use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse;
use Phake;
/** @covers \JKingWeb\Arsse\REST\NextCloudNews\V1_2<extended> */
@ -317,14 +318,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->clearData();
}
protected function assertResponse(Response $exp, Response $act, string $text = null) {
$this->assertEquals($exp, $act, $text);
$this->assertSame($exp->payload, $act->payload, $text);
}
public function testSendAuthenticationChallenge() {
Phake::when(Arsse::$user)->authHTTP->thenReturn(false);
$exp = new Response(401, "", "", ['WWW-Authenticate: Basic realm="'.V1_2::REALM.'"']);
$exp = new EmptyResponse(401, ['WWW-Authenticate' => 'Basic realm="'.V1_2::REALM.'"']);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/")));
}
@ -361,12 +357,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
],
];
foreach ($errs[404] as $req) {
$exp = new Response(404);
$exp = new EmptyResponse(404);
list($method, $path) = $req;
$this->assertResponse($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404.");
}
foreach ($errs[405] as $allow => $cases) {
$exp = new Response(405, "", "", ['Allow: '.$allow]);
$exp = new EmptyResponse(405, ['Allow' => $allow]);
foreach ($cases as $req) {
list($method, $path) = $req;
$this->assertResponse($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405.");
@ -375,29 +371,29 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
}
public function testRespondToInvalidInputTypes() {
$exp = new Response(415, "", "", ['Accept: application/json']);
$exp = new EmptyResponse(415, ['Accept' => "application/json"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", '<data/>', 'application/xml')));
$exp = new Response(400);
$exp = new EmptyResponse(400);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", '<data/>', 'application/json')));
}
public function testRespondToOptionsRequests() {
$exp = new Response(204, "", "", [
"Allow: HEAD,GET,POST",
"Accept: application/json",
$exp = new EmptyResponse(204, [
'Allow' => "HEAD,GET,POST",
'Accept' => "application/json",
]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/feeds")));
$exp = new Response(204, "", "", [
"Allow: DELETE",
"Accept: application/json",
$exp = new EmptyResponse(204, [
'Allow' => "DELETE",
'Accept' => "application/json",
]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/feeds/2112")));
$exp = new Response(204, "", "", [
"Allow: HEAD,GET",
"Accept: application/json",
$exp = new EmptyResponse(204, [
'Allow' => "HEAD,GET",
'Accept' => "application/json",
]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/user")));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "/invalid/path")));
}
@ -411,9 +407,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 12, 'name' => "Hardware"],
];
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($list));
$exp = new Response(200, ['folders' => []]);
$exp = new Response(['folders' => []]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders")));
$exp = new Response(200, ['folders' => $out]);
$exp = new Response(['folders' => $out]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders")));
}
@ -441,33 +437,33 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => " "])->thenThrow(new ExceptionInput("whitespace"));
// correctly add two folders, using different means
$exp = new Response(200, ['folders' => [$out[0]]]);
$exp = new Response(['folders' => [$out[0]]]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json')));
$exp = new Response(200, ['folders' => [$out[1]]]);
$exp = new Response(['folders' => [$out[1]]]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders?name=Hardware")));
Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[0]);
Phake::verify(Arsse::$db)->folderAdd(Arsse::$user->id, $in[1]);
Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 1);
Phake::verify(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2);
// test bad folder names
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders")));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":""}', 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":" "}', 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":{}}', 'application/json')));
// try adding the same two folders again
$exp = new Response(409);
$exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software")));
$exp = new Response(409);
$exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json')));
}
public function testRemoveAFolder() {
Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
// fail on the second invocation because it no longer exists
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
Phake::verify(Arsse::$db, Phake::times(2))->folderRemove(Arsse::$user->id, 1);
}
@ -486,22 +482,22 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 1, $in[3])->thenThrow(new ExceptionInput("whitespace"));
Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database
Phake::when(Arsse::$db)->folderPropertiesSet(Arsse::$user->id, 3, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // folder ID 3 does not exist
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json')));
$exp = new Response(409);
$exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json')));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json')));
}
public function testRetrieveServerVersion() {
$exp = new Response(200, [
$exp = new Response([
'version' => V1_2::VERSION,
'arsse_version' => Arsse::VERSION,
]);
@ -521,9 +517,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([]))->thenReturn(new Result($this->feeds['db']));
Phake::when(Arsse::$db)->articleStarred(Arsse::$user->id)->thenReturn(['total' => 0])->thenReturn(['total' => 5]);
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id)->thenReturn(0)->thenReturn(4758915);
$exp = new Response(200, $exp1);
$exp = new Response($exp1);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds")));
$exp = new Response(200, $exp2);
$exp = new Response($exp2);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds")));
}
@ -556,31 +552,31 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
// set up a mock for a bad feed which succeeds the second time
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.net/news.atom")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.net/news.atom", new \PicoFeed\Client\InvalidUrlException()))->thenReturn(47);
// add the subscriptions
$exp = new Response(200, $out[0]);
$exp = new Response($out[0]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json')));
$exp = new Response(200, $out[1]);
$exp = new Response($out[1]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json')));
// try to add them a second time
$exp = new Response(409);
$exp = new EmptyResponse(409);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json')));
// try to add a bad feed
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[2]), 'application/json')));
// try again (this will succeed), with an invalid folder ID
$exp = new Response(200, $out[2]);
$exp = new Response($out[2]);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[3]), 'application/json')));
// try to add no feed
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[4]), 'application/json')));
}
public function testRemoveASubscription() {
Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, 1)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1")));
// fail on the second invocation because it no longer exists
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1")));
Phake::verify(Arsse::$db, Phake::times(2))->subscriptionRemove(Arsse::$user->id, 1);
}
@ -599,17 +595,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => 2112])->thenThrow(new ExceptionInput("idMissing")); // folder does not exist
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => -1])->thenThrow(new ExceptionInput("typeViolation")); // folder is invalid
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing")); // subscription does not exist
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[0]), 'application/json')));
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[2]), 'application/json')));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/move", json_encode($in[3]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[4]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[5]), 'application/json')));
}
@ -629,17 +625,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, $this->identicalTo(['title' => ""]))->thenThrow(new ExceptionInput("missing"));
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, $this->identicalTo(['title' => false]))->thenThrow(new ExceptionInput("missing"));
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[0]), 'application/json')));
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[2]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[3]), 'application/json')));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/rename", json_encode($in[4]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[6]), 'application/json')));
}
@ -655,11 +651,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
],
];
Phake::when(Arsse::$db)->feedListStale->thenReturn(array_column($out, "id"));
$exp = new Response(200, ['feeds' => $out]);
$exp = new Response(['feeds' => $out]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all")));
// retrieving the list when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new Response(403);
$exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all")));
}
@ -674,17 +670,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->feedUpdate(42)->thenReturn(true);
Phake::when(Arsse::$db)->feedUpdate(2112)->thenThrow(new ExceptionInput("subjectMissing"));
Phake::when(Arsse::$db)->feedUpdate($this->lessThan(1))->thenThrow(new ExceptionInput("typeViolation"));
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json')));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[2]), 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[3]), 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[4]), 'application/json')));
// updating a feed when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new Response(403);
$exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json')));
}
@ -710,12 +706,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(2112), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("idMissing"));
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation"));
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation"));
$exp = new Response(200, ['items' => $this->articles['rest']]);
$exp = new Response(['items' => $this->articles['rest']]);
// check the contents of the response
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items"))); // first instance of base context
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items/updated"))); // second instance of base context
// check error conditions
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[0]), 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[1]), 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[2]), 'application/json')));
@ -748,13 +744,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(1)->latestEdition(2112))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->folder(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // folder doesn't exist
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read", $in, 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read?newestItemId=2112")));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/1/read?newestItemId=ook")));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/folders/42/read", $in, 'application/json')));
}
@ -763,13 +759,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(1)->latestEdition(2112))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->subscription(42)->latestEdition(2112))->thenThrow(new ExceptionInput("idMissing")); // subscription doesn't exist
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read", $in, 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read?newestItemId=2112")));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/read?newestItemId=ook")));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/read", $in, 'application/json')));
}
@ -777,10 +773,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$read = ['read' => true];
$in = json_encode(['newestItemId' => 2112]);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $read, (new Context)->latestEdition(2112))->thenReturn(42);
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read", $in, 'application/json')));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read?newestItemId=2112")));
$exp = new Response(422);
$exp = new EmptyResponse(422);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read?newestItemId=ook")));
}
@ -798,12 +794,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $star, (new Context)->article(2112))->thenThrow(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(4))->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $unstar, (new Context)->article(1337))->thenThrow(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/read")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/2/unread")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/3/star")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/4400/4/unstar")));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/42/read")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/47/unread")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/1/2112/star")));
@ -829,7 +825,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), $this->anything())->thenReturn(42);
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->editions([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
Phake::when(Arsse::$db)->articleMark(Arsse::$user->id, $this->anything(), (new Context)->articles([]))->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/read/multiple")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/unread/multiple")));
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "/items/star/multiple")));
@ -882,29 +878,29 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
];
$arr2['warnings']['improperlyConfiguredCron'] = true;
$arr2['warnings']['incorrectDbCharset'] = true;
$exp = new Response(200, $arr1);
$exp = new Response($arr1);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/status")));
}
public function testCleanUpBeforeUpdate() {
Phake::when(Arsse::$db)->feedCleanup()->thenReturn(true);
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/before-update")));
Phake::verify(Arsse::$db)->feedCleanup();
// performing a cleanup when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new Response(403);
$exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/before-update")));
}
public function testCleanUpAfterUpdate() {
Phake::when(Arsse::$db)->articleCleanup()->thenReturn(true);
$exp = new Response(204);
$exp = new EmptyResponse(204);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/after-update")));
Phake::verify(Arsse::$db)->articleCleanup();
// performing a cleanup when not an admin fails
Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
$exp = new Response(403);
$exp = new EmptyResponse(403);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/cleanup/after-update")));
}
}

25
tests/cases/REST/NextCloudNews/TestVersions.php

@ -8,7 +8,8 @@ namespace JKingWeb\Arsse\TestCase\REST\NextCloudNews;
use JKingWeb\Arsse\REST\NextCloudNews\Versions;
use JKingWeb\Arsse\REST\Request;
use JKingWeb\Arsse\REST\Response;
use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\NextCloudNews\Versions */
class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
@ -17,43 +18,43 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
}
public function testFetchVersionList() {
$exp = new Response(200, ['apiLevels' => ['v1-2']]);
$exp = new Response(['apiLevels' => ['v1-2']]);
$h = new Versions;
$req = new Request("GET", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
$req = new Request("GET", "");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
$req = new Request("GET", "/?id=1827");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
}
public function testRespondToOptionsRequest() {
$exp = new Response(204, "", "", ["Allow: HEAD,GET"]);
$exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]);
$h = new Versions;
$req = new Request("OPTIONS", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
}
public function testUseIncorrectMethod() {
$exp = new Response(405, "", "", ["Allow: HEAD,GET"]);
$exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]);
$h = new Versions;
$req = new Request("POST", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
}
public function testUseIncorrectPath() {
$exp = new Response(404);
$exp = new EmptyResponse(404);
$h = new Versions;
$req = new Request("GET", "/ook");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
$req = new Request("OPTIONS", "/ook");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$this->assertResponse($exp, $res);
}
}

82
tests/cases/REST/TinyTinyRSS/TestAPI.php

@ -12,13 +12,15 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\REST\Request;
use JKingWeb\Arsse\REST\Response;
use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\Context;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse as Response;
use Zend\Diactoros\Response\EmptyResponse;
use Phake;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
@ -122,12 +124,12 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
</section>
LONG_STRING;
protected function req($data) : Response {
protected function req($data): ResponseInterface {
return $this->h->dispatch(new Request("POST", "", json_encode($data)));
}
protected function respGood($content = null, $seq = 0): Response {
return new Response(200, [
return new Response([
'seq' => $seq,
'status' => 0,
'content' => $content,
@ -136,18 +138,13 @@ LONG_STRING;
protected function respErr(string $msg, $content = [], $seq = 0): Response {
$err = ['error' => $msg];
return new Response(200, [
return new Response([
'seq' => $seq,
'status' => 1,
'content' => array_merge($err, $content, $err),
]);
}
protected function assertResponse(Response $exp, Response $act, string $text = null) {
$this->assertEquals($exp, $act, $text);
$this->assertSame($exp->payload, $act->payload, $text);
}
public function setUp() {
$this->clearData();
Arsse::$conf = new Conf();
@ -178,14 +175,14 @@ LONG_STRING;
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", "")));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/", "")));
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/index.php", "")));
$exp = new Response(404);
$exp = new EmptyResponse(404);
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "/bad/path", "")));
}
public function testHandleOptionsRequest() {
$exp = new Response(204, "", "", [
"Allow: POST",
"Accept: application/json, text/json",
$exp = new EmptyResponse(204, [
'Allow' => "POST",
'Accept' => "application/json, text/json",
]);
$this->assertResponse($exp, $this->h->dispatch(new Request("OPTIONS", "")));
}
@ -226,7 +223,7 @@ LONG_STRING;
'user' => Arsse::$user->id,
'password' => "secret",
];
$exp = new Response(500);
$exp = new EmptyResponse(500);
$this->assertResponse($exp, $this->req($data));
}
@ -1630,10 +1627,10 @@ LONG_STRING;
$this->assertResponse($this->outputHeadlines(1), $test);
// test 'show_content'
$test = $this->req($in[1]);
$this->assertArrayHasKey("content", $test->payload['content'][0]);
$this->assertArrayHasKey("content", $test->payload['content'][1]);
$this->assertArrayHasKey("content", $test->getPayload()['content'][0]);
$this->assertArrayHasKey("content", $test->getPayload()['content'][1]);
foreach ($this->generateHeadlines(1) as $key => $row) {
$this->assertSame($row['content'], $test->payload['content'][$key]['content']);
$this->assertSame($row['content'], $test->getPayload()['content'][$key]['content']);
}
// test 'include_attachments'
$test = $this->req($in[2]);
@ -1649,25 +1646,23 @@ LONG_STRING;
'post_id' => "2112",
],
];
$this->assertArrayHasKey("attachments", $test->payload['content'][0]);
$this->assertArrayHasKey("attachments", $test->payload['content'][1]);
$this->assertSame([], $test->payload['content'][0]['attachments']);
$this->assertSame($exp, $test->payload['content'][1]['attachments']);
$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']);
// test 'include_header'
$test = $this->req($in[3]);
$exp = $this->outputHeadlines(1);
$exp->payload['content'] = [
$exp = $this->respGood([
['id' => -4, 'is_cat' => false, 'first_id' => 1],
$exp->payload['content'],
];
$this->outputHeadlines(1)->getPayload()['content'],
]);
$this->assertResponse($exp, $test);
// test 'include_header' with a category
$test = $this->req($in[4]);
$exp = $this->outputHeadlines(1);
$exp->payload['content'] = [
$exp = $this->respGood([
['id' => -3, 'is_cat' => true, 'first_id' => 1],
$exp->payload['content'],
];
$this->outputHeadlines(1)->getPayload()['content'],
]);
$this->assertResponse($exp, $test);
// test 'include_header' with an empty result
$test = $this->req($in[5]);
@ -1686,37 +1681,34 @@ LONG_STRING;
$this->assertResponse($exp, $test);
// test 'include_header' with ascending order
$test = $this->req($in[7]);
$exp = $this->outputHeadlines(1);
$exp->payload['content'] = [
$exp = $this->respGood([
['id' => -4, 'is_cat' => false, 'first_id' => 0],
$exp->payload['content'],
];
$this->outputHeadlines(1)->getPayload()['content'],
]);
$this->assertResponse($exp, $test);
// test 'include_header' with skip
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->reverse(true)->limit(1)->subscription(42), Database::LIST_MINIMAL)->thenReturn($this->generateHeadlines(1867));
$test = $this->req($in[8]);
$exp = $this->outputHeadlines(1);
$exp->payload['content'] = [
$exp = $this->respGood([
['id' => 42, 'is_cat' => false, 'first_id' => 1867],
$exp->payload['content'],
];
$this->outputHeadlines(1)->getPayload()['content'],
]);
$this->assertResponse($exp, $test);
// test 'include_header' with skip and ascending order
$test = $this->req($in[9]);
$exp = $this->outputHeadlines(1);
$exp->payload['content'] = [
$exp = $this->respGood([
['id' => 42, 'is_cat' => false, 'first_id' => 0],
$exp->payload['content'],
];
$this->outputHeadlines(1)->getPayload()['content'],
]);
$this->assertResponse($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->payload['content'][0]);
$this->assertArrayHasKey("excerpt", $test->payload['content'][1]);
$this->assertSame($exp1, $test->payload['content'][0]['excerpt']);
$this->assertSame($exp2, $test->payload['content'][1]['excerpt']);
$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']);
}
protected function generateHeadlines(int $id): Result {

26
tests/cases/REST/TinyTinyRSS/TestIcon.php

@ -12,7 +12,7 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\REST\TinyTinyRSS\Icon;
use JKingWeb\Arsse\REST\Request;
use JKingWeb\Arsse\REST\Response;
use Zend\Diactoros\Response\EmptyResponse as Response;
use Phake;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon<extended> */
@ -38,20 +38,20 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->subscriptionFavicon(2112)->thenReturn("http://example.net/logo.png");
Phake::when(Arsse::$db)->subscriptionFavicon(1337)->thenReturn("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"]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "42.ico")));
$exp = new Response(301, "", "", ["Location: http://example.net/logo.png"]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "2112.ico")));
$exp = new Response(301, "", "", ["Location: http://example.org/icon.gif"]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "1337.ico")));
$exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "42.ico")));
$exp = new Response(301, ['Location' => "http://example.net/logo.png"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "2112.ico")));
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "1337.ico")));
// these requests should fail
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "ook.ico")));
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "ook")));
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "47.ico")));
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "2112.png")));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "ook.ico")));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "ook")));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "47.ico")));
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "2112.png")));
// only GET is allowed
$exp = new Response(405, "", "", ["Allow: GET"]);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "2112.ico")));
$exp = new Response(405, ['Allow' => "GET"]);
$this->assertResponse($exp, $this->h->dispatch(new Request("PUT", "2112.ico")));
}
}

13
tests/lib/AbstractTest.php

@ -9,6 +9,9 @@ namespace JKingWeb\Arsse\Test;
use JKingWeb\Arsse\Exception;
use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Misc\Date;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Response\EmptyResponse;
/** @coversNothing */
abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
@ -29,6 +32,16 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
}
}
protected function assertResponse(ResponseInterface $exp, ResponseInterface $act, string $text = null) {
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
$this->assertEquals($exp->getStatusCode(), $act->getStatusCode(), $text);
$this->assertInstanceOf(get_class($exp), $act);
if ($exp instanceof JsonResponse) {
$this->assertEquals($exp->getPayload(), $act->getPayload(), $text);
$this->assertSame($exp->getPayload(), $act->getPayload(), $text);
}
}
public function approximateTime($exp, $act) {
if (is_null($act)) {
return null;

20
vendor-bin/phpunit/composer.lock

@ -777,16 +777,16 @@
},
{
"name": "phpunit/phpunit",
"version": "6.5.4",
"version": "6.5.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "1b2f933d5775f9237369deaa2d2bfbf9d652be4c"
"reference": "83d27937a310f2984fd575686138597147bdc7df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1b2f933d5775f9237369deaa2d2bfbf9d652be4c",
"reference": "1b2f933d5775f9237369deaa2d2bfbf9d652be4c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df",
"reference": "83d27937a310f2984fd575686138597147bdc7df",
"shasum": ""
},
"require": {
@ -857,7 +857,7 @@
"testing",
"xunit"
],
"time": "2017-12-10T08:06:19+00:00"
"time": "2017-12-17T06:31:19+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -965,16 +965,16 @@
},
{
"name": "sebastian/comparator",
"version": "2.1.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "1174d9018191e93cb9d719edec01257fc05f8158"
"reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158",
"reference": "1174d9018191e93cb9d719edec01257fc05f8158",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
"reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
"shasum": ""
},
"require": {
@ -1025,7 +1025,7 @@
"compare",
"equality"
],
"time": "2017-11-03T07:16:52+00:00"
"time": "2017-12-22T14:50:35+00:00"
},
{
"name": "sebastian/diff",

76
vendor-bin/robo/composer.lock

@ -59,28 +59,33 @@
},
{
"name": "consolidation/config",
"version": "1.0.7",
"version": "1.0.9",
"source": {
"type": "git",
"url": "https://github.com/consolidation/config.git",
"reference": "b59a3b9ea750c21397f26a68fd2e04d9580af42e"
"reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/consolidation/config/zipball/b59a3b9ea750c21397f26a68fd2e04d9580af42e",
"reference": "b59a3b9ea750c21397f26a68fd2e04d9580af42e",
"url": "https://api.github.com/repos/consolidation/config/zipball/34ca8d7c1ee60a7b591b10617114cf1210a2e92c",
"reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c",
"shasum": ""
},
"require": {
"dflydev/dot-access-data": "^1.1.0",
"grasmash/yaml-expander": "^1.1",
"grasmash/expander": "^1",
"php": ">=5.4.0"
},
"require-dev": {
"greg-1-anderson/composer-test-scenarios": "^1",
"phpunit/phpunit": "^4",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*",
"symfony/console": "^2.5|^3"
"symfony/console": "^2.5|^3|^4",
"symfony/yaml": "^2.8.11|^3|^4"
},
"suggest": {
"symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader"
},
"type": "library",
"extra": {
@ -104,7 +109,7 @@
}
],
"description": "Provide configuration services for a commandline tool.",
"time": "2017-10-25T05:50:10+00:00"
"time": "2017-12-22T17:28:19+00:00"
},
{
"name": "consolidation/log",
@ -205,16 +210,16 @@
},
{
"name": "consolidation/robo",
"version": "1.2.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/consolidation/Robo.git",
"reference": "c46c13de3eca55e6b3635f363688ce85e845adf0"
"reference": "b6296f1cf1088f1a11b0b819f9e42ef6f00b79a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/consolidation/Robo/zipball/c46c13de3eca55e6b3635f363688ce85e845adf0",
"reference": "c46c13de3eca55e6b3635f363688ce85e845adf0",
"url": "https://api.github.com/repos/consolidation/Robo/zipball/b6296f1cf1088f1a11b0b819f9e42ef6f00b79a9",
"reference": "b6296f1cf1088f1a11b0b819f9e42ef6f00b79a9",
"shasum": ""
},
"require": {
@ -278,7 +283,7 @@
}
],
"description": "Modern task runner",
"time": "2017-12-13T02:10:49+00:00"
"time": "2017-12-29T06:48:35+00:00"
},
{
"name": "container-interop/container-interop",
@ -370,6 +375,53 @@
],
"time": "2017-01-20T21:14:22+00:00"
},
{
"name": "grasmash/expander",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/grasmash/expander.git",
"reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f",
"reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f",
"shasum": ""
},
"require": {
"dflydev/dot-access-data": "^1.1.0",
"php": ">=5.4"
},
"require-dev": {
"greg-1-anderson/composer-test-scenarios": "^1",
"phpunit/phpunit": "^4|^5.5.4",
"satooshi/php-coveralls": "^1.0.2|dev-master",
"squizlabs/php_codesniffer": "^2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Grasmash\\Expander\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matthew Grasmick"
}
],
"description": "Expands internal property references in PHP arrays file.",
"time": "2017-12-21T22:14:55+00:00"
},
{
"name": "grasmash/yaml-expander",
"version": "1.4.0",

Caricamento…
Annulla
Salva