Browse Source

Fix whitespace

Also fixed my editor so tabs won't happen again!
microsub
J. King 7 years ago
parent
commit
a67fe30408
  1. 2
      lib/Database.php
  2. 74
      lib/Db/AbstractStatement.php
  3. 2
      lib/Db/Result.php
  4. 20
      lib/Db/SQLite3/Statement.php
  5. 48
      lib/Db/Statement.php
  6. 2
      lib/REST.php
  7. 4
      lib/REST/AbstractHandler.php
  8. 2
      lib/REST/Handler.php
  9. 2
      lib/REST/NextCloudNews/V1_2.php
  10. 40
      lib/REST/NextCloudNews/Versions.php
  11. 104
      lib/REST/Request.php
  12. 28
      lib/REST/Response.php
  13. 12
      lib/User.php
  14. 8
      lib/User/Internal/InternalFunctions.php
  15. 6
      tests/Conf/TestConf.php
  16. 486
      tests/Db/SQLite3/TestDbDriverSQLite3.php
  17. 102
      tests/Db/SQLite3/TestDbStatementSQLite3.php
  18. 122
      tests/Db/SQLite3/TestDbUpdateSQLite3.php
  19. 4
      tests/Exception/TestException.php
  20. 6
      tests/Lang/testLangComplex.php
  21. 294
      tests/REST/NextCloudNews/TestNCNV1_2.php
  22. 60
      tests/REST/NextCloudNews/TestNCNVersionDiscovery.php
  23. 556
      tests/User/TestAuthorization.php
  24. 8
      tests/User/TestUserInternalDriver.php
  25. 8
      tests/User/TestUserMockExternal.php
  26. 12
      tests/User/TestUserMockInternal.php
  27. 138
      tests/lib/Database/Setup.php
  28. 404
      tests/lib/Db/BindingTests.php
  29. 252
      tests/lib/User/CommonTests.php
  30. 6
      tests/lib/User/Database.php
  31. 6
      tests/lib/User/DriverExternalMock.php
  32. 10
      tests/lib/User/DriverSkeleton.php

2
lib/Database.php

@ -362,7 +362,7 @@ class Database {
// if a parent is specified, make sure it exists and belongs to the user; get its root (first-level) folder if it's a nested folder // if a parent is specified, make sure it exists and belongs to the user; get its root (first-level) folder if it's a nested folder
$p = $this->db->prepare( $p = $this->db->prepare(
"WITH RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and id is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) ". "WITH RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and id is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) ".
"SELECT id,(id not in (select id from folders)) as valid from arsse_folders where owner is ? and id is ?", "SELECT id,(id not in (select id from folders)) as valid from arsse_folders where owner is ? and id is ?",
"str", "int", "str", "int")->run($user, $id, $user, $parent)->getRow(); "str", "int", "str", "int")->run($user, $id, $user, $parent)->getRow();
if(!$p) { if(!$p) {
throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "parent", 'id' => $parent]); throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "parent", 'id' => $parent]);

74
lib/Db/AbstractStatement.php

