1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Implement NCN API v1-2 feed handling: tests

- Fixes #8
- Fixes #9
- Fixes #10
- Fixes #11
- Fixes #12

- Differentiated between a missing ID which is the subject of an action (e.g. the subscription when moving a subscription) and other missing IDs (e.g. the folder when moving a subscription)
- Enforced same rules for subscription titles as for folder names, save that null is valid for subscriptions
This commit is contained in:
J. King 2017-05-21 10:10:36 -04:00
parent 054200dfc0
commit 88c0f4986c
7 changed files with 259 additions and 48 deletions

View file

@ -43,6 +43,7 @@ abstract class AbstractException extends \Exception {
"Db/ExceptionInput.constraintViolation" => 10236, "Db/ExceptionInput.constraintViolation" => 10236,
"Db/ExceptionInput.typeViolation" => 10237, "Db/ExceptionInput.typeViolation" => 10237,
"Db/ExceptionInput.circularDependence" => 10238, "Db/ExceptionInput.circularDependence" => 10238,
"Db/ExceptionInput.subjectMissing" => 10239,
"Db/ExceptionTimeout.general" => 10241, "Db/ExceptionTimeout.general" => 10241,
"Conf/Exception.fileMissing" => 10301, "Conf/Exception.fileMissing" => 10301,
"Conf/Exception.fileUnusable" => 10302, "Conf/Exception.fileUnusable" => 10302,

View file

@ -344,25 +344,23 @@ class Database {
public function folderRemove(string $user, int $id): bool { public function folderRemove(string $user, int $id): bool {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$changes = $this->db->prepare("DELETE FROM arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->changes(); $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("idMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]); if(!$changes) throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
return true; return true;
} }
public function folderPropertiesGet(string $user, int $id): array { public function folderPropertiesGet(string $user, int $id): array {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$props = $this->db->prepare("SELECT id,name,parent from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->getRow(); $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("idMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]); if(!$props) throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
return $props; return $props;
} }
public function folderPropertiesSet(string $user, int $id, array $data): bool { public function folderPropertiesSet(string $user, int $id, array $data): bool {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
// validate the folder ID and, if specified, the parent to move it to // validate the folder ID and, if specified, the parent to move it to
if(array_key_exists("parent", $data)) { $parent = null;
$f = $this->folderValidateId($user, $id, $data['parent']); if(array_key_exists("parent", $data)) $parent = $data['parent'];
} else { $f = $this->folderValidateId($user, $id, $parent, true);
$f = $this->folderValidateId($user, $id);
}
// if a new name is specified, validate it // if a new name is specified, validate it
if(array_key_exists("name", $data)) { if(array_key_exists("name", $data)) {
$this->folderValidateName($data['name']); $this->folderValidateName($data['name']);
@ -381,7 +379,7 @@ class Database {
return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause where owner is ? and id is ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes(); return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause where owner is ? and id is ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
} }
protected function folderValidateId(string $user, int $id = null, int $parent = null): array { protected function folderValidateId(string $user, int $id = null, int $parent = null, bool $subject = false): array {
if(is_null($id)) { if(is_null($id)) {
// if no ID is specified this is a no-op, unless a parent is specified, which is always a circular dependence // if no ID is specified this is a no-op, unless a parent is specified, which is always a circular dependence
if(!is_null($parent)) { if(!is_null($parent)) {
@ -391,7 +389,7 @@ class Database {
} }
// check whether the folder exists and is owned by the user // check whether the folder exists and is owned by the user
$f = $this->db->prepare("SELECT name,parent from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->getRow(); $f = $this->db->prepare("SELECT name,parent from arsse_folders where owner is ? and id is ?", "str", "int")->run($user, $id)->getRow();
if(!$f) throw new Db\ExceptionInput("idMissing", ["action" => $this->caller(), "field" => "folder", 'id' => $parent]); if(!$f) throw new Db\ExceptionInput($subject ? "subjectMissing" : "idMissing", ["action" => $this->caller(), "field" => "folder", 'id' => $parent]);
// if we're moving a folder to a new parent, check that the parent is valid // if we're moving a folder to a new parent, check that the parent is valid
if(!is_null($parent)) { if(!is_null($parent)) {
// make sure both that the parent exists, and that the parent is not either the folder itself or one of its children (a circular dependence) // make sure both that the parent exists, and that the parent is not either the folder itself or one of its children (a circular dependence)
@ -413,7 +411,7 @@ class Database {
protected function folderValidateName($name): bool { protected function folderValidateName($name): bool {
$name = (string) $name; $name = (string) $name;
if($name=="") { if(!strlen($name)) {
throw new Db\ExceptionInput("missing", ["action" => $this->caller(), "field" => "name"]); throw new Db\ExceptionInput("missing", ["action" => $this->caller(), "field" => "name"]);
} else if(!strlen(trim($name))) { } else if(!strlen(trim($name))) {
throw new Db\ExceptionInput("whitespace", ["action" => $this->caller(), "field" => "name"]); throw new Db\ExceptionInput("whitespace", ["action" => $this->caller(), "field" => "name"]);
@ -473,7 +471,7 @@ class Database {
public function subscriptionRemove(string $user, int $id): bool { public function subscriptionRemove(string $user, int $id): bool {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$changes = $this->db->prepare("DELETE from arsse_subscriptions where owner is ? and id is ?", "str", "int")->run($user, $id)->changes(); $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("idMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]); if(!$changes) throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "folder", 'id' => $id]);
return true; return true;
} }
@ -483,7 +481,7 @@ class Database {
Data::$user->authorizationEnabled(false); Data::$user->authorizationEnabled(false);
$sub = $this->subscriptionList($user, null, $id)->getRow(); $sub = $this->subscriptionList($user, null, $id)->getRow();
Data::$user->authorizationEnabled(true); Data::$user->authorizationEnabled(true);
if(!$sub) throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]); if(!$sub) throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
return $sub; return $sub;
} }
@ -492,18 +490,20 @@ class Database {
$tr = $this->db->begin(); $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(!$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 // if the ID doesn't exist or doesn't belong to the user, throw an exception
throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]); throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
} }
if(array_key_exists("folder", $data)) { if(array_key_exists("folder", $data)) {
// ensure the target folder exists and belong to the user // ensure the target folder exists and belong to the user
$this->folderValidateId($user, $data['folder']); $this->folderValidateId($user, $data['folder']);
} }
if(array_key_exists("title", $data)) { if(array_key_exists("title", $data)) {
// if the title is effectively an empty string, change it to null so that the feed title is used instead // if the title is null, this signals intended use of the default title; otherwise make sure it's not effectively an empty string
$title = (string) $data['title']; if(!is_null($data['title'])) {
$title = trim($title); $title = (string) $data['title'];
if($title==="") $title = null; if(!strlen($title)) throw new Db\ExceptionInput("missing", ["action" => __FUNCTION__, "field" => "title"]);
$data['title'] = $title; if(!strlen(trim($title))) throw new Db\ExceptionInput("whitespace", ["action" => __FUNCTION__, "field" => "title"]);
$data['title'] = $title;
}
} }
$valid = [ $valid = [
'title' => "str", 'title' => "str",
@ -558,7 +558,7 @@ class Database {
$tr = $this->db->begin(); $tr = $this->db->begin();
// check to make sure the feed exists // check to make sure the feed exists
$f = $this->db->prepare('SELECT url, username, password, DATEFORMAT("http", modified) AS lastmodified, etag, err_count FROM arsse_feeds where id is ?', "int")->run($feedID)->getRow(); $f = $this->db->prepare('SELECT url, username, password, DATEFORMAT("http", modified) AS lastmodified, etag, err_count FROM arsse_feeds where id is ?', "int")->run($feedID)->getRow();
if(!$f) throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $feedID]); if(!$f) throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $feedID]);
// the Feed object throws an exception when there are problems, but that isn't ideal // the Feed object throws an exception when there are problems, but that isn't ideal
// here. When an exception is thrown it should update the database with the // 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 // error instead of failing; if other exceptions are thrown, we should simply roll back

View file

@ -159,7 +159,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch(ExceptionInput $e) { } catch(ExceptionInput $e) {
switch($e->getCode()) { switch($e->getCode()) {
// folder does not exist // folder does not exist
case 10235: return new Response(404); case 10239: return new Response(404);
// folder already exists // folder already exists
case 10236: return new Response(409); case 10236: return new Response(409);
// folder name not acceptable // folder name not acceptable
@ -206,13 +206,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
$out = ['feeds' => $out]; $out = ['feeds' => $out];
$out['starredCount'] = Data::$db->articleStarredCount(Data::$user->id); $out['starredCount'] = Data::$db->articleStarredCount(Data::$user->id);
$newest = Data::$db->editionLatest(Data::$user->id, ['subscription' => $id]); $newest = Data::$db->editionLatest(Data::$user->id);
if($newest) $out['newestItemId'] = $newest; if($newest) $out['newestItemId'] = $newest;
return new Response(200, $out); return new Response(200, $out);
} }
// return list of feeds which should be refreshed // return list of feeds which should be refreshed
// refresh a feed
protected function feedListStale(array $url, array $data): Response { protected function feedListStale(array $url, array $data): Response {
// function requires admin rights per spec // function requires admin rights per spec
if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403); if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403);
@ -305,7 +304,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
try { try {
Data::$db->subscriptionPropertiesSet(Data::$user->id, (int) $url[1], $in); Data::$db->subscriptionPropertiesSet(Data::$user->id, (int) $url[1], $in);
} catch(ExceptionInput $e) { } catch(ExceptionInput $e) {
return new Response(404); switch($e->getCode()) {
// subscription does not exist
case 10239: return new Response(404);
// name is invalid
case 10231:
case 10232: return new Response(422);
// other errors related to input
default: return new Response(400);
}
} }
return new Response(204); return new Response(204);
} }
@ -326,7 +333,14 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
try { try {
Data::$db->subscriptionPropertiesSet(Data::$user->id, (int) $url[1], $in); Data::$db->subscriptionPropertiesSet(Data::$user->id, (int) $url[1], $in);
} catch(ExceptionInput $e) { } catch(ExceptionInput $e) {
return new Response(404); 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);
}
} }
return new Response(204); return new Response(204);
} }

View file

@ -66,6 +66,7 @@ return [
'Exception.JKingWeb/Arsse/Db/ExceptionInput.whitespace' => 'Required field "{field}" of action "{action}" may not contain only whitespace', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.whitespace' => 'Required field "{field}" of action "{action}" may not contain only whitespace',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.tooLong' => 'Required field "{field}" of action "{action}" has a maximum length of {max}', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.tooLong' => 'Required field "{field}" of action "{action}" has a maximum length of {max}',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.tooShort' => 'Required field "{field}" of action "{action}" has a minimum length of {min}', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.tooShort' => 'Required field "{field}" of action "{action}" has a minimum length of {min}',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.subjectMissing' => 'Referenced ID ({id}) in field "{field}" does not exist',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.idMissing' => 'Referenced ID ({id}) in field "{field}" does not exist', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.idMissing' => 'Referenced ID ({id}) in field "{field}" does not exist',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.circularDependence' => 'Referenced ID ({id}) in field "{field}" creates a circular dependence', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.circularDependence' => 'Referenced ID ({id}) in field "{field}" creates a circular dependence',
'Exception.JKingWeb/Arsse/Db/ExceptionInput.constraintViolation' => '{0}', 'Exception.JKingWeb/Arsse/Db/ExceptionInput.constraintViolation' => '{0}',

View file

@ -11,6 +11,68 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
protected $h; protected $h;
protected $feeds = [ // expected sample output of a feed list from the database, and the resultant expected transformation by the REST handler
'db' => [
[
'id' => 2112,
'url' => 'http://example.com/news.atom',
'favicon' => 'http://example.com/favicon.png',
'source' => 'http://example.com/',
'folder' => NULL,
'pinned' => 0,
'err_count' => 0,
'err_msg' => '',
'order_type' => 0,
'added' => 1495287354,
'title' => 'First example feed',
'unread' => 50048,
],
[
'id' => 42,
'url' => 'http://example.org/news.atom',
'favicon' => 'http://example.org/favicon.png',
'source' => 'http://example.org/',
'folder' => 8,
'pinned' => 1,
'err_count' => 0,
'err_msg' => '',
'order_type' => 2,
'added' => 1495287354,
'title' => 'Second example feed',
'unread' => 23,
],
],
'rest' => [
[
'id' => 2112,
'url' => 'http://example.com/news.atom',
'faviconLink' => 'http://example.com/favicon.png',
'link' => 'http://example.com/',
'folderId' => 0,
'pinned' => false,
'updateErrorCount' => 0,
'lastUpdateError' => '',
'ordering' => 0,
'added' => 1495287354,
'title' => 'First example feed',
'unreadCount' => 50048,
],
[
'id' => 42,
'url' => 'http://example.org/news.atom',
'faviconLink' => 'http://example.org/favicon.png',
'link' => 'http://example.org/',
'folderId' => 8,
'pinned' => true,
'updateErrorCount' => 0,
'lastUpdateError' => '',
'ordering' => 2,
'added' => 1495287354,
'title' => 'Second example feed',
'unreadCount' => 23,
],
],
];
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
@ -78,7 +140,9 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
['id' => 1, 'name' => "Software", 'parent' => null], ['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 12, 'name' => "Hardware", 'parent' => null], ['id' => 12, 'name' => "Hardware", 'parent' => null],
]; ];
Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list)); Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($list));
$exp = new Response(200, ['folders' => []]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
$exp = new Response(200, ['folders' => $list]); $exp = new Response(200, ['folders' => $list]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders"))); $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
} }
@ -123,7 +187,7 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
} }
function testRemoveAFolder() { function testRemoveAFolder() {
Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing"));
$exp = new Response(204); $exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1"))); $this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
@ -145,7 +209,7 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[2])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing")); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[2])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[3])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace")); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[3])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 3, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); // folder ID 3 does not exist Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 3, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing")); // folder ID 3 does not exist
$exp = new Response(204); $exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json')));
$exp = new Response(409); $exp = new Response(409);
@ -164,4 +228,122 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
$exp = new Response(200, ['version' => \JKingWeb\Arsse\VERSION]); $exp = new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/version"))); $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/version")));
} }
function testListSubscriptions() {
$exp1 = [
'feeds' => [],
'starredCount' => 0,
];
$exp2 = [
'feeds' => $this->feeds['rest'],
'starredCount' => 5,
'newestItemId' => 4758915,
];
Phake::when(Data::$db)->subscriptionList(Data::$user->id)->thenReturn(new Result([]))->thenReturn(new Result($this->feeds['db']));
Phake::when(Data::$db)->articleStarredCount(Data::$user->id)->thenReturn(0)->thenReturn(5);
Phake::when(Data::$db)->editionLatest(Data::$user->id)->thenReturn(0)->thenReturn(4758915);
$exp = new Response(200, $exp1);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds")));
$exp = new Response(200, $exp2);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds")));
// make sure the correct date format is actually requested
Phake::verify(Data::$db, Phake::atLeast(1))->dateFormatDefault("unix");
}
function testAddASubscription() {
$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],
];
$out = [
['feeds' => [$this->feeds['rest'][0]]],
['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915],
[],
];
// set up the necessary mocks
Phake::when(Data::$db)->subscriptionAdd(Data::$user->id, "http://example.com/news.atom")->thenReturn(2112)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Data::$db)->subscriptionAdd(Data::$user->id, "http://example.org/news.atom")->thenReturn( 42 )->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Data::$db)->subscriptionPropertiesGet(Data::$user->id, 2112)->thenReturn($this->feeds['db'][0]);
Phake::when(Data::$db)->subscriptionPropertiesGet(Data::$user->id, 42)->thenReturn($this->feeds['db'][1]);
Phake::when(Data::$db)->editionLatest(Data::$user->id, ['subscription' => 2112])->thenReturn(0);
Phake::when(Data::$db)->editionLatest(Data::$user->id, ['subscription' => 42])->thenReturn(4758915);
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 2112, ['folder' => 3])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); // folder ID 3 does not exist
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 42, ['folder' => 8])->thenReturn(true);
// set up a mock for a bad feed
Phake::when(Data::$db)->subscriptionAdd(Data::$user->id, "http://example.net/news.atom")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.net/news.atom", new \PicoFeed\Client\InvalidUrlException()));
// 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')));
$exp = new Response(200, $out[1]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/feeds", json_encode($in[1]), 'application/json')));
// try to add them a second time
$exp = new Response(409);
$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')));
// make sure the correct date format is actually requested
Phake::verify(Data::$db, Phake::atLeast(1))->dateFormatDefault("unix");
}
function testRemoveASubscription() {
Phake::when(Data::$db)->subscriptionRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing"));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1")));
// fail on the second invocation because it no longer exists
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/feeds/1")));
Phake::verify(Data::$db, Phake::times(2))->subscriptionRemove(Data::$user->id, 1);
}
function testMoveASubscription() {
$in = [
['folderId' => 0],
['folderId' => 42],
['folderId' => 2112],
['folderId' => 42],
];
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, ['folder' => 42])->thenReturn(true);
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, ['folder' => null])->thenReturn(true);
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, ['folder' => 2112])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); // folder does not exist
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 42, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\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')));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/move", json_encode($in[2]), 'application/json')));
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/move", json_encode($in[3]), 'application/json')));
}
function testRenameASubscription() {
$in = [
['feedTitle' => null],
['feedTitle' => "Ook"],
['feedTitle' => " "],
['feedTitle' => ""],
['feedTitle' => false],
['feedTitle' => "Feed does not exist"],
];
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, $this->identicalTo(['title' => null]))->thenReturn(true);
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, $this->identicalTo(['title' => "Ook"]))->thenReturn(true);
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, $this->identicalTo(['title' => " "]))->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, $this->identicalTo(['title' => ""]))->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 1, $this->identicalTo(['title' => false]))->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->subscriptionPropertiesSet(Data::$user->id, 42, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing"));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[0]), 'application/json')));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[2]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/1/rename", json_encode($in[3]), 'application/json')));
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/feeds/42/rename", json_encode($in[4]), 'application/json')));
}
} }

