mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Merge PDO branch
This commit is contained in:
commit
1a4989e1e0
54 changed files with 2053 additions and 518 deletions
|
@ -1,6 +1,9 @@
|
||||||
Version 0.3.0 (2018-??-??)
|
Version 0.3.0 (2018-??-??)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Support for SQLite3 via PDO
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
- Make date strings in TTRSS explicitly UTC
|
- Make date strings in TTRSS explicitly UTC
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ The Arsse has the following requirements:
|
||||||
- PHP 7.0.7 or later with the following extensions:
|
- PHP 7.0.7 or later with the following extensions:
|
||||||
- [intl](http://php.net/manual/en/book.intl.php), [json](http://php.net/manual/en/book.json.php), [hash](http://php.net/manual/en/book.hash.php), and [pcre](http://php.net/manual/en/book.pcre.php)
|
- [intl](http://php.net/manual/en/book.intl.php), [json](http://php.net/manual/en/book.json.php), [hash](http://php.net/manual/en/book.hash.php), and [pcre](http://php.net/manual/en/book.pcre.php)
|
||||||
- [dom](http://php.net/manual/en/book.dom.php), [simplexml](http://php.net/manual/en/book.simplexml.php), and [iconv](http://php.net/manual/en/book.iconv.php) (for picoFeed)
|
- [dom](http://php.net/manual/en/book.dom.php), [simplexml](http://php.net/manual/en/book.simplexml.php), and [iconv](http://php.net/manual/en/book.iconv.php) (for picoFeed)
|
||||||
- [sqlite3](http://php.net/manual/en/book.sqlite3.php)
|
- [sqlite3](http://php.net/manual/en/book.sqlite3.php) or [pdo_sqlite](http://ca1.php.net/manual/en/ref.pdo-sqlite.php)
|
||||||
- Privileges to create and run daemon processes on the server
|
- Privileges to create and run daemon processes on the server
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
|
@ -45,7 +45,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* See help for the "test" task for more details.
|
* See help for the "test" task for more details.
|
||||||
*/
|
*/
|
||||||
public function testQuick(array $args): Result {
|
public function testQuick(array $args): Result {
|
||||||
return $this->test(array_merge(["--exclude-group","slow"], $args));
|
return $this->test(array_merge(["--exclude-group", "slow,optional"], $args));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Produces a code coverage report
|
/** Produces a code coverage report
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Database {
|
||||||
|
|
||||||
public function __construct($initialize = true) {
|
public function __construct($initialize = true) {
|
||||||
$driver = Arsse::$conf->dbDriver;
|
$driver = Arsse::$conf->dbDriver;
|
||||||
$this->db = new $driver();
|
$this->db = $driver::create();
|
||||||
$ver = $this->db->schemaVersion();
|
$ver = $this->db->schemaVersion();
|
||||||
if ($initialize && $ver < self::SCHEMA_VERSION) {
|
if ($initialize && $ver < self::SCHEMA_VERSION) {
|
||||||
$this->db->schemaUpdate(self::SCHEMA_VERSION);
|
$this->db->schemaUpdate(self::SCHEMA_VERSION);
|
||||||
|
@ -415,7 +415,7 @@ class Database {
|
||||||
return $f;
|
return $f;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function folderValidateMove(string $user, int $id = null, $parent = null, string $name = null) {
|
protected function folderValidateMove(string $user, $id = null, $parent = null, string $name = null) {
|
||||||
$errData = ["action" => $this->caller(), "field" => "parent", 'id' => $parent];
|
$errData = ["action" => $this->caller(), "field" => "parent", 'id' => $parent];
|
||||||
if (!$id) {
|
if (!$id) {
|
||||||
// the root cannot be moved
|
// the root cannot be moved
|
||||||
|
@ -467,7 +467,7 @@ class Database {
|
||||||
return $parent;
|
return $parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function folderValidateName($name, bool $checkDuplicates = false, int $parent = null): bool {
|
protected function folderValidateName($name, bool $checkDuplicates = false, $parent = null): bool {
|
||||||
$info = ValueInfo::str($name);
|
$info = ValueInfo::str($name);
|
||||||
if ($info & (ValueInfo::NULL | ValueInfo::EMPTY)) {
|
if ($info & (ValueInfo::NULL | ValueInfo::EMPTY)) {
|
||||||
throw new Db\ExceptionInput("missing", ["action" => $this->caller(), "field" => "name"]);
|
throw new Db\ExceptionInput("missing", ["action" => $this->caller(), "field" => "name"]);
|
||||||
|
@ -572,7 +572,7 @@ class Database {
|
||||||
// add a suitable WHERE condition
|
// add a suitable WHERE condition
|
||||||
$q->setWhere("folder in (select folder from folders)");
|
$q->setWhere("folder in (select folder from folders)");
|
||||||
}
|
}
|
||||||
return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue();
|
return (int) $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function subscriptionRemove(string $user, $id): bool {
|
public function subscriptionRemove(string $user, $id): bool {
|
||||||
|
@ -1102,7 +1102,7 @@ class Database {
|
||||||
$q = $this->articleQuery($user, $context);
|
$q = $this->articleQuery($user, $context);
|
||||||
$q->pushCTE("selected_articles");
|
$q->pushCTE("selected_articles");
|
||||||
$q->setBody("SELECT count(*) from selected_articles");
|
$q->setBody("SELECT count(*) from selected_articles");
|
||||||
return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue();
|
return (int) $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db;
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
abstract class AbstractDriver implements Driver {
|
abstract class AbstractDriver implements Driver {
|
||||||
protected $locked = false;
|
protected $locked = false;
|
||||||
protected $transDepth = 0;
|
protected $transDepth = 0;
|
||||||
protected $transStatus = [];
|
protected $transStatus = [];
|
||||||
|
|
||||||
abstract public function prepareArray(string $query, array $paramTypes): Statement;
|
abstract protected function getError(): string;
|
||||||
abstract protected function lock(): bool;
|
|
||||||
abstract protected function unlock(bool $rollback = false) : bool;
|
|
||||||
|
|
||||||
/** @codeCoverageIgnore */
|
/** @codeCoverageIgnore */
|
||||||
public function schemaVersion(): int {
|
public function schemaVersion(): int {
|
||||||
|
@ -25,6 +25,54 @@ abstract class AbstractDriver implements Driver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function schemaUpdate(int $to, string $basePath = null): bool {
|
||||||
|
$ver = $this->schemaVersion();
|
||||||
|
if (!Arsse::$conf->dbAutoUpdate) {
|
||||||
|
throw new Exception("updateManual", ['version' => $ver, 'driver_name' => $this->driverName()]);
|
||||||
|
} elseif ($ver >= $to) {
|
||||||
|
throw new Exception("updateTooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
||||||
|
}
|
||||||
|
$sep = \DIRECTORY_SEPARATOR;
|
||||||
|
$path = ($basePath ?? \JKingWeb\Arsse\BASE."sql").$sep.static::schemaID().$sep;
|
||||||
|
// lock the database
|
||||||
|
$this->savepointCreate(true);
|
||||||
|
for ($a = $this->schemaVersion(); $a < $to; $a++) {
|
||||||
|
$this->savepointCreate();
|
||||||
|
try {
|
||||||
|
$file = $path.$a.".sql";
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
throw new Exception("updateFileMissing", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
|
} elseif (!is_readable($file)) {
|
||||||
|
throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
|
}
|
||||||
|
$sql = @file_get_contents($file);
|
||||||
|
if ($sql===false) {
|
||||||
|
throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); // @codeCoverageIgnore
|
||||||
|
} elseif ($sql==="") {
|
||||||
|
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->exec($sql);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]);
|
||||||
|
}
|
||||||
|
if ($this->schemaVersion() != $a+1) {
|
||||||
|
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// undo any partial changes from the failed update
|
||||||
|
$this->savepointUndo();
|
||||||
|
// commit any successful updates if updating by more than one version
|
||||||
|
$this->savepointRelease();
|
||||||
|
// throw the error received
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
$this->savepointRelease();
|
||||||
|
}
|
||||||
|
$this->savepointRelease();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public function begin(bool $lock = false): Transaction {
|
public function begin(bool $lock = false): Transaction {
|
||||||
return new Transaction($this, $lock);
|
return new Transaction($this, $lock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,29 +7,31 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db;
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
|
|
||||||
abstract class AbstractStatement implements Statement {
|
abstract class AbstractStatement implements Statement {
|
||||||
protected $types = [];
|
protected $types = [];
|
||||||
protected $isNullable = [];
|
protected $isNullable = [];
|
||||||
|
|
||||||
abstract public function runArray(array $values = []): Result;
|
abstract public function runArray(array $values = []): Result;
|
||||||
|
abstract protected function bindValue($value, string $type, int $position): bool;
|
||||||
|
|
||||||
public function run(...$values): Result {
|
public function run(...$values): Result {
|
||||||
return $this->runArray($values);
|
return $this->runArray($values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rebind(...$bindings): bool {
|
public function retype(...$bindings): bool {
|
||||||
return $this->rebindArray($bindings);
|
return $this->retypeArray($bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rebindArray(array $bindings, bool $append = false): bool {
|
public function retypeArray(array $bindings, bool $append = false): bool {
|
||||||
if (!$append) {
|
if (!$append) {
|
||||||
$this->types = [];
|
$this->types = [];
|
||||||
}
|
}
|
||||||
foreach ($bindings as $binding) {
|
foreach ($bindings as $binding) {
|
||||||
if (is_array($binding)) {
|
if (is_array($binding)) {
|
||||||
// recursively flatten any arrays, which may be provided for SET or IN() clauses
|
// recursively flatten any arrays, which may be provided for SET or IN() clauses
|
||||||
$this->rebindArray($binding, true);
|
$this->retypeArray($binding, true);
|
||||||
} else {
|
} else {
|
||||||
$binding = trim(strtolower($binding));
|
$binding = trim(strtolower($binding));
|
||||||
if (strpos($binding, "strict ")===0) {
|
if (strpos($binding, "strict ")===0) {
|
||||||
|
@ -50,43 +52,42 @@ abstract class AbstractStatement implements Statement {
|
||||||
|
|
||||||
protected function cast($v, string $t, bool $nullable) {
|
protected function cast($v, string $t, bool $nullable) {
|
||||||
switch ($t) {
|
switch ($t) {
|
||||||
case "date":
|
|
||||||
if (is_null($v) && !$nullable) {
|
|
||||||
$v = 0;
|
|
||||||
}
|
|
||||||
return Date::transform($v, "date");
|
|
||||||
case "time":
|
|
||||||
if (is_null($v) && !$nullable) {
|
|
||||||
$v = 0;
|
|
||||||
}
|
|
||||||
return Date::transform($v, "time");
|
|
||||||
case "datetime":
|
case "datetime":
|
||||||
|
$v = Date::transform($v, "sql");
|
||||||
if (is_null($v) && !$nullable) {
|
if (is_null($v) && !$nullable) {
|
||||||
$v = 0;
|
$v = 0;
|
||||||
}
|
$v = Date::transform($v, "sql");
|
||||||
return Date::transform($v, "sql");
|
|
||||||
case "null":
|
|
||||||
case "integer":
|
|
||||||
case "float":
|
|
||||||
case "binary":
|
|
||||||
case "string":
|
|
||||||
case "boolean":
|
|
||||||
if ($t=="binary") {
|
|
||||||
$t = "string";
|
|
||||||
}
|
|
||||||
if ($v instanceof \DateTimeInterface) {
|
|
||||||
if ($t=="string") {
|
|
||||||
return Date::transform($v, "sql");
|
|
||||||
} else {
|
|
||||||
$v = $v->getTimestamp();
|
|
||||||
settype($v, $t);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
settype($v, $t);
|
|
||||||
}
|
}
|
||||||
return $v;
|
return $v;
|
||||||
|
case "integer":
|
||||||
|
return ValueInfo::normalize($v, ValueInfo::T_INT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
|
||||||
|
case "float":
|
||||||
|
return ValueInfo::normalize($v, ValueInfo::T_FLOAT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
|
||||||
|
case "binary":
|
||||||
|
case "string":
|
||||||
|
return ValueInfo::normalize($v, ValueInfo::T_STRING | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
|
||||||
|
case "boolean":
|
||||||
|
$v = ValueInfo::normalize($v, ValueInfo::T_BOOL | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
|
||||||
|
return is_null($v) ? $v : (int) $v;
|
||||||
default:
|
default:
|
||||||
throw new Exception("paramTypeUnknown", $type); // @codeCoverageIgnore
|
throw new Exception("paramTypeUnknown", $type); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function bindValues(array $values, int $offset = 0): int {
|
||||||
|
$a = $offset;
|
||||||
|
foreach ($values as $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
// recursively flatten any arrays, which may be provided for SET or IN() clauses
|
||||||
|
$a += $this->bindValues($value, $a);
|
||||||
|
} elseif (array_key_exists($a, $this->types)) {
|
||||||
|
$value = $this->cast($value, $this->types[$a], $this->isNullable[$a]);
|
||||||
|
$this->bindValue($value, $this->types[$a], $a+1);
|
||||||
|
$a++;
|
||||||
|
} else {
|
||||||
|
throw new Exception("paramTypeMissing", $a+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $a - $offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,13 @@ interface Driver {
|
||||||
const TR_PEND_COMMIT = -1;
|
const TR_PEND_COMMIT = -1;
|
||||||
const TR_PEND_ROLLBACK = -2;
|
const TR_PEND_ROLLBACK = -2;
|
||||||
|
|
||||||
public function __construct();
|
public static function create(): Driver;
|
||||||
// returns a human-friendly name for the driver (for display in installer, for example)
|
// returns a human-friendly name for the driver (for display in installer, for example)
|
||||||
public static function driverName(): string;
|
public static function driverName(): string;
|
||||||
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
||||||
public function schemaVersion(): int;
|
public function schemaVersion(): int;
|
||||||
|
// returns the schema set to be used for database set-up
|
||||||
|
public static function schemaID(): string;
|
||||||
// return a Transaction object
|
// return a Transaction object
|
||||||
public function begin(bool $lock = false): Transaction;
|
public function begin(bool $lock = false): Transaction;
|
||||||
// manually begin a real or synthetic transactions, with real or synthetic nesting
|
// manually begin a real or synthetic transactions, with real or synthetic nesting
|
||||||
|
|
47
lib/Db/PDODriver.php
Normal file
47
lib/Db/PDODriver.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
|
trait PDODriver {
|
||||||
|
use PDOError;
|
||||||
|
|
||||||
|
public function exec(string $query): bool {
|
||||||
|
try {
|
||||||
|
$this->db->exec($query);
|
||||||
|
return true;
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
|
||||||
|
throw new $excClass($excMsg, $excData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query(string $query): Result {
|
||||||
|
try {
|
||||||
|
$r = $this->db->query($query);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
|
||||||
|
throw new $excClass($excMsg, $excData);
|
||||||
|
}
|
||||||
|
$changes = $r->rowCount();
|
||||||
|
try {
|
||||||
|
$lastId = 0;
|
||||||
|
$lastId = $this->db->lastInsertId();
|
||||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
return new PDOResult($r, [$changes, $lastId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareArray(string $query, array $paramTypes): Statement {
|
||||||
|
try {
|
||||||
|
$s = $this->db->prepare($query);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
|
||||||
|
throw new $excClass($excMsg, $excData);
|
||||||
|
}
|
||||||
|
return new PDOStatement($this->db, $s, $paramTypes);
|
||||||
|
}
|
||||||
|
}
|
44
lib/Db/PDOError.php
Normal file
44
lib/Db/PDOError.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
|
trait PDOError {
|
||||||
|
public function exceptionBuild() {
|
||||||
|
if ($this instanceof Statement) {
|
||||||
|
$err = $this->st->errorInfo();
|
||||||
|
} else {
|
||||||
|
$err = $this->db->errorInfo();
|
||||||
|
}
|
||||||
|
switch ($err[0]) {
|
||||||
|
case "23000":
|
||||||
|
return [ExceptionInput::class, "constraintViolation", $err[2]];
|
||||||
|
case "HY000":
|
||||||
|
// engine-specific errors
|
||||||
|
switch ($this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
|
||||||
|
case "sqlite":
|
||||||
|
switch ($err[1]) {
|
||||||
|
case \JKingWeb\Arsse\Db\SQLite3\Driver::SQLITE_BUSY:
|
||||||
|
return [ExceptionTimeout::class, 'general', $err[2]];
|
||||||
|
case \JKingWeb\Arsse\Db\SQLite3\Driver::SQLITE_MISMATCH:
|
||||||
|
return [ExceptionInput::class, 'engineTypeViolation', $err[2]];
|
||||||
|
default:
|
||||||
|
return [Exception::class, "engineErrorGeneral", $err[1]." - ".$err[2]];
|
||||||
|
}
|
||||||
|
// no break
|
||||||
|
default:
|
||||||
|
return [Exception::class, "engineErrorGeneral", $err[2]]; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
// no break
|
||||||
|
default:
|
||||||
|
return [Exception::class, "engineErrorGeneral", $err[0].": ".$err[2]]; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError(): string {
|
||||||
|
return (string) $this->db->errorInfo()[2];
|
||||||
|
}
|
||||||
|
}
|
49
lib/Db/PDOResult.php
Normal file
49
lib/Db/PDOResult.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Db\Exception;
|
||||||
|
|
||||||
|
class PDOResult extends AbstractResult {
|
||||||
|
protected $set;
|
||||||
|
protected $cur = null;
|
||||||
|
protected $rows = 0;
|
||||||
|
protected $id = 0;
|
||||||
|
|
||||||
|
// actual public methods
|
||||||
|
|
||||||
|
public function changes() {
|
||||||
|
return $this->rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastId() {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructor/destructor
|
||||||
|
|
||||||
|
public function __construct(\PDOStatement $result, array $changes = [0,0]) {
|
||||||
|
$this->set = $result;
|
||||||
|
$this->rows = (int) $changes[0];
|
||||||
|
$this->id = (int) $changes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
try {
|
||||||
|
$this->set->closeCursor();
|
||||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
unset($this->set);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHP iterator methods
|
||||||
|
|
||||||
|
public function valid() {
|
||||||
|
$this->cur = $this->set->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
return ($this->cur !== false);
|
||||||
|
}
|
||||||
|
}
|
55
lib/Db/PDOStatement.php
Normal file
55
lib/Db/PDOStatement.php
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
|
class PDOStatement extends AbstractStatement {
|
||||||
|
use PDOError;
|
||||||
|
|
||||||
|
const BINDINGS = [
|
||||||
|
"integer" => \PDO::PARAM_INT,
|
||||||
|
"float" => \PDO::PARAM_STR,
|
||||||
|
"datetime" => \PDO::PARAM_STR,
|
||||||
|
"binary" => \PDO::PARAM_LOB,
|
||||||
|
"string" => \PDO::PARAM_STR,
|
||||||
|
"boolean" => \PDO::PARAM_BOOL,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $st;
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
public function __construct(\PDO $db, \PDOStatement $st, array $bindings = []) {
|
||||||
|
$this->db = $db;
|
||||||
|
$this->st = $st;
|
||||||
|
$this->retypeArray($bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
unset($this->st);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result {
|
||||||
|
$this->st->closeCursor();
|
||||||
|
$this->bindValues($values);
|
||||||
|
try {
|
||||||
|
$this->st->execute();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
|
||||||
|
throw new $excClass($excMsg, $excData);
|
||||||
|
}
|
||||||
|
$changes = $this->st->rowCount();
|
||||||
|
try {
|
||||||
|
$lastId = 0;
|
||||||
|
$lastId = $this->db->lastInsertId();
|
||||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
return new PDOResult($this->st, [$changes, $lastId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function bindValue($value, string $type, int $position): bool {
|
||||||
|
return $this->st->bindValue($position, $value, is_null($value) ? \PDO::PARAM_NULL : self::BINDINGS[$type]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,17 +22,14 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
|
|
||||||
public function __construct(string $dbFile = null) {
|
public function __construct(string $dbFile = null) {
|
||||||
// check to make sure required extension is loaded
|
// check to make sure required extension is loaded
|
||||||
if (!class_exists("SQLite3")) {
|
if (!self::requirementsMet()) {
|
||||||
throw new Exception("extMissing", self::driverName()); // @codeCoverageIgnore
|
throw new Exception("extMissing", self::driverName()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
// if no database file is specified in the configuration, use a suitable default
|
// if no database file is specified in the configuration, use a suitable default
|
||||||
$dbFile = $dbFile ?? Arsse::$conf->dbSQLite3File ?? \JKingWeb\Arsse\BASE."arsse.db";
|
$dbFile = $dbFile ?? Arsse::$conf->dbSQLite3File ?? \JKingWeb\Arsse\BASE."arsse.db";
|
||||||
$mode = \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE;
|
|
||||||
$timeout = Arsse::$conf->dbSQLite3Timeout * 1000;
|
$timeout = Arsse::$conf->dbSQLite3Timeout * 1000;
|
||||||
try {
|
try {
|
||||||
$this->db = $this->makeConnection($dbFile, $mode, Arsse::$conf->dbSQLite3Key);
|
$this->makeConnection($dbFile, Arsse::$conf->dbSQLite3Key);
|
||||||
// enable exceptions
|
|
||||||
$this->db->enableExceptions(true);
|
|
||||||
// set the timeout; parameters are not allowed for pragmas, but this usage should be safe
|
// set the timeout; parameters are not allowed for pragmas, but this usage should be safe
|
||||||
$this->exec("PRAGMA busy_timeout = $timeout");
|
$this->exec("PRAGMA busy_timeout = $timeout");
|
||||||
// set other initial options
|
// set other initial options
|
||||||
|
@ -60,8 +57,14 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $file, int $opts, string $key) {
|
public static function requirementsMet(): bool {
|
||||||
return new \SQLite3($file, $opts, $key);
|
return class_exists("SQLite3");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeConnection(string $file, string $key) {
|
||||||
|
$this->db = new \SQLite3($file, \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE, $key);
|
||||||
|
// enable exceptions
|
||||||
|
$this->db->enableExceptions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
|
@ -72,60 +75,42 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
unset($this->db);
|
unset($this->db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
public static function create(): \JKingWeb\Arsse\Db\Driver {
|
||||||
|
if (self::requirementsMet()) {
|
||||||
|
return new self;
|
||||||
|
} elseif (PDODriver::requirementsMet()) {
|
||||||
|
return new PDODriver;
|
||||||
|
} else {
|
||||||
|
throw new Exception("extMissing", self::driverName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function driverName(): string {
|
public static function driverName(): string {
|
||||||
return Arsse::$lang->msg("Driver.Db.SQLite3.Name");
|
return Arsse::$lang->msg("Driver.Db.SQLite3.Name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function schemaID(): string {
|
||||||
|
return "SQLite3";
|
||||||
|
}
|
||||||
|
|
||||||
public function schemaVersion(): int {
|
public function schemaVersion(): int {
|
||||||
return $this->query("PRAGMA user_version")->getValue();
|
return (int) $this->query("PRAGMA user_version")->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function schemaUpdate(int $to, string $basePath = null): bool {
|
public function schemaUpdate(int $to, string $basePath = null): bool {
|
||||||
$ver = $this->schemaVersion();
|
|
||||||
if (!Arsse::$conf->dbAutoUpdate) {
|
|
||||||
throw new Exception("updateManual", ['version' => $ver, 'driver_name' => $this->driverName()]);
|
|
||||||
} elseif ($ver >= $to) {
|
|
||||||
throw new Exception("updateTooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
|
||||||
}
|
|
||||||
$sep = \DIRECTORY_SEPARATOR;
|
|
||||||
$path = ($basePath ?? \JKingWeb\Arsse\BASE."sql").$sep."SQLite3".$sep;
|
|
||||||
// turn off foreign keys
|
// turn off foreign keys
|
||||||
$this->exec("PRAGMA foreign_keys = no");
|
$this->exec("PRAGMA foreign_keys = no");
|
||||||
// lock the database
|
// run the generic updater
|
||||||
$this->savepointCreate(true);
|
|
||||||
for ($a = $this->schemaVersion(); $a < $to; $a++) {
|
|
||||||
$this->savepointCreate();
|
|
||||||
try {
|
try {
|
||||||
$file = $path.$a.".sql";
|
parent::schemaUpdate($to, $basePath);
|
||||||
if (!file_exists($file)) {
|
|
||||||
throw new Exception("updateFileMissing", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
|
||||||
} elseif (!is_readable($file)) {
|
|
||||||
throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
|
||||||
}
|
|
||||||
$sql = @file_get_contents($file);
|
|
||||||
if ($sql===false) {
|
|
||||||
throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$this->exec($sql);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]);
|
// turn foreign keys back on
|
||||||
}
|
$this->exec("PRAGMA foreign_keys = yes");
|
||||||
if ($this->schemaVersion() != $a+1) {
|
// pass the exception up
|
||||||
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// undo any partial changes from the failed update
|
|
||||||
$this->savepointUndo();
|
|
||||||
// commit any successful updates if updating by more than one version
|
|
||||||
$this->savepointRelease();
|
|
||||||
// throw the error received
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
$this->savepointRelease();
|
|
||||||
}
|
|
||||||
$this->savepointRelease();
|
|
||||||
// turn foreign keys back on
|
// turn foreign keys back on
|
||||||
$this->exec("PRAGMA foreign_keys = yes");
|
$this->exec("PRAGMA foreign_keys = yes");
|
||||||
return true;
|
return true;
|
||||||
|
|
46
lib/Db/SQLite3/PDODriver.php
Normal file
46
lib/Db/SQLite3/PDODriver.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Db\SQLite3;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Db\Exception;
|
||||||
|
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
|
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||||
|
|
||||||
|
class PDODriver extends Driver {
|
||||||
|
use \JKingWeb\Arsse\Db\PDODriver;
|
||||||
|
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
public static function requirementsMet(): bool {
|
||||||
|
return class_exists("PDO") && in_array("sqlite", \PDO::getAvailableDrivers());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeConnection(string $file, string $key) {
|
||||||
|
$this->db = new \PDO("sqlite:".$file, "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
unset($this->db);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
public static function create(): \JKingWeb\Arsse\Db\Driver {
|
||||||
|
if (self::requirementsMet()) {
|
||||||
|
return new self;
|
||||||
|
} elseif (Driver::requirementsMet()) {
|
||||||
|
return new Driver;
|
||||||
|
} else {
|
||||||
|
throw new Exception("extMissing", self::driverName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function driverName(): string {
|
||||||
|
return Arsse::$lang->msg("Driver.Db.SQLite3PDO.Name");
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,11 +17,8 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
const SQLITE_CONSTRAINT = 19;
|
const SQLITE_CONSTRAINT = 19;
|
||||||
const SQLITE_MISMATCH = 20;
|
const SQLITE_MISMATCH = 20;
|
||||||
const BINDINGS = [
|
const BINDINGS = [
|
||||||
"null" => \SQLITE3_NULL,
|
|
||||||
"integer" => \SQLITE3_INTEGER,
|
"integer" => \SQLITE3_INTEGER,
|
||||||
"float" => \SQLITE3_FLOAT,
|
"float" => \SQLITE3_FLOAT,
|
||||||
"date" => \SQLITE3_TEXT,
|
|
||||||
"time" => \SQLITE3_TEXT,
|
|
||||||
"datetime" => \SQLITE3_TEXT,
|
"datetime" => \SQLITE3_TEXT,
|
||||||
"binary" => \SQLITE3_BLOB,
|
"binary" => \SQLITE3_BLOB,
|
||||||
"string" => \SQLITE3_TEXT,
|
"string" => \SQLITE3_TEXT,
|
||||||
|
@ -34,7 +31,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
public function __construct(\SQLite3 $db, \SQLite3Stmt $st, array $bindings = []) {
|
public function __construct(\SQLite3 $db, \SQLite3Stmt $st, array $bindings = []) {
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
$this->st = $st;
|
$this->st = $st;
|
||||||
$this->rebindArray($bindings);
|
$this->retypeArray($bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
|
@ -59,34 +56,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
return new Result($r, [$changes, $lastId], $this);
|
return new Result($r, [$changes, $lastId], $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function bindValues(array $values, int $offset = 0): int {
|
protected function bindValue($value, string $type, int $position): bool {
|
||||||
$a = $offset;
|
return $this->st->bindValue($position, $value, is_null($value) ? \SQLITE3_NULL : self::BINDINGS[$type]);
|
||||||
foreach ($values as $value) {
|
|
||||||
if (is_array($value)) {
|
|
||||||
// recursively flatten any arrays, which may be provided for SET or IN() clauses
|
|
||||||
$a += $this->bindValues($value, $a);
|
|
||||||
} elseif (array_key_exists($a, $this->types)) {
|
|
||||||
// if the parameter type is something other than the known values, this is an error
|
|
||||||
assert(array_key_exists($this->types[$a], self::BINDINGS), new Exception("paramTypeUnknown", $this->types[$a]));
|
|
||||||
// if the parameter type is null or the value is null (and the type is nullable), just bind null
|
|
||||||
if ($this->types[$a]=="null" || ($this->isNullable[$a] && is_null($value))) {
|
|
||||||
$this->st->bindValue($a+1, null, \SQLITE3_NULL);
|
|
||||||
} else {
|
|
||||||
// otherwise cast the value to the right type and bind the result
|
|
||||||
$type = self::BINDINGS[$this->types[$a]];
|
|
||||||
$value = $this->cast($value, $this->types[$a], $this->isNullable[$a]);
|
|
||||||
// re-adjust for null casts
|
|
||||||
if ($value===null) {
|
|
||||||
$type = \SQLITE3_NULL;
|
|
||||||
}
|
|
||||||
// perform binding
|
|
||||||
$this->st->bindValue($a+1, $value, $type);
|
|
||||||
}
|
|
||||||
$a++;
|
|
||||||
} else {
|
|
||||||
throw new Exception("paramTypeMissing", $a+1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $a - $offset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,12 @@ namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
interface Statement {
|
interface Statement {
|
||||||
const TYPES = [
|
const TYPES = [
|
||||||
"null" => "null",
|
|
||||||
"nil" => "null",
|
|
||||||
"int" => "integer",
|
"int" => "integer",
|
||||||
"integer" => "integer",
|
"integer" => "integer",
|
||||||
"float" => "float",
|
"float" => "float",
|
||||||
"double" => "float",
|
"double" => "float",
|
||||||
"real" => "float",
|
"real" => "float",
|
||||||
"numeric" => "float",
|
"numeric" => "float",
|
||||||
"date" => "date",
|
|
||||||
"time" => "time",
|
|
||||||
"datetime" => "datetime",
|
"datetime" => "datetime",
|
||||||
"timestamp" => "datetime",
|
"timestamp" => "datetime",
|
||||||
"blob" => "binary",
|
"blob" => "binary",
|
||||||
|
@ -33,6 +29,6 @@ interface Statement {
|
||||||
|
|
||||||
public function run(...$values): Result;
|
public function run(...$values): Result;
|
||||||
public function runArray(array $values = []): Result;
|
public function runArray(array $values = []): Result;
|
||||||
public function rebind(...$bindings): bool;
|
public function retype(...$bindings): bool;
|
||||||
public function rebindArray(array $bindings): bool;
|
public function retypeArray(array $bindings): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,7 +353,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out = [];
|
$out = [];
|
||||||
foreach ($feeds as $feed) {
|
foreach ($feeds as $feed) {
|
||||||
// since in our implementation feeds don't belong the users, the 'userId' field will always be an empty string
|
// since in our implementation feeds don't belong the users, the 'userId' field will always be an empty string
|
||||||
$out[] = ['id' => $feed, 'userId' => ""];
|
$out[] = ['id' => (int) $feed, 'userId' => ""];
|
||||||
}
|
}
|
||||||
return new Response(200, ['feeds' => $out]);
|
return new Response(200, ['feeds' => $out]);
|
||||||
}
|
}
|
||||||
|
@ -419,7 +419,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out[] = $this->feedTranslate($sub);
|
$out[] = $this->feedTranslate($sub);
|
||||||
}
|
}
|
||||||
$out = ['feeds' => $out];
|
$out = ['feeds' => $out];
|
||||||
$out['starredCount'] = Arsse::$db->articleStarred(Arsse::$user->id)['total'];
|
$out['starredCount'] = (int) Arsse::$db->articleStarred(Arsse::$user->id)['total'];
|
||||||
$newest = Arsse::$db->editionLatest(Arsse::$user->id);
|
$newest = Arsse::$db->editionLatest(Arsse::$user->id);
|
||||||
if ($newest) {
|
if ($newest) {
|
||||||
$out['newestItemId'] = $newest;
|
$out['newestItemId'] = $newest;
|
||||||
|
|
|
@ -231,7 +231,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// prepare data for each subscription; we also add unread counts for their host categories
|
// prepare data for each subscription; we also add unread counts for their host categories
|
||||||
foreach (Arsse::$db->subscriptionList($user) as $f) {
|
foreach (Arsse::$db->subscriptionList($user) as $f) {
|
||||||
// add the feed to the list of feeds
|
// add the feed to the list of feeds
|
||||||
$feeds[] = ['id' => (string) $f['id'], 'updated' => Date::transform($f['updated'], "iso8601", "sql"),'counter' => $f['unread'], 'has_img' => (int) (strlen((string) $f['favicon']) > 0)]; // ID is cast to string for consistency with TTRSS
|
$feeds[] = ['id' => (string) $f['id'], 'updated' => Date::transform($f['updated'], "iso8601", "sql"),'counter' => (int) $f['unread'], 'has_img' => (int) (strlen((string) $f['favicon']) > 0)]; // ID is cast to string for consistency with TTRSS
|
||||||
// add the feed's unread count to the global unread count
|
// add the feed's unread count to the global unread count
|
||||||
$countAll += $f['unread'];
|
$countAll += $f['unread'];
|
||||||
// add the feed's unread count to its category unread count
|
// add the feed's unread count to its category unread count
|
||||||
|
@ -242,7 +242,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// prepare data for each non-empty label
|
// prepare data for each non-empty label
|
||||||
foreach (Arsse::$db->labelList($user, false) as $l) {
|
foreach (Arsse::$db->labelList($user, false) as $l) {
|
||||||
$unread = $l['articles'] - $l['read'];
|
$unread = $l['articles'] - $l['read'];
|
||||||
$labels[] = ['id' => $this->labelOut($l['id']), 'counter' => $unread, 'auxcounter' => $l['articles']];
|
$labels[] = ['id' => $this->labelOut($l['id']), 'counter' => $unread, 'auxcounter' => (int) $l['articles']];
|
||||||
$categories[$catmap[self::CAT_LABELS]]['counter'] += $unread;
|
$categories[$catmap[self::CAT_LABELS]]['counter'] += $unread;
|
||||||
}
|
}
|
||||||
// do a second pass on categories, summing descendant unread counts for ancestors
|
// do a second pass on categories, summing descendant unread counts for ancestors
|
||||||
|
@ -266,14 +266,14 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
// do a third pass on categories, building a final category list; this is done so that the original sort order is retained
|
// do a third pass on categories, building a final category list; this is done so that the original sort order is retained
|
||||||
foreach ($categories as $c) {
|
foreach ($categories as $c) {
|
||||||
$cats[] = ['id' => $c['id'], 'kind' => "cat", 'counter' => $catCounts[$c['id']]];
|
$cats[] = ['id' => (int) $c['id'], 'kind' => "cat", 'counter' => $catCounts[$c['id']]];
|
||||||
}
|
}
|
||||||
// prepare data for the virtual feeds and other counters
|
// prepare data for the virtual feeds and other counters
|
||||||
$special = [
|
$special = [
|
||||||
['id' => "global-unread", 'counter' => $countAll], //this should not count archived articles, but we do not have an archive
|
['id' => "global-unread", 'counter' => $countAll], //this should not count archived articles, but we do not have an archive
|
||||||
['id' => "subscribed-feeds", 'counter' => $countSubs],
|
['id' => "subscribed-feeds", 'counter' => $countSubs],
|
||||||
['id' => self::FEED_ARCHIVED, 'counter' => 0, 'auxcounter' => 0], // Archived articles
|
['id' => self::FEED_ARCHIVED, 'counter' => 0, 'auxcounter' => 0], // Archived articles
|
||||||
['id' => self::FEED_STARRED, 'counter' => $starred['unread'], 'auxcounter' => $starred['total']], // Starred articles
|
['id' => self::FEED_STARRED, 'counter' => (int) $starred['unread'], 'auxcounter' => (int) $starred['total']], // Starred articles
|
||||||
['id' => self::FEED_PUBLISHED, 'counter' => 0, 'auxcounter' => 0], // Published articles
|
['id' => self::FEED_PUBLISHED, 'counter' => 0, 'auxcounter' => 0], // Published articles
|
||||||
['id' => self::FEED_FRESH, 'counter' => $fresh, 'auxcounter' => 0], // Fresh articles
|
['id' => self::FEED_FRESH, 'counter' => $fresh, 'auxcounter' => 0], // Fresh articles
|
||||||
['id' => self::FEED_ALL, 'counter' => $countAll, 'auxcounter' => 0], // All articles
|
['id' => self::FEED_ALL, 'counter' => $countAll, 'auxcounter' => 0], // All articles
|
||||||
|
@ -323,7 +323,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
'id' => "FEED:".self::FEED_STARRED,
|
'id' => "FEED:".self::FEED_STARRED,
|
||||||
'bare_id' => self::FEED_STARRED,
|
'bare_id' => self::FEED_STARRED,
|
||||||
'icon' => "images/star.png",
|
'icon' => "images/star.png",
|
||||||
'unread' => Arsse::$db->articleStarred($user)['unread'],
|
'unread' => (int) Arsse::$db->articleStarred($user)['unread'],
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Published articles
|
array_merge([ // Published articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Published"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Published"),
|
||||||
|
@ -406,7 +406,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return ['categories' => ['identifier' => "id", 'label' => "name", 'items' => $out]];
|
return ['categories' => ['identifier' => "id", 'label' => "name", 'items' => $out]];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function enumerateFeeds(array $subs, int $parent = null): array {
|
protected function enumerateFeeds(array $subs, $parent = null): array {
|
||||||
$out = [];
|
$out = [];
|
||||||
foreach ($subs as $s) {
|
foreach ($subs as $s) {
|
||||||
if ($s['folder'] != $parent) {
|
if ($s['folder'] != $parent) {
|
||||||
|
@ -415,7 +415,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'name' => $s['title'],
|
'name' => $s['title'],
|
||||||
'id' => "FEED:".$s['id'],
|
'id' => "FEED:".$s['id'],
|
||||||
'bare_id' => $s['id'],
|
'bare_id' => (int) $s['id'],
|
||||||
'icon' => $s['favicon'] ? "feed-icons/".$s['id'].".ico" : false,
|
'icon' => $s['favicon'] ? "feed-icons/".$s['id'].".ico" : false,
|
||||||
'error' => (string) $s['err_msg'],
|
'error' => (string) $s['err_msg'],
|
||||||
'param' => Date::transform($s['updated'], "iso8601", "sql"),
|
'param' => Date::transform($s['updated'], "iso8601", "sql"),
|
||||||
|
@ -428,7 +428,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function enumerateCategories(array $cats, array $subs, int $parent = null, bool $all = false): array {
|
protected function enumerateCategories(array $cats, array $subs, $parent = null, bool $all = false): array {
|
||||||
$out = [];
|
$out = [];
|
||||||
$feedTotal = 0;
|
$feedTotal = 0;
|
||||||
foreach ($cats as $c) {
|
foreach ($cats as $c) {
|
||||||
|
@ -442,8 +442,8 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'name' => $c['name'],
|
'name' => $c['name'],
|
||||||
'id' => "CAT:".$c['id'],
|
'id' => "CAT:".$c['id'],
|
||||||
'bare_id' => $c['id'],
|
'bare_id' => (int) $c['id'],
|
||||||
'parent_id' => $c['parent'], // top-level categories are not supposed to have this property; we deviated and have the property set to null because it's simpler that way
|
'parent_id' => (int) $c['parent'] ?: null, // top-level categories are not supposed to have this property; we deviated and have the property set to null because it's simpler that way
|
||||||
'type' => "category",
|
'type' => "category",
|
||||||
'auxcounter' => 0,
|
'auxcounter' => 0,
|
||||||
'unread' => 0,
|
'unread' => 0,
|
||||||
|
@ -714,12 +714,12 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// NOTE: the list is a flat one: it includes children, but not other descendents
|
// NOTE: the list is a flat one: it includes children, but not other descendents
|
||||||
foreach (Arsse::$db->folderList($user, $cat, false) as $c) {
|
foreach (Arsse::$db->folderList($user, $cat, false) as $c) {
|
||||||
// get the number of unread for the category and its descendents; those with zero unread are excluded in "unread-only" mode
|
// get the number of unread for the category and its descendents; those with zero unread are excluded in "unread-only" mode
|
||||||
$count = Arsse::$db->articleCount($user, (new Context)->unread(true)->folder($c['id']));
|
$count = Arsse::$db->articleCount($user, (new Context)->unread(true)->folder((int) $c['id']));
|
||||||
if (!$unread || $count) {
|
if (!$unread || $count) {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'id' => $c['id'],
|
'id' => (int) $c['id'],
|
||||||
'title' => $c['name'],
|
'title' => $c['name'],
|
||||||
'unread' => $count,
|
'unread' => (int) $count,
|
||||||
'is_cat' => true,
|
'is_cat' => true,
|
||||||
'order_id' => ++$order,
|
'order_id' => ++$order,
|
||||||
];
|
];
|
||||||
|
@ -764,9 +764,9 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
// otherwise, append the subscription
|
// otherwise, append the subscription
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'id' => $s['id'],
|
'id' => (int) $s['id'],
|
||||||
'title' => $s['title'],
|
'title' => $s['title'],
|
||||||
'unread' => $s['unread'],
|
'unread' => (int) $s['unread'],
|
||||||
'cat_id' => (int) $s['folder'],
|
'cat_id' => (int) $s['folder'],
|
||||||
'feed_url' => $s['url'],
|
'feed_url' => $s['url'],
|
||||||
'has_icon' => (bool) $s['favicon'],
|
'has_icon' => (bool) $s['favicon'],
|
||||||
|
@ -920,8 +920,8 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return (abs($id) - self::LABEL_OFFSET);
|
return (abs($id) - self::LABEL_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function labelOut(int $id): int {
|
protected function labelOut($id): int {
|
||||||
return ($id * -1 - self::LABEL_OFFSET);
|
return ((int) $id * -1 - self::LABEL_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function opGetLabels(array $data): array {
|
public function opGetLabels(array $data): array {
|
||||||
|
@ -1194,12 +1194,12 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function articleLabelList(array $labels, int $id): array {
|
protected function articleLabelList(array $labels, $id): array {
|
||||||
$out = [];
|
$out = [];
|
||||||
if (!$labels) {
|
if (!$labels) {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
foreach (Arsse::$db->articleLabelsGet(Arsse::$user->id, $id) as $label) {
|
foreach (Arsse::$db->articleLabelsGet(Arsse::$user->id, (int) $id) as $label) {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
$this->labelOut($label), // ID
|
$this->labelOut($label), // ID
|
||||||
$labels[$label], // name
|
$labels[$label], // name
|
||||||
|
@ -1224,7 +1224,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out = [];
|
$out = [];
|
||||||
try {
|
try {
|
||||||
foreach ($this->fetchArticles($data, Database::LIST_MINIMAL) as $row) {
|
foreach ($this->fetchArticles($data, Database::LIST_MINIMAL) as $row) {
|
||||||
$out[] = ['id' => $row['id']];
|
$out[] = ['id' => (int) $row['id']];
|
||||||
}
|
}
|
||||||
} catch (ExceptionInput $e) {
|
} catch (ExceptionInput $e) {
|
||||||
// ignore database errors (feeds/categories that don't exist)
|
// ignore database errors (feeds/categories that don't exist)
|
||||||
|
@ -1246,7 +1246,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
try {
|
try {
|
||||||
foreach ($this->fetchArticles($data, Database::LIST_FULL) as $article) {
|
foreach ($this->fetchArticles($data, Database::LIST_FULL) as $article) {
|
||||||
$row = [
|
$row = [
|
||||||
'id' => $article['id'],
|
'id' => (int) $article['id'],
|
||||||
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : "",
|
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : "",
|
||||||
'title' => $article['title'],
|
'title' => $article['title'],
|
||||||
'link' => $article['url'],
|
'link' => $article['url'],
|
||||||
|
@ -1313,9 +1313,9 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// wrap the output with (but after) the header
|
// wrap the output with (but after) the header
|
||||||
$out = [
|
$out = [
|
||||||
[
|
[
|
||||||
'id' => $data['feed_id'],
|
'id' => (int) $data['feed_id'],
|
||||||
'is_cat' => $data['is_cat'] ?? false,
|
'is_cat' => $data['is_cat'] ?? false,
|
||||||
'first_id' => $firstID,
|
'first_id' => (int) $firstID,
|
||||||
],
|
],
|
||||||
$out,
|
$out,
|
||||||
];
|
];
|
||||||
|
|
|
@ -16,6 +16,7 @@ return [
|
||||||
'API.TTRSS.FeedCount' => '{0, select, 1 {(1 feed)} other {({0} feeds)}}',
|
'API.TTRSS.FeedCount' => '{0, select, 1 {(1 feed)} other {({0} feeds)}}',
|
||||||
|
|
||||||
'Driver.Db.SQLite3.Name' => 'SQLite 3',
|
'Driver.Db.SQLite3.Name' => 'SQLite 3',
|
||||||
|
'Driver.Db.SQLite3PDO.Name' => 'SQLite 3 (PDO)',
|
||||||
'Driver.Service.Curl.Name' => 'HTTP (curl)',
|
'Driver.Service.Curl.Name' => 'HTTP (curl)',
|
||||||
'Driver.Service.Internal.Name' => 'Internal',
|
'Driver.Service.Internal.Name' => 'Internal',
|
||||||
'Driver.User.Internal.Name' => 'Internal',
|
'Driver.User.Internal.Name' => 'Internal',
|
||||||
|
|
|
@ -45,4 +45,4 @@ drop table arsse_marks_old;
|
||||||
|
|
||||||
-- set version marker
|
-- set version marker
|
||||||
pragma user_version = 2;
|
pragma user_version = 2;
|
||||||
update arsse_meta set value = '2' where key is 'schema_version';
|
update arsse_meta set value = '2' where key = 'schema_version';
|
|
@ -108,4 +108,4 @@ drop table arsse_labels_old;
|
||||||
|
|
||||||
-- set version marker
|
-- set version marker
|
||||||
pragma user_version = 3;
|
pragma user_version = 3;
|
||||||
update arsse_meta set value = '3' where key is 'schema_version';
|
update arsse_meta set value = '3' where key = 'schema_version';
|
|
@ -21,7 +21,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $ch;
|
protected $ch;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
if (!extension_loaded("sqlite3")) {
|
if (!Driver::requirementsMet()) {
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
|
@ -109,7 +109,6 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
// set up configuration
|
// set up configuration
|
||||||
Arsse::$conf = new Conf();
|
Arsse::$conf = new Conf();
|
||||||
Arsse::$conf->dbSQLite3File = ":memory:";
|
Arsse::$conf->dbSQLite3File = ":memory:";
|
||||||
// set up database shim
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown() {
|
public function tearDown() {
|
||||||
|
|
|
@ -10,8 +10,8 @@ use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Conf;
|
use JKingWeb\Arsse\Conf;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\Db\SQLite3\Driver;
|
use JKingWeb\Arsse\Db\SQLite3\Driver;
|
||||||
use JKingWeb\Arsse\Db\SQLite3\Result;
|
use JKingWeb\Arsse\Db\Result;
|
||||||
use JKingWeb\Arsse\Db\SQLite3\Statement;
|
use JKingWeb\Arsse\Db\Statement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \JKingWeb\Arsse\Db\SQLite3\Driver<extended>
|
* @covers \JKingWeb\Arsse\Db\SQLite3\Driver<extended>
|
||||||
|
@ -22,7 +22,7 @@ class TestDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $ch;
|
protected $ch;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
if (!extension_loaded("sqlite3")) {
|
if (!Driver::requirementsMet()) {
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
|
|
|
@ -13,9 +13,10 @@ class TestResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $c;
|
protected $c;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
if (!extension_loaded("sqlite3")) {
|
if (!\JKingWeb\Arsse\Db\SQLite3\Driver::requirementsMet()) {
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
|
$this->clearData();
|
||||||
$c = new \SQLite3(":memory:");
|
$c = new \SQLite3(":memory:");
|
||||||
$c->enableExceptions(true);
|
$c->enableExceptions(true);
|
||||||
$this->c = $c;
|
$this->c = $c;
|
||||||
|
@ -24,6 +25,7 @@ class TestResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function tearDown() {
|
public function tearDown() {
|
||||||
$this->c->close();
|
$this->c->close();
|
||||||
unset($this->c);
|
unset($this->c);
|
||||||
|
$this->clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConstructResult() {
|
public function testConstructResult() {
|
||||||
|
|
|
@ -12,16 +12,14 @@ use JKingWeb\Arsse\Db\Statement;
|
||||||
* @covers \JKingWeb\Arsse\Db\SQLite3\Statement<extended>
|
* @covers \JKingWeb\Arsse\Db\SQLite3\Statement<extended>
|
||||||
* @covers \JKingWeb\Arsse\Db\SQLite3\ExceptionBuilder */
|
* @covers \JKingWeb\Arsse\Db\SQLite3\ExceptionBuilder */
|
||||||
class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
use \JKingWeb\Arsse\Test\Db\BindingTests;
|
|
||||||
|
|
||||||
protected $c;
|
protected $c;
|
||||||
protected static $imp = \JKingWeb\Arsse\Db\SQLite3\Statement::class;
|
protected static $imp = \JKingWeb\Arsse\Db\SQLite3\Statement::class;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
$this->clearData();
|
if (!\JKingWeb\Arsse\Db\SQLite3\Driver::requirementsMet()) {
|
||||||
if (!extension_loaded("sqlite3")) {
|
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
|
$this->clearData();
|
||||||
$c = new \SQLite3(":memory:");
|
$c = new \SQLite3(":memory:");
|
||||||
$c->enableExceptions(true);
|
$c->enableExceptions(true);
|
||||||
$this->c = $c;
|
$this->c = $c;
|
||||||
|
@ -37,15 +35,222 @@ class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$s = new self::$imp($this->c, $nativeStatement);
|
$s = new self::$imp($this->c, $nativeStatement);
|
||||||
$types = array_unique(Statement::TYPES);
|
$types = array_unique(Statement::TYPES);
|
||||||
foreach ($types as $type) {
|
foreach ($types as $type) {
|
||||||
$s->rebindArray([$strict ? "strict $type" : $type]);
|
$s->retypeArray([$strict ? "strict $type" : $type]);
|
||||||
$val = $s->runArray([$input])->getRow()['value'];
|
$val = $s->runArray([$input])->getRow()['value'];
|
||||||
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
||||||
$s->rebind(...[$strict ? "strict $type" : $type]);
|
$s->retype(...[$strict ? "strict $type" : $type]);
|
||||||
$val = $s->run(...[$input])->getRow()['value'];
|
$val = $s->run(...[$input])->getRow()['value'];
|
||||||
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideBindings */
|
||||||
|
public function testBindATypedValue($value, $type, $exp) {
|
||||||
|
$typeStr = "'".str_replace("'", "''", $type)."'";
|
||||||
|
$nativeStatement = $this->c->prepare(
|
||||||
|
"SELECT (
|
||||||
|
(CASE WHEN substr($typeStr, 0, 7) <> 'strict ' then null else 1 end) is null
|
||||||
|
and ? is null
|
||||||
|
) or (
|
||||||
|
$exp = ?
|
||||||
|
) as pass"
|
||||||
|
);
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement);
|
||||||
|
$s->retypeArray([$type, $type]);
|
||||||
|
$act = (bool) $s->run(...[$value, $value])->getRow()['pass'];
|
||||||
|
$this->assertTrue($act);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideBindings() {
|
||||||
|
$dateMutable = new \DateTime("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
|
return [
|
||||||
|
/* input, type, expected binding as SQL fragment */
|
||||||
|
[null, "integer", "null"],
|
||||||
|
[null, "float", "null"],
|
||||||
|
[null, "string", "null"],
|
||||||
|
[null, "binary", "null"],
|
||||||
|
[null, "datetime", "null"],
|
||||||
|
[null, "boolean", "null"],
|
||||||
|
[null, "strict integer", "0"],
|
||||||
|
[null, "strict float", "0.0"],
|
||||||
|
[null, "strict string", "''"],
|
||||||
|
[null, "strict binary", "x''"],
|
||||||
|
[null, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[null, "strict boolean", "0"],
|
||||||
|
// true
|
||||||
|
[true, "integer", "1"],
|
||||||
|
[true, "float", "1.0"],
|
||||||
|
[true, "string", "'1'"],
|
||||||
|
[true, "binary", "x'31'"],
|
||||||
|
[true, "datetime", "null"],
|
||||||
|
[true, "boolean", "1"],
|
||||||
|
[true, "strict integer", "1"],
|
||||||
|
[true, "strict float", "1.0"],
|
||||||
|
[true, "strict string", "'1'"],
|
||||||
|
[true, "strict binary", "x'31'"],
|
||||||
|
[true, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[true, "strict boolean", "1"],
|
||||||
|
// false
|
||||||
|
[false, "integer", "0"],
|
||||||
|
[false, "float", "0.0"],
|
||||||
|
[false, "string", "''"],
|
||||||
|
[false, "binary", "x''"],
|
||||||
|
[false, "datetime", "null"],
|
||||||
|
[false, "boolean", "0"],
|
||||||
|
[false, "strict integer", "0"],
|
||||||
|
[false, "strict float", "0.0"],
|
||||||
|
[false, "strict string", "''"],
|
||||||
|
[false, "strict binary", "x''"],
|
||||||
|
[false, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[false, "strict boolean", "0"],
|
||||||
|
// integer
|
||||||
|
[2112, "integer", "2112"],
|
||||||
|
[2112, "float", "2112.0"],
|
||||||
|
[2112, "string", "'2112'"],
|
||||||
|
[2112, "binary", "x'32313132'"],
|
||||||
|
[2112, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112, "boolean", "1"],
|
||||||
|
[2112, "strict integer", "2112"],
|
||||||
|
[2112, "strict float", "2112.0"],
|
||||||
|
[2112, "strict string", "'2112'"],
|
||||||
|
[2112, "strict binary", "x'32313132'"],
|
||||||
|
[2112, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112, "strict boolean", "1"],
|
||||||
|
// integer zero
|
||||||
|
[0, "integer", "0"],
|
||||||
|
[0, "float", "0.0"],
|
||||||
|
[0, "string", "'0'"],
|
||||||
|
[0, "binary", "x'30'"],
|
||||||
|
[0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0, "boolean", "0"],
|
||||||
|
[0, "strict integer", "0"],
|
||||||
|
[0, "strict float", "0.0"],
|
||||||
|
[0, "strict string", "'0'"],
|
||||||
|
[0, "strict binary", "x'30'"],
|
||||||
|
[0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0, "strict boolean", "0"],
|
||||||
|
// float
|
||||||
|
[2112.99, "integer", "2112"],
|
||||||
|
[2112.99, "float", "2112.99"],
|
||||||
|
[2112.99, "string", "'2112.99'"],
|
||||||
|
[2112.99, "binary", "x'323131322e3939'"],
|
||||||
|
[2112.99, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112.99, "boolean", "1"],
|
||||||
|
[2112.99, "strict integer", "2112"],
|
||||||
|
[2112.99, "strict float", "2112.99"],
|
||||||
|
[2112.99, "strict string", "'2112.99'"],
|
||||||
|
[2112.99, "strict binary", "x'323131322e3939'"],
|
||||||
|
[2112.99, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112.99, "strict boolean", "1"],
|
||||||
|
// float zero
|
||||||
|
[0.0, "integer", "0"],
|
||||||
|
[0.0, "float", "0.0"],
|
||||||
|
[0.0, "string", "'0'"],
|
||||||
|
[0.0, "binary", "x'30'"],
|
||||||
|
[0.0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0.0, "boolean", "0"],
|
||||||
|
[0.0, "strict integer", "0"],
|
||||||
|
[0.0, "strict float", "0.0"],
|
||||||
|
[0.0, "strict string", "'0'"],
|
||||||
|
[0.0, "strict binary", "x'30'"],
|
||||||
|
[0.0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0.0, "strict boolean", "0"],
|
||||||
|
// ASCII string
|
||||||
|
["Random string", "integer", "0"],
|
||||||
|
["Random string", "float", "0.0"],
|
||||||
|
["Random string", "string", "'Random string'"],
|
||||||
|
["Random string", "binary", "x'52616e646f6d20737472696e67'"],
|
||||||
|
["Random string", "datetime", "null"],
|
||||||
|
["Random string", "boolean", "1"],
|
||||||
|
["Random string", "strict integer", "0"],
|
||||||
|
["Random string", "strict float", "0.0"],
|
||||||
|
["Random string", "strict string", "'Random string'"],
|
||||||
|
["Random string", "strict binary", "x'52616e646f6d20737472696e67'"],
|
||||||
|
["Random string", "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
["Random string", "strict boolean", "1"],
|
||||||
|
// UTF-8 string
|
||||||
|
["é", "integer", "0"],
|
||||||
|
["é", "float", "0.0"],
|
||||||
|
["é", "string", "char(233)"],
|
||||||
|
["é", "binary", "x'c3a9'"],
|
||||||
|
["é", "datetime", "null"],
|
||||||
|
["é", "boolean", "1"],
|
||||||
|
["é", "strict integer", "0"],
|
||||||
|
["é", "strict float", "0.0"],
|
||||||
|
["é", "strict string", "char(233)"],
|
||||||
|
["é", "strict binary", "x'c3a9'"],
|
||||||
|
["é", "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
["é", "strict boolean", "1"],
|
||||||
|
// binary string
|
||||||
|
[chr(233).chr(233), "integer", "0"],
|
||||||
|
[chr(233).chr(233), "float", "0.0"],
|
||||||
|
[chr(233).chr(233), "string", "'".chr(233).chr(233)."'"],
|
||||||
|
[chr(233).chr(233), "binary", "x'e9e9'"],
|
||||||
|
[chr(233).chr(233), "datetime", "null"],
|
||||||
|
[chr(233).chr(233), "boolean", "1"],
|
||||||
|
[chr(233).chr(233), "strict integer", "0"],
|
||||||
|
[chr(233).chr(233), "strict float", "0.0"],
|
||||||
|
[chr(233).chr(233), "strict string", "'".chr(233).chr(233)."'"],
|
||||||
|
[chr(233).chr(233), "strict binary", "x'e9e9'"],
|
||||||
|
[chr(233).chr(233), "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[chr(233).chr(233), "strict boolean", "1"],
|
||||||
|
// ISO 8601 date string
|
||||||
|
["2017-01-09T13:11:17", "integer", "0"],
|
||||||
|
["2017-01-09T13:11:17", "float", "0.0"],
|
||||||
|
["2017-01-09T13:11:17", "string", "'2017-01-09T13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
|
["2017-01-09T13:11:17", "datetime", "'2017-01-09 13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "boolean", "1"],
|
||||||
|
["2017-01-09T13:11:17", "strict integer", "0"],
|
||||||
|
["2017-01-09T13:11:17", "strict float", "0.0"],
|
||||||
|
["2017-01-09T13:11:17", "strict string", "'2017-01-09T13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "strict binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
|
["2017-01-09T13:11:17", "strict datetime", "'2017-01-09 13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "strict boolean", "1"],
|
||||||
|
// arbitrary date string
|
||||||
|
["Today", "integer", "0"],
|
||||||
|
["Today", "float", "0.0"],
|
||||||
|
["Today", "string", "'Today'"],
|
||||||
|
["Today", "binary", "x'546f646179'"],
|
||||||
|
["Today", "datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
|
["Today", "boolean", "1"],
|
||||||
|
["Today", "strict integer", "0"],
|
||||||
|
["Today", "strict float", "0.0"],
|
||||||
|
["Today", "strict string", "'Today'"],
|
||||||
|
["Today", "strict binary", "x'546f646179'"],
|
||||||
|
["Today", "strict datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
|
["Today", "strict boolean", "1"],
|
||||||
|
// mutable date object
|
||||||
|
[$dateMutable, "integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateMutable, "float", $dateUTC->getTimestamp().".0"],
|
||||||
|
[$dateMutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateMutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "boolean", "1"],
|
||||||
|
[$dateMutable, "strict integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateMutable, "strict float", $dateUTC->getTimestamp().".0"],
|
||||||
|
[$dateMutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateMutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "strict boolean", "1"],
|
||||||
|
// immutable date object
|
||||||
|
[$dateImmutable, "integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateImmutable, "float", $dateUTC->getTimestamp().".0"],
|
||||||
|
[$dateImmutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateImmutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "boolean", "1"],
|
||||||
|
[$dateImmutable, "strict integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateImmutable, "strict float", $dateUTC->getTimestamp().".0"],
|
||||||
|
[$dateImmutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateImmutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "strict boolean", "1"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function testConstructStatement() {
|
public function testConstructStatement() {
|
||||||
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
||||||
$this->assertInstanceOf(Statement::class, new \JKingWeb\Arsse\Db\SQLite3\Statement($this->c, $nativeStatement));
|
$this->assertInstanceOf(Statement::class, new \JKingWeb\Arsse\Db\SQLite3\Statement($this->c, $nativeStatement));
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TestUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
const MINIMAL2 = "pragma user_version=2";
|
const MINIMAL2 = "pragma user_version=2";
|
||||||
|
|
||||||
public function setUp(Conf $conf = null) {
|
public function setUp(Conf $conf = null) {
|
||||||
if (!extension_loaded("sqlite3")) {
|
if (!Driver::requirementsMet()) {
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
|
@ -73,6 +73,12 @@ class TestUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLoadEmptyFile() {
|
||||||
|
file_put_contents($this->path."0.sql", "");
|
||||||
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
public function testLoadCorrectFile() {
|
public function testLoadCorrectFile() {
|
||||||
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
@ -81,7 +87,7 @@ class TestUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
public function testPerformPartialUpdate() {
|
public function testPerformPartialUpdate() {
|
||||||
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
||||||
file_put_contents($this->path."1.sql", "");
|
file_put_contents($this->path."1.sql", " ");
|
||||||
$this->assertException("updateFileIncomplete", "Db");
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
try {
|
try {
|
||||||
$this->drv->schemaUpdate(2, $this->base);
|
$this->drv->schemaUpdate(2, $this->base);
|
||||||
|
|
14
tests/cases/Db/SQLite3PDO/Database/TestArticle.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestArticle.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestArticle extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesArticle;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestCleanup.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestCleanup.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestCleanup extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesCleanup;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestFeed.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestFeed.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesFeed;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestFolder.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestFolder.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestFolder extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesFolder;
|
||||||
|
}
|
10
tests/cases/Db/SQLite3PDO/Database/TestLabel.php
Normal file
10
tests/cases/Db/SQLite3PDO/Database/TestLabel.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestLabel extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesLabel;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestMeta.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestMeta.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestMeta extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesMeta;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestMiscellany.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestMiscellany.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestMiscellany extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesMiscellany;
|
||||||
|
}
|
10
tests/cases/Db/SQLite3PDO/Database/TestSession.php
Normal file
10
tests/cases/Db/SQLite3PDO/Database/TestSession.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestSession extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesSession;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestSubscription.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestSubscription.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestSubscription extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesSubscription;
|
||||||
|
}
|
14
tests/cases/Db/SQLite3PDO/Database/TestUser.php
Normal file
14
tests/cases/Db/SQLite3PDO/Database/TestUser.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */
|
||||||
|
class TestUser extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
use \JKingWeb\Arsse\Test\Database\Setup;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO;
|
||||||
|
use \JKingWeb\Arsse\Test\Database\SeriesUser;
|
||||||
|
}
|
196
tests/cases/Db/SQLite3PDO/TestCreation.php
Normal file
196
tests/cases/Db/SQLite3PDO/TestCreation.php
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Conf;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver as Driver;
|
||||||
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
use Phake;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended>
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDODriver
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */
|
||||||
|
class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $data;
|
||||||
|
protected $drv;
|
||||||
|
protected $ch;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
if (!Driver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
// test files
|
||||||
|
$this->files = [
|
||||||
|
// cannot create files
|
||||||
|
'Cmain' => [],
|
||||||
|
'Cshm' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
],
|
||||||
|
'Cwal' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
],
|
||||||
|
// cannot write to files
|
||||||
|
'Wmain' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Wwal' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Wshm' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
// cannot read from files
|
||||||
|
'Rmain' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Rwal' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Rshm' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
// can neither read from or write to files
|
||||||
|
'Amain' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Awal' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
'Ashm' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
// non-filesystem errors
|
||||||
|
'corrupt' => [
|
||||||
|
'arsse.db' => "",
|
||||||
|
'arsse.db-wal' => "",
|
||||||
|
'arsse.db-shm' => "",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$vfs = vfsStream::setup("dbtest", 0777, $this->files);
|
||||||
|
$this->path = $path = $vfs->url()."/";
|
||||||
|
// set up access blocks
|
||||||
|
chmod($path."Cmain", 0555);
|
||||||
|
chmod($path."Cwal", 0555);
|
||||||
|
chmod($path."Cshm", 0555);
|
||||||
|
chmod($path."Rmain/arsse.db", 0333);
|
||||||
|
chmod($path."Rwal/arsse.db-wal", 0333);
|
||||||
|
chmod($path."Rshm/arsse.db-shm", 0333);
|
||||||
|
chmod($path."Wmain/arsse.db", 0555);
|
||||||
|
chmod($path."Wwal/arsse.db-wal", 0555);
|
||||||
|
chmod($path."Wshm/arsse.db-shm", 0555);
|
||||||
|
chmod($path."Amain/arsse.db", 0111);
|
||||||
|
chmod($path."Awal/arsse.db-wal", 0111);
|
||||||
|
chmod($path."Ashm/arsse.db-shm", 0111);
|
||||||
|
// set up configuration
|
||||||
|
Arsse::$conf = new Conf();
|
||||||
|
Arsse::$conf->dbSQLite3File = ":memory:";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
$this->clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToCreateDatabase() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db";
|
||||||
|
$this->assertException("fileUncreatable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToCreateJournal() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db";
|
||||||
|
$this->assertException("fileUncreatable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToCreateSharedMmeory() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db";
|
||||||
|
$this->assertException("fileUncreatable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToReadDatabase() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db";
|
||||||
|
$this->assertException("fileUnreadable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToReadJournal() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db";
|
||||||
|
$this->assertException("fileUnreadable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToReadSharedMmeory() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db";
|
||||||
|
$this->assertException("fileUnreadable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToWriteToDatabase() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db";
|
||||||
|
$this->assertException("fileUnwritable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToWriteToJournal() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db";
|
||||||
|
$this->assertException("fileUnwritable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToWriteToSharedMmeory() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db";
|
||||||
|
$this->assertException("fileUnwritable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToAccessDatabase() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db";
|
||||||
|
$this->assertException("fileUnusable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToAccessJournal() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db";
|
||||||
|
$this->assertException("fileUnusable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailToAccessSharedMmeory() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db";
|
||||||
|
$this->assertException("fileUnusable", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAssumeDatabaseCorruption() {
|
||||||
|
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db";
|
||||||
|
$this->assertException("fileCorrupt", "Db");
|
||||||
|
new Driver;
|
||||||
|
}
|
||||||
|
}
|
344
tests/cases/Db/SQLite3PDO/TestDriver.php
Normal file
344
tests/cases/Db/SQLite3PDO/TestDriver.php
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Conf;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver;
|
||||||
|
use JKingWeb\Arsse\Db\Result;
|
||||||
|
use JKingWeb\Arsse\Db\Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended>
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDODriver
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */
|
||||||
|
class TestDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $data;
|
||||||
|
protected $drv;
|
||||||
|
protected $ch;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
if (!PDODriver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
$conf = new Conf();
|
||||||
|
Arsse::$conf = $conf;
|
||||||
|
$conf->dbDriver = PDODriver::class;
|
||||||
|
$conf->dbSQLite3Timeout = 0;
|
||||||
|
$conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook');
|
||||||
|
$this->drv = new PDODriver();
|
||||||
|
$this->ch = new \PDO("sqlite:".Arsse::$conf->dbSQLite3File, "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
unset($this->drv);
|
||||||
|
unset($this->ch);
|
||||||
|
if (isset(Arsse::$conf)) {
|
||||||
|
unlink(Arsse::$conf->dbSQLite3File);
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchDriverName() {
|
||||||
|
$class = Arsse::$conf->dbDriver;
|
||||||
|
$this->assertTrue(strlen($class::driverName()) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCharacterSetAcceptability() {
|
||||||
|
$this->assertTrue($this->drv->charsetAcceptable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecAValidStatement() {
|
||||||
|
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecAnInvalidStatement() {
|
||||||
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
|
$this->drv->exec("And the meek shall inherit the earth...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecMultipleStatements() {
|
||||||
|
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)"));
|
||||||
|
$this->assertEquals(2112, $this->ch->query("SELECT id from test")->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecTimeout() {
|
||||||
|
$this->ch->exec("BEGIN EXCLUSIVE TRANSACTION");
|
||||||
|
$this->assertException("general", "Db", "ExceptionTimeout");
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecConstraintViolation() {
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer not null)");
|
||||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
|
$this->drv->exec("INSERT INTO test(id) values(null)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecTypeViolation() {
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
|
$this->drv->exec("INSERT INTO test(id) values('ook')");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMakeAValidQuery() {
|
||||||
|
$this->assertInstanceOf(Result::class, $this->drv->query("SELECT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMakeAnInvalidQuery() {
|
||||||
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
|
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryTimeout() {
|
||||||
|
$this->ch->exec("BEGIN EXCLUSIVE TRANSACTION");
|
||||||
|
$this->assertException("general", "Db", "ExceptionTimeout");
|
||||||
|
$this->drv->query("CREATE TABLE test(id integer primary key)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryConstraintViolation() {
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer not null)");
|
||||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
|
$this->drv->query("INSERT INTO test(id) values(null)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryTypeViolation() {
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
|
$this->drv->query("INSERT INTO test(id) values('ook')");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrepareAValidQuery() {
|
||||||
|
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
|
||||||
|
$this->assertInstanceOf(Statement::class, $s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrepareAnInvalidQuery() {
|
||||||
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
|
$s = $this->drv->prepare("This is an invalid query", "int", "int");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateASavepoint() {
|
||||||
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReleaseASavepoint() {
|
||||||
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(true, $this->drv->savepointRelease());
|
||||||
|
$this->assertException("savepointInvalid", "Db");
|
||||||
|
$this->drv->savepointRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUndoASavepoint() {
|
||||||
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(true, $this->drv->savepointUndo());
|
||||||
|
$this->assertException("savepointInvalid", "Db");
|
||||||
|
$this->drv->savepointUndo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManipulateSavepoints() {
|
||||||
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(4, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(5, $this->drv->savepointCreate());
|
||||||
|
$this->assertTrue($this->drv->savepointUndo(3));
|
||||||
|
$this->assertFalse($this->drv->savepointRelease(4));
|
||||||
|
$this->assertEquals(6, $this->drv->savepointCreate());
|
||||||
|
$this->assertFalse($this->drv->savepointRelease(5));
|
||||||
|
$this->assertTrue($this->drv->savepointRelease(6));
|
||||||
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
$this->assertTrue($this->drv->savepointRelease(2));
|
||||||
|
$this->assertException("savepointStale", "Db");
|
||||||
|
$this->drv->savepointRelease(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManipulateSavepointsSomeMore() {
|
||||||
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
$this->assertEquals(4, $this->drv->savepointCreate());
|
||||||
|
$this->assertTrue($this->drv->savepointRelease(2));
|
||||||
|
$this->assertFalse($this->drv->savepointUndo(3));
|
||||||
|
$this->assertException("savepointStale", "Db");
|
||||||
|
$this->drv->savepointUndo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBeginATransaction() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCommitATransaction() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr->commit();
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(1, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRollbackATransaction() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr->rollback();
|
||||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBeginChainedTransactions() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCommitChainedTransactions() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2->commit();
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr1->commit();
|
||||||
|
$this->assertEquals(2, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCommitChainedTransactionsOutOfOrder() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr1->commit();
|
||||||
|
$this->assertEquals(2, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRollbackChainedTransactions() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2->rollback();
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr1->rollback();
|
||||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRollbackChainedTransactionsOutOfOrder() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr1->rollback();
|
||||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2->rollback();
|
||||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPartiallyRollbackChainedTransactions() {
|
||||||
|
$select = "SELECT count(*) FROM test";
|
||||||
|
$insert = "INSERT INTO test(id) values(null)";
|
||||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$tr1 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2 = $this->drv->begin();
|
||||||
|
$this->drv->query($insert);
|
||||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr2->rollback();
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn());
|
||||||
|
$tr1->commit();
|
||||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue());
|
||||||
|
$this->assertEquals(1, $this->ch->query($select)->fetchColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchSchemaVersion() {
|
||||||
|
$this->assertSame(0, $this->drv->schemaVersion());
|
||||||
|
$this->drv->exec("PRAGMA user_version=1");
|
||||||
|
$this->assertSame(1, $this->drv->schemaVersion());
|
||||||
|
$this->drv->exec("PRAGMA user_version=2");
|
||||||
|
$this->assertSame(2, $this->drv->schemaVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLockTheDatabase() {
|
||||||
|
$this->drv->savepointCreate(true);
|
||||||
|
$this->ch->exec("PRAGMA busy_timeout = 0");
|
||||||
|
$this->assertException();
|
||||||
|
$this->ch->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlockTheDatabase() {
|
||||||
|
$this->drv->savepointCreate(true);
|
||||||
|
$this->drv->savepointRelease();
|
||||||
|
$this->drv->savepointCreate(true);
|
||||||
|
$this->drv->savepointUndo();
|
||||||
|
$this->assertSame(0, $this->ch->exec("CREATE TABLE test(id integer primary key)"));
|
||||||
|
}
|
||||||
|
}
|
108
tests/cases/Db/SQLite3PDO/TestResult.php
Normal file
108
tests/cases/Db/SQLite3PDO/TestResult.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Db\Result;
|
||||||
|
use JKingWeb\Arsse\Db\PDOResult;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\Db\PDOResult<extended> */
|
||||||
|
class TestResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $c;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
if (!PDODriver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
$c = new \PDO("sqlite::memory:", "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
||||||
|
$this->c = $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
unset($this->c);
|
||||||
|
$this->clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstructResult() {
|
||||||
|
$set = $this->c->query("SELECT 1");
|
||||||
|
$this->assertInstanceOf(Result::class, new PDOResult($set));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetChangeCountAndLastInsertId() {
|
||||||
|
$this->c->query("CREATE TABLE test(col)");
|
||||||
|
$set = $this->c->query("INSERT INTO test(col) values(1)");
|
||||||
|
$rows = $set->rowCount();
|
||||||
|
$id = $this->c->lastInsertID();
|
||||||
|
$r = new PDOResult($set, [$rows,$id]);
|
||||||
|
$this->assertSame((int) $rows, $r->changes());
|
||||||
|
$this->assertSame((int) $id, $r->lastId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIterateOverResults() {
|
||||||
|
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
|
||||||
|
$rows = [];
|
||||||
|
foreach (new PDOResult($set) as $index => $row) {
|
||||||
|
$rows[$index] = $row['col'];
|
||||||
|
}
|
||||||
|
$this->assertSame([0 => "1", 1 => "2", 2 => "3"], $rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIterateOverResultsTwice() {
|
||||||
|
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
|
||||||
|
$rows = [];
|
||||||
|
$test = new PDOResult($set);
|
||||||
|
foreach ($test as $row) {
|
||||||
|
$rows[] = $row['col'];
|
||||||
|
}
|
||||||
|
$this->assertSame(["1","2","3"], $rows);
|
||||||
|
$this->assertException("resultReused", "Db");
|
||||||
|
foreach ($test as $row) {
|
||||||
|
$rows[] = $row['col'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSingleValues() {
|
||||||
|
$set = $this->c->query("SELECT 1867 as year union select 1970 as year union select 2112 as year");
|
||||||
|
$test = new PDOResult($set);
|
||||||
|
$this->assertEquals(1867, $test->getValue());
|
||||||
|
$this->assertEquals(1970, $test->getValue());
|
||||||
|
$this->assertEquals(2112, $test->getValue());
|
||||||
|
$this->assertSame(null, $test->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetFirstValuesOnly() {
|
||||||
|
$set = $this->c->query("SELECT 1867 as year, 19 as century union select 1970 as year, 20 as century union select 2112 as year, 22 as century");
|
||||||
|
$test = new PDOResult($set);
|
||||||
|
$this->assertEquals(1867, $test->getValue());
|
||||||
|
$this->assertEquals(1970, $test->getValue());
|
||||||
|
$this->assertEquals(2112, $test->getValue());
|
||||||
|
$this->assertSame(null, $test->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRows() {
|
||||||
|
$set = $this->c->query("SELECT '2112' as album, '2112' as track union select 'Clockwork Angels' as album, 'The Wreckers' as track");
|
||||||
|
$rows = [
|
||||||
|
['album' => '2112', 'track' => '2112'],
|
||||||
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
||||||
|
];
|
||||||
|
$test = new PDOResult($set);
|
||||||
|
$this->assertEquals($rows[0], $test->getRow());
|
||||||
|
$this->assertEquals($rows[1], $test->getRow());
|
||||||
|
$this->assertSame(null, $test->getRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllRows() {
|
||||||
|
$set = $this->c->query("SELECT '2112' as album, '2112' as track union select 'Clockwork Angels' as album, 'The Wreckers' as track");
|
||||||
|
$rows = [
|
||||||
|
['album' => '2112', 'track' => '2112'],
|
||||||
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
||||||
|
];
|
||||||
|
$test = new PDOResult($set);
|
||||||
|
$this->assertEquals($rows, $test->getAll());
|
||||||
|
}
|
||||||
|
}
|
313
tests/cases/Db/SQLite3PDO/TestStatement.php
Normal file
313
tests/cases/Db/SQLite3PDO/TestStatement.php
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Db\Statement;
|
||||||
|
use JKingWeb\Arsse\Db\PDOStatement;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDOStatement<extended>
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */
|
||||||
|
class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $c;
|
||||||
|
protected static $imp = \JKingWeb\Arsse\Db\PDOStatement::class;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
if (!PDODriver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
$c = new \PDO("sqlite::memory:", "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
||||||
|
$this->c = $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
unset($this->c);
|
||||||
|
$this->clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkBinding($input, array $expectations, bool $strict = false) {
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement);
|
||||||
|
$types = array_unique(Statement::TYPES);
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$s->retypeArray([$strict ? "strict $type" : $type]);
|
||||||
|
$val = $s->runArray([$input])->getRow()['value'];
|
||||||
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
||||||
|
$s->retype(...[$strict ? "strict $type" : $type]);
|
||||||
|
$val = $s->run(...[$input])->getRow()['value'];
|
||||||
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideBindings */
|
||||||
|
public function testBindATypedValue($value, $type, $exp) {
|
||||||
|
$typeStr = "'".str_replace("'", "''", $type)."'";
|
||||||
|
$nativeStatement = $this->c->prepare(
|
||||||
|
"SELECT (
|
||||||
|
(CASE WHEN substr($typeStr, 0, 7) <> 'strict ' then null else 1 end) is null
|
||||||
|
and ? is null
|
||||||
|
) or (
|
||||||
|
$exp = ?
|
||||||
|
) as pass"
|
||||||
|
);
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement);
|
||||||
|
$s->retype(...[$type, $type]);
|
||||||
|
$act = (bool) $s->run(...[$value, $value])->getRow()['pass'];
|
||||||
|
$this->assertTrue($act);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideBindings() {
|
||||||
|
$dateMutable = new \DateTime("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
|
return [
|
||||||
|
/* input, type, expected binding as SQL fragment */
|
||||||
|
[null, "integer", "null"],
|
||||||
|
[null, "float", "null"],
|
||||||
|
[null, "string", "null"],
|
||||||
|
[null, "binary", "null"],
|
||||||
|
[null, "datetime", "null"],
|
||||||
|
[null, "boolean", "null"],
|
||||||
|
[null, "strict integer", "0"],
|
||||||
|
[null, "strict float", "'0'"],
|
||||||
|
[null, "strict string", "''"],
|
||||||
|
[null, "strict binary", "x''"],
|
||||||
|
[null, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[null, "strict boolean", "0"],
|
||||||
|
// true
|
||||||
|
[true, "integer", "1"],
|
||||||
|
[true, "float", "'1'"],
|
||||||
|
[true, "string", "'1'"],
|
||||||
|
[true, "binary", "x'31'"],
|
||||||
|
[true, "datetime", "null"],
|
||||||
|
[true, "boolean", "1"],
|
||||||
|
[true, "strict integer", "1"],
|
||||||
|
[true, "strict float", "'1'"],
|
||||||
|
[true, "strict string", "'1'"],
|
||||||
|
[true, "strict binary", "x'31'"],
|
||||||
|
[true, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[true, "strict boolean", "1"],
|
||||||
|
// false
|
||||||
|
[false, "integer", "0"],
|
||||||
|
[false, "float", "'0'"],
|
||||||
|
[false, "string", "''"],
|
||||||
|
[false, "binary", "x''"],
|
||||||
|
[false, "datetime", "null"],
|
||||||
|
[false, "boolean", "0"],
|
||||||
|
[false, "strict integer", "0"],
|
||||||
|
[false, "strict float", "'0'"],
|
||||||
|
[false, "strict string", "''"],
|
||||||
|
[false, "strict binary", "x''"],
|
||||||
|
[false, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[false, "strict boolean", "0"],
|
||||||
|
// integer
|
||||||
|
[2112, "integer", "2112"],
|
||||||
|
[2112, "float", "'2112'"],
|
||||||
|
[2112, "string", "'2112'"],
|
||||||
|
[2112, "binary", "x'32313132'"],
|
||||||
|
[2112, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112, "boolean", "1"],
|
||||||
|
[2112, "strict integer", "2112"],
|
||||||
|
[2112, "strict float", "'2112'"],
|
||||||
|
[2112, "strict string", "'2112'"],
|
||||||
|
[2112, "strict binary", "x'32313132'"],
|
||||||
|
[2112, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112, "strict boolean", "1"],
|
||||||
|
// integer zero
|
||||||
|
[0, "integer", "0"],
|
||||||
|
[0, "float", "'0'"],
|
||||||
|
[0, "string", "'0'"],
|
||||||
|
[0, "binary", "x'30'"],
|
||||||
|
[0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0, "boolean", "0"],
|
||||||
|
[0, "strict integer", "0"],
|
||||||
|
[0, "strict float", "'0'"],
|
||||||
|
[0, "strict string", "'0'"],
|
||||||
|
[0, "strict binary", "x'30'"],
|
||||||
|
[0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0, "strict boolean", "0"],
|
||||||
|
// float
|
||||||
|
[2112.5, "integer", "2112"],
|
||||||
|
[2112.5, "float", "'2112.5'"],
|
||||||
|
[2112.5, "string", "'2112.5'"],
|
||||||
|
[2112.5, "binary", "x'323131322e35'"],
|
||||||
|
[2112.5, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112.5, "boolean", "1"],
|
||||||
|
[2112.5, "strict integer", "2112"],
|
||||||
|
[2112.5, "strict float", "'2112.5'"],
|
||||||
|
[2112.5, "strict string", "'2112.5'"],
|
||||||
|
[2112.5, "strict binary", "x'323131322e35'"],
|
||||||
|
[2112.5, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
|
[2112.5, "strict boolean", "1"],
|
||||||
|
// float zero
|
||||||
|
[0.0, "integer", "0"],
|
||||||
|
[0.0, "float", "'0'"],
|
||||||
|
[0.0, "string", "'0'"],
|
||||||
|
[0.0, "binary", "x'30'"],
|
||||||
|
[0.0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0.0, "boolean", "0"],
|
||||||
|
[0.0, "strict integer", "0"],
|
||||||
|
[0.0, "strict float", "'0'"],
|
||||||
|
[0.0, "strict string", "'0'"],
|
||||||
|
[0.0, "strict binary", "x'30'"],
|
||||||
|
[0.0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[0.0, "strict boolean", "0"],
|
||||||
|
// ASCII string
|
||||||
|
["Random string", "integer", "0"],
|
||||||
|
["Random string", "float", "'0'"],
|
||||||
|
["Random string", "string", "'Random string'"],
|
||||||
|
["Random string", "binary", "x'52616e646f6d20737472696e67'"],
|
||||||
|
["Random string", "datetime", "null"],
|
||||||
|
["Random string", "boolean", "1"],
|
||||||
|
["Random string", "strict integer", "0"],
|
||||||
|
["Random string", "strict float", "'0'"],
|
||||||
|
["Random string", "strict string", "'Random string'"],
|
||||||
|
["Random string", "strict binary", "x'52616e646f6d20737472696e67'"],
|
||||||
|
["Random string", "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
["Random string", "strict boolean", "1"],
|
||||||
|
// UTF-8 string
|
||||||
|
["é", "integer", "0"],
|
||||||
|
["é", "float", "'0'"],
|
||||||
|
["é", "string", "char(233)"],
|
||||||
|
["é", "binary", "x'c3a9'"],
|
||||||
|
["é", "datetime", "null"],
|
||||||
|
["é", "boolean", "1"],
|
||||||
|
["é", "strict integer", "0"],
|
||||||
|
["é", "strict float", "'0'"],
|
||||||
|
["é", "strict string", "char(233)"],
|
||||||
|
["é", "strict binary", "x'c3a9'"],
|
||||||
|
["é", "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
["é", "strict boolean", "1"],
|
||||||
|
// binary string
|
||||||
|
[chr(233).chr(233), "integer", "0"],
|
||||||
|
[chr(233).chr(233), "float", "'0'"],
|
||||||
|
[chr(233).chr(233), "string", "'".chr(233).chr(233)."'"],
|
||||||
|
[chr(233).chr(233), "binary", "x'e9e9'"],
|
||||||
|
[chr(233).chr(233), "datetime", "null"],
|
||||||
|
[chr(233).chr(233), "boolean", "1"],
|
||||||
|
[chr(233).chr(233), "strict integer", "0"],
|
||||||
|
[chr(233).chr(233), "strict float", "'0'"],
|
||||||
|
[chr(233).chr(233), "strict string", "'".chr(233).chr(233)."'"],
|
||||||
|
[chr(233).chr(233), "strict binary", "x'e9e9'"],
|
||||||
|
[chr(233).chr(233), "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
|
[chr(233).chr(233), "strict boolean", "1"],
|
||||||
|
// ISO 8601 date string
|
||||||
|
["2017-01-09T13:11:17", "integer", "0"],
|
||||||
|
["2017-01-09T13:11:17", "float", "'0'"],
|
||||||
|
["2017-01-09T13:11:17", "string", "'2017-01-09T13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
|
["2017-01-09T13:11:17", "datetime", "'2017-01-09 13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "boolean", "1"],
|
||||||
|
["2017-01-09T13:11:17", "strict integer", "0"],
|
||||||
|
["2017-01-09T13:11:17", "strict float", "'0'"],
|
||||||
|
["2017-01-09T13:11:17", "strict string", "'2017-01-09T13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "strict binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
|
["2017-01-09T13:11:17", "strict datetime", "'2017-01-09 13:11:17'"],
|
||||||
|
["2017-01-09T13:11:17", "strict boolean", "1"],
|
||||||
|
// arbitrary date string
|
||||||
|
["Today", "integer", "0"],
|
||||||
|
["Today", "float", "'0'"],
|
||||||
|
["Today", "string", "'Today'"],
|
||||||
|
["Today", "binary", "x'546f646179'"],
|
||||||
|
["Today", "datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
|
["Today", "boolean", "1"],
|
||||||
|
["Today", "strict integer", "0"],
|
||||||
|
["Today", "strict float", "'0'"],
|
||||||
|
["Today", "strict string", "'Today'"],
|
||||||
|
["Today", "strict binary", "x'546f646179'"],
|
||||||
|
["Today", "strict datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
|
["Today", "strict boolean", "1"],
|
||||||
|
// mutable date object
|
||||||
|
[$dateMutable, "integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateMutable, "float", "'".$dateUTC->getTimestamp()."'"],
|
||||||
|
[$dateMutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateMutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "boolean", "1"],
|
||||||
|
[$dateMutable, "strict integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateMutable, "strict float", "'".$dateUTC->getTimestamp()."'"],
|
||||||
|
[$dateMutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateMutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateMutable, "strict boolean", "1"],
|
||||||
|
// immutable date object
|
||||||
|
[$dateImmutable, "integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateImmutable, "float", "'".$dateUTC->getTimestamp()."'"],
|
||||||
|
[$dateImmutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateImmutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "boolean", "1"],
|
||||||
|
[$dateImmutable, "strict integer", $dateUTC->getTimestamp()],
|
||||||
|
[$dateImmutable, "strict float", "'".$dateUTC->getTimestamp()."'"],
|
||||||
|
[$dateImmutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
|
[$dateImmutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
|
[$dateImmutable, "strict boolean", "1"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstructStatement() {
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
||||||
|
$this->assertInstanceOf(Statement::class, new PDOStatement($this->c, $nativeStatement));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBindMissingValue() {
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement);
|
||||||
|
$val = $s->runArray()->getRow()['value'];
|
||||||
|
$this->assertSame(null, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBindMultipleValues() {
|
||||||
|
$exp = [
|
||||||
|
'one' => "1",
|
||||||
|
'two' => "2",
|
||||||
|
];
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int", "int"]);
|
||||||
|
$val = $s->runArray([1,2])->getRow();
|
||||||
|
$this->assertSame($exp, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBindRecursively() {
|
||||||
|
$exp = [
|
||||||
|
'one' => "1",
|
||||||
|
'two' => "2",
|
||||||
|
'three' => "3",
|
||||||
|
'four' => "4",
|
||||||
|
];
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]);
|
||||||
|
$val = $s->runArray([1, [2, 3], 4])->getRow();
|
||||||
|
$this->assertSame($exp, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBindWithoutType() {
|
||||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value");
|
||||||
|
$this->assertException("paramTypeMissing", "Db");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement, []);
|
||||||
|
$s->runArray([1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testViolateConstraint() {
|
||||||
|
$this->c->exec("CREATE TABLE test(id integer not null)");
|
||||||
|
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int"]);
|
||||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
|
$s->runArray([null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMismatchTypes() {
|
||||||
|
$this->c->exec("CREATE TABLE test(id integer primary key)");
|
||||||
|
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
|
||||||
|
$s = new self::$imp($this->c, $nativeStatement, ["str"]);
|
||||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
|
$s->runArray(['ook']);
|
||||||
|
}
|
||||||
|
}
|
125
tests/cases/Db/SQLite3PDO/TestUpdate.php
Normal file
125
tests/cases/Db/SQLite3PDO/TestUpdate.php
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Conf;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
|
use JKingWeb\Arsse\Db\Exception;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver;
|
||||||
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended>
|
||||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */
|
||||||
|
class TestUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
protected $data;
|
||||||
|
protected $drv;
|
||||||
|
protected $vfs;
|
||||||
|
protected $base;
|
||||||
|
|
||||||
|
const MINIMAL1 = "create table arsse_meta(key text primary key not null, value text); pragma user_version=1";
|
||||||
|
const MINIMAL2 = "pragma user_version=2";
|
||||||
|
|
||||||
|
public function setUp(Conf $conf = null) {
|
||||||
|
if (!PDODriver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
$this->clearData();
|
||||||
|
$this->vfs = vfsStream::setup("schemata", null, ['SQLite3' => []]);
|
||||||
|
if (!$conf) {
|
||||||
|
$conf = new Conf();
|
||||||
|
}
|
||||||
|
$conf->dbDriver = PDODriver::class;
|
||||||
|
$conf->dbSQLite3File = ":memory:";
|
||||||
|
Arsse::$conf = $conf;
|
||||||
|
$this->base = $this->vfs->url();
|
||||||
|
$this->path = $this->base."/SQLite3/";
|
||||||
|
$this->drv = new PDODriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
unset($this->drv);
|
||||||
|
unset($this->data);
|
||||||
|
unset($this->vfs);
|
||||||
|
$this->clearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadMissingFile() {
|
||||||
|
$this->assertException("updateFileMissing", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadUnreadableFile() {
|
||||||
|
touch($this->path."0.sql");
|
||||||
|
chmod($this->path."0.sql", 0000);
|
||||||
|
$this->assertException("updateFileUnreadable", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadCorruptFile() {
|
||||||
|
file_put_contents($this->path."0.sql", "This is a corrupt file");
|
||||||
|
$this->assertException("updateFileError", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadIncompleteFile() {
|
||||||
|
file_put_contents($this->path."0.sql", "create table arsse_meta(key text primary key not null, value text);");
|
||||||
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadEmptyFile() {
|
||||||
|
file_put_contents($this->path."0.sql", "");
|
||||||
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadCorrectFile() {
|
||||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
||||||
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
|
$this->assertEquals(1, $this->drv->schemaVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPerformPartialUpdate() {
|
||||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
||||||
|
file_put_contents($this->path."1.sql", " ");
|
||||||
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
|
try {
|
||||||
|
$this->drv->schemaUpdate(2, $this->base);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals(1, $this->drv->schemaVersion());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPerformSequentialUpdate() {
|
||||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1);
|
||||||
|
file_put_contents($this->path."1.sql", self::MINIMAL2);
|
||||||
|
$this->drv->schemaUpdate(2, $this->base);
|
||||||
|
$this->assertEquals(2, $this->drv->schemaVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPerformActualUpdate() {
|
||||||
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
||||||
|
$this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeclineManualUpdate() {
|
||||||
|
// turn auto-updating off
|
||||||
|
$conf = new Conf();
|
||||||
|
$conf->dbAutoUpdate = false;
|
||||||
|
$this->setUp($conf);
|
||||||
|
$this->assertException("updateManual", "Db");
|
||||||
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeclineDowngrade() {
|
||||||
|
$this->assertException("updateTooNew", "Db");
|
||||||
|
$this->drv->schemaUpdate(-1, $this->base);
|
||||||
|
}
|
||||||
|
}
|
12
tests/cases/REST/NextCloudNews/PDO/TestV1_2.php
Normal file
12
tests/cases/REST/NextCloudNews/PDO/TestV1_2.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\REST\NextCloudNews\PDO;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\REST\NextCloudNews\V1_2<extended> */
|
||||||
|
class TestV1_2 extends \JKingWeb\Arsse\TestCase\REST\NextCloudNews\TestV1_2 {
|
||||||
|
use \JKingWeb\Arsse\Test\PDOTest;
|
||||||
|
}
|
|
@ -317,6 +317,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function v($value) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
protected function assertResponse(Response $exp, Response $act, string $text = null) {
|
protected function assertResponse(Response $exp, Response $act, string $text = null) {
|
||||||
$this->assertEquals($exp, $act, $text);
|
$this->assertEquals($exp, $act, $text);
|
||||||
$this->assertSame($exp->payload, $act->payload, $text);
|
$this->assertSame($exp->payload, $act->payload, $text);
|
||||||
|
@ -404,13 +408,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function testListFolders() {
|
public function testListFolders() {
|
||||||
$list = [
|
$list = [
|
||||||
['id' => 1, 'name' => "Software", 'parent' => null],
|
['id' => 1, 'name' => "Software", 'parent' => null],
|
||||||
['id' => "12", 'name' => "Hardware", 'parent' => null],
|
['id' => 12, 'name' => "Hardware", 'parent' => null],
|
||||||
];
|
];
|
||||||
$out = [
|
$out = [
|
||||||
['id' => 1, 'name' => "Software"],
|
['id' => 1, 'name' => "Software"],
|
||||||
['id' => 12, 'name' => "Hardware"],
|
['id' => 12, 'name' => "Hardware"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($list));
|
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([]))->thenReturn(new Result($this->v($list)));
|
||||||
$exp = new Response(200, ['folders' => []]);
|
$exp = new Response(200, ['folders' => []]);
|
||||||
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders")));
|
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/folders")));
|
||||||
$exp = new Response(200, ['folders' => $out]);
|
$exp = new Response(200, ['folders' => $out]);
|
||||||
|
@ -434,8 +438,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
Phake::when(Arsse::$db)->folderAdd($this->anything(), $this->anything())->thenThrow(new \Exception);
|
Phake::when(Arsse::$db)->folderAdd($this->anything(), $this->anything())->thenThrow(new \Exception);
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $in[0])->thenReturn(1)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $in[0])->thenReturn(1)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $in[1])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $in[1])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 1)->thenReturn($db[0]);
|
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 1)->thenReturn($this->v($db[0]));
|
||||||
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2)->thenReturn($db[1]);
|
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2)->thenReturn($this->v($db[1]));
|
||||||
// set up mocks that produce errors
|
// set up mocks that produce errors
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
|
||||||
|
@ -518,8 +522,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
'starredCount' => 5,
|
'starredCount' => 5,
|
||||||
'newestItemId' => 4758915,
|
'newestItemId' => 4758915,
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([]))->thenReturn(new Result($this->feeds['db']));
|
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([]))->thenReturn(new Result($this->v($this->feeds['db'])));
|
||||||
Phake::when(Arsse::$db)->articleStarred(Arsse::$user->id)->thenReturn(['total' => 0])->thenReturn(['total' => 5]);
|
Phake::when(Arsse::$db)->articleStarred(Arsse::$user->id)->thenReturn($this->v(['total' => 0]))->thenReturn($this->v(['total' => 5]));
|
||||||
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id)->thenReturn(0)->thenReturn(4758915);
|
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id)->thenReturn(0)->thenReturn(4758915);
|
||||||
$exp = new Response(200, $exp1);
|
$exp = new Response(200, $exp1);
|
||||||
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds")));
|
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds")));
|
||||||
|
@ -544,9 +548,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.com/news.atom")->thenReturn(2112)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.com/news.atom")->thenReturn(2112)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.org/news.atom")->thenReturn(42)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "http://example.org/news.atom")->thenReturn(42)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", new \PicoFeed\Reader\SubscriptionNotFoundException));
|
Phake::when(Arsse::$db)->subscriptionAdd(Arsse::$user->id, "")->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", new \PicoFeed\Reader\SubscriptionNotFoundException));
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2112)->thenReturn($this->feeds['db'][0]);
|
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2112)->thenReturn($this->v($this->feeds['db'][0]));
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->feeds['db'][1]);
|
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->v($this->feeds['db'][1]));
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->feeds['db'][2]);
|
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->v($this->feeds['db'][2]));
|
||||||
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(2112))->thenReturn(0);
|
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(2112))->thenReturn(0);
|
||||||
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(42))->thenReturn(4758915);
|
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(42))->thenReturn(4758915);
|
||||||
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(47))->thenReturn(2112);
|
Phake::when(Arsse::$db)->editionLatest(Arsse::$user->id, (new Context)->subscription(47))->thenReturn(2112);
|
||||||
|
@ -654,7 +658,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
'userId' => "",
|
'userId' => "",
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->feedListStale->thenReturn(array_column($out, "id"));
|
Phake::when(Arsse::$db)->feedListStale->thenReturn($this->v(array_column($out, "id")));
|
||||||
$exp = new Response(200, ['feeds' => $out]);
|
$exp = new Response(200, ['feeds' => $out]);
|
||||||
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all")));
|
$this->assertResponse($exp, $this->h->dispatch(new Request("GET", "/feeds/all")));
|
||||||
// retrieving the list when not an admin fails
|
// retrieving the list when not an admin fails
|
||||||
|
@ -689,7 +693,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListArticles() {
|
public function testListArticles() {
|
||||||
$res = new Result($this->articles['db']);
|
|
||||||
$t = new \DateTime;
|
$t = new \DateTime;
|
||||||
$in = [
|
$in = [
|
||||||
['type' => 0, 'id' => 42], // type=0 => subscription/feed
|
['type' => 0, 'id' => 42], // type=0 => subscription/feed
|
||||||
|
@ -705,7 +708,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
['lastModified' => $t->getTimestamp()],
|
['lastModified' => $t->getTimestamp()],
|
||||||
['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], // offset=0 should not set the latestEdition context
|
['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], // offset=0 should not set the latestEdition context
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, $this->anything(), Database::LIST_TYPICAL)->thenReturn($res);
|
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, $this->anything(), Database::LIST_TYPICAL)->thenReturn(new Result($this->v($this->articles['db'])));
|
||||||
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("idMissing"));
|
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(42), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("idMissing"));
|
||||||
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(2112), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("idMissing"));
|
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->folder(2112), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("idMissing"));
|
||||||
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation"));
|
Phake::when(Arsse::$db)->articleList(Arsse::$user->id, (new Context)->reverse(true)->subscription(-1), Database::LIST_TYPICAL)->thenThrow(new ExceptionInput("typeViolation"));
|
||||||
|
|
13
tests/cases/REST/TinyTinyRSS/PDO/TestAPI.php
Normal file
13
tests/cases/REST/TinyTinyRSS/PDO/TestAPI.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\REST\TinyTinyRSS\PDO;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
|
||||||
|
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
|
||||||
|
class TestAPI extends \JKingWeb\Arsse\TestCase\REST\TinyTinyRSS\TestAPI {
|
||||||
|
use \JKingWeb\Arsse\Test\PDOTest;
|
||||||
|
}
|
|
@ -122,6 +122,10 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
</section>
|
</section>
|
||||||
LONG_STRING;
|
LONG_STRING;
|
||||||
|
|
||||||
|
protected function v($value) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
protected function req($data) : Response {
|
protected function req($data) : Response {
|
||||||
return $this->h->dispatch(new Request("POST", "", json_encode($data)));
|
return $this->h->dispatch(new Request("POST", "", json_encode($data)));
|
||||||
}
|
}
|
||||||
|
@ -320,8 +324,8 @@ LONG_STRING;
|
||||||
// set of various mocks for testing
|
// set of various mocks for testing
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result([$out[0], $out[2]]));
|
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, null, false)->thenReturn(new Result($this->v([$out[0], $out[2]])));
|
||||||
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, 1, false)->thenReturn(new Result([$out[1]]));
|
Phake::when(Arsse::$db)->folderList(Arsse::$user->id, 1, false)->thenReturn(new Result($this->v([$out[1]])));
|
||||||
// set up mocks that produce errors
|
// set up mocks that produce errors
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[2])->thenThrow(new ExceptionInput("idMissing")); // parent folder does not exist
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, $db[2])->thenThrow(new ExceptionInput("idMissing")); // parent folder does not exist
|
||||||
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
Phake::when(Arsse::$db)->folderAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
||||||
|
@ -522,12 +526,12 @@ LONG_STRING;
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException()));
|
Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException()));
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(...$db[8])->thenReturn(4);
|
Phake::when(Arsse::$db)->subscriptionAdd(...$db[8])->thenReturn(4);
|
||||||
Phake::when(Arsse::$db)->subscriptionAdd(...$db[9])->thenThrow(new ExceptionInput("constraintViolation"));
|
Phake::when(Arsse::$db)->subscriptionAdd(...$db[9])->thenThrow(new ExceptionInput("constraintViolation"));
|
||||||
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 42)->thenReturn(['id' => 42]);
|
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->v(['id' => 42]));
|
||||||
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 47)->thenReturn(['id' => 47]);
|
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 47)->thenReturn($this->v(['id' => 47]));
|
||||||
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2112)->thenThrow(new ExceptionInput("subjectMissing"));
|
Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 2112)->thenThrow(new ExceptionInput("subjectMissing"));
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything())->thenReturn(true);
|
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, $this->anything(), $this->anything())->thenReturn(true);
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 4, $this->anything())->thenThrow(new ExceptionInput("idMissing"));
|
Phake::when(Arsse::$db)->subscriptionPropertiesSet(Arsse::$user->id, 4, $this->anything())->thenThrow(new ExceptionInput("idMissing"));
|
||||||
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($list));
|
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v($list)));
|
||||||
for ($a = 0; $a < (sizeof($in) - 4); $a++) {
|
for ($a = 0; $a < (sizeof($in) - 4); $a++) {
|
||||||
$exp = $this->respGood($out[$a]);
|
$exp = $this->respGood($out[$a]);
|
||||||
$this->assertResponse($exp, $this->req($in[$a]), "Failed test $a");
|
$this->assertResponse($exp, $this->req($in[$a]), "Failed test $a");
|
||||||
|
@ -647,11 +651,11 @@ LONG_STRING;
|
||||||
|
|
||||||
public function testRetrieveTheGlobalUnreadCount() {
|
public function testRetrieveTheGlobalUnreadCount() {
|
||||||
$in = ['op' => "getUnread", 'sid' => "PriestsOfSyrinx"];
|
$in = ['op' => "getUnread", 'sid' => "PriestsOfSyrinx"];
|
||||||
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result([
|
Phake::when(Arsse::$db)->subscriptionList(Arsse::$user->id)->thenReturn(new Result($this->v([
|
||||||
['id' => 1, 'unread' => 2112],
|
['id' => 1, 'unread' => 2112],
|
||||||
['id' => 2, 'unread' => 42],
|
['id' => 2, 'unread' => 42],
|
||||||
['id' => 3, 'unread' => 47],
|
['id' => 3, 'unread' => 47],
|
||||||
]));
|
])));
|
||||||
$exp = $this->respGood(['unread' => (string) (2112 + 42 + 47)]);
|
$exp = $this->respGood(['unread' => (string) (2112 + 42 + 47)]);
|
||||||
$this->assertResponse($exp, $this->req($in));
|
$this->assertResponse($exp, $this->req($in));
|
||||||
}
|
}
|
||||||
|
@ -679,7 +683,7 @@ LONG_STRING;
|
||||||
['op' => "updateFeed", 'sid' => "PriestsOfSyrinx"],
|
['op' => "updateFeed", 'sid' => "PriestsOfSyrinx"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->feedUpdate(11)->thenReturn(true);
|
Phake::when(Arsse::$db)->feedUpdate(11)->thenReturn(true);
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 1)->thenReturn(['id' => 1, 'feed' => 11]);
|
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 1)->thenReturn($this->v(['id' => 1, 'feed' => 11]));
|
||||||
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2)->thenThrow(new ExceptionInput("subjectMissing"));
|
Phake::when(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 2)->thenThrow(new ExceptionInput("subjectMissing"));
|
||||||
$exp = $this->respGood(['status' => "OK"]);
|
$exp = $this->respGood(['status' => "OK"]);
|
||||||
$this->assertResponse($exp, $this->req($in[0]));
|
$this->assertResponse($exp, $this->req($in[0]));
|
||||||
|
@ -711,8 +715,8 @@ LONG_STRING;
|
||||||
// set of various mocks for testing
|
// set of various mocks for testing
|
||||||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[0])->thenReturn(2)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, $db[1])->thenReturn(3)->thenThrow(new ExceptionInput("constraintViolation")); // error on the second call
|
||||||
Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Software", true)->thenReturn($out[0]);
|
Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Software", true)->thenReturn($this->v($out[0]));
|
||||||
Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Hardware", true)->thenReturn($out[1]);
|
Phake::when(Arsse::$db)->labelPropertiesGet(Arsse::$user->id, "Hardware", true)->thenReturn($this->v($out[1]));
|
||||||
// set up mocks that produce errors
|
// set up mocks that produce errors
|
||||||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, [])->thenThrow(new ExceptionInput("missing"));
|
||||||
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
|
Phake::when(Arsse::$db)->labelAdd(Arsse::$user->id, ['name' => ""])->thenThrow(new ExceptionInput("missing"));
|
||||||
|
@ -819,12 +823,12 @@ LONG_STRING;
|
||||||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true],
|
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true],
|
||||||
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true, 'unread_only' => true],
|
['op' => "getCategories", 'sid' => "PriestsOfSyrinx", 'enable_nested' => true, 'unread_only' => true],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->folders));
|
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders)));
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->topFolders));
|
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->topFolders)));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->subscriptions));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->starred);
|
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
|
||||||
$exp = [
|
$exp = [
|
||||||
[
|
[
|
||||||
['id' => "5", 'title' => "Local", 'unread' => 10, 'order_id' => 1],
|
['id' => "5", 'title' => "Local", 'unread' => 10, 'order_id' => 1],
|
||||||
|
@ -884,11 +888,11 @@ LONG_STRING;
|
||||||
|
|
||||||
public function testRetrieveCounterList() {
|
public function testRetrieveCounterList() {
|
||||||
$in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"];
|
$in = ['op' => "getCounters", 'sid' => "PriestsOfSyrinx"];
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->folders));
|
Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders)));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->subscriptions));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->starred);
|
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => "global-unread", 'counter' => 35],
|
['id' => "global-unread", 'counter' => 35],
|
||||||
['id' => "subscribed-feeds", 'counter' => 6],
|
['id' => "subscribed-feeds", 'counter' => 6],
|
||||||
|
@ -925,9 +929,9 @@ LONG_STRING;
|
||||||
['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 3],
|
['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 3],
|
||||||
['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 4],
|
['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 4],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 1)->thenReturn([1,3]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 1)->thenReturn($this->v([1,3]));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2)->thenReturn([3]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2)->thenReturn($this->v([3]));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 3)->thenReturn([]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 3)->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 4)->thenThrow(new ExceptionInput("idMissing"));
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 4)->thenThrow(new ExceptionInput("idMissing"));
|
||||||
$exp = [
|
$exp = [
|
||||||
|
@ -1005,11 +1009,11 @@ LONG_STRING;
|
||||||
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
|
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx", 'include_empty' => true],
|
||||||
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx"],
|
['op' => "getFeedTree", 'sid' => "PriestsOfSyrinx"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->folders));
|
Phake::when(Arsse::$db)->folderList($this->anything(), null, true)->thenReturn(new Result($this->v($this->folders)));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->subscriptions));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything())->thenReturn(new Result($this->v($this->subscriptions)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), true)->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), true)->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
Phake::when(Arsse::$db)->articleCount($this->anything(), $this->anything())->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->starred);
|
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
|
||||||
// the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them
|
// the expectations are packed tightly since they're very verbose; one can use var_export() (or convert to JSON) to pretty-print them
|
||||||
$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,],],],],],];
|
$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,],],],],],];
|
||||||
$this->assertResponse($this->respGood($exp), $this->req($in[0]));
|
$this->assertResponse($this->respGood($exp), $this->req($in[0]));
|
||||||
|
@ -1094,24 +1098,24 @@ LONG_STRING;
|
||||||
['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'offset' => 2],
|
['op' => "getFeeds", 'sid' => "PriestsOfSyrinx", 'cat_id' => 6, 'offset' => 2],
|
||||||
];
|
];
|
||||||
// statistical mocks
|
// statistical mocks
|
||||||
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->starred);
|
Phake::when(Arsse::$db)->articleStarred($this->anything())->thenReturn($this->v($this->starred));
|
||||||
Phake::when(Arsse::$db)->articleCount->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
Phake::when(Arsse::$db)->articleCount->thenReturn(7); // FIXME: this should check an unread+modifiedSince context
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(35);
|
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(35);
|
||||||
// label mocks
|
// label mocks
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
|
||||||
// subscription and folder list and unread count mocks
|
// subscription and folder list and unread count mocks
|
||||||
Phake::when(Arsse::$db)->folderList->thenThrow(new ExceptionInput("subjectMissing"));
|
Phake::when(Arsse::$db)->folderList->thenThrow(new ExceptionInput("subjectMissing"));
|
||||||
Phake::when(Arsse::$db)->subscriptionList->thenThrow(new ExceptionInput("subjectMissing"));
|
Phake::when(Arsse::$db)->subscriptionList->thenThrow(new ExceptionInput("subjectMissing"));
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->folders));
|
Phake::when(Arsse::$db)->folderList($this->anything())->thenReturn(new Result($this->v($this->folders)));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, true)->thenReturn(new Result($this->subscriptions));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, true)->thenReturn(new Result($this->v($this->subscriptions)));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, false)->thenReturn(new Result($this->filterSubs(null)));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, false)->thenReturn(new Result($this->v($this->filterSubs(null))));
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), null)->thenReturn(new Result($this->folders));
|
Phake::when(Arsse::$db)->folderList($this->anything(), null)->thenReturn(new Result($this->v($this->folders)));
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->filterFolders(null)));
|
Phake::when(Arsse::$db)->folderList($this->anything(), null, false)->thenReturn(new Result($this->v($this->filterFolders(null))));
|
||||||
foreach ($this->folders as $f) {
|
foreach ($this->folders as $f) {
|
||||||
Phake::when(Arsse::$db)->folderList($this->anything(), $f['id'], false)->thenReturn(new Result($this->filterFolders($f['id'])));
|
Phake::when(Arsse::$db)->folderList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterFolders($f['id']))));
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->folder($f['id']))->thenReturn($this->reduceFolders($f['id']));
|
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true)->folder($f['id']))->thenReturn($this->reduceFolders($f['id']));
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything(), $f['id'], false)->thenReturn(new Result($this->filterSubs($f['id'])));
|
Phake::when(Arsse::$db)->subscriptionList($this->anything(), $f['id'], false)->thenReturn(new Result($this->v($this->filterSubs($f['id']))));
|
||||||
}
|
}
|
||||||
$exp = [
|
$exp = [
|
||||||
[
|
[
|
||||||
|
@ -1263,10 +1267,10 @@ LONG_STRING;
|
||||||
['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 4], // invalid field
|
['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "42, 2112, -1", 'field' => 4], // invalid field
|
||||||
['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "0, -1", 'field' => 3], // no valid IDs
|
['op' => "updateArticle", 'sid' => "PriestsOfSyrinx", 'article_ids' => "0, -1", 'field' => 3], // no valid IDs
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(true), $this->anything())->thenReturn(new Result([['id' => 42]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(true), $this->anything())->thenReturn(new Result($this->v([['id' => 42]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(false), $this->anything())->thenReturn(new Result([['id' => 2112]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->starred(false), $this->anything())->thenReturn(new Result($this->v([['id' => 2112]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(true), $this->anything())->thenReturn(new Result([['id' => 42]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(true), $this->anything())->thenReturn(new Result($this->v([['id' => 42]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(false), $this->anything())->thenReturn(new Result([['id' => 2112]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([42, 2112])->unread(false), $this->anything())->thenReturn(new Result($this->v([['id' => 2112]])));
|
||||||
Phake::when(Arsse::$db)->articleMark->thenReturn(1);
|
Phake::when(Arsse::$db)->articleMark->thenReturn(1);
|
||||||
Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => false], (new Context)->articles([42, 2112]))->thenReturn(2);
|
Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => false], (new Context)->articles([42, 2112]))->thenReturn(2);
|
||||||
Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => true], (new Context)->articles([42, 2112]))->thenReturn(4);
|
Phake::when(Arsse::$db)->articleMark($this->anything(), ['starred' => true], (new Context)->articles([42, 2112]))->thenReturn(4);
|
||||||
|
@ -1327,13 +1331,13 @@ LONG_STRING;
|
||||||
['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "101"],
|
['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "101"],
|
||||||
['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "102"],
|
['op' => "getArticle", 'sid' => "PriestsOfSyrinx", 'article_id' => "102"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 101)->thenReturn([]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 101)->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 102)->thenReturn([1,3]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 102)->thenReturn($this->v([1,3]));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101, 102]))->thenReturn(new Result($this->articles));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101, 102]))->thenReturn(new Result($this->v($this->articles)));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101]))->thenReturn(new Result([$this->articles[0]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([101]))->thenReturn(new Result($this->v([$this->articles[0]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([102]))->thenReturn(new Result([$this->articles[1]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->articles([102]))->thenReturn(new Result($this->v([$this->articles[1]])));
|
||||||
$exp = $this->respErr("INCORRECT_USAGE");
|
$exp = $this->respErr("INCORRECT_USAGE");
|
||||||
$this->assertResponse($exp, $this->req($in[0]));
|
$this->assertResponse($exp, $this->req($in[0]));
|
||||||
$this->assertResponse($exp, $this->req($in[1]));
|
$this->assertResponse($exp, $this->req($in[1]));
|
||||||
|
@ -1436,22 +1440,22 @@ LONG_STRING;
|
||||||
['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
||||||
['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
|
['op' => "getCompactHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->articleList->thenReturn(new Result([['id' => 0]]));
|
Phake::when(Arsse::$db)->articleList->thenReturn(new Result($this->v([['id' => 0]])));
|
||||||
Phake::when(Arsse::$db)->articleCount->thenReturn(0);
|
Phake::when(Arsse::$db)->articleCount->thenReturn(0);
|
||||||
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
|
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
|
||||||
$c = (new Context)->reverse(true);
|
$c = (new Context)->reverse(true);
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), Database::LIST_MINIMAL)->thenThrow(new ExceptionInput("subjectMissing"));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(2112), Database::LIST_MINIMAL)->thenThrow(new ExceptionInput("subjectMissing"));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), $c, Database::LIST_MINIMAL)->thenReturn(new Result($this->articles));
|
Phake::when(Arsse::$db)->articleList($this->anything(), $c, Database::LIST_MINIMAL)->thenReturn(new Result($this->v($this->articles)));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 1]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 1]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 2]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 2]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 3]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 3]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 4]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->label(1088)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 4]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 5]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 5]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 6]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->subscription(42)->annotated(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 6]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 7]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 7]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 8]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 8]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 9]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->limit(5)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 9]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 10]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->oldestArticle(48), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 10]])));
|
||||||
$out1 = [
|
$out1 = [
|
||||||
$this->respErr("INCORRECT_USAGE"),
|
$this->respErr("INCORRECT_USAGE"),
|
||||||
$this->respGood([]),
|
$this->respGood([]),
|
||||||
|
@ -1483,9 +1487,9 @@ LONG_STRING;
|
||||||
$this->assertResponse($out1[$a], $this->req($in1[$a]), "Test $a failed");
|
$this->assertResponse($out1[$a], $this->req($in1[$a]), "Test $a failed");
|
||||||
}
|
}
|
||||||
for ($a = 0; $a < sizeof($in2); $a++) {
|
for ($a = 0; $a < sizeof($in2); $a++) {
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(false)->markedSince(Date::sub("PT24H")), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 1001]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(false)->markedSince(Date::sub("PT24H")), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 1001]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 1002]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H")), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 1002]])));
|
||||||
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 1003]]));
|
Phake::when(Arsse::$db)->articleList($this->anything(), (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H"))->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result($this->v([['id' => 1003]])));
|
||||||
$this->assertResponse($out2[$a], $this->req($in2[$a]), "Test $a failed");
|
$this->assertResponse($out2[$a], $this->req($in2[$a]), "Test $a failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1531,10 +1535,10 @@ LONG_STRING;
|
||||||
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
||||||
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
|
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3, 'view_mode' => "marked"],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
|
Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn([1,3]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3]));
|
||||||
Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
|
Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
|
Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
|
||||||
Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(0));
|
Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(0));
|
||||||
|
@ -1616,10 +1620,10 @@ LONG_STRING;
|
||||||
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'skip' => 47, 'include_header' => true, 'order_by' => "date_reverse"],
|
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'skip' => 47, 'include_header' => true, 'order_by' => "date_reverse"],
|
||||||
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'show_excerpt' => true],
|
['op' => "getHeadlines", 'sid' => "PriestsOfSyrinx", 'feed_id' => -4, 'show_excerpt' => true],
|
||||||
];
|
];
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels));
|
Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->v($this->labels)));
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels)));
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
|
Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn([1,3]);
|
Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2112)->thenReturn($this->v([1,3]));
|
||||||
Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
|
Phake::when(Arsse::$db)->articleCategoriesGet->thenReturn([]);
|
||||||
Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
|
Phake::when(Arsse::$db)->articleCategoriesGet($this->anything(), 2112)->thenReturn(["Boring","Illogical"]);
|
||||||
Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(1));
|
Phake::when(Arsse::$db)->articleList->thenReturn($this->generateHeadlines(1));
|
||||||
|
@ -1720,7 +1724,7 @@ LONG_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateHeadlines(int $id): Result {
|
protected function generateHeadlines(int $id): Result {
|
||||||
return new Result([
|
return new Result($this->v([
|
||||||
[
|
[
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'url' => 'http://example.com/1',
|
'url' => 'http://example.com/1',
|
||||||
|
@ -1761,7 +1765,7 @@ LONG_STRING;
|
||||||
'media_type' => "text/plain",
|
'media_type' => "text/plain",
|
||||||
'note' => "Note 2",
|
'note' => "Note 2",
|
||||||
],
|
],
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function outputHeadlines(int $id): Response {
|
protected function outputHeadlines(int $id): Response {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\User;
|
namespace JKingWeb\Arsse\TestCase\User;
|
||||||
|
|
||||||
|
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Conf;
|
use JKingWeb\Arsse\Conf;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
|
|
|
@ -32,6 +32,8 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
public function approximateTime($exp, $act) {
|
public function approximateTime($exp, $act) {
|
||||||
if (is_null($act)) {
|
if (is_null($act)) {
|
||||||
return null;
|
return null;
|
||||||
|
} elseif (is_null($exp)) {
|
||||||
|
return $act;
|
||||||
}
|
}
|
||||||
$target = Date::normalize($exp)->getTimeStamp();
|
$target = Date::normalize($exp)->getTimeStamp();
|
||||||
$value = Date::normalize($act)->getTimeStamp();
|
$value = Date::normalize($act)->getTimeStamp();
|
||||||
|
|
|
@ -11,7 +11,7 @@ use JKingWeb\Arsse\Db\SQLite3\Driver;
|
||||||
|
|
||||||
trait DriverSQLite3 {
|
trait DriverSQLite3 {
|
||||||
public function setUpDriver() {
|
public function setUpDriver() {
|
||||||
if (!extension_loaded("sqlite3")) {
|
if (!Driver::requirementsMet()) {
|
||||||
$this->markTestSkipped("SQLite extension not loaded");
|
$this->markTestSkipped("SQLite extension not loaded");
|
||||||
}
|
}
|
||||||
Arsse::$conf->dbSQLite3File = ":memory:";
|
Arsse::$conf->dbSQLite3File = ":memory:";
|
||||||
|
|
24
tests/lib/Database/DriverSQLite3PDO.php
Normal file
24
tests/lib/Database/DriverSQLite3PDO.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Test\Database;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver;
|
||||||
|
|
||||||
|
trait DriverSQLite3PDO {
|
||||||
|
public function setUpDriver() {
|
||||||
|
if (!PDODriver::requirementsMet()) {
|
||||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded");
|
||||||
|
}
|
||||||
|
Arsse::$conf->dbSQLite3File = ":memory:";
|
||||||
|
$this->drv = new PDODriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nextID(string $table): int {
|
||||||
|
return (int) $this->drv->query("SELECT (case when max(id) then max(id) else 0 end)+1 from $table")->getValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -888,8 +888,8 @@ trait SeriesArticle {
|
||||||
public function testFetchStarredCounts() {
|
public function testFetchStarredCounts() {
|
||||||
$exp1 = ['total' => 2, 'unread' => 1, 'read' => 1];
|
$exp1 = ['total' => 2, 'unread' => 1, 'read' => 1];
|
||||||
$exp2 = ['total' => 0, 'unread' => 0, 'read' => 0];
|
$exp2 = ['total' => 0, 'unread' => 0, 'read' => 0];
|
||||||
$this->assertSame($exp1, Arsse::$db->articleStarred("john.doe@example.com"));
|
$this->assertEquals($exp1, Arsse::$db->articleStarred("john.doe@example.com"));
|
||||||
$this->assertSame($exp2, Arsse::$db->articleStarred("jane.doe@example.com"));
|
$this->assertEquals($exp2, Arsse::$db->articleStarred("jane.doe@example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFetchStarredCountsWithoutAuthority() {
|
public function testFetchStarredCountsWithoutAuthority() {
|
||||||
|
|
|
@ -256,9 +256,9 @@ trait SeriesFeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListStaleFeeds() {
|
public function testListStaleFeeds() {
|
||||||
$this->assertSame([1,3,4], Arsse::$db->feedListStale());
|
$this->assertEquals([1,3,4], Arsse::$db->feedListStale());
|
||||||
Arsse::$db->feedUpdate(3);
|
Arsse::$db->feedUpdate(3);
|
||||||
Arsse::$db->feedUpdate(4);
|
Arsse::$db->feedUpdate(4);
|
||||||
$this->assertSame([1], Arsse::$db->feedListStale());
|
$this->assertEquals([1], Arsse::$db->feedListStale());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use JKingWeb\Arsse\User\Driver as UserDriver;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Conf;
|
use JKingWeb\Arsse\Conf;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
use JKingWeb\Arsse\Test\Database;
|
use JKingWeb\Arsse\Test\Database;
|
||||||
use JKingWeb\Arsse\Db\Result;
|
use JKingWeb\Arsse\Db\Result;
|
||||||
use Phake;
|
use Phake;
|
||||||
|
@ -90,8 +91,19 @@ trait Setup {
|
||||||
$row = array_combine($cols, $row);
|
$row = array_combine($cols, $row);
|
||||||
foreach ($data as $index => $test) {
|
foreach ($data as $index => $test) {
|
||||||
foreach ($test as $col => $value) {
|
foreach ($test as $col => $value) {
|
||||||
if ($types[$col]=="datetime") {
|
switch ($types[$col]) {
|
||||||
|
case "datetime":
|
||||||
$test[$col] = $this->approximateTime($row[$col], $value);
|
$test[$col] = $this->approximateTime($row[$col], $value);
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
$test[$col] = ValueInfo::normalize($value, ValueInfo::T_INT | ValueInfo::M_DROP | valueInfo::M_NULL);
|
||||||
|
break;
|
||||||
|
case "float":
|
||||||
|
$test[$col] = ValueInfo::normalize($value, ValueInfo::T_FLOAT | ValueInfo::M_DROP | valueInfo::M_NULL);
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
$test[$col] = (int) ValueInfo::normalize($value, ValueInfo::T_BOOL | ValueInfo::M_DROP | valueInfo::M_NULL);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($row===$test) {
|
if ($row===$test) {
|
||||||
|
|
|
@ -1,253 +0,0 @@
|
||||||
<?php
|
|
||||||
/** @license MIT
|
|
||||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
|
||||||
* See LICENSE and AUTHORS files for details */
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
namespace JKingWeb\Arsse\Test\Db;
|
|
||||||
|
|
||||||
use JKingWeb\Arsse\Db\Statement;
|
|
||||||
|
|
||||||
trait BindingTests {
|
|
||||||
public function testBindNull() {
|
|
||||||
$input = null;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => null,
|
|
||||||
"float" => null,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => null,
|
|
||||||
"string" => null,
|
|
||||||
"boolean" => null,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
// types may also be strict (e.g. "strict integer") and never pass null to the database; this is useful for NOT NULL columns
|
|
||||||
// only null input should yield different results, so only this test has different expectations
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => gmdate("Y-m-d", 0),
|
|
||||||
"time" => gmdate("H:i:s", 0),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", 0),
|
|
||||||
"binary" => "",
|
|
||||||
"string" => "",
|
|
||||||
"boolean" => 0,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindTrue() {
|
|
||||||
$input = true;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 1,
|
|
||||||
"float" => 1.0,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => "1",
|
|
||||||
"string" => "1",
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindFalse() {
|
|
||||||
$input = false;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => "",
|
|
||||||
"string" => "",
|
|
||||||
"boolean" => 0,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindInteger() {
|
|
||||||
$input = 2112;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 2112,
|
|
||||||
"float" => 2112.0,
|
|
||||||
"date" => gmdate("Y-m-d", 2112),
|
|
||||||
"time" => gmdate("H:i:s", 2112),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", 2112),
|
|
||||||
"binary" => "2112",
|
|
||||||
"string" => "2112",
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindIntegerZero() {
|
|
||||||
$input = 0;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => gmdate("Y-m-d", 0),
|
|
||||||
"time" => gmdate("H:i:s", 0),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", 0),
|
|
||||||
"binary" => "0",
|
|
||||||
"string" => "0",
|
|
||||||
"boolean" => 0,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindFloat() {
|
|
||||||
$input = 2112.0;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 2112,
|
|
||||||
"float" => 2112.0,
|
|
||||||
"date" => gmdate("Y-m-d", 2112),
|
|
||||||
"time" => gmdate("H:i:s", 2112),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", 2112),
|
|
||||||
"binary" => "2112",
|
|
||||||
"string" => "2112",
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindFloatZero() {
|
|
||||||
$input = 0.0;
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => gmdate("Y-m-d", 0),
|
|
||||||
"time" => gmdate("H:i:s", 0),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", 0),
|
|
||||||
"binary" => "0",
|
|
||||||
"string" => "0",
|
|
||||||
"boolean" => 0,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindAsciiString() {
|
|
||||||
$input = "Random string";
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => $input,
|
|
||||||
"string" => $input,
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindUtf8String() {
|
|
||||||
$input = "é";
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => $input,
|
|
||||||
"string" => $input,
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindBinaryString() {
|
|
||||||
// FIXME: This test may be unreliable; SQLite happily stores invalid UTF-8 text as bytes untouched, but other engines probably don't do this
|
|
||||||
$input = chr(233);
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => null,
|
|
||||||
"time" => null,
|
|
||||||
"datetime" => null,
|
|
||||||
"binary" => $input,
|
|
||||||
"string" => $input,
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindIso8601DateString() {
|
|
||||||
$input = "2017-01-09T13:11:17";
|
|
||||||
$time = strtotime($input." UTC");
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 2017,
|
|
||||||
"float" => 2017.0,
|
|
||||||
"date" => gmdate("Y-m-d", $time),
|
|
||||||
"time" => gmdate("H:i:s", $time),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", $time),
|
|
||||||
"binary" => $input,
|
|
||||||
"string" => $input,
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindArbitraryDateString() {
|
|
||||||
$input = "Today";
|
|
||||||
$time = date_create($input, new \DateTimezone("UTC"))->getTimestamp();
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => 0,
|
|
||||||
"float" => 0.0,
|
|
||||||
"date" => gmdate("Y-m-d", $time),
|
|
||||||
"time" => gmdate("H:i:s", $time),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", $time),
|
|
||||||
"binary" => $input,
|
|
||||||
"string" => $input,
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindMutableDateObject($class = '\DateTime') {
|
|
||||||
$input = new $class("Noon Today");
|
|
||||||
$time = $input->getTimestamp();
|
|
||||||
$exp = [
|
|
||||||
"null" => null,
|
|
||||||
"integer" => $time,
|
|
||||||
"float" => (float) $time,
|
|
||||||
"date" => gmdate("Y-m-d", $time),
|
|
||||||
"time" => gmdate("H:i:s", $time),
|
|
||||||
"datetime" => gmdate("Y-m-d H:i:s", $time),
|
|
||||||
"binary" => gmdate("Y-m-d H:i:s", $time),
|
|
||||||
"string" => gmdate("Y-m-d H:i:s", $time),
|
|
||||||
"boolean" => 1,
|
|
||||||
];
|
|
||||||
$this->checkBinding($input, $exp);
|
|
||||||
$this->checkBinding($input, $exp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testBindImmutableDateObject() {
|
|
||||||
$this->testBindMutableDateObject('\DateTimeImmutable');
|
|
||||||
}
|
|
||||||
}
|
|
23
tests/lib/PDOTest.php
Normal file
23
tests/lib/PDOTest.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Test;
|
||||||
|
|
||||||
|
trait PDOTest {
|
||||||
|
protected function v($value) {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
foreach($value as $k => $v) {
|
||||||
|
if (is_array($v)) {
|
||||||
|
$value[$k] = $this->v($v);
|
||||||
|
} elseif (is_int($v) || is_float($v)) {
|
||||||
|
$value[$k] = (string) $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,12 @@
|
||||||
<file>cases/Db/SQLite3/TestCreation.php</file>
|
<file>cases/Db/SQLite3/TestCreation.php</file>
|
||||||
<file>cases/Db/SQLite3/TestDriver.php</file>
|
<file>cases/Db/SQLite3/TestDriver.php</file>
|
||||||
<file>cases/Db/SQLite3/TestUpdate.php</file>
|
<file>cases/Db/SQLite3/TestUpdate.php</file>
|
||||||
|
|
||||||
|
<file>cases/Db/SQLite3PDO/TestResult.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/TestStatement.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/TestCreation.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/TestDriver.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/TestUpdate.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Database functions">
|
<testsuite name="Database functions">
|
||||||
<file>cases/Db/SQLite3/Database/TestMiscellany.php</file>
|
<file>cases/Db/SQLite3/Database/TestMiscellany.php</file>
|
||||||
|
@ -64,15 +70,28 @@
|
||||||
<file>cases/Db/SQLite3/Database/TestArticle.php</file>
|
<file>cases/Db/SQLite3/Database/TestArticle.php</file>
|
||||||
<file>cases/Db/SQLite3/Database/TestLabel.php</file>
|
<file>cases/Db/SQLite3/Database/TestLabel.php</file>
|
||||||
<file>cases/Db/SQLite3/Database/TestCleanup.php</file>
|
<file>cases/Db/SQLite3/Database/TestCleanup.php</file>
|
||||||
|
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestMiscellany.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestMeta.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestUser.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestSession.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestFolder.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestFeed.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestSubscription.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestArticle.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestLabel.php</file>
|
||||||
|
<file>cases/Db/SQLite3PDO/Database/TestCleanup.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Controllers">
|
<testsuite name="Controllers">
|
||||||
<testsuite name="NCNv1">
|
<testsuite name="NCNv1">
|
||||||
<file>cases/REST/NextCloudNews/TestVersions.php</file>
|
<file>cases/REST/NextCloudNews/TestVersions.php</file>
|
||||||
<file>cases/REST/NextCloudNews/TestV1_2.php</file>
|
<file>cases/REST/NextCloudNews/TestV1_2.php</file>
|
||||||
|
<file>cases/REST/NextCloudNews/PDO/TestV1_2.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="TTRSS">
|
<testsuite name="TTRSS">
|
||||||
<file>cases/REST/TinyTinyRSS/TestAPI.php</file>
|
<file>cases/REST/TinyTinyRSS/TestAPI.php</file>
|
||||||
<file>cases/REST/TinyTinyRSS/TestIcon.php</file>
|
<file>cases/REST/TinyTinyRSS/TestIcon.php</file>
|
||||||
|
<file>cases/REST/TinyTinyRSS/PDO/TestAPI.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Refresh service">
|
<testsuite name="Refresh service">
|
||||||
|
|
Loading…
Reference in a new issue