@ -5,7 +5,7 @@ namespace JKingWeb\Arsse\Db;
abstract class AbstractStatement implements Statement { abstract class AbstractStatement implements Statement {
abstract function runArray(array $values): Result; abstract function runArray(array $values): Result;
abstract static function dateFormat(int $part = self::TS_BOTH): string; abstract static function dateFormat(int $part = self::TS_BOTH): string;
public function run(...$values): Result { public function run(...$values): Result {
return $this->runArray($values); return $this->runArray($values);
@ -30,41 +30,41 @@ abstract class AbstractStatement implements Statement {
return true; return true;
} }
protected function cast($v, string $t) { protected function cast($v, string $t) {
switch($t) { switch($t) {
case "date": case "date":
return $this->formatDate($v, self::TS_DATE); return $this->formatDate($v, self::TS_DATE);
case "time": case "time":
return $this->formatDate($v, self::TS_TIME); return $this->formatDate($v, self::TS_TIME);
case "datetime": case "datetime":
return $this->formatDate($v, self::TS_BOTH); return $this->formatDate($v, self::TS_BOTH);
case "null": case "null":
case "integer": case "integer":
case "float": case "float":
case "binary": case "binary":
case "string": case "string":
case "boolean": case "boolean":
if($t=="binary") $t = "string"; if($t=="binary") $t = "string";
$value = $v; $value = $v;
try{ try{
settype($value, $t); settype($value, $t);
} catch(\Throwable $e) { } catch(\Throwable $e) {
// handle objects // handle objects
$value = $v; $value = $v;
if($value instanceof \DateTimeInterface) { if($value instanceof \DateTimeInterface) {
$value = $value->getTimestamp(); $value = $value->getTimestamp();
if($t=="string") $value = $this->formatDate($value, self::TS_BOTH); if($t=="string") $value = $this->formatDate($value, self::TS_BOTH);
settype($value, $t); settype($value, $t);
} else { } else {
$value = null; $value = null;
settype($value, $t); settype($value, $t);
} }
} }
return $value; return $value;
default: default:
throw new Exception("paramTypeUnknown", $type); throw new Exception("paramTypeUnknown", $type);
} }
} }
protected function formatDate($date, int $part = self::TS_BOTH) { protected function formatDate($date, int $part = self::TS_BOTH) {
// Force UTC. // Force UTC.
@ -82,7 +82,7 @@ abstract class AbstractStatement implements Statement {
$time = strtotime($date); $time = strtotime($date);
if($time===false) return null; if($time===false) return null;
} else if (is_bool($date)) { } else if (is_bool($date)) {
return null; return null;
} else { } else {
$time = (int) $date; $time = (int) $date;
} }

2
lib/Db/Result.php

@ -12,7 +12,7 @@ interface Result extends \Iterator {
function getRow(); function getRow();
function getAll(): array; function getAll(): array;
function getValue(); function getValue();
function changes(); function changes();
function lastId(); function lastId();
} }

20
lib/Db/SQLite3/Statement.php

@ -12,17 +12,17 @@ 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, "null" => \SQLITE3_NULL,
"integer" => \SQLITE3_INTEGER, "integer" => \SQLITE3_INTEGER,
"float" => \SQLITE3_FLOAT, "float" => \SQLITE3_FLOAT,
"date" => \SQLITE3_TEXT, "date" => \SQLITE3_TEXT,
"time" => \SQLITE3_TEXT, "time" => \SQLITE3_TEXT,
"datetime" => \SQLITE3_TEXT, "datetime" => \SQLITE3_TEXT,
"binary" => \SQLITE3_BLOB, "binary" => \SQLITE3_BLOB,
"string" => \SQLITE3_TEXT, "string" => \SQLITE3_TEXT,
"boolean" => \SQLITE3_INTEGER, "boolean" => \SQLITE3_INTEGER,
]; ];
protected $db; protected $db;
protected $st; protected $st;
protected $types; protected $types;

48
lib/Db/Statement.php

@ -6,32 +6,32 @@ interface Statement {
const TS_TIME = -1; const TS_TIME = -1;
const TS_DATE = 0; const TS_DATE = 0;
const TS_BOTH = 1; const TS_BOTH = 1;
const TYPES = [ const TYPES = [
"null" => "null", "null" => "null",
"nil" => "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", "date" => "date",
"time" => "time", "time" => "time",
"datetime" => "datetime", "datetime" => "datetime",
"timestamp" => "datetime", "timestamp" => "datetime",
"blob" => "binary", "blob" => "binary",
"bin" => "binary", "bin" => "binary",
"binary" => "binary", "binary" => "binary",
"text" => "string", "text" => "string",
"string" => "string", "string" => "string",
"str" => "string", "str" => "string",
"bool" => "boolean", "bool" => "boolean",
"boolean" => "boolean", "boolean" => "boolean",
"bit" => "boolean", "bit" => "boolean",
]; ];
static function dateFormat(int $part = self::TS_BOTH): string; static function dateFormat(int $part = self::TS_BOTH): string;
function run(...$values): Result; function run(...$values): Result;
function runArray(array $values): Result; function runArray(array $values): Result;
function rebind(...$bindings): bool; function rebind(...$bindings): bool;

2
lib/REST.php

@ -24,7 +24,7 @@ class REST {
// Fever https://feedafever.com/api // Fever https://feedafever.com/api
// NewsBlur http://www.newsblur.com/api // NewsBlur http://www.newsblur.com/api
]; ];
function __construct() { function __construct() {
} }

4
lib/REST/AbstractHandler.php

@ -3,6 +3,6 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST; namespace JKingWeb\Arsse\REST;
abstract class AbstractHandler implements Handler { abstract class AbstractHandler implements Handler {
abstract function __construct(); abstract function __construct();
abstract function dispatch(Request $req): Response; abstract function dispatch(Request $req): Response;
} }

2
lib/REST/Handler.php

@ -4,5 +4,5 @@ namespace JKingWeb\Arsse\REST;
interface Handler { interface Handler {
function __construct(); function __construct();
function dispatch(Request $req): Response; function dispatch(Request $req): Response;
} }

2
lib/REST/NextCloudNews/V1_2.php

@ -29,7 +29,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$data = []; $data = [];
} }
// FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ? // FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ?
$data = array_merge($data, $req->query); $data = array_merge($data, $req->query);
// match the path // match the path
if(preg_match("<^/(items|folders|feeds|cleanup|version|status|user)(?:/([^/]+))?(?:/([^/]+))?(?:/([^/]+))?/?$>", $req->path, $url)) { if(preg_match("<^/(items|folders|feeds|cleanup|version|status|user)(?:/([^/]+))?(?:/([^/]+))?(?:/([^/]+))?/?$>", $req->path, $url)) {
// clean up the path // clean up the path

40
lib/REST/NextCloudNews/Versions.php

@ -4,25 +4,25 @@ namespace JKingWeb\Arsse\REST\NextCloudNews;
use JKingWeb\Arsse\REST\Response; use JKingWeb\Arsse\REST\Response;
class Versions extends \JKingWeb\Arsse\REST\AbstractHandler { class Versions extends \JKingWeb\Arsse\REST\AbstractHandler {
function __construct() { function __construct() {
} }
function dispatch(\JKingWeb\Arsse\REST\Request $req): \JKingWeb\Arsse\REST\Response { function dispatch(\JKingWeb\Arsse\REST\Request $req): \JKingWeb\Arsse\REST\Response {
// if a method other than GET was used, this is an error // if a method other than GET was used, this is an error
if($req->method != "GET") { if($req->method != "GET") {
return new Response(405); return new Response(405);
} }
if(preg_match("<^/?$>",$req->path)) { if(preg_match("<^/?$>",$req->path)) {
// if the request path is an empty string or just a slash, return the supported versions // if the request path is an empty string or just a slash, return the supported versions
$out = [ $out = [
'apiLevels' => [ 'apiLevels' => [
'v1-2', 'v1-2',
] ]
]; ];
return new Response(200, $out); return new Response(200, $out);
} else { } else {
// if the URL path was anything else, the client is probably trying a version we don't support // if the URL path was anything else, the client is probably trying a version we don't support
return new Response(404); return new Response(404);
} }
} }
} }

104
lib/REST/Request.php

@ -3,59 +3,59 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST; namespace JKingWeb\Arsse\REST;
class Request { class Request {
public $method = "GET"; public $method = "GET";
public $url = ""; public $url = "";
public $path =""; public $path ="";
public $query = ""; public $query = "";
public $type =""; public $type ="";
public $body = ""; public $body = "";
function __construct(string $method = null, string $url = null, string $body = null, string $contentType = null) { function __construct(string $method = null, string $url = null, string $body = null, string $contentType = null) {
if(is_null($method)) $method = $_SERVER['REQUEST_METHOD']; if(is_null($method)) $method = $_SERVER['REQUEST_METHOD'];
if(is_null($url)) $url = $_SERVER['REQUEST_URI']; if(is_null($url)) $url = $_SERVER['REQUEST_URI'];
if(is_null($body)) $body = file_get_contents("php://input"); if(is_null($body)) $body = file_get_contents("php://input");
if(is_null($contentType)) { if(is_null($contentType)) {
if(isset($_SERVER['HTTP_CONTENT_TYPE'])) { if(isset($_SERVER['HTTP_CONTENT_TYPE'])) {
$contentType = $_SERVER['HTTP_CONTENT_TYPE']; $contentType = $_SERVER['HTTP_CONTENT_TYPE'];
} else { } else {
$contentType = ""; $contentType = "";
} }
} }
$this->method = strtoupper($method); $this->method = strtoupper($method);
$this->url = $url; $this->url = $url;
$this->body = $body; $this->body = $body;
$this->type = $contentType; $this->type = $contentType;
$this->refreshURL(); $this->refreshURL();
} }
public function refreshURL() { public function refreshURL() {
$url = $this->parseURL($this->url); $url = $this->parseURL($this->url);
$this->path = $url['path']; $this->path = $url['path'];
$this->query = $url['query']; $this->query = $url['query'];
} }
protected function parseURL(string $url): array { protected function parseURL(string $url): array {
// split the query string from the path // split the query string from the path
$parts = explode("?", $url); $parts = explode("?", $url);
$out = ['path' => $parts[0], 'query' => []]; $out = ['path' => $parts[0], 'query' => []];
// if there is a query string, parse it // if there is a query string, parse it
if(isset($parts[1])) { if(isset($parts[1])) {
// split along & to get key-value pairs // split along & to get key-value pairs
$query = explode("&", $parts[1]); $query = explode("&", $parts[1]);
for($a = 0; $a < sizeof($query); $a++) { for($a = 0; $a < sizeof($query); $a++) {
// split each pair, into no more than two parts // split each pair, into no more than two parts
$data = explode("=", $query[$a], 2); $data = explode("=", $query[$a], 2);
// decode the key // decode the key
$key = rawurldecode($data[0]); $key = rawurldecode($data[0]);
// decode the value if there is one // decode the value if there is one
$value = ""; $value = "";
if(isset($data[1])) { if(isset($data[1])) {
$value = rawurldecode($data[1]); $value = rawurldecode($data[1]);
} }
// add the pair to the query output, overwriting earlier values for the same key, is present // add the pair to the query output, overwriting earlier values for the same key, is present
$out['query'][$key] = $value; $out['query'][$key] = $value;
} }
} }
return $out; return $out;
} }
} }

28
lib/REST/Response.php

@ -3,20 +3,20 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST; namespace JKingWeb\Arsse\REST;
class Response { class Response {
const T_JSON = "application/json"; const T_JSON = "application/json";
const T_XML = "application/xml"; const T_XML = "application/xml";
const T_TEXT = "text/plain"; const T_TEXT = "text/plain";
public $code; public $code;
public $payload; public $payload;
public $type; public $type;
public $fields; public $fields;
function __construct(int $code, $payload = null, string $type = self::T_JSON, array $extraFields = []) { function __construct(int $code, $payload = null, string $type = self::T_JSON, array $extraFields = []) {
$this->code = $code; $this->code = $code;
$this->payload = $payload; $this->payload = $payload;
$this->type = $type; $this->type = $type;
$this->fields = $extraFields; $this->fields = $extraFields;
} }
} }

12
lib/User.php

@ -9,7 +9,7 @@ class User {
protected $authz = true; protected $authz = true;
protected $authzSupported = 0; protected $authzSupported = 0;
protected $actor = []; protected $actor = [];
static public function listDrivers(): array { static public function listDrivers(): array {
$sep = \DIRECTORY_SEPARATOR; $sep = \DIRECTORY_SEPARATOR;
$path = __DIR__.$sep."User".$sep; $path = __DIR__.$sep."User".$sep;
@ -75,7 +75,7 @@ class User {
if(!in_array($affectedRights,[User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN])) return false; if(!in_array($affectedRights,[User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN])) return false;
return true; return true;
} }
public function credentials(): array { public function credentials(): array {
if(Data::$conf->userAuthPreferHTTP) { if(Data::$conf->userAuthPreferHTTP) {
return $this->credentialsHTTP(); return $this->credentialsHTTP();
@ -142,7 +142,7 @@ class User {
public function driverFunctions(string $function = null) { public function driverFunctions(string $function = null) {
return $this->u->driverFunctions($function); return $this->u->driverFunctions($function);
} }
public function list(string $domain = null): array { public function list(string $domain = null): array {
$func = "userList"; $func = "userList";
switch($this->u->driverFunctions($func)) { switch($this->u->driverFunctions($func)) {
@ -166,7 +166,7 @@ class User {
$this->authz = $setting; $this->authz = $setting;
return $setting; return $setting;
} }
public function exists(string $user): bool { public function exists(string $user): bool {
$func = "userExists"; $func = "userExists";
switch($this->u->driverFunctions($func)) { switch($this->u->driverFunctions($func)) {
@ -321,7 +321,7 @@ class User {
return User\Driver::RIGHTS_NONE; return User\Driver::RIGHTS_NONE;
} }
} }
public function rightsSet(string $user, int $level): bool { public function rightsSet(string $user, int $level): bool {
$func = "userRightsSet"; $func = "userRightsSet";
switch($this->u->driverFunctions($func)) { switch($this->u->driverFunctions($func)) {
@ -346,7 +346,7 @@ class User {
throw new User\ExceptionNotImplemented("notImplemented", ["action" => $func, "user" => $user]); throw new User\ExceptionNotImplemented("notImplemented", ["action" => $func, "user" => $user]);
} }
} }
// FIXME: stubs // FIXME: stubs
public function challenge(): bool {throw new User\Exception("authFailed");} public function challenge(): bool {throw new User\Exception("authFailed");}
public function challengeForm(): bool {throw new User\Exception("authFailed");} public function challengeForm(): bool {throw new User\Exception("authFailed");}

8
lib/User/Internal/InternalFunctions.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\User\Internal; namespace JKingWeb\Arsse\User\Internal;
use JKingWeb\Arsse\Data; use JKingWeb\Arsse\Data;
trait InternalFunctions { trait InternalFunctions {
protected $actor = []; protected $actor = [];
public function __construct() { public function __construct() {
@ -32,7 +32,7 @@ trait InternalFunctions {
function userList(string $domain = null): array { function userList(string $domain = null): array {
return $this->db->userList($domain); return $this->db->userList($domain);
} }
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string { function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
return $this->db->userPasswordSet($user, $newPassword); return $this->db->userPasswordSet($user, $newPassword);
} }
@ -48,8 +48,8 @@ trait InternalFunctions {
function userRightsGet(string $user): int { function userRightsGet(string $user): int {
return $this->db->userRightsGet($user); return $this->db->userRightsGet($user);
} }
function userRightsSet(string $user, int $level): bool { function userRightsSet(string $user, int $level): bool {
return $this->db->userRightsSet($user, $level); return $this->db->userRightsSet($user, $level);
} }
} }

6
tests/Conf/TestConf.php

@ -6,7 +6,7 @@ use org\bovigo\vfs\vfsStream;
class TestConf extends \PHPUnit\Framework\TestCase { class TestConf extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
static $vfs; static $vfs;
static $path; static $path;
@ -30,11 +30,11 @@ class TestConf extends \PHPUnit\Framework\TestCase {
self::$vfs = null; self::$vfs = null;
$this->clearData(); $this->clearData();
} }
function testLoadDefaultValues() { function testLoadDefaultValues() {
$this->assertInstanceOf(Conf::class, new Conf()); $this->assertInstanceOf(Conf::class, new Conf());
} }
/** /**
* @depends testLoadDefaultValues * @depends testLoadDefaultValues
*/ */

486
tests/Db/SQLite3/TestDbDriverSQLite3.php

@ -7,279 +7,279 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
protected $data; protected $data;
protected $drv; protected $drv;
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
$conf = new Conf(); $conf = new Conf();
$conf->dbDriver = Db\SQLite3\Driver::class; $conf->dbDriver = Db\SQLite3\Driver::class;
$conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook'); $conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook');
Data::$conf = $conf; Data::$conf = $conf;
$this->drv = new Db\SQLite3\Driver(true); $this->drv = new Db\SQLite3\Driver(true);
} }
function tearDown() { function tearDown() {
unset($this->drv); unset($this->drv);
unlink(Data::$conf->dbSQLite3File); unlink(Data::$conf->dbSQLite3File);
$this->clearData(); $this->clearData();
} }
function testFetchDriverName() { function testFetchDriverName() {
$class = Data::$conf->dbDriver; $class = Data::$conf->dbDriver;
$this->assertTrue(strlen($class::driverName()) > 0); $this->assertTrue(strlen($class::driverName()) > 0);
} }
function testExecAValidStatement() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)"));
}
function testExecAnInvalidStatement() { function testExecAValidStatement() {
$this->assertException("engineErrorGeneral", "Db"); $this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)"));
$this->drv->exec("And the meek shall inherit the earth..."); }
}
function testExecMultipleStatements() { function testExecAnInvalidStatement() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)")); $this->assertException("engineErrorGeneral", "Db");
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $this->drv->exec("And the meek shall inherit the earth...");
$this->assertEquals(2112, $ch->querySingle("SELECT id from test")); }
}
function testExecTimeout() { function testExecMultipleStatements() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)"));
$ch->exec("BEGIN EXCLUSIVE TRANSACTION"); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->assertException("general", "Db", "ExceptionTimeout"); $this->assertEquals(2112, $ch->querySingle("SELECT id from test"));
$this->drv->exec("CREATE TABLE test(id integer primary key)"); }
}
function testExecConstraintViolation() { function testExecTimeout() {
$this->drv->exec("CREATE TABLE test(id integer not null)"); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->assertException("constraintViolation", "Db", "ExceptionInput"); $ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->drv->exec("INSERT INTO test(id) values(null)"); $this->assertException("general", "Db", "ExceptionTimeout");
} $this->drv->exec("CREATE TABLE test(id integer primary key)");
}
function testExecTypeViolation() { function testExecConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("typeViolation", "Db", "ExceptionInput"); $this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values('ook')"); $this->drv->exec("INSERT INTO test(id) values(null)");
} }
function testMakeAValidQuery() { function testExecTypeViolation() {
$this->assertInstanceOf(Db\SQLite3\Result::class, $this->drv->query("SELECT 1")); $this->drv->exec("CREATE TABLE test(id integer primary key)");
} $this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values('ook')");
}
function testMakeAnInvalidQuery() { function testMakeAValidQuery() {
$this->assertException("engineErrorGeneral", "Db"); $this->assertInstanceOf(Db\SQLite3\Result::class, $this->drv->query("SELECT 1"));
$this->drv->query("Apollo was astonished; Dionysus thought me mad"); }
}
function testMakeAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db");
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
}
function testQueryTimeout() { function testQueryTimeout() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$ch->exec("BEGIN EXCLUSIVE TRANSACTION"); $ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->assertException("general", "Db", "ExceptionTimeout"); $this->assertException("general", "Db", "ExceptionTimeout");
$this->drv->query("CREATE TABLE test(id integer primary key)"); $this->drv->query("CREATE TABLE test(id integer primary key)");
} }
function testQueryConstraintViolation() { function testQueryConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer not null)"); $this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("constraintViolation", "Db", "ExceptionInput"); $this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values(null)"); $this->drv->query("INSERT INTO test(id) values(null)");
} }
function testQueryTypeViolation() { function testQueryTypeViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->assertException("typeViolation", "Db", "ExceptionInput"); $this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values('ook')"); $this->drv->query("INSERT INTO test(id) values('ook')");
} }
function testPrepareAValidQuery() { function testPrepareAValidQuery() {
$s = $this->drv->prepare("SELECT ?, ?", "int", "int"); $s = $this->drv->prepare("SELECT ?, ?", "int", "int");
$this->assertInstanceOf(Db\SQLite3\Statement::class, $s); $this->assertInstanceOf(Db\SQLite3\Statement::class, $s);
} }
function testPrepareAnInvalidQuery() { function testPrepareAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db"); $this->assertException("engineErrorGeneral", "Db");
$s = $this->drv->prepare("This is an invalid query", "int", "int"); $s = $this->drv->prepare("This is an invalid query", "int", "int");
} }
function testBeginTransaction() { function testBeginTransaction() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
} }
function testCommitTransaction() { function testCommitTransaction() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(); $this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select)); $this->assertEquals(1, $ch->querySingle($select));
} }
function testRollbackTransaction() { function testRollbackTransaction() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(); $this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue()); $this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
} }
function testBeginChainedTransactions() { function testBeginChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
} }
function testCommitChainedTransactions() { function testCommitChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(); $this->drv->commit();
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(); $this->drv->commit();
$this->assertEquals(2, $ch->querySingle($select)); $this->assertEquals(2, $ch->querySingle($select));
} }
function testRollbackChainedTransactions() { function testRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(); $this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(); $this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue()); $this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
} }
function testPartiallyRollbackChainedTransactions() { function testPartiallyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(); $this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(); $this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select)); $this->assertEquals(1, $ch->querySingle($select));
} }
function testFullyRollbackChainedTransactions() { function testFullyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(true); $this->drv->rollback(true);
$this->assertEquals(0, $this->drv->query($select)->getValue()); $this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
} }
function testFullyCommitChainedTransactions() { function testFullyCommitChainedTransactions() {
$select = "SELECT count(*) FROM test"; $select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)"; $insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File); $ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)"); $this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue()); $this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin(); $this->drv->begin();
$this->drv->query($insert); $this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select)); $this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(true); $this->drv->commit(true);
$this->assertEquals(2, $this->drv->query($select)->getValue()); $this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(2, $ch->querySingle($select)); $this->assertEquals(2, $ch->querySingle($select));
} }
function testFetchSchemaVersion() { function testFetchSchemaVersion() {
$this->assertSame(0, $this->drv->schemaVersion()); $this->assertSame(0, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=1"); $this->drv->exec("PRAGMA user_version=1");
$this->assertSame(1, $this->drv->schemaVersion()); $this->assertSame(1, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=2"); $this->drv->exec("PRAGMA user_version=2");
$this->assertSame(2, $this->drv->schemaVersion()); $this->assertSame(2, $this->drv->schemaVersion());
} }
function testManipulateAdvisoryLock() { function testManipulateAdvisoryLock() {
$this->assertTrue($this->drv->unlock()); $this->assertTrue($this->drv->unlock());
$this->assertFalse($this->drv->isLocked()); $this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock()); $this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked()); $this->assertFalse($this->drv->isLocked());
$this->drv->exec("CREATE TABLE arsse_settings(key primary key, value, type) without rowid; PRAGMA user_version=1"); $this->drv->exec("CREATE TABLE arsse_settings(key primary key, value, type) without rowid; PRAGMA user_version=1");
$this->assertTrue($this->drv->lock()); $this->assertTrue($this->drv->lock());
$this->assertTrue($this->drv->isLocked()); $this->assertTrue($this->drv->isLocked());
$this->assertFalse($this->drv->lock()); $this->assertFalse($this->drv->lock());
$this->drv->exec("PRAGMA user_version=0"); $this->drv->exec("PRAGMA user_version=0");
$this->assertFalse($this->drv->isLocked()); $this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock()); $this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked()); $this->assertFalse($this->drv->isLocked());
$this->drv->exec("PRAGMA user_version=1"); $this->drv->exec("PRAGMA user_version=1");
$this->assertTrue($this->drv->isLocked()); $this->assertTrue($this->drv->isLocked());
$this->assertTrue($this->drv->unlock()); $this->assertTrue($this->drv->unlock());
$this->assertFalse($this->drv->isLocked()); $this->assertFalse($this->drv->isLocked());
} }
} }

