The clean & modern RSS server that doesn't give you any crap. https://thearsse.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TestAPI.php 136KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123
  1. <?php
  2. /** @license MIT
  3. * Copyright 2017 J. King, Dustin Wilson et al.
  4. * See LICENSE and AUTHORS files for details */
  5. declare(strict_types=1);
  6. namespace JKingWeb\Arsse\TestCase\REST\TinyTinyRSS;
  7. use JKingWeb\Arsse\Arsse;
  8. use JKingWeb\Arsse\Conf;
  9. use JKingWeb\Arsse\User;
  10. use JKingWeb\Arsse\Database;
  11. use JKingWeb\Arsse\Service;
  12. use JKingWeb\Arsse\REST\Request;
  13. use JKingWeb\Arsse\Test\Result;
  14. use JKingWeb\Arsse\Misc\Date;
  15. use JKingWeb\Arsse\Misc\Context;
  16. use JKingWeb\Arsse\Db\ExceptionInput;
  17. use JKingWeb\Arsse\Db\Transaction;
  18. use JKingWeb\Arsse\REST\TinyTinyRSS\API;
  19. use Psr\Http\Message\ResponseInterface;
  20. use Zend\Diactoros\ServerRequest;
  21. use Zend\Diactoros\Response\JsonResponse as Response;
  22. use Zend\Diactoros\Response\EmptyResponse;
  23. use Phake;
  24. /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
  25. * @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
  26. class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
  27. protected $h;
  28. protected $folders = [
  29. ['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"],
  30. ['id' => 6, 'parent' => 3, 'children' => 0, 'feeds' => 2, 'name' => "National"],
  31. ['id' => 4, 'parent' => null, 'children' => 0, 'feeds' => 0, 'name' => "Photography"],
  32. ['id' => 3, 'parent' => null, 'children' => 2, 'feeds' => 0, 'name' => "Politics"],
  33. ['id' => 2, 'parent' => 1, 'children' => 0, 'feeds' => 1, 'name' => "Rocketry"],
  34. ['id' => 1, 'parent' => null, 'children' => 1, 'feeds' => 1, 'name' => "Science"],
  35. ];
  36. protected $topFolders = [
  37. ['id' => 4, 'parent' => null, 'children' => 0, 'feeds' => 0, 'name' => "Photography"],
  38. ['id' => 3, 'parent' => null, 'children' => 2, 'feeds' => 0, 'name' => "Politics"],
  39. ['id' => 1, 'parent' => null, 'children' => 1, 'feeds' => 1, 'name' => "Science"],
  40. ];
  41. protected $subscriptions = [
  42. ['id' => 3, 'folder' => 1, 'top_folder' => 1, 'unread' => 2, 'updated' => "2016-05-23 06:40:02", 'err_msg' => 'argh', 'title' => 'Ars Technica', 'url' => " http://example.com/3", 'favicon' => 'http://example.com/3.png'],
  43. ['id' => 4, 'folder' => 6, 'top_folder' => 3, 'unread' => 6, 'updated' => "2017-10-09 15:58:34", 'err_msg' => '', 'title' => 'CBC News', 'url' => " http://example.com/4", 'favicon' => 'http://example.com/4.png'],
  44. ['id' => 6, 'folder' => null, 'top_folder' => null, 'unread' => 0, 'updated' => "2010-02-12 20:08:47", 'err_msg' => '', 'title' => 'Eurogamer', 'url' => " http://example.com/6", 'favicon' => 'http://example.com/6.png'],
  45. ['id' => 1, 'folder' => 2, 'top_folder' => 1, 'unread' => 5, 'updated' => "2017-09-15 22:54:16", 'err_msg' => '', 'title' => 'NASA JPL', 'url' => " http://example.com/1", 'favicon' => null],
  46. ['id' => 5, 'folder' => 6, 'top_folder' => 3, 'unread' => 12, 'updated' => "2017-07-07 17:07:17", 'err_msg' => '', 'title' => 'Ottawa Citizen', 'url' => " http://example.com/5", 'favicon' => ''],
  47. ['id' => 2, 'folder' => 5, 'top_folder' => 3, 'unread' => 10, 'updated' => "2011-11-11 11:11:11", 'err_msg' => 'oops', 'title' => 'Toronto Star', 'url' => " http://example.com/2", 'favicon' => 'http://example.com/2.png'],
  48. ];
  49. protected $labels = [
  50. ['id' => 3, 'articles' => 100, 'read' => 94, 'unread' => 6, 'name' => "Fascinating"],
  51. ['id' => 5, 'articles' => 0, 'read' => 0, 'unread' => 0, 'name' => "Interesting"],
  52. ['id' => 1, 'articles' => 2, 'read' => 2, 'unread' => 0, 'name' => "Logical"],
  53. ];
  54. protected $usedLabels = [
  55. ['id' => 3, 'articles' => 100, 'read' => 94, 'unread' => 6, 'name' => "Fascinating"],
  56. ['id' => 1, 'articles' => 2, 'read' => 2, 'unread' => 0, 'name' => "Logical"],
  57. ];
  58. protected $starred = ['total' => 10, 'unread' => 4, 'read' => 6];
  59. protected $articles = [
  60. [
  61. 'id' => 101,
  62. 'url' => 'http://example.com/1',
  63. 'title' => 'Article title 1',
  64. 'subscription_title' => "Feed 11",
  65. 'author' => '',
  66. 'content' => '<p>Article content 1</p>',
  67. 'guid' => '',
  68. 'published_date' => '2000-01-01 00:00:00',
  69. 'edited_date' => '2000-01-01 00:00:01',
  70. 'modified_date' => '2000-01-01 01:00:00',
  71. 'unread' => 1,
  72. 'starred' => 0,
  73. 'edition' => 101,
  74. 'subscription' => 8,
  75. 'fingerprint' => 'f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6:fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4:18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',
  76. 'media_url' => null,
  77. 'media_type' => null,
  78. 'note' => "",
  79. ],
  80. [
  81. 'id' => 102,
  82. 'url' => 'http://example.com/2',
  83. 'title' => 'Article title 2',
  84. 'subscription_title' => "Feed 11",
  85. 'author' => 'J. King',
  86. 'content' => '<p>Article content 2</p>',
  87. 'guid' => '5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7',
  88. 'published_date' => '2000-01-02 00:00:00',
  89. 'edited_date' => '2000-01-02 00:00:02',
  90. 'modified_date' => '2000-01-02 02:00:00',
  91. 'unread' => 0,
  92. 'starred' => 0,
  93. 'edition' => 202,
  94. 'subscription' => 8,
  95. 'fingerprint' => '0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153:13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9:2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',
  96. 'media_url' => "http://example.com/text",
  97. 'media_type' => "text/plain",
  98. 'note' => "Note 2",
  99. ],
  100. ];
  101. // text from https://corrigeur.fr/lorem-ipsum-traduction-origine.php
  102. protected $richContent = <<<LONG_STRING
  103. <section>
  104. <p>
  105. <b>Pour</b> vous faire mieux
  106. connaitre d’ou\u{300} vient
  107. l’erreur de ceux qui
  108. bla\u{302}ment la
  109. volupte\u{301}, et qui louent
  110. en quelque sorte la douleur,
  111. je vais entrer dans une
  112. explication plus
  113. e\u{301}tendue, et vous faire
  114. voir tout ce qui a
  115. e\u{301}te\u{301} dit
  116. la\u{300}-dessus par
  117. l’inventeur de la
  118. ve\u{301}rite\u{301}, et, pour
  119. ainsi dire, par l’architecte
  120. de la vie heureuse.
  121. </p>
  122. </section>
  123. LONG_STRING;
  124. protected function v($value) {
  125. return $value;
  126. }
  127. protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
  128. $url = "/tt-rss/api".$target;
  129. $server = [
  130. 'REQUEST_METHOD' => $method,
  131. 'REQUEST_URI' => $url,
  132. 'HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded",
  133. ];
  134. $req = new ServerRequest($server, [], $url, $method, "php://memory");
  135. $body = $req->getBody();
  136. if (!is_null($strData)) {
  137. $body->write($strData);
  138. } else {
  139. $body->write(json_encode($data));
  140. }
  141. $req = $req->withBody($body)->withRequestTarget($target);
  142. if (isset($user)) {
  143. if (strlen($user)) {
  144. $req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
  145. } else {
  146. $req = $req->withAttribute("authenticationFailed", true);
  147. }
  148. }
  149. return $this->h->dispatch($req);
  150. }
  151. protected function reqAuth($data, $user) {
  152. return $this->req($data, "POST", "", null, $user);
  153. }
  154. protected function respGood($content = null, $seq = 0): Response {
  155. return new Response([
  156. 'seq' => $seq,
  157. 'status' => 0,
  158. 'content' => $content,
  159. ]);
  160. }
  161. protected function respErr(string $msg, $content = [], $seq = 0): Response {
  162. $err = ['error' => $msg];
  163. return new Response([
  164. 'seq' => $seq,
  165. 'status' => 1,
  166. 'content' => array_merge($err, $content, $err),
  167. ]);
  168. }
  169. public function setUp() {
  170. self::clearData();
  171. self::setConf();
  172. // create a mock user manager
  173. Arsse::$user = Phake::mock(User::class);
  174. Phake::when(Arsse::$user)->auth->thenReturn(true);
  175. Arsse::$user->id = "john.doe@example.com";
  176. // create a mock database interface
  177. Arsse::$db = Phake::mock(Database::class);
  178. Phake::when(Arsse::$db)->begin->thenReturn(Phake::mock(Transaction::class));
  179. Phake::when(Arsse::$db)->sessionResume->thenThrow(new \JKingWeb\Arsse\User\ExceptionSession("invalid"));
  180. Phake::when(Arsse::$db)->sessionResume("PriestsOfSyrinx")->thenReturn([
  181. 'id' => "PriestsOfSyrinx",
  182. 'created' => "2000-01-01 00:00:00",
  183. 'expires' => "2112-12-21 21:12:00",
  184. 'user' => Arsse::$user->id,
  185. ]);
  186. $this->h = new API();
  187. }
  188. public function tearDown() {
  189. self::clearData();
  190. }
  191. public function testHandleInvalidPaths() {
  192. $exp = $this->respErr("MALFORMED_INPUT", [], null);
  193. $this->assertMessage($exp, $this->req(null, "POST", "", ""));
  194. $this->assertMessage($exp, $this->req(null, "POST", "/", ""));
  195. $this->assertMessage($exp, $this->req(null, "POST", "/index.php", ""));
  196. $exp = new EmptyResponse(404);
  197. $this->assertMessage($exp, $this->req(null, "POST", "/bad/path", ""));
  198. }
  199. public function testHandleOptionsRequest() {
  200. $exp = new EmptyResponse(204, [
  201. 'Allow' => "POST",
  202. 'Accept' => "application/json, text/json",
  203. ]);
  204. $this->assertMessage($exp, $this->req(null, "OPTIONS", "", ""));
  205. }
  206. public function testHandleInvalidData() {
  207. $exp = $this->respErr("MALFORMED_INPUT", [], null);
  208. $this->assertMessage($exp, $this->req(null, "POST", "", "This is not valid JSON data"));
  209. $this->assertMessage($exp, $this->req(null, "POST", "", "")); // lack of data is also an error
  210. }
  211. /** @dataProvider provideLoginRequests */
  212. public function testLogIn(array $conf, $httpUser, array $data, $sessions) {
  213. Arsse::$user->id = null;
  214. self::setConf($conf);
  215. Phake::when(Arsse::$user)->auth->thenReturn(false);
  216. Phake::when(Arsse::$user)->auth("john.doe@example.com", "secret")->thenReturn(true);
  217. Phake::when(Arsse::$user)->auth("jane.doe@example.com", "superman")->thenReturn(true);
  218. Phake::when(Arsse::$db)->sessionCreate("john.doe@example.com")->thenReturn("PriestsOfSyrinx")->thenReturn("SolarFederation");
  219. Phake::when(Arsse::$db)->sessionCreate("jane.doe@example.com")->thenReturn("ClockworkAngels")->thenReturn("SevenCitiesOfGold");
  220. if ($sessions instanceof EmptyResponse) {
  221. $exp1 = $sessions;
  222. $exp2 = $sessions;
  223. } elseif ($sessions) {
  224. $exp1 = $this->respGood(['session_id' => $sessions[0], 'api_level' => \JKingWeb\Arsse\REST\TinyTinyRSS\API::LEVEL]);
  225. $exp2 = $this->respGood(['session_id' => $sessions[1], 'api_level' => \JKingWeb\Arsse\REST\TinyTinyRSS\API::LEVEL]);
  226. } else {
  227. $exp1 = $this->respErr("LOGIN_ERROR");
  228. $exp2 = $this->respErr("LOGIN_ERROR");
  229. }
  230. $data['op'] = "login";
  231. $this->assertMessage($exp1, $this->reqAuth($data, $httpUser));
  232. // base64 passwords are also accepted
  233. if (isset($data['password'])) {
  234. $data['password'] = base64_encode($data['password']);
  235. }
  236. $this->assertMessage($exp2, $this->reqAuth($data, $httpUser));
  237. // logging in should never try to resume a session
  238. Phake::verify(Arsse::$db, Phake::times(0))->sessionResume($this->anything());
  239. }
  240. public function provideLoginRequests() {
  241. return $this->generateLoginRequests("login");
  242. }
  243. /** @dataProvider provideResumeRequests */
  244. public function testValidateASession(array $conf, $httpUser, string $data, $result) {
  245. Arsse::$user->id = null;
  246. self::setConf($conf);
  247. Phake::when(Arsse::$db)->sessionResume("PriestsOfSyrinx")->thenReturn([
  248. 'id' => "PriestsOfSyrinx",
  249. 'created' => "2000-01-01 00:00:00",
  250. 'expires' => "2112-12-21 21:12:00",
  251. 'user' => "john.doe@example.com",
  252. ]);
  253. Phake::when(Arsse::$db)->sessionResume("ClockworkAngels")->thenReturn([
  254. 'id' => "ClockworkAngels",
  255. 'created' => "2000-01-01 00:00:00",
  256. 'expires' => "2112-12-21 21:12:00",
  257. 'user' => "jane.doe@example.com",
  258. ]);
  259. $data = [
  260. 'op' => "isLoggedIn",
  261. 'sid' => $data,
  262. ];
  263. if ($result instanceof EmptyResponse) {
  264. $exp1 = $result;
  265. $exp2 = null;
  266. } elseif ($result) {
  267. $exp1 = $this->respGood(['status' => true]);
  268. $exp2 = $result;
  269. } else {
  270. $exp1 = $this->respErr("NOT_LOGGED_IN");
  271. $exp2 = ($httpUser) ? $httpUser : null;
  272. }
  273. $this->assertMessage($exp1, $this->reqAuth($data, $httpUser));
  274. $this->assertSame($exp2, Arsse::$user->id);
  275. }
  276. public function provideResumeRequests() {
  277. return $this->generateLoginRequests("isLoggedIn");
  278. }
  279. public function generateLoginRequests(string $type) {
  280. $john = "john.doe@example.com";
  281. $johnGood = [
  282. 'user' => $john,
  283. 'password' => "secret",
  284. ];
  285. $johnBad = [
  286. 'user' => $john,
  287. 'password' => "superman",
  288. ];
  289. $johnSess = ["PriestsOfSyrinx", "SolarFederation"];
  290. $jane = "jane.doe@example.com";
  291. $janeGood = [
  292. 'user' => $jane,
  293. 'password' => "superman",
  294. ];
  295. $janeBad = [
  296. 'user' => $jane,
  297. 'password' => "secret",
  298. ];
  299. $janeSess = ["ClockworkAngels", "SevenCitiesOfGold"];
  300. $missingU = [
  301. 'password' => "secret",
  302. ];
  303. $missingP = [
  304. 'user' => $john,
  305. ];
  306. $sidJohn = "PriestsOfSyrinx";
  307. $sidJane = "ClockworkAngels";
  308. $sidBad = "TheWatchmaker";
  309. $defaults = [
  310. 'userPreAuth' => false,
  311. 'userHTTPAuthRequired' => false,
  312. 'userSessionEnforced' => true,
  313. ];
  314. $preAuth = [
  315. 'userPreAuth' => true,
  316. 'userHTTPAuthRequired' => false, // implied true by pre-auth
  317. 'userSessionEnforced' => true,
  318. ];
  319. $httpReq = [
  320. 'userPreAuth' => false,
  321. 'userHTTPAuthRequired' => true,
  322. 'userSessionEnforced' => true,
  323. ];
  324. $noSess = [
  325. 'userPreAuth' => false,
  326. 'userHTTPAuthRequired' => false,
  327. 'userSessionEnforced' => false,
  328. ];
  329. $fullHttp = [
  330. 'userPreAuth' => false,
  331. 'userHTTPAuthRequired' => true,
  332. 'userSessionEnforced' => false,
  333. ];
  334. $http401 = new EmptyResponse(401);
  335. if ($type=="login") {
  336. return [
  337. // conf, user, data, result
  338. [$defaults, null, $johnGood, $johnSess],
  339. [$defaults, null, $johnBad, false],
  340. [$defaults, null, $janeGood, $janeSess],
  341. [$defaults, null, $janeBad, false],
  342. [$defaults, null, $missingU, false],
  343. [$defaults, null, $missingP, false],
  344. [$defaults, $john, $johnGood, $johnSess],
  345. [$defaults, $john, $johnBad, false],
  346. [$defaults, $john, $janeGood, $janeSess],
  347. [$defaults, $john, $janeBad, false],
  348. [$defaults, $john, $missingU, false],
  349. [$defaults, $john, $missingP, false],
  350. [$defaults, $jane, $johnGood, $johnSess],
  351. [$defaults, $jane, $johnBad, false],
  352. [$defaults, $jane, $janeGood, $janeSess],
  353. [$defaults, $jane, $janeBad, false],
  354. [$defaults, $jane, $missingU, false],
  355. [$defaults, $jane, $missingP, false],
  356. [$defaults, "", $johnGood, $http401],
  357. [$defaults, "", $johnBad, $http401],
  358. [$defaults, "", $janeGood, $http401],
  359. [$defaults, "", $janeBad, $http401],
  360. [$defaults, "", $missingU, $http401],
  361. [$defaults, "", $missingP, $http401],
  362. [$preAuth, null, $johnGood, $http401],
  363. [$preAuth, null, $johnBad, $http401],
  364. [$preAuth, null, $janeGood, $http401],
  365. [$preAuth, null, $janeBad, $http401],
  366. [$preAuth, null, $missingU, $http401],
  367. [$preAuth, null, $missingP, $http401],
  368. [$preAuth, $john, $johnGood, $johnSess],
  369. [$preAuth, $john, $johnBad, $johnSess],
  370. [$preAuth, $john, $janeGood, false],
  371. [$preAuth, $john, $janeBad, false],
  372. [$preAuth, $john, $missingU, false],
  373. [$preAuth, $john, $missingP, $johnSess],
  374. [$preAuth, $jane, $johnGood, false],
  375. [$preAuth, $jane, $johnBad, false],
  376. [$preAuth, $jane, $janeGood, $janeSess],
  377. [$preAuth, $jane, $janeBad, $janeSess],
  378. [$preAuth, $jane, $missingU, false],
  379. [$preAuth, $jane, $missingP, false],
  380. [$preAuth, "", $johnGood, $http401],
  381. [$preAuth, "", $johnBad, $http401],
  382. [$preAuth, "", $janeGood, $http401],
  383. [$preAuth, "", $janeBad, $http401],
  384. [$preAuth, "", $missingU, $http401],
  385. [$preAuth, "", $missingP, $http401],
  386. [$httpReq, null, $johnGood, $http401],
  387. [$httpReq, null, $johnBad, $http401],
  388. [$httpReq, null, $janeGood, $http401],
  389. [$httpReq, null, $janeBad, $http401],
  390. [$httpReq, null, $missingU, $http401],
  391. [$httpReq, null, $missingP, $http401],
  392. [$httpReq, $john, $johnGood, $johnSess],
  393. [$httpReq, $john, $johnBad, false],
  394. [$httpReq, $john, $janeGood, $janeSess],
  395. [$httpReq, $john, $janeBad, false],
  396. [$httpReq, $john, $missingU, false],
  397. [$httpReq, $john, $missingP, false],
  398. [$httpReq, $jane, $johnGood, $johnSess],
  399. [$httpReq, $jane, $johnBad, false],
  400. [$httpReq, $jane, $janeGood, $janeSess],
  401. [$httpReq, $jane, $janeBad, false],
  402. [$httpReq, $jane, $missingU, false],
  403. [$httpReq, $jane, $missingP, false],
  404. [$httpReq, "", $johnGood, $http401],
  405. [$httpReq, "", $johnBad, $http401],
  406. [$httpReq, "", $janeGood, $http401],
  407. [$httpReq, "", $janeBad, $http401],
  408. [$httpReq, "", $missingU, $http401],
  409. [$httpReq, "", $missingP, $http401],
  410. [$noSess, null, $johnGood, $johnSess],
  411. [$noSess, null, $johnBad, false],
  412. [$noSess, null, $janeGood, $janeSess],
  413. [$noSess, null, $janeBad, false],
  414. [$noSess, null, $missingU, false],
  415. [$noSess, null, $missingP, false],
  416. [$noSess, $john, $johnGood, $johnSess],
  417. [$noSess, $john, $johnBad, $johnSess],
  418. [$noSess, $john, $janeGood, $johnSess],
  419. [$noSess, $john, $janeBad, $johnSess],
  420. [$noSess, $john, $missingU, $johnSess],
  421. [$noSess, $john, $missingP, $johnSess],
  422. [$noSess, $jane, $johnGood, $janeSess],
  423. [$noSess, $jane, $johnBad, $janeSess],
  424. [$noSess, $jane, $janeGood, $janeSess],
  425. [$noSess, $jane, $janeBad, $janeSess],
  426. [$noSess, $jane, $missingU, $janeSess],
  427. [$noSess, $jane, $missingP, $janeSess],
  428. [$noSess, "", $johnGood, $http401],
  429. [$noSess, "", $johnBad, $http401],
  430. [$noSess, "", $janeGood, $http401],
  431. [$noSess, "", $janeBad, $http401],
  432. [$noSess, "", $missingU, $http401],
  433. [$noSess, "", $missingP, $http401],
  434. [$fullHttp, null, $johnGood, $http401],
  435. [$fullHttp, null, $johnBad, $http401],
  436. [$fullHttp, null, $janeGood, $http401],
  437. [$fullHttp, null, $janeBad, $http401],
  438. [$fullHttp, null, $missingU, $http401],
  439. [$fullHttp, null, $missingP, $http401],
  440. [$fullHttp, $john, $johnGood, $johnSess],
  441. [$fullHttp, $john, $johnBad, $johnSess],
  442. [$fullHttp, $john, $janeGood, $johnSess],
  443. [$fullHttp, $john, $janeBad, $johnSess],
  444. [$fullHttp, $john, $missingU, $johnSess],
  445. [$fullHttp, $john, $missingP, $johnSess],
  446. [$fullHttp, $jane, $johnGood, $janeSess],
  447. [$fullHttp, $jane, $johnBad, $janeSess],
  448. [$fullHttp, $jane, $janeGood, $janeSess],
  449. [$fullHttp, $jane, $janeBad, $janeSess],
  450. [$fullHttp, $jane, $missingU, $janeSess],
  451. [$fullHttp, $jane, $missingP, $janeSess],
  452. [$fullHttp, "", $johnGood, $http401],
  453. [$fullHttp, "", $johnBad, $http401],
  454. [$fullHttp, "", $janeGood, $http401],
  455. [$fullHttp, "", $janeBad, $http401],
  456. [$fullHttp, "", $missingU, $http401],
  457. [$fullHttp, "", $missingP, $http401],
  458. ];
  459. } elseif ($type=="isLoggedIn") {
  460. return [
  461. // conf, user, session, result
  462. [$defaults, null, $sidJohn, $john],
  463. [$defaults, null, $sidJane, $jane],
  464. [$defaults, null, $sidBad, false],
  465. [$defaults, $john, $sidJohn, $john],
  466. [$defaults, $john, $sidJane, $jane],
  467. [$defaults, $john, $sidBad, false],
  468. [$defaults, $jane, $sidJohn, $john],
  469. [$defaults, $jane, $sidJane, $jane],
  470. [$defaults, $jane, $sidBad, false],
  471. [$defaults, "", $sidJohn, $http401],
  472. [$defaults, "", $sidJane, $http401],
  473. [$defaults, "", $sidBad, $http401],
  474. [$preAuth, null, $sidJohn, $http401],
  475. [$preAuth, null, $sidJane, $http401],
  476. [$preAuth, null, $sidBad, $http401],
  477. [$preAuth, $john, $sidJohn, $john],
  478. [$preAuth, $john, $sidJane, $jane],
  479. [$preAuth, $john, $sidBad, false],
  480. [$preAuth, $jane, $sidJohn, $john],
  481. [$preAuth, $jane, $sidJane, $jane],
  482. [$preAuth, $jane, $sidBad, false],
  483. [$preAuth, "", $sidJohn, $http401],
  484. [$preAuth, "", $sidJane, $http401],
  485. [$preAuth, "", $sidBad, $http401],
  486. [$httpReq, null, $sidJohn, $http401],
  487. [$httpReq, null, $sidJane, $http401],
  488. [$httpReq, null, $sidBad, $http401],
  489. [$httpReq, $john, $sidJohn, $john],
  490. [$httpReq, $john, $sidJane, $jane],
  491. [$httpReq, $john, $sidBad, false],
  492. [$httpReq, $jane, $sidJohn, $john],
  493. [$httpReq, $jane, $sidJane, $jane],
  494. [$httpReq, $jane, $sidBad, false],
  495. [$httpReq, "", $sidJohn, $http401],
  496. [$httpReq, "", $sidJane, $http401],
  497. [$httpReq, "", $sidBad, $http401],
  498. [$noSess, null, $sidJohn, $john],
  499. [$noSess, null, $sidJane, $jane],
  500. [$noSess, null, $sidBad, false],
  501. [$noSess, $john, $sidJohn, $john],
  502. [$noSess, $john, $sidJane, $john],
  503. [$noSess, $john, $sidBad, $john],
  504. [$noSess, $jane, $sidJohn, $jane],
  505. [$noSess, $jane, $sidJane, $jane],
  506. [$noSess, $jane, $sidBad, $jane],
  507. [$noSess, "", $sidJohn, $http401],
  508. [$noSess, "", $sidJane, $http401],
  509. [$noSess, "", $sidBad, $http401],
  510. [$fullHttp, null, $sidJohn, $http401],
  511. [$fullHttp, null, $sidJane, $http401],
  512. [$fullHttp, null, $sidBad, $http401],
  513. [$fullHttp, $john, $sidJohn, $john],
  514. [$fullHttp, $john, $sidJane, $john],
  515. [$fullHttp, $john, $sidBad, $john],
  516. [$fullHttp, $jane, $sidJohn, $jane],
  517. [$fullHttp, $jane, $sidJane, $jane],
  518. [$fullHttp, $jane, $sidBad, $jane],
  519. [$fullHttp, "", $sidJohn, $http401],
  520. [$fullHttp, "", $sidJane, $http401],
  521. [$fullHttp, "", $sidBad, $http401],
  522. ];
  523. }
  524. }
  525. public function testHandleGenericError() {
  526. Phake::when(Arsse::$user)->auth(Arsse::$user->id, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionTimeout("general"));
  527. $data = [
  528. 'op' => "login",
  529. 'user' => Arsse::$user->id,
  530. 'password' => "secret",
  531. ];
  532. $exp = new EmptyResponse(500);
  533. $this->assertMessage($exp, $this->req($data));
  534. }
  535. public function testLogOut() {
  536. Phake::when(Arsse::$db)->sessionDestroy->thenReturn(true);
  537. $data = [
  538. 'op' => "logout",
  539. 'sid' => "PriestsOfSyrinx",
  540. ];
  541. $exp = $this->respGood(['status' => "OK"]);
  542. $this->assertMessage($exp, $this->req($data));
  543. Phake::verify(Arsse::$db)->sessionDestroy(Arsse::$user->id, "PriestsOfSyrinx");
  544. }
  545. public function testHandleUnknownMethods() {
  546. $exp = $this->respErr("UNKNOWN_METHOD", ['method' => "thisMethodDoesNotExist"]);
  547. $data = [
  548. 'op' => "thisMethodDoesNotExist",
  549. 'sid' => "PriestsOfSyrinx",
  550. ];
  551. $this->assertMessage($exp, $this->req($data));
  552. }
  553. public function testHandleMixedCaseMethods() {
  554. $data = [
  555. 'op' => "isLoggedIn",
  556. 'sid' => "PriestsOfSyrinx",
  557. ];
  558. $exp = $this->respGood(['status' => true]);
  559. $this->assertMessage($exp, $this->req($data));
  560. $data['op'] = "isloggedin";
  561. $this->assertMessage($exp, $this->req($data));
  562. $data['op'] = "ISLOGGEDIN";
  563. $this->assertMessage($exp, $this->req($data));
  564. $data['op'] = "iSlOgGeDiN";
  565. $this->assertMessage($exp, $this->req($data));
  566. }
  567. public function testRetrieveServerVersion() {
  568. $data = [
  569. 'op' => "getVersion",
  570. 'sid' => "PriestsOfSyrinx",
  571. ];
  572. $exp = $this->respGood([
  573. 'version' => \JKingWeb\Arsse\REST\TinyTinyRSS\API::VERSION,
  574. 'arsse_version' => Arsse::VERSION,
  575. ]);
  576. $this->assertMessage($exp, $this->req($data));
  577. }
  578. public function testRetrieveProtocolLevel() {
  579. $data = [
  580. 'op' => "getApiLevel",
  581. 'sid' => "PriestsOfSyrinx",
  582. ];
  583. $exp = $this->respGood(['level' => \JKingWeb\Arsse\REST\TinyTinyRSS\API::LEVEL]);
  584. $this->assertMessage($exp, $this->req($data));
  585. }
  586. public function testAddACategory() {
  587. $in = [
  588. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Software"],
  589. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Hardware", 'parent_id' => 1],
  590. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Hardware", 'parent_id' => 2112],
  591. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx"],
  592. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => ""],
  593. ['op' => "addCategory", 'sid' => "PriestsOfSyrinx", 'caption' => " "],
  594. ];
  595. $db = [
  596. ['name' => "Software", 'parent' => null],
  597. ['name' => "Hardware", 'parent' => 1],
  598. ['name' => "Hardware", 'parent' => 2112],
  599. ];
  600. $out = [
  601. ['id' => 2, 'name' => "Software", 'parent' => null],
  602. ['id' => 3, 'name' => "Hardware", 'parent' => 1],
  603. ['id' => 1, 'name' => "Politics", 'parent' => null],
  604. ];
  605. // set of various mocks for testing
  606. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
  607. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
  608. Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result($this->v([$out[0], $out[2]])));
  609. Phake::when(Arsse::$db)->folderList(Arsse::$user->id, 1, false)->thenReturn(new Result($this->v([$out[1]])));
  610. // set up mocks that produce errors
  611. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[2])->thenThrow(new ExceptionInput("idMissing")); // parent folder does not exist
  612. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
  613. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => "", 'parent' => null])->thenThrow(new ExceptionInput("missing"));
  614. Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => " ", 'parent' => null])->thenThrow(new ExceptionInput("whitespace"));
  615. // correctly add two folders
  616. $exp = $this->respGood("2");
  617. $this->assertMessage($exp, $this->req($in[0]));
  618. $exp = $this->respGood("3");
  619. $this->assertMessage($exp, $this->req($in[1]));
  620. // attempt to add the two folders again
  621. $exp = $this->respGood("2");
  622. $this->assertMessage($exp, $this->req($in[0]));
  623. $exp = $this->respGood("3");
  624. $this->assertMessage($exp, $this->req($in[1]));
  625. Phake::verify(Arsse::$db)->folderList(Arsse::$user->id, null, false);
  626. Phake::verify(Arsse::$db)->folderList(Arsse::$user->id, 1, false);
  627. // add a folder to a missing parent (silently fails)
  628. $exp = $this->respGood(false);
  629. $this->assertMessage($exp, $this->req($in[2]));
  630. // add some invalid folders
  631. $exp = $this->respErr("INCORRECT_USAGE");
  632. $this->assertMessage($exp, $this->req($in[3]));
  633. $this->assertMessage($exp, $this->req($in[4]));
  634. $this->assertMessage($exp, $this->req($in[5]));
  635. }
  636. public function testRemoveACategory() {
  637. $in = [
  638. ['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42],
  639. ['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112],
  640. ['op' => "removeCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1],
  641. ];
  642. Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
  643. Phake::when(Arsse::$db)->folderRemove(Arsse::$user->id, 42)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
  644. // succefully delete a folder
  645. $exp = $this->respGood();
  646. $this->assertMessage($exp, $this->req($in[0]));
  647. // try deleting it again (this should silently fail)
  648. $exp = $this->respGood();
  649. $this->assertMessage($exp, $this->req($in[0]));
  650. // delete a folder which does not exist (this should also silently fail)
  651. $exp = $this->respGood();
  652. $this->assertMessage($exp, $this->req($in[1]));
  653. // delete an invalid folder (causes an error)
  654. $exp = $this->respErr("INCORRECT_USAGE");
  655. $this->assertMessage($exp, $this->req($in[2]));
  656. Phake::verify(Arsse::$db, Phake::times(3))->folderRemove(Arsse::$user->id, $this->anything());
  657. }
  658. public function testMoveACategory() {
  659. $in = [
  660. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 1],
  661. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112, 'parent_id' => 2],
  662. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 0],
  663. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => 47],
  664. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1, 'parent_id' => 1],
  665. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'parent_id' => -1],
  666. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42],
  667. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx", 'parent_id' => -1],
  668. ['op' => "moveCategory", 'sid' => "PriestsOfSyrinx"],
  669. ];
  670. $db = [
  671. [Arsse::$user->id, 42, ['parent' => 1]],
  672. [Arsse::$user->id, 2112, ['parent' => 2]],
  673. [Arsse::$user->id, 42, ['parent' => 0]],
  674. [Arsse::$user->id, 42, ['parent' => 47]],
  675. [Arsse::$user->id, -1, ['parent' => 1]],
  676. [Arsse::$user->id, 42, ['parent' => -1]],
  677. [Arsse::$user->id, 42, ['parent' => 0]],
  678. [Arsse::$user->id, 0, ['parent' => -1]],
  679. [Arsse::$user->id, 0, ['parent' => 0]],
  680. ];
  681. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[0])->thenReturn(true);
  682. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
  683. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
  684. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[3])->thenThrow(new ExceptionInput("idMissing"));
  685. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[4])->thenThrow(new ExceptionInput("typeViolation"));
  686. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[5])->thenThrow(new ExceptionInput("typeViolation"));
  687. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[6])->thenThrow(new ExceptionInput("constraintViolation"));
  688. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[7])->thenThrow(new ExceptionInput("typeViolation"));
  689. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[8])->thenThrow(new ExceptionInput("typeViolation"));
  690. // succefully move a folder
  691. $exp = $this->respGood();
  692. $this->assertMessage($exp, $this->req($in[0]));
  693. // move a folder which does not exist (this should silently fail)
  694. $exp = $this->respGood();
  695. $this->assertMessage($exp, $this->req($in[1]));
  696. // move a folder causing a duplication (this should also silently fail)
  697. $exp = $this->respGood();
  698. $this->assertMessage($exp, $this->req($in[2]));
  699. $this->assertMessage($exp, $this->req($in[3]));
  700. $this->assertMessage($exp, $this->req($in[6]));
  701. // all the rest should cause errors
  702. $exp = $this->respErr("INCORRECT_USAGE");
  703. $this->assertMessage($exp, $this->req($in[4]));
  704. $this->assertMessage($exp, $this->req($in[5]));
  705. $this->assertMessage($exp, $this->req($in[7]));
  706. $this->assertMessage($exp, $this->req($in[8]));
  707. Phake::verify(Arsse::$db, Phake::times(5))->folderPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
  708. }
  709. public function testRenameACategory() {
  710. $in = [
  711. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => "Ook"],
  712. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 2112, 'caption' => "Eek"],
  713. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => "Eek"],
  714. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => ""],
  715. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42, 'caption' => " "],
  716. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => -1, 'caption' => "Ook"],
  717. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'category_id' => 42],
  718. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx", 'caption' => "Ook"],
  719. ['op' => "renameCategory", 'sid' => "PriestsOfSyrinx"],
  720. ];
  721. $db = [
  722. [Arsse::$user->id, 42, ['name' => "Ook"]],
  723. [Arsse::$user->id, 2112, ['name' => "Eek"]],
  724. [Arsse::$user->id, 42, ['name' => "Eek"]],
  725. ];
  726. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[0])->thenReturn(true);
  727. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
  728. Phake::when(Arsse::$db)->folderPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
  729. // succefully rename a folder
  730. $exp = $this->respGood();
  731. $this->assertMessage($exp, $this->req($in[0]));
  732. // rename a folder which does not exist (this should silently fail)
  733. $exp = $this->respGood();
  734. $this->assertMessage($exp, $this->req($in[1]));
  735. // rename a folder causing a duplication (this should also silently fail)
  736. $exp = $this->respGood();
  737. $this->assertMessage($exp, $this->req($in[2]));
  738. // all the rest should cause errors
  739. $exp = $this->respErr("INCORRECT_USAGE");
  740. $this->assertMessage($exp, $this->req($in[3]));
  741. $this->assertMessage($exp, $this->req($in[4]));
  742. $this->assertMessage($exp, $this->req($in[5]));
  743. $this->assertMessage($exp, $this->req($in[6]));
  744. $this->assertMessage($exp, $this->req($in[7]));
  745. $this->assertMessage($exp, $this->req($in[8]));
  746. Phake::verify(Arsse::$db, Phake::times(3))->folderPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
  747. }
  748. public function testAddASubscription() {
  749. $in = [
  750. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/0"],
  751. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/1", 'category_id' => 42],
  752. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/2", 'category_id' => 2112],
  753. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/3"],
  754. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://localhost:8000/Feed/Discovery/Valid"],
  755. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://localhost:8000/Feed/Discovery/Invalid"],
  756. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/6"],
  757. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/7"],
  758. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/8", 'category_id' => 47],
  759. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/9", 'category_id' => 1],
  760. // these don't even query the database as the input is syntactically invalid
  761. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx"],
  762. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'login' => []],
  763. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'login' => "", 'password' => []],
  764. ['op' => "subscribeToFeed", 'sid' => "PriestsOfSyrinx", 'feed_url' => "http://example.com/", 'category_id' => -1],
  765. ];
  766. $db = [
  767. [Arsse::$user->id, "http://example.com/0", "", ""],
  768. [Arsse::$user->id, "http://example.com/1", "", ""],
  769. [Arsse::$user->id, "http://example.com/2", "", ""],
  770. [Arsse::$user->id, "http://example.com/3", "", ""],
  771. [Arsse::$user->id, "http://localhost:8000/Feed/Discovery/Valid", "", ""],
  772. [Arsse::$user->id, "http://localhost:8000/Feed/Discovery/Invalid", "", ""],
  773. [Arsse::$user->id, "http://example.com/6", "", ""],
  774. [Arsse::$user->id, "http://example.com/7", "", ""],
  775. [Arsse::$user->id, "http://example.com/8", "", ""],
  776. [Arsse::$user->id, "http://example.com/9", "", ""],
  777. ];
  778. $out = [
  779. ['code' => 1, 'feed_id' => 2],
  780. ['code' => 5, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/1", new \PicoFeed\Client\UnauthorizedException()))->getMessage()],
  781. ['code' => 1, 'feed_id' => 0],
  782. ['code' => 0, 'feed_id' => 3],
  783. ['code' => 0, 'feed_id' => 1],
  784. ['code' => 3, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://localhost:8000/Feed/Discovery/Invalid", new \PicoFeed\Reader\SubscriptionNotFoundException()))->getMessage()],
  785. ['code' => 2, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/6", new \PicoFeed\Client\InvalidUrlException()))->getMessage()],
  786. ['code' => 6, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException()))->getMessage()],
  787. ['code' => 1, 'feed_id' => 4],
  788. ['code' => 0, 'feed_id' => 4],
  789. ];
  790. $list = [
  791. ['id' => 1, 'url' => "http://localhost:8000/Feed/Discovery/Feed"],
  792. ['id' => 2, 'url' => "http://example.com/0"],
  793. ['id' => 3, 'url' => "http://example.com/3"],
  794. ['id' => 4, 'url' => "http://example.com/9"],
  795. ];
  796. Phake::when(Arsse::$db)->subscriptionAdd(...$db[0])->thenReturn(2);
  797. Phake::when(Arsse::$db)->subscriptionAdd(...$db[1])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/1", new \PicoFeed\Client\UnauthorizedException()));
  798. Phake::when(Arsse::$db)->subscriptionAdd(...$db[2])->thenReturn(2);
  799. Phake::when(Arsse::$db)->subscriptionAdd(...$db[3])->thenThrow(new ExceptionInput("constraintViolation"));
  800. Phake::when(Arsse::$db)->subscriptionAdd(...$db[4])->thenThrow(new ExceptionInput("constraintViolation"));
  801. Phake::when(Arsse::$db)->subscriptionAdd(...$db[5])->thenThrow(new ExceptionInput("constraintViolation"));
  802. Phake::when(Arsse::$db)->subscriptionAdd(...$db[6])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/6", new \PicoFeed\Client\InvalidUrlException()));
  803. Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException()));
  804. Phake::when(Arsse::$db)->subscriptionAdd(...$db[8])->thenReturn(4);
  805. Phake::when(Arsse::$db)->subscriptionAdd(...$db[9])->thenThrow(new ExceptionInput("constraintViolation"));
  806. Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->v(['id' => 42]));
  807. Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->v(['id' => 47]));
  808. Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2112)->thenThrow(new ExceptionInput("subjectMissing"));
  809. Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything())->thenReturn(true);
  810. Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 4, $this->anything())->thenThrow(new ExceptionInput("idMissing"));
  811. Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v($list)));
  812. for ($a = 0; $a < (sizeof($in) - 4); $a++) {
  813. $exp = $this->respGood($out[$a]);
  814. $this->assertMessage($exp, $this->req($in[$a]), "Failed test $a");
  815. }
  816. $exp = $this->respErr("INCORRECT_USAGE");
  817. for ($a = (sizeof($in) - 4); $a < sizeof($in); $a++) {
  818. $this->assertMessage($exp, $this->req($in[$a]), "Failed test $a");
  819. }
  820. Phake::verify(Arsse::$db, Phake::times(0))->subscriptionPropertiesSet(Arsse::$user->id, 4, ['folder' => 1]);
  821. }
  822. public function testRemoveASubscription() {
  823. $in = [
  824. ['op' => "unsubscribeFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42],
  825. ['op' => "unsubscribeFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
  826. ['op' => "unsubscribeFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
  827. ['op' => "unsubscribeFeed", 'sid' => "PriestsOfSyrinx"],
  828. ];
  829. Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, $this->anything())->thenThrow(new ExceptionInput("typeViolation"));
  830. Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, 2112)->thenThrow(new ExceptionInput("subjectMissing"));
  831. Phake::when(Arsse::$db)->subscriptionRemove(Arsse::$user->id, 42)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
  832. // succefully delete a folder
  833. $exp = $this->respGood(['status' => "OK"]);
  834. $this->assertMessage($exp, $this->req($in[0]));
  835. // try deleting it again (this should noisily fail, as should everything else)
  836. $exp = $this->respErr("FEED_NOT_FOUND");
  837. $this->assertMessage($exp, $this->req($in[0]));
  838. $this->assertMessage($exp, $this->req($in[1]));
  839. $this->assertMessage($exp, $this->req($in[2]));
  840. $this->assertMessage($exp, $this->req($in[3]));
  841. Phake::verify(Arsse::$db, Phake::times(5))->subscriptionRemove(Arsse::$user->id, $this->anything());
  842. }
  843. public function testMoveASubscription() {
  844. $in = [
  845. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'category_id' => 1],
  846. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112, 'category_id' => 2],
  847. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'category_id' => 0],
  848. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'category_id' => 47],
  849. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'category_id' => 1],
  850. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'category_id' => -1],
  851. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42],
  852. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx", 'category_id' => -1],
  853. ['op' => "moveFeed", 'sid' => "PriestsOfSyrinx"],
  854. ];
  855. $db = [
  856. [Arsse::$user->id, 42, ['folder' => 1]],
  857. [Arsse::$user->id, 2112, ['folder' => 2]],
  858. [Arsse::$user->id, 42, ['folder' => 0]],
  859. [Arsse::$user->id, 42, ['folder' => 47]],
  860. ];
  861. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[0])->thenReturn(true);
  862. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
  863. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
  864. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[3])->thenThrow(new ExceptionInput("constraintViolation"));
  865. // succefully move a subscription
  866. $exp = $this->respGood();
  867. $this->assertMessage($exp, $this->req($in[0]));
  868. // move a subscription which does not exist (this should silently fail)
  869. $exp = $this->respGood();
  870. $this->assertMessage($exp, $this->req($in[1]));
  871. // move a subscription causing a duplication (this should also silently fail)
  872. $exp = $this->respGood();
  873. $this->assertMessage($exp, $this->req($in[2]));
  874. $this->assertMessage($exp, $this->req($in[3]));
  875. // all the rest should cause errors
  876. $exp = $this->respErr("INCORRECT_USAGE");
  877. $this->assertMessage($exp, $this->req($in[4]));
  878. $this->assertMessage($exp, $this->req($in[5]));
  879. $this->assertMessage($exp, $this->req($in[6]));
  880. $this->assertMessage($exp, $this->req($in[7]));
  881. $this->assertMessage($exp, $this->req($in[8]));
  882. Phake::verify(Arsse::$db, Phake::times(4))->subscriptionPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
  883. }
  884. public function testRenameASubscription() {
  885. $in = [
  886. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'caption' => "Ook"],
  887. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112, 'caption' => "Eek"],
  888. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'caption' => "Eek"],
  889. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'caption' => ""],
  890. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'caption' => " "],
  891. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'caption' => "Ook"],
  892. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42],
  893. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx", 'caption' => "Ook"],
  894. ['op' => "renameFeed", 'sid' => "PriestsOfSyrinx"],
  895. ];
  896. $db = [
  897. [Arsse::$user->id, 42, ['title' => "Ook"]],
  898. [Arsse::$user->id, 2112, ['title' => "Eek"]],
  899. [Arsse::$user->id, 42, ['title' => "Eek"]],
  900. ];
  901. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[0])->thenReturn(true);
  902. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
  903. Phake::when(Arsse::$db)->subscriptionPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
  904. // succefully rename a subscription
  905. $exp = $this->respGood();
  906. $this->assertMessage($exp, $this->req($in[0]));
  907. // rename a subscription which does not exist (this should silently fail)
  908. $exp = $this->respGood();
  909. $this->assertMessage($exp, $this->req($in[1]));
  910. // rename a subscription causing a duplication (this should also silently fail)
  911. $exp = $this->respGood();
  912. $this->assertMessage($exp, $this->req($in[2]));
  913. // all the rest should cause errors
  914. $exp = $this->respErr("INCORRECT_USAGE");
  915. $this->assertMessage($exp, $this->req($in[3]));
  916. $this->assertMessage($exp, $this->req($in[4]));
  917. $this->assertMessage($exp, $this->req($in[5]));
  918. $this->assertMessage($exp, $this->req($in[6]));
  919. $this->assertMessage($exp, $this->req($in[7]));
  920. $this->assertMessage($exp, $this->req($in[8]));
  921. Phake::verify(Arsse::$db)->subscriptionPropertiesSet(...$db[0]);
  922. Phake::verify(Arsse::$db)->subscriptionPropertiesSet(...$db[1]);
  923. Phake::verify(Arsse::$db)->subscriptionPropertiesSet(...$db[2]);
  924. }
  925. public function testRetrieveTheGlobalUnreadCount() {
  926. $in = ['op' => "getUnread", 'sid' => "PriestsOfSyrinx"];
  927. Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v([
  928. ['id' => 1, 'unread' => 2112],
  929. ['id' => 2, 'unread' => 42],
  930. ['id' => 3, 'unread' => 47],
  931. ])));
  932. $exp = $this->respGood(['unread' => (string) (2112 + 42 + 47)]);
  933. $this->assertMessage($exp, $this->req($in));
  934. }
  935. public function testRetrieveTheServerConfiguration() {
  936. $in = ['op' => "getConfig", 'sid' => "PriestsOfSyrinx"];
  937. $interval = Service::interval();
  938. $valid = (new \DateTimeImmutable("now", new \DateTimezone("UTC")))->sub($interval);
  939. $invalid = $valid->sub($interval)->sub($interval);
  940. Phake::when(Arsse::$db)->metaGet("service_last_checkin")->thenReturn(Date::transform($valid, "sql"))->thenReturn(Date::transform($invalid, "sql"));
  941. Phake::when(Arsse::$db)->subscriptionCount(Arsse::$user->id)->thenReturn(12)->thenReturn(2);
  942. $exp = [
  943. ['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => true, 'num_feeds' => 12],
  944. ['icons_dir' => "feed-icons", 'icons_url' => "feed-icons", 'daemon_is_running' => false, 'num_feeds' => 2],
  945. ];
  946. $this->assertMessage($this->respGood($exp[0]), $this->req($in));
  947. $this->assertMessage($this->respGood($exp[1]), $this->req($in));
  948. }
  949. public function testUpdateAFeed() {
  950. $in = [
  951. ['op' => "updateFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 1],
  952. ['op' => "updateFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2],
  953. ['op' => "updateFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
  954. ['op' => "updateFeed", 'sid' => "PriestsOfSyrinx"],
  955. ];
  956. Phake::when(Arsse::$db)->feedUpdate(11)->thenReturn(true);
  957. Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 1)->thenReturn($this->v(['id' => 1, 'feed' => 11]));
  958. Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2)->thenThrow(new ExceptionInput("subjectMissing"));
  959. $exp = $this->respGood(['status' => "OK"]);
  960. $this->assertMessage($exp, $this->req($in[0]));
  961. Phake::verify(Arsse::$db)->feedUpdate(11);
  962. $exp = $this->respErr("FEED_NOT_FOUND");
  963. $this->assertMessage($exp, $this->req($in[1]));
  964. $exp = $this->respErr("INCORRECT_USAGE");
  965. $this->assertMessage($exp, $this->req($in[2]));
  966. $this->assertMessage($exp, $this->req($in[3]));
  967. }
  968. public function testAddALabel() {
  969. $in = [
  970. ['op' => "addLabel", 'sid' => "PriestsOfSyrinx", 'caption' => "Software"],
  971. ['op' => "addLabel", 'sid' => "PriestsOfSyrinx", 'caption' => "Hardware",],
  972. ['op' => "addLabel", 'sid' => "PriestsOfSyrinx"],
  973. ['op' => "addLabel", 'sid' => "PriestsOfSyrinx", 'caption' => ""],
  974. ['op' => "addLabel", 'sid' => "PriestsOfSyrinx", 'caption' => " "],
  975. ];
  976. $db = [
  977. ['name' => "Software"],
  978. ['name' => "Hardware"],
  979. ];
  980. $out = [
  981. ['id' => 2, 'name' => "Software"],
  982. ['id' => 3, 'name' => "Hardware"],
  983. ['id' => 1, 'name' => "Politics"],
  984. ];
  985. // set of various mocks for testing
  986. Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
  987. Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
  988. Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Software", true)->thenReturn($this->v($out[0]));
  989. Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Hardware", true)->thenReturn($this->v($out[1]));
  990. // set up mocks that produce errors
  991. Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
  992. Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
  993. Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => " "])->thenThrow(new ExceptionInput("whitespace"));
  994. // correctly add two labels
  995. $exp = $this->respGood((-1 * API::LABEL_OFFSET) - 2);
  996. $this->assertMessage($exp, $this->req($in[0]));
  997. $exp = $this->respGood((-1 * API::LABEL_OFFSET) - 3);
  998. $this->assertMessage($exp, $this->req($in[1]));
  999. // attempt to add the two labels again
  1000. $exp = $this->respGood((-1 * API::LABEL_OFFSET) - 2);
  1001. $this->assertMessage($exp, $this->req($in[0]));
  1002. $exp = $this->respGood((-1 * API::LABEL_OFFSET) - 3);
  1003. $this->assertMessage($exp, $this->req($in[1]));
  1004. Phake::verify(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Software", true);
  1005. Phake::verify(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Hardware", true);
  1006. // add some invalid labels
  1007. $exp = $this->respErr("INCORRECT_USAGE");
  1008. $this->assertMessage($exp, $this->req($in[2]));
  1009. $this->assertMessage($exp, $this->req($in[3]));
  1010. $this->assertMessage($exp, $this->req($in[4]));
  1011. }
  1012. public function testRemoveALabel() {
  1013. $in = [
  1014. ['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042],
  1015. ['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112],
  1016. ['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 1],
  1017. ['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 0],
  1018. ['op' => "removeLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -10],
  1019. ];
  1020. Phake::when(Arsse::$db)->labelRemove(Arsse::$user->id, $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
  1021. Phake::when(Arsse::$db)->labelRemove(Arsse::$user->id, 18)->thenReturn(true)->thenThrow(new ExceptionInput("subjectMissing"));
  1022. // succefully delete a label
  1023. $exp = $this->respGood();
  1024. $this->assertMessage($exp, $this->req($in[0]));
  1025. // try deleting it again (this should silently fail)
  1026. $exp = $this->respGood();
  1027. $this->assertMessage($exp, $this->req($in[0]));
  1028. // delete a label which does not exist (this should also silently fail)
  1029. $exp = $this->respGood();
  1030. $this->assertMessage($exp, $this->req($in[1]));
  1031. // delete some invalid labels (causes an error)
  1032. $exp = $this->respErr("INCORRECT_USAGE");
  1033. $this->assertMessage($exp, $this->req($in[2]));
  1034. $this->assertMessage($exp, $this->req($in[3]));
  1035. $this->assertMessage($exp, $this->req($in[4]));
  1036. Phake::verify(Arsse::$db, Phake::times(2))->labelRemove(Arsse::$user->id, 18);
  1037. Phake::verify(Arsse::$db)->labelRemove(Arsse::$user->id, 1088);
  1038. }
  1039. public function testRenameALabel() {
  1040. $in = [
  1041. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => "Ook"],
  1042. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112, 'caption' => "Eek"],
  1043. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => "Eek"],
  1044. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => ""],
  1045. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042, 'caption' => " "],
  1046. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1042],
  1047. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -1, 'caption' => "Ook"],
  1048. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx", 'caption' => "Ook"],
  1049. ['op' => "renameLabel", 'sid' => "PriestsOfSyrinx"],
  1050. ];
  1051. $db = [
  1052. [Arsse::$user->id, 18, ['name' => "Ook"]],
  1053. [Arsse::$user->id, 1088, ['name' => "Eek"]],
  1054. [Arsse::$user->id, 18, ['name' => "Eek"]],
  1055. [Arsse::$user->id, 18, ['name' => ""]],
  1056. [Arsse::$user->id, 18, ['name' => " "]],
  1057. [Arsse::$user->id, 18, ['name' => ""]],
  1058. ];
  1059. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[0])->thenReturn(true);
  1060. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[1])->thenThrow(new ExceptionInput("subjectMissing"));
  1061. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[2])->thenThrow(new ExceptionInput("constraintViolation"));
  1062. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[3])->thenThrow(new ExceptionInput("typeViolation"));
  1063. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[4])->thenThrow(new ExceptionInput("typeViolation"));
  1064. Phake::when(Arsse::$db)->labelPropertiesSet(...$db[5])->thenThrow(new ExceptionInput("typeViolation"));
  1065. // succefully rename a label
  1066. $exp = $this->respGood();
  1067. $this->assertMessage($exp, $this->req($in[0]));
  1068. // rename a label which does not exist (this should silently fail)
  1069. $exp = $this->respGood();
  1070. $this->assertMessage($exp, $this->req($in[1]));
  1071. // rename a label causing a duplication (this should also silently fail)
  1072. $exp = $this->respGood();
  1073. $this->assertMessage($exp, $this->req($in[2]));
  1074. // all the rest should cause errors
  1075. $exp = $this->respErr("INCORRECT_USAGE");
  1076. $this->assertMessage($exp, $this->req($in[3]));
  1077. $this->assertMessage($exp, $this->req($in[4]));
  1078. $this->assertMessage($exp, $this->req($in[5]));
  1079. $this->assertMessage($exp, $this->req($in[6]));
  1080. $this->assertMessage($exp, $this->req($in[7]));
  1081. $this->assertMessage($exp, $this->req($in[8]));
  1082. Phake::verify(Arsse::$db, Phake::times(6))->labelPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything());
  1083. }
  1084. public function testRetrieveCategoryLists() {
  1085. $in = [
  1086. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
  1087. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx"],
  1088. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'unread_only' => true],
  1089. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true, 'include_empty' => true],
  1090. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true],
  1091. ['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true, 'unread_only' => true],
  1092. ];
  1093. Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders)));
  1094. Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->topFolders)));
  1095. Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
  1096. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1097. Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
  1098. Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
  1099. $exp = [
  1100. [
  1101. ['id' => "5", 'title' => "Local", 'unread' => 10, 'order_id' => 1],
  1102. ['id' => "6", 'title' => "National", 'unread' => 18, 'order_id' => 2],
  1103. ['id' => "4", 'title' => "Photography", 'unread' => 0, 'order_id' => 3],
  1104. ['id' => "3", 'title' => "Politics", 'unread' => 0, 'order_id' => 4],
  1105. ['id' => "2", 'title' => "Rocketry", 'unread' => 5, 'order_id' => 5],
  1106. ['id' => "1", 'title' => "Science", 'unread' => 2, 'order_id' => 6],
  1107. ['id' => 0, 'title' => "Uncategorized", 'unread' => 0],
  1108. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1109. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1110. ],
  1111. [
  1112. ['id' => "5", 'title' => "Local", 'unread' => 10, 'order_id' => 1],
  1113. ['id' => "6", 'title' => "National", 'unread' => 18, 'order_id' => 2],
  1114. ['id' => "3", 'title' => "Politics", 'unread' => 0, 'order_id' => 4],
  1115. ['id' => "2", 'title' => "Rocketry", 'unread' => 5, 'order_id' => 5],
  1116. ['id' => "1", 'title' => "Science", 'unread' => 2, 'order_id' => 6],
  1117. ['id' => 0, 'title' => "Uncategorized", 'unread' => 0],
  1118. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1119. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1120. ],
  1121. [
  1122. ['id' => "5", 'title' => "Local", 'unread' => 10, 'order_id' => 1],
  1123. ['id' => "6", 'title' => "National", 'unread' => 18, 'order_id' => 2],
  1124. ['id' => "2", 'title' => "Rocketry", 'unread' => 5, 'order_id' => 5],
  1125. ['id' => "1", 'title' => "Science", 'unread' => 2, 'order_id' => 6],
  1126. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1127. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1128. ],
  1129. [
  1130. ['id' => "4", 'title' => "Photography", 'unread' => 0, 'order_id' => 1],
  1131. ['id' => "3", 'title' => "Politics", 'unread' => 28, 'order_id' => 2],
  1132. ['id' => "1", 'title' => "Science", 'unread' => 7, 'order_id' => 3],
  1133. ['id' => 0, 'title' => "Uncategorized", 'unread' => 0],
  1134. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1135. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1136. ],
  1137. [
  1138. ['id' => "3", 'title' => "Politics", 'unread' => 28, 'order_id' => 2],
  1139. ['id' => "1", 'title' => "Science", 'unread' => 7, 'order_id' => 3],
  1140. ['id' => 0, 'title' => "Uncategorized", 'unread' => 0],
  1141. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1142. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1143. ],
  1144. [
  1145. ['id' => "3", 'title' => "Politics", 'unread' => 28, 'order_id' => 2],
  1146. ['id' => "1", 'title' => "Science", 'unread' => 7, 'order_id' => 3],
  1147. ['id' => -1, 'title' => "Special", 'unread' => 11],
  1148. ['id' => -2, 'title' => "Labels", 'unread' => "6"],
  1149. ],
  1150. ];
  1151. for ($a = 0; $a < sizeof($in); $a++) {
  1152. $this->assertMessage($this->respGood($exp[$a]), $this->req($in[$a]), "Test $a failed");
  1153. }
  1154. }
  1155. public function testRetrieveCounterList() {
  1156. $in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"];
  1157. Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders)));
  1158. Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
  1159. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
  1160. Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
  1161. Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
  1162. $exp = [
  1163. ['id' => "global-unread", 'counter' => 35],
  1164. ['id' => "subscribed-feeds", 'counter' => 6],
  1165. ['id' => 0, 'counter' => 0, 'auxcounter' => 0],
  1166. ['id' => -1, 'counter' => 4, 'auxcounter' => 10],
  1167. ['id' => -2, 'counter' => 0, 'auxcounter' => 0],
  1168. ['id' => -3, 'counter' => 7, 'auxcounter' => 0],
  1169. ['id' => -4, 'counter' => 35, 'auxcounter' => 0],
  1170. ['id' => -1027, 'counter' => 6, 'auxcounter' => 100],
  1171. ['id' => -1025, 'counter' => 0, 'auxcounter' => 2],
  1172. ['id' => "3", 'updated' => "2016-05-23T06:40:02Z", 'counter' => 2, 'has_img' => 1],
  1173. ['id' => "4", 'updated' => "2017-10-09T15:58:34Z", 'counter' => 6, 'has_img' => 1],
  1174. ['id' => "6", 'updated' => "2010-02-12T20:08:47Z", 'counter' => 0, 'has_img' => 1],
  1175. ['id' => "1", 'updated' => "2017-09-15T22:54:16Z", 'counter' => 5, 'has_img' => 0],
  1176. ['id' => "5", 'updated' => "2017-07-07T17:07:17Z", 'counter' => 12, 'has_img' => 0],
  1177. ['id' => "2", 'updated' => "2011-11-11T11:11:11Z", 'counter' => 10, 'has_img' => 1],
  1178. ['id' => 5, 'kind' => "cat", 'counter' => 10],
  1179. ['id' => 6, 'kind' => "cat", 'counter' => 18],
  1180. ['id' => 4, 'kind' => "cat", 'counter' => 0],
  1181. ['id' => 3, 'kind' => "cat", 'counter' => 28],
  1182. ['id' => 2, 'kind' => "cat", 'counter' => 5],
  1183. ['id' => 1, 'kind' => "cat", 'counter' => 7],
  1184. ['id' => 0, 'kind' => "cat", 'counter' => 0],
  1185. ['id' => -2, 'kind' => "cat", 'counter' => 6],
  1186. ];
  1187. $this->assertMessage($this->respGood($exp), $this->req($in));
  1188. }
  1189. public function testRetrieveTheLabelList() {
  1190. $in = [
  1191. ['op' => "getLabels", 'sid' => "PriestsOfSyrinx"],
  1192. ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 1],
  1193. ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 2],
  1194. ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 3],
  1195. ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 4],
  1196. ];
  1197. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1198. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 1)->thenReturn($this->v([1,3]));
  1199. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2)->thenReturn($this->v([3]));
  1200. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 3)->thenReturn([]);
  1201. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 4)->thenThrow(new ExceptionInput("idMissing"));
  1202. $exp = [
  1203. [
  1204. ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1205. ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1206. ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1207. ],
  1208. [
  1209. ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => true],
  1210. ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1211. ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => true],
  1212. ],
  1213. [
  1214. ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => true],
  1215. ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1216. ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1217. ],
  1218. [
  1219. ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1220. ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1221. ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1222. ],
  1223. [
  1224. ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1225. ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1226. ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false],
  1227. ],
  1228. ];
  1229. for ($a = 0; $a < sizeof($in); $a++) {
  1230. $this->assertMessage($this->respGood($exp[$a]), $this->req($in[$a]), "Test $a failed");
  1231. }
  1232. }
  1233. public function testAssignArticlesToALabel() {
  1234. $list = [
  1235. range(1, 100),
  1236. range(1, 50),
  1237. range(51, 100),
  1238. ];
  1239. $in = [
  1240. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112, 'article_ids' => implode(",", $list[0])],
  1241. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112, 'article_ids' => implode(",", $list[0]), 'assign' => true],
  1242. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -2112],
  1243. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => -42],
  1244. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 42],
  1245. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx", 'label_id' => 0],
  1246. ['op' => "setArticleLabel", 'sid' => "PriestsOfSyrinx"],
  1247. ];
  1248. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, $this->anything(), (new Context)->articles([]), $this->anything())->thenThrow(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
  1249. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, $this->anything(), (new Context)->articles($list[0]), $this->anything())->thenThrow(new ExceptionInput("tooLong")); // data model function limited to 50 items for multiples
  1250. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), true)->thenReturn(42);
  1251. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), true)->thenReturn(47);
  1252. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), false)->thenReturn(5);
  1253. Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), false)->thenReturn(2);
  1254. $exp = $this->respGood(['status' => "OK", 'updated' => 89]);
  1255. $this->assertMessage($exp, $this->req($in[0]));
  1256. Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), true);
  1257. Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), true);
  1258. $exp = $this->respGood(['status' => "OK", 'updated' => 7]);
  1259. $this->assertMessage($exp, $this->req($in[1]));
  1260. Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), false);
  1261. Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), false);
  1262. $exp = $this->respGood(['status' => "OK", 'updated' => 0]);
  1263. $this->assertMessage($exp, $this->req($in[2]));
  1264. $exp = $this->respErr("INCORRECT_USAGE");
  1265. $this->assertMessage($exp, $this->req($in[3]));
  1266. $this->assertMessage($exp, $this->req($in[4]));
  1267. $this->assertMessage($exp, $this->req($in[5]));
  1268. $this->assertMessage($exp, $this->req($in[6]));
  1269. }
  1270. public function testRetrieveFeedTree() {
  1271. $in = [
  1272. ['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
  1273. ['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx"],
  1274. ];
  1275. Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders)));
  1276. Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
  1277. Phake::when(Arsse::$db)->labelList($this->anything(), true)->thenReturn(new Result($this->v($this->labels)));
  1278. Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
  1279. Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
  1280. // the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them
  1281. $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,],],],],],];
  1282. $this->assertMessage($this->respGood($exp), $this->req($in[0]));
  1283. $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,],],],],],];
  1284. $this->assertMessage($this->respGood($exp), $this->req($in[1]));
  1285. }
  1286. public function testMarkFeedsAsRead() {
  1287. $in1 = [
  1288. // no-ops
  1289. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx"],
  1290. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0],
  1291. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2],
  1292. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6],
  1293. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'is_cat' => true],
  1294. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'is_cat' => true],
  1295. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'is_cat' => true],
  1296. ];
  1297. $in2 = [
  1298. // simple contexts
  1299. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
  1300. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4],
  1301. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
  1302. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
  1303. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true],
  1304. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true],
  1305. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true],
  1306. ];
  1307. $in3 = [
  1308. // this one has a tricky time-based context
  1309. ['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
  1310. ];
  1311. Phake::when(Arsse::$db)->articleMark->thenThrow(new ExceptionInput("typeViolation"));
  1312. $exp = $this->respGood(['status' => "OK"]);
  1313. // verify the above are in fact no-ops
  1314. for ($a = 0; $a < sizeof($in1); $a++) {
  1315. $this->assertMessage($exp, $this->req($in1[$a]), "Test $a failed");
  1316. }
  1317. Phake::verify(Arsse::$db, Phake::times(0))->articleMark;
  1318. // verify the simple contexts
  1319. for ($a = 0; $a < sizeof($in2); $a++) {
  1320. $this->assertMessage($exp, $this->req($in2[$a]), "Test $a failed");
  1321. }
  1322. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], new Context);
  1323. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->starred(true));
  1324. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1088));
  1325. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(2112));
  1326. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folder(42));
  1327. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folderShallow(0));
  1328. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->labelled(true));
  1329. // verify the time-based mock
  1330. $t = Date::sub("PT24H");
  1331. for ($a = 0; $a < sizeof($in3); $a++) {
  1332. $this->assertMessage($exp, $this->req($in3[$a]), "Test $a failed");
  1333. }
  1334. Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->modifiedSince($t));
  1335. }
  1336. public function testRetrieveFeedList() {
  1337. $in1 = [
  1338. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx"],
  1339. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -1],
  1340. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -1, 'unread_only' => true],
  1341. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -2],
  1342. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -2, 'unread_only' => true],
  1343. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -3],
  1344. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -3, 'unread_only' => true],
  1345. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -4],
  1346. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => -4, 'unread_only' => true],
  1347. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6],
  1348. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'limit' => 1],
  1349. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'limit' => 1, 'offset' => 1],
  1350. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 1],
  1351. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 1, 'include_nested' => true],
  1352. ];
  1353. $in2 = [
  1354. // these should all return an empty list
  1355. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 0, 'unread_only' => true],
  1356. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 2112],
  1357. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 2112, 'include_nested' => true],
  1358. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'limit' => -42],
  1359. ['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'offset' => 2],
  1360. ];
  1361. // statistical mocks
  1362. Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
  1363. Phake::when(Arsse::$db)->articleCount->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
  1364. Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(35);
  1365. // label mocks
  1366. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1367. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
  1368. // subscription and folder list and unread count mocks
  1369. Phake::when(Arsse::$db)->folderList->thenThrow(new ExceptionInput("subjectMissing"));
  1370. Phake::when(Arsse::$db)->subscriptionList->thenThrow(new ExceptionInput("subjectMissing"));
  1371. Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders)));
  1372. Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, true)->thenReturn(new Result($this->v($this->subscriptions)));
  1373. Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, false)->thenReturn(new Result($this->v($this->filterSubs(null))));
  1374. Phake::when(Arsse::$db)->folderList($this->anything(), null)->thenReturn(new Result($this->v($this->folders)));
  1375. Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->filterFolders(null))));
  1376. foreach ($this->folders as $f) {
  1377. Phake::when(Arsse::$db)->folderList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterFolders($f['id']))));
  1378. Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->folder($f['id']))->thenReturn($this->reduceFolders($f['id']));
  1379. Phake::when(Arsse::$db)->subscriptionList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterSubs($f['id']))));
  1380. }
  1381. $exp = [
  1382. [
  1383. ['id' => 6, 'title' => 'Eurogamer', 'unread' => 0, 'cat_id' => 0, 'feed_url' => " http://example.com/6", 'has_icon' => true, 'last_updated' => 1266005327, 'order_id' => 1],
  1384. ],
  1385. [
  1386. ['id' => -1, 'title' => "Starred articles", 'unread' => "4", 'cat_id' => -1],
  1387. ['id' => -2, 'title' => "Published articles", 'unread' => "0", 'cat_id' => -1],
  1388. ['id' => -3, 'title' => "Fresh articles", 'unread' => "7", 'cat_id' => -1],
  1389. ['id' => -4, 'title' => "All articles", 'unread' => "35", 'cat_id' => -1],
  1390. ['id' => -6, 'title' => "Recently read", 'unread' => 0, 'cat_id' => -1],
  1391. ['id' => 0, 'title' => "Archived articles", 'unread' => "0", 'cat_id' => -1],
  1392. ],
  1393. [
  1394. ['id' => -1, 'title' => "Starred articles", 'unread' => "4", 'cat_id' => -1],
  1395. ['id' => -3, 'title' => "Fresh articles", 'unread' => "7", 'cat_id' => -1],
  1396. ['id' => -4, 'title' => "All articles", 'unread' => "35", 'cat_id' => -1],
  1397. ],
  1398. [
  1399. ['id' => -1027, 'title' => "Fascinating", 'unread' => "6", 'cat_id' => -2],
  1400. ['id' => -1025, 'title' => "Logical", 'unread' => "0", 'cat_id' => -2],
  1401. ],
  1402. [
  1403. ['id' => -1027, 'title' => "Fascinating", 'unread' => "6", 'cat_id' => -2],
  1404. ],
  1405. [
  1406. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1407. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 2],
  1408. ['id' => 6, 'title' => 'Eurogamer', 'unread' => 0, 'cat_id' => 0, 'feed_url' => " http://example.com/6", 'has_icon' => true, 'last_updated' => 1266005327, 'order_id' => 3],
  1409. ['id' => 1, 'title' => 'NASA JPL', 'unread' => 5, 'cat_id' => 2, 'feed_url' => " http://example.com/1", 'has_icon' => false, 'last_updated' => 1505516056, 'order_id' => 4],
  1410. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 5],
  1411. ['id' => 2, 'title' => 'Toronto Star', 'unread' => 10, 'cat_id' => 5, 'feed_url' => " http://example.com/2", 'has_icon' => true, 'last_updated' => 1321009871, 'order_id' => 6],
  1412. ],
  1413. [
  1414. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1415. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 2],
  1416. ['id' => 1, 'title' => 'NASA JPL', 'unread' => 5, 'cat_id' => 2, 'feed_url' => " http://example.com/1", 'has_icon' => false, 'last_updated' => 1505516056, 'order_id' => 4],
  1417. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 5],
  1418. ['id' => 2, 'title' => 'Toronto Star', 'unread' => 10, 'cat_id' => 5, 'feed_url' => " http://example.com/2", 'has_icon' => true, 'last_updated' => 1321009871, 'order_id' => 6],
  1419. ],
  1420. [
  1421. ['id' => -1027, 'title' => "Fascinating", 'unread' => "6", 'cat_id' => -2],
  1422. ['id' => -1025, 'title' => "Logical", 'unread' => "0", 'cat_id' => -2],
  1423. ['id' => -1, 'title' => "Starred articles", 'unread' => "4", 'cat_id' => -1],
  1424. ['id' => -2, 'title' => "Published articles", 'unread' => "0", 'cat_id' => -1],
  1425. ['id' => -3, 'title' => "Fresh articles", 'unread' => "7", 'cat_id' => -1],
  1426. ['id' => -4, 'title' => "All articles", 'unread' => "35", 'cat_id' => -1],
  1427. ['id' => -6, 'title' => "Recently read", 'unread' => 0, 'cat_id' => -1],
  1428. ['id' => 0, 'title' => "Archived articles", 'unread' => "0", 'cat_id' => -1],
  1429. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1430. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 2],
  1431. ['id' => 6, 'title' => 'Eurogamer', 'unread' => 0, 'cat_id' => 0, 'feed_url' => " http://example.com/6", 'has_icon' => true, 'last_updated' => 1266005327, 'order_id' => 3],
  1432. ['id' => 1, 'title' => 'NASA JPL', 'unread' => 5, 'cat_id' => 2, 'feed_url' => " http://example.com/1", 'has_icon' => false, 'last_updated' => 1505516056, 'order_id' => 4],
  1433. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 5],
  1434. ['id' => 2, 'title' => 'Toronto Star', 'unread' => 10, 'cat_id' => 5, 'feed_url' => " http://example.com/2", 'has_icon' => true, 'last_updated' => 1321009871, 'order_id' => 6],
  1435. ],
  1436. [
  1437. ['id' => -1027, 'title' => "Fascinating", 'unread' => "6", 'cat_id' => -2],
  1438. ['id' => -1, 'title' => "Starred articles", 'unread' => "4", 'cat_id' => -1],
  1439. ['id' => -3, 'title' => "Fresh articles", 'unread' => "7", 'cat_id' => -1],
  1440. ['id' => -4, 'title' => "All articles", 'unread' => "35", 'cat_id' => -1],
  1441. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1442. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 2],
  1443. ['id' => 1, 'title' => 'NASA JPL', 'unread' => 5, 'cat_id' => 2, 'feed_url' => " http://example.com/1", 'has_icon' => false, 'last_updated' => 1505516056, 'order_id' => 4],
  1444. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 5],
  1445. ['id' => 2, 'title' => 'Toronto Star', 'unread' => 10, 'cat_id' => 5, 'feed_url' => " http://example.com/2", 'has_icon' => true, 'last_updated' => 1321009871, 'order_id' => 6],
  1446. ],
  1447. [
  1448. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 1],
  1449. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 2],
  1450. ],
  1451. [
  1452. ['id' => 4, 'title' => 'CBC News', 'unread' => 6, 'cat_id' => 6, 'feed_url' => " http://example.com/4", 'has_icon' => true, 'last_updated' => 1507564714, 'order_id' => 1],
  1453. ],
  1454. [
  1455. ['id' => 5, 'title' => 'Ottawa Citizen', 'unread' => 12, 'cat_id' => 6, 'feed_url' => " http://example.com/5", 'has_icon' => false, 'last_updated' => 1499447237, 'order_id' => 2],
  1456. ],
  1457. [
  1458. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1459. ],
  1460. [
  1461. ['id' => 2, 'title' => "Rocketry", 'unread' => 5, 'is_cat' => true, 'order_id' => 1],
  1462. ['id' => 3, 'title' => 'Ars Technica', 'unread' => 2, 'cat_id' => 1, 'feed_url' => " http://example.com/3", 'has_icon' => true, 'last_updated' => 1463985602, 'order_id' => 1],
  1463. ],
  1464. ];
  1465. for ($a = 0; $a < sizeof($in1); $a++) {
  1466. $this->assertMessage($this->respGood($exp[$a]), $this->req($in1[$a]), "Test $a failed");
  1467. }
  1468. for ($a = 0; $a < sizeof($in2); $a++) {
  1469. $this->assertMessage($this->respGood([]), $this->req($in2[$a]), "Test $a failed");
  1470. }
  1471. }
  1472. protected function filterFolders(int $id = null): array {
  1473. return array_filter($this->folders, function($value) use ($id) {
  1474. return $value['parent']==$id;
  1475. });
  1476. }
  1477. protected function filterSubs(int $folder = null): array {
  1478. return array_filter($this->subscriptions, function($value) use ($folder) {
  1479. return $value['folder']==$folder;
  1480. });
  1481. }
  1482. protected function reduceFolders(int $id = null) : int {
  1483. $out = 0;
  1484. foreach ($this->filterFolders($id) as $f) {
  1485. $out += $this->reduceFolders($f['id']);
  1486. }
  1487. $out += array_reduce(array_filter($this->subscriptions, function($value) use ($id) {
  1488. return $value['folder']==$id;
  1489. }), function($sum, $value) {
  1490. return $sum + $value['unread'];
  1491. }, 0);
  1492. return $out;
  1493. }
  1494. public function testChangeArticles() {
  1495. $in = [
  1496. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx"],
  1497. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1"],
  1498. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 0],
  1499. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 0, 'mode' => 0],
  1500. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 0, 'mode' => 1],
  1501. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 0, 'mode' => 2],
  1502. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 0, 'mode' => 3], // invalid mode
  1503. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 1], // Published feed' no-op
  1504. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 1, 'mode' => 0],
  1505. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 1, 'mode' => 1],
  1506. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 1, 'mode' => 2],
  1507. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 1, 'mode' => 3], // invalid mode
  1508. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 2],
  1509. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 2, 'mode' => 0],
  1510. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 2, 'mode' => 1],
  1511. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 2, 'mode' => 2],
  1512. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 2, 'mode' => 3], // invalid mode
  1513. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3],
  1514. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3, 'mode' => 0],
  1515. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3, 'mode' => 1],
  1516. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3, 'mode' => 2],
  1517. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3, 'mode' => 3], // invalid mode
  1518. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 3, 'data' => "eh"],
  1519. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 4], // invalid field
  1520. ['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "0, -1", 'field' => 3], // no valid IDs
  1521. ];
  1522. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(true), $this->anything())->thenReturn(new Result($this->v([['id' => 42]])));
  1523. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(false), $this->anything())->thenReturn(new Result($this->v([['id' => 2112]])));
  1524. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(true), $this->anything())->thenReturn(new Result($this->v([['id' => 42]])));
  1525. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(false), $this->anything())->thenReturn(new Result($this->v([['id' => 2112]])));
  1526. Phake::when(Arsse::$db)->articleMark->thenReturn(1);
  1527. Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => false], (new Context)->articles([42, 2112]))->thenReturn(2);
  1528. Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => true], (new Context)->articles([42, 2112]))->thenReturn(4);
  1529. Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => false], (new Context)->articles([42]))->thenReturn(8);
  1530. Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => true], (new Context)->articles([2112]))->thenReturn(16);
  1531. Phake::when(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->articles([42, 2112]))->thenReturn(32); // false is read for TT-RSS
  1532. Phake::when(Arsse::$db)->articleMark($this->anything(), ['read' => false], (new Context)->articles([42, 2112]))->thenReturn(64);
  1533. Phake::when(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->articles([42]))->thenReturn(128);
  1534. Phake::when(Arsse::$db)->articleMark($this->anything(), ['read' => false], (new Context)->articles([2112]))->thenReturn(256);
  1535. Phake::when(Arsse::$db)->articleMark($this->anything(), ['note' => ""], (new Context)->articles([42, 2112]))->thenReturn(512);
  1536. Phake::when(Arsse::$db)->articleMark($this->anything(), ['note' => "eh"], (new Context)->articles([42, 2112]))->thenReturn(1024);
  1537. $out = [
  1538. $this->respErr("INCORRECT_USAGE"),
  1539. $this->respGood(['status' => "OK", 'updated' => 2]),
  1540. $this->respGood(['status' => "OK", 'updated' => 2]),
  1541. $this->respGood(['status' => "OK", 'updated' => 2]),
  1542. $this->respGood(['status' => "OK", 'updated' => 4]),
  1543. $this->respGood(['status' => "OK", 'updated' => 24]),
  1544. $this->respErr("INCORRECT_USAGE"),
  1545. $this->respGood(['status' => "OK", 'updated' => 0]),
  1546. $this->respGood(['status' => "OK", 'updated' => 0]),
  1547. $this->respGood(['status' => "OK", 'updated' => 0]),
  1548. $this->respGood(['status' => "OK", 'updated' => 0]),
  1549. $this->respErr("INCORRECT_USAGE"),
  1550. $this->respGood(['status' => "OK", 'updated' => 32]),
  1551. $this->respGood(['status' => "OK", 'updated' => 32]),
  1552. $this->respGood(['status' => "OK", 'updated' => 64]),
  1553. $this->respGood(['status' => "OK", 'updated' => 384]),
  1554. $this->respErr("INCORRECT_USAGE"),
  1555. $this->respGood(['status' => "OK", 'updated' => 512]),
  1556. $this->respGood(['status' => "OK", 'updated' => 512]),
  1557. $this->respGood(['status' => "OK", 'updated' => 512]),
  1558. $this->respGood(['status' => "OK", 'updated' => 512]),
  1559. $this->respGood(['status' => "OK", 'updated' => 512]),
  1560. $this->respGood(['status' => "OK", 'updated' => 1024]),
  1561. $this->respErr("INCORRECT_USAGE"),
  1562. $this->respErr("INCORRECT_USAGE"),
  1563. ];
  1564. for ($a = 0; $a < sizeof($in); $a++) {
  1565. $this->assertMessage($out[$a], $this->req($in[$a]), "Test $a failed");
  1566. }
  1567. }
  1568. public function testListArticles() {
  1569. $in = [
  1570. // error conditions
  1571. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx"],
  1572. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => 0],
  1573. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => -1],
  1574. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "0,-1"],
  1575. // acceptable input
  1576. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "101,102"],
  1577. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "101"],
  1578. ['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "102"],
  1579. ];
  1580. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1581. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
  1582. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 101)->thenReturn([]);
  1583. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 102)->thenReturn($this->v([1,3]));
  1584. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101, 102]), $this->anything())->thenReturn(new Result($this->v($this->articles)));
  1585. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101]), $this->anything())->thenReturn(new Result($this->v([$this->articles[0]])));
  1586. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([102]), $this->anything())->thenReturn(new Result($this->v([$this->articles[1]])));
  1587. $exp = $this->respErr("INCORRECT_USAGE");
  1588. $this->assertMessage($exp, $this->req($in[0]));
  1589. $this->assertMessage($exp, $this->req($in[1]));
  1590. $this->assertMessage($exp, $this->req($in[2]));
  1591. $this->assertMessage($exp, $this->req($in[3]));
  1592. $exp = [
  1593. [
  1594. 'id' => "101",
  1595. 'guid' => null,
  1596. 'title' => 'Article title 1',
  1597. 'link' => 'http://example.com/1',
  1598. 'labels' => [],
  1599. 'unread' => true,
  1600. 'marked' => false,
  1601. 'published' => false,
  1602. 'comments' => "",
  1603. 'author' => '',
  1604. 'updated' => strtotime('2000-01-01T00:00:01Z'),
  1605. 'feed_id' => "8",
  1606. 'feed_title' => "Feed 11",
  1607. 'attachments' => [],
  1608. 'score' => 0,
  1609. 'note' => null,
  1610. 'lang' => "",
  1611. 'content' => '<p>Article content 1</p>',
  1612. ],
  1613. [
  1614. 'id' => "102",
  1615. 'guid' => "SHA256:5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7",
  1616. 'title' => 'Article title 2',
  1617. 'link' => 'http://example.com/2',
  1618. 'labels' => [
  1619. [-1025, "Logical", "", ""],
  1620. [-1027, "Fascinating", "", ""],
  1621. ],
  1622. 'unread' => false,
  1623. 'marked' => false,
  1624. 'published' => false,
  1625. 'comments' => "",
  1626. 'author' => "J. King",
  1627. 'updated' => strtotime('2000-01-02T00:00:02Z'),
  1628. 'feed_id' => "8",
  1629. 'feed_title' => "Feed 11",
  1630. 'attachments' => [
  1631. [
  1632. 'id' => "0",
  1633. 'content_url' => "http://example.com/text",
  1634. 'content_type' => "text/plain",
  1635. 'title' => "",
  1636. 'duration' => "",
  1637. 'width' => "",
  1638. 'height' => "",
  1639. 'post_id' => "102",
  1640. ],
  1641. ],
  1642. 'score' => 0,
  1643. 'note' => "Note 2",
  1644. 'lang' => "",
  1645. 'content' => '<p>Article content 2</p>',
  1646. ],
  1647. ];
  1648. $this->assertMessage($this->respGood($exp), $this->req($in[4]));
  1649. $this->assertMessage($this->respGood([$exp[0]]), $this->req($in[5]));
  1650. $this->assertMessage($this->respGood([$exp[1]]), $this->req($in[6]));
  1651. // test the special case when labels are not used
  1652. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result([]));
  1653. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result([]));
  1654. $this->assertMessage($this->respGood([$exp[0]]), $this->req($in[5]));
  1655. }
  1656. public function testRetrieveCompactHeadlines() {
  1657. $in1 = [
  1658. // erroneous input
  1659. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"],
  1660. // empty results
  1661. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0],
  1662. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2],
  1663. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true], // is_cat is not used in getCompactHeadlines
  1664. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
  1665. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "published"],
  1666. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "unread"],
  1667. // non-empty results
  1668. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4],
  1669. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
  1670. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
  1671. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "adaptive"],
  1672. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "adaptive"],
  1673. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "unread"],
  1674. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "marked"],
  1675. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "has_note"],
  1676. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5],
  1677. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'skip' => 2],
  1678. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5, 'skip' => 2],
  1679. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'since_id' => 47],
  1680. ];
  1681. $in2 = [
  1682. // time-based contexts, handled separately
  1683. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6],
  1684. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "adaptive"],
  1685. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
  1686. ['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
  1687. ];
  1688. Phake::when(Arsse::$db)->articleList->thenReturn(new Result($this->v([['id' => 0]])));
  1689. Phake::when(Arsse::$db)->articleCount->thenReturn(0);
  1690. Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
  1691. $c = (new Context)->reverse(true);
  1692. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), ["id"])->thenThrow(new ExceptionInput("subjectMissing"));
  1693. Phake::when(Arsse::$db)->articleList($this->anything(), $c, ["id"])->thenReturn(new Result($this->v($this->articles)));
  1694. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), ["id"])->thenReturn(new Result($this->v([['id' => 1]])));
  1695. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), ["id"])->thenReturn(new Result($this->v([['id' => 2]])));
  1696. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), ["id"])->thenReturn(new Result($this->v([['id' => 3]])));
  1697. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), ["id"])->thenReturn(new Result($this->v([['id' => 4]])));
  1698. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), ["id"])->thenReturn(new Result($this->v([['id' => 5]])));
  1699. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), ["id"])->thenReturn(new Result($this->v([['id' => 6]])));
  1700. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), ["id"])->thenReturn(new Result($this->v([['id' => 7]])));
  1701. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), ["id"])->thenReturn(new Result($this->v([['id' => 8]])));
  1702. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), ["id"])->thenReturn(new Result($this->v([['id' => 9]])));
  1703. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), ["id"])->thenReturn(new Result($this->v([['id' => 10]])));
  1704. $out1 = [
  1705. $this->respErr("INCORRECT_USAGE"),
  1706. $this->respGood([]),
  1707. $this->respGood([]),
  1708. $this->respGood([]),
  1709. $this->respGood([]),
  1710. $this->respGood([]),
  1711. $this->respGood([]),
  1712. $this->respGood([['id' => 101],['id' => 102]]),
  1713. $this->respGood([['id' => 1]]),
  1714. $this->respGood([['id' => 2]]),
  1715. $this->respGood([['id' => 3]]),
  1716. $this->respGood([['id' => 2]]), // the result is 2 rather than 4 because there are no unread, so the unread context is not used
  1717. $this->respGood([['id' => 4]]),
  1718. $this->respGood([['id' => 5]]),
  1719. $this->respGood([['id' => 6]]),
  1720. $this->respGood([['id' => 7]]),
  1721. $this->respGood([['id' => 8]]),
  1722. $this->respGood([['id' => 9]]),
  1723. $this->respGood([['id' => 10]]),
  1724. ];
  1725. $out2 = [
  1726. $this->respGood([['id' => 1001]]),
  1727. $this->respGood([['id' => 1001]]),
  1728. $this->respGood([['id' => 1002]]),
  1729. $this->respGood([['id' => 1003]]),
  1730. ];
  1731. for ($a = 0; $a < sizeof($in1); $a++) {
  1732. $this->assertMessage($out1[$a], $this->req($in1[$a]), "Test $a failed");
  1733. }
  1734. for ($a = 0; $a < sizeof($in2); $a++) {
  1735. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(false)->markedSince(Date::sub("PT24H")), ["id"])->thenReturn(new Result($this->v([['id' => 1001]])));
  1736. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), ["id"])->thenReturn(new Result($this->v([['id' => 1002]])));
  1737. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), ["id"])->thenReturn(new Result($this->v([['id' => 1003]])));
  1738. $this->assertMessage($out2[$a], $this->req($in2[$a]), "Test $a failed");
  1739. }
  1740. }
  1741. public function testRetrieveFullHeadlines() {
  1742. $in1 = [
  1743. // empty results
  1744. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0],
  1745. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2],
  1746. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'is_cat' => true],
  1747. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "published"],
  1748. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "unread"],
  1749. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
  1750. ];
  1751. $in2 = [
  1752. // simple context tests
  1753. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx"],
  1754. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4],
  1755. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1],
  1756. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
  1757. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'view_mode' => "adaptive"],
  1758. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "adaptive"],
  1759. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112, 'view_mode' => "unread"],
  1760. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "marked"],
  1761. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'view_mode' => "has_note"],
  1762. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5],
  1763. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'skip' => 2],
  1764. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'limit' => 5, 'skip' => 2],
  1765. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'since_id' => 47],
  1766. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'is_cat' => true],
  1767. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'is_cat' => true],
  1768. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true],
  1769. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true],
  1770. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true],
  1771. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true, 'include_nested' => true],
  1772. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'order_by' => "feed_dates"],
  1773. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'order_by' => "date_reverse"],
  1774. ];
  1775. $in3 = [
  1776. // time-based context tests
  1777. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6],
  1778. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -6, 'view_mode' => "adaptive"],
  1779. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
  1780. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
  1781. ];
  1782. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1783. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
  1784. Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
  1785. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3]));
  1786. Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
  1787. Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
  1788. Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(0));
  1789. Phake::when(Arsse::$db)->articleCount->thenReturn(0);
  1790. Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
  1791. $c = (new Context)->limit(200)->reverse(true);
  1792. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
  1793. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), $this->anything())->thenReturn($this->generateHeadlines(1));
  1794. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), $this->anything())->thenReturn($this->generateHeadlines(2));
  1795. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), $this->anything())->thenReturn($this->generateHeadlines(3));
  1796. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), $this->anything())->thenReturn($this->generateHeadlines(4));
  1797. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), $this->anything())->thenReturn($this->generateHeadlines(5));
  1798. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), $this->anything())->thenReturn($this->generateHeadlines(6));
  1799. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), $this->anything())->thenReturn($this->generateHeadlines(7));
  1800. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), $this->anything())->thenReturn($this->generateHeadlines(8));
  1801. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), $this->anything())->thenReturn($this->generateHeadlines(9));
  1802. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), $this->anything())->thenReturn($this->generateHeadlines(10));
  1803. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c), $this->anything())->thenReturn($this->generateHeadlines(11));
  1804. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->labelled(true), $this->anything())->thenReturn($this->generateHeadlines(12));
  1805. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folderShallow(0), $this->anything())->thenReturn($this->generateHeadlines(13));
  1806. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folderShallow(42), $this->anything())->thenReturn($this->generateHeadlines(14));
  1807. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->folder(42), $this->anything())->thenReturn($this->generateHeadlines(15));
  1808. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->reverse(false), $this->anything())->thenReturn($this->generateHeadlines(16));
  1809. $out2 = [
  1810. $this->respErr("INCORRECT_USAGE"),
  1811. $this->outputHeadlines(11),
  1812. $this->outputHeadlines(1),
  1813. $this->outputHeadlines(2),
  1814. $this->outputHeadlines(3),
  1815. $this->outputHeadlines(2), // the result is 2 rather than 4 because there are no unread, so the unread context is not used
  1816. $this->outputHeadlines(4),
  1817. $this->outputHeadlines(5),
  1818. $this->outputHeadlines(6),
  1819. $this->outputHeadlines(7),
  1820. $this->outputHeadlines(8),
  1821. $this->outputHeadlines(9),
  1822. $this->outputHeadlines(10),
  1823. $this->outputHeadlines(11),
  1824. $this->outputHeadlines(11),
  1825. $this->outputHeadlines(12),
  1826. $this->outputHeadlines(13),
  1827. $this->outputHeadlines(14),
  1828. $this->outputHeadlines(15),
  1829. $this->outputHeadlines(11), // defaulting sorting is not fully implemented
  1830. $this->outputHeadlines(16),
  1831. ];
  1832. $out3 = [
  1833. $this->outputHeadlines(1001),
  1834. $this->outputHeadlines(1001),
  1835. $this->outputHeadlines(1002),
  1836. $this->outputHeadlines(1003),
  1837. ];
  1838. for ($a = 0; $a < sizeof($in1); $a++) {
  1839. $this->assertMessage($this->respGood([]), $this->req($in1[$a]), "Test $a failed");
  1840. }
  1841. for ($a = 0; $a < sizeof($in2); $a++) {
  1842. $this->assertMessage($out2[$a], $this->req($in2[$a]), "Test $a failed");
  1843. }
  1844. for ($a = 0; $a < sizeof($in3); $a++) {
  1845. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(false)->markedSince(Date::sub("PT24H")), $this->anything())->thenReturn($this->generateHeadlines(1001));
  1846. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), $this->anything())->thenReturn($this->generateHeadlines(1002));
  1847. Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), $this->anything())->thenReturn($this->generateHeadlines(1003));
  1848. $this->assertMessage($out3[$a], $this->req($in3[$a]), "Test $a failed");
  1849. }
  1850. }
  1851. public function testRetrieveFullHeadlinesCheckingExtraFields() {
  1852. $in = [
  1853. // empty results
  1854. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4],
  1855. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'show_content' => true],
  1856. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'include_attachments' => true],
  1857. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'include_header' => true],
  1858. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'is_cat' => true, 'include_header' => true],
  1859. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -1, 'is_cat' => true, 'include_header' => true],
  1860. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112, 'include_header' => true],
  1861. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'include_header' => true, 'order_by' => "date_reverse"],
  1862. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'skip' => 47, 'include_header' => true],
  1863. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'skip' => 47, 'include_header' => true, 'order_by' => "date_reverse"],
  1864. ['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'show_excerpt' => true],
  1865. ];
  1866. Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
  1867. Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
  1868. Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
  1869. Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3]));
  1870. Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
  1871. Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
  1872. Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(1));
  1873. Phake::when(Arsse::$db)->articleCount->thenReturn(0);
  1874. Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
  1875. // sanity check; this makes sure extra fields are not included in default situations
  1876. $test = $this->req($in[0]);
  1877. $this->assertMessage($this->outputHeadlines(1), $test);
  1878. // test 'show_content'
  1879. $test = $this->req($in[1]);
  1880. $this->assertArrayHasKey("content", $test->getPayload()['content'][0]);
  1881. $this->assertArrayHasKey("content", $test->getPayload()['content'][1]);
  1882. foreach ($this->generateHeadlines(1) as $key => $row) {
  1883. $this->assertSame($row['content'], $test->getPayload()['content'][$key]['content']);
  1884. }
  1885. // test 'include_attachments'
  1886. $test = $this->req($in[2]);
  1887. $exp = [
  1888. [
  1889. 'id' => "0",
  1890. 'content_url' => "http://example.com/text",
  1891. 'content_type' => "text/plain",
  1892. 'title' => "",
  1893. 'duration' => "",
  1894. 'width' => "",
  1895. 'height' => "",
  1896. 'post_id' => "2112",
  1897. ],
  1898. ];
  1899. $this->assertArrayHasKey("attachments", $test->getPayload()['content'][0]);
  1900. $this->assertArrayHasKey("attachments", $test->getPayload()['content'][1]);
  1901. $this->assertSame([], $test->getPayload()['content'][0]['attachments']);
  1902. $this->assertSame($exp, $test->getPayload()['content'][1]['attachments']);
  1903. // test 'include_header'
  1904. $test = $this->req($in[3]);
  1905. $exp = $this->respGood([
  1906. ['id' => -4, 'is_cat' => false, 'first_id' => 1],
  1907. $this->outputHeadlines(1)->getPayload()['content'],
  1908. ]);
  1909. $this->assertMessage($exp, $test);
  1910. // test 'include_header' with a category
  1911. $test = $this->req($in[4]);
  1912. $exp = $this->respGood([
  1913. ['id' => -3, 'is_cat' => true, 'first_id' => 1],
  1914. $this->outputHeadlines(1)->getPayload()['content'],
  1915. ]);
  1916. $this->assertMessage($exp, $test);
  1917. // test 'include_header' with an empty result
  1918. $test = $this->req($in[5]);
  1919. $exp = $this->respGood([
  1920. ['id' => -1, 'is_cat' => true, 'first_id' => 0],
  1921. [],
  1922. ]);
  1923. $this->assertMessage($exp, $test);
  1924. // test 'include_header' with an erroneous result
  1925. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(200)->reverse(true)->subscription(2112), $this->anything())->thenThrow(new ExceptionInput("subjectMissing"));
  1926. $test = $this->req($in[6]);
  1927. $exp = $this->respGood([
  1928. ['id' => 2112, 'is_cat' => false, 'first_id' => 0],
  1929. [],
  1930. ]);
  1931. $this->assertMessage($exp, $test);
  1932. // test 'include_header' with ascending order
  1933. $test = $this->req($in[7]);
  1934. $exp = $this->respGood([
  1935. ['id' => -4, 'is_cat' => false, 'first_id' => 0],
  1936. $this->outputHeadlines(1)->getPayload()['content'],
  1937. ]);
  1938. $this->assertMessage($exp, $test);
  1939. // test 'include_header' with skip
  1940. Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->reverse(true)->limit(1)->subscription(42), $this->anything())->thenReturn($this->generateHeadlines(1867));
  1941. $test = $this->req($in[8]);
  1942. $exp = $this->respGood([
  1943. ['id' => 42, 'is_cat' => false, 'first_id' => 1867],
  1944. $this->outputHeadlines(1)->getPayload()['content'],
  1945. ]);
  1946. $this->assertMessage($exp, $test);
  1947. // test 'include_header' with skip and ascending order
  1948. $test = $this->req($in[9]);
  1949. $exp = $this->respGood([
  1950. ['id' => 42, 'is_cat' => false, 'first_id' => 0],
  1951. $this->outputHeadlines(1)->getPayload()['content'],
  1952. ]);
  1953. $this->assertMessage($exp, $test);
  1954. // test 'show_excerpt'
  1955. $exp1 = "“This & that, you know‽”";
  1956. $exp2 = "Pour vous faire mieux connaitre d’ou\u{300} vient l’erreur de ceux qui bla\u{302}ment la volupte\u{301}, et qui louent en…";
  1957. $test = $this->req($in[10]);
  1958. $this->assertArrayHasKey("excerpt", $test->getPayload()['content'][0]);
  1959. $this->assertArrayHasKey("excerpt", $test->getPayload()['content'][1]);
  1960. $this->assertSame($exp1, $test->getPayload()['content'][0]['excerpt']);
  1961. $this->assertSame($exp2, $test->getPayload()['content'][1]['excerpt']);
  1962. }
  1963. protected function generateHeadlines(int $id): Result {
  1964. return new Result($this->v([
  1965. [
  1966. 'id' => $id,
  1967. 'url' => 'http://example.com/1',
  1968. 'title' => 'Article title 1',
  1969. 'subscription_title' => "Feed 2112",
  1970. 'author' => '',
  1971. 'content' => '<p>&ldquo;This &amp; that, you know&#8253;&rdquo;</p>',
  1972. 'guid' => null,
  1973. 'published_date' => '2000-01-01 00:00:00',
  1974. 'edited_date' => '2000-01-01 00:00:00',
  1975. 'modified_date' => '2000-01-01 01:00:00',
  1976. 'unread' => 0,
  1977. 'starred' => 0,
  1978. 'edition' => 101,
  1979. 'subscription' => 12,
  1980. 'fingerprint' => 'f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6:fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4:18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',
  1981. 'media_url' => null,
  1982. 'media_type' => null,
  1983. 'note' => "",
  1984. ],
  1985. [
  1986. 'id' => 2112,
  1987. 'url' => 'http://example.com/2',
  1988. 'title' => 'Article title 2',
  1989. 'subscription_title' => "Feed 11",
  1990. 'author' => 'J. King',
  1991. 'content' => $this->richContent,
  1992. 'guid' => '5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7',
  1993. 'published_date' => '2000-01-02 00:00:00',
  1994. 'edited_date' => '2000-01-02 00:00:02',
  1995. 'modified_date' => '2000-01-02 02:00:00',
  1996. 'unread' => 1,
  1997. 'starred' => 1,
  1998. 'edition' => 202,
  1999. 'subscription' => 8,
  2000. 'fingerprint' => '0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153:13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9:2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',
  2001. 'media_url' => "http://example.com/text",
  2002. 'media_type' => "text/plain",
  2003. 'note' => "Note 2",
  2004. ],
  2005. ]));
  2006. }
  2007. protected function outputHeadlines(int $id): Response {
  2008. return $this->respGood([
  2009. [
  2010. 'id' => $id,
  2011. 'guid' => '',
  2012. 'title' => 'Article title 1',
  2013. 'link' => 'http://example.com/1',
  2014. 'labels' => [],
  2015. 'unread' => false,
  2016. 'marked' => false,
  2017. 'published' => false,
  2018. 'author' => '',
  2019. 'updated' => strtotime('2000-01-01T00:00:00Z'),
  2020. 'is_updated' => false,
  2021. 'feed_id' => "12",
  2022. 'feed_title' => "Feed 2112",
  2023. 'score' => 0,
  2024. 'note' => null,
  2025. 'lang' => "",
  2026. 'tags' => [],
  2027. 'comments_count' => 0,
  2028. 'comments_link' => "",
  2029. 'always_display_attachments' => false,
  2030. ],
  2031. [
  2032. 'id' => 2112,
  2033. 'guid' => "SHA256:5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7",
  2034. 'title' => 'Article title 2',
  2035. 'link' => 'http://example.com/2',
  2036. 'labels' => [
  2037. [-1025, "Logical", "", ""],
  2038. [-1027, "Fascinating", "", ""],
  2039. ],
  2040. 'unread' => true,
  2041. 'marked' => true,
  2042. 'published' => false,
  2043. 'author' => "J. King",
  2044. 'updated' => strtotime('2000-01-02T00:00:02Z'),
  2045. 'is_updated' => true,
  2046. 'feed_id' => "8",
  2047. 'feed_title' => "Feed 11",
  2048. 'score' => 0,
  2049. 'note' => "Note 2",
  2050. 'lang' => "",
  2051. 'tags' => ["Boring", "Illogical"],
  2052. 'comments_count' => 0,
  2053. 'comments_link' => "",
  2054. 'always_display_attachments' => false,
  2055. ],
  2056. ]);
  2057. }
  2058. }