View file

@ -139,12 +139,12 @@ trait SeriesFolder {
} }
function testRemoveAMissingFolder() { function testRemoveAMissingFolder() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderRemove("john.doe@example.com", 2112); Data::$db->folderRemove("john.doe@example.com", 2112);
} }
function testRemoveAFolderOfTheWrongOwner() { function testRemoveAFolderOfTheWrongOwner() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderRemove("john.doe@example.com", 4); // folder ID 4 belongs to Jane Data::$db->folderRemove("john.doe@example.com", 4); // folder ID 4 belongs to Jane
} }
@ -165,12 +165,12 @@ trait SeriesFolder {
} }
function testGetThePropertiesOfAMissingFolder() { function testGetThePropertiesOfAMissingFolder() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderPropertiesGet("john.doe@example.com", 2112); Data::$db->folderPropertiesGet("john.doe@example.com", 2112);
} }
function testGetThePropertiesOfAFolderOfTheWrongOwner() { function testGetThePropertiesOfAFolderOfTheWrongOwner() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderPropertiesGet("john.doe@example.com", 4); // folder ID 4 belongs to Jane Data::$db->folderPropertiesGet("john.doe@example.com", 4); // folder ID 4 belongs to Jane
} }
@ -217,12 +217,12 @@ trait SeriesFolder {
} }
function testSetThePropertiesOfAMissingFolder() { function testSetThePropertiesOfAMissingFolder() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderPropertiesSet("john.doe@example.com", 2112, ['parent' => null]); Data::$db->folderPropertiesSet("john.doe@example.com", 2112, ['parent' => null]);
} }
function testSetThePropertiesOfAFolderOfTheWrongOwner() { function testSetThePropertiesOfAFolderForTheWrongOwner() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->folderPropertiesSet("john.doe@example.com", 4, ['parent' => null]); // folder ID 4 belongs to Jane Data::$db->folderPropertiesSet("john.doe@example.com", 4, ['parent' => null]); // folder ID 4 belongs to Jane
} }

