diff --git a/lib/Database.php b/lib/Database.php
index 9accd6ef..3a97177a 100644
--- a/lib/Database.php
+++ b/lib/Database.php
@@ -238,17 +238,13 @@ class Database {
return $this->db->prepare("INSERT INTO arsse_folders(owner,parent,name) values(?,?,?)", "str", "int", "str")->run($user, $parent, $name)->lastId();
}
- public function folderList(string $user, int $parent = null, bool $recursive = true): Db\Result {
+ public function folderList(string $user, $parent = null, bool $recursive = true): Db\Result {
// if the user isn't authorized to perform this action then throw an exception.
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
// check to make sure the parent exists, if one is specified
- if (!is_null($parent)) {
- if (!$this->db->prepare("SELECT count(*) from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $parent)->getValue()) {
- throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "parent", 'id' => $parent]);
- }
- }
+ $parent = $this->folderValidateId($user, $parent)['id'];
// if we're not returning a recursive list we can use a simpler query
if (!$recursive) {
return $this->db->prepare("SELECT id,name,parent from arsse_folders where owner is ? and parent is ?", "str", "int")->run($user, $parent);
@@ -260,10 +256,13 @@ class Database {
}
}
- public function folderRemove(string $user, int $id): bool {
+ public function folderRemove(string $user, $id): bool {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id, 'type' => "int > 0"]);
+ }
$changes = $this->db->prepare("DELETE FROM arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->changes();
if (!$changes) {
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
@@ -271,10 +270,13 @@ class Database {
return true;
}
- public function folderPropertiesGet(string $user, int $id): array {
+ public function folderPropertiesGet(string $user, $id): array {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id, 'type' => "int > 0"]);
+ }
$props = $this->db->prepare("SELECT id,name,parent from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->getRow();
if (!$props) {
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
@@ -282,7 +284,7 @@ class Database {
return $props;
}
- public function folderPropertiesSet(string $user, int $id, array $data): bool {
+ public function folderPropertiesSet(string $user, $id, array $data): bool {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
@@ -294,14 +296,18 @@ class Database {
// if a new name and parent are specified, validate both together
$this->folderValidateName($data['name']);
$in['name'] = $data['name'];
- $in['parent'] = $this->folderValidateMove($user, $id, $data['parent'], $data['name']);
+ $in['parent'] = $this->folderValidateMove($user, (int) $id, $data['parent'], $data['name']);
} elseif ($name) {
+ // if we're trying to rename the root folder, this simply fails
+ if (!$id) {
+ return false;
+ }
// if a new name is specified, validate it
$this->folderValidateName($data['name'], true, $in['parent']);
$in['name'] = $data['name'];
} elseif ($parent) {
// if a new parent is specified, validate it
- $in['parent'] = $this->folderValidateMove($user, $id, $data['parent']);
+ $in['parent'] = $this->folderValidateMove($user, (int) $id, $data['parent']);
} else {
// if neither was specified, do nothing
return false;
@@ -315,14 +321,13 @@ class Database {
}
protected function folderValidateId(string $user, $id = null, bool $subject = false): array {
- $idInfo = ValueInfo::int($id);
- if ($idInfo & (ValueInfo::NULL | ValueInfo::ZERO)) {
- // if a null or zero ID is specified this is a no-op
- return ['id' => null, 'name' => null, 'parent' => null];
+ // if the specified ID is not a non-negative integer (or null), this will always fail
+ if(!ValueInfo::id($id, true)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => $this->caller(), "field" => "folder", 'type' => "int >= 0"]);
}
- // if a negative integer or non-integer is specified this will always fail
- if (!($idInfo & ValueInfo::VALID) || (($idInfo & ValueInfo::NEG))) {
- throw new Db\ExceptionInput($subject ? "subjectMissing" : "idMissing", ["action" => $this->caller(), "field" => "folder", 'id' => $id]);
+ // if a null or zero ID is specified this is a no-op
+ if (!$id) {
+ return ['id' => null, 'name' => null, 'parent' => null];
}
// check whether the folder exists and is owned by the user
$f = $this->db->prepare("SELECT id,name,parent from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->getRow();
@@ -417,10 +422,12 @@ class Database {
return $this->db->prepare('INSERT INTO arsse_subscriptions(owner,feed) values(?,?)', 'str', 'int')->run($user, $feedID)->lastId();
}
- public function subscriptionList(string $user, int $folder = null, int $id = null): Db\Result {
+ public function subscriptionList(string $user, $folder = null, int $id = null): Db\Result {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
+ // validate inputs
+ $folder = $this->folderValidateId($user, $folder)['id'];
// create a complex query
$q = new Query(
"SELECT
@@ -439,13 +446,11 @@ class Database {
$q->setCTE("user(user)", "SELECT ?", "str", $user); // the subject user; this way we only have to pass it to prepare() once
// topmost folders belonging to the user
$q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders join user on owner is user where parent is null union select id,top from arsse_folders join topmost on parent=f_id");
- if (!is_null($id)) {
+ if ($id) {
// this condition facilitates the implementation of subscriptionPropertiesGet, which would otherwise have to duplicate the complex query; it takes precedence over a specified folder
// if an ID is specified, add a suitable WHERE condition and bindings
$q->setWhere("arsse_subscriptions.id is ?", "int", $id);
} elseif ($folder) {
- // if a folder is specified, make sure it exists
- $this->folderValidateId($user, $folder);
// if it does exist, add a common table expression to list it and its children so that we select from the entire subtree
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent is folder", "int", $folder);
// add a suitable WHERE condition
@@ -454,24 +459,30 @@ class Database {
return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
}
- public function subscriptionRemove(string $user, int $id): bool {
+ public function subscriptionRemove(string $user, $id): bool {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id, 'type' => "int > 0"]);
+ }
$changes = $this->db->prepare("DELETE from arsse_subscriptions where owner is ? and id is ?", "str", "int")->run($user, $id)->changes();
if (!$changes) {
- throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
+ throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
}
return true;
}
- public function subscriptionPropertiesGet(string $user, int $id): array {
+ public function subscriptionPropertiesGet(string $user, $id): array {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id, 'type' => "int > 0"]);
+ }
// disable authorization checks for the list call
Arsse::$user->authorizationEnabled(false);
- $sub = $this->subscriptionList($user, null, $id)->getRow();
+ $sub = $this->subscriptionList($user, null, (int) $id)->getRow();
Arsse::$user->authorizationEnabled(true);
if (!$sub) {
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
@@ -479,15 +490,13 @@ class Database {
return $sub;
}
- public function subscriptionPropertiesSet(string $user, int $id, array $data): bool {
+ public function subscriptionPropertiesSet(string $user, $id, array $data): bool {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
$tr = $this->db->begin();
- if (!$this->db->prepare("SELECT count(*) from arsse_subscriptions where owner is ? and id is ?", "str", "int")->run($user, $id)->getValue()) {
- // if the ID doesn't exist or doesn't belong to the user, throw an exception
- throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
- }
+ // validate the ID
+ $id = $this->subscriptionValidateId($user, $id, true)['id'];
if (array_key_exists("folder", $data)) {
// ensure the target folder exists and belong to the user
$data['folder'] = $this->folderValidateId($user, $data['folder'])['id'];
@@ -517,10 +526,13 @@ class Database {
return $out;
}
- protected function subscriptionValidateId(string $user, int $id): array {
- $out = $this->db->prepare("SELECT feed from arsse_subscriptions where id is ? and owner is ?", "int", "str")->run($id, $user)->getRow();
+ protected function subscriptionValidateId(string $user, $id, bool $subject = false): array {
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => $this->caller(), "field" => "feed", 'id' => $id, 'type' => "int > 0"]);
+ }
+ $out = $this->db->prepare("SELECT id,feed from arsse_subscriptions where id is ? and owner is ?", "int", "str")->run($id, $user)->getRow();
if (!$out) {
- throw new Db\ExceptionInput("idMissing", ["action" => $this->caller(), "field" => "subscription", 'id' => $id]);
+ throw new Db\ExceptionInput($subject ? "subjectMissing" : "idMissing", ["action" => $this->caller(), "field" => "subscription", 'id' => $id]);
}
return $out;
}
@@ -530,9 +542,12 @@ class Database {
return array_column($feeds, 'id');
}
- public function feedUpdate(int $feedID, bool $throwError = false): bool {
+ public function feedUpdate($feedID, bool $throwError = false): bool {
$tr = $this->db->begin();
// check to make sure the feed exists
+ if (!ValueInfo::id($feedID)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "feed", 'id' => $feedID, 'type' => "int > 0"]);
+ }
$f = $this->db->prepare("SELECT url, username, password, modified, etag, err_count, scrape FROM arsse_feeds where id is ?", "int")->run($feedID)->getRow();
if (!$f) {
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $feedID]);
@@ -543,7 +558,7 @@ class Database {
// here. When an exception is thrown it should update the database with the
// error instead of failing; if other exceptions are thrown, we should simply roll back
try {
- $feed = new Feed($feedID, $f['url'], (string) Date::transform($f['modified'], "http", "sql"), $f['etag'], $f['username'], $f['password'], $scrape);
+ $feed = new Feed((int) $feedID, $f['url'], (string) Date::transform($f['modified'], "http", "sql"), $f['etag'], $f['username'], $f['password'], $scrape);
if (!$feed->modified) {
// if the feed hasn't changed, just compute the next fetch time and record it
$this->db->prepare("UPDATE arsse_feeds SET updated = CURRENT_TIMESTAMP, next_fetch = ? WHERE id is ?", 'datetime', 'int')->run($feed->nextFetch, $feedID);
@@ -951,7 +966,10 @@ class Database {
return true;
}
- protected function articleValidateId(string $user, int $id): array {
+ protected function articleValidateId(string $user, $id): array {
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => $this->caller(), "field" => "article", 'id' => $id, 'type' => "int > 0"]); // @codeCoverageIgnore
+ }
$out = $this->db->prepare(
"SELECT
arsse_articles.id as article,
@@ -970,6 +988,9 @@ class Database {
}
protected function articleValidateEdition(string $user, int $id): array {
+ if (!ValueInfo::id($id)) {
+ throw new Db\ExceptionInput("typeViolation", ["action" => $this->caller(), "field" => "edition", 'id' => $id, 'type' => "int > 0"]); // @codeCoverageIgnore
+ }
$out = $this->db->prepare(
"SELECT
arsse_editions.id as edition,
diff --git a/lib/Misc/Context.php b/lib/Misc/Context.php
index 566d9285..01e0528f 100644
--- a/lib/Misc/Context.php
+++ b/lib/Misc/Context.php
@@ -37,7 +37,7 @@ class Context {
protected function cleanArray(array $spec): array {
$spec = array_values($spec);
for ($a = 0; $a < sizeof($spec); $a++) {
- if(ValueInfo::int($spec[$a])===ValueInfo::VALID) {
+ if(ValueInfo::id($spec[$a])) {
$spec[$a] = (int) $spec[$a];
} else {
$spec[$a] = 0;
diff --git a/lib/Misc/ValueInfo.php b/lib/Misc/ValueInfo.php
index f6f85e9b..bc7e9d38 100644
--- a/lib/Misc/ValueInfo.php
+++ b/lib/Misc/ValueInfo.php
@@ -15,31 +15,31 @@ class ValueInfo {
static public function int($value): int {
$out = 0;
- // check if the input is null
if (is_null($value)) {
- $out += self::NULL;
- }
- // normalize the value to an integer or float if possible
- if (is_string($value)) {
- if (strval(@intval($value))===$value) {
- $value = (int) $value;
- } elseif (strval(@floatval($value))===$value) {
- $value = (float) $value;
- }
- // the empty string is equivalent to null when evaluating an integer
+ // check if the input is null
+ return self::NULL;
+ } elseif (is_string($value)) {
+ // normalize a string an integer or float if possible
if (!strlen((string) $value)) {
- $out += self::NULL;
+ // the empty string is equivalent to null when evaluating an integer
+ return self::NULL;
+ } elseif (filter_var($value, \FILTER_VALIDATE_FLOAT) !== false && !fmod((float) $value, 1)) {
+ // an integral float is acceptable
+ $value = (int) $value;
+ } else {
+ return $out;
}
- }
- // if the value is not an integer or integral float, stop
- if (!is_int($value) && (!is_float($value) || fmod($value, 1))) {
+ } elseif (is_float($value) && !fmod($value, 1)) {
+ // an integral float is acceptable
+ $value = (int) $value;
+ } elseif (!is_int($value)) {
+ // if the value is not an integer or integral float, stop
return $out;
}
// mark validity
- $value = (int) $value;
$out += self::VALID;
// mark zeroness
- if(!$value) {
+ if($value==0) {
$out += self::ZERO;
}
// mark negativeness
@@ -55,8 +55,8 @@ class ValueInfo {
if (is_null($value)) {
$out += self::NULL;
}
- // if the value is not scalar, it cannot be valid
- if (!is_scalar($value)) {
+ // if the value is not scalar, is a boolean, or is infinity or NaN, it cannot be valid
+ if (!is_scalar($value) || is_bool($value) || (is_float($value) && !is_finite($value))) {
return $out;
}
// mark validity
@@ -70,4 +70,19 @@ class ValueInfo {
}
return $out;
}
+
+ static public function id($value, bool $allowNull = false): bool {
+ $info = self::int($value);
+ if ($allowNull && ($info & self::NULL)) { // null (and allowed)
+ return true;
+ } elseif (!($info & self::VALID)) { // not an integer
+ return false;
+ } elseif ($info & self::NEG) { // negative integer
+ return false;
+ } elseif (!$allowNull && ($info & self::ZERO)) { // zero (and not allowed)
+ return false;
+ } else { // non-negative integer
+ return true;
+ }
+ }
}
\ No newline at end of file
diff --git a/lib/REST/AbstractHandler.php b/lib/REST/AbstractHandler.php
index 281c7c54..fbc42d41 100644
--- a/lib/REST/AbstractHandler.php
+++ b/lib/REST/AbstractHandler.php
@@ -32,10 +32,6 @@ abstract class AbstractHandler implements Handler {
return $data;
}
- protected function validateInt($id): bool {
- return (bool) (ValueInfo::int($id) & ValueInfo::VALID);
- }
-
protected function NormalizeInput(array $data, array $types, string $dateFormat = null): array {
$out = [];
foreach ($data as $key => $value) {
@@ -49,34 +45,29 @@ abstract class AbstractHandler implements Handler {
}
switch ($types[$key]) {
case "int":
- if ($this->validateInt($value)) {
+ if (valueInfo::int($value) & ValueInfo::VALID) {
$out[$key] = (int) $value;
}
break;
case "string":
- $out[$key] = (string) $value;
+ if(is_bool($value)) {
+ $out[$key] = var_export($value, true);
+ } elseif (!is_scalar($value)) {
+ break;
+ } else {
+ $out[$key] = (string) $value;
+ }
break;
case "bool":
- if (is_bool($value)) {
- $out[$key] = $value;
- } elseif ($this->validateInt($value)) {
- $value = (int) $value;
- if ($value > -1 && $value < 2) {
- $out[$key] = $value;
- }
- } elseif (is_string($value)) {
- $value = trim(strtolower($value));
- if ($value=="false") {
- $out[$key] = false;
- }
- if ($value=="true") {
- $out[$key] = true;
- }
+ $test = filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE);
+ if (!is_null($test)) {
+ $out[$key] = $test;
}
break;
case "float":
- if (is_numeric($value)) {
- $out[$key] = (float) $value;
+ $test = filter_var($value, \FILTER_VALIDATE_FLOAT);
+ if ($test !== false) {
+ $out[$key] = $test;
}
break;
case "datetime":
diff --git a/lib/REST/NextCloudNews/V1_2.php b/lib/REST/NextCloudNews/V1_2.php
index f17a298d..caf3d404 100644
--- a/lib/REST/NextCloudNews/V1_2.php
+++ b/lib/REST/NextCloudNews/V1_2.php
@@ -6,6 +6,7 @@ use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\Misc\Context;
+use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Feed\Exception as FeedException;
@@ -94,15 +95,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
'items' => [],
'folders' => [
'' => ['GET' => "folderList", 'POST' => "folderAdd"],
- '0' => ['PUT' => "folderRename", 'DELETE' => "folderRemove"],
- '0/read' => ['PUT' => "folderMarkRead"],
+ '1' => ['PUT' => "folderRename", 'DELETE' => "folderRemove"],
+ '1/read' => ['PUT' => "folderMarkRead"],
],
'feeds' => [
'' => ['GET' => "subscriptionList", 'POST' => "subscriptionAdd"],
- '0' => ['DELETE' => "subscriptionRemove"],
- '0/move' => ['PUT' => "subscriptionMove"],
- '0/rename' => ['PUT' => "subscriptionRename"],
- '0/read' => ['PUT' => "subscriptionMarkRead"],
+ '1' => ['DELETE' => "subscriptionRemove"],
+ '1/move' => ['PUT' => "subscriptionMove"],
+ '1/rename' => ['PUT' => "subscriptionRename"],
+ '1/read' => ['PUT' => "subscriptionMarkRead"],
'all' => ['GET' => "feedListStale"],
'update' => ['GET' => "feedUpdate"],
],
@@ -110,12 +111,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
'' => ['GET' => "articleList"],
'updated' => ['GET' => "articleList"],
'read' => ['PUT' => "articleMarkReadAll"],
- '0/read' => ['PUT' => "articleMarkRead"],
- '0/unread' => ['PUT' => "articleMarkRead"],
+ '1/read' => ['PUT' => "articleMarkRead"],
+ '1/unread' => ['PUT' => "articleMarkRead"],
'read/multiple' => ['PUT' => "articleMarkReadMulti"],
'unread/multiple' => ['PUT' => "articleMarkReadMulti"],
- '0/0/star' => ['PUT' => "articleMarkStarred"],
- '0/0/unstar' => ['PUT' => "articleMarkStarred"],
+ '1/1/star' => ['PUT' => "articleMarkStarred"],
+ '1/1/unstar' => ['PUT' => "articleMarkStarred"],
'star/multiple' => ['PUT' => "articleMarkStarredMulti"],
'unstar/multiple' => ['PUT' => "articleMarkStarredMulti"],
],
@@ -135,10 +136,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
];
// the first path element is the overall scope of the request
$scope = $url[0];
- // any URL components which are only digits should be replaced with "0", for easier comparison (integer segments are IDs, and we don't care about the specific ID)
+ // any URL components which are database IDs (integers greater than zero) should be replaced with "1", for easier comparison (we don't care about the specific ID)
for ($a = 0; $a < sizeof($url); $a++) {
- if ($this->validateInt($url[$a])) {
- $url[$a] = "0";
+ if (ValueInfo::id($url[$a])) {
+ $url[$a] = "1";
}
}
// normalize the HTTP method to uppercase
@@ -336,7 +337,14 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
try {
Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) {
- return new Response(404);
+ switch ($e->getCode()) {
+ case 10239: // feed does not exist
+ return new Response(404);
+ case 10237: // feed ID invalid
+ return new Response(422);
+ default: // other errors related to input
+ return new Response(400); // @codeCoverageIgnore
+ }
}
return new Response(204);
}
@@ -347,8 +355,8 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (!isset($data['url'])) {
return new Response(422);
}
- // normalize the folder ID, if specified; zero should be transformed to null
- $folder = (isset($data['folderId']) && $data['folderId']) ? $data['folderId'] : null;
+ // normalize the folder ID, if specified
+ $folder = isset($data['folderId']) ? $data['folderId'] : null;
// try to add the feed
$tr = Arsse::$db->begin();
try {
@@ -446,12 +454,13 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->subscriptionPropertiesSet(Arsse::$user->id, (int) $url[1], $in);
} catch (ExceptionInput $e) {
switch ($e->getCode()) {
- // subscription does not exist
- case 10239: return new Response(404);
- // folder does not exist
- case 10235: return new Response(422);
- // other errors related to input
- default: return new Response(400); // @codeCoverageIgnore
+ case 10239: // subscription does not exist
+ return new Response(404);
+ case 10235: // folder does not exist
+ case 10237: // folder ID is invalid
+ return new Response(422);
+ default: // other errors related to input
+ return new Response(400); // @codeCoverageIgnore
}
}
return new Response(204);
diff --git a/tests/Misc/TestContext.php b/tests/Misc/TestContext.php
index f03ca41d..49ab9bdc 100644
--- a/tests/Misc/TestContext.php
+++ b/tests/Misc/TestContext.php
@@ -47,9 +47,9 @@ class TestContext extends Test\AbstractTest {
$this->assertInstanceOf(Context::class, $c->$method($v[$method]));
$this->assertTrue($c->$method());
if (in_array($method, $times)) {
- $this->assertTime($c->$method, $v[$method]);
+ $this->assertTime($c->$method, $v[$method], "Context method $method did not return the expected results");
} else {
- $this->assertSame($c->$method, $v[$method]);
+ $this->assertSame($c->$method, $v[$method], "Context method $method did not return the expected results");
}
}
}
diff --git a/tests/Misc/TestValueInfo.php b/tests/Misc/TestValueInfo.php
new file mode 100644
index 00000000..8a590552
--- /dev/null
+++ b/tests/Misc/TestValueInfo.php
@@ -0,0 +1,185 @@
+assertSame($exp, I::int($value), "Test returned ".decbin(I::int($value))." for value: ".var_export($value, true));
+ }
+ }
+ public function testGetStringInfo() {
+ $tests = [
+ [null, I::NULL],
+ ["", I::VALID | I::EMPTY],
+ [1, I::VALID],
+ [PHP_INT_MAX, I::VALID],
+ [1.0, I::VALID],
+ ["1.0", I::VALID],
+ ["001.0", I::VALID],
+ ["1.0e2", I::VALID],
+ ["1", I::VALID],
+ ["001", I::VALID],
+ ["1e2", I::VALID],
+ ["+1.0", I::VALID],
+ ["+001.0", I::VALID],
+ ["+1.0e2", I::VALID],
+ ["+1", I::VALID],
+ ["+001", I::VALID],
+ ["+1e2", I::VALID],
+ [0, I::VALID],
+ ["0", I::VALID],
+ ["000", I::VALID],
+ [0.0, I::VALID],
+ ["0.0", I::VALID],
+ ["000.000", I::VALID],
+ ["+0", I::VALID],
+ ["+000", I::VALID],
+ ["+0.0", I::VALID],
+ ["+000.000", I::VALID],
+ [-1, I::VALID],
+ [-1.0, I::VALID],
+ ["-1.0", I::VALID],
+ ["-001.0", I::VALID],
+ ["-1.0e2", I::VALID],
+ ["-1", I::VALID],
+ ["-001", I::VALID],
+ ["-1e2", I::VALID],
+ [-0, I::VALID],
+ ["-0", I::VALID],
+ ["-000", I::VALID],
+ [-0.0, I::VALID],
+ ["-0.0", I::VALID],
+ ["-000.000", I::VALID],
+ [false, 0],
+ [true, 0],
+ [INF, 0],
+ [-INF, 0],
+ [NAN, 0],
+ [[], 0],
+ ["some string", I::VALID],
+ [" ", I::VALID | I::WHITE],
+ [new \StdClass, 0],
+ ];
+ foreach ($tests as $test) {
+ list($value, $exp) = $test;
+ $this->assertSame($exp, I::str($value), "Test returned ".decbin(I::str($value))." for value: ".var_export($value, true));
+ }
+ }
+
+ public function testValidateDatabaseIdentifier() {
+ $tests = [
+ [null, false, true],
+ ["", false, true],
+ [1, true, true],
+ [PHP_INT_MAX, true, true],
+ [1.0, true, true],
+ ["1.0", true, true],
+ ["001.0", true, true],
+ ["1.0e2", true, true],
+ ["1", true, true],
+ ["001", true, true],
+ ["1e2", true, true],
+ ["+1.0", true, true],
+ ["+001.0", true, true],
+ ["+1.0e2", true, true],
+ ["+1", true, true],
+ ["+001", true, true],
+ ["+1e2", true, true],
+ [0, false, true],
+ ["0", false, true],
+ ["000", false, true],
+ [0.0, false, true],
+ ["0.0", false, true],
+ ["000.000", false, true],
+ ["+0", false, true],
+ ["+000", false, true],
+ ["+0.0", false, true],
+ ["+000.000", false, true],
+ [-1, false, false],
+ [-1.0, false, false],
+ ["-1.0", false, false],
+ ["-001.0", false, false],
+ ["-1.0e2", false, false],
+ ["-1", false, false],
+ ["-001", false, false],
+ ["-1e2", false, false],
+ [-0, false, true],
+ ["-0", false, true],
+ ["-000", false, true],
+ [-0.0, false, true],
+ ["-0.0", false, true],
+ ["-000.000", false, true],
+ [false, false, false],
+ [true, false, false],
+ [INF, false, false],
+ [-INF, false, false],
+ [NAN, false, false],
+ [[], false, false],
+ ["some string", false, false],
+ [" ", false, false],
+ [new \StdClass, false, false],
+ ];
+ foreach ($tests as $test) {
+ list($value, $exp, $expNull) = $test;
+ $this->assertSame($exp, I::id($value), "Non-null test failed for value: ".var_export($value, true));
+ $this->assertSame($expNull, I::id($value, true), "Null test failed for value: ".var_export($value, true));
+ }
+ }
+}
diff --git a/tests/REST/NextCloudNews/TestNCNV1_2.php b/tests/REST/NextCloudNews/TestNCNV1_2.php
index a137df97..24665991 100644
--- a/tests/REST/NextCloudNews/TestNCNV1_2.php
+++ b/tests/REST/NextCloudNews/TestNCNV1_2.php
@@ -46,6 +46,21 @@ class TestNCNV1_2 extends Test\AbstractTest {
'title' => 'Second example feed',
'unread' => 23,
],
+ [
+ 'id' => 47,
+ 'url' => 'http://example.net/news.atom',
+ 'favicon' => 'http://example.net/favicon.png',
+ 'source' => 'http://example.net/',
+ 'folder' => null,
+ 'top_folder' => null,
+ 'pinned' => 0,
+ 'err_count' => 0,
+ 'err_msg' => '',
+ 'order_type' => 1,
+ 'added' => '2017-05-20 13:35:54',
+ 'title' => 'Third example feed',
+ 'unread' => 0,
+ ],
],
'rest' => [
[
@@ -76,6 +91,20 @@ class TestNCNV1_2 extends Test\AbstractTest {
'title' => 'Second example feed',
'unreadCount' => 23,
],
+ [
+ 'id' => 47,
+ 'url' => 'http://example.net/news.atom',
+ 'faviconLink' => 'http://example.net/favicon.png',
+ 'link' => 'http://example.net/',
+ 'folderId' => 0,
+ 'pinned' => false,
+ 'updateErrorCount' => 0,
+ 'lastUpdateError' => '',
+ 'ordering' => 1,
+ 'added' => 1495287354,
+ 'title' => 'Third example feed',
+ 'unreadCount' => 0,
+ ],
],
];
protected $articles = [
@@ -331,7 +360,7 @@ class TestNCNV1_2 extends Test\AbstractTest {
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", '', 'application/json')));
}
- public function testReceiveAuthenticationChallenge() {
+ public function testSendAuthenticationChallenge() {
Phake::when(Arsse::$user)->authHTTP->thenReturn(false);
$exp = new Response(401, "", "", ['WWW-Authenticate: Basic realm="'.REST\NextCloudNews\V1_2::REALM.'"']);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/")));
@@ -381,6 +410,7 @@ class TestNCNV1_2 extends Test\AbstractTest {
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders")));
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":""}', 'application/json')));
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":" "}', 'application/json')));
+ $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":{}}', 'application/json')));
// try adding the same two folders again
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software")));
@@ -457,26 +487,29 @@ class TestNCNV1_2 extends Test\AbstractTest {
$in = [
['url' => "http://example.com/news.atom", 'folderId' => 3],
['url' => "http://example.org/news.atom", 'folderId' => 8],
- ['url' => "http://example.net/news.atom", 'folderId' => 0],
+ ['url' => "http://example.net/news.atom", 'folderId' => 8],
+ ['url' => "http://example.net/news.atom", 'folderId' => -1],
[],
];
$out = [
['feeds' => [$this->feeds['rest'][0]]],
['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915],
- [],
- [],
+ ['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112],
];
// set up the necessary mocks
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.com/news.atom")->thenReturn(2112)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.org/news.atom")->thenReturn(42)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2112)->thenReturn($this->feeds['db'][0]);
- Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->feeds['db'][1]);
+ Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->feeds['db'][1]);
+ Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->feeds['db'][2]);
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(2112))->thenReturn(0);
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(42))->thenReturn(4758915);
- Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 2112, ['folder' => 3])->thenThrow(new ExceptionInput("idMissing")); // folder ID 3 does not exist
- Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, ['folder' => 8])->thenReturn(true);
- // set up a mock for a bad feed
- 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()));
+ Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(47))->thenReturn(2112);
+ Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 2112, ['folder' => 3])->thenThrow(new ExceptionInput("idMissing")); // folder ID 3 does not exist
+ Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 42, ['folder' => 8])->thenReturn(true);
+ Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 47, ['folder' => -1])->thenThrow(new ExceptionInput("typeViolation")); // folder ID -1 is invalid
+ // 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]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json')));
@@ -485,14 +518,16 @@ class TestNCNV1_2 extends Test\AbstractTest {
// try to add them a second time
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[0]), 'application/json')));
- $exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json')));
// try to add a bad feed
$exp = new Response(422);
$this->assertEquals($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]);
+ $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[3]), 'application/json')));
// try to add no feed
$exp = new Response(422);
- $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[3]), 'application/json')));
+ $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[4]), 'application/json')));
}
public function testRemoveASubscription() {
@@ -511,11 +546,13 @@ class TestNCNV1_2 extends Test\AbstractTest {
['folderId' => 42],
['folderId' => 2112],
['folderId' => 42],
+ ['folderId' => -1],
[],
];
- Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => 42])->thenReturn(true);
+ Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => 42])->thenReturn(true);
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 1, ['folder' => null])->thenReturn(true);
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);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[0]), 'application/json')));
@@ -527,6 +564,8 @@ class TestNCNV1_2 extends Test\AbstractTest {
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/move", json_encode($in[3]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[4]), 'application/json')));
+ $exp = new Response(422);
+ $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[5]), 'application/json')));
}
public function testRenameASubscription() {
@@ -584,18 +623,20 @@ class TestNCNV1_2 extends Test\AbstractTest {
['feedId' => 42], // valid
['feedId' => 2112], // feed does not exist
['feedId' => "ook"], // invalid ID
+ ['feedId' => -1], // invalid ID
['feed' => 42], // invalid input
];
Phake::when(Arsse::$db)->feedUpdate(42)->thenReturn(true);
Phake::when(Arsse::$db)->feedUpdate(2112)->thenThrow(new ExceptionInput("subjectMissing"));
+ Phake::when(Arsse::$db)->feedUpdate(-1)->thenThrow(new ExceptionInput("typeViolation"));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json')));
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[2]), 'application/json')));
- $exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[3]), 'application/json')));
+ $this->assertEquals($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);
@@ -606,13 +647,15 @@ class TestNCNV1_2 extends Test\AbstractTest {
$res = new Result($this->articles['db']);
$t = new \DateTime;
$in = [
- ['type' => 0, 'id' => 42],
- ['type' => 1, 'id' => 2112],
- ['type' => 2, 'id' => 0],
- ['type' => 3, 'id' => 0],
+ ['type' => 0, 'id' => 42], // type=0 => subscription/feed
+ ['type' => 1, 'id' => 2112], // type=1 => folder
+ ['type' => 0, 'id' => -1], // type=0 => subscription/feed; invalid ID
+ ['type' => 1, 'id' => -1], // type=1 => folder; invalid ID
+ ['type' => 2, 'id' => 0], // type=2 => starred
+ ['type' => 3, 'id' => 0], // type=3 => all (default); base context
['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5],
['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5],
- ['getRead' => true],
+ ['getRead' => true], // base context
['getRead' => false],
['lastModified' => $t->getTimestamp()],
['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], // offset=0 should not set the latestEdition context
@@ -620,6 +663,8 @@ class TestNCNV1_2 extends Test\AbstractTest {
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, $this->anything())->thenReturn($res);
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42))->thenThrow(new ExceptionInput("idMissing"));
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(2112))->thenThrow(new ExceptionInput("idMissing"));
+ Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(-1))->thenThrow(new ExceptionInput("typeViolation"));
+ Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(-1))->thenThrow(new ExceptionInput("typeViolation"));
$exp = new Response(200, ['items' => $this->articles['rest']]);
// check the contents of the response
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/items"))); // first instance of base context
@@ -628,22 +673,26 @@ class TestNCNV1_2 extends Test\AbstractTest {
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[0]), 'application/json')));
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[1]), 'application/json')));
+ $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[2]), 'application/json')));
+ $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/items", json_encode($in[3]), 'application/json')));
// simply run through the remainder of the input for later method verification
- $this->h->dispatch(new Request("GET", "/items", json_encode($in[2]), 'application/json'));
- $this->h->dispatch(new Request("GET", "/items", json_encode($in[3]), 'application/json')); // third instance of base context
$this->h->dispatch(new Request("GET", "/items", json_encode($in[4]), 'application/json'));
- $this->h->dispatch(new Request("GET", "/items", json_encode($in[5]), 'application/json'));
- $this->h->dispatch(new Request("GET", "/items", json_encode($in[6]), 'application/json')); // fourth instance of base context
+ $this->h->dispatch(new Request("GET", "/items", json_encode($in[5]), 'application/json')); // third instance of base context
+ $this->h->dispatch(new Request("GET", "/items", json_encode($in[6]), 'application/json'));
$this->h->dispatch(new Request("GET", "/items", json_encode($in[7]), 'application/json'));
- $this->h->dispatch(new Request("GET", "/items", json_encode($in[8]), 'application/json'));
+ $this->h->dispatch(new Request("GET", "/items", json_encode($in[8]), 'application/json')); // fourth instance of base context
$this->h->dispatch(new Request("GET", "/items", json_encode($in[9]), 'application/json'));
+ $this->h->dispatch(new Request("GET", "/items", json_encode($in[10]), 'application/json'));
+ $this->h->dispatch(new Request("GET", "/items", json_encode($in[11]), 'application/json'));
// perform method verifications
Phake::verify(Arsse::$db, Phake::times(4))->articleList(Arsse::$user->id, (new Context)->reverse(true));
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42));
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(2112));
+ Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(-1));
+ Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(-1));
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->starred(true));
- Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(false)->limit(10)->oldestEdition(6));
- Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->limit(5)->latestEdition(4));
+ Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(false)->limit(10)->oldestEdition(6)); // offset is one more than specified
+ Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->limit(5)->latestEdition(4)); // offset is one less than specified
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->unread(true));
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->modifiedSince($t));
Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->limit(5));
diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php
index 391ae7aa..a310cda0 100644
--- a/tests/lib/AbstractTest.php
+++ b/tests/lib/AbstractTest.php
@@ -25,10 +25,10 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
}
}
- public function assertTime($exp, $test) {
+ public function assertTime($exp, $test, string $msg = null) {
$exp = Date::transform($exp, "iso8601");
$test = Date::transform($test, "iso8601");
- $this->assertSame($exp, $test);
+ $this->assertSame($exp, $test, $msg);
}
public function clearData(bool $loadLang = true): bool {
diff --git a/tests/lib/Database/SeriesFeed.php b/tests/lib/Database/SeriesFeed.php
index d2cd7ed1..09312e52 100644
--- a/tests/lib/Database/SeriesFeed.php
+++ b/tests/lib/Database/SeriesFeed.php
@@ -221,6 +221,11 @@ trait SeriesFeed {
Arsse::$db->feedUpdate(2112);
}
+ public function testUpdateAnInvalidFeed() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->feedUpdate(-1);
+ }
+
public function testUpdateAFeedThrowingExceptions() {
$this->assertException("invalidUrl", "Feed");
Arsse::$db->feedUpdate(3, true);
diff --git a/tests/lib/Database/SeriesFolder.php b/tests/lib/Database/SeriesFolder.php
index 16312f2e..5c0d15b4 100644
--- a/tests/lib/Database/SeriesFolder.php
+++ b/tests/lib/Database/SeriesFolder.php
@@ -77,7 +77,7 @@ trait SeriesFolder {
}
public function testAddANestedFolderToAnInvalidParent() {
- $this->assertException("idMissing", "Db", "ExceptionInput");
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => "stringFolderId"]);
}
@@ -184,6 +184,11 @@ trait SeriesFolder {
Arsse::$db->folderRemove("john.doe@example.com", 2112);
}
+ public function testRemoveAnInvalidFolder() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->folderRemove("john.doe@example.com", -1);
+ }
+
public function testRemoveAFolderOfTheWrongOwner() {
$this->assertException("subjectMissing", "Db", "ExceptionInput");
Arsse::$db->folderRemove("john.doe@example.com", 4); // folder ID 4 belongs to Jane
@@ -210,6 +215,11 @@ trait SeriesFolder {
Arsse::$db->folderPropertiesGet("john.doe@example.com", 2112);
}
+ public function testGetThePropertiesOfAnInvalidFolder() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->folderPropertiesGet("john.doe@example.com", -1);
+ }
+
public function testGetThePropertiesOfAFolderOfTheWrongOwner() {
$this->assertException("subjectMissing", "Db", "ExceptionInput");
Arsse::$db->folderPropertiesGet("john.doe@example.com", 4); // folder ID 4 belongs to Jane
@@ -233,6 +243,10 @@ trait SeriesFolder {
$this->compareExpectations($state);
}
+ public function testRenameTheRootFolder() {
+ $this->assertFalse(Arsse::$db->folderPropertiesSet("john.doe@example.com", null, ['name' => "Opinion"]));
+ }
+
public function testRenameAFolderToTheEmptyString() {
$this->assertException("missing", "Db", "ExceptionInput");
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => ""]));
@@ -296,6 +310,11 @@ trait SeriesFolder {
Arsse::$db->folderPropertiesSet("john.doe@example.com", 2112, ['parent' => null]);
}
+ public function testSetThePropertiesOfAnInvalidFolder() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->folderPropertiesSet("john.doe@example.com", -1, ['parent' => null]);
+ }
+
public function testSetThePropertiesOfAFolderForTheWrongOwner() {
$this->assertException("subjectMissing", "Db", "ExceptionInput");
Arsse::$db->folderPropertiesSet("john.doe@example.com", 4, ['parent' => null]); // folder ID 4 belongs to Jane
diff --git a/tests/lib/Database/SeriesSubscription.php b/tests/lib/Database/SeriesSubscription.php
index 12f57600..b68ecb47 100644
--- a/tests/lib/Database/SeriesSubscription.php
+++ b/tests/lib/Database/SeriesSubscription.php
@@ -193,6 +193,11 @@ trait SeriesSubscription {
Arsse::$db->subscriptionRemove($this->user, 2112);
}
+ public function testRemoveAnInvalidSubscription() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->subscriptionRemove($this->user, -1);
+ }
+
public function testRemoveASubscriptionForTheWrongOwner() {
$this->user = "jane.doe@example.com";
$this->assertException("subjectMissing", "Db", "ExceptionInput");
@@ -264,6 +269,11 @@ trait SeriesSubscription {
Arsse::$db->subscriptionPropertiesGet($this->user, 2112);
}
+ public function testGetThePropertiesOfAnInvalidSubscription() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->subscriptionPropertiesGet($this->user, -1);
+ }
+
public function testGetThePropertiesOfASubscriptionWithoutAuthority() {
Phake::when(Arsse::$user)->authorize->thenReturn(false);
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
@@ -311,7 +321,7 @@ trait SeriesSubscription {
}
public function testRenameASubscriptionToFalse() {
- $this->assertException("missing", "Db", "ExceptionInput");
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => false]);
}
@@ -329,6 +339,11 @@ trait SeriesSubscription {
Arsse::$db->subscriptionPropertiesSet($this->user, 2112, ['folder' => null]);
}
+ public function testSetThePropertiesOfAnInvalidSubscription() {
+ $this->assertException("typeViolation", "Db", "ExceptionInput");
+ Arsse::$db->subscriptionPropertiesSet($this->user, -1, ['folder' => null]);
+ }
+
public function testSetThePropertiesOfASubscriptionWithoutAuthority() {
Phake::when(Arsse::$user)->authorize->thenReturn(false);
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 2a2b4434..1e30465b 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -31,6 +31,10 @@
Conf/TestConf.php
+
+ Misc/TestValueInfo.php
+ Misc/TestContext.php
+
User/TestUserMockInternal.php
User/TestUserMockExternal.php
@@ -41,9 +45,6 @@
Feed/TestFeedFetching.php
Feed/TestFeed.php
-
- Misc/TestContext.php
-
Db/TestTransaction.php
Db/SQLite3/TestDbResultSQLite3.php