Browse Source

PArtial rewrite of TT-RSS tests

rpm
J. King 3 years ago
parent
commit
e90aa585b2
  1. 497
      tests/cases/REST/TinyTinyRSS/TestAPI.php

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

@ -16,6 +16,7 @@ use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\TinyTinyRSS\API; use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse; use Laminas\Diactoros\Response\EmptyResponse;
@ -26,6 +27,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
protected const NOW = "2020-12-21T23:09:17.189065Z"; protected const NOW = "2020-12-21T23:09:17.189065Z";
protected $h; protected $h;
protected $userId = "john.doe@example.com";
protected $folders = [ protected $folders = [
['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"], ['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"],
['id' => 6, 'parent' => 3, 'children' => 0, 'feeds' => 2, 'name' => "National"], ['id' => 6, 'parent' => 3, 'children' => 0, 'feeds' => 2, 'name' => "National"],
@ -127,7 +129,33 @@ LONG_STRING;
return $value; return $value;
} }
public function setUp(): void {
parent::setUp();
self::setConf();
// create mock timestamps
$this->objMock->get->with(\DateTimeImmutable::class)->returns(new \DateTimeImmutable(self::NOW));
// create a mock user manager
$this->userId = "john.doe@example.com";
$this->userMock = $this->mock(User::class);
$this->userMock->auth->returns(true);
// create a mock database interface
$this->dbMock = $this->mock(Database::class);
$this->dbMock->begin->returns($this->mock(Transaction::class));
$this->dbMock->sessionResume->throws(new \JKingWeb\Arsse\User\ExceptionSession("invalid"));
$this->dbMock->sessionResume->with("PriestsOfSyrinx")->returns([
'id' => "PriestsOfSyrinx",
'created' => "2000-01-01 00:00:00",
'expires' => "2112-12-21 21:12:00",
'user' => $this->userId,
]);
$this->h = new API();
}
protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface { protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
Arsse::$obj = $this->objMock->get();
Arsse::$db = $this->dbMock->get();
Arsse::$user = $this->userMock->get();
Arsse::$user->id = $this->userId;
$prefix = "/tt-rss/api"; $prefix = "/tt-rss/api";
$url = $prefix.$target; $url = $prefix.$target;
$body = $strData ?? json_encode($data); $body = $strData ?? json_encode($data);
@ -156,30 +184,6 @@ LONG_STRING;
]); ]);
} }
public function setUp(): void {
self::clearData();
self::setConf();
// create a mock user manager
Arsse::$user = \Phake::mock(User::class);
\Phake::when(Arsse::$user)->auth->thenReturn(true);
Arsse::$user->id = "john.doe@example.com";
// create a mock database interface
Arsse::$db = \Phake::mock(Database::class);
\Phake::when(Arsse::$db)->begin->thenReturn(\Phake::mock(Transaction::class));
\Phake::when(Arsse::$db)->sessionResume->thenThrow(new \JKingWeb\Arsse\User\ExceptionSession("invalid"));
\Phake::when(Arsse::$db)->sessionResume("PriestsOfSyrinx")->thenReturn([
'id' => "PriestsOfSyrinx",
'created' => "2000-01-01 00:00:00",
'expires' => "2112-12-21 21:12:00",
'user' => Arsse::$user->id,
]);
$this->h = new API();
}
public function tearDown(): void {
self::clearData();
}
public function testHandleInvalidPaths(): void { public function testHandleInvalidPaths(): void {
$exp = $this->respErr("MALFORMED_INPUT", [], null); $exp = $this->respErr("MALFORMED_INPUT", [], null);
$this->assertMessage($exp, $this->req(null, "POST", "", "")); $this->assertMessage($exp, $this->req(null, "POST", "", ""));
@ -205,13 +209,13 @@ LONG_STRING;
/** @dataProvider provideLoginRequests */ /** @dataProvider provideLoginRequests */
public function testLogIn(array $conf, $httpUser, array $data, $sessions): void { public function testLogIn(array $conf, $httpUser, array $data, $sessions): void {
Arsse::$user->id = null; $this->userId = null;
self::setConf($conf); self::setConf($conf);
\Phake::when(Arsse::$user)->auth->thenReturn(false); $this->userMock->auth->returns(false);
\Phake::when(Arsse::$user)->auth("john.doe@example.com", "secret")->thenReturn(true); $this->userMock->auth->with("john.doe@example.com", "secret")->returns(true);
\Phake::when(Arsse::$user)->auth("jane.doe@example.com", "superman")->thenReturn(true); $this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true);
\Phake::when(Arsse::$db)->sessionCreate("john.doe@example.com")->thenReturn("PriestsOfSyrinx")->thenReturn("SolarFederation"); $this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation");
\Phake::when(Arsse::$db)->sessionCreate("jane.doe@example.com")->thenReturn("ClockworkAngels")->thenReturn("SevenCitiesOfGold"); $this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold");
if ($sessions instanceof EmptyResponse) { if ($sessions instanceof EmptyResponse) {
$exp1 = $sessions; $exp1 = $sessions;
$exp2 = $sessions; $exp2 = $sessions;
@ -230,7 +234,7 @@ LONG_STRING;
} }
$this->assertMessage($exp2, $this->reqAuth($data, $httpUser)); $this->assertMessage($exp2, $this->reqAuth($data, $httpUser));
// logging in should never try to resume a session // logging in should never try to resume a session
\Phake::verify(Arsse::$db, \Phake::times(0))->sessionResume($this->anything()); $this->dbMock->sessionResume->never()->called();
} }
public function provideLoginRequests(): iterable { public function provideLoginRequests(): iterable {
@ -239,15 +243,15 @@ LONG_STRING;
/** @dataProvider provideResumeRequests */ /** @dataProvider provideResumeRequests */
public function testValidateASession(array $conf, $httpUser, string $data, $result): void { public function testValidateASession(array $conf, $httpUser, string $data, $result): void {
Arsse::$user->id = null; $this->userId = null;
self::setConf($conf); self::setConf($conf);
\Phake::when(Arsse::$db)->sessionResume("PriestsOfSyrinx")->thenReturn([ $this->dbMock->sessionResume->with("PriestsOfSyrinx")->returns([
'id' => "PriestsOfSyrinx", 'id' => "PriestsOfSyrinx",
'created' => "2000-01-01 00:00:00", 'created' => "2000-01-01 00:00:00",
'expires' => "2112-12-21 21:12:00", 'expires' => "2112-12-21 21:12:00",
'user' => "john.doe@example.com", 'user' => "john.doe@example.com",
]); ]);
\Phake::when(Arsse::$db)->sessionResume("ClockworkAngels")->thenReturn([ $this->dbMock->sessionResume->with("ClockworkAngels")->returns([
'id' => "ClockworkAngels", 'id' => "ClockworkAngels",
'created' => "2000-01-01 00:00:00", 'created' => "2000-01-01 00:00:00",
'expires' => "2112-12-21 21:12:00", 'expires' => "2112-12-21 21:12:00",
@ -523,10 +527,10 @@ LONG_STRING;
} }
public function testHandleGenericError(): void { public function testHandleGenericError(): void {
\Phake::when(Arsse::$user)->auth(Arsse::$user->id, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionTimeout("general")); $this->userMock->auth->throws(new \JKingWeb\Arsse\Db\ExceptionTimeout("general"));
$data = [ $data = [
'op' => "login", 'op' => "login",
'user' => Arsse::$user->id, 'user' => $this->userId,
'password' => "secret", 'password' => "secret",
]; ];
$exp = new EmptyResponse(500); $exp = new EmptyResponse(500);
@ -534,14 +538,14 @@ LONG_STRING;
} }
public function testLogOut(): void { public function testLogOut(): void {
\Phake::when(Arsse::$db)->sessionDestroy->thenReturn(true); $this->dbMock->sessionDestroy->returns(true);
$data = [ $data = [
'op' => "logout", 'op' => "logout",
'sid' => "PriestsOfSyrinx", 'sid' => "PriestsOfSyrinx",
]; ];
$exp = $this->respGood(['status' => "OK"]); $exp = $this->respGood(['status' => "OK"]);
$this->assertMessage($exp, $this->req($data)); $this->assertMessage($exp, $this->req($data));
\Phake::verify(Arsse::$db)->sessionDestroy(Arsse::$user->id, "PriestsOfSyrinx"); $this->dbMock->sessionDestroy->calledWith($this->userId, "PriestsOfSyrinx");
} }
public function testHandleUnknownMethods(): void { public function testHandleUnknownMethods(): void {
@ -589,245 +593,155 @@ LONG_STRING;
$this->assertMessage($exp, $this->req($data)); $this->assertMessage($exp, $this->req($data));
} }
public function testAddACategory(): void { /** @dataProvider provideCategoryAdditions */
$in = [ public function testAddACategory(array $in, array $data, $out, ResponseInterface $exp): void {
['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Software"], $in = array_merge(['op' => "addCategory", 'sid' => "PriestsOfSyrinx"], $in);
['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Hardware", 'parent_id' => 1], $action = ($out instanceof \Exception) ? "throws" : "returns";
['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Hardware", 'parent_id' => 2112], $this->dbMock->folderAdd->$action($out);
['op' => "addCategory", 'sid' => "PriestsOfSyrinx"], $this->dbMock->folderList->with("~", null, false)->returns(new Result($this->v([
['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => ""],
['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => " "],
];
$db = [
['name' => "Software", 'parent' => null],
['name' => "Hardware", 'parent' => 1],
['name' => "Hardware", 'parent' => 2112],
];
$out = [
['id' => 2, 'name' => "Software", 'parent' => null], ['id' => 2, 'name' => "Software", 'parent' => null],
['id' => 3, 'name' => "Hardware", 'parent' => 1],
['id' => 1, 'name' => "Politics", 'parent' => null], ['id' => 1, 'name' => "Politics", 'parent' => null],
]; ])));
// set of various mocks for testing $this->dbMock->folderList->with("~", 1, false)->returns(new Result($this->v([
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call ['id' => 3, 'name' => "Hardware", 'parent' => 1],
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call ])));
\Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result($this->v([$out[0], $out[2]]))); $this->assertMessage($exp, $this->req($in));
\Phake::when(Arsse::$db)->folderList(Arsse::$user->id, 1, false)->thenReturn(new Result($this->v([$out[1]]))); $this->dbMock->folderAdd->calledWith($this->userId, $data);
// set up mocks that produce errors if (!$out instanceof \Exception) {
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[2])->thenThrow(new ExceptionInput("idMissing")); // parent folder does not exist $this->dbMock->folderList->never()->called();
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing")); }
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => "", 'parent' => null])->thenThrow(new ExceptionInput("missing"));
\Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => " ", 'parent' => null])->thenThrow(new ExceptionInput("whitespace"));
// correctly add two folders
$exp = $this->respGood("2");
$this->assertMessage($exp, $this->req($in[0]));
$exp = $this->respGood("3");
$this->assertMessage($exp, $this->req($in[1]));
// attempt to add the two folders again
$exp = $this->respGood("2");
$this->assertMessage($exp, $this->req($in[0]));
$exp = $this->respGood("3");
$this->assertMessage($exp, $this->req($in[1]));
\Phake::verify(Arsse::$db)->folderList(Arsse::$user->id, null, false);
\Phake::verify(Arsse::$db)->folderList(Arsse::$user->id, 1, false);
// add a folder to a missing parent (silently fails)
$exp = $this->respGood(false);
$this->assertMessage($exp, $this->req($in[2]));
// add some invalid folders
$exp = $this->respErr("INCORRECT_USAGE");
$this->assertMessage($exp, $this->req($in[3]));
$this->assertMessage($exp, $this->req($in[4]));
$this->assertMessage($exp, $this->req($in[5]));
} }
public function testRemoveACategory(): void { public function provideCategoryAdditions(): iterable {
$in = [ return [
['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42], [[], ['name' => null, 'parent' => null], new ExceptionInput("missing"), $this->respErr("INCORRECT_USAGE")],
['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112], [['caption' => ""], ['name' => "", 'parent' => null], new ExceptionInput("missing"), $this->respErr("INCORRECT_USAGE")],
['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1], [['caption' => " "], ['name' => " ", 'parent' => null], new ExceptionInput("whitespace"), $this->respErr("INCORRECT_USAGE")],
[['caption' => "Software"], ['name' => "Software", 'parent' => null], 2, $this->respGood("2")],
[['caption' => "Hardware", 'parent_id' => 1], ['name' => "Hardware", 'parent' => 1], 3, $this->respGood("3")],
[['caption' => "Hardware", 'parent_id' => 2112], ['name' => "Hardware", 'parent' => 2112], new ExceptionInput("idMissing"), $this->respGood(false)],
[['caption' => "Software"], ['name' => "Software", 'parent' => null], new ExceptionInput("constraintViolation"), $this->respGood("2")],
[['caption' => "Hardware", 'parent_id' => 1], ['name' => "Hardware", 'parent' => 1], new ExceptionInput("constraintViolation"), $this->respGood("3")],
]; ];
\Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, 42)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
// succefully delete a folder
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[0]));
// try deleting it again (this should silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[0]));
// delete a folder which does not exist (this should also silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[1]));
// delete an invalid folder (causes an error)
$exp = $this->respErr("INCORRECT_USAGE");
$this->assertMessage($exp, $this->req($in[2]));
\Phake::verify(Arsse::$db, \Phake::times(3))->folderRemove(Arsse::$user->id, $this->anything());
} }
public function testMoveACategory(): void { /** @dataProvider provideCategoryRemovals */
$in = [ public function testRemoveACategory(array $in, ?int $data, $out, ResponseInterface $exp): void {
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 1], $in = array_merge(['op' => "removeCategory", 'sid' => "PriestsOfSyrinx"], $in);
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112, 'parent_id' => 2], $action = ($out instanceof \Exception) ? "throws" : "returns";
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 0], $this->dbMock->folderRemove->$action($out);
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 47], $this->assertMessage($exp, $this->req($in));
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1, 'parent_id' => 1], if ($data > 0) {
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => -1], $this->dbMock->folderRemove->calledWith($this->userId, (int) $data);
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42], }
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'parent_id' => -1],
['op' => "moveCategory", 'sid' => "PriestsOfSyrinx"],
];
$db = [
[Arsse::$user->id, 42, ['parent' => 1]],
[Arsse::$user->id, 2112, ['parent' => 2]],
[Arsse::$user->id, 42, ['parent' => 0]],
[Arsse::$user->id, 42, ['parent' => 47]],
[Arsse::$user->id, -1, ['parent' => 1]],
[Arsse::$user->id, 42, ['parent' => -1]],
[Arsse::$user->id, 42, ['parent' => 0]],
[Arsse::$user->id, 0, ['parent' => -1]],
[Arsse::$user->id, 0, ['parent' => 0]],
];
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[0])->thenReturn(true);
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[3])->thenThrow(new ExceptionInput("idMissing"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[4])->thenThrow(new ExceptionInput("typeViolation"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[5])->thenThrow(new ExceptionInput("typeViolation"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[6])->thenThrow(new ExceptionInput("constraintViolation"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[7])->thenThrow(new ExceptionInput("typeViolation"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[8])->thenThrow(new ExceptionInput("typeViolation"));
// succefully move a folder
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[0]));
// move a folder which does not exist (this should silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[1]));
// move a folder causing a duplication (this should also silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[2]));
$this->assertMessage($exp, $this->req($in[3]));
$this->assertMessage($exp, $this->req($in[6]));
// all the rest should cause errors
$exp = $this->respErr("INCORRECT_USAGE");
$this->assertMessage($exp, $this->req($in[4]));
$this->assertMessage($exp, $this->req($in[5]));
$this->assertMessage($exp, $this->req($in[7]));
$this->assertMessage($exp, $this->req($in[8]));
\Phake::verify(Arsse::$db, \Phake::times(5))->folderPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
} }
public function testRenameACategory(): void { public function provideCategoryRemovals(): iterable {
$in = [ return [
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => "Ook"], [['category_id' => 42], 42, true, $this->respGood()],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112, 'caption' => "Eek"], [['category_id' => 2112], 2112, new ExceptionInput("subjectMissing"), $this->respGood()],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => "Eek"], [[], null, null, $this->respErr("INCORRECT_USAGE")],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => ""], [['category_id' => -1], null, null, $this->respErr("INCORRECT_USAGE")],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => " "],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1, 'caption' => "Ook"],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Ook"],
['op' => "renameCategory", 'sid' => "PriestsOfSyrinx"],
];
$db = [
[Arsse::$user->id, 42, ['name' => "Ook"]],
[Arsse::$user->id, 2112, ['name' => "Eek"]],
[Arsse::$user->id, 42, ['name' => "Eek"]],
]; ];
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[0])->thenReturn(true);
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->folderPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
// succefully rename a folder
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[0]));
// rename a folder which does not exist (this should silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[1]));
// rename a folder causing a duplication (this should also silently fail)
$exp = $this->respGood();
$this->assertMessage($exp, $this->req($in[2]));
// all the rest should cause errors
$exp = $this->respErr("INCORRECT_USAGE");
$this->assertMessage($exp, $this->req($in[3]));
$this->assertMessage($exp, $this->req($in[4]));
$this->assertMessage($exp, $this->req($in[5]));
$this->assertMessage($exp, $this->req($in[6]));
$this->assertMessage($exp, $this->req($in[7]));
$this->assertMessage($exp, $this->req($in[8]));
\Phake::verify(Arsse::$db, \Phake::times(3))->folderPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
} }
public function testAddASubscription(): void { /** @dataProvider provideCategoryMoves */
$in = [ public function testMoveACategory(array $in, array $data, $out, ResponseInterface $exp): void {
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/0"], $in = array_merge(['op' => "moveCategory", 'sid' => "PriestsOfSyrinx"], $in);
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/1", 'category_id' => 42], $action = ($out instanceof \Exception) ? "throws" : "returns";
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/2", 'category_id' => 2112], $this->dbMock->folderPropertiesSet->$action($out);
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/3"], $this->assertMessage($exp, $this->req($in));
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://localhost:8000/Feed/Discovery/Valid"], if ($out !== null) {
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://localhost:8000/Feed/Discovery/Invalid"], $this->dbMock->folderPropertiesSet->calledWith(...$data);
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/6"], } else {
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/7"], $this->dbMock->folderPropertiesSet->never()->called();
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/8", 'category_id' => 47], }
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/9", 'category_id' => 1], }
// these don't even query the database as the input is syntactically invalid
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx"], public function provideCategoryMoves(): iterable {
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'login' => []], return [
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'login' => "", 'password' => []], [['category_id' => 42, 'parent_id' => 1], [$this->userId, 42, ['parent' => 1]], true, $this->respGood()],
['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'category_id' => -1], [['category_id' => 2112, 'parent_id' => 2], [$this->userId, 2112, ['parent' => 2]], new ExceptionInput("subjectMissing"), $this->respGood()],
]; [['category_id' => 42, 'parent_id' => 0], [$this->userId, 42, ['parent' => 0]], new ExceptionInput("constraintViolation"), $this->respGood()],
$db = [ [['category_id' => 42, 'parent_id' => 47], [$this->userId, 42, ['parent' => 47]], new ExceptionInput("idMissing"), $this->respGood()],
[Arsse::$user->id, "http://example.com/0", "", ""], [['category_id' => -1, 'parent_id' => 1], [$this->userId, -1, ['parent' => 1]], null, $this->respErr("INCORRECT_USAGE")],
[Arsse::$user->id, "http://example.com/1", "", ""], [['category_id' => 42, 'parent_id' => -1], [$this->userId, 42, ['parent' => -1]], null, $this->respErr("INCORRECT_USAGE")],
[Arsse::$user->id, "http://example.com/2", "", ""], [['category_id' => 42], [$this->userId, 42, ['parent' => 0]], new ExceptionInput("constraintViolation"), $this->respGood()],
[Arsse::$user->id, "http://example.com/3", "", ""], [['parent_id' => -1], [$this->userId, 0, ['parent' => -1]], null, $this->respErr("INCORRECT_USAGE")],
[Arsse::$user->id, "http://localhost:8000/Feed/Discovery/Valid", "", ""], [[], [$this->userId, 0, ['parent' => 0]], null, $this->respErr("INCORRECT_USAGE")],
[Arsse::$user->id, "http://localhost:8000/Feed/Discovery/Invalid", "", ""],
[Arsse::$user->id, "http://example.com/6", "", ""],
[Arsse::$user->id, "http://example.com/7", "", ""],
[Arsse::$user->id, "http://example.com/8", "", ""],
[Arsse::$user->id, "http://example.com/9", "", ""],
]; ];
$out = [ }
['code' => 1, 'feed_id' => 2],
['code' => 5, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/1"], $this->mockGuzzleException(ClientException::class, "", 401)))->getMessage()], /** @dataProvider provideCategoryRenamings */
['code' => 1, 'feed_id' => 0], public function testRenameACategory(array $in, ?array $data, $out, ResponseInterface $exp): void {
['code' => 0, 'feed_id' => 3], $in = array_merge(['op' => "renameCategory", 'sid' => "PriestsOfSyrinx"], $in);
['code' => 0, 'feed_id' => 1], $action = ($out instanceof \Exception) ? "throws" : "returns";
['code' => 3, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://localhost:8000/Feed/Discovery/Invalid"], new \PicoFeed\Reader\SubscriptionNotFoundException()))->getMessage()], $this->dbMock->folderPropertiesSet->$action($out);
['code' => 2, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/6"], $this->mockGuzzleException(ClientException::class, "", 404)))->getMessage()], $this->assertMessage($exp, $this->req($in));
['code' => 6, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/7"], new \PicoFeed\Parser\MalformedXmlException()))->getMessage()], if ($out !== null) {
['code' => 1, 'feed_id' => 4], $this->dbMock->folderPropertiesSet->calledWith(...$data);
['code' => 0, 'feed_id' => 4], } else {
$this->dbMock->folderPropertiesSet->never()->called();
}
}
public function provideCategoryRenamings(): iterable {
return [
[['category_id' => 42, 'caption' => "Ook"], [$this->userId, 42, ['name' => "Ook"]], true, $this->respGood()],
[['category_id' => 2112, 'caption' => "Eek"], [$this->userId, 2112, ['name' => "Eek"]], new ExceptionInput("subjectMissing"), $this->respGood()],
[['category_id' => 42, 'caption' => "Eek"], [$this->userId, 42, ['name' => "Eek"]], new ExceptionInput("constraintViolation"), $this->respGood()],
[['category_id' => 42, 'caption' => ""], null, null, $this->respErr("INCORRECT_USAGE")],
[['category_id' => 42, 'caption' => " "], null, null, $this->respErr("INCORRECT_USAGE")],
[['category_id' => -1, 'caption' => "Ook"], null, null, $this->respErr("INCORRECT_USAGE")],
[['category_id' => 42], null, null, $this->respErr("INCORRECT_USAGE")],
[['caption' => "Ook"], null, null, $this->respErr("INCORRECT_USAGE")],
[[], null, null, $this->respErr("INCORRECT_USAGE")],
]; ];
}
/** @dataProvider provideSubscriptionAdditions */
public function testAddASubscription(array $in, ?array $data, $out, ResponseInterface $exp): void {
$in = array_merge(['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx"], $in);
$action = ($out instanceof \Exception) ? "throws" : "returns";
$list = [ $list = [
['id' => 1, 'url' => "http://localhost:8000/Feed/Discovery/Feed"], ['id' => 1, 'url' => "http://localhost:8000/Feed/Discovery/Feed"],
['id' => 2, 'url' => "http://example.com/0"], ['id' => 2, 'url' => "http://example.com/0"],
['id' => 3, 'url' => "http://example.com/3"], ['id' => 3, 'url' => "http://example.com/3"],
['id' => 4, 'url' => "http://example.com/9"], ['id' => 4, 'url' => "http://example.com/9"],
]; ];
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[0])->thenReturn(2); $this->dbMock->subscriptionAdd->$action($out);
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[1])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/1"], $this->mockGuzzleException(ClientException::class, "", 401))); $this->dbMock->folderPropertiesGet->with($this->userId, 42)->returns($this->v(['id' => 42]));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[2])->thenReturn(2); $this->dbMock->folderPropertiesGet->with($this->userId, 47)->returns($this->v(['id' => 47]));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[3])->thenThrow(new ExceptionInput("constraintViolation")); $this->dbMock->folderPropertiesGet->with($this->userId, 2112)->throws(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[4])->thenThrow(new ExceptionInput("constraintViolation")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, "*")->returns(true);
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[5])->thenThrow(new ExceptionInput("constraintViolation")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 4, "~")->throws(new ExceptionInput("idMissing"));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[6])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/6"], $this->mockGuzzleException(ClientException::class, "", 404))); $this->dbMock->subscriptionList->with($this->userId)->returns(new Result($this->v($list)));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/7"], new \PicoFeed\Parser\MalformedXmlException())); $this->assertMessage($exp, $this->req($in));
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[8])->thenReturn(4); if ($data !== null) {
\Phake::when(Arsse::$db)->subscriptionAdd(...$db[9])->thenThrow(new ExceptionInput("constraintViolation")); $this->dbMock->subscriptionAdd->calledWith(...$data);
\Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->v(['id' => 42])); } else {
\Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->v(['id' => 47])); $this->dbMock->subscriptionAdd->never()->called();
\Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2112)->thenThrow(new ExceptionInput("subjectMissing"));
\Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything())->thenReturn(true);
\Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 4, $this->anything())->thenThrow(new ExceptionInput("idMissing"));
\Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v($list)));
for ($a = 0; $a < (sizeof($in) - 4); $a++) {
$exp = $this->respGood($out[$a]);
$this->assertMessage($exp, $this->req($in[$a]), "Failed test $a");
}
$exp = $this->respErr("INCORRECT_USAGE");
for ($a = (sizeof($in) - 4); $a < sizeof($in); $a++) {
$this->assertMessage($exp, $this->req($in[$a]), "Failed test $a");
} }
\Phake::verify(Arsse::$db, \Phake::times(0))->subscriptionPropertiesSet(Arsse::$user->id, 4, ['folder' => 1]); $this->dbMock->subscriptionPropertiesSet->never()->calledWith($this->userId, 4, ['folder' => 1]);
}
public function provideSubscriptionAdditions(): iterable {
return [
[['feed_url' => "http://example.com/0"], [$this->userId, "http://example.com/0", "", ""], 2, $this->respGood(['code' => 1, 'feed_id' => 2])],
[['feed_url' => "http://example.com/1", 'category_id' => 42], [$this->userId, "http://example.com/1", "", ""], new FeedException("unauthorized"), $this->respGood(['code' => 5, 'message' => (new FeedException("unauthorized"))->getMessage()])],
[['feed_url' => "http://example.com/2", 'category_id' => 2112], null, null, $this->respGood(['code' => 1, 'feed_id' => 0])],
[['feed_url' => "http://example.com/3"], [$this->userId, "http://example.com/3", "", ""], new ExceptionInput("constraintViolation"), $this->respGood(['code' => 0, 'feed_id' => 3])],
[['feed_url' => "http://localhost:8000/Feed/Discovery/Valid"], [$this->userId, "http://localhost:8000/Feed/Discovery/Valid", "", ""], new ExceptionInput("constraintViolation"), $this->respGood(['code' => 0, 'feed_id' => 1])],
[['feed_url' => "http://localhost:8000/Feed/Discovery/Invalid"], [$this->userId, "http://localhost:8000/Feed/Discovery/Invalid", "", ""], new ExceptionInput("constraintViolation"), $this->respGood(['code' => 3, 'message' => (new FeedException("subscriptionNotFound", ['url' => "http://localhost:8000/Feed/Discovery/Invalid"]))->getMessage()])],
[['feed_url' => "http://example.com/6"], [$this->userId, "http://example.com/6", "", ""], new FeedException("invalidUrl"), $this->respGood(['code' => 2, 'message' => (new FeedException("invalidUrl"))->getMessage()])],
[['feed_url' => "http://example.com/7"], [$this->userId, "http://example.com/7", "", ""], new FeedException("malformedXml"), $this->respGood(['code' => 6, 'message' => (new FeedException("malformedXml"))->getMessage()])],
[['feed_url' => "http://example.com/8", 'category_id' => 47], [$this->userId, "http://example.com/8", "", ""], 4, $this->respGood(['code' => 1, 'feed_id' => 4])],
[['feed_url' => "http://example.com/9", 'category_id' => 1], [$this->userId, "http://example.com/9", "", ""], new ExceptionInput("constraintViolation"), $this->respGood(['code' => 0, 'feed_id' => 4])],
[[], null, null, $this->respErr("INCORRECT_USAGE")],
[['feed_url' => "http://example.com/", 'login' => []], null, null, $this->respErr("INCORRECT_USAGE")],
[['feed_url' => "http://example.com/", 'login' => "", 'password' => []], null, null, $this->respErr("INCORRECT_USAGE")],
[['feed_url' => "http://example.com/", 'category_id' => -1], null, null, $this->respErr("INCORRECT_USAGE")],
];
} }
public function testRemoveASubscription(): void { public function testRemoveASubscription(): void {
@ -938,7 +852,7 @@ LONG_STRING;
public function testRetrieveTheGlobalUnreadCount(): void { public function testRetrieveTheGlobalUnreadCount(): void {
$in = ['op' => "getUnread", 'sid' => "PriestsOfSyrinx"]; $in = ['op' => "getUnread", 'sid' => "PriestsOfSyrinx"];
\Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v([ $this->dbMock->subscriptionList->returns(new Result($this->v([
['id' => 1, 'unread' => 2112], ['id' => 1, 'unread' => 2112],
['id' => 2, 'unread' => 42], ['id' => 2, 'unread' => 42],
['id' => 3, 'unread' => 47], ['id' => 3, 'unread' => 47],
@ -952,14 +866,10 @@ LONG_STRING;
$interval = Arsse::$conf->serviceFrequency; $interval = Arsse::$conf->serviceFrequency;
$valid = (new \DateTimeImmutable("now", new \DateTimezone("UTC")))->sub($interval); $valid = (new \DateTimeImmutable("now", new \DateTimezone("UTC")))->sub($interval);
$invalid = $valid->sub($interval)->sub($interval); $invalid = $valid->sub($interval)->sub($interval);
\Phake::when(Arsse::$db)->metaGet("service_last_checkin")->thenReturn(Date::transform($valid, "sql"))->thenReturn(Date::transform($invalid, "sql")); $this->dbMock->metaGet->with("service_last_checkin")->returns(Date::transform($valid, "sql"))->returns(Date::transform($invalid, "sql"));
\Phake::when(Arsse::$db)->subscriptionCount(Arsse::$user->id)->thenReturn(12)->thenReturn(2); $this->dbMock->subscriptionCount->with($this->userId)->returns(12, 2);
$exp = [ $this->assertMessage($this->respGood(['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => true, 'num_feeds' => 12]), $this->req($in));
['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => true, 'num_feeds' => 12], $this->assertMessage($this->respGood(['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => false, 'num_feeds' => 2]), $this->req($in));
['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => false, 'num_feeds' => 2],
];
$this->assertMessage($this->respGood($exp[0]), $this->req($in));
$this->assertMessage($this->respGood($exp[1]), $this->req($in));
} }
public function testUpdateAFeed(): void { public function testUpdateAFeed(): void {
@ -1176,11 +1086,11 @@ LONG_STRING;
public function testRetrieveCounterList(): void { public function testRetrieveCounterList(): void {
$in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"]; $in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"];
\Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders))); $this->dbMock->folderList->returns(new Result($this->v($this->folders)));
\Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions))); $this->dbMock->subscriptionList->returns(new Result($this->v($this->subscriptions)));
\Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); $this->dbMock->labelList->with("~", false)->returns(new Result($this->v($this->usedLabels)));
\Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); $this->dbMock->articleCount->returns(7);
\Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); $this->dbMock->articleStarred->returns($this->v($this->starred));
$exp = [ $exp = [
['id' => "global-unread", 'counter' => 35], ['id' => "global-unread", 'counter' => 35],
['id' => "subscribed-feeds", 'counter' => 6], ['id' => "subscribed-feeds", 'counter' => 6],
@ -1207,6 +1117,7 @@ LONG_STRING;
['id' => -2, 'kind' => "cat", 'counter' => 6], ['id' => -2, 'kind' => "cat", 'counter' => 6],
]; ];
$this->assertMessage($this->respGood($exp), $this->req($in)); $this->assertMessage($this->respGood($exp), $this->req($in));
$this->dbMock->articleCount->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW))));
} }
public function testRetrieveTheLabelList(): void { public function testRetrieveTheLabelList(): void {
@ -1297,31 +1208,32 @@ LONG_STRING;
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx", 'include_empty' => true], ['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx"], ['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx"],
]; ];
\Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders))); $this->dbMock->folderList->with("~", null, true)->returns(new Result($this->v($this->folders)));
\Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions))); $this->dbMock->subscriptionList->returns(new Result($this->v($this->subscriptions)));
\Phake::when(Arsse::$db)->labelList($this->anything(), true)->thenReturn(new Result($this->v($this->labels))); $this->dbMock->labelList->with("~", true)->returns(new Result($this->v($this->labels)));
\Phake::when(Arsse::$db)->articleCount($this->anything(), $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H")), 2))->thenReturn(7); $this->dbMock->articleCount->returns(7);
\Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred)); $this->dbMock->articleStarred->returns($this->v($this->starred));
// the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them // the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them
$exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Photography','id' => 'CAT:4','bare_id' => 4,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(0 feeds)','items' => []],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]]; $exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Photography','id' => 'CAT:4','bare_id' => 4,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(0 feeds)','items' => []],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]];
$this->assertMessage($this->respGood($exp), $this->req($in[0])); $this->assertMessage($this->respGood($exp), $this->req($in[0]));
$exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]]; $exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]];
$this->assertMessage($this->respGood($exp), $this->req($in[1])); $this->assertMessage($this->respGood($exp), $this->req($in[1]));
$this->dbMock->articleCount->twice()->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW))));
} }
/** @dataProvider provideMassMarkings */ /** @dataProvider provideMassMarkings */
public function testMarkFeedsAsRead(array $in, ?Context $c): void { public function testMarkFeedsAsRead(array $in, ?Context $c): void {
$base = ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx"]; $base = ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx"];
$in = array_merge($base, $in); $in = array_merge($base, $in);
\Phake::when(Arsse::$db)->articleMark->thenThrow(new ExceptionInput("typeViolation")); $this->dbMock->articleMark->throws(new ExceptionInput("typeViolation"));
// create a mock-current time // create a mock-current time
\Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); $this->objMock->get->with(\DateTimeImmutable::class)->returns(new \DateTimeImmutable(self::NOW));
// TT-RSS always responds the same regardless of success or failure // TT-RSS always responds the same regardless of success or failure
$this->assertMessage($this->respGood(['status' => "OK"]), $this->req($in)); $this->assertMessage($this->respGood(['status' => "OK"]), $this->req($in));
if (isset($c)) { if (isset($c)) {
\Phake::verify(Arsse::$db)->articleMark(Arsse::$user->id, ['read' => true], $c); $this->dbMock->articleMark->calledWith(Arsse::$user->id, ['read' => true], $this->equalTo($c));
} else { } else {
\Phake::verify(Arsse::$db, \Phake::times(0))->articleMark; $this->dbMock->articleMark->never()->called();
} }
} }
@ -1691,24 +1603,21 @@ LONG_STRING;
public function testRetrieveHeadlines(bool $full, array $in, $out, Context $c, array $fields, array $order, ResponseInterface $exp): void { public function testRetrieveHeadlines(bool $full, array $in, $out, Context $c, array $fields, array $order, ResponseInterface $exp): void {
$base = ['op' => $full ? "getHeadlines" : "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"]; $base = ['op' => $full ? "getHeadlines" : "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"];
$in = array_merge($base, $in); $in = array_merge($base, $in);
\Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); $action = ($out instanceof \Exception) ? "throws" : "returns";
\Phake::when(Arsse::$db)->labelList->thenReturn(new Result($this->v($this->labels))); $this->objMock->get->with(\DateTimeImmutable::class)->returns(new \DateTimeImmutable(self::NOW));
\Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); $this->dbMock->labelList->returns(new Result($this->v($this->labels)));
\Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]); $this->dbMock->labelList->with("~", false)->returns(new Result($this->v($this->usedLabels)));
\Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3])); $this->dbMock->articleLabelsGet->returns([]);
\Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]); $this->dbMock->articleLabelsGet->with("~", 2112)->returns($this->v([1,3]));
\Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]); $this->dbMock->articleCategoriesGet->returns([]);
\Phake::when(Arsse::$db)->articleCount->thenReturn(2); $this->dbMock->articleCategoriesGet->with("~", 2112)->returns(["Boring","Illogical"]);
if ($out instanceof \Exception) { $this->dbMock->articleCount->returns(2);
\Phake::when(Arsse::$db)->articleList->thenThrow($out); $this->dbMock->articleList->$action($out);
} else {
\Phake::when(Arsse::$db)->articleList->thenReturn($out);
}
$this->assertMessage($exp, $this->req($in)); $this->assertMessage($exp, $this->req($in));
if ($out) { if ($out) {
\Phake::verify(Arsse::$db)->articleList(Arsse::$user->id, $c, $fields, $order); $this->dbMock->articleList->calledWith($this->userId, $this->equalTo($c), $fields, $order);
} else { } else {
\Phake::verify(Arsse::$db, \Phake::times(0))->articleList; $this->dbMock->articleList->never()->called();
} }
} }

Loading…
Cancel
Save