102
tests/Db/SQLite3/TestDbStatementSQLite3.php

@ -8,88 +8,88 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\Db\BindingTests; use Test\Tools, Test\Db\BindingTests;
protected $c; protected $c;
static protected $imp = Db\SQLite3\Statement::class; static protected $imp = Db\SQLite3\Statement::class;
function setUp() { function setUp() {
date_default_timezone_set("UTC"); date_default_timezone_set("UTC");
$c = new \SQLite3(":memory:"); $c = new \SQLite3(":memory:");
$c->enableExceptions(true); $c->enableExceptions(true);
$this->c = $c; $this->c = $c;
} }
function tearDown() { function tearDown() {
try {$this->s->close();} catch(\Exception $e) {} try {$this->s->close();} catch(\Exception $e) {}
$this->c->close(); $this->c->close();
unset($this->c); unset($this->c);
} }
protected function checkBinding($input, array $expectations) { protected function checkBinding($input, array $expectations) {
$nativeStatement = $this->c->prepare("SELECT ? as value"); $nativeStatement = $this->c->prepare("SELECT ? as value");
$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([$type]); $s->rebindArray([$type]);
$val = $s->runArray([$input])->getRow()['value']; $val = $s->runArray([$input])->getRow()['value'];
$this->assertSame($expectations[$type], $val, "Type $type failed comparison."); $this->assertSame($expectations[$type], $val, "Type $type failed comparison.");
} }
} }
function testConstructStatement() { function testConstructStatement() {
$nativeStatement = $this->c->prepare("SELECT ? as value"); $nativeStatement = $this->c->prepare("SELECT ? as value");
$this->assertInstanceOf(Statement::class, new Db\SQLite3\Statement($this->c, $nativeStatement)); $this->assertInstanceOf(Statement::class, new Db\SQLite3\Statement($this->c, $nativeStatement));
} }
function testBindMissingValue() { function testBindMissingValue() {
$nativeStatement = $this->c->prepare("SELECT ? as value"); $nativeStatement = $this->c->prepare("SELECT ? as value");
$s = new self::$imp($this->c, $nativeStatement); $s = new self::$imp($this->c, $nativeStatement);
$val = $s->runArray()->getRow()['value']; $val = $s->runArray()->getRow()['value'];
$this->assertSame(null, $val); $this->assertSame(null, $val);
} }
function testBindMultipleValues() { function testBindMultipleValues() {
$exp = [ $exp = [
'one' => 1, 'one' => 1,
'two' => 2, 'two' => 2,
]; ];
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two"); $nativeStatement = $this->c->prepare("SELECT ? as one, ? as two");
$s = new self::$imp($this->c, $nativeStatement, ["int", "int"]); $s = new self::$imp($this->c, $nativeStatement, ["int", "int"]);
$val = $s->runArray([1,2])->getRow(); $val = $s->runArray([1,2])->getRow();
$this->assertSame($exp, $val); $this->assertSame($exp, $val);
} }
function testBindRecursively() { function testBindRecursively() {
$exp = [ $exp = [
'one' => 1, 'one' => 1,
'two' => 2, 'two' => 2,
'three' => 3, 'three' => 3,
'four' => 4, 'four' => 4,
]; ];
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four"); $nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four");
$s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]); $s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]);
$val = $s->runArray([1, [2, 3], 4])->getRow(); $val = $s->runArray([1, [2, 3], 4])->getRow();
$this->assertSame($exp, $val); $this->assertSame($exp, $val);
} }
function testBindWithoutType() { function testBindWithoutType() {
$nativeStatement = $this->c->prepare("SELECT ? as value"); $nativeStatement = $this->c->prepare("SELECT ? as value");
$this->assertException("paramTypeMissing", "Db"); $this->assertException("paramTypeMissing", "Db");
$s = new self::$imp($this->c, $nativeStatement, []); $s = new self::$imp($this->c, $nativeStatement, []);
$s->runArray([1]); $s->runArray([1]);
} }
function testViolateConstraint() { function testViolateConstraint() {
$this->c->exec("CREATE TABLE test(id integer not null)"); $this->c->exec("CREATE TABLE test(id integer not null)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)"); $nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["int"]); $s = new self::$imp($this->c, $nativeStatement, ["int"]);
$this->assertException("constraintViolation", "Db", "ExceptionInput"); $this->assertException("constraintViolation", "Db", "ExceptionInput");
$s->runArray([null]); $s->runArray([null]);
} }
function testMismatchTypes() { function testMismatchTypes() {
$this->c->exec("CREATE TABLE test(id integer primary key)"); $this->c->exec("CREATE TABLE test(id integer primary key)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)"); $nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["str"]); $s = new self::$imp($this->c, $nativeStatement, ["str"]);
$this->assertException("typeViolation", "Db", "ExceptionInput"); $this->assertException("typeViolation", "Db", "ExceptionInput");
$s->runArray(['ook']); $s->runArray(['ook']);
} }
} }

