mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-23 09:02:41 +00:00
Allow for replacing label and tag associations
This supplements adding and removing
This commit is contained in:
parent
2af223753d
commit
3899ee6b4e
5 changed files with 173 additions and 69 deletions
141
lib/Database.php
141
lib/Database.php
|
@ -43,6 +43,12 @@ class Database {
|
||||||
const LIMIT_SET_SIZE = 25;
|
const LIMIT_SET_SIZE = 25;
|
||||||
/** The length of a string in an embedded set beyond which a parameter placeholder will be used for the string */
|
/** The length of a string in an embedded set beyond which a parameter placeholder will be used for the string */
|
||||||
const LIMIT_SET_STRING_LENGTH = 200;
|
const LIMIT_SET_STRING_LENGTH = 200;
|
||||||
|
/** Makes tag/label association change operations remove members */
|
||||||
|
const ASSOC_REMOVE = 0;
|
||||||
|
/** Makes tag/label association change operations add members */
|
||||||
|
const ASSOC_ADD = 1;
|
||||||
|
/** Makes tag/label association change operations replace members */
|
||||||
|
const ASSOC_REPLACE = 2;
|
||||||
/** A map database driver short-names and their associated class names */
|
/** A map database driver short-names and their associated class names */
|
||||||
const DRIVER_NAMES = [
|
const DRIVER_NAMES = [
|
||||||
'sqlite3' => \JKingWeb\Arsse\Db\SQLite3\Driver::class,
|
'sqlite3' => \JKingWeb\Arsse\Db\SQLite3\Driver::class,
|
||||||
|
@ -1955,37 +1961,61 @@ class Database {
|
||||||
* @param string $user The owner of the label
|
* @param string $user The owner of the label
|
||||||
* @param integer|string $id The numeric identifier or name of the label
|
* @param integer|string $id The numeric identifier or name of the label
|
||||||
* @param Context $context The query context matching the desired articles
|
* @param Context $context The query context matching the desired articles
|
||||||
* @param boolean $remove Whether to remove (true) rather than add (true) an association with the articles matching the context
|
* @param int $mode Whether to add (ASSOC_ADD), remove (ASSOC_REMOVE), or replace with (ASSOC_REPLACE) the matching associations
|
||||||
* @param boolean $byName Whether to interpret the $id parameter as the label's name (true) or identifier (false)
|
* @param boolean $byName Whether to interpret the $id parameter as the label's name (true) or identifier (false)
|
||||||
*/
|
*/
|
||||||
public function labelArticlesSet(string $user, $id, Context $context = null, bool $remove = false, bool $byName = false): int {
|
public function labelArticlesSet(string $user, $id, Context $context, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
||||||
|
if (!in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE])) {
|
||||||
|
throw new Exception("constantUnknown", $mode); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
// validate the label ID, and get the numeric ID if matching by name
|
// validate the tag ID, and get the numeric ID if matching by name
|
||||||
$id = $this->labelValidateId($user, $id, $byName, true)['id'];
|
$id = $this->labelValidateId($user, $id, $byName, true)['id'];
|
||||||
$context = $context ?? new Context;
|
// get the list of articles matching the context
|
||||||
// prepare either one or two queries
|
$articles = iterator_to_array($this->articleList($user, $context ?? new Context));
|
||||||
// first update any existing entries with the removal or re-addition of their association
|
// an empty article list is a special case
|
||||||
$q1 = $this->articleQuery($user, $context);
|
if (!sizeof($articles)) {
|
||||||
$q1->pushCTE("target_articles");
|
if ($mode == self::ASSOC_REPLACE) {
|
||||||
$q1->setBody("UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label = ? and assigned <> ? and article in (select id from target_articles)", ["bool","int","bool"], [!$remove, $id, !$remove]);
|
// replacing with an empty set means setting everything to zero
|
||||||
$v1 = $q1->getValues();
|
return $this->db->prepare("UPDATE arsse_label_members set assigned = 0, modified = CURRENT_TIMESTAMP where label = ? and assigned = 1", "int")->run($id)->changes();
|
||||||
$q1 = $this->db->prepare($q1->getQuery(), $q1->getTypes());
|
} else {
|
||||||
// next, if we're not removing, add any new entries that need to be added
|
// adding or removing is a no-op
|
||||||
if (!$remove) {
|
return 0;
|
||||||
$q2 = $this->articleQuery($user, $context, ["id", "subscription"]);
|
}
|
||||||
$q2->pushCTE("target_articles");
|
} else {
|
||||||
$q2->setBody("SELECT ?,id,subscription from target_articles where id not in (select article from arsse_label_members where label = ?)", ["int", "int"], [$id, $id]);
|
$articles = array_column($articles, "id");
|
||||||
$v2 = $q2->getValues();
|
}
|
||||||
$q2 = $this->db->prepare("INSERT INTO arsse_label_members(label,article,subscription) ".$q2->getQuery(), $q2->getTypes());
|
// prepare up to three queries: removing requires one, adding two, and replacing three
|
||||||
|
list($inClause, $inTypes, $inValues) = $this->generateIn($articles, "int");
|
||||||
|
$updateQ = "UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label = ? and assigned <> ? and article %in% ($inClause)";
|
||||||
|
$updateT = ["bool", "int", "bool", $inTypes];
|
||||||
|
$insertQ = "INSERT INTO arsse_label_members(label,article,subscription) SELECT ?,a.id,s.id from arsse_articles as a join arsse_subscriptions as s on a.feed = s.feed where s.owner = ? and a.id not in (select article from arsse_label_members where label = ?) and a.id in ($inClause)";
|
||||||
|
$insertT = ["int", "str", "int", $inTypes];
|
||||||
|
$clearQ = str_replace("%in%", "not in", $updateQ);
|
||||||
|
$clearT = $updateT;
|
||||||
|
$updateQ = str_replace("%in%", "in", $updateQ);
|
||||||
|
$qList = [];
|
||||||
|
switch ($mode) {
|
||||||
|
case self::ASSOC_REMOVE:
|
||||||
|
$qList[] = [$updateQ, $updateT, [false, $id, false, $inValues]]; // soft-delete any existing associations
|
||||||
|
break;
|
||||||
|
case self::ASSOC_ADD:
|
||||||
|
$qList[] = [$updateQ, $updateT, [true, $id, true, $inValues]]; // re-enable any previously soft-deleted association
|
||||||
|
$qList[] = [$insertQ, $insertT, [$id, $user, $id, $inValues]]; // insert any newly-required associations
|
||||||
|
break;
|
||||||
|
case self::ASSOC_REPLACE:
|
||||||
|
$qList[] = [$clearQ, $clearT, [false, $id, false, $inValues]]; // soft-delete any existing associations for articles not in the list
|
||||||
|
$qList[] = [$updateQ, $updateT, [true, $id, true, $inValues]]; // re-enable any previously soft-deleted association
|
||||||
|
$qList[] = [$insertQ, $insertT, [$id, $user, $id, $inValues]]; // insert any newly-required associations
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// execute them in a transaction
|
// execute them in a transaction
|
||||||
$out = 0;
|
$out = 0;
|
||||||
$tr = $this->begin();
|
$tr = $this->begin();
|
||||||
$out += $q1->run($v1)->changes();
|
foreach ($qList as list($q, $t, $v)) {
|
||||||
if (!$remove) {
|
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
||||||
$out += $q2->run($v2)->changes();
|
|
||||||
}
|
}
|
||||||
$tr->commit();
|
$tr->commit();
|
||||||
return $out;
|
return $out;
|
||||||
|
@ -2235,47 +2265,58 @@ class Database {
|
||||||
*
|
*
|
||||||
* @param string $user The owner of the tag
|
* @param string $user The owner of the tag
|
||||||
* @param integer|string $id The numeric identifier or name of the tag
|
* @param integer|string $id The numeric identifier or name of the tag
|
||||||
* @param integer[] $context The query context matching the desired subscriptions
|
* @param integer[] $subscriptions An array listing the desired subscriptions
|
||||||
* @param boolean $remove Whether to remove (true) rather than add (true) an association with the subscriptions matching the context
|
* @param int $mode Whether to add (ASSOC_ADD), remove (ASSOC_REMOVE), or replace with (ASSOC_REPLACE) the listed associations
|
||||||
* @param boolean $byName Whether to interpret the $id parameter as the tag's name (true) or identifier (false)
|
* @param boolean $byName Whether to interpret the $id parameter as the tag's name (true) or identifier (false)
|
||||||
*/
|
*/
|
||||||
public function tagSubscriptionsSet(string $user, $id, array $subscriptions, bool $remove = false, bool $byName = false): int {
|
public function tagSubscriptionsSet(string $user, $id, array $subscriptions, int $mode = self::ASSOC_ADD, bool $byName = false): int {
|
||||||
|
if (!in_array($mode, [self::ASSOC_ADD, self::ASSOC_REMOVE, self::ASSOC_REPLACE])) {
|
||||||
|
throw new Exception("constantUnknown", $mode); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
// validate the tag ID, and get the numeric ID if matching by name
|
// validate the tag ID, and get the numeric ID if matching by name
|
||||||
$id = $this->tagValidateId($user, $id, $byName, true)['id'];
|
$id = $this->tagValidateId($user, $id, $byName, true)['id'];
|
||||||
// prepare either one or two queries
|
// an empty subscription list is a special case
|
||||||
|
if (!sizeof($subscriptions)) {
|
||||||
|
if ($mode == self::ASSOC_REPLACE) {
|
||||||
|
// replacing with an empty set means setting everything to zero
|
||||||
|
return $this->db->prepare("UPDATE arsse_tag_members set assigned = 0, modified = CURRENT_TIMESTAMP where tag = ? and assigned = 1", "int")->run($id)->changes();
|
||||||
|
} else {
|
||||||
|
// adding or removing is a no-op
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// prepare up to three queries: removing requires one, adding two, and replacing three
|
||||||
list($inClause, $inTypes, $inValues) = $this->generateIn($subscriptions, "int");
|
list($inClause, $inTypes, $inValues) = $this->generateIn($subscriptions, "int");
|
||||||
// first update any existing entries with the removal or re-addition of their association
|
$updateQ = "UPDATE arsse_tag_members set assigned = ?, modified = CURRENT_TIMESTAMP where tag = ? and assigned <> ? and subscription in (select id from arsse_subscriptions where owner = ? and id %in% ($inClause))";
|
||||||
$q1 = $this->db->prepare(
|
$updateT = ["bool", "int", "bool", "str", $inTypes];
|
||||||
"UPDATE arsse_tag_members
|
$insertQ = "INSERT INTO arsse_tag_members(tag,subscription) SELECT ?,id from arsse_subscriptions where id not in (select subscription from arsse_tag_members where tag = ?) and owner = ? and id in ($inClause)";
|
||||||
set assigned = ?, modified = CURRENT_TIMESTAMP
|
$insertT = ["int", "int", "str", $inTypes];
|
||||||
where tag = ? and assigned <> ? and subscription in (select id from arsse_subscriptions where owner = ? and id in ($inClause))",
|
$clearQ = str_replace("%in%", "not in", $updateQ);
|
||||||
"bool",
|
$clearT = $updateT;
|
||||||
"int",
|
$updateQ = str_replace("%in%", "in", $updateQ);
|
||||||
"bool",
|
$qList = [];
|
||||||
"str",
|
switch ($mode) {
|
||||||
$inTypes
|
case self::ASSOC_REMOVE:
|
||||||
);
|
$qList[] = [$updateQ, $updateT, [0, $id, 0, $user, $inValues]]; // soft-delete any existing associations
|
||||||
$v1 = [!$remove, $id, !$remove, $user, $inValues];
|
break;
|
||||||
// next, if we're not removing, add any new entries that need to be added
|
case self::ASSOC_ADD:
|
||||||
if (!$remove) {
|
$qList[] = [$updateQ, $updateT, [1, $id, 1, $user, $inValues]]; // re-enable any previously soft-deleted association
|
||||||
$q2 = $this->db->prepare(
|
$qList[] = [$insertQ, $insertT, [$id, $id, $user, $inValues]]; // insert any newly-required associations
|
||||||
"INSERT INTO arsse_tag_members(tag,subscription) SELECT ?,id from arsse_subscriptions where id not in (select subscription from arsse_tag_members where tag = ?) and owner = ? and id in ($inClause)",
|
break;
|
||||||
"int",
|
case self::ASSOC_REPLACE:
|
||||||
"int",
|
$qList[] = [$clearQ, $clearT, [0, $id, 0, $user, $inValues]]; // soft-delete any existing associations for subscriptions not in the list
|
||||||
"str",
|
$qList[] = [$updateQ, $updateT, [1, $id, 1, $user, $inValues]]; // re-enable any previously soft-deleted association
|
||||||
$inTypes
|
$qList[] = [$insertQ, $insertT, [$id, $id, $user, $inValues]]; // insert any newly-required associations
|
||||||
);
|
break;
|
||||||
$v2 = [$id, $id, $user, $inValues];
|
|
||||||
}
|
}
|
||||||
// execute them in a transaction
|
// execute them in a transaction
|
||||||
$out = 0;
|
$out = 0;
|
||||||
$tr = $this->begin();
|
$tr = $this->begin();
|
||||||
$out += $q1->run($v1)->changes();
|
foreach ($qList as list($q, $t, $v)) {
|
||||||
if (!$remove) {
|
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
||||||
$out += $q2->run($v2)->changes();
|
|
||||||
}
|
}
|
||||||
$tr->commit();
|
$tr->commit();
|
||||||
return $out;
|
return $out;
|
||||||
|
|
|
@ -1017,6 +1017,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$label = $this->labelIn($data['label_id']);
|
$label = $this->labelIn($data['label_id']);
|
||||||
$articles = explode(",", (string) $data['article_ids']);
|
$articles = explode(",", (string) $data['article_ids']);
|
||||||
$assign = $data['assign'] ?? false;
|
$assign = $data['assign'] ?? false;
|
||||||
|
$assign = $assign ? Database::ASSOC_ADD : Database::ASSOC_REMOVE;
|
||||||
$out = 0;
|
$out = 0;
|
||||||
$in = array_chunk($articles, 50);
|
$in = array_chunk($articles, 50);
|
||||||
for ($a = 0; $a < sizeof($in); $a++) {
|
for ($a = 0; $a < sizeof($in); $a++) {
|
||||||
|
@ -1024,7 +1025,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$c = new Context;
|
$c = new Context;
|
||||||
$c->articles($in[$a]);
|
$c->articles($in[$a]);
|
||||||
try {
|
try {
|
||||||
$out += Arsse::$db->labelArticlesSet(Arsse::$user->id, $label, $c, !$assign);
|
$out += Arsse::$db->labelArticlesSet(Arsse::$user->id, $label, $c, $assign);
|
||||||
} catch (ExceptionInput $e) {
|
} catch (ExceptionInput $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\Database;
|
namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\Context\Context;
|
use JKingWeb\Arsse\Context\Context;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
use Phake;
|
use Phake;
|
||||||
|
@ -490,14 +491,14 @@ trait SeriesLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearALabelFromArticles() {
|
public function testClearALabelFromArticles() {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([1,5]), true);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([1,5]), Database::ASSOC_REMOVE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyALabelToArticlesByName() {
|
public function testApplyALabelToArticlesByName() {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([2,5]), false, true);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([2,5]), Database::ASSOC_ADD, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][4][3] = 1;
|
$state['arsse_label_members']['rows'][4][3] = 1;
|
||||||
$state['arsse_label_members']['rows'][] = [1,2,1,1];
|
$state['arsse_label_members']['rows'][] = [1,2,1,1];
|
||||||
|
@ -505,12 +506,42 @@ trait SeriesLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearALabelFromArticlesByName() {
|
public function testClearALabelFromArticlesByName() {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([1,5]), true, true);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([1,5]), Database::ASSOC_REMOVE, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testApplyALabelToNoArticles() {
|
||||||
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]));
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearALabelFromNoArticles() {
|
||||||
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REMOVE);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReplaceArticlesOfALabel() {
|
||||||
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]), Database::ASSOC_REPLACE);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
|
$state['arsse_label_members']['rows'][2][3] = 0;
|
||||||
|
$state['arsse_label_members']['rows'][4][3] = 1;
|
||||||
|
$state['arsse_label_members']['rows'][] = [1,2,1,1];
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPurgeArticlesOfALabel() {
|
||||||
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REPLACE);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
|
$state['arsse_label_members']['rows'][2][3] = 0;
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
public function testApplyALabelToArticlesWithoutAuthority() {
|
public function testApplyALabelToArticlesWithoutAuthority() {
|
||||||
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
|
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\Database;
|
namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
use Phake;
|
use Phake;
|
||||||
|
|
||||||
|
@ -311,7 +312,7 @@ trait SeriesTag {
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTagledSubscriptions() {
|
public function testListTaggedSubscriptions() {
|
||||||
$exp = [1,5];
|
$exp = [1,5];
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1));
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Interesting", true));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Interesting", true));
|
||||||
|
@ -323,17 +324,17 @@ trait SeriesTag {
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Lonely", true));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Lonely", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTagledSubscriptionsForAMissingTag() {
|
public function testListTaggedSubscriptionsForAMissingTag() {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 3);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTagledSubscriptionsForAnInvalidTag() {
|
public function testListTaggedSubscriptionsForAnInvalidTag() {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", -1);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTagledSubscriptionsWithoutAuthority() {
|
public function testListTaggedSubscriptionsWithoutAuthority() {
|
||||||
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1);
|
||||||
|
@ -348,14 +349,14 @@ trait SeriesTag {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearATagFromSubscriptions() {
|
public function testClearATagFromSubscriptions() {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [1,3], true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [1,3], Database::ASSOC_REMOVE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyATagToSubscriptionsByName() {
|
public function testApplyATagToSubscriptionsByName() {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [3,4], false, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [3,4], Database::ASSOC_ADD, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][1][2] = 1;
|
$state['arsse_tag_members']['rows'][1][2] = 1;
|
||||||
$state['arsse_tag_members']['rows'][] = [1,4,1];
|
$state['arsse_tag_members']['rows'][] = [1,4,1];
|
||||||
|
@ -363,12 +364,42 @@ trait SeriesTag {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearATagFromSubscriptionsByName() {
|
public function testClearATagFromSubscriptionsByName() {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [1,3], true, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [1,3], Database::ASSOC_REMOVE, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testApplyATagToNoSubscriptionsByName() {
|
||||||
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_ADD, true);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearATagFromNoSubscriptionsByName() {
|
||||||
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_REMOVE, true);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReplaceSubscriptionsOfATag() {
|
||||||
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4], Database::ASSOC_REPLACE);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
|
$state['arsse_tag_members']['rows'][1][2] = 1;
|
||||||
|
$state['arsse_tag_members']['rows'][2][2] = 0;
|
||||||
|
$state['arsse_tag_members']['rows'][] = [1,4,1];
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPurgeSubscriptionsOfATag() {
|
||||||
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [], Database::ASSOC_REPLACE);
|
||||||
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
|
$state['arsse_tag_members']['rows'][2][2] = 0;
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
public function testApplyATagToSubscriptionsWithoutAuthority() {
|
public function testApplyATagToSubscriptionsWithoutAuthority() {
|
||||||
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
|
|
|
@ -1289,18 +1289,18 @@ LONG_STRING;
|
||||||
];
|
];
|
||||||
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
|
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
|
||||||
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
|
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
|
||||||
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), true)->thenReturn(42);
|
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), Database::ASSOC_REMOVE)->thenReturn(42);
|
||||||
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), true)->thenReturn(47);
|
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), Database::ASSOC_REMOVE)->thenReturn(47);
|
||||||
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), false)->thenReturn(5);
|
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), Database::ASSOC_ADD)->thenReturn(5);
|
||||||
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), false)->thenReturn(2);
|
Phake::when(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), Database::ASSOC_ADD)->thenReturn(2);
|
||||||
$exp = $this->respGood(['status' => "OK", 'updated' => 89]);
|
$exp = $this->respGood(['status' => "OK", 'updated' => 89]);
|
||||||
$this->assertMessage($exp, $this->req($in[0]));
|
$this->assertMessage($exp, $this->req($in[0]));
|
||||||
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), true);
|
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), Database::ASSOC_REMOVE);
|
||||||
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), true);
|
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), Database::ASSOC_REMOVE);
|
||||||
$exp = $this->respGood(['status' => "OK", 'updated' => 7]);
|
$exp = $this->respGood(['status' => "OK", 'updated' => 7]);
|
||||||
$this->assertMessage($exp, $this->req($in[1]));
|
$this->assertMessage($exp, $this->req($in[1]));
|
||||||
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), false);
|
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[1]), Database::ASSOC_ADD);
|
||||||
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), false);
|
Phake::verify(Arsse::$db)->labelArticlesSet(Arsse::$user->id, 1088, (new Context)->articles($list[2]), Database::ASSOC_ADD);
|
||||||
$exp = $this->respGood(['status' => "OK", 'updated' => 0]);
|
$exp = $this->respGood(['status' => "OK", 'updated' => 0]);
|
||||||
$this->assertMessage($exp, $this->req($in[2]));
|
$this->assertMessage($exp, $this->req($in[2]));
|
||||||
$exp = $this->respErr("INCORRECT_USAGE");
|
$exp = $this->respErr("INCORRECT_USAGE");
|
||||||
|
|
Loading…
Reference in a new issue