From ee9d8c1ceb86bec05e6fd67df7bfe60eb8fda591 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Thu, 22 Jun 2017 13:07:56 -0400 Subject: [PATCH] First tests for articleList Fixed many errors in queries: - Prepared statement wasn't running at all :) - Returning feed ID rather than subscription ID - latestEdition wasn't matching --- lib/Database.php | 47 +++-- .../Database/TestDatabaseArticleSQLite3.php | 9 + tests/lib/Database/SeriesArticle.php | 197 ++++++++++++++++++ tests/phpunit.xml | 1 + 4 files changed, 232 insertions(+), 22 deletions(-) create mode 100644 tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php create mode 100644 tests/lib/Database/SeriesArticle.php diff --git a/lib/Database.php b/lib/Database.php index 13587f6f..5f6e2469 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -619,9 +619,9 @@ class Database { if(!$context) $context = new Context; $q = new Query( "SELECT - arsse_articles.id, - arsse_articles.url, - title,author,content,feed,guid, + arsse_articles.id as id, + arsse_articles.url as url, + title,author,content,guid, DATEFORMAT(?, published) as published, DATEFORMAT(?, edited) as edited, DATEFORMAT(?, max( @@ -630,7 +630,8 @@ class Database { )) as modified, NOT (SELECT count(*) from arsse_marks join user on user is owner where article is arsse_articles.id and read is 1) as unread, (SELECT count(*) from arsse_marks join user on user is owner where article is arsse_articles.id and starred is 1) as starred, - (SELECT max(id) from arsse_editions where article is arsse_articles.id) as latestEdition, + (SELECT max(id) from arsse_editions where article is arsse_articles.id) as edition, + subscribed_feeds.sub as subscription, url_title_hash||':'||url_content_hash||':'||title_content_hash as fingerprint, arsse_enclosures.url as media_url, arsse_enclosures.type as media_type @@ -639,7 +640,7 @@ class Database { left join arsse_enclosures on arsse_enclosures.article is arsse_articles.id ", "", // WHERE clause - "latestEdition".(!$context->reverse ? " desc" : ""), // ORDER BY clause + "edition".(!$context->reverse ? " desc" : ""), // ORDER BY clause $context->limit, $context->offset ); @@ -648,31 +649,29 @@ class Database { // if a subscription is specified, make sure it exists $id = $this->subscriptionValidateId($user, $context->subscription)['feed']; // add a basic CTE that will join in only the requested subscription - $q->setCTE("subscribed_feeds(id) as (SELECT ?)", "int", $id); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT ?,?)", ["int","int"], [$id,$context->subscription]); } else if($context->folder()) { // if a folder is specified, make sure it exists $this->folderValidateId($user, $context->folder); // if it does exist, add a common table expression to list it and its children so that we select from the entire subtree $q->setCTE("folders(folder) as (SELECT ? union select id from arsse_folders join folders on parent is folder)", "int", $context->folder); // add another CTE for the subscriptions within the folder - $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner join folders on arsse_subscription.folder is folders.folder)"); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder)"); } else { // otherwise add a CTE for all the user's subscriptions - $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner)"); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner)"); } // filter based on edition offset - if($context->oldestEdition()) { - $q->setWhere("latestEdition >= ?", "int", $context->oldestEdition); - } else if($context->latestEdition()) { - $q->setWhere("latestEdition <= ?", "int", $context->oldestEdition); - } + if($context->oldestEdition()) $q->setWhere("edition >= ?", "int", $context->oldestEdition); + if($context->latestEdition()) $q->setWhere("edition <= ?", "int", $context->latestEdition); // filter based on lastmod time if($context->modifiedSince()) $q->setWhere("modified >= ?", "datetime", $context->modifiedSince); + if($context->notModifiedSince()) $q->setWhere("modified <= ?", "datetime", $context->notModifiedSince); // filter for un/read and un/starred status if specified if($context->unread()) $q->setWhere("unread is ?", "bool", $context->unread); if($context->starred()) $q->setWhere("starred is ?", "bool", $context->starred); // perform the query and return results - return $this->db->prepare($q, "str", "str", "str")-run($this->dateFormatDefault, $this->dateFormatDefault, $this->dateFormatDefault); + return $this->db->prepare($q, "str", "str", "str")->run($this->dateFormatDefault, $this->dateFormatDefault, $this->dateFormatDefault); } public function articlePropertiesSet(string $user, array $data, Context $context = null): bool { @@ -712,8 +711,11 @@ class Database { $q = new Query( "SELECT arsse_articles.id as id, - max(arsse_editions.id) as latestEdition - (select count(*) from arsse_marks join user on user is owner where article is arsse_articles.id) as exists + max(arsse_editions.id) as edition + (select count(*) from arsse_marks join user on user is owner where article is arsse_articles.id) as exists, + max(modified, + coalesce((SELECT modified from arsse_marks join user on user is owner where article is arsse_articles.id),'') + ) as modified, FROM arsse_articles join subscribed_feeds on feed is subscribed_feeds.id join arsse_editions on arsse_articles.id is arsse_editions.article @@ -724,26 +726,27 @@ class Database { // if a subscription is specified, make sure it exists $id = $this->subscriptionValidateId($user, $context->subscription)['feed']; // add a basic CTE that will join in only the requested subscription - $q->setCTE("subscribed_feeds(id) as (SELECT ?)", "int", $id); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT ?,?)", ["int","int"], [$id,$context->subscription]); } else if($context->folder()) { // if a folder is specified, make sure it exists $this->folderValidateId($user, $context->folder); // if it does exist, add a common table expression to list it and its children so that we select from the entire subtree $q->setCTE("folders(folder) as (SELECT ? union select id from arsse_folders join folders on parent is folder)", "int", $context->folder); // add another CTE for the subscriptions within the folder - $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner join folders on arsse_subscription.folder is folders.folder)"); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder)"); } else { // otherwise add a CTE for all the user's subscriptions - $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner)"); + $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner)"); } // filter for specific article or edition if($context->article()) $q->setWhere("arsse_article.id is ?", "int", $context->article); if($context->edition()) $q->setWhere("arsse_article.id is (SELECT article from arsse_editions where id is ?)", "int", $context->edition); - // filter for un/read and un/starred status if specified - if($context->unread()) $q->setWhere("unread is ?", "bool", $context->unread); - if($context->starred()) $q->setWhere("starred is ?", "bool", $context->starred); + // filter based on edition offset + if($context->oldestEdition()) $q->setWhere("edition >= ?", "int", $context->oldestEdition); + if($context->latestEdition()) $q->setWhere("edition <= ?", "int", $context->latestEdition); // filter based on lastmod time if($context->modifiedSince()) $q->setWhere("modified >= ?", "datetime", $context->modifiedSince); + if($context->notModifiedSince()) $q->setWhere("modified <= ?", "datetime", $context->notModifiedSince); // push the current query onto the CTE stack and execute the query we're actually interested in $q->pushCTE( "target_articles(id, exists)", // CTE table specification diff --git a/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php b/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php new file mode 100644 index 00000000..5da9784c --- /dev/null +++ b/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php @@ -0,0 +1,9 @@ + [ + 'columns' => [ + 'id' => 'str', + 'password' => 'str', + 'name' => 'str', + 'rights' => 'int', + ], + 'rows' => [ + ["jane.doe@example.com", "", "Jane Doe", UserDriver::RIGHTS_NONE], + ["john.doe@example.com", "", "John Doe", UserDriver::RIGHTS_NONE], + ], + ], + 'arsse_folders' => [ + 'columns' => [ + 'id' => "int", + 'owner' => "str", + 'parent' => "int", + 'name' => "str", + ], + 'rows' => [ + [1, "john.doe@example.com", null, "Technology"], + [2, "john.doe@example.com", 1, "Software"], + [3, "john.doe@example.com", 1, "Rocketry"], + [4, "jane.doe@example.com", null, "Politics"], + [5, "john.doe@example.com", null, "Politics"], + [6, "john.doe@example.com", 2, "Politics"], + ] + ], + 'arsse_feeds' => [ + 'columns' => [ + 'id' => "int", + 'url' => "str", + ], + 'rows' => [ + [1,"http://example.com/1"], + [2,"http://example.com/2"], + [3,"http://example.com/3"], + [4,"http://example.com/4"], + [5,"http://example.com/5"], + [6,"http://example.com/6"], + [7,"http://example.com/7"], + [8,"http://example.com/8"], + [9,"http://example.com/9"], + [10,"http://example.com/10"], + [11,"http://example.com/11"], + [12,"http://example.com/12"], + [13,"http://example.com/13"], + [14,"http://example.com/14"], + [15,"http://example.com/15"], + [16,"http://example.com/16"], + [17,"http://example.com/17"], + [18,"http://example.com/18"], + [19,"http://example.com/19"], + [20,"http://example.com/20"], + ] + ], + 'arsse_subscriptions' => [ + 'columns' => [ + 'id' => "int", + 'owner' => "str", + 'feed' => "int", + 'folder' => "int", + ], + 'rows' => [ + [1,"john.doe@example.com",1,null], + [2,"john.doe@example.com",2,null], + [3,"john.doe@example.com",3,1], + [4,"john.doe@example.com",4,6], + [5,"john.doe@example.com",10,5], + [6,"jane.doe@example.com",1,null], + [7,"jane.doe@example.com",12,null],/* + [8,"john.doe@example.com",1,null], + [9,"john.doe@example.com",1,null], + [10,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null], + [1,"john.doe@example.com",1,null],*/ + ] + ], + 'arsse_articles' => [ + 'columns' => [ + 'id' => "int", + 'feed' => "int", + 'modified' => "datetime", + 'url_title_hash' => "str", + 'url_content_hash' => "str", + 'title_content_hash' => "str", + ], + 'rows' => [] // filled by series setup + ], + 'arsse_enclosures' => [ + 'columns' => [ + 'article' => "int", + 'url' => "str", + 'type' => "str", + ], + 'rows' => [] + ], + 'arsse_editions' => [ + 'columns' => [ + 'id' => "int", + 'article' => "int", + ], + 'rows' => [ // lower IDs are filled by series setup + [1001,20], + ] + ], + 'arsse_marks' => [ + 'columns' => [ + 'owner' => "str", + 'article' => "int", + 'read' => "bool", + 'starred' => "bool", + ], + 'rows' => [ + ["john.doe@example.com",1,1,1], + ["john.doe@example.com",19,1,0], + ["john.doe@example.com",20,0,1], + ["jane.doe@example.com",20,1,0], + ] + ], + ]; + + function setUpSeries() { + for($a = 0, $b = 1; $b <= sizeof($this->data['arsse_feeds']['rows']); $b++) { + // add two generic articles per feed, and matching initial editions + $this->data['arsse_articles']['rows'][] = [++$a,$b,"2000-01-01T00:00:00Z","","",""]; + $this->data['arsse_editions']['rows'][] = [$a,$a]; + $this->data['arsse_articles']['rows'][] = [++$a,$b,"2010-01-01T00:00:00Z","","",""]; + $this->data['arsse_editions']['rows'][] = [$a,$a]; + } + $this->user = "john.doe@example.com"; + } + + function testListArticlesByContext() { + // get all items for user + $exp = [1,2,3,4,5,6,7,8,19,20]; + $this->compareIds($exp, new Context); + // get items from a folder tree + $exp = [5,6,7,8]; + $this->compareIds($exp, (new Context)->folder(1)); + // get items from a leaf folder + $exp = [7,8]; + $this->compareIds($exp, (new Context)->folder(6)); + // get items from a single subscription + $exp = [19,20]; + $this->compareIds($exp, (new Context)->subscription(5)); + // get un/read items from a single subscription + $this->compareIds([20], (new Context)->subscription(5)->unread(true)); + $this->compareIds([19], (new Context)->subscription(5)->unread(false)); + // get starred articles + $this->compareIds([1,20], (new Context)->starred(true)); + $this->compareIds([1], (new Context)->starred(true)->unread(false)); + $this->compareIds([], (new Context)->starred(true)->unread(false)->subscription(5)); + // get items relative to edition + $this->compareIds([19], (new Context)->subscription(5)->latestEdition(999)); + $this->compareIds([19], (new Context)->subscription(5)->latestEdition(19)); + $this->compareIds([20], (new Context)->subscription(5)->oldestEdition(999)); + $this->compareIds([20], (new Context)->subscription(5)->oldestEdition(1001)); + // get items relative to modification date + $exp = [2,4,6,8,20]; + $this->compareIds($exp, (new Context)->modifiedSince("2005-01-01T00:00:00Z")); + $this->compareIds($exp, (new Context)->modifiedSince("2010-01-01T00:00:00Z")); + $exp = [1,3,5,7,19]; + $this->compareIds($exp, (new Context)->notModifiedSince("2005-01-01T00:00:00Z")); + $this->compareIds($exp, (new Context)->notModifiedSince("2000-01-01T00:00:00Z")); + + } + + protected function compareIds(array $exp, Context $c) { + $ids = array_column($ids = Data::$db->articleList($this->user, $c)->getAll(), "id"); + sort($ids); + sort($exp); + $this->assertEquals($exp, $ids); + } +} \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 62886791..6263443a 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -46,6 +46,7 @@ Db/SQLite3/Database/TestDatabaseFolderSQLite3.php Db/SQLite3/Database/TestDatabaseFeedSQLite3.php Db/SQLite3/Database/TestDatabaseSubscriptionSQLite3.php + Db/SQLite3/Database/TestDatabaseArticleSQLite3.php REST/NextCloudNews/TestNCNVersionDiscovery.php