122
tests/Db/SQLite3/TestDbUpdateSQLite3.php

@ -8,84 +8,84 @@ class TestDbUpdateSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
protected $data; protected $data;
protected $drv; protected $drv;
protected $vfs; protected $vfs;
protected $base; protected $base;
const MINIMAL1 = "create table arsse_settings(key text primary key not null, value text, type text not null); pragma user_version=1"; const MINIMAL1 = "create table arsse_settings(key text primary key not null, value text, type text not null); pragma user_version=1";
const MINIMAL2 = "pragma user_version=2"; const MINIMAL2 = "pragma user_version=2";
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
$this->vfs = vfsStream::setup("schemata", null, ['SQLite3' => []]); $this->vfs = vfsStream::setup("schemata", null, ['SQLite3' => []]);
$conf = new Conf(); $conf = new Conf();
$conf->dbDriver = Db\SQLite3\Driver::class; $conf->dbDriver = Db\SQLite3\Driver::class;
$conf->dbSchemaBase = $this->vfs->url(); $conf->dbSchemaBase = $this->vfs->url();
$this->base = $this->vfs->url()."/SQLite3/"; $this->base = $this->vfs->url()."/SQLite3/";
$conf->dbSQLite3File = ":memory:"; $conf->dbSQLite3File = ":memory:";
Data::$conf = $conf; Data::$conf = $conf;
$this->drv = new Db\SQLite3\Driver(true); $this->drv = new Db\SQLite3\Driver(true);
} }
function tearDown() { function tearDown() {
unset($this->drv); unset($this->drv);
unset($this->data); unset($this->data);
unset($this->vfs); unset($this->vfs);
$this->clearData(); $this->clearData();
} }
function testLoadMissingFile() { function testLoadMissingFile() {
$this->assertException("updateFileMissing", "Db"); $this->assertException("updateFileMissing", "Db");
$this->drv->schemaUpdate(1); $this->drv->schemaUpdate(1);
} }
function testLoadUnreadableFile() { function testLoadUnreadableFile() {
touch($this->base."0.sql"); touch($this->base."0.sql");
chmod($this->base."0.sql", 0000); chmod($this->base."0.sql", 0000);
$this->assertException("updateFileUnreadable", "Db"); $this->assertException("updateFileUnreadable", "Db");
$this->drv->schemaUpdate(1); $this->drv->schemaUpdate(1);
} }
function testLoadCorruptFile() { function testLoadCorruptFile() {
file_put_contents($this->base."0.sql", "This is a corrupt file"); file_put_contents($this->base."0.sql", "This is a corrupt file");
$this->assertException("updateFileError", "Db"); $this->assertException("updateFileError", "Db");
$this->drv->schemaUpdate(1); $this->drv->schemaUpdate(1);
} }
function testLoadIncompleteFile() { function testLoadIncompleteFile() {
file_put_contents($this->base."0.sql", "create table arsse_settings(key text primary key not null, value text, type text not null);"); file_put_contents($this->base."0.sql", "create table arsse_settings(key text primary key not null, value text, type text not null);");
$this->assertException("updateFileIncomplete", "Db"); $this->assertException("updateFileIncomplete", "Db");
$this->drv->schemaUpdate(1); $this->drv->schemaUpdate(1);
} }
function testLoadCorrectFile() { function testLoadCorrectFile() {
file_put_contents($this->base."0.sql", self::MINIMAL1); file_put_contents($this->base."0.sql", self::MINIMAL1);
$this->drv->schemaUpdate(1); $this->drv->schemaUpdate(1);
$this->assertEquals(1, $this->drv->schemaVersion()); $this->assertEquals(1, $this->drv->schemaVersion());
} }
function testPerformPartialUpdate() { function testPerformPartialUpdate() {
file_put_contents($this->base."0.sql", self::MINIMAL1); file_put_contents($this->base."0.sql", self::MINIMAL1);
file_put_contents($this->base."1.sql", ""); file_put_contents($this->base."1.sql", "");
$this->assertException("updateFileIncomplete", "Db"); $this->assertException("updateFileIncomplete", "Db");
try { try {
$this->drv->schemaUpdate(2); $this->drv->schemaUpdate(2);
} catch(Exception $e) { } catch(Exception $e) {
$this->assertEquals(1, $this->drv->schemaVersion()); $this->assertEquals(1, $this->drv->schemaVersion());
throw $e; throw $e;
} }
} }
function testPerformSequentialUpdate() { function testPerformSequentialUpdate() {
file_put_contents($this->base."0.sql", self::MINIMAL1); file_put_contents($this->base."0.sql", self::MINIMAL1);
file_put_contents($this->base."1.sql", self::MINIMAL2); file_put_contents($this->base."1.sql", self::MINIMAL2);
$this->drv->schemaUpdate(2); $this->drv->schemaUpdate(2);
$this->assertEquals(2, $this->drv->schemaVersion()); $this->assertEquals(2, $this->drv->schemaVersion());
} }
function testPerformActualUpdate() { function testPerformActualUpdate() {
Data::$conf->dbSchemaBase = (new Conf())->dbSchemaBase; Data::$conf->dbSchemaBase = (new Conf())->dbSchemaBase;
$this->drv->schemaUpdate(Database::SCHEMA_VERSION); $this->drv->schemaUpdate(Database::SCHEMA_VERSION);
$this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion()); $this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion());
} }
} }

4
tests/Exception/TestException.php

