mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-23 17:12:41 +00:00
Merge branch 'master' into microsub
This commit is contained in:
commit
6d2b587e38
22 changed files with 215 additions and 133 deletions
|
@ -4,6 +4,8 @@ Version 0.8.1 (2019-??-??)
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
- Don't crash updating feeds cached solely via ETag
|
- Don't crash updating feeds cached solely via ETag
|
||||||
- Don't fail adding a feed which collides with another via redirection
|
- Don't fail adding a feed which collides with another via redirection
|
||||||
|
- Don't fail on very long text-search queries containing question marks
|
||||||
|
when using PostgreSQL or MySQL
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
- Include a user manual
|
- Include a user manual
|
||||||
|
|
|
@ -126,7 +126,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
|
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
|
||||||
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
|
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
|
||||||
$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
|
$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
|
||||||
return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
|
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Packages a given commit of the software into a release tarball
|
/** Packages a given commit of the software into a release tarball
|
||||||
|
|
|
@ -165,7 +165,7 @@ class Database {
|
||||||
// nulls are pointless to have
|
// nulls are pointless to have
|
||||||
continue;
|
continue;
|
||||||
} elseif (is_string($v)) {
|
} elseif (is_string($v)) {
|
||||||
if (strlen($v) > self::LIMIT_SET_STRING_LENGTH) {
|
if (strlen($v) > self::LIMIT_SET_STRING_LENGTH || strpos($v, "?") !== false) {
|
||||||
$clause[] = "?";
|
$clause[] = "?";
|
||||||
$params[] = $v;
|
$params[] = $v;
|
||||||
} else {
|
} else {
|
||||||
|
@ -198,16 +198,14 @@ class Database {
|
||||||
* @param boolean $matchAny Whether the search is successful when it matches any (true) or all (false) terms
|
* @param boolean $matchAny Whether the search is successful when it matches any (true) or all (false) terms
|
||||||
*/
|
*/
|
||||||
protected function generateSearch(array $terms, array $cols, bool $matchAny = false): array {
|
protected function generateSearch(array $terms, array $cols, bool $matchAny = false): array {
|
||||||
if (!$cols) {
|
|
||||||
throw new Exception("arrayEmpty", "cols"); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
$clause = [];
|
$clause = [];
|
||||||
$types = [];
|
$types = [];
|
||||||
$values = [];
|
$values = [];
|
||||||
$like = $this->db->sqlToken("like");
|
$like = $this->db->sqlToken("like");
|
||||||
|
assert(sizeof($cols) > 0, new Exception("arrayEmpty", "cols"));
|
||||||
$embedSet = sizeof($terms) > ((int) (self::LIMIT_SET_SIZE / sizeof($cols)));
|
$embedSet = sizeof($terms) > ((int) (self::LIMIT_SET_SIZE / sizeof($cols)));
|
||||||
foreach ($terms as $term) {
|
foreach ($terms as $term) {
|
||||||
$embedTerm = ($embedSet && strlen($term) <= self::LIMIT_SET_STRING_LENGTH);
|
$embedTerm = ($embedSet && strlen($term) <= self::LIMIT_SET_STRING_LENGTH && strpos($term, "?") === false);
|
||||||
$term = str_replace(["%", "_", "^"], ["^%", "^_", "^^"], $term);
|
$term = str_replace(["%", "_", "^"], ["^%", "^_", "^^"], $term);
|
||||||
$term = "%$term%";
|
$term = "%$term%";
|
||||||
$term = $embedTerm ? $this->db->literalString($term) : $term;
|
$term = $embedTerm ? $this->db->literalString($term) : $term;
|
||||||
|
@ -221,10 +219,11 @@ class Database {
|
||||||
$values[] = $term;
|
$values[] = $term;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$clause[] = "(".implode(" or ", $spec).")";
|
$spec = sizeof($spec) > 1 ? "(".implode(" or ", $spec).")" : (string) array_pop($spec);
|
||||||
|
$clause[] = $spec;
|
||||||
}
|
}
|
||||||
$glue = $matchAny ? "or" : "and";
|
$glue = $matchAny ? "or" : "and";
|
||||||
$clause = $clause ? "(".implode(" $glue ", $clause).")" : "";
|
$clause = sizeof($clause) > 1 ? "(".implode(" $glue ", $clause).")" : (string) array_pop($clause);
|
||||||
return [$clause, $types, $values];
|
return [$clause, $types, $values];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2082,9 +2081,7 @@ class Database {
|
||||||
* @param boolean $byName Whether to interpret the $id parameter as the label's name (true) or identifier (false)
|
* @param boolean $byName Whether to interpret the $id parameter as the label's name (true) or identifier (false)
|
||||||
*/
|
*/
|
||||||
public function labelArticlesSet(string $user, $id, Context $context, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
public function labelArticlesSet(string $user, $id, Context $context, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
||||||
if (!in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE])) {
|
assert(in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE]), new Exception("constantUnknown", $mode));
|
||||||
throw new Exception("constantUnknown", $mode); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
|
@ -2387,9 +2384,7 @@ class Database {
|
||||||
* @param boolean $byName Whether to interpret the $id parameter as the tag's name (true) or identifier (false)
|
* @param boolean $byName Whether to interpret the $id parameter as the tag's name (true) or identifier (false)
|
||||||
*/
|
*/
|
||||||
public function tagSubscriptionsSet(string $user, $id, array $subscriptions, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
public function tagSubscriptionsSet(string $user, $id, array $subscriptions, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
||||||
if (!in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE])) {
|
assert(in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE]), new Exception("constantUnknown", $mode));
|
||||||
throw new Exception("constantUnknown", $mode); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,9 +56,7 @@ abstract class AbstractStatement implements Statement {
|
||||||
$this->retypeArray($binding, true);
|
$this->retypeArray($binding, true);
|
||||||
} else {
|
} else {
|
||||||
$bindId = self::TYPES[trim(strtolower($binding))] ?? 0;
|
$bindId = self::TYPES[trim(strtolower($binding))] ?? 0;
|
||||||
if (!$bindId) {
|
assert($bindId, new Exception("paramTypeInvalid", $binding));
|
||||||
throw new Exception("paramTypeInvalid", $binding); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
$this->types[] = $bindId;
|
$this->types[] = $bindId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,13 +152,17 @@ class URL {
|
||||||
* @param string $data The data to append. This should already be escaped where necessary and not start with any delimiter
|
* @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
|
* @param string $glue The query subcomponent delimiter, usually "&". If the URL has no query, "?" will be prepended instead
|
||||||
*/
|
*/
|
||||||
public function queryAppend(string $url, string $data, string $glue = "&"): string {
|
public static function queryAppend(string $url, string $data, string $glue = "&"): string {
|
||||||
|
if (!strlen($data)) {
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
$insPos = strpos($url, "#");
|
$insPos = strpos($url, "#");
|
||||||
$insPos = $insPos === false ? strlen($url) : $insPos;
|
$insPos = $insPos === false ? strlen($url) : $insPos;
|
||||||
$hasQuery = strpos($url, "?") !== false;
|
$qPos = strpos($url, "?");
|
||||||
|
$hasQuery = $qPos !== false;
|
||||||
$glue = $hasQuery ? $glue : "?";
|
$glue = $hasQuery ? $glue : "?";
|
||||||
if ($hasQuery && $insPos > 0) {
|
if ($hasQuery && $insPos > 0) {
|
||||||
if ($url[$insPos - 1] === $glue) {
|
if ($url[$insPos - 1] === $glue || ($insPos - 1) == $qPos) {
|
||||||
// if the URL already has excess glue, use it
|
// if the URL already has excess glue, use it
|
||||||
$glue = "";
|
$glue = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,7 @@ const NS_BASE = __NAMESPACE__."\\";
|
||||||
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);
|
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);
|
||||||
const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR;
|
const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR;
|
||||||
ini_set("memory_limit", "-1");
|
ini_set("memory_limit", "-1");
|
||||||
|
ini_set("zend.assertions", "1");
|
||||||
|
ini_set("assert.exception", "true");
|
||||||
error_reporting(\E_ALL);
|
error_reporting(\E_ALL);
|
||||||
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
|
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
|
||||||
|
|
|
@ -10,7 +10,7 @@ use JKingWeb\Arsse\Test\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
|
|
||||||
abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest {
|
abstract class AbstractTest extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
use SeriesMiscellany;
|
use SeriesMiscellany;
|
||||||
use SeriesMeta;
|
use SeriesMeta;
|
||||||
use SeriesUser;
|
use SeriesUser;
|
86
tests/cases/Database/TestDatabase.php
Normal file
86
tests/cases/Database/TestDatabase.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?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\Database;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database */
|
||||||
|
class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $db = null;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
self::clearData();
|
||||||
|
self::setConf();
|
||||||
|
try {
|
||||||
|
$this->db = \Phake::makeVisible(\Phake::partialMock(Database::class));
|
||||||
|
} catch (\JKingWeb\Arsse\Db\Exception $e) {
|
||||||
|
$this->markTestSkipped("SQLite 3 database driver not available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
$this->db = null;
|
||||||
|
self::clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideInClauses */
|
||||||
|
public function testGenerateInClause(string $clause, array $values, array $inV, string $inT) {
|
||||||
|
$types = array_fill(0, sizeof($values), $inT);
|
||||||
|
$exp = [$clause, $types, $values];
|
||||||
|
$this->assertSame($exp, $this->db->generateIn($inV, $inT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideInClauses() {
|
||||||
|
$l = Database::LIMIT_SET_SIZE + 1;
|
||||||
|
$strings = array_fill(0, $l, "");
|
||||||
|
$ints = range(1, $l);
|
||||||
|
$longString = str_repeat("0", Database::LIMIT_SET_STRING_LENGTH + 1);
|
||||||
|
$params = implode(",", array_fill(0, $l, "?"));
|
||||||
|
$intList = implode(",", $ints);
|
||||||
|
$stringList = implode(",", array_fill(0, $l, "''"));
|
||||||
|
return [
|
||||||
|
["null", [], [], "str"],
|
||||||
|
["?", [1], [1], "int"],
|
||||||
|
["?", ["1"], ["1"], "int"],
|
||||||
|
["?,?", [null, null], [null, null], "str"],
|
||||||
|
["null", [], array_fill(0, $l, null), "str"],
|
||||||
|
["$intList", [], $ints, "int"],
|
||||||
|
["$intList,".($l+1), [], array_merge($ints, [$l+1]), "int"],
|
||||||
|
["$intList,0", [], array_merge($ints, ["OOK"]), "int"],
|
||||||
|
["$intList", [], array_merge($ints, [null]), "int"],
|
||||||
|
["$stringList,''", [], array_merge($strings, [""]), "str"],
|
||||||
|
["$stringList", [], array_merge($strings, [null]), "str"],
|
||||||
|
["$stringList,?", [$longString], array_merge($strings, [$longString]), "str"],
|
||||||
|
["$stringList,'A''s'", [], array_merge($strings, ["A's"]), "str"],
|
||||||
|
["$stringList,?", ["???"], array_merge($strings, ["???"]), "str"],
|
||||||
|
["$params", $ints, $ints, "bool"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideSearchClauses */
|
||||||
|
public function testGenerateSearchClause(string $clause, array $values, array $inV, array $inC, bool $inAny) {
|
||||||
|
// this is not an exhaustive test; integration tests already cover the ins and outs of the functionality
|
||||||
|
$types = array_fill(0, sizeof($values), "str");
|
||||||
|
$exp = [$clause, $types, $values];
|
||||||
|
$this->assertSame($exp, $this->db->generateSearch($inV, $inC, $inAny));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideSearchClauses() {
|
||||||
|
$terms = array_fill(0, Database::LIMIT_SET_SIZE + 1, "a");
|
||||||
|
$clause = array_fill(0, Database::LIMIT_SET_SIZE + 1, "test like '%a%' escape '^'");
|
||||||
|
$longString = str_repeat("0", Database::LIMIT_SET_STRING_LENGTH + 1);
|
||||||
|
return [
|
||||||
|
["test like ? escape '^'", ["%a%"], ["a"], ["test"], true],
|
||||||
|
["(col1 like ? escape '^' or col2 like ? escape '^')", ["%a%", "%a%"], ["a"], ["col1", "col2"], true],
|
||||||
|
["(".implode(" or ", $clause).")", [], $terms, ["test"], true],
|
||||||
|
["(".implode(" and ", $clause).")", [], $terms, ["test"], false],
|
||||||
|
["(".implode(" or ", $clause)." or test like ? escape '^')", ["%$longString%"], array_merge($terms, [$longString]), ["test"], true],
|
||||||
|
["(".implode(" or ", $clause)." or test like ? escape '^')", ["%Eh?%"], array_merge($terms, ["Eh?"]), ["test"], true],
|
||||||
|
["(".implode(" or ", $clause)." or test like ? escape '^')", ["%?%"], array_merge($terms, ["?"]), ["test"], true],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ namespace JKingWeb\Arsse\TestCase\Db\MySQL;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\MySQL;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\MySQL;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace JKingWeb\Arsse\TestCase\Db\MySQLPDO;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\MySQLPDO;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\MySQLPDO;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace JKingWeb\Arsse\TestCase\Db\PostgreSQL;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\PostgreSQL;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\PostgreSQL;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace JKingWeb\Arsse\TestCase\Db\PostgreSQLPDO;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\PostgreSQLPDO;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\PostgreSQLPDO;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace JKingWeb\Arsse\TestCase\Db\SQLite3;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\SQLite3;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\SQLite3;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
* @covers \JKingWeb\Arsse\Database<extended>
|
* @covers \JKingWeb\Arsse\Database<extended>
|
||||||
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
* @covers \JKingWeb\Arsse\Misc\Query<extended>
|
||||||
*/
|
*/
|
||||||
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
|
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\DatabaseDrivers\SQLite3PDO;
|
use \JKingWeb\Arsse\Test\DatabaseDrivers\SQLite3PDO;
|
||||||
|
|
||||||
protected function nextID(string $table): int {
|
protected function nextID(string $table): int {
|
||||||
|
|
|
@ -75,4 +75,20 @@ class TestURL extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
[" ", "%20"],
|
[" ", "%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"],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,33 +145,11 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function req($dataGet, $dataPost = "", string $method = "POST", string $type = null, string $url = "", string $user = null): ServerRequest {
|
protected function req($dataGet, $dataPost = "", string $method = "POST", string $type = null, string $target = "", string $user = null): ServerRequest {
|
||||||
$url = "/fever/".$url;
|
$prefix = "/fever/";
|
||||||
|
$url = $prefix.$target;
|
||||||
$type = $type ?? "application/x-www-form-urlencoded";
|
$type = $type ?? "application/x-www-form-urlencoded";
|
||||||
$server = [
|
return $this->serverRequest($method, $url, $prefix, [], [], $dataPost, $type, $dataGet, $user);
|
||||||
'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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
|
@ -457,7 +435,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
return [
|
return [
|
||||||
'Not an API request' => [$this->req(""), new EmptyResponse(404)],
|
'Not an API request' => [$this->req(""), new EmptyResponse(404)],
|
||||||
'Wrong method' => [$this->req("api", "", "GET"), new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])],
|
'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"])],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -298,40 +298,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function req(string $method, string $target, string $data = "", array $headers = []): ResponseInterface {
|
protected function req(string $method, string $target, string $data = "", array $headers = [], bool $authenticated = true): ResponseInterface {
|
||||||
$url = "/index.php/apps/news/api/v1-2".$target;
|
$prefix = "/index.php/apps/news/api/v1-2";
|
||||||
$server = [
|
$url = $prefix.$target;
|
||||||
'REQUEST_METHOD' => $method,
|
$req = $this->serverRequest($method, $url, $prefix, $headers, [], $data, "application/json", [], $authenticated ? "john.doe@example.com" : "");
|
||||||
'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);
|
|
||||||
return $this->h->dispatch($req);
|
return $this->h->dispatch($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +310,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::setConf();
|
self::setConf();
|
||||||
// create a mock user manager
|
// create a mock user manager
|
||||||
Arsse::$user = \Phake::mock(User::class);
|
Arsse::$user = \Phake::mock(User::class);
|
||||||
\Phake::when(Arsse::$user)->auth->thenReturn(true);
|
|
||||||
Arsse::$user->id = "john.doe@example.com";
|
Arsse::$user->id = "john.doe@example.com";
|
||||||
// create a mock database interface
|
// create a mock database interface
|
||||||
Arsse::$db = \Phake::mock(Database::class);
|
Arsse::$db = \Phake::mock(Database::class);
|
||||||
|
@ -357,9 +326,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSendAuthenticationChallenge() {
|
public function testSendAuthenticationChallenge() {
|
||||||
\Phake::when(Arsse::$user)->auth->thenReturn(false);
|
|
||||||
$exp = new EmptyResponse(401);
|
$exp = new EmptyResponse(401);
|
||||||
$this->assertMessage($exp, $this->req("GET", "/"));
|
$this->assertMessage($exp, $this->req("GET", "/", "", [], false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRespondToInvalidPaths() {
|
public function testRespondToInvalidPaths() {
|
||||||
|
|
|
@ -19,13 +19,9 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function req(string $method, string $target): ResponseInterface {
|
protected function req(string $method, string $target): ResponseInterface {
|
||||||
$url = "/index.php/apps/news/api".$target;
|
$prefix = "/index.php/apps/news/api";
|
||||||
$server = [
|
$url = $prefix.$target;
|
||||||
'REQUEST_METHOD' => $method,
|
$req = $this->serverRequest($method, $url, $prefix);
|
||||||
'REQUEST_URI' => $url,
|
|
||||||
];
|
|
||||||
$req = new ServerRequest($server, [], $url, $method, "php://memory");
|
|
||||||
$req = $req->withRequestTarget($target);
|
|
||||||
return (new Versions)->dispatch($req);
|
return (new Versions)->dispatch($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,27 +126,10 @@ LONG_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
|
protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
|
||||||
$url = "/tt-rss/api".$target;
|
$prefix = "/tt-rss/api";
|
||||||
$server = [
|
$url = $prefix.$target;
|
||||||
'REQUEST_METHOD' => $method,
|
$body = $strData ?? json_encode($data);
|
||||||
'REQUEST_URI' => $url,
|
$req = $this->serverRequest($method, $url, $prefix, [], ['HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded"], $body, "application/json", [], $user);
|
||||||
'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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->h->dispatch($req);
|
return $this->h->dispatch($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,20 +34,9 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function req(string $target, string $method = "GET", string $user = null): ResponseInterface {
|
protected function req(string $target, string $method = "GET", string $user = null): ResponseInterface {
|
||||||
$url = "/tt-rss/feed-icons/".$target;
|
$prefix = "/tt-rss/feed-icons/";
|
||||||
$server = [
|
$url = $prefix.$target;
|
||||||
'REQUEST_METHOD' => $method,
|
$req = $this->serverRequest($method, $url, $prefix, [], [], null, "", [], $user);
|
||||||
'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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->h->dispatch($req);
|
return $this->h->dispatch($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,12 @@ use JKingWeb\Arsse\Db\Driver;
|
||||||
use JKingWeb\Arsse\Db\Result;
|
use JKingWeb\Arsse\Db\Result;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
|
use JKingWeb\Arsse\Misc\URL;
|
||||||
use Psr\Http\Message\MessageInterface;
|
use Psr\Http\Message\MessageInterface;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Zend\Diactoros\ServerRequest;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\Diactoros\Response\XmlResponse;
|
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);
|
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") {
|
public function assertException($msg = "", string $prefix = "", string $type = "Exception") {
|
||||||
if (func_num_args()) {
|
if (func_num_args()) {
|
||||||
if ($msg instanceof \JKingWeb\Arsse\AbstractException) {
|
if ($msg instanceof \JKingWeb\Arsse\AbstractException) {
|
||||||
|
|
|
@ -98,6 +98,7 @@
|
||||||
<file>cases/Db/MySQLPDO/TestUpdate.php</file>
|
<file>cases/Db/MySQLPDO/TestUpdate.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Database functions">
|
<testsuite name="Database functions">
|
||||||
|
<file>cases/Database/TestDatabase.php</file>
|
||||||
<file>cases/Db/SQLite3/TestDatabase.php</file>
|
<file>cases/Db/SQLite3/TestDatabase.php</file>
|
||||||
<file>cases/Db/SQLite3PDO/TestDatabase.php</file>
|
<file>cases/Db/SQLite3PDO/TestDatabase.php</file>
|
||||||
<file>cases/Db/PostgreSQL/TestDatabase.php</file>
|
<file>cases/Db/PostgreSQL/TestDatabase.php</file>
|
||||||
|
|
Loading…
Reference in a new issue