View file

@ -167,13 +167,13 @@ trait SeriesSubscription {
} }
function testRemoveAMissingSubscription() { function testRemoveAMissingSubscription() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->subscriptionRemove($this->user, 2112); Data::$db->subscriptionRemove($this->user, 2112);
} }
function testRemoveASubscriptionForTheWrongOwner() { function testRemoveASubscriptionForTheWrongOwner() {
$this->user = "jane.doe@example.com"; $this->user = "jane.doe@example.com";
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->subscriptionRemove($this->user, 1); Data::$db->subscriptionRemove($this->user, 1);
} }
@ -257,31 +257,44 @@ trait SeriesSubscription {
$state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,"Ook Ook",3,0,0]; $state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,"Ook Ook",3,0,0];
$this->compareExpectations($state); $this->compareExpectations($state);
Data::$db->subscriptionPropertiesSet($this->user, 1,[ Data::$db->subscriptionPropertiesSet($this->user, 1,[
'title' => " ", 'title' => null,
]); ]);
$state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,null,3,0,0]; $state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,null,3,0,0];
$this->compareExpectations($state); $this->compareExpectations($state);
} }
function testMoveSubscriptionToAMissingFolder() { function testMoveASubscriptionToAMissingFolder() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("idMissing", "Db", "ExceptionInput");
Data::$db->subscriptionPropertiesSet($this->user, 1,[ Data::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => 4]);
'folder' => 4, }
]);
function testRenameASubscriptionToABlankTitle() {
$this->assertException("missing", "Db", "ExceptionInput");
Data::$db->subscriptionPropertiesSet($this->user, 1, ['title' => ""]);
}
function testRenameASubscriptionToAWhitespaceTitle() {
$this->assertException("whitespace", "Db", "ExceptionInput");
Data::$db->subscriptionPropertiesSet($this->user, 1, ['title' => " "]);
}
function testRenameASubscriptionToFalse() {
$this->assertException("missing", "Db", "ExceptionInput");
Data::$db->subscriptionPropertiesSet($this->user, 1, ['title' => false]);
}
function testRenameASubscriptionToZero() {
$this->assertTrue(Data::$db->subscriptionPropertiesSet($this->user, 1, ['title' => 0]));
} }
function testSetThePropertiesOfAMissingSubscription() { function testSetThePropertiesOfAMissingSubscription() {
$this->assertException("idMissing", "Db", "ExceptionInput"); $this->assertException("subjectMissing", "Db", "ExceptionInput");
Data::$db->subscriptionPropertiesSet($this->user, 2112,[ Data::$db->subscriptionPropertiesSet($this->user, 2112, ['folder' => null]);
'folder' => null,
]);
} }
function testSetThePropertiesOfASubscriptionWithoutAuthority() { function testSetThePropertiesOfASubscriptionWithoutAuthority() {
Phake::when(Data::$user)->authorize->thenReturn(false); Phake::when(Data::$user)->authorize->thenReturn(false);
$this->assertException("notAuthorized", "User", "ExceptionAuthz"); $this->assertException("notAuthorized", "User", "ExceptionAuthz");
Data::$db->subscriptionPropertiesSet($this->user, 1,[ Data::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => null]);
'folder' => null,
]);
} }
} }