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

2
lib/Db/Result.php

@ -12,7 +12,7 @@ interface Result extends \Iterator {
function getRow();
function getAll(): array;
function getValue();
function changes();
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_MISMATCH = 20;
const BINDINGS = [
"null" => \SQLITE3_NULL,
"integer" => \SQLITE3_INTEGER,
"float" => \SQLITE3_FLOAT,
"date" => \SQLITE3_TEXT,
"time" => \SQLITE3_TEXT,
"datetime" => \SQLITE3_TEXT,
"binary" => \SQLITE3_BLOB,
"string" => \SQLITE3_TEXT,
"boolean" => \SQLITE3_INTEGER,
"null" => \SQLITE3_NULL,
"integer" => \SQLITE3_INTEGER,
"float" => \SQLITE3_FLOAT,
"date" => \SQLITE3_TEXT,
"time" => \SQLITE3_TEXT,
"datetime" => \SQLITE3_TEXT,
"binary" => \SQLITE3_BLOB,
"string" => \SQLITE3_TEXT,
"boolean" => \SQLITE3_INTEGER,
];
protected $db;
protected $st;
protected $types;

48
lib/Db/Statement.php

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

2
lib/REST.php

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

4
lib/REST/AbstractHandler.php

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

2
lib/REST/Handler.php

@ -4,5 +4,5 @@ namespace JKingWeb\Arsse\REST;
interface Handler {
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 = [];
}
// 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
if(preg_match("<^/(items|folders|feeds|cleanup|version|status|user)(?:/([^/]+))?(?:/([^/]+))?(?:/([^/]+))?/?$>", $req->path, $url)) {
// clean up the path

40
lib/REST/NextCloudNews/Versions.php

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

104
lib/REST/Request.php

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

28
lib/REST/Response.php

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

12
lib/User.php

@ -9,7 +9,7 @@ class User {
protected $authz = true;
protected $authzSupported = 0;
protected $actor = [];
static public function listDrivers(): array {
$sep = \DIRECTORY_SEPARATOR;
$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;
return true;
}
public function credentials(): array {
if(Data::$conf->userAuthPreferHTTP) {
return $this->credentialsHTTP();
@ -142,7 +142,7 @@ class User {
public function driverFunctions(string $function = null) {
return $this->u->driverFunctions($function);
}
public function list(string $domain = null): array {
$func = "userList";
switch($this->u->driverFunctions($func)) {
@ -166,7 +166,7 @@ class User {
$this->authz = $setting;
return $setting;
}
public function exists(string $user): bool {
$func = "userExists";
switch($this->u->driverFunctions($func)) {
@ -321,7 +321,7 @@ class User {
return User\Driver::RIGHTS_NONE;
}
}
public function rightsSet(string $user, int $level): bool {
$func = "userRightsSet";
switch($this->u->driverFunctions($func)) {
@ -346,7 +346,7 @@ class User {
throw new User\ExceptionNotImplemented("notImplemented", ["action" => $func, "user" => $user]);
}
}
// FIXME: stubs
public function challenge(): 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;
use JKingWeb\Arsse\Data;
trait InternalFunctions {
trait InternalFunctions {
protected $actor = [];
public function __construct() {
@ -32,7 +32,7 @@ trait InternalFunctions {
function userList(string $domain = null): array {
return $this->db->userList($domain);
}
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
return $this->db->userPasswordSet($user, $newPassword);
}
@ -48,8 +48,8 @@ trait InternalFunctions {
function userRightsGet(string $user): int {
return $this->db->userRightsGet($user);
}
function userRightsSet(string $user, int $level): bool {
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 {
use Test\Tools;
static $vfs;
static $path;
@ -30,11 +30,11 @@ class TestConf extends \PHPUnit\Framework\TestCase {
self::$vfs = null;
$this->clearData();
}
function testLoadDefaultValues() {
$this->assertInstanceOf(Conf::class, new Conf());
}
/**
* @depends testLoadDefaultValues
*/

486
tests/Db/SQLite3/TestDbDriverSQLite3.php

@ -7,279 +7,279 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools;
protected $data;
protected $drv;
protected $drv;
function setUp() {
$this->clearData();
$conf = new Conf();
$conf->dbDriver = Db\SQLite3\Driver::class;
$conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook');
Data::$conf = $conf;
$this->drv = new Db\SQLite3\Driver(true);
$this->clearData();
$conf = new Conf();
$conf->dbDriver = Db\SQLite3\Driver::class;
$conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook');
Data::$conf = $conf;
$this->drv = new Db\SQLite3\Driver(true);
}
function tearDown() {
unset($this->drv);
unlink(Data::$conf->dbSQLite3File);
$this->clearData();
unlink(Data::$conf->dbSQLite3File);
$this->clearData();
}
function testFetchDriverName() {
$class = Data::$conf->dbDriver;
$this->assertTrue(strlen($class::driverName()) > 0);
}
function testExecAValidStatement() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)"));
}
function testFetchDriverName() {
$class = Data::$conf->dbDriver;
$this->assertTrue(strlen($class::driverName()) > 0);
}
function testExecAnInvalidStatement() {
$this->assertException("engineErrorGeneral", "Db");
$this->drv->exec("And the meek shall inherit the earth...");
}
function testExecAValidStatement() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)"));
}
function testExecMultipleStatements() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)"));
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->assertEquals(2112, $ch->querySingle("SELECT id from test"));
}
function testExecAnInvalidStatement() {
$this->assertException("engineErrorGeneral", "Db");
$this->drv->exec("And the meek shall inherit the earth...");
}
function testExecTimeout() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->assertException("general", "Db", "ExceptionTimeout");
$this->drv->exec("CREATE TABLE test(id integer primary key)");
}
function testExecMultipleStatements() {
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)"));
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->assertEquals(2112, $ch->querySingle("SELECT id from test"));
}
function testExecConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values(null)");
}
function testExecTimeout() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->assertException("general", "Db", "ExceptionTimeout");
$this->drv->exec("CREATE TABLE test(id integer primary key)");
}
function testExecTypeViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values('ook')");
}
function testExecConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values(null)");
}
function testMakeAValidQuery() {
$this->assertInstanceOf(Db\SQLite3\Result::class, $this->drv->query("SELECT 1"));
}
function testExecTypeViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->exec("INSERT INTO test(id) values('ook')");
}
function testMakeAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db");
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
}
function testMakeAValidQuery() {
$this->assertInstanceOf(Db\SQLite3\Result::class, $this->drv->query("SELECT 1"));
}
function testMakeAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db");
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
}
function testQueryTimeout() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->assertException("general", "Db", "ExceptionTimeout");
$this->drv->query("CREATE TABLE test(id integer primary key)");
}
function testQueryTimeout() {
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$ch->exec("BEGIN EXCLUSIVE TRANSACTION");
$this->assertException("general", "Db", "ExceptionTimeout");
$this->drv->query("CREATE TABLE test(id integer primary key)");
}
function testQueryConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values(null)");
}
function testQueryConstraintViolation() {
$this->drv->exec("CREATE TABLE test(id integer not null)");
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values(null)");
}
function testQueryTypeViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values('ook')");
}
function testQueryTypeViolation() {
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->assertException("typeViolation", "Db", "ExceptionInput");
$this->drv->query("INSERT INTO test(id) values('ook')");
}
function testPrepareAValidQuery() {
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
$this->assertInstanceOf(Db\SQLite3\Statement::class, $s);
}
function testPrepareAValidQuery() {
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
$this->assertInstanceOf(Db\SQLite3\Statement::class, $s);
}
function testPrepareAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db");
$s = $this->drv->prepare("This is an invalid query", "int", "int");
}
function testPrepareAnInvalidQuery() {
$this->assertException("engineErrorGeneral", "Db");
$s = $this->drv->prepare("This is an invalid query", "int", "int");
}
function testBeginTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testBeginTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testCommitTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select));
}
function testCommitTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select));
}
function testRollbackTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testRollbackTransaction() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testBeginChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testBeginChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testCommitChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(2, $ch->querySingle($select));
}
function testCommitChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(2, $ch->querySingle($select));
}
function testRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testPartiallyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select));
}
function testPartiallyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit();
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(1, $ch->querySingle($select));
}
function testFullyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(true);
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testFullyRollbackChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->rollback(true);
$this->assertEquals(0, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
}
function testFullyCommitChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(true);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(2, $ch->querySingle($select));
}
function testFullyCommitChainedTransactions() {
$select = "SELECT count(*) FROM test";
$insert = "INSERT INTO test(id) values(null)";
$ch = new \SQLite3(Data::$conf->dbSQLite3File);
$this->drv->exec("CREATE TABLE test(id integer primary key)");
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(1, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->begin();
$this->drv->query($insert);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(0, $ch->querySingle($select));
$this->drv->commit(true);
$this->assertEquals(2, $this->drv->query($select)->getValue());
$this->assertEquals(2, $ch->querySingle($select));
}
function testFetchSchemaVersion() {
$this->assertSame(0, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=1");
$this->assertSame(1, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=2");
$this->assertSame(2, $this->drv->schemaVersion());
function testFetchSchemaVersion() {
$this->assertSame(0, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=1");
$this->assertSame(1, $this->drv->schemaVersion());
$this->drv->exec("PRAGMA user_version=2");
$this->assertSame(2, $this->drv->schemaVersion());
}
}
function testManipulateAdvisoryLock() {
$this->assertTrue($this->drv->unlock());
$this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked());
$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->isLocked());
$this->assertFalse($this->drv->lock());
$this->drv->exec("PRAGMA user_version=0");
$this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked());
$this->drv->exec("PRAGMA user_version=1");
$this->assertTrue($this->drv->isLocked());
$this->assertTrue($this->drv->unlock());
$this->assertFalse($this->drv->isLocked());
}
function testManipulateAdvisoryLock() {
$this->assertTrue($this->drv->unlock());
$this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked());
$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->isLocked());
$this->assertFalse($this->drv->lock());
$this->drv->exec("PRAGMA user_version=0");
$this->assertFalse($this->drv->isLocked());
$this->assertTrue($this->drv->lock());
$this->assertFalse($this->drv->isLocked());
$this->drv->exec("PRAGMA user_version=1");
$this->assertTrue($this->drv->isLocked());
$this->assertTrue($this->drv->unlock());
$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;
protected $c;
static protected $imp = Db\SQLite3\Statement::class;
static protected $imp = Db\SQLite3\Statement::class;
function setUp() {
date_default_timezone_set("UTC");
date_default_timezone_set("UTC");
$c = new \SQLite3(":memory:");
$c->enableExceptions(true);
$this->c = $c;
}
function tearDown() {
try {$this->s->close();} catch(\Exception $e) {}
try {$this->s->close();} catch(\Exception $e) {}
$this->c->close();
unset($this->c);
}
protected function checkBinding($input, array $expectations) {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$s = new self::$imp($this->c, $nativeStatement);
$types = array_unique(Statement::TYPES);
foreach($types as $type) {
$s->rebindArray([$type]);
$val = $s->runArray([$input])->getRow()['value'];
$this->assertSame($expectations[$type], $val, "Type $type failed comparison.");
}
}
protected function checkBinding($input, array $expectations) {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$s = new self::$imp($this->c, $nativeStatement);
$types = array_unique(Statement::TYPES);
foreach($types as $type) {
$s->rebindArray([$type]);
$val = $s->runArray([$input])->getRow()['value'];
$this->assertSame($expectations[$type], $val, "Type $type failed comparison.");
}
}
function testConstructStatement() {
function testConstructStatement() {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$this->assertInstanceOf(Statement::class, new Db\SQLite3\Statement($this->c, $nativeStatement));
}
function testBindMissingValue() {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$s = new self::$imp($this->c, $nativeStatement);
$val = $s->runArray()->getRow()['value'];
$this->assertSame(null, $val);
}
$this->assertInstanceOf(Statement::class, new Db\SQLite3\Statement($this->c, $nativeStatement));
}
function testBindMissingValue() {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$s = new self::$imp($this->c, $nativeStatement);
$val = $s->runArray()->getRow()['value'];
$this->assertSame(null, $val);
}
function testBindMultipleValues() {
$exp = [
'one' => 1,
'two' => 2,
];
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two");
$s = new self::$imp($this->c, $nativeStatement, ["int", "int"]);
$val = $s->runArray([1,2])->getRow();
$this->assertSame($exp, $val);
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two");
$s = new self::$imp($this->c, $nativeStatement, ["int", "int"]);
$val = $s->runArray([1,2])->getRow();
$this->assertSame($exp, $val);
}
function testBindRecursively() {
function testBindRecursively() {
$exp = [
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
'four' => 4,
];
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four");
$s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]);
$val = $s->runArray([1, [2, 3], 4])->getRow();
$this->assertSame($exp, $val);
}
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four");
$s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]);
$val = $s->runArray([1, [2, 3], 4])->getRow();
$this->assertSame($exp, $val);
}
function testBindWithoutType() {
$nativeStatement = $this->c->prepare("SELECT ? as value");
$this->assertException("paramTypeMissing", "Db");
$s = new self::$imp($this->c, $nativeStatement, []);
$s->runArray([1]);
$this->assertException("paramTypeMissing", "Db");
$s = new self::$imp($this->c, $nativeStatement, []);
$s->runArray([1]);
}
function testViolateConstraint() {
$this->c->exec("CREATE TABLE test(id integer not null)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["int"]);
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$s->runArray([null]);
}
function testViolateConstraint() {
$this->c->exec("CREATE TABLE test(id integer not null)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["int"]);
$this->assertException("constraintViolation", "Db", "ExceptionInput");
$s->runArray([null]);
}
function testMismatchTypes() {
$this->c->exec("CREATE TABLE test(id integer primary key)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["str"]);
$this->assertException("typeViolation", "Db", "ExceptionInput");
$s->runArray(['ook']);
}
function testMismatchTypes() {
$this->c->exec("CREATE TABLE test(id integer primary key)");
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)");
$s = new self::$imp($this->c, $nativeStatement, ["str"]);
$this->assertException("typeViolation", "Db", "ExceptionInput");
$s->runArray(['ook']);
}
}

122
tests/Db/SQLite3/TestDbUpdateSQLite3.php

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

6
tests/Lang/testLangComplex.php

@ -19,7 +19,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
$this->l->set("ja");
$this->assertArrayNotHasKey('Test.absentText', $this->l->dump());
}
/**
* @depends testLazyLoad
*/
@ -29,7 +29,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
$this->assertEquals("ja", $this->l->get());
$this->assertEquals("en", $this->l->get(true));
}
function testLoadCascadeOfFiles() {
$this->l->set("ja", true);
$this->assertEquals("de", $this->l->set("de", true));
@ -44,7 +44,7 @@ class TestLangComplex extends \PHPUnit\Framework\TestCase {
function testLoadSubtag() {
$this->assertEquals("en_ca", $this->l->set("en_ca", true));
}
function testFetchAMessage() {
$this->l->set("de", true);
$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 {
use Test\Tools;
protected $h;
protected $h;
function setUp() {
$this->clearData();
function setUp() {
$this->clearData();
// create a mock user manager
Data::$user = Phake::mock(User::class);
Phake::when(Data::$user)->authHTTP->thenReturn(true);
Data::$user->id = "john.doe@example.com";
// create a mock database interface
Data::$db = Phake::mock(Database::Class);
$this->h = new REST\NextCloudNews\V1_2();
}
Phake::when(Data::$user)->authHTTP->thenReturn(true);
Data::$user->id = "john.doe@example.com";
// create a mock database interface
Data::$db = Phake::mock(Database::Class);
$this->h = new REST\NextCloudNews\V1_2();
}
function tearDown() {
$this->clearData();
}
function tearDown() {
$this->clearData();
}
function testRespondToInvalidPaths() {
$errs = [
404 => [
['GET', "/"],
['PUT', "/"],
['POST', "/"],
['DELETE', "/"],
['GET', "/folders/1/invalid"],
['PUT', "/folders/1/invalid"],
['POST', "/folders/1/invalid"],
['DELETE', "/folders/1/invalid"],
['GET', "/version/invalid"],
['PUT', "/version/invalid"],
['POST', "/version/invalid"],
['DELETE', "/version/invalid"],
],
405 => [
'GET' => [
['PUT', "/version"],
['POST', "/version"],
['DELETE', "/version"],
],
'GET, POST' => [
['PUT', "/folders"],
['DELETE', "/folders"],
],
'PUT, DELETE' => [
['GET', "/folders/1"],
['POST', "/folders/1"],
],
],
];
foreach($errs[404] as $req) {
$exp = new Response(404);
list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404.");
}
foreach($errs[405] as $allow => $cases) {
$exp = new Response(405, "", "", ['Allow: '.$allow]);
foreach($cases as $req) {
list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405.");
}
}
}
function testRespondToInvalidPaths() {
$errs = [
404 => [
['GET', "/"],
['PUT', "/"],
['POST', "/"],
['DELETE', "/"],
['GET', "/folders/1/invalid"],
['PUT', "/folders/1/invalid"],
['POST', "/folders/1/invalid"],
['DELETE', "/folders/1/invalid"],
['GET', "/version/invalid"],
['PUT', "/version/invalid"],
['POST', "/version/invalid"],
['DELETE', "/version/invalid"],
],
405 => [
'GET' => [
['PUT', "/version"],
['POST', "/version"],
['DELETE', "/version"],
],
'GET, POST' => [
['PUT', "/folders"],
['DELETE', "/folders"],
],
'PUT, DELETE' => [
['GET', "/folders/1"],
['POST', "/folders/1"],
],
],
];
foreach($errs[404] as $req) {
$exp = new Response(404);
list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 404.");
}
foreach($errs[405] as $allow => $cases) {
$exp = new Response(405, "", "", ['Allow: '.$allow]);
foreach($cases as $req) {
list($method, $path) = $req;
$this->assertEquals($exp, $this->h->dispatch(new Request($method, $path)), "$method call to $path did not return 405.");
}
}
}
function testListFolders() {
$list = [
['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 12, 'name' => "Hardware", 'parent' => null],
];
Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list));
$exp = new Response(200, ['folders' => $list]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
}
function testListFolders() {
$list = [
['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 12, 'name' => "Hardware", 'parent' => null],
];
Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list));
$exp = new Response(200, ['folders' => $list]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
}
function testAddAFolder() {
$in = [
["name" => "Software"],
["name" => "Hardware"],
];
$out = [
['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 2, 'name' => "Hardware", 'parent' => null],
];
// 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[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, 2)->thenReturn($out[1]);
// 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, ['name' => ""])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => " "])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
// correctly add two folders, using different means
$exp = new Response(200, ['folders' => [$out[0]]]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json')));
$exp = new Response(200, ['folders' => [$out[1]]]);
$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[1]);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 1);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 2);
// test bad folder names
$exp = new Response(422);
$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')));
// try adding the same two folders again
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software")));
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json')));
}
function testAddAFolder() {
$in = [
["name" => "Software"],
["name" => "Hardware"],
];
$out = [
['id' => 1, 'name' => "Software", 'parent' => null],
['id' => 2, 'name' => "Hardware", 'parent' => null],
];
// 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[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, 2)->thenReturn($out[1]);
// 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, ['name' => ""])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("missing"));
Phake::when(Data::$db)->folderAdd(Data::$user->id, ['name' => " "])->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("whitespace"));
// correctly add two folders, using different means
$exp = new Response(200, ['folders' => [$out[0]]]);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[0]), 'application/json')));
$exp = new Response(200, ['folders' => [$out[1]]]);
$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[1]);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 1);
Phake::verify(Data::$db)->folderPropertiesGet(Data::$user->id, 2);
// test bad folder names
$exp = new Response(422);
$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')));
// try adding the same two folders again
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders?name=Software")));
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("POST", "/folders", json_encode($in[1]), 'application/json')));
}
function testRemoveAFolder() {
Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing"));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
// fail on the second invocation because it no longer exists
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
Phake::verify(Data::$db, Phake::times(2))->folderRemove(Data::$user->id, 1);
// use a non-integer folder ID
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/invalid")));
}
function testRemoveAFolder() {
Phake::when(Data::$db)->folderRemove(Data::$user->id, 1)->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\Db\ExceptionInput("idMissing"));
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
// fail on the second invocation because it no longer exists
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/1")));
Phake::verify(Data::$db, Phake::times(2))->folderRemove(Data::$user->id, 1);
// use a non-integer folder ID
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("DELETE", "/folders/invalid")));
}
function testRenameAFolder() {
$in = [
["name" => "Software"],
["name" => "Software"],
["name" => ""],
["name" => " "],
[],
];
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, 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[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
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json')));
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json')));
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json')));
}
function testRenameAFolder() {
$in = [
["name" => "Software"],
["name" => "Software"],
["name" => ""],
["name" => " "],
[],
];
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, 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[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
$exp = new Response(204);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[0]), 'application/json')));
$exp = new Response(409);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/2", json_encode($in[1]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[2]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[3]), 'application/json')));
$exp = new Response(422);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/1", json_encode($in[4]), 'application/json')));
$exp = new Response(404);
$this->assertEquals($exp, $this->h->dispatch(new Request("PUT", "/folders/3", json_encode($in[0]), 'application/json')));
}
function testRetrieveServerVersion() {
$exp = new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/version")));
}
function testRetrieveServerVersion() {
$exp = new Response(200, ['version' => \JKingWeb\Arsse\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 {
use Test\Tools;
function setUp() {
$this->clearData();
}
function setUp() {
$this->clearData();
}
function testFetchVersionList() {
$exp = new Response(200, ['apiLevels' => ['v1-2']]);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$req = new Request("GET", "");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$req = new Request("GET", "/?id=1827");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
function testFetchVersionList() {
$exp = new Response(200, ['apiLevels' => ['v1-2']]);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$req = new Request("GET", "");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
$req = new Request("GET", "/?id=1827");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
function testUseIncorrectMethod() {
$exp = new Response(405);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("POST", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
function testUseIncorrectMethod() {
$exp = new Response(405);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("POST", "/");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
function testUseIncorrectPath() {
$exp = new Response(404);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/ook");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
function testUseIncorrectPath() {
$exp = new Response(404);
$h = new Rest\NextCloudNews\Versions();
$req = new Request("GET", "/ook");
$res = $h->dispatch($req);
$this->assertEquals($exp, $res);
}
}

556
tests/User/TestAuthorization.php

@ -5,297 +5,297 @@ namespace JKingWeb\Arsse;
class TestAuthorization extends \PHPUnit\Framework\TestCase {
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 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',
"",
];
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,
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) {
$this->clearData();
$conf = new Conf();
$conf->userDriver = $drv;
$conf->userAuthPreferHTTP = true;
$conf->userComposeNames = true;
Data::$conf = $conf;
if($db !== null) {
Data::$db = new $db();
}
Data::$user = new User();
Data::$user->authorizationEnabled(false);
foreach(self::USERS as $user => $level) {
Data::$user->add($user, "");
Data::$user->rightsSet($user, $level);
}
Data::$user->authorizationEnabled(true);
}
$this->clearData();
$conf = new Conf();
$conf->userDriver = $drv;
$conf->userAuthPreferHTTP = true;
$conf->userComposeNames = true;
Data::$conf = $conf;
if($db !== null) {
Data::$db = new $db();
}
Data::$user = new User();
Data::$user->authorizationEnabled(false);
foreach(self::USERS as $user => $level) {
Data::$user->add($user, "");
Data::$user->rightsSet($user, $level);
}
Data::$user->authorizationEnabled(true);
}
function tearDown() {
$this->clearData();
}
function tearDown() {
$this->clearData();
}
function testSelfActionLogic() {
foreach(array_keys(self::USERS) as $user) {
Data::$user->auth($user, "");
// 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, "userRemove"), "User $user could not act for themselves.");
}
}
function testSelfActionLogic() {
foreach(array_keys(self::USERS) as $user) {
Data::$user->auth($user, "");
// 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, "userRemove"), "User $user could not act for themselves.");
}
}
function testRegularUserLogic() {
foreach(self::USERS as $actor => $rights) {
if($rights != User\Driver::RIGHTS_NONE) continue;
Data::$user->auth($actor, "");
foreach(array_keys(self::USERS) as $affected) {
// regular users should only be able to act for themselves
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} 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, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
}
// they should never be able to set rights
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.");
}
}
// they should not be able to list users
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.");
}
}
}
function testRegularUserLogic() {
foreach(self::USERS as $actor => $rights) {
if($rights != User\Driver::RIGHTS_NONE) continue;
Data::$user->auth($actor, "");
foreach(array_keys(self::USERS) as $affected) {
// regular users should only be able to act for themselves
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} 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, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
}
// they should never be able to set rights
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.");
}
}
// they should not be able to list users
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.");
}
}
}
function testDomainManagerLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain managers should be able to check any user on the same domain
if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else {
$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
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
}
}
}
}
function testDomainManagerLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain managers should be able to check any user on the same domain
if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else {
$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
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
}
}
}
}
function testDomainAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_ADMIN) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
$allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN];
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain admins should be able to check any user on the same domain
if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else {
$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
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
}
}
}
}
function testDomainAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_DOMAIN_ADMIN) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
$allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN];
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// domain admins should be able to check any user on the same domain
if($actorDomain==$affectedDomain) {
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
} else {
$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
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
foreach(self::DOMAINS as $domain) {
if($domain=="@".$actorDomain) {
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
} else {
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
}
}
}
}
function testGlobalManagerLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// 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.");
// they should only be able to act for regular users
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
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.");
}
}
}
function testGlobalManagerLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_MANAGER) continue;
$actorDomain = substr($actor,strrpos($actor,"@")+1);
Data::$user->auth($actor, "");
foreach(self::USERS as $affected => $affectedRights) {
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
// 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.");
// they should only be able to act for regular users
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.");
} else {
$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
foreach(self::LEVELS as $level) {
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.");
} else {
$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
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.");
}
}
}
function testGlobalAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_ADMIN) continue;
Data::$user->auth($actor, "");
// global admins can do anything
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
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.");
}
}
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.");
}
}
}
function testGlobalAdministratorLogic() {
foreach(self::USERS as $actor => $actorRights) {
if($actorRights != User\Driver::RIGHTS_GLOBAL_ADMIN) continue;
Data::$user->auth($actor, "");
// global admins can do anything
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
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.");
}
}
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.");
}
}
}
function testInvalidLevelLogic() {
foreach(self::USERS as $actor => $rights) {
if(in_array($rights, self::LEVELS)) continue;
Data::$user->auth($actor, "");
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
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} 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, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
}
// they should never be able to set rights
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.");
}
}
// they should not be able to list users
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.");
}
}
}
function testInvalidLevelLogic() {
foreach(self::USERS as $actor => $rights) {
if(in_array($rights, self::LEVELS)) continue;
Data::$user->auth($actor, "");
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
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, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
} 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, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
}
// they should never be able to set rights
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.");
}
}
// they should not be able to list users
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.");
}
}
}
function testInternalExceptionLogic() {
$tests = [
// methods of User class to test, with parameters besides affected user
'exists' => [],
'remove' => [],
'add' => [''],
'passwordSet' => [''],
'propertiesGet' => [],
'propertiesSet' => [[]],
'rightsGet' => [],
'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN],
'list' => [],
];
// try first with a global admin (there should be no exception)
Data::$user->auth("gadm@example.com", "");
$this->assertCount(0, $this->checkExceptions("user@example.org", $tests));
// next try with a regular user acting on another user (everything should fail)
Data::$user->auth("user@example.com", "");
$this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests));
}
function testInternalExceptionLogic() {
$tests = [
// methods of User class to test, with parameters besides affected user
'exists' => [],
'remove' => [],
'add' => [''],
'passwordSet' => [''],
'propertiesGet' => [],
'propertiesSet' => [[]],
'rightsGet' => [],
'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN],
'list' => [],
];
// try first with a global admin (there should be no exception)
Data::$user->auth("gadm@example.com", "");
$this->assertCount(0, $this->checkExceptions("user@example.org", $tests));
// next try with a regular user acting on another user (everything should fail)
Data::$user->auth("user@example.com", "");
$this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests));
}
function testExternalExceptionLogic() {
// set up the test for an external driver
$this->setUp(Test\User\DriverExternalMock::class, Test\User\Database::class);
// run the previous test with the external driver set up
$this->testInternalExceptionLogic();
}
function testExternalExceptionLogic() {
// set up the test for an external driver
$this->setUp(Test\User\DriverExternalMock::class, Test\User\Database::class);
// run the previous test with the external driver set up
$this->testInternalExceptionLogic();
}
// meat of testInternalExceptionLogic and testExternalExceptionLogic
// 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 {
$err = [];
foreach($tests as $func => $args) {
// list method does not take an affected user, so do not unshift for that one
if($func != "list") array_unshift($args, $user);
try {
call_user_func_array(array(Data::$user, $func), $args);
} catch(User\ExceptionAuthz $e) {
$err[] = $func;
}
}
return $err;
}
// meat of testInternalExceptionLogic and testExternalExceptionLogic
// 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 {
$err = [];
foreach($tests as $func => $args) {
// list method does not take an affected user, so do not unshift for that one
if($func != "list") array_unshift($args, $user);
try {
call_user_func_array(array(Data::$user, $func), $args);
} catch(User\ExceptionAuthz $e) {
$err[] = $func;
}
}
return $err;
}
}

8
tests/User/TestUserInternalDriver.php

@ -5,9 +5,9 @@ namespace JKingWeb\Arsse;
class TestUserInternalDriver extends \PHPUnit\Framework\TestCase {
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 {
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 {
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() {
Data::$db = null;
}
Data::$db = null;
}
}

138
tests/lib/Database/Setup.php

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

404
tests/lib/Db/BindingTests.php

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

252
tests/lib/User/CommonTests.php

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

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;
return false;
}
function userExists(string $user): bool {
return parent::userExists($user);
}
@ -67,7 +67,7 @@ class DriverExternalMock extends DriverSkeleton implements Driver {
return parent::userList($domain);
}
}
function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
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]);
return parent::userRightsGet($user);
}
function userRightsSet(string $user, int $level): bool {
if(!$this->userExists($user)) throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
return parent::userRightsSet($user, $level);

10
tests/lib/User/DriverSkeleton.php

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

Loading…
Cancel
Save