@ -21,7 +21,7 @@ class TestException extends \PHPUnit\Framework\TestCase {
// clean up // clean up
$this->clearData(true); $this->clearData(true);
} }
function testBaseClass() { function testBaseClass() {
$this->assertException("unknown"); $this->assertException("unknown");
throw new Exception("unknown"); throw new Exception("unknown");
@ -34,7 +34,7 @@ class TestException extends \PHPUnit\Framework\TestCase {
$this->assertException("unknown"); $this->assertException("unknown");
throw new Exception(); throw new Exception();
} }
/** /**
* @depends testBaseClass * @depends testBaseClass
*/ */

6
tests/Lang/testLangComplex.php

@ -19,7 +19,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
$this->l->set("ja"); $this->l->set("ja");
$this->assertArrayNotHasKey('Test.absentText', $this->l->dump()); $this->assertArrayNotHasKey('Test.absentText', $this->l->dump());
} }
/** /**
* @depends testLazyLoad * @depends testLazyLoad
*/ */
@ -29,7 +29,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
$this->assertEquals("ja", $this->l->get()); $this->assertEquals("ja", $this->l->get());
$this->assertEquals("en", $this->l->get(true)); $this->assertEquals("en", $this->l->get(true));
} }
function testLoadCascadeOfFiles() { function testLoadCascadeOfFiles() {
$this->l->set("ja", true); $this->l->set("ja", true);
$this->assertEquals("de", $this->l->set("de", true)); $this->assertEquals("de", $this->l->set("de", true));
@ -44,7 +44,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
function testLoadSubtag() { function testLoadSubtag() {
$this->assertEquals("en_ca", $this->l->set("en_ca", true)); $this->assertEquals("en_ca", $this->l->set("en_ca", true));
} }
function testFetchAMessage() { function testFetchAMessage() {
$this->l->set("de", true); $this->l->set("de", true);
$this->assertEquals('und der Stein der Weisen', $this->l->msg('Test.presentText')); $this->assertEquals('und der Stein der Weisen', $this->l->msg('Test.presentText'));

294
tests/REST/NextCloudNews/TestNCNV1_2.php

@ -10,161 +10,161 @@ use Phake;
class TestNCNV1_2 extends \PHPUnit\Framework\TestCase { class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
protected $h; protected $h;
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
// create a mock user manager // create a mock user manager
Data::$user = Phake::mock(User::class); Data::$user = Phake::mock(User::class);
Phake::when(Data::$user)->authHTTP->thenReturn(true); Phake::when(Data::$user)->authHTTP->thenReturn(true);
Data::$user->id = "john.doe@example.com"; Data::$user->id = "john.doe@example.com";
// create a mock database interface // create a mock database interface
Data::$db = Phake::mock(Database::Class); Data::$db = Phake::mock(Database::Class);
$this->h = new REST\NextCloudNews\V1_2(); $this->h = new REST\NextCloudNews\V1_2();
} }
function tearDown() { function tearDown() {
$this->clearData(); $this->clearData();
} }
function testRespondToInvalidPaths() { function testRespondToInvalidPaths() {
$errs = [ $errs = [
404 => [ 404 => [
['GET', "/"], ['GET', "/"],
['PUT', "/"], ['PUT', "/"],
['POST', "/"], ['POST', "/"],
['DELETE', "/"], ['DELETE', "/"],
['GET', "/folders/1/invalid"], ['GET', "/folders/1/invalid"],
['PUT', "/folders/1/invalid"], ['PUT', "/folders/1/invalid"],
['POST', "/folders/1/invalid"], ['POST', "/folders/1/invalid"],
['DELETE', "/folders/1/invalid"], ['DELETE', "/folders/1/invalid"],
['GET', "/version/invalid"], ['GET', "/version/invalid"],
['PUT', "/version/invalid"], ['PUT', "/version/invalid"],
['POST', "/version/invalid"], ['POST', "/version/invalid"],
['DELETE', "/version/invalid"], ['DELETE', "/version/invalid"],
], ],
405 => [ 405 => [
'GET' => [ 'GET' => [
['PUT', "/version"], ['PUT', "/version"],
['POST', "/version"], ['POST', "/version"],
['DELETE', "/version"], ['DELETE', "/version"],
], ],
'GET, POST' => [ 'GET, POST' => [
['PUT', "/folders"], ['PUT', "/folders"],
['DELETE', "/folders"], ['DELETE', "/folders"],
], ],
'PUT, DELETE' => [ 'PUT, DELETE' => [
['GET', "/folders/1"], ['GET', "/folders/1"],
['POST', "/folders/1"], ['POST', "/folders/1"],
], ],
], ],
]; ];
foreach($errs[404] as $req) { foreach($errs[404] as $req) {
$exp = new Response(404); $exp = new Response(404);
list($method, $path) = $req; list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404."); $this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404.");
} }
foreach($errs[405] as $allow => $cases) { foreach($errs[405] as $allow => $cases) {
$exp = new Response(405, "", "", ['Allow: '.$allow]); $exp = new Response(405, "", "", ['Allow: '.$allow]);
foreach($cases as $req) { foreach($cases as $req) {
list($method, $path) = $req; list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405."); $this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405.");
} }
} }
} }
function testListFolders() { 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],
]; ];
Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list)); Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list));
$exp = new Response(200, ['folders' => $list]); $exp = new Response(200, ['folders' => $list]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders"))); $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
} }
function testAddAFolder() { function testAddAFolder() {
$in = [ $in = [
["name" => "Software"], ["name" => "Software"],
["name" => "Hardware"], ["name" => "Hardware"],
]; ];
$out = [ $out = [
['id' => 1, 'name' => "Software", 'parent' => null], ['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 2, 'name' => "Hardware", 'parent' => null], ['id' => 2, 'name' => "Hardware", 'parent' => null],
]; ];
// set of various mocks for testing // set of various mocks for testing
Phake::when(Data::$db)->folderAdd(Data::$user->id, $in[0])->thenReturn(1)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call Phake::when(Data::$db)->folderAdd(Data::$user->id, $in[0])->thenReturn(1)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Data::$db)->folderAdd(Data::$user->id, $in[1])->thenReturn(2)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call Phake::when(Data::$db)->folderAdd(Data::$user->id, $in[1])->thenReturn(2)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); // error on the second call
Phake::when(Data::$db)->folderPropertiesGet(Data::$user->id, 1)->thenReturn($out[0]); Phake::when(Data::$db)->folderPropertiesGet(Data::$user->id, 1)->thenReturn($out[0]);
Phake::when(Data::$db)->folderPropertiesGet(Data::$user->id, 2)->thenReturn($out[1]); Phake::when(Data::$db)->folderPropertiesGet(Data::$user->id, 2)->thenReturn($out[1]);
// set up mocks that produce errors // set up mocks that produce errors
Phake::when(Data::$db)->folderAdd(Data::$user->id, [])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing")); Phake::when(Data::$db)->folderAdd(Data::$user->id, [])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => ""])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing")); Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => ""])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => " "])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace")); Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => " "])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
// correctly add two folders, using different means // correctly add two folders, using different means
$exp = new Response(200, ['folders' => [$out[0]]]); $exp = new Response(200, ['folders' => [$out[0]]]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json')));
$exp = new Response(200, ['folders' => [$out[1]]]); $exp = new Response(200, ['folders' => [$out[1]]]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Hardware"))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Hardware")));
Phake::verify(Data::$db)->folderAdd(Data::$user->id, $in[0]); Phake::verify(Data::$db)->folderAdd(Data::$user->id, $in[0]);
Phake::verify(Data::$db)->folderAdd(Data::$user->id, $in[1]); Phake::verify(Data::$db)->folderAdd(Data::$user->id, $in[1]);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 1); Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 1);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 2); Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 2);
// test bad folder names // test bad folder names
$exp = new Response(422); $exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders"))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders")));
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":""}', 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":""}', 'application/json')));
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":" "}', 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", '{"name":" "}', 'application/json')));
// try adding the same two folders again // try adding the same two folders again
$exp = new Response(409); $exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software"))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software")));
$exp = new Response(409); $exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json')));
} }
function testRemoveAFolder() { function testRemoveAFolder() {
Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing"));
$exp = new Response(204); $exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1"))); $this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
$exp = new Response(404); $exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1"))); $this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
Phake::verify(Data::$db, Phake::times(2))->folderRemove(Data::$user->id, 1); Phake::verify(Data::$db, Phake::times(2))->folderRemove(Data::$user->id, 1);
// use a non-integer folder ID // use a non-integer folder ID
$exp = new Response(404); $exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/invalid"))); $this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/invalid")));
} }
function testRenameAFolder() { function testRenameAFolder() {
$in = [ $in = [
["name" => "Software"], ["name" => "Software"],
["name" => "Software"], ["name" => "Software"],
["name" => ""], ["name" => ""],
["name" => " "], ["name" => " "],
[], [],
]; ];
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[0])->thenReturn(true); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[0])->thenReturn(true);
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 2, $in[1])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation")); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 2, $in[1])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("constraintViolation"));
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[2])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing")); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[2])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[3])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace")); Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[3])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 1, $in[4])->thenReturn(true); // this should be stopped by the handler before the request gets to the database
Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 3, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); // folder ID 3 does not exist Phake::when(Data::$db)->folderPropertiesSet(Data::$user->id, 3, $this->anything())->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing")); // folder ID 3 does not exist
$exp = new Response(204); $exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json')));
$exp = new Response(409); $exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json')));
$exp = new Response(422); $exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json')));
$exp = new Response(422); $exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json')));
$exp = new Response(422); $exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json')));
$exp = new Response(404); $exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json'))); $this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json')));
} }
function testRetrieveServerVersion() { function testRetrieveServerVersion() {
$exp = new Response(200, ['version' => \JKingWeb\Arsse\VERSION]); $exp = new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/version"))); $this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/version")));
} }
} }

60
tests/REST/NextCloudNews/TestNCNVersionDiscovery.php

@ -8,37 +8,37 @@ use JKingWeb\Arsse\Rest\Response;
class TestNCNVersionDiscovery extends \PHPUnit\Framework\TestCase { class TestNCNVersionDiscovery extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
} }
function testFetchVersionList() { function testFetchVersionList() {
$exp = new Response(200, ['apiLevels' => ['v1-2']]); $exp = new Response(200, ['apiLevels' => ['v1-2']]);
$h = new Rest\NextCloudNews\Versions(); $h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/"); $req = new Request("GET", "/");
$res = $h->dispatch($req); $res = $h->dispatch($req);
$this->assertEquals($exp, $res); $this->assertEquals($exp, $res);
$req = new Request("GET", ""); $req = new Request("GET", "");
$res = $h->dispatch($req); $res = $h->dispatch($req);
$this->assertEquals($exp, $res); $this->assertEquals($exp, $res);
$req = new Request("GET", "/?id=1827"); $req = new Request("GET", "/?id=1827");
$res = $h->dispatch($req); $res = $h->dispatch($req);
$this->assertEquals($exp, $res); $this->assertEquals($exp, $res);
} }
function testUseIncorrectMethod() { function testUseIncorrectMethod() {
$exp = new Response(405); $exp = new Response(405);
$h = new Rest\NextCloudNews\Versions(); $h = new Rest\NextCloudNews\Versions();
$req = new Request("POST", "/"); $req = new Request("POST", "/");
$res = $h->dispatch($req); $res = $h->dispatch($req);
$this->assertEquals($exp, $res); $this->assertEquals($exp, $res);
} }
function testUseIncorrectPath() { function testUseIncorrectPath() {
$exp = new Response(404); $exp = new Response(404);
$h = new Rest\NextCloudNews\Versions(); $h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/ook"); $req = new Request("GET", "/ook");
$res = $h->dispatch($req); $res = $h->dispatch($req);
$this->assertEquals($exp, $res); $this->assertEquals($exp, $res);
} }
} }

556
tests/User/TestAuthorization.php

