*/ class TestOPML extends \JKingWeb\Arsse\Test\AbstractTest { protected $folders = [ ['id' => 5, 'parent' => 3, 'children' => 0, 'feeds' => 1, 'name' => "Local"], ['id' => 6, 'parent' => 3, 'children' => 0, 'feeds' => 2, 'name' => "National"], ['id' => 4, 'parent' => null, 'children' => 0, 'feeds' => 0, 'name' => "Photography"], ['id' => 3, 'parent' => null, 'children' => 2, 'feeds' => 0, 'name' => "Politics"], ['id' => 2, 'parent' => 1, 'children' => 0, 'feeds' => 1, 'name' => "Rocketry"], ['id' => 1, 'parent' => null, 'children' => 1, 'feeds' => 1, 'name' => "Science"], ]; protected $subscriptions = [ ['id' => 3, 'folder' => 1, 'top_folder' => 1, 'unread' => 2, 'updated' => "2016-05-23 06:40:02", 'err_msg' => 'argh', 'title' => 'Ars Technica', 'url' => "http://localhost:8000/3", 'favicon' => 'http://localhost:8000/3.png'], ['id' => 4, 'folder' => 6, 'top_folder' => 3, 'unread' => 6, 'updated' => "2017-10-09 15:58:34", 'err_msg' => '', 'title' => 'CBC News', 'url' => "http://localhost:8000/4", 'favicon' => 'http://localhost:8000/4.png'], ['id' => 6, 'folder' => null, 'top_folder' => null, 'unread' => 0, 'updated' => "2010-02-12 20:08:47", 'err_msg' => '', 'title' => 'Eurogamer', 'url' => "http://localhost:8000/6", 'favicon' => 'http://localhost:8000/6.png'], ['id' => 1, 'folder' => 2, 'top_folder' => 1, 'unread' => 5, 'updated' => "2017-09-15 22:54:16", 'err_msg' => '', 'title' => 'NASA JPL', 'url' => "http://localhost:8000/1", 'favicon' => null], ['id' => 5, 'folder' => 6, 'top_folder' => 3, 'unread' => 12, 'updated' => "2017-07-07 17:07:17", 'err_msg' => '', 'title' => 'Ottawa Citizen', 'url' => "http://localhost:8000/5", 'favicon' => ''], ['id' => 2, 'folder' => 5, 'top_folder' => 3, 'unread' => 10, 'updated' => "2011-11-11 11:11:11", 'err_msg' => 'oops', 'title' => 'Toronto Star', 'url' => "http://localhost:8000/2", 'favicon' => 'http://localhost:8000/2.png'], ]; protected $tags = [ ['id' => 1, 'name' => "Canada", 'subscription' => 2], ['id' => 1, 'name' => "Canada", 'subscription' => 4], ['id' => 1, 'name' => "Canada", 'subscription' => 5], ['id' => 2, 'name' => "Politics", 'subscription' => 4], ['id' => 2, 'name' => "Politics", 'subscription' => 5], ['id' => 3, 'name' => "Science, etc", 'subscription' => 1], ['id' => 3, 'name' => "Science, etc", 'subscription' => 3], // Eurogamer is untagged ]; protected $serialization = << OPML_EXPORT_SERIALIZATION; protected $serializationFlat = << OPML_EXPORT_SERIALIZATION; public function setUp(): void { self::clearData(); Arsse::$db = \Phake::mock(\JKingWeb\Arsse\Database::class); \Phake::when(Arsse::$db)->userExists->thenReturn(true); } public function testExportToOpml(): void { \Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders)); \Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions)); \Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags)); $this->assertXmlStringEqualsXmlString($this->serialization, (new OPML)->export("john.doe@example.com")); } public function testExportToFlatOpml(): void { \Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders)); \Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions)); \Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags)); $this->assertXmlStringEqualsXmlString($this->serializationFlat, (new OPML)->export("john.doe@example.com", true)); } public function testExportToOpmlAMissingUser(): void { \Phake::when(Arsse::$db)->userExists->thenReturn(false); $this->assertException("doesNotExist", "User", "ExceptionConflict"); (new OPML)->export("john.doe@example.com"); } /** @dataProvider provideParserData */ public function testParseOpmlForImport(string $file, bool $flat, $exp): void { $data = file_get_contents(\JKingWeb\Arsse\DOCROOT."Import/OPML/$file"); // set up a partial mock to make the ImportExport::parse() method visible $parser = \Phake::makeVisible(\Phake::partialMock(OPML::class)); if ($exp instanceof \JKingWeb\Arsse\AbstractException) { $this->assertException($exp); $parser->parse($data, $flat); } else { $this->assertSame($exp, $parser->parse($data, $flat)); } } public function provideParserData(): iterable { return [ ["BrokenXML.opml", false, new Exception("invalidSyntax")], ["BrokenOPML.1.opml", false, new Exception("invalidSemantics")], ["BrokenOPML.2.opml", false, new Exception("invalidSemantics")], ["BrokenOPML.3.opml", false, new Exception("invalidSemantics")], ["BrokenOPML.4.opml", false, new Exception("invalidSemantics")], ["Empty.1.opml", false, [[], []]], ["Empty.2.opml", false, [[], []]], ["Empty.3.opml", false, [[], []]], ["FeedsOnly.opml", false, [[ ['url' => "http://localhost:8000/1", 'title' => "Feed 1", 'folder' => 0, 'tags' => []], ['url' => "http://localhost:8000/2", 'title' => "", 'folder' => 0, 'tags' => []], ['url' => "http://localhost:8000/3", 'title' => "", 'folder' => 0, 'tags' => []], ['url' => "http://localhost:8000/4", 'title' => "", 'folder' => 0, 'tags' => []], ['url' => "", 'title' => "", 'folder' => 0, 'tags' => ["whee"]], ['url' => "", 'title' => "", 'folder' => 0, 'tags' => ["whee", "whoo"]], ], []]], ["FoldersOnly.opml", true, [[], []]], ["FoldersOnly.opml", false, [[], [1 => ['id' => 1, 'name' => "Folder 1", 'parent' => 0], ['id' => 2, 'name' => "Folder 2", 'parent' => 0], ['id' => 3, 'name' => "Also a folder", 'parent' => 2], ['id' => 4, 'name' => "Still a folder", 'parent' => 2], ['id' => 5, 'name' => "Folder 5", 'parent' => 4], ['id' => 6, 'name' => "Folder 6", 'parent' => 0], ]]], ["MixedContent.opml", false, [[ ['url' => "https://www.jpl.nasa.gov/multimedia/rss/news.xml", 'title' => "NASA JPL", 'folder' => 3, 'tags' => ["tech"]], ['url' => "http://feeds.arstechnica.com/arstechnica/index/", 'title' => "Ars Technica", 'folder' => 2, 'tags' => ["frequent", "tech"]], ['url' => "https://www.thestar.com/content/thestar/feed.RSSManagerServlet.topstories.rss", 'title' => "Toronto Star", 'folder' => 5, 'tags' => ["news", "canada", "toronto"]], ['url' => "http://rss.canada.com/get/?F239", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]], ['url' => "https://www.eurogamer.net/?format=rss", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]], ], [1 => ['id' => 1, 'name' => "Photography", 'parent' => 0], ['id' => 2, 'name' => "Science", 'parent' => 0], ['id' => 3, 'name' => "Rocketry", 'parent' => 2], ['id' => 4, 'name' => "Politics", 'parent' => 0], ['id' => 5, 'name' => "Local", 'parent' => 4], ['id' => 6, 'name' => "National", 'parent' => 4], ]]], ]; } }