@ -26,7 +26,6 @@ use JKingWeb\Arsse\User\ExceptionConflict;
use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput;
use JKingWeb\Arsse\Test\Result;
use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\TextResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\V1< extended > */
class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
@ -101,7 +100,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideAuthResponses */
public function testAuthenticateAUser($token, bool $auth, bool $success): void {
$exp = $success ? HTTP::respEmpty(404) : new ErrorResponse ("401", 401);
$exp = $success ? HTTP::respEmpty(404) : V1::respError ("401", 401);
$user = "john.doe@example.com";
if ($token !== null) {
$headers = ['X-Auth-Token' => $token];
@ -165,7 +164,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
}
public function testRejectBadlyTypedData(): void {
$exp = new ErrorResponse (["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422);
$exp = V1::respError (["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422);
$this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112]));
}
@ -183,10 +182,10 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
return [
["http://localhost:8000/Feed/Discovery/Valid", HTTP::respJson($discovered)],
["http://localhost:8000/Feed/Discovery/Invalid", HTTP::respJson([])],
["http://localhost:8000/Feed/Discovery/Missing", new ErrorResponse ("Fetch404", 502)],
[1, new ErrorResponse (["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)],
["Not a URL", new ErrorResponse (["InvalidInputValue", 'field' => "url"], 422)],
[null, new ErrorResponse (["MissingInputValue", 'field' => "url"], 422)],
["http://localhost:8000/Feed/Discovery/Missing", V1::respError ("Fetch404", 502)],
[1, V1::respError (["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)],
["Not a URL", V1::respError (["InvalidInputValue", 'field' => "url"], 422)],
[null, V1::respError (["MissingInputValue", 'field' => "url"], 422)],
];
}
@ -231,16 +230,16 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
[true, "/users/1", HTTP::respJson(self::USERS[0])],
[true, "/users/jane.doe@example.com", HTTP::respJson(self::USERS[1])],
[true, "/users/2", HTTP::respJson(self::USERS[1])],
[true, "/users/jack.doe@example.com", new ErrorResponse ("404", 404)],
[true, "/users/47", new ErrorResponse ("404", 404)],
[false, "/users", new ErrorResponse ("403", 403)],
[true, "/users/jack.doe@example.com", V1::respError ("404", 404)],
[true, "/users/47", V1::respError ("404", 404)],
[false, "/users", V1::respError ("403", 403)],
[false, "/me", HTTP::respJson(self::USERS[1])],
[false, "/users/john.doe@example.com", new ErrorResponse ("403", 403)],
[false, "/users/1", new ErrorResponse ("403", 403)],
[false, "/users/jane.doe@example.com", new ErrorResponse ("403", 403)],
[false, "/users/2", new ErrorResponse ("403", 403)],
[false, "/users/jack.doe@example.com", new ErrorResponse ("403", 403)],
[false, "/users/47", new ErrorResponse ("403", 403)],
[false, "/users/john.doe@example.com", V1::respError ("403", 403)],
[false, "/users/1", V1::respError ("403", 403)],
[false, "/users/jane.doe@example.com", V1::respError ("403", 403)],
[false, "/users/2", V1::respError ("403", 403)],
[false, "/users/jack.doe@example.com", V1::respError ("403", 403)],
[false, "/users/47", V1::respError ("403", 403)],
];
}
@ -305,21 +304,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$resp1 = array_merge(self::USERS[1], ['username' => "john.doe@example.com"]);
$resp2 = array_merge(self::USERS[1], ['id' => 1, 'is_admin' => true]);
return [
[false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, new ErrorResponse (["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)],
[false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)],
[false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse ("403", 403)],
[false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, new ErrorResponse ("InvalidElevation", 403)],
[false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, V1::respError (["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)],
[false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)],
[false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, V1::respError ("403", 403)],
[false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, V1::respError ("InvalidElevation", 403)],
[false, "/users/2", ['language' => "fr_CA"], null, null, null, null, ['lang' => "fr_CA"], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entry_sorting_direction' => "asc"], null, null, null, null, ['sort_asc' => true], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entry_sorting_direction' => "desc"], null, null, null, null, ['sort_asc' => false], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse (["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse (["InvalidInputValue", 'field' => "timezone"], 422)],
[false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "username"], 422)],
[false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, new ErrorResponse (["DuplicateUser", 'user' => "ook"], 409)],
[false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError (["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), V1::respError (["InvalidInputValue", 'field' => "timezone"], 422)],
[false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "username"], 422)],
[false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, V1::respError (["DuplicateUser", 'user' => "ook"], 409)],
[false, "/users/2", ['password' => "ook"], null, null, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['password' => "ook"]), 201)],
[false, "/users/2", ['username' => "ook", 'password' => "ook"], "ook", true, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['username' => "ook", 'password' => "ook"]), 201)],
[true, "/users/1", ['theme' => "stark"], null, null, null, null, ['theme' => "stark"], $out2, HTTP::respJson($resp2, 201)],
[true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse ("404", 404)],
[true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, V1::respError ("404", 404)],
];
}
@ -360,18 +359,18 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideUserAdditions(): iterable {
$resp1 = array_merge(self::USERS[1], ['username' => "ook", 'password' => "eek"]);
return [
[[], null, null, null, null, new ErrorResponse (["MissingInputValue", 'field' => "username"], 422)],
[['username' => "ook"], null, null, null, null, new ErrorResponse (["MissingInputValue", 'field' => "password"], 422)],
[['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, new ErrorResponse (["DuplicateUser", 'user' => "ook"], 409)],
[['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, new ErrorResponse (["InvalidInputValue", 'field' => "username"], 422)],
[['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse (["InvalidInputValue", 'field' => "timezone"], 422)],
[['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse (["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[[], null, null, null, null, V1::respError (["MissingInputValue", 'field' => "username"], 422)],
[['username' => "ook"], null, null, null, null, V1::respError (["MissingInputValue", 'field' => "password"], 422)],
[['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, V1::respError (["DuplicateUser", 'user' => "ook"], 409)],
[['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, V1::respError (["InvalidInputValue", 'field' => "username"], 422)],
[['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), V1::respError (["InvalidInputValue", 'field' => "timezone"], 422)],
[['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError (["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], HTTP::respJson($resp1, 201)],
];
}
public function testAddAUserWithoutAuthority(): void {
$this->assertMessage(new ErrorResponse ("403", 403), $this->req("POST", "/users", []));
$this->assertMessage(V1::respError ("403", 403), $this->req("POST", "/users", []));
}
public function testDeleteAUser(): void {
@ -391,13 +390,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->method("remove")->willReturn(true);
Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112);
Arsse::$user->expects($this->exactly(0))->method("remove");
$this->assertMessage(new ErrorResponse ("404", 404), $this->req("DELETE", "/users/2112"));
$this->assertMessage(V1::respError ("404", 404), $this->req("DELETE", "/users/2112"));
}
public function testDeleteAUserWithoutAuthority(): void {
Arsse::$user->expects($this->exactly(0))->method("lookup");
Arsse::$user->expects($this->exactly(0))->method("remove");
$this->assertMessage(new ErrorResponse ("403", 403), $this->req("DELETE", "/users/2112"));
$this->assertMessage(V1::respError ("403", 403), $this->req("DELETE", "/users/2112"));
}
public function testListCategories(): void {
@ -440,11 +439,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideCategoryAdditions(): iterable {
return [
["New", HTTP::respJson(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)],
["Duplicate", new ErrorResponse (["DuplicateCategory", 'title' => "Duplicate"], 409)],
["", new ErrorResponse (["InvalidCategory", 'title' => ""], 422)],
[" ", new ErrorResponse (["InvalidCategory", 'title' => " "], 422)],
[null, new ErrorResponse (["MissingInputValue", 'field' => "title"], 422)],
[false, new ErrorResponse (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
["Duplicate", V1::respError (["DuplicateCategory", 'title' => "Duplicate"], 409)],
["", V1::respError (["InvalidCategory", 'title' => ""], 422)],
[" ", V1::respError (["InvalidCategory", 'title' => " "], 422)],
[null, V1::respError (["MissingInputValue", 'field' => "title"], 422)],
[false, V1::respError (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
];
}
@ -465,19 +464,19 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideCategoryUpdates(): iterable {
return [
[3, "New", "subjectMissing", new ErrorResponse ("404", 404)],
[3, "New", "subjectMissing", V1::respError ("404", 404)],
[2, "New", true, HTTP::respJson(['id' => 2, 'title' => "New", 'user_id' => 42], 201)],
[2, "Duplicate", "constraintViolation", new ErrorResponse (["DuplicateCategory", 'title' => "Duplicate"], 409)],
[2, "", "missing", new ErrorResponse (["InvalidCategory", 'title' => ""], 422)],
[2, " ", "whitespace", new ErrorResponse (["InvalidCategory", 'title' => " "], 422)],
[2, null, "missing", new ErrorResponse (["MissingInputValue", 'field' => "title"], 422)],
[2, false, "subjectMissing", new ErrorResponse (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
[2, "Duplicate", "constraintViolation", V1::respError (["DuplicateCategory", 'title' => "Duplicate"], 409)],
[2, "", "missing", V1::respError (["InvalidCategory", 'title' => ""], 422)],
[2, " ", "whitespace", V1::respError (["InvalidCategory", 'title' => " "], 422)],
[2, null, "missing", V1::respError (["MissingInputValue", 'field' => "title"], 422)],
[2, false, "subjectMissing", V1::respError (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
[1, "New", true, HTTP::respJson(['id' => 1, 'title' => "New", 'user_id' => 42], 201)],
[1, "Duplicate", "constraintViolation", HTTP::respJson(['id' => 1, 'title' => "Duplicate", 'user_id' => 42], 201)], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used
[1, "", "missing", new ErrorResponse (["InvalidCategory", 'title' => ""], 422)],
[1, " ", "whitespace", new ErrorResponse (["InvalidCategory", 'title' => " "], 422)],
[1, null, "missing", new ErrorResponse (["MissingInputValue", 'field' => "title"], 422)],
[1, false, false, new ErrorResponse (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
[1, "", "missing", V1::respError (["InvalidCategory", 'title' => ""], 422)],
[1, " ", "whitespace", V1::respError (["InvalidCategory", 'title' => " "], 422)],
[1, null, "missing", V1::respError (["MissingInputValue", 'field' => "title"], 422)],
[1, false, false, V1::respError (["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
];
}
@ -485,7 +484,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/2112"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111);
$this->assertMessage(new ErrorResponse ("404", 404), $this->req("DELETE", "/categories/47"));
$this->assertMessage(V1::respError ("404", 404), $this->req("DELETE", "/categories/47"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 46);
}
@ -529,7 +528,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testListFeedsOfAMissingCategory(): void {
$this->dbMock->subscriptionList->throws(new ExceptionInput("idMissing"));
$exp = new ErrorResponse ("404", 404);
$exp = V1::respError ("404", 404);
$this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds"));
$this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true);
}
@ -544,7 +543,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testGetAMissingFeed(): void {
$this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse ("404", 404), $this->req("GET", "/feeds/1"));
$this->assertMessage(V1::respError ("404", 404), $this->req("GET", "/feeds/1"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1);
}
@ -610,29 +609,29 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFeedCreations(): iterable {
self::clearData();
return [
[['category_id' => 1], null, null, null, null, new ErrorResponse (["MissingInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/"], null, null, null, null, new ErrorResponse (["MissingInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, new ErrorResponse (["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)],
[['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "keeplist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, new ErrorResponse (["InvalidInputValue", 'field' => "blocklist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, new ErrorResponse ("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, new ErrorResponse ("Fetch403", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, new ErrorResponse ("Fetch401", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, new ErrorResponse ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, new ErrorResponse ("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, new ErrorResponse ("FetchFormat", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, new ErrorResponse ("DuplicateFeed", 409)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, new ErrorResponse ("MissingCategory", 422)],
[['category_id' => 1], null, null, null, null, V1::respError (["MissingInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/"], null, null, null, null, V1::respError (["MissingInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, V1::respError (["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)],
[['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "keeplist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, V1::respError (["InvalidInputValue", 'field' => "blocklist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, V1::respError ("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, V1::respError ("Fetch403", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, V1::respError ("Fetch401", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, V1::respError ("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, V1::respError ("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, V1::respError ("FetchFormat", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, V1::respError ("DuplicateFeed", 409)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, V1::respError ("MissingCategory", 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, true, null, HTTP::respJson(['feed_id' => 44], 201)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "^A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)],
@ -657,11 +656,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$success = HTTP::respJson(self::FEEDS_OUT[0], 201);
return [
[[], [], true, $success],
[[], [], new ExceptionInput("subjectMissing"), new ErrorResponse ("404", 404)],
[['title' => ""], ['title' => ""], new ExceptionInput("missing"), new ErrorResponse ("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse ("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse ("InvalidTitle", 422)],
[['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), new ErrorResponse ("MissingCategory", 422)],
[[], [], new ExceptionInput("subjectMissing"), V1::respError ("404", 404)],
[['title' => ""], ['title' => ""], new ExceptionInput("missing"), V1::respError ("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError ("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError ("InvalidTitle", 422)],
[['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), V1::respError ("MissingCategory", 422)],
[['crawler' => false], ['scrape' => false], true, $success],
[['keeplist_rules' => ""], ['keep_rule' => ""], true, $success],
[['blocklist_rules' => "ook"], ['block_rule' => "ook"], true, $success],
@ -685,7 +684,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testDeleteAMissingFeed(): void {
$this->dbMock->subscriptionRemove->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse ("404", 404), $this->req("DELETE", "/feeds/2112"));
$this->assertMessage(V1::respError ("404", 404), $this->req("DELETE", "/feeds/2112"));
$this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112);
}
@ -703,11 +702,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideIcons(): iterable {
return [
[['id' => 44, 'type' => "image/svg+xml", 'data' => "< svg / > "], HTTP::respJson(['id' => 44, 'data' => "image/svg+xml;base64,PHN2Zy8+", 'mime_type' => "image/svg+xml"])],
[['id' => 47, 'type' => "", 'data' => "< svg / > "], new ErrorResponse ("404", 404)],
[['id' => 47, 'type' => null, 'data' => "< svg / > "], new ErrorResponse ("404", 404)],
[['id' => 47, 'type' => null, 'data' => null], new ErrorResponse ("404", 404)],
[null, new ErrorResponse ("404", 404)],
[new ExceptionInput("subjectMissing"), new ErrorResponse ("404", 404)],
[['id' => 47, 'type' => "", 'data' => "< svg / > "], V1::respError ("404", 404)],
[['id' => 47, 'type' => null, 'data' => "< svg / > "], V1::respError ("404", 404)],
[['id' => 47, 'type' => null, 'data' => null], V1::respError ("404", 404)],
[null, V1::respError ("404", 404)],
[new ExceptionInput("subjectMissing"), V1::respError ("404", 404)],
];
}
@ -743,17 +742,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$c = (new Context)->limit(100);
$o = ["modified_date"]; // the default sort order
return [
["/entries?after=A", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "after"], 400)],
["/entries?before=B", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "before"], 400)],
["/entries?category_id=0", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "category_id"], 400)],
["/entries?after_entry_id=0", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "after_entry_id"], 400)],
["/entries?before_entry_id=0", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "before_entry_id"], 400)],
["/entries?limit=-1", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "limit"], 400)],
["/entries?offset=-1", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "offset"], 400)],
["/entries?direction=sideways", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "direction"], 400)],
["/entries?order=false", null, null, [], false, new ErrorResponse (["InvalidInputValue", 'field' => "order"], 400)],
["/entries?starred& starred", null, null, [], false, new ErrorResponse (["DuplicateInputValue", 'field' => "starred"], 400)],
["/entries?after& after=0", null, null, [], false, new ErrorResponse (["DuplicateInputValue", 'field' => "after"], 400)],
["/entries?after=A", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "after"], 400)],
["/entries?before=B", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "before"], 400)],
["/entries?category_id=0", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "category_id"], 400)],
["/entries?after_entry_id=0", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "after_entry_id"], 400)],
["/entries?before_entry_id=0", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "before_entry_id"], 400)],
["/entries?limit=-1", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "limit"], 400)],
["/entries?offset=-1", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "offset"], 400)],
["/entries?direction=sideways", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "direction"], 400)],
["/entries?order=false", null, null, [], false, V1::respError (["InvalidInputValue", 'field' => "order"], 400)],
["/entries?starred& starred", null, null, [], false, V1::respError (["DuplicateInputValue", 'field' => "starred"], 400)],
["/entries?after& after=0", null, null, [], false, V1::respError (["DuplicateInputValue", 'field' => "after"], 400)],
["/entries", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=47", (clone $c)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=1", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
@ -790,15 +789,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
["/entries?order=category_id& direction=desc", $c, ["top_folder desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=category_title& direction=desc", $c, ["top_folder_name desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=status& direction=desc", $c, ["hidden desc", "unread"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse ("MissingCategory")],
["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError ("MissingCategory")],
["/feeds/42/entries", (clone $c)->subscription(42), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/feeds/42/entries?category_id=47", (clone $c)->subscription(42)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, new ErrorResponse ("404", 404)],
["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, V1::respError ("404", 404)],
["/categories/42/entries", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/42/entries?category_id=47", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/42/entries?starred", (clone $c)->folder(41)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/1/entries", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse ("404", 404)],
["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError ("404", 404)],
];
}
@ -828,15 +827,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$c = new Context;
return [
["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse ("404", 404)],
["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), V1::respError ("404", 404)],
["/feeds/47/entries/42", (clone $c)->subscription(47)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], new ErrorResponse ("404", 404)],
["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse ("404", 404)],
["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), new ErrorResponse ("404", 404)],
["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], V1::respError ("404", 404)],
["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), V1::respError ("404", 404)],
["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), V1::respError ("404", 404)],
["/categories/47/entries/42", (clone $c)->folder(46)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], new ErrorResponse ("404", 404)],
["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse ("404", 404)],
["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), new ErrorResponse ("404", 404)],
["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], V1::respError ("404", 404)],
["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), V1::respError ("404", 404)],
["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), V1::respError ("404", 404)],
["/categories/1/entries/42", (clone $c)->folderShallow(0)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
];
}
@ -855,14 +854,14 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideEntryMarkings(): iterable {
self::clearData();
return [
[['status' => "read"], null, new ErrorResponse (["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1]], null, new ErrorResponse (["MissingInputValue", 'field' => "status"], 422)],
[['entry_ids' => [], 'status' => "read"], null, new ErrorResponse (["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => 1, 'status' => "read"], null, new ErrorResponse (["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)],
[['entry_ids' => ["1"], 'status' => "read"], null, new ErrorResponse (["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 422)],
[['entry_ids' => [1], 'status' => 1], null, new ErrorResponse (["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)],
[['entry_ids' => [0], 'status' => "read"], null, new ErrorResponse (["InvalidInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1], 'status' => "reread"], null, new ErrorResponse (["InvalidInputValue", 'field' => "status"], 422)],
[['status' => "read"], null, V1::respError (["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1]], null, V1::respError (["MissingInputValue", 'field' => "status"], 422)],
[['entry_ids' => [], 'status' => "read"], null, V1::respError (["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => 1, 'status' => "read"], null, V1::respError (["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)],
[['entry_ids' => ["1"], 'status' => "read"], null, V1::respError (["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 422)],
[['entry_ids' => [1], 'status' => 1], null, V1::respError (["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)],
[['entry_ids' => [0], 'status' => "read"], null, V1::respError (["InvalidInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1], 'status' => "reread"], null, V1::respError (["InvalidInputValue", 'field' => "status"], 422)],
[['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], HTTP::respEmpty(204)],
@ -889,11 +888,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$c = (new Context)->hidden(false);
return [
["/users/42/mark-all-as-read", $c, 1123, HTTP::respEmpty(204)],
["/users/2112/mark-all-as-read", $c, null, new ErrorResponse ("403", 403)],
["/users/2112/mark-all-as-read", $c, null, V1::respError ("403", 403)],
["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, HTTP::respEmpty(204)],
["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), new ErrorResponse ("404", 404)],
["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), V1::respError ("404", 404)],
["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, HTTP::respEmpty(204)],
["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), new ErrorResponse ("404", 404)],
["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), V1::respError ("404", 404)],
["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, HTTP::respEmpty(204)],
];
}
@ -930,7 +929,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
return [
[1, true, HTTP::respEmpty(204)],
[0, false, HTTP::respEmpty(204)],
[new ExceptionInput("subjectMissing"), null, new ErrorResponse ("404", 404)],
[new ExceptionInput("subjectMissing"), null, V1::respError ("404", 404)],
];
}
@ -942,7 +941,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testRefreshAMissingFeed(): void {
$this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse ("404", 404), $this->req("PUT", "/feeds/2112/refresh"));
$this->assertMessage(V1::respError ("404", 404), $this->req("PUT", "/feeds/2112/refresh"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 2112);
}
@ -963,12 +962,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideImports(): iterable {
self::clearData();
return [
[new ImportException("invalidSyntax"), new ErrorResponse ("InvalidBodyXML", 400)],
[new ImportException("invalidSemantics"), new ErrorResponse ("InvalidBodyOPML", 422)],
[new ImportException("invalidFolderName"), new ErrorResponse ("InvalidImportCategory", 422)],
[new ImportException("invalidFolderCopy"), new ErrorResponse ("DuplicateImportCategory", 422)],
[new ImportException("invalidTagName"), new ErrorResponse ("InvalidImportLabel", 422)],
[new FeedException("invalidUrl", ['url' => "http://example.com/"]), new ErrorResponse (["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)],
[new ImportException("invalidSyntax"), V1::respError ("InvalidBodyXML", 400)],
[new ImportException("invalidSemantics"), V1::respError ("InvalidBodyOPML", 422)],
[new ImportException("invalidFolderName"), V1::respError ("InvalidImportCategory", 422)],
[new ImportException("invalidFolderCopy"), V1::respError ("DuplicateImportCategory", 422)],
[new ImportException("invalidTagName"), V1::respError ("InvalidImportLabel", 422)],
[new FeedException("invalidUrl", ['url' => "http://example.com/"]), V1::respError (["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)],
[true, HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")])],
];
}
@ -976,8 +975,8 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testExport(): void {
$opml = $this->mock(OPML::class);
$this->objMock->get->with(OPML::class)->returns($opml);
$opml->export->returns("EXPORT DATA ");
$this->assertMessage(new TextResponse("EXPORT DATA ", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export"));
$opml->export->returns("< EXPORT_DATA / > ");
$this->assertMessage(HTTP::respText("< EXPORT_DATA / > ", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export"));
$opml->export->calledWith(Arsse::$user->id);
}
}