Browse Source

Consolidate creation of synthetic server requests

rpm
J. King 5 years ago
parent
commit
c5337b37b4
  1. 30
      lib/Misc/URL.php
  2. 16
      tests/cases/Misc/TestURL.php
  3. 32
      tests/cases/REST/Fever/TestAPI.php
  4. 42
      tests/cases/REST/NextCloudNews/TestV1_2.php
  5. 10
      tests/cases/REST/NextCloudNews/TestVersions.php
  6. 25
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  7. 17
      tests/cases/REST/TinyTinyRSS/TestIcon.php
  8. 64
      tests/lib/AbstractTest.php

30
lib/Misc/URL.php

@ -10,6 +10,12 @@ namespace JKingWeb\Arsse\Misc;
* A collection of functions for manipulating URLs
*/
class URL {
/** Returns whether a URL is absolute i.e. has a scheme */
public static function absolute(string $url): bool {
return (bool) strlen((string) parse_url($url, \PHP_URL_SCHEME));
}
/** Normalizes a URL
*
* Normalizations performed are:
@ -137,4 +143,28 @@ class URL {
$out = ($absolute ? "/" : "").$out.($index ? "/" : "");
return str_replace("//", "/", $out);
}
/** Appends data to a URL's query component
*
* @param string $url The input URL
* @param string $data The data to append. This should already be escaped where necessary and not start with any delimiter
* @param string $glue The query subcomponent delimiter, usually "&". If the URL has no query, "?" will be prepended instead
*/
public static function queryAppend(string $url, string $data, string $glue = "&"): string {
if (!strlen($data)) {
return $url;
}
$insPos = strpos($url, "#");
$insPos = $insPos === false ? strlen($url) : $insPos;
$qPos = strpos($url, "?");
$hasQuery = $qPos !== false;
$glue = $hasQuery ? $glue : "?";
if ($hasQuery && $insPos > 0) {
if ($url[$insPos - 1] === $glue || ($insPos - 1) == $qPos) {
// if the URL already has excess glue, use it
$glue = "";
}
}
return substr($url, 0, $insPos).$glue.$data.substr($url, $insPos);
}
}

16
tests/cases/Misc/TestURL.php

@ -75,4 +75,20 @@ class TestURL extends \JKingWeb\Arsse\Test\AbstractTest {
[" ", "%20"],
];
}
/** @dataProvider provideQueries */
public function testAppendQueryParameters(string $url, string $query, string $exp) {
$this->assertSame($exp, URL::queryAppend($url, $query));
}
public function provideQueries() {
return [
["/", "ook=eek", "/?ook=eek"],
["/?", "ook=eek", "/?ook=eek"],
["/#ack", "ook=eek", "/?ook=eek#ack"],
["/?Huh?", "ook=eek", "/?Huh?&ook=eek"],
["/?Eh?&Huh?&", "ook=eek", "/?Eh?&Huh?&ook=eek"],
["/#ack", "", "/#ack"],
];
}
}

32
tests/cases/REST/Fever/TestAPI.php

@ -145,33 +145,11 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
return $value;
}
protected function req($dataGet, $dataPost = "", string $method = "POST", string $type = null, string $url = "", string $user = null): ServerRequest {
$url = "/fever/".$url;
protected function req($dataGet, $dataPost = "", string $method = "POST", string $type = null, string $target = "", string $user = null): ServerRequest {
$prefix = "/fever/";
$url = $prefix.$target;
$type = $type ?? "application/x-www-form-urlencoded";
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
'HTTP_CONTENT_TYPE' => $type,
];
$req = new ServerRequest($server, [], $url, $method, "php://memory", ['Content-Type' => $type]);
if (!is_array($dataGet)) {
parse_str($dataGet, $dataGet);
}
$req = $req->withRequestTarget($url)->withQueryParams($dataGet);
if (is_array($dataPost)) {
$req = $req->withParsedBody($dataPost);
} else {
parse_str($dataPost, $arr);
$req = $req->withParsedBody($arr);
}
if (isset($user)) {
if (strlen($user)) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
} else {
$req = $req->withAttribute("authenticationFailed", true);
}
}
return $req;
return $this->serverRequest($method, $url, $prefix, [], [], $dataPost, $type, $dataGet, $user);
}
public function setUp() {
@ -457,7 +435,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
return [
'Not an API request' => [$this->req(""), new EmptyResponse(404)],
'Wrong method' => [$this->req("api", "", "GET"), new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])],
'Wrong content type' => [$this->req("api", "", "POST", "application/json"), new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded"])],
'Wrong content type' => [$this->req("api", '{"api_key":"validToken"}', "POST", "application/json"), new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded"])],
];
}

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