@ -5,297 +5,297 @@ namespace JKingWeb\Arsse;
class TestAuthorization extends \PHPUnit\Framework\TestCase { class TestAuthorization extends \PHPUnit\Framework\TestCase {
use Test\Tools; use Test\Tools;
const USERS = [
'user@example.com' => User\Driver::RIGHTS_NONE,
'user@example.org' => User\Driver::RIGHTS_NONE,
'dman@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER,
'dman@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER,
'dadm@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN,
'dadm@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN,
'gman@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER,
'gman@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER,
'gadm@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN,
'gadm@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN,
// invalid rights levels
'bad1@example.com' => User\Driver::RIGHTS_NONE+1,
'bad1@example.org' => User\Driver::RIGHTS_NONE+1,
'bad2@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
'bad2@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
'bad3@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
'bad3@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
'bad4@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
'bad4@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
'bad5@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
'bad5@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
]; const USERS = [
const LEVELS = [ 'user@example.com' => User\Driver::RIGHTS_NONE,
User\Driver::RIGHTS_NONE, 'user@example.org' => User\Driver::RIGHTS_NONE,
User\Driver::RIGHTS_DOMAIN_MANAGER, 'dman@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER,
User\Driver::RIGHTS_DOMAIN_ADMIN, 'dman@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER,
User\Driver::RIGHTS_GLOBAL_MANAGER, 'dadm@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN,
User\Driver::RIGHTS_GLOBAL_ADMIN, 'dadm@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN,
]; 'gman@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER,
const DOMAINS = [ 'gman@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER,
'@example.com', 'gadm@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN,
'@example.org', 'gadm@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN,
"", // invalid rights levels
]; 'bad1@example.com' => User\Driver::RIGHTS_NONE+1,
'bad1@example.org' => User\Driver::RIGHTS_NONE+1,
'bad2@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
'bad2@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
'bad3@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
'bad3@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
'bad4@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
'bad4@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
'bad5@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
'bad5@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
protected $data; ];
const LEVELS = [
User\Driver::RIGHTS_NONE,
User\Driver::RIGHTS_DOMAIN_MANAGER,
User\Driver::RIGHTS_DOMAIN_ADMIN,
User\Driver::RIGHTS_GLOBAL_MANAGER,
User\Driver::RIGHTS_GLOBAL_ADMIN,
];
const DOMAINS = [
'@example.com',
'@example.org',
"",
];
protected $data;
function setUp(string $drv = Test\User\DriverInternalMock::class, string $db = null) { function setUp(string $drv = Test\User\DriverInternalMock::class, string $db = null) {
$this->clearData(); $this->clearData();
$conf = new Conf(); $conf = new Conf();
$conf->userDriver = $drv; $conf->userDriver = $drv;
$conf->userAuthPreferHTTP = true; $conf->userAuthPreferHTTP = true;
$conf->userComposeNames = true; $conf->userComposeNames = true;
Data::$conf = $conf; Data::$conf = $conf;
if($db !== null) { if($db !== null) {
Data::$db = new $db(); Data::$db = new $db();
} }
Data::$user = new User(); Data::$user = new User();
Data::$user->authorizationEnabled(false); Data::$user->authorizationEnabled(false);
foreach(self::USERS as $user => $level) { foreach(self::USERS as $user => $level) {
Data::$user->add($user, ""); Data::$user->add($user, "");
Data::$user->rightsSet($user, $level); Data::$user->rightsSet($user, $level);
} }
Data::$user->authorizationEnabled(true); Data::$user->authorizationEnabled(true);
} }
function tearDown() { function tearDown() {
$this->clearData(); $this->clearData();
} }
function testSelfActionLogic() { function testSelfActionLogic() {
foreach(array_keys(self::USERS) as $user) { foreach(array_keys(self::USERS) as $user) {
Data::$user->auth($user, ""); Data::$user->auth($user, "");
// users should be able to do basic actions for themselves // users should be able to do basic actions for themselves
$this->assertTrue(Data::$user->authorize($user, "userExists"), "User $user could not act for themselves."); $this->assertTrue(Data::$user->authorize($user, "userExists"), "User $user could not act for themselves.");
$this->assertTrue(Data::$user->authorize($user, "userRemove"), "User $user could not act for themselves."); $this->assertTrue(Data::$user->authorize($user, "userRemove"), "User $user could not act for themselves.");
} }
} }
function testRegularUserLogic() { function testRegularUserLogic() {
foreach(self::USERS as $actor => $rights) { foreach(self::USERS as $actor => $rights) {
if($rights != User\Driver::RIGHTS_NONE) continue; if($rights != User\Driver::RIGHTS_NONE) continue;
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
foreach(array_keys(self::USERS) as $affected) { foreach(array_keys(self::USERS) as $affected) {
// regular users should only be able to act for themselves // regular users should only be able to act for themselves
if($actor==$affected) { if($actor==$affected) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// they should never be able to set rights // they should never be able to set rights
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
} }
} }
// they should not be able to list users // they should not be able to list users
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed."); $this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
} }
} }
} }
function testDomainManagerLogic() { function testDomainManagerLogic() {
foreach(self::USERS as $actor => $actorRights) { foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_MANAGER) continue; if($actorRights != User\Driver::RIGHTS_DOMAIN_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1); $actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) { foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1); $affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain managers should be able to check any user on the same domain // domain managers should be able to check any user on the same domain
if($actorDomain==$affectedDomain) { if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// they should only be able to act for regular users on the same domain // they should only be able to act for regular users on the same domain
if($actor==$affected || ($actorDomain==$affectedDomain && $affectedRights==User\Driver::RIGHTS_NONE)) { if($actor==$affected || ($actorDomain==$affectedDomain && $affectedRights==User\Driver::RIGHTS_NONE)) {
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// and they should only be able to set their own rights to regular user // and they should only be able to set their own rights to regular user
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_DOMAIN_MANAGER])) { if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_DOMAIN_MANAGER])) {
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
} }
} }
} }
// they should also be able to list all users on their own domain // they should also be able to list all users on their own domain
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) { if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied."); $this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed."); $this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
} }
} }
} }
} }
function testDomainAdministratorLogic() { function testDomainAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) { foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_ADMIN) continue; if($actorRights != User\Driver::RIGHTS_DOMAIN_ADMIN) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1); $actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
$allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN]; $allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN];
foreach(self::USERS as $affected => $affectedRights) { foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1); $affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain admins should be able to check any user on the same domain // domain admins should be able to check any user on the same domain
if($actorDomain==$affectedDomain) { if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// they should be able to act for any user on the same domain who is not a global manager or admin // they should be able to act for any user on the same domain who is not a global manager or admin
if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed)) { if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed)) {
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// they should be able to set rights for any user on their domain who is not a global manager or admin, up to domain admin level // they should be able to set rights for any user on their domain who is not a global manager or admin, up to domain admin level
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed) && in_array($level, $allowed)) { if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed) && in_array($level, $allowed)) {
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
} }
} }
} }
// they should also be able to list all users on their own domain // they should also be able to list all users on their own domain
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) { if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied."); $this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed."); $this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
} }
} }
} }
} }
function testGlobalManagerLogic() { function testGlobalManagerLogic() {
foreach(self::USERS as $actor => $actorRights) { foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_MANAGER) continue; if($actorRights != User\Driver::RIGHTS_GLOBAL_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1); $actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) { foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1); $affectedDomain = substr($affected,strrpos($affected,"@")+1);
// global managers should be able to check any user // global managers should be able to check any user
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
// they should only be able to act for regular users // they should only be able to act for regular users
if($actor==$affected || $affectedRights==User\Driver::RIGHTS_NONE) { if($actor==$affected || $affectedRights==User\Driver::RIGHTS_NONE) {
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// and they should only be able to set their own rights to regular user // and they should only be able to set their own rights to regular user
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_GLOBAL_MANAGER])) { if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_GLOBAL_MANAGER])) {
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
} }
} }
} }
// they should also be able to list all users // they should also be able to list all users
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied."); $this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} }
} }
} }
function testGlobalAdministratorLogic() { function testGlobalAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) { foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_ADMIN) continue; if($actorRights != User\Driver::RIGHTS_GLOBAL_ADMIN) continue;
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
// global admins can do anything // global admins can do anything
foreach(self::USERS as $affected => $affectedRights) { foreach(self::USERS as $affected => $affectedRights) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
} }
} }
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied."); $this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} }
} }
} }
function testInvalidLevelLogic() { function testInvalidLevelLogic() {
foreach(self::USERS as $actor => $rights) { foreach(self::USERS as $actor => $rights) {
if(in_array($rights, self::LEVELS)) continue; if(in_array($rights, self::LEVELS)) continue;
Data::$user->auth($actor, ""); Data::$user->auth($actor, "");
foreach(array_keys(self::USERS) as $affected) { foreach(array_keys(self::USERS) as $affected) {
// users with unknown/invalid rights should be treated just like regular users and only be able to act for themselves // users with unknown/invalid rights should be treated just like regular users and only be able to act for themselves
if($actor==$affected) { if($actor==$affected) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied."); $this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} else { } else {
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
} }
// they should never be able to set rights // they should never be able to set rights
foreach(self::LEVELS as $level) { foreach(self::LEVELS as $level) {
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed."); $this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
} }
} }
// they should not be able to list users // they should not be able to list users
foreach(self::DOMAINS as $domain) { foreach(self::DOMAINS as $domain) {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed."); $this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
} }
} }
} }
function testInternalExceptionLogic() { function testInternalExceptionLogic() {
$tests = [ $tests = [
// methods of User class to test, with parameters besides affected user // methods of User class to test, with parameters besides affected user
'exists' => [], 'exists' => [],
'remove' => [], 'remove' => [],
'add' => [''], 'add' => [''],
'passwordSet' => [''], 'passwordSet' => [''],
'propertiesGet' => [], 'propertiesGet' => [],
'propertiesSet' => [[]], 'propertiesSet' => [[]],
'rightsGet' => [], 'rightsGet' => [],
'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN], 'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN],
'list' => [], 'list' => [],
]; ];
// try first with a global admin (there should be no exception) // try first with a global admin (there should be no exception)
Data::$user->auth("gadm@example.com", ""); Data::$user->auth("gadm@example.com", "");
$this->assertCount(0, $this->checkExceptions("user@example.org", $tests)); $this->assertCount(0, $this->checkExceptions("user@example.org", $tests));
// next try with a regular user acting on another user (everything should fail) // next try with a regular user acting on another user (everything should fail)
Data::$user->auth("user@example.com", ""); Data::$user->auth("user@example.com", "");
$this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests)); $this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests));
} }
function testExternalExceptionLogic() { function testExternalExceptionLogic() {
// set up the test for an external driver // set up the test for an external driver
$this->setUp(Test\User\DriverExternalMock::class, Test\User\Database::class); $this->setUp(Test\User\DriverExternalMock::class, Test\User\Database::class);
// run the previous test with the external driver set up // run the previous test with the external driver set up
$this->testInternalExceptionLogic(); $this->testInternalExceptionLogic();
} }
// meat of testInternalExceptionLogic and testExternalExceptionLogic // meat of testInternalExceptionLogic and testExternalExceptionLogic
// calls each requested function with supplied arguments, catches authorization exceptions, and returns an array of caught failed calls // calls each requested function with supplied arguments, catches authorization exceptions, and returns an array of caught failed calls
protected function checkExceptions(string $user, $tests): array { protected function checkExceptions(string $user, $tests): array {
$err = []; $err = [];
foreach($tests as $func => $args) { foreach($tests as $func => $args) {
// list method does not take an affected user, so do not unshift for that one // list method does not take an affected user, so do not unshift for that one
if($func != "list") array_unshift($args, $user); if($func != "list") array_unshift($args, $user);
try { try {
call_user_func_array(array(Data::$user, $func), $args); call_user_func_array(array(Data::$user, $func), $args);
} catch(User\ExceptionAuthz $e) { } catch(User\ExceptionAuthz $e) {
$err[] = $func; $err[] = $func;
} }
} }
return $err; return $err;
} }
} }

8
tests/User/TestUserInternalDriver.php

@ -5,9 +5,9 @@ namespace JKingWeb\Arsse;
class TestUserInternalDriver extends \PHPUnit\Framework\TestCase { class TestUserInternalDriver extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\User\CommonTests; use Test\Tools, Test\User\CommonTests;
const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = User\Internal\Driver::class; const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = User\Internal\Driver::class;
} }

8
tests/User/TestUserMockExternal.php

@ -5,9 +5,9 @@ namespace JKingWeb\Arsse;
class TestUserMockExternal extends \PHPUnit\Framework\TestCase { class TestUserMockExternal extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\User\CommonTests; use Test\Tools, Test\User\CommonTests;
const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = Test\User\DriverExternalMock::class; const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = Test\User\DriverExternalMock::class;
} }

12
tests/User/TestUserMockInternal.php

@ -5,13 +5,13 @@ namespace JKingWeb\Arsse;
class TestUserMockInternal extends \PHPUnit\Framework\TestCase { class TestUserMockInternal extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\User\CommonTests; use Test\Tools, Test\User\CommonTests;
const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = Test\User\DriverInternalMock::class; const USER1 = "john.doe@example.com";
const USER2 = "jane.doe@example.com";
public $drv = Test\User\DriverInternalMock::class;
function setUpSeries() { function setUpSeries() {
Data::$db = null; Data::$db = null;
} }
} }

138
tests/lib/Database/Setup.php

