mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Implement new context options other than not().
Context handling has also been re-organized to simplify later implementation of the not() option
This commit is contained in:
parent
f4a74eec5d
commit
14c02d56ac
3 changed files with 162 additions and 201 deletions
193
lib/Database.php
193
lib/Database.php
|
@ -39,8 +39,6 @@ class Database {
|
|||
const SCHEMA_VERSION = 4;
|
||||
/** The maximum number of articles to mark in one query without chunking */
|
||||
const LIMIT_ARTICLES = 50;
|
||||
/** The maximum number of search terms allowed; this is a hard limit */
|
||||
const LIMIT_TERMS = 100;
|
||||
/** A map database driver short-names and their associated class names */
|
||||
const DRIVER_NAMES = [
|
||||
'sqlite3' => \JKingWeb\Arsse\Db\SQLite3\Driver::class,
|
||||
|
@ -129,7 +127,7 @@ class Database {
|
|||
|
||||
/** Conputes the contents of an SQL "IN()" clause, producing one parameter placeholder for each input value
|
||||
*
|
||||
* Returns an indexed array containing the clause text, and an array of types
|
||||
* Returns an indexed array containing the clause text, an array of types, and the array of values
|
||||
*
|
||||
* @param array $values Arbitrary values
|
||||
* @param string $type A single data type applied to each value
|
||||
|
@ -138,6 +136,7 @@ class Database {
|
|||
$out = [
|
||||
"", // query clause
|
||||
[], // binding types
|
||||
$values, // binding values
|
||||
];
|
||||
if (sizeof($values)) {
|
||||
// the query clause is just a series of question marks separated by commas
|
||||
|
@ -1096,8 +1095,32 @@ class Database {
|
|||
* @param array $cols The columns to request in the result set
|
||||
*/
|
||||
protected function articleQuery(string $user, Context $context, array $cols = ["id"]): Query {
|
||||
// validate input
|
||||
if ($context->subscription()) {
|
||||
$this->subscriptionValidateId($user, $context->subscription);
|
||||
}
|
||||
if ($context->folder()) {
|
||||
$this->folderValidateId($user, $context->folder);
|
||||
}
|
||||
if ($context->folderShallow()) {
|
||||
$this->folderValidateId($user, $context->folderShallow);
|
||||
}
|
||||
if ($context->edition()) {
|
||||
$this->articleValidateEdition($user, $context->edition);
|
||||
}
|
||||
if ($context->article()) {
|
||||
$this->articleValidateId($user, $context->article);
|
||||
}
|
||||
if ($context->label()) {
|
||||
$this->labelValidateId($user, $context->label, false);
|
||||
}
|
||||
if ($context->labelName()) {
|
||||
// dereference the label name to an ID
|
||||
$context->label((int) $this->labelValidateId($user, $context->labelName, true)['id']);
|
||||
$context->labelName(null);
|
||||
}
|
||||
// prepare the output column list; the column definitions are also used later
|
||||
$greatest = $this->db->sqlToken("greatest");
|
||||
// prepare the output column list
|
||||
$colDefs = [
|
||||
'id' => "arsse_articles.id",
|
||||
'edition' => "latest_editions.edition",
|
||||
|
@ -1107,6 +1130,7 @@ class Database {
|
|||
'content' => "arsse_articles.content",
|
||||
'guid' => "arsse_articles.guid",
|
||||
'fingerprint' => "arsse_articles.url_title_hash || ':' || arsse_articles.url_content_hash || ':' || arsse_articles.title_content_hash",
|
||||
'folder' => "coalesce(arsse_subscriptions.folder,0)",
|
||||
'subscription' => "arsse_subscriptions.id",
|
||||
'feed' => "arsse_subscriptions.feed",
|
||||
'starred' => "coalesce(arsse_marks.starred,0)",
|
||||
|
@ -1148,127 +1172,82 @@ class Database {
|
|||
["str"],
|
||||
[$user]
|
||||
);
|
||||
$q->setLimit($context->limit, $context->offset);
|
||||
$q->setCTE("latest_editions(article,edition)", "SELECT article,max(id) from arsse_editions group by article", [], [], "join latest_editions on arsse_articles.id = latest_editions.article");
|
||||
if ($cols) {
|
||||
// if there are no output columns requested we're getting a count and should not group, but otherwise we should
|
||||
$q->setGroup("arsse_articles.id", "arsse_marks.note", "arsse_enclosures.url", "arsse_enclosures.type", "arsse_subscriptions.title", "arsse_feeds.title", "arsse_subscriptions.id", "arsse_marks.modified", "arsse_label_members.modified", "arsse_marks.read", "arsse_marks.starred", "latest_editions.edition");
|
||||
}
|
||||
$q->setLimit($context->limit, $context->offset);
|
||||
if ($context->subscription()) {
|
||||
// if a subscription is specified, make sure it exists
|
||||
$this->subscriptionValidateId($user, $context->subscription);
|
||||
// filter for the subscription
|
||||
$q->setWhere("arsse_subscriptions.id = ?", "int", $context->subscription);
|
||||
} elseif ($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)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $context->folder);
|
||||
// limit subscriptions to the listed folders
|
||||
$q->setWhere("arsse_subscriptions.folder in (select folder from folders)");
|
||||
} elseif ($context->folderShallow()) {
|
||||
// if a shallow folder is specified, make sure it exists
|
||||
$this->folderValidateId($user, $context->folderShallow);
|
||||
// if it does exist, filter for that folder only
|
||||
$q->setWhere("coalesce(arsse_subscriptions.folder,0) = ?", "int", $context->folderShallow);
|
||||
}
|
||||
if ($context->edition()) {
|
||||
// if an edition is specified, first validate it, then filter for it
|
||||
$this->articleValidateEdition($user, $context->edition);
|
||||
$q->setWhere("latest_editions.edition = ?", "int", $context->edition);
|
||||
} elseif ($context->article()) {
|
||||
// if an article is specified, first validate it, then filter for it
|
||||
$this->articleValidateId($user, $context->article);
|
||||
$q->setWhere("arsse_articles.id = ?", "int", $context->article);
|
||||
}
|
||||
if ($context->editions()) {
|
||||
// if multiple specific editions have been requested, filter against the list
|
||||
if (!$context->editions) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => "editions", 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->editions) > self::LIMIT_ARTICLES) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => "editions", 'action' => $this->caller(), 'max' => self::LIMIT_ARTICLES]); // @codeCoverageIgnore
|
||||
// handle the simple context options
|
||||
foreach ([
|
||||
// each context array consists of a column identifier (see $colDefs above), a comparison operator, a data type, and an upper bound if the value is an array
|
||||
"edition" => ["edition", "=", "int", 1],
|
||||
"editions" => ["edition", "in", "int", self::LIMIT_ARTICLES],
|
||||
"article" => ["id", "=", "int", 1],
|
||||
"articles" => ["id", "in", "int", self::LIMIT_ARTICLES],
|
||||
"oldestArticle" => ["id", ">=", "int", 1],
|
||||
"latestArticle" => ["id", "<=", "int", 1],
|
||||
"oldestEdition" => ["edition", ">=", "int", 1],
|
||||
"latestEdition" => ["edition", "<=", "int", 1],
|
||||
"modifiedSince" => ["modified_date", ">=", "datetime", 1],
|
||||
"notModifiedSince" => ["modified_date", "<=", "datetime", 1],
|
||||
"markedSince" => ["marked_date", ">=", "datetime", 1],
|
||||
"notMarkedSince" => ["marked_date", "<=", "datetime", 1],
|
||||
"folderShallow" => ["folder", "=", "int", 1],
|
||||
"subscription" => ["subscription", "=", "int", 1],
|
||||
"unread" => ["unread", "=", "bool", 1],
|
||||
"starred" => ["starred", "=", "bool", 1],
|
||||
] as $m => list($col, $op, $type, $max)) {
|
||||
if (!$context->$m()) {
|
||||
// context is not being used
|
||||
continue;
|
||||
} elseif (is_array($context->$m)) {
|
||||
if (!$context->$m) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->$m) > $max) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => $m, 'action' => $this->caller(), 'max' => $max]); // @codeCoverageIgnore
|
||||
}
|
||||
list($clause, $types, $values) = $this->generateIn($context->$m, $type);
|
||||
$q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values);
|
||||
} else {
|
||||
$q->setWhere("{$colDefs[$col]} $op ?", $type, $context->$m);
|
||||
}
|
||||
list($inParams, $inTypes) = $this->generateIn($context->editions, "int");
|
||||
$q->setWhere("latest_editions.edition in ($inParams)", $inTypes, $context->editions);
|
||||
} elseif ($context->articles()) {
|
||||
// if multiple specific articles have been requested, filter against the list
|
||||
if (!$context->articles) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => "articles", 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->articles) > self::LIMIT_ARTICLES) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => "articles", 'action' => $this->caller(), 'max' => self::LIMIT_ARTICLES]); // @codeCoverageIgnore
|
||||
}
|
||||
list($inParams, $inTypes) = $this->generateIn($context->articles, "int");
|
||||
$q->setWhere("arsse_articles.id in ($inParams)", $inTypes, $context->articles);
|
||||
}
|
||||
// filter based on label by ID or name
|
||||
// handle complex context options
|
||||
if ($context->labelled()) {
|
||||
// any label (true) or no label (false)
|
||||
$isOrIsNot = (!$context->labelled ? "is" : "is not");
|
||||
$q->setWhere("arsse_labels.id $isOrIsNot null");
|
||||
} elseif ($context->label() || $context->labelName()) {
|
||||
// specific label ID or name
|
||||
if ($context->label()) {
|
||||
$id = $this->labelValidateId($user, $context->label, false)['id'];
|
||||
} else {
|
||||
$id = $this->labelValidateId($user, $context->labelName, true)['id'];
|
||||
}
|
||||
$q->setWhere("arsse_labels.id = ?", "int", $id);
|
||||
}
|
||||
// filter based on article or edition offset
|
||||
if ($context->oldestArticle()) {
|
||||
$q->setWhere("arsse_articles.id >= ?", "int", $context->oldestArticle);
|
||||
if ($context->label()) {
|
||||
// label ID (label names are dereferenced during input validation above)
|
||||
$q->setWhere("arsse_labels.id = ?", "int", $context->label);
|
||||
}
|
||||
if ($context->latestArticle()) {
|
||||
$q->setWhere("arsse_articles.id <= ?", "int", $context->latestArticle);
|
||||
}
|
||||
if ($context->oldestEdition()) {
|
||||
$q->setWhere("latest_editions.edition >= ?", "int", $context->oldestEdition);
|
||||
}
|
||||
if ($context->latestEdition()) {
|
||||
$q->setWhere("latest_editions.edition <= ?", "int", $context->latestEdition);
|
||||
}
|
||||
// filter based on time at which an article was changed by feed updates (modified), or by user action (marked)
|
||||
if ($context->modifiedSince()) {
|
||||
$q->setWhere("arsse_articles.modified >= ?", "datetime", $context->modifiedSince);
|
||||
}
|
||||
if ($context->notModifiedSince()) {
|
||||
$q->setWhere("arsse_articles.modified <= ?", "datetime", $context->notModifiedSince);
|
||||
}
|
||||
if ($context->markedSince()) {
|
||||
$q->setWhere($colDefs['marked_date']." >= ?", "datetime", $context->markedSince);
|
||||
}
|
||||
if ($context->notMarkedSince()) {
|
||||
$q->setWhere($colDefs['marked_date']." <= ?", "datetime", $context->notMarkedSince);
|
||||
}
|
||||
// filter for un/read and un/starred status if specified
|
||||
if ($context->unread()) {
|
||||
$q->setWhere("coalesce(arsse_marks.read,0) = ?", "bool", !$context->unread);
|
||||
}
|
||||
if ($context->starred()) {
|
||||
$q->setWhere("coalesce(arsse_marks.starred,0) = ?", "bool", $context->starred);
|
||||
}
|
||||
// filter based on whether the article has a note
|
||||
if ($context->annotated()) {
|
||||
$comp = ($context->annotated) ? "<>" : "=";
|
||||
$q->setWhere("coalesce(arsse_marks.note,'') $comp ''");
|
||||
}
|
||||
// filter based on search terms
|
||||
if ($context->searchTerms()) {
|
||||
if (!$context->searchTerms) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => "searchTerms", 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->searchTerms) > self::LIMIT_TERMS) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => "searchTerms", 'action' => $this->caller(), 'max' => self::LIMIT_TERMS]);
|
||||
}
|
||||
$q->setWhere(...$this->generateSearch($context->searchTerms, ["arsse_articles.title", "arsse_articles.content"]));
|
||||
if ($context->folder()) {
|
||||
// add a common table expression to list the folder and its children so that we select from the entire subtree
|
||||
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $context->folder);
|
||||
// limit subscriptions to the listed folders
|
||||
$q->setWhere("arsse_subscriptions.folder in (select folder from folders)");
|
||||
}
|
||||
// filter based on search terms in note
|
||||
if ($context->annotationTerms()) {
|
||||
if (!$context->annotationTerms) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => "annotationTerms", 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->annotationTerms) > self::LIMIT_TERMS) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => "annotationTerms", 'action' => $this->caller(), 'max' => self::LIMIT_TERMS]);
|
||||
// handle text-matching context options
|
||||
foreach ([
|
||||
"titleTerms" => [10, ["arsse_articles.title"]],
|
||||
"searchTerms" => [20, ["arsse_articles.title", "arsse_articles.content"]],
|
||||
"authorTerms" => [10, ["arsse_articles.author"]],
|
||||
"annotationTerms" => [20, ["arsse_marks.note"]],
|
||||
] as $m => list($max, $cols)) {
|
||||
if (!$context->$m()) {
|
||||
continue;
|
||||
} elseif (!$context->$m) {
|
||||
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||
} elseif (sizeof($context->$m) > $max) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => $m, 'action' => $this->caller(), 'max' => $max]);
|
||||
}
|
||||
$q->setWhere(...$this->generateSearch($context->annotationTerms, ["arsse_marks.note"]));
|
||||
$q->setWhere(...$this->generateSearch($context->$m, $cols));
|
||||
}
|
||||
// return the query
|
||||
return $q;
|
||||
|
@ -1306,7 +1285,7 @@ class Database {
|
|||
*
|
||||
* @param string $user The user whose articles are to be listed
|
||||
* @param Context $context The search context
|
||||
* @param array $cols The columns to return in the result set, any of: id, edition, url, title, author, content, guid, fingerprint, subscription, feed, starred, unread, note, published_date, edited_date, modified_date, marked_date, subscription_title, media_url, media_type
|
||||
* @param array $cols The columns to return in the result set, any of: id, edition, url, title, author, content, guid, fingerprint, folder, subscription, feed, starred, unread, note, published_date, edited_date, modified_date, marked_date, subscription_title, media_url, media_type
|
||||
*/
|
||||
public function articleList(string $user, Context $context = null, array $fields = ["id"]): Db\Result {
|
||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||
|
|
|
@ -66,7 +66,7 @@ abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
|
||||
public function setUp() {
|
||||
// get the name of the test's test series
|
||||
$this->series = $this->findTraitofTest($this->getName());
|
||||
$this->series = $this->findTraitofTest($this->getName(false));
|
||||
static::clearData();
|
||||
static::setConf();
|
||||
if (strlen(static::$failureReason)) {
|
||||
|
@ -88,7 +88,7 @@ abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
|
||||
public function tearDown() {
|
||||
// call the series-specific teardown method
|
||||
$this->series = $this->findTraitofTest($this->getName());
|
||||
$this->series = $this->findTraitofTest($this->getName(false));
|
||||
$tearDown = "tearDown".$this->series;
|
||||
$this->$tearDown();
|
||||
// clean up
|
||||
|
|
|
@ -114,10 +114,10 @@ trait SeriesArticle {
|
|||
[1,1,null,"Title one", null,null,null,"First article", null,"","","","2000-01-01T00:00:00Z"],
|
||||
[2,1,null,"Title two", null,null,null,"Second article",null,"","","","2010-01-01T00:00:00Z"],
|
||||
[3,2,null,"Title three",null,null,null,"Third article", null,"","","","2000-01-01T00:00:00Z"],
|
||||
[4,2,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
[5,3,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
|
||||
[6,3,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
[7,4,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
|
||||
[4,2,null,null,"John Doe",null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
[5,3,null,null,"John Doe",null,null,null,null,"","","","2000-01-01T00:00:00Z"],
|
||||
[6,3,null,null,"Jane Doe",null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
[7,4,null,null,"Jane Doe",null,null,null,null,"","","","2000-01-01T00:00:00Z"],
|
||||
[8,4,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
[9,5,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
|
||||
[10,5,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"],
|
||||
|
@ -414,94 +414,76 @@ trait SeriesArticle {
|
|||
$this->assertEquals($exp, Arsse::$db->editionArticle(...range(1, 1001)));
|
||||
}
|
||||
|
||||
public function testListArticlesCheckingContext() {
|
||||
$compareIds = function(array $exp, Context $c) {
|
||||
$ids = array_column($ids = Arsse::$db->articleList("john.doe@example.com", $c)->getAll(), "id");
|
||||
sort($ids);
|
||||
sort($exp);
|
||||
$this->assertEquals($exp, $ids);
|
||||
};
|
||||
// get all items for user
|
||||
$exp = [1,2,3,4,5,6,7,8,19,20];
|
||||
$compareIds($exp, new Context);
|
||||
$compareIds($exp, (new Context)->articles(range(1, Database::LIMIT_ARTICLES * 3)));
|
||||
// get items from a folder tree
|
||||
$compareIds([5,6,7,8], (new Context)->folder(1));
|
||||
// get items from a leaf folder
|
||||
$compareIds([7,8], (new Context)->folder(6));
|
||||
// get items from a non-leaf folder without descending
|
||||
$compareIds([1,2,3,4], (new Context)->folderShallow(0));
|
||||
$compareIds([5,6], (new Context)->folderShallow(1));
|
||||
// get items from a single subscription
|
||||
$exp = [19,20];
|
||||
$compareIds($exp, (new Context)->subscription(5));
|
||||
// get un/read items from a single subscription
|
||||
$compareIds([20], (new Context)->subscription(5)->unread(true));
|
||||
$compareIds([19], (new Context)->subscription(5)->unread(false));
|
||||
// get starred articles
|
||||
$compareIds([1,20], (new Context)->starred(true));
|
||||
$compareIds([2,3,4,5,6,7,8,19], (new Context)->starred(false));
|
||||
$compareIds([1], (new Context)->starred(true)->unread(false));
|
||||
$compareIds([], (new Context)->starred(true)->unread(false)->subscription(5));
|
||||
// get items relative to edition
|
||||
$compareIds([19], (new Context)->subscription(5)->latestEdition(999));
|
||||
$compareIds([19], (new Context)->subscription(5)->latestEdition(19));
|
||||
$compareIds([20], (new Context)->subscription(5)->oldestEdition(999));
|
||||
$compareIds([20], (new Context)->subscription(5)->oldestEdition(1001));
|
||||
// get items relative to article ID
|
||||
$compareIds([1,2,3], (new Context)->latestArticle(3));
|
||||
$compareIds([19,20], (new Context)->oldestArticle(19));
|
||||
// get items relative to (feed) modification date
|
||||
$exp = [2,4,6,8,20];
|
||||
$compareIds($exp, (new Context)->modifiedSince("2005-01-01T00:00:00Z"));
|
||||
$compareIds($exp, (new Context)->modifiedSince("2010-01-01T00:00:00Z"));
|
||||
$exp = [1,3,5,7,19];
|
||||
$compareIds($exp, (new Context)->notModifiedSince("2005-01-01T00:00:00Z"));
|
||||
$compareIds($exp, (new Context)->notModifiedSince("2000-01-01T00:00:00Z"));
|
||||
// get items relative to (user) modification date (both marks and labels apply)
|
||||
$compareIds([8,19], (new Context)->markedSince("2014-01-01T00:00:00Z"));
|
||||
$compareIds([2,4,6,8,19,20], (new Context)->markedSince("2010-01-01T00:00:00Z"));
|
||||
$compareIds([1,2,3,4,5,6,7,20], (new Context)->notMarkedSince("2014-01-01T00:00:00Z"));
|
||||
$compareIds([1,3,5,7], (new Context)->notMarkedSince("2005-01-01T00:00:00Z"));
|
||||
// paged results
|
||||
$compareIds([1], (new Context)->limit(1));
|
||||
$compareIds([2], (new Context)->limit(1)->oldestEdition(1+1));
|
||||
$compareIds([3], (new Context)->limit(1)->oldestEdition(2+1));
|
||||
$compareIds([4,5], (new Context)->limit(2)->oldestEdition(3+1));
|
||||
// reversed results
|
||||
$compareIds([20], (new Context)->reverse(true)->limit(1));
|
||||
$compareIds([19], (new Context)->reverse(true)->limit(1)->latestEdition(1001-1));
|
||||
$compareIds([8], (new Context)->reverse(true)->limit(1)->latestEdition(19-1));
|
||||
$compareIds([7,6], (new Context)->reverse(true)->limit(2)->latestEdition(8-1));
|
||||
// get articles by label ID
|
||||
$compareIds([1,19], (new Context)->label(1));
|
||||
$compareIds([1,5,20], (new Context)->label(2));
|
||||
// get articles by label name
|
||||
$compareIds([1,19], (new Context)->labelName("Interesting"));
|
||||
$compareIds([1,5,20], (new Context)->labelName("Fascinating"));
|
||||
// get articles with any or no label
|
||||
$compareIds([1,5,8,19,20], (new Context)->labelled(true));
|
||||
$compareIds([2,3,4,6,7], (new Context)->labelled(false));
|
||||
// get a specific article or edition
|
||||
$compareIds([20], (new Context)->article(20));
|
||||
$compareIds([20], (new Context)->edition(1001));
|
||||
// get multiple specific articles or editions
|
||||
$compareIds([1,20], (new Context)->articles([1,20,50]));
|
||||
$compareIds([1,20], (new Context)->editions([1,1001,50]));
|
||||
// get articles base on whether or not they have notes
|
||||
$compareIds([1,3,4,5,6,7,8,19,20], (new Context)->annotated(false));
|
||||
$compareIds([2], (new Context)->annotated(true));
|
||||
// get specific starred articles
|
||||
$compareIds([1], (new Context)->articles([1,2,3])->starred(true));
|
||||
$compareIds([2,3], (new Context)->articles([1,2,3])->starred(false));
|
||||
// get items that match search terms
|
||||
$compareIds([1,2,3], (new Context)->searchTerms(["Article"]));
|
||||
$compareIds([1], (new Context)->searchTerms(["one", "first"]));
|
||||
// get items that match search terms in note
|
||||
$compareIds([2], (new Context)->annotationTerms(["some"]));
|
||||
$compareIds([2], (new Context)->annotationTerms(["some", "note"]));
|
||||
$compareIds([2], (new Context)->annotationTerms(["some note"]));
|
||||
$compareIds([], (new Context)->annotationTerms(["some", "sauce"]));
|
||||
/** @dataProvider provideContextMatches */
|
||||
public function testListArticlesCheckingContext(Context $c, array $exp) {
|
||||
$ids = array_column($ids = Arsse::$db->articleList("john.doe@example.com", $c)->getAll(), "id");
|
||||
sort($ids);
|
||||
sort($exp);
|
||||
$this->assertEquals($exp, $ids);
|
||||
}
|
||||
|
||||
public function provideContextMatches() {
|
||||
return [
|
||||
"Blank context" => [new Context, [1,2,3,4,5,6,7,8,19,20]],
|
||||
"Folder tree" => [(new Context)->folder(1), [5,6,7,8]],
|
||||
"Leaf folder" => [(new Context)->folder(6), [7,8]],
|
||||
"Root folder only" => [(new Context)->folderShallow(0), [1,2,3,4]],
|
||||
"Shallow folder" => [(new Context)->folderShallow(1), [5,6]],
|
||||
"Subscription" => [(new Context)->subscription(5), [19,20]],
|
||||
"Unread" => [(new Context)->subscription(5)->unread(true), [20]],
|
||||
"Read" => [(new Context)->subscription(5)->unread(false), [19]],
|
||||
"Starred" => [(new Context)->starred(true), [1,20]],
|
||||
"Unstarred" => [(new Context)->starred(false), [2,3,4,5,6,7,8,19]],
|
||||
"Starred and Read" => [(new Context)->starred(true)->unread(false), [1]],
|
||||
"Starred and Read in subscription" => [(new Context)->starred(true)->unread(false)->subscription(5), []],
|
||||
"Annotated" => [(new Context)->annotated(true), [2]],
|
||||
"Not annotated" => [(new Context)->annotated(false), [1,3,4,5,6,7,8,19,20]],
|
||||
"Labelled" => [(new Context)->labelled(true), [1,5,8,19,20]],
|
||||
"Not labelled" => [(new Context)->labelled(false), [2,3,4,6,7]],
|
||||
"Not after edition 999" => [(new Context)->subscription(5)->latestEdition(999), [19]],
|
||||
"Not after edition 19" => [(new Context)->subscription(5)->latestEdition(19), [19]],
|
||||
"Not before edition 999" => [(new Context)->subscription(5)->oldestEdition(999), [20]],
|
||||
"Not before edition 1001" => [(new Context)->subscription(5)->oldestEdition(1001), [20]],
|
||||
"Not after article 3" => [(new Context)->latestArticle(3), [1,2,3]],
|
||||
"Not before article 19" => [(new Context)->oldestArticle(19), [19,20]],
|
||||
"Modified by author since 2005" => [(new Context)->modifiedSince("2005-01-01T00:00:00Z"), [2,4,6,8,20]],
|
||||
"Modified by author since 2010" => [(new Context)->modifiedSince("2010-01-01T00:00:00Z"), [2,4,6,8,20]],
|
||||
"Not modified by author since 2005" => [(new Context)->notModifiedSince("2005-01-01T00:00:00Z"), [1,3,5,7,19]],
|
||||
"Not modified by author since 2000" => [(new Context)->notModifiedSince("2000-01-01T00:00:00Z"), [1,3,5,7,19]],
|
||||
"Marked or labelled since 2014" => [(new Context)->markedSince("2014-01-01T00:00:00Z"), [8,19]],
|
||||
"Marked or labelled since 2010" => [(new Context)->markedSince("2010-01-01T00:00:00Z"), [2,4,6,8,19,20]],
|
||||
"Not marked or labelled since 2014" => [(new Context)->notMarkedSince("2014-01-01T00:00:00Z"), [1,2,3,4,5,6,7,20]],
|
||||
"Not marked or labelled since 2005" => [(new Context)->notMarkedSince("2005-01-01T00:00:00Z"), [1,3,5,7]],
|
||||
"Paged results" => [(new Context)->limit(2)->oldestEdition(4), [4,5]],
|
||||
"Reversed paged results" => [(new Context)->limit(2)->latestEdition(7)->reverse(true), [7,6]],
|
||||
"With label ID 1" => [(new Context)->label(1), [1,19]],
|
||||
"With label ID 2" => [(new Context)->label(2), [1,5,20]],
|
||||
"With label 'Interesting'" => [(new Context)->labelName("Interesting"), [1,19]],
|
||||
"With label 'Fascinating'" => [(new Context)->labelName("Fascinating"), [1,5,20]],
|
||||
"Article ID 20" => [(new Context)->article(20), [20]],
|
||||
"Edition ID 1001" => [(new Context)->edition(1001), [20]],
|
||||
"Multiple articles" => [(new Context)->articles([1,20,50]), [1,20]],
|
||||
"Multiple starred articles" => [(new Context)->articles([1,2,3])->starred(true), [1]],
|
||||
"Multiple unstarred articles" => [(new Context)->articles([1,2,3])->starred(false), [2,3]],
|
||||
"Multiple articles" => [(new Context)->articles([1,20,50]), [1,20]],
|
||||
"Multiple editions" => [(new Context)->editions([1,1001,50]), [1,20]],
|
||||
"150 articles" => [(new Context)->articles(range(1, Database::LIMIT_ARTICLES * 3)), [1,2,3,4,5,6,7,8,19,20]],
|
||||
"Search title or content 1" => [(new Context)->searchTerms(["Article"]), [1,2,3]],
|
||||
"Search title or content 2" => [(new Context)->searchTerms(["one", "first"]), [1]],
|
||||
"Search title or content 3" => [(new Context)->searchTerms(["one first"]), []],
|
||||
"Search title 1" => [(new Context)->titleTerms(["two"]), [2]],
|
||||
"Search title 2" => [(new Context)->titleTerms(["title two"]), [2]],
|
||||
"Search title 3" => [(new Context)->titleTerms(["two", "title"]), [2]],
|
||||
"Search title 4" => [(new Context)->titleTerms(["two title"]), []],
|
||||
"Search note 1" => [(new Context)->annotationTerms(["some"]), [2]],
|
||||
"Search note 2" => [(new Context)->annotationTerms(["some Note"]), [2]],
|
||||
"Search note 3" => [(new Context)->annotationTerms(["note", "some"]), [2]],
|
||||
"Search note 4" => [(new Context)->annotationTerms(["some", "sauce"]), []],
|
||||
"Search author 1" => [(new Context)->authorTerms(["doe"]), [4,5,6,7]],
|
||||
"Search author 2" => [(new Context)->authorTerms(["jane doe"]), [6,7]],
|
||||
"Search author 3" => [(new Context)->authorTerms(["doe", "jane"]), [6,7]],
|
||||
"Search author 4" => [(new Context)->authorTerms(["doe jane"]), []],
|
||||
];
|
||||
}
|
||||
|
||||
public function testListArticlesOfAMissingFolder() {
|
||||
|
|
Loading…
Reference in a new issue