@ -298,40 +298,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
],
];
protected function req(string $method, string $target, string $data = "", array $headers = []): ResponseInterface {
$url = "/index.php/apps/news/api/v1-2".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
'PHP_AUTH_USER' => "john.doe@example.com",
'PHP_AUTH_PW' => "secret",
'REMOTE_USER' => "john.doe@example.com",
];
if (strlen($data)) {
$server['HTTP_CONTENT_TYPE'] = "application/json";
}
$req = new ServerRequest($server, [], $url, $method, "php://memory");
if (Arsse::$user->auth("john.doe@example.com", "secret")) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", "john.doe@example.com");
}
foreach ($headers as $key => $value) {
if (!is_null($value)) {
$req = $req->withHeader($key, $value);
} else {
$req = $req->withoutHeader($key);
}
}
if (strlen($data)) {
$body = $req->getBody();
$body->write($data);
$req = $req->withBody($body);
}
$q = $req->getUri()->getQuery();
if (strlen($q)) {
parse_str($q, $q);
$req = $req->withQueryParams($q);
}
$req = $req->withRequestTarget($target);
protected function req(string $method, string $target, string $data = "", array $headers = [], bool $authenticated = true): ResponseInterface {
$prefix = "/index.php/apps/news/api/v1-2";
$url = $prefix.$target;
$req = $this->serverRequest($method, $url, $prefix, $headers, [], $data, "application/json", [], $authenticated ? "john.doe@example.com" : "");
return $this->h->dispatch($req);
}
@ -340,7 +310,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
self::setConf();
// create a mock user manager
Arsse::$user = \Phake::mock(User::class);
\Phake::when(Arsse::$user)->auth->thenReturn(true);
Arsse::$user->id = "john.doe@example.com";
// create a mock database interface
Arsse::$db = \Phake::mock(Database::class);
@ -357,9 +326,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
}
public function testSendAuthenticationChallenge() {
\Phake::when(Arsse::$user)->auth->thenReturn(false);
$exp = new EmptyResponse(401);
$this->assertMessage($exp, $this->req("GET", "/"));
$this->assertMessage($exp, $this->req("GET", "/", "", [], false));
}
public function testRespondToInvalidPaths() {

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

@ -19,13 +19,9 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
}
protected function req(string $method, string $target): ResponseInterface {
$url = "/index.php/apps/news/api".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$req = $req->withRequestTarget($target);
$prefix = "/index.php/apps/news/api";
$url = $prefix.$target;
$req = $this->serverRequest($method, $url, $prefix);
return (new Versions)->dispatch($req);
}

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

@ -126,27 +126,10 @@ LONG_STRING;
}
protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
$url = "/tt-rss/api".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
'HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded",
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$body = $req->getBody();
if (!is_null($strData)) {
$body->write($strData);
} else {
$body->write(json_encode($data));
}
$req = $req->withBody($body)->withRequestTarget($target);
if (isset($user)) {
if (strlen($user)) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
} else {
$req = $req->withAttribute("authenticationFailed", true);
}
}
$prefix = "/tt-rss/api";
$url = $prefix.$target;
$body = $strData ?? json_encode($data);
$req = $this->serverRequest($method, $url, $prefix, [], ['HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded"], $body, "application/json", [], $user);
return $this->h->dispatch($req);
}

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

@ -34,20 +34,9 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
}
protected function req(string $target, string $method = "GET", string $user = null): ResponseInterface {
$url = "/tt-rss/feed-icons/".$target;
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
];
$req = new ServerRequest($server, [], $url, $method, "php://memory");
$req = $req->withRequestTarget($target);
if (isset($user)) {
if (strlen($user)) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
} else {
$req = $req->withAttribute("authenticationFailed", true);
}
}
$prefix = "/tt-rss/feed-icons/";
$url = $prefix.$target;
$req = $this->serverRequest($method, $url, $prefix, [], [], null, "", [], $user);
return $this->h->dispatch($req);
}

64
tests/lib/AbstractTest.php

@ -13,10 +13,12 @@ use JKingWeb\Arsse\Db\Driver;
use JKingWeb\Arsse\Db\Result;
use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\Misc\URL;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Response\XmlResponse;
@ -61,6 +63,68 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
Arsse::$conf = (($force ? null : Arsse::$conf) ?? (new Conf))->import($defaults)->import($conf);
}
protected function serverRequest(string $method, string $url, string $urlPrefix, array $headers = [], array $vars = [], $body = null, string $type = "", $params = [], string $user = null): ServerRequestInterface {
$server = [
'REQUEST_METHOD' => $method,
'REQUEST_URI' => $url,
];
if (strlen($type)) {
$server['HTTP_CONTENT_TYPE'] = $type;
}
if (isset($params)) {
if (is_array($params)) {
$params = implode("&", array_map(function($v, $k) {
return rawurlencode($k).(isset($v) ? "=".rawurlencode($v) : "");
}, $params, array_keys($params)));
}
$url = URL::queryAppend($url, (string) $params);
}
$q = parse_url($url, \PHP_URL_QUERY);
if (strlen($q ?? "")) {
parse_str($q, $params);
} else {
$params = [];
}
$parsedBody = null;
if (isset($body)) {
if (is_string($body) && in_array(strtolower($type), ["", "application/x-www-form-urlencoded"])) {
parse_str($body, $parsedBody);
} elseif (!is_string($body) && in_array(strtolower($type), ["application/json", "text/json"])) {
$body = json_encode($body, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
} elseif (!is_string($body) && in_array(strtolower($type), ["", "application/x-www-form-urlencoded"])) {
$parsedBody = $body;
$body = http_build_query($body, "a", "&");
}
}
$server = array_merge($server, $vars);
$req = new ServerRequest($server, [], $url, $method, "php://memory", [], [], $params, $parsedBody);
if (isset($user)) {
if (strlen($user)) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
} else {
$req = $req->withAttribute("authenticationFailed", true);
}
}
if (strlen($type) &&strlen($body ?? "")) {
$req = $req->withHeader("Content-Type", $type);
}
foreach ($headers as $key => $value) {
if (!is_null($value)) {
$req = $req->withHeader($key, $value);
} else {
$req = $req->withoutHeader($key);
}
}
$target = substr(URL::normalize($url), strlen($urlPrefix));
$req = $req->withRequestTarget($target);
if (strlen($body ?? "")) {
$p = $req->getBody();
$p->write($body);
$req = $req->withBody($p);
}
return $req;
}
public function assertException($msg = "", string $prefix = "", string $type = "Exception") {
if (func_num_args()) {
if ($msg instanceof \JKingWeb\Arsse\AbstractException) {

Loading…
Cancel
Save