@ -9,8 +9,8 @@ use JKingWeb\Arsse\Database;
use Phake; use Phake;
trait Setup { trait Setup {
protected $drv; protected $drv;
protected $data = [ protected $data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => [ 'columns' => [
'id' => 'str', 'id' => 'str',
@ -32,7 +32,7 @@ trait Setup {
// create a default configuration // create a default configuration
Data::$conf = new Conf(); Data::$conf = new Conf();
// configure and create the relevant database driver // configure and create the relevant database driver
$this->setUpDriver(); $this->setUpDriver();
// create the database interface with the suitable driver // create the database interface with the suitable driver
Data::$db = new Database($this->drv); Data::$db = new Database($this->drv);
Data::$db->schemaUpdate(); Data::$db->schemaUpdate();
@ -43,77 +43,77 @@ trait Setup {
if(method_exists($this, "setUpSeries")) $this->setUpSeries(); if(method_exists($this, "setUpSeries")) $this->setUpSeries();
} }
function tearDown() { function tearDown() {
// call the additional teardiwn method if it exists // call the additional teardiwn method if it exists
if(method_exists($this, "tearDownSeries")) $this->tearDownSeries(); if(method_exists($this, "tearDownSeries")) $this->tearDownSeries();
// clean up // clean up
$this->drv = null; $this->drv = null;
$this->clearData(); $this->clearData();
} }
function primeDatabase(array $data): bool { function primeDatabase(array $data): bool {
$this->drv->begin(); $this->drv->begin();
foreach($data as $table => $info) { foreach($data as $table => $info) {
$cols = implode(",", array_keys($info['columns'])); $cols = implode(",", array_keys($info['columns']));
$bindings = array_values($info['columns']); $bindings = array_values($info['columns']);
$params = implode(",", array_fill(0, sizeof($info['columns']), "?")); $params = implode(",", array_fill(0, sizeof($info['columns']), "?"));
$s = $this->drv->prepareArray("INSERT INTO $table($cols) values($params)", $bindings); $s = $this->drv->prepareArray("INSERT INTO $table($cols) values($params)", $bindings);
foreach($info['rows'] as $row) { foreach($info['rows'] as $row) {
$this->assertEquals(1, $s->runArray($row)->changes()); $this->assertEquals(1, $s->runArray($row)->changes());
} }
} }
$this->drv->commit(); $this->drv->commit();
return true; return true;
} }
function compareExpectations(array $expected): bool { function compareExpectations(array $expected): bool {
foreach($expected as $table => $info) { foreach($expected as $table => $info) {
$cols = implode(",", array_keys($info['columns'])); $cols = implode(",", array_keys($info['columns']));
$data = $this->drv->prepare("SELECT $cols from $table")->run()->getAll(); $data = $this->drv->prepare("SELECT $cols from $table")->run()->getAll();
$cols = array_keys($info['columns']); $cols = array_keys($info['columns']);
foreach($info['rows'] as $index => $values) { foreach($info['rows'] as $index => $values) {
$row = []; $row = [];
foreach($values as $key => $value) { foreach($values as $key => $value) {
$row[$cols[$key]] = $value; $row[$cols[$key]] = $value;
} }
$found = array_search($row, $data); $found = array_search($row, $data);
$this->assertNotSame(false, $found, "Table $table does not contain record at array index $index."); $this->assertNotSame(false, $found, "Table $table does not contain record at array index $index.");
unset($data[$found]); unset($data[$found]);
} }
$this->assertSame([], $data); $this->assertSame([], $data);
} }
return true; return true;
} }
function primeExpectations(array $source, array $tableSpecs = null): array { function primeExpectations(array $source, array $tableSpecs = null): array {
$out = []; $out = [];
foreach($tableSpecs as $table => $columns) { foreach($tableSpecs as $table => $columns) {
if(!isset($source[$table])) { if(!isset($source[$table])) {
$this->assertTrue(false, "Source for expectations does not contain requested table $table."); $this->assertTrue(false, "Source for expectations does not contain requested table $table.");
return []; return [];
} }
$out[$table] = [ $out[$table] = [
'columns' => [], 'columns' => [],
'rows' => [], 'rows' => [],
]; ];
$transformations = []; $transformations = [];
foreach($columns as $target => $col) { foreach($columns as $target => $col) {
if(!isset($source[$table]['columns'][$col])) { if(!isset($source[$table]['columns'][$col])) {
$this->assertTrue(false, "Source for expectations does not contain requested column $col of table $table."); $this->assertTrue(false, "Source for expectations does not contain requested column $col of table $table.");
return []; return [];
} }
$found = array_search($col, array_keys($source[$table]['columns'])); $found = array_search($col, array_keys($source[$table]['columns']));
$transformations[$found] = $target; $transformations[$found] = $target;
$out[$table]['columns'][$col] = $source[$table]['columns'][$col]; $out[$table]['columns'][$col] = $source[$table]['columns'][$col];
} }
foreach($source[$table]['rows'] as $sourceRow) { foreach($source[$table]['rows'] as $sourceRow) {
$newRow = []; $newRow = [];
foreach($transformations as $from => $to) { foreach($transformations as $from => $to) {
$newRow[$to] = $sourceRow[$from]; $newRow[$to] = $sourceRow[$from];
} }
$out[$table]['rows'][] = $newRow; $out[$table]['rows'][] = $newRow;
} }
} }
return $out; return $out;
} }
} }

404
tests/lib/Db/BindingTests.php

@ -4,219 +4,219 @@ namespace JKingWeb\Arsse\Test\Db;
use JKingWeb\Arsse\Db\Statement; use JKingWeb\Arsse\Db\Statement;
trait BindingTests { trait BindingTests {
function testBindNull() { function testBindNull() {
$input = null; $input = null;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => null, "integer" => null,
"float" => null, "float" => null,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => null, "binary" => null,
"string" => null, "string" => null,
"boolean" => null, "boolean" => null,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindTrue() { function testBindTrue() {
$input = true; $input = true;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 1, "integer" => 1,
"float" => 1.0, "float" => 1.0,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => "1", "binary" => "1",
"string" => "1", "string" => "1",
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindFalse() { function testBindFalse() {
$input = false; $input = false;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => "", "binary" => "",
"string" => "", "string" => "",
"boolean" => 0, "boolean" => 0,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindInteger() { function testBindInteger() {
$input = 2112; $input = 2112;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 2112, "integer" => 2112,
"float" => 2112.0, "float" => 2112.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), 2112), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), 2112),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), 2112), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), 2112),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 2112), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 2112),
"binary" => "2112", "binary" => "2112",
"string" => "2112", "string" => "2112",
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindIntegerZero() { function testBindIntegerZero() {
$input = 0; $input = 0;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), 0), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), 0),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), 0), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), 0),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 0), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 0),
"binary" => "0", "binary" => "0",
"string" => "0", "string" => "0",
"boolean" => 0, "boolean" => 0,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindFloat() { function testBindFloat() {
$input = 2112.0; $input = 2112.0;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 2112, "integer" => 2112,
"float" => 2112.0, "float" => 2112.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), 2112), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), 2112),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), 2112), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), 2112),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 2112), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 2112),
"binary" => "2112", "binary" => "2112",
"string" => "2112", "string" => "2112",
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindFloatZero() { function testBindFloatZero() {
$input = 0.0; $input = 0.0;
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), 0), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), 0),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), 0), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), 0),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 0), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), 0),
"binary" => "0", "binary" => "0",
"string" => "0", "string" => "0",
"boolean" => 0, "boolean" => 0,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindAsciiString() { function testBindAsciiString() {
$input = "Random string"; $input = "Random string";
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => $input, "binary" => $input,
"string" => $input, "string" => $input,
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindUtf8String() { function testBindUtf8String() {
$input = "é"; $input = "é";
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => $input, "binary" => $input,
"string" => $input, "string" => $input,
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindBinaryString() { 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 // 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); $input = chr(233);
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => null, "date" => null,
"time" => null, "time" => null,
"datetime" => null, "datetime" => null,
"binary" => $input, "binary" => $input,
"string" => $input, "string" => $input,
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindIso8601DateString() { function testBindIso8601DateString() {
$input = "2017-01-09T13:11:17"; $input = "2017-01-09T13:11:17";
$time = strtotime($input); $time = strtotime($input);
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 2017, "integer" => 2017,
"float" => 2017.0, "float" => 2017.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time),
"binary" => $input, "binary" => $input,
"string" => $input, "string" => $input,
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindArbitraryDateString() { function testBindArbitraryDateString() {
$input = "Today"; $input = "Today";
$time = strtotime($input); $time = strtotime($input);
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => 0, "integer" => 0,
"float" => 0.0, "float" => 0.0,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time),
"binary" => $input, "binary" => $input,
"string" => $input, "string" => $input,
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindMutableDateObject($class = '\DateTime') { function testBindMutableDateObject($class = '\DateTime') {
$input = new $class("Noon Today"); $input = new $class("Noon Today");
$time = $input->getTimestamp(); $time = $input->getTimestamp();
$exp = [ $exp = [
"null" => null, "null" => null,
"integer" => $time, "integer" => $time,
"float" => (float) $time, "float" => (float) $time,
"date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time), "date" => date(self::$imp::dateFormat(Statement::TS_DATE), $time),
"time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time), "time" => date(self::$imp::dateFormat(Statement::TS_TIME), $time),
"datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time), "datetime" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time),
"binary" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time), "binary" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time),
"string" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time), "string" => date(self::$imp::dateFormat(Statement::TS_BOTH), $time),
"boolean" => 1, "boolean" => 1,
]; ];
$this->checkBinding($input, $exp); $this->checkBinding($input, $exp);
} }
function testBindImmutableDateObject() { function testBindImmutableDateObject() {
$this->testBindMutableDateObject('\DateTimeImmutable'); $this->testBindMutableDateObject('\DateTimeImmutable');
} }
} }

252
tests/lib/User/CommonTests.php

@ -9,135 +9,135 @@ use JKingWeb\Arsse\User\Driver;
trait CommonTests { trait CommonTests {
function setUp() { function setUp() {
$this->clearData(); $this->clearData();
$conf = new Conf(); $conf = new Conf();
$conf->userDriver = $this->drv; $conf->userDriver = $this->drv;
$conf->userAuthPreferHTTP = true; $conf->userAuthPreferHTTP = true;
Data::$conf = $conf; Data::$conf = $conf;
Data::$db = new Database(); Data::$db = new Database();
Data::$user = new User(); Data::$user = new User();
Data::$user->authorizationEnabled(false); Data::$user->authorizationEnabled(false);
$_SERVER['PHP_AUTH_USER'] = self::USER1; $_SERVER['PHP_AUTH_USER'] = self::USER1;
$_SERVER['PHP_AUTH_PW'] = "secret"; $_SERVER['PHP_AUTH_PW'] = "secret";
// call the additional setup method if it exists // call the additional setup method if it exists
if(method_exists($this, "setUpSeries")) $this->setUpSeries(); if(method_exists($this, "setUpSeries")) $this->setUpSeries();
} }
function tearDown() { function tearDown() {
$this->clearData(); $this->clearData();
// call the additional teardiwn method if it exists // call the additional teardiwn method if it exists
if(method_exists($this, "tearDownSeries")) $this->tearDownSeries(); if(method_exists($this, "tearDownSeries")) $this->tearDownSeries();
} }
function testListUsers() { function testListUsers() {
$this->assertCount(0,Data::$user->list()); $this->assertCount(0,Data::$user->list());
} }
function testCheckIfAUserDoesNotExist() { function testCheckIfAUserDoesNotExist() {
$this->assertFalse(Data::$user->exists(self::USER1)); $this->assertFalse(Data::$user->exists(self::USER1));
} }
function testAddAUser() { function testAddAUser() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
$this->assertCount(1,Data::$user->list()); $this->assertCount(1,Data::$user->list());
} }
function testCheckIfAUserDoesExist() { function testCheckIfAUserDoesExist() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
$this->assertTrue(Data::$user->exists(self::USER1)); $this->assertTrue(Data::$user->exists(self::USER1));
} }
function testAddADuplicateUser() { function testAddADuplicateUser() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
$this->assertException("alreadyExists", "User"); $this->assertException("alreadyExists", "User");
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
} }
function testAddMultipleUsers() { function testAddMultipleUsers() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
Data::$user->add(self::USER2, ""); Data::$user->add(self::USER2, "");
$this->assertCount(2,Data::$user->list()); $this->assertCount(2,Data::$user->list());
} }
function testRemoveAUser() { function testRemoveAUser() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
$this->assertCount(1,Data::$user->list()); $this->assertCount(1,Data::$user->list());
Data::$user->remove(self::USER1); Data::$user->remove(self::USER1);
$this->assertCount(0,Data::$user->list()); $this->assertCount(0,Data::$user->list());
} }
function testRemoveAMissingUser() { function testRemoveAMissingUser() {
$this->assertException("doesNotExist", "User"); $this->assertException("doesNotExist", "User");
Data::$user->remove(self::USER1); Data::$user->remove(self::USER1);
} }
function testAuthenticateAUser() { function testAuthenticateAUser() {
$_SERVER['PHP_AUTH_USER'] = self::USER1; $_SERVER['PHP_AUTH_USER'] = self::USER1;
$_SERVER['PHP_AUTH_PW'] = "secret"; $_SERVER['PHP_AUTH_PW'] = "secret";
Data::$user->add(self::USER1, "secret"); Data::$user->add(self::USER1, "secret");
Data::$user->add(self::USER2, ""); Data::$user->add(self::USER2, "");
$this->assertTrue(Data::$user->auth()); $this->assertTrue(Data::$user->auth());
$this->assertTrue(Data::$user->auth(self::USER1, "secret")); $this->assertTrue(Data::$user->auth(self::USER1, "secret"));
$this->assertFalse(Data::$user->auth(self::USER1, "superman")); $this->assertFalse(Data::$user->auth(self::USER1, "superman"));
$this->assertTrue(Data::$user->auth(self::USER2, "")); $this->assertTrue(Data::$user->auth(self::USER2, ""));
} }
function testChangeAPassword() { function testChangeAPassword() {
Data::$user->add(self::USER1, "secret"); Data::$user->add(self::USER1, "secret");
$this->assertEquals("superman", Data::$user->passwordSet(self::USER1, "superman")); $this->assertEquals("superman", Data::$user->passwordSet(self::USER1, "superman"));
$this->assertTrue(Data::$user->auth(self::USER1, "superman")); $this->assertTrue(Data::$user->auth(self::USER1, "superman"));
$this->assertFalse(Data::$user->auth(self::USER1, "secret")); $this->assertFalse(Data::$user->auth(self::USER1, "secret"));
$this->assertEquals("", Data::$user->passwordSet(self::USER1, "")); $this->assertEquals("", Data::$user->passwordSet(self::USER1, ""));
$this->assertTrue(Data::$user->auth(self::USER1, "")); $this->assertTrue(Data::$user->auth(self::USER1, ""));
$this->assertEquals(Data::$conf->userTempPasswordLength, strlen(Data::$user->passwordSet(self::USER1))); $this->assertEquals(Data::$conf->userTempPasswordLength, strlen(Data::$user->passwordSet(self::USER1)));
} }
function testChangeAPasswordForAMissingUser() { function testChangeAPasswordForAMissingUser() {
$this->assertException("doesNotExist", "User"); $this->assertException("doesNotExist", "User");
Data::$user->passwordSet(self::USER1, "superman"); Data::$user->passwordSet(self::USER1, "superman");
} }
function testGetThePropertiesOfAUser() { function testGetThePropertiesOfAUser() {
Data::$user->add(self::USER1, "secret"); Data::$user->add(self::USER1, "secret");
$p = Data::$user->propertiesGet(self::USER1); $p = Data::$user->propertiesGet(self::USER1);
$this->assertArrayHasKey('id', $p); $this->assertArrayHasKey('id', $p);
$this->assertArrayHasKey('name', $p); $this->assertArrayHasKey('name', $p);
$this->assertArrayHasKey('domain', $p); $this->assertArrayHasKey('domain', $p);
$this->assertArrayHasKey('rights', $p); $this->assertArrayHasKey('rights', $p);
$this->assertArrayNotHasKey('password', $p); $this->assertArrayNotHasKey('password', $p);
$this->assertEquals(self::USER1, $p['name']); $this->assertEquals(self::USER1, $p['name']);
} }
function testSetThePropertiesOfAUser() { function testSetThePropertiesOfAUser() {
$pSet = [ $pSet = [
'name' => 'John Doe', 'name' => 'John Doe',
'id' => 'invalid', 'id' => 'invalid',
'domain' => 'localhost', 'domain' => 'localhost',
'rights' => Driver::RIGHTS_GLOBAL_ADMIN, 'rights' => Driver::RIGHTS_GLOBAL_ADMIN,
'password' => 'superman', 'password' => 'superman',
]; ];
$pGet = [ $pGet = [
'name' => 'John Doe', 'name' => 'John Doe',
'id' => self::USER1, 'id' => self::USER1,
'domain' => 'example.com', 'domain' => 'example.com',
'rights' => Driver::RIGHTS_NONE, 'rights' => Driver::RIGHTS_NONE,
]; ];
Data::$user->add(self::USER1, "secret"); Data::$user->add(self::USER1, "secret");
Data::$user->propertiesSet(self::USER1, $pSet); Data::$user->propertiesSet(self::USER1, $pSet);
$p = Data::$user->propertiesGet(self::USER1); $p = Data::$user->propertiesGet(self::USER1);
$this->assertArraySubset($pGet, $p); $this->assertArraySubset($pGet, $p);
$this->assertArrayNotHasKey('password', $p); $this->assertArrayNotHasKey('password', $p);
$this->assertFalse(Data::$user->auth(self::USER1, "superman")); $this->assertFalse(Data::$user->auth(self::USER1, "superman"));
} }
function testGetTheRightsOfAUser() { function testGetTheRightsOfAUser() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
$this->assertEquals(Driver::RIGHTS_NONE, Data::$user->rightsGet(self::USER1)); $this->assertEquals(Driver::RIGHTS_NONE, Data::$user->rightsGet(self::USER1));
} }
function testSetTheRightsOfAUser() { function testSetTheRightsOfAUser() {
Data::$user->add(self::USER1, ""); Data::$user->add(self::USER1, "");
Data::$user->rightsSet(self::USER1, Driver::RIGHTS_GLOBAL_ADMIN); Data::$user->rightsSet(self::USER1, Driver::RIGHTS_GLOBAL_ADMIN);
$this->assertEquals(Driver::RIGHTS_GLOBAL_ADMIN, Data::$user->rightsGet(self::USER1)); $this->assertEquals(Driver::RIGHTS_GLOBAL_ADMIN, Data::$user->rightsGet(self::USER1));
} }
} }

