mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
First tests for Miniflux
This commit is contained in:
parent
f6cd2b87ce
commit
06dee77bac
13 changed files with 328 additions and 33 deletions
|
@ -400,7 +400,7 @@ class Database {
|
||||||
* @param \DateTimeInterface|null $expires An optional expiry date and time for the token
|
* @param \DateTimeInterface|null $expires An optional expiry date and time for the token
|
||||||
* @param string $data Application-specific data associated with a token
|
* @param string $data Application-specific data associated with a token
|
||||||
*/
|
*/
|
||||||
public function tokenCreate(string $user, string $class, string $id = null, \DateTimeInterface $expires = null, string $data = null): string {
|
public function tokenCreate(string $user, string $class, string $id = null, ?\DateTimeInterface $expires = null, string $data = null): string {
|
||||||
if (!$this->userExists($user)) {
|
if (!$this->userExists($user)) {
|
||||||
throw new User\ExceptionConflict("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionConflict("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
|
@ -418,7 +418,7 @@ class Database {
|
||||||
* @param string $class The class of the token e.g. the protocol name
|
* @param string $class The class of the token e.g. the protocol name
|
||||||
* @param string|null $id The ID of a specific token, or null for all tokens in the class
|
* @param string|null $id The ID of a specific token, or null for all tokens in the class
|
||||||
*/
|
*/
|
||||||
public function tokenRevoke(string $user, string $class, string $id = null): bool {
|
public function tokenRevoke(string $user, string $class, ?string $id = null): bool {
|
||||||
if (is_null($id)) {
|
if (is_null($id)) {
|
||||||
$out = $this->db->prepare("DELETE FROM arsse_tokens where \"user\" = ? and class = ?", "str", "str")->run($user, $class)->changes();
|
$out = $this->db->prepare("DELETE FROM arsse_tokens where \"user\" = ? and class = ?", "str", "str")->run($user, $class)->changes();
|
||||||
} else {
|
} else {
|
||||||
|
@ -436,6 +436,11 @@ class Database {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** List tokens associated with a user */
|
||||||
|
public function tokenList(string $user, string $class): Db\Result {
|
||||||
|
return $this->db->prepare("SELECT id,created,expires,data from arsse_tokens where class = ? and user = ? and (expires is null or expires > CURRENT_TIMESTAMP)", "str", "str")->run($class, $user);
|
||||||
|
}
|
||||||
|
|
||||||
/** Deletes expires tokens from the database, returning the number of deleted tokens */
|
/** Deletes expires tokens from the database, returning the number of deleted tokens */
|
||||||
public function tokenCleanup(): int {
|
public function tokenCleanup(): int {
|
||||||
return $this->db->query("DELETE FROM arsse_tokens where expires < CURRENT_TIMESTAMP")->changes();
|
return $this->db->query("DELETE FROM arsse_tokens where expires < CURRENT_TIMESTAMP")->changes();
|
||||||
|
|
12
lib/REST.php
12
lib/REST.php
|
@ -42,9 +42,19 @@ class REST {
|
||||||
],
|
],
|
||||||
'miniflux' => [ // Miniflux https://miniflux.app/docs/api.html
|
'miniflux' => [ // Miniflux https://miniflux.app/docs/api.html
|
||||||
'match' => '/v1/',
|
'match' => '/v1/',
|
||||||
'strip' => '/v1',
|
'strip' => '',
|
||||||
'class' => REST\Miniflux\V1::class,
|
'class' => REST\Miniflux\V1::class,
|
||||||
],
|
],
|
||||||
|
'miniflux-version' => [ // Miniflux version report
|
||||||
|
'match' => '/version',
|
||||||
|
'strip' => '',
|
||||||
|
'class' => REST\Miniflux\Status::class,
|
||||||
|
],
|
||||||
|
'miniflux-healthcheck' => [ // Miniflux health check
|
||||||
|
'match' => '/healthcheck',
|
||||||
|
'strip' => '',
|
||||||
|
'class' => REST\Miniflux\Status::class,
|
||||||
|
],
|
||||||
// Other candidates:
|
// Other candidates:
|
||||||
// Microsub https://indieweb.org/Microsub
|
// Microsub https://indieweb.org/Microsub
|
||||||
// Google Reader http://feedhq.readthedocs.io/en/latest/api/index.html
|
// Google Reader http://feedhq.readthedocs.io/en/latest/api/index.html
|
||||||
|
|
19
lib/REST/Miniflux/ErrorResponse.php
Normal file
19
lib/REST/Miniflux/ErrorResponse.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\REST\Miniflux;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
|
class ErrorResponse extends \Laminas\Diactoros\Response\JsonResponse {
|
||||||
|
public function __construct($data, int $status = 400, array $headers = [], int $encodingOptions = self::DEFAULT_JSON_FLAGS) {
|
||||||
|
assert(isset(Arsse::$lang) && Arsse::$lang instanceof \JKingWeb\Arsse\Lang, new \Exception("Language database must be initialized before use"));
|
||||||
|
$data = (array) $data;
|
||||||
|
$msg = array_shift($data);
|
||||||
|
$data = ["error_message" => Arsse::$lang->msg("API.Miniflux.Error.".$msg, $data)];
|
||||||
|
parent::__construct($data, $status, $headers, $encodingOptions);
|
||||||
|
}
|
||||||
|
}
|
37
lib/REST/Miniflux/Status.php
Normal file
37
lib/REST/Miniflux/Status.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\REST\Miniflux;
|
||||||
|
|
||||||
|
use 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(ServerRequestInterface $req): ResponseInterface {
|
||||||
|
$target = parse_url($req->getRequestTarget())['path'] ?? "";
|
||||||
|
if (!in_array($target, ["/version", "/healthcheck"])) {
|
||||||
|
return new EmptyResponse(404);
|
||||||
|
}
|
||||||
|
$method = $req->getMethod();
|
||||||
|
if ($method === "OPTIONS") {
|
||||||
|
return new EmptyResponse(204, ['Allow' => "HEAD, GET"]);
|
||||||
|
} elseif ($method !== "GET") {
|
||||||
|
return new EmptyResponse(405, ['Allow' => "HEAD, GET"]);
|
||||||
|
}
|
||||||
|
$out = "";
|
||||||
|
if ($target === "/version") {
|
||||||
|
$out = V1::VERSION;
|
||||||
|
} elseif ($target === "/healthcheck") {
|
||||||
|
$out = "OK";
|
||||||
|
}
|
||||||
|
return new TextResponse($out);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ use JKingWeb\Arsse\Misc\HTTP;
|
||||||
use JKingWeb\Arsse\REST\Exception;
|
use JKingWeb\Arsse\REST\Exception;
|
||||||
use JKingWeb\Arsse\REST\Exception404;
|
use JKingWeb\Arsse\REST\Exception404;
|
||||||
use JKingWeb\Arsse\REST\Exception405;
|
use JKingWeb\Arsse\REST\Exception405;
|
||||||
|
use JKingWeb\Arsse\User\ExceptionConflict as UserException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
@ -20,6 +21,8 @@ use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
protected const ACCEPTED_TYPES_OPML = ["text/xml", "application/xml", "text/x-opml"];
|
protected const ACCEPTED_TYPES_OPML = ["text/xml", "application/xml", "text/x-opml"];
|
||||||
protected const ACCEPTED_TYPES_JSON = ["application/json", "text/json"];
|
protected const ACCEPTED_TYPES_JSON = ["application/json", "text/json"];
|
||||||
|
public const VERSION = "2.0.25";
|
||||||
|
|
||||||
protected $paths = [
|
protected $paths = [
|
||||||
'/categories' => ['GET' => "getCategories", 'POST' => "createCategory"],
|
'/categories' => ['GET' => "getCategories", 'POST' => "createCategory"],
|
||||||
'/categories/1' => ['PUT' => "updateCategory", 'DELETE' => "deleteCategory"],
|
'/categories/1' => ['PUT' => "updateCategory", 'DELETE' => "deleteCategory"],
|
||||||
|
@ -35,25 +38,41 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
'/feeds/1/icon' => ['GET' => "getFeedIcon"],
|
'/feeds/1/icon' => ['GET' => "getFeedIcon"],
|
||||||
'/feeds/1/refresh' => ['PUT' => "refreshFeed"],
|
'/feeds/1/refresh' => ['PUT' => "refreshFeed"],
|
||||||
'/feeds/refresh' => ['PUT' => "refreshAllFeeds"],
|
'/feeds/refresh' => ['PUT' => "refreshAllFeeds"],
|
||||||
'/healthcheck' => ['GET' => "healthCheck"],
|
|
||||||
'/import' => ['POST' => "opmlImport"],
|
'/import' => ['POST' => "opmlImport"],
|
||||||
'/me' => ['GET' => "getCurrentUser"],
|
'/me' => ['GET' => "getCurrentUser"],
|
||||||
'/users' => ['GET' => "getUsers", 'POST' => "createUser"],
|
'/users' => ['GET' => "getUsers", 'POST' => "createUser"],
|
||||||
'/users/1' => ['GET' => "getUser", 'PUT' => "updateUser", 'DELETE' => "deleteUser"],
|
'/users/1' => ['GET' => "getUser", 'PUT' => "updateUser", 'DELETE' => "deleteUser"],
|
||||||
'/users/*' => ['GET' => "getUser"],
|
'/users/*' => ['GET' => "getUser"],
|
||||||
'/version' => ['GET' => "getVersion"],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch(ServerRequestInterface $req): ResponseInterface {
|
protected function authenticate(ServerRequestInterface $req): bool {
|
||||||
// try to authenticate
|
// first check any tokens; this is what Miniflux does
|
||||||
|
foreach ($req->getHeader("X-Auth-Token") as $t) {
|
||||||
|
if (strlen($t)) {
|
||||||
|
// a non-empty header is authoritative, so we'll stop here one way or the other
|
||||||
|
try {
|
||||||
|
$d = Arsse::$db->tokenLookup("miniflux.login", $t);
|
||||||
|
} catch (ExceptionInput $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Arsse::$user->id = $d->user;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// next check HTTP auth
|
||||||
if ($req->getAttribute("authenticated", false)) {
|
if ($req->getAttribute("authenticated", false)) {
|
||||||
Arsse::$user->id = $req->getAttribute("authenticatedUser");
|
Arsse::$user->id = $req->getAttribute("authenticatedUser");
|
||||||
} else {
|
}
|
||||||
// TODO: Handle X-Auth-Token authentication
|
return false;
|
||||||
return new EmptyResponse(401);
|
}
|
||||||
|
|
||||||
|
public function dispatch(ServerRequestInterface $req): ResponseInterface {
|
||||||
|
// try to authenticate
|
||||||
|
if (!$this->authenticate($req)) {
|
||||||
|
return new ErrorResponse("401", 401);
|
||||||
}
|
}
|
||||||
// get the request path only; this is assumed to already be normalized
|
// get the request path only; this is assumed to already be normalized
|
||||||
$target = parse_url($req->getRequestTarget())['path'] ?? "";
|
$target = parse_url($req->getRequestTarget())['path'] ?? "";
|
||||||
|
@ -65,17 +84,14 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$func = $this->chooseCall($target, $method);
|
$func = $this->chooseCall($target, $method);
|
||||||
if ($func === "opmlImport") {
|
if ($func === "opmlImport") {
|
||||||
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_OPML])) {
|
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_OPML])) {
|
||||||
return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_OPML)]);
|
return new ErrorResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_OPML)]);
|
||||||
}
|
}
|
||||||
$data = (string) $req->getBody();
|
$data = (string) $req->getBody();
|
||||||
} elseif ($method === "POST" || $method === "PUT") {
|
} elseif ($method === "POST" || $method === "PUT") {
|
||||||
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_JSON])) {
|
|
||||||
return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_JSON)]);
|
|
||||||
}
|
|
||||||
$data = @json_decode($data, true);
|
$data = @json_decode($data, true);
|
||||||
if (json_last_error() !== \JSON_ERROR_NONE) {
|
if (json_last_error() !== \JSON_ERROR_NONE) {
|
||||||
// if the body could not be parsed as JSON, return "400 Bad Request"
|
// if the body could not be parsed as JSON, return "400 Bad Request"
|
||||||
return new EmptyResponse(400);
|
return new ErrorResponse(["invalidBodyJSON", json_last_error_msg()], 400);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$data = null;
|
$data = null;
|
||||||
|
@ -154,4 +170,20 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
throw new Exception404();
|
throw new Exception404();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function tokenGenerate(string $user, string $label): string {
|
||||||
|
$t = base64_encode(random_bytes(24));
|
||||||
|
return Arsse::$db->tokenCreate($user, "miniflux.login", $t, null, $label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tokenList(string $user): array {
|
||||||
|
if (!Arsse::$db->userExists($user)) {
|
||||||
|
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
|
}
|
||||||
|
$out = [];
|
||||||
|
foreach (Arsse::$db->tokenList($user, "miniflux.login") as $r) {
|
||||||
|
$out[] = ['label' => $r['data'], 'id' => $r['id']];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ class User {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function remove(string $user): bool {
|
public function remove(string $user): bool {
|
||||||
try {
|
try {
|
||||||
$out = $this->u->userRemove($user);
|
$out = $this->u->userRemove($user);
|
||||||
|
|
|
@ -7,6 +7,9 @@ return [
|
||||||
'CLI.Auth.Success' => 'Authentication successful',
|
'CLI.Auth.Success' => 'Authentication successful',
|
||||||
'CLI.Auth.Failure' => 'Authentication failed',
|
'CLI.Auth.Failure' => 'Authentication failed',
|
||||||
|
|
||||||
|
'API.Miniflux.Error.401' => 'Access Unauthorized',
|
||||||
|
'API.Miniflux.Error.invalidBodyJSON' => 'Invalid JSON payload: {0}',
|
||||||
|
|
||||||
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
||||||
'API.TTRSS.Category.Special' => 'Special',
|
'API.TTRSS.Category.Special' => 'Special',
|
||||||
'API.TTRSS.Category.Labels' => 'Labels',
|
'API.TTRSS.Category.Labels' => 'Labels',
|
||||||
|
|
|
@ -33,12 +33,16 @@ trait SeriesToken {
|
||||||
'class' => "str",
|
'class' => "str",
|
||||||
'user' => "str",
|
'user' => "str",
|
||||||
'expires' => "datetime",
|
'expires' => "datetime",
|
||||||
|
'data' => "str",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
|
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff, null],
|
||||||
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past], // expired
|
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past, null], // expired
|
||||||
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null],
|
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null, null],
|
||||||
["da772f8fa13c11e78667001e673b2560", "class.class", "john.doe@example.com", $future],
|
["da772f8fa13c11e78667001e673b2560", "class.class", "john.doe@example.com", $future, null],
|
||||||
|
["A", "miniflux.login", "jane.doe@example.com", null, "Label 1"],
|
||||||
|
["B", "miniflux.login", "jane.doe@example.com", null, "Label 2"],
|
||||||
|
["C", "miniflux.login", "john.doe@example.com", null, "Label 1"],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -127,4 +131,13 @@ trait SeriesToken {
|
||||||
// revoking tokens which do not exist is not an error
|
// revoking tokens which do not exist is not an error
|
||||||
$this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class"));
|
$this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testListTokens(): void {
|
||||||
|
$user = "jane.doe@example.com";
|
||||||
|
$exp = [
|
||||||
|
['id' => "A", 'data' => "Label 1"],
|
||||||
|
['id' => "B", 'data' => "Label 2"],
|
||||||
|
];
|
||||||
|
$this->assertResult($exp, Arsse::$db->tokenList($user, "miniflux.login"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
22
tests/cases/REST/Miniflux/TestErrorResponse.php
Normal file
22
tests/cases/REST/Miniflux/TestErrorResponse.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\REST\Miniflux\ErrorResponse */
|
||||||
|
class TestErrorResponse extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
public function testCreateConstantResponse(): void {
|
||||||
|
$act = new ErrorResponse("401", 401);
|
||||||
|
$this->assertSame('{"error_message":"Access Unauthorized"}', (string) $act->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateVariableResponse(): void {
|
||||||
|
$act = new ErrorResponse(["invalidBodyJSON", "Doh!"], 401);
|
||||||
|
$this->assertSame('{"error_message":"Invalid JSON payload: Doh!"}', (string) $act->getBody());
|
||||||
|
}
|
||||||
|
}
|
34
tests/cases/REST/Miniflux/TestStatus.php
Normal file
34
tests/cases/REST/Miniflux/TestStatus.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\REST\Miniflux\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 {
|
||||||
|
/** @dataProvider provideRequests */
|
||||||
|
public function testAnswerStatusRequests(string $url, string $method, ResponseInterface $exp): void {
|
||||||
|
$act = (new Status)->dispatch($this->serverRequest($method, $url, ""));
|
||||||
|
$this->assertMessage($exp, $act);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
118
tests/cases/REST/Miniflux/TestV1.php
Normal file
118
tests/cases/REST/Miniflux/TestV1.php
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\User;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
|
use JKingWeb\Arsse\Db\Transaction;
|
||||||
|
use JKingWeb\Arsse\REST\Miniflux\V1;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\REST\Miniflux\V1<extended> */
|
||||||
|
class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $h;
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
protected function req(string $method, string $target, $data = "", array $headers = [], bool $authenticated = true, bool $body = true): ResponseInterface {
|
||||||
|
$prefix = "/v1";
|
||||||
|
$url = $prefix.$target;
|
||||||
|
if ($body) {
|
||||||
|
$params = [];
|
||||||
|
} else {
|
||||||
|
$params = $data;
|
||||||
|
$data = [];
|
||||||
|
}
|
||||||
|
$req = $this->serverRequest($method, $url, $prefix, $headers, [], $data, "application/json", $params, $authenticated ? "john.doe@example.com" : "");
|
||||||
|
return $this->h->dispatch($req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
self::clearData();
|
||||||
|
self::setConf();
|
||||||
|
// create a mock user manager
|
||||||
|
Arsse::$user = \Phake::mock(User::class);
|
||||||
|
Arsse::$user->id = "john.doe@example.com";
|
||||||
|
// create a mock database interface
|
||||||
|
Arsse::$db = \Phake::mock(Database::class);
|
||||||
|
$this->transaction = \Phake::mock(Transaction::class);
|
||||||
|
\Phake::when(Arsse::$db)->begin->thenReturn($this->transaction);
|
||||||
|
//initialize a handler
|
||||||
|
$this->h = new V1();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown(): void {
|
||||||
|
self::clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function v($value) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSendAuthenticationChallenge(): void {
|
||||||
|
$exp = new EmptyResponse(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] : []);
|
||||||
|
$this->assertMessage($exp, $this->req($method, $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideInvalidPaths(): array {
|
||||||
|
return [
|
||||||
|
["/", "GET", 404],
|
||||||
|
["/", "POST", 404],
|
||||||
|
["/", "PUT", 404],
|
||||||
|
["/", "DELETE", 404],
|
||||||
|
["/", "OPTIONS", 404],
|
||||||
|
["/version/invalid", "GET", 404],
|
||||||
|
["/version/invalid", "POST", 404],
|
||||||
|
["/version/invalid", "PUT", 404],
|
||||||
|
["/version/invalid", "DELETE", 404],
|
||||||
|
["/version/invalid", "OPTIONS", 404],
|
||||||
|
["/folders/1/invalid", "GET", 404],
|
||||||
|
["/folders/1/invalid", "POST", 404],
|
||||||
|
["/folders/1/invalid", "PUT", 404],
|
||||||
|
["/folders/1/invalid", "DELETE", 404],
|
||||||
|
["/folders/1/invalid", "OPTIONS", 404],
|
||||||
|
["/version", "POST", 405, "GET"],
|
||||||
|
["/version", "PUT", 405, "GET"],
|
||||||
|
["/version", "DELETE", 405, "GET"],
|
||||||
|
["/folders", "PUT", 405, "GET, POST"],
|
||||||
|
["/folders", "DELETE", 405, "GET, POST"],
|
||||||
|
["/folders/1", "GET", 405, "PUT, DELETE"],
|
||||||
|
["/folders/1", "POST", 405, "PUT, DELETE"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRespondToInvalidInputTypes(): void {
|
||||||
|
$exp = new EmptyResponse(415, ['Accept' => "application/json"]);
|
||||||
|
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"]));
|
||||||
|
$exp = new EmptyResponse(400);
|
||||||
|
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>'));
|
||||||
|
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideOptionsRequests */
|
||||||
|
public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void {
|
||||||
|
$exp = new EmptyResponse(204, [
|
||||||
|
'Allow' => $allow,
|
||||||
|
'Accept' => $accept,
|
||||||
|
]);
|
||||||
|
$this->assertMessage($exp, $this->req("OPTIONS", $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideOptionsRequests(): array {
|
||||||
|
return [
|
||||||
|
["/feeds", "HEAD,GET,POST", "application/json"],
|
||||||
|
["/feeds/2112", "DELETE", "application/json"],
|
||||||
|
["/user", "HEAD,GET", "application/json"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ namespace JKingWeb\Arsse\TestCase\User;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
use JKingWeb\Arsse\AbstractException as Exception;
|
|
||||||
use JKingWeb\Arsse\User\ExceptionConflict;
|
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||||
use JKingWeb\Arsse\User\ExceptionInput;
|
use JKingWeb\Arsse\User\ExceptionInput;
|
||||||
use JKingWeb\Arsse\User\Driver;
|
use JKingWeb\Arsse\User\Driver;
|
||||||
|
|
|
@ -112,6 +112,10 @@
|
||||||
<testsuite name="REST">
|
<testsuite name="REST">
|
||||||
<file>cases/REST/TestREST.php</file>
|
<file>cases/REST/TestREST.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
<testsuite name="Miniflux">
|
||||||
|
<file>cases/REST/Miniflux/TestErrorResponse.php</file>
|
||||||
|
<file>cases/REST/Miniflux/TestStatus.php</file>
|
||||||
|
</testsuite>
|
||||||
<testsuite name="NCNv1">
|
<testsuite name="NCNv1">
|
||||||
<file>cases/REST/NextcloudNews/TestVersions.php</file>
|
<file>cases/REST/NextcloudNews/TestVersions.php</file>
|
||||||
<file>cases/REST/NextcloudNews/TestV1_2.php</file>
|
<file>cases/REST/NextcloudNews/TestV1_2.php</file>
|
||||||
|
|
Loading…
Reference in a new issue