6
tests/lib/User/Database.php

@ -13,7 +13,7 @@ class Database extends DriverSkeleton {
public function __construct() { public function __construct() {
} }
function userExists(string $user): bool { function userExists(string $user): bool {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
return parent::userExists($user); return parent::userExists($user);
@ -42,7 +42,7 @@ class Database extends DriverSkeleton {
return parent::userList($domain); return parent::userList($domain);
} }
} }
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string { function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
@ -70,7 +70,7 @@ class Database extends DriverSkeleton {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
return parent::userRightsGet($user); return parent::userRightsGet($user);
} }
function userRightsSet(string $user, int $level): bool { function userRightsSet(string $user, int $level): bool {
if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); if(!Data::$user->authorize($user, __FUNCTION__)) throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);

6
tests/lib/User/DriverExternalMock.php

@ -44,7 +44,7 @@ class DriverExternalMock extends DriverSkeleton implements Driver {
if(password_verify($password, $this->db[$user]['password'])) return true; if(password_verify($password, $this->db[$user]['password'])) return true;
return false; return false;
} }
function userExists(string $user): bool { function userExists(string $user): bool {
return parent::userExists($user); return parent::userExists($user);
} }
@ -67,7 +67,7 @@ class DriverExternalMock extends DriverSkeleton implements Driver {
return parent::userList($domain); return parent::userList($domain);
} }
} }
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string { function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
if($newPassword===null) $newPassword = (new PassGen)->length(Data::$conf->userTempPasswordLength)->get(); if($newPassword===null) $newPassword = (new PassGen)->length(Data::$conf->userTempPasswordLength)->get();
@ -89,7 +89,7 @@ class DriverExternalMock extends DriverSkeleton implements Driver {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
return parent::userRightsGet($user); return parent::userRightsGet($user);
} }
function userRightsSet(string $user, int $level): bool { function userRightsSet(string $user, int $level): bool {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]); if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
return parent::userRightsSet($user, $level); return parent::userRightsSet($user, $level);

10
tests/lib/User/DriverSkeleton.php

@ -9,9 +9,9 @@ use PasswordGenerator\Generator as PassGen;
abstract class DriverSkeleton { abstract class DriverSkeleton {
protected $db = []; protected $db = [];
function userExists(string $user): bool { function userExists(string $user): bool {
return array_key_exists($user, $this->db); return array_key_exists($user, $this->db);
} }
@ -41,7 +41,7 @@ abstract class DriverSkeleton {
}); });
} }
} }
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string { function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
$this->db[$user]['password'] = password_hash($newPassword, \PASSWORD_DEFAULT); $this->db[$user]['password'] = password_hash($newPassword, \PASSWORD_DEFAULT);
return $newPassword; return $newPassword;
@ -60,7 +60,7 @@ abstract class DriverSkeleton {
function userRightsGet(string $user): int { function userRightsGet(string $user): int {
return $this->db[$user]['rights']; return $this->db[$user]['rights'];
} }
function userRightsSet(string $user, int $level): bool { function userRightsSet(string $user, int $level): bool {
$this->db[$user]['rights'] = $level; $this->db[$user]['rights'] = $level;
return true; return true;

Loading…
Cancel
Save