mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Merge branch 'master' into microsub
This commit is contained in:
commit
8d1451d26c
144 changed files with 6450 additions and 5262 deletions
62
.php_cs.dist
62
.php_cs.dist
|
@ -3,6 +3,7 @@
|
||||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
* See LICENSE and AUTHORS files for details */
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
const BASE = __DIR__.DIRECTORY_SEPARATOR;
|
const BASE = __DIR__.DIRECTORY_SEPARATOR;
|
||||||
|
@ -15,10 +16,63 @@ $paths = [
|
||||||
BASE."tests",
|
BASE."tests",
|
||||||
];
|
];
|
||||||
$rules = [
|
$rules = [
|
||||||
|
// house rules where PSR series is silent
|
||||||
|
'align_multiline_comment' => ['comment_type' => "phpdocs_only"],
|
||||||
|
'array_syntax' => ['syntax' => "short"],
|
||||||
|
'binary_operator_spaces' => [
|
||||||
|
'default' => "single_space",
|
||||||
|
'operators' => ['=>' => "align_single_space"],
|
||||||
|
],
|
||||||
|
'cast_spaces' => ['space' => "single"],
|
||||||
|
'concat_space' => ['spacing' => "none"],
|
||||||
|
'list_syntax' => ['syntax' => "short"],
|
||||||
|
'magic_constant_casing' => true,
|
||||||
|
'magic_method_casing' => true,
|
||||||
|
'modernize_types_casting' => true,
|
||||||
|
'native_function_casing' => true,
|
||||||
|
'native_function_type_declaration_casing' => true,
|
||||||
|
'no_binary_string' => true,
|
||||||
|
'no_blank_lines_after_phpdoc' => true,
|
||||||
|
'no_empty_comment' => true,
|
||||||
|
'no_empty_phpdoc' => true,
|
||||||
|
'no_extra_blank_lines' => true, // this could probably use more configuration
|
||||||
|
'no_mixed_echo_print' => ['use' => "echo"],
|
||||||
|
'no_short_bool_cast' => true,
|
||||||
|
'no_trailing_comma_in_singleline_array' => true,
|
||||||
|
'no_unneeded_control_parentheses' => true,
|
||||||
|
'no_unneeded_curly_braces' => true,
|
||||||
|
'no_unused_imports' => true,
|
||||||
|
'no_whitespace_before_comma_in_array' => true,
|
||||||
|
'normalize_index_brace' => true,
|
||||||
|
'object_operator_without_whitespace' => true,
|
||||||
|
'pow_to_exponentiation' => true,
|
||||||
|
'set_type_to_cast' => true,
|
||||||
|
'standardize_not_equals' => true,
|
||||||
|
'trailing_comma_in_multiline_array' => true,
|
||||||
|
'unary_operator_spaces' => true,
|
||||||
|
'yoda_style' => false,
|
||||||
|
// PSR standard to apply
|
||||||
'@PSR2' => true,
|
'@PSR2' => true,
|
||||||
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
|
// PSR-12 rules; php-cs-fixer does not yet support PSR-12 natively
|
||||||
'function_declaration' => ['closure_function_spacing' => "none"],
|
'compact_nullable_typehint' => true,
|
||||||
];
|
'declare_equal_normalize' => ['space' => "none"],
|
||||||
|
'function_typehint_space' => true,
|
||||||
|
'lowercase_cast' => true,
|
||||||
|
'lowercase_static_reference' => true,
|
||||||
|
'no_alternative_syntax' => true,
|
||||||
|
'no_empty_statement' => true,
|
||||||
|
'no_leading_import_slash' => true,
|
||||||
|
'no_leading_namespace_whitespace' => true,
|
||||||
|
'no_whitespace_in_blank_line' => true,
|
||||||
|
'return_type_declaration' => ['space_before' => "none"],
|
||||||
|
'single_trait_insert_per_statement' => true,
|
||||||
|
'short_scalar_cast' => true,
|
||||||
|
'visibility_required' => ['elements' => ["const", "property", "method"]],
|
||||||
|
// house exceptions to PSR rules
|
||||||
|
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
|
||||||
|
'function_declaration' => ['closure_function_spacing' => "none"],
|
||||||
|
'new_with_braces' => false, // no option to specify absence of braces
|
||||||
|
];
|
||||||
|
|
||||||
$finder = \PhpCsFixer\Finder::create();
|
$finder = \PhpCsFixer\Finder::create();
|
||||||
foreach ($paths as $path) {
|
foreach ($paths as $path) {
|
||||||
|
@ -28,4 +82,4 @@ foreach ($paths as $path) {
|
||||||
$finder = $finder->in($path);
|
$finder = $finder->in($path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return \PhpCsFixer\Config::create()->setRules($rules)->setFinder($finder);
|
return \PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);
|
||||||
|
|
26
CHANGELOG
26
CHANGELOG
|
@ -1,3 +1,23 @@
|
||||||
|
Version 0.8.5 (2020-10-27)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Relax Fever HTTP correctness, to fix some clients
|
||||||
|
- Add the QUERY_STRING FastCGI parameter to the sample Nginx configuration
|
||||||
|
|
||||||
|
Version 0.8.4 (2020-09-09)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Don't crash updating feeds cached without ETag (regression since 0.8.3)
|
||||||
|
|
||||||
|
Version 0.8.3 (2020-02-16)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Officially require PHP 7.1 (accidentally required since version 0.8.0)
|
||||||
|
- Various internal changes pursuant to use of PHP 7.1
|
||||||
|
|
||||||
Version 0.8.2 (2019-12-07)
|
Version 0.8.2 (2019-12-07)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -24,7 +44,7 @@ Version 0.8.0 (2019-07-26)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
- Support for the Fever protocol (see README.md for details)
|
- Support for the Fever protocol (see manual for details)
|
||||||
- Command line functionality for clearing a password, disabling the account
|
- Command line functionality for clearing a password, disabling the account
|
||||||
- Command line options for dealing with Fever passwords
|
- Command line options for dealing with Fever passwords
|
||||||
- Command line functionality for importing and exporting OPML
|
- Command line functionality for importing and exporting OPML
|
||||||
|
@ -116,7 +136,7 @@ Version 0.4.0 (2018-10-26)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
- Support for HTTP authentication in Tiny Tiny RSS (see README.md for details)
|
- Support for HTTP authentication in Tiny Tiny RSS (see manual for details)
|
||||||
- New userHTTPAuthRequired and userSessionEnforced settings
|
- New userHTTPAuthRequired and userSessionEnforced settings
|
||||||
|
|
||||||
Version 0.3.1 (2018-07-22)
|
Version 0.3.1 (2018-07-22)
|
||||||
|
@ -162,7 +182,7 @@ Version 0.2.0 (2017-11-30)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
- Support for the Tiny Tiny RSS protocol (see README.md for details)
|
- Support for the Tiny Tiny RSS protocol (see manual for details)
|
||||||
- Support for HTTP OPTIONS requests in all protocols
|
- Support for HTTP OPTIONS requests in all protocols
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
|
@ -6,7 +6,7 @@ Information on how to install and use the software can be found in [the manual](
|
||||||
|
|
||||||
# Installing from source
|
# Installing from source
|
||||||
|
|
||||||
The main repository for The Arsse can be found at [code.mensbeam.com](https://code.mensbeam.com/MensBeam/arsse/), with a mirror also available [at GitHub](https://github.com/mensbeam/arsse/). The main repository is preferred, as the GitHub mirror can sometimes be out of date.
|
The main repository for The Arsse can be found at [code.mensbeam.com](https://code.mensbeam.com/MensBeam/arsse/), with a mirror also available [at GitHub](https://github.com/mensbeam/arsse/). The GitHub mirror does not accept bug reports, but the two should otherwise be equivalent.
|
||||||
|
|
||||||
[Composer](https://getcomposer.org/) is required to manage PHP dependencies. After cloning the repository or downloading a source code tarball, running `composer install` will download all the required dependencies, and will advise if any PHP extensions need to be installed. If not installing as a programming environment, running `composer install --no-dev` is recommended.
|
[Composer](https://getcomposer.org/) is required to manage PHP dependencies. After cloning the repository or downloading a source code tarball, running `composer install` will download all the required dependencies, and will advise if any PHP extensions need to be installed. If not installing as a programming environment, running `composer install --no-dev` is recommended.
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ There is also a `test:quick` Robo task which excludes slower tests, and a `test:
|
||||||
|
|
||||||
### Test coverage
|
### Test coverage
|
||||||
|
|
||||||
Computing the coverage of tests can be done by running `./robo coverage`, after which an HTML-format coverage report will be written to `/tests/coverage/`. Either [Xdebug](https://xdebug.org) or [phpdbg](https://php.net/manual/en/book.phpdbg.php) is required for this. Xdebug is generally recommended as it is better maintained, though phpdbg is significantly faster. If using Xdebug, the extension need not be enabled globally; PHPUnit will enable it when needed.
|
Computing the coverage of tests can be done by running `./robo coverage`, after which an HTML-format coverage report will be written to `/tests/coverage/`. Either [PCOV](https://github.com/krakjoe/pcov), [Xdebug](https://xdebug.org), or [phpdbg](https://php.net/manual/en/book.phpdbg.php) is required for this. PCOV is generally recommended as it is faster than Xdebug; phpdbg is faster still, but less accurate. If using either PCOV or Xdebug, the extension need not be enabled globally; PHPUnit will enable it when needed.
|
||||||
|
|
||||||
## Enforcing coding style
|
## Enforcing coding style
|
||||||
|
|
||||||
|
|
53
RoboFile.php
53
RoboFile.php
|
@ -24,7 +24,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* ./robo test --testsuite TTRSS --exclude-group slow --testdox
|
* ./robo test --testsuite TTRSS --exclude-group slow --testdox
|
||||||
*
|
*
|
||||||
* Please see the PHPUnit documentation for available options.
|
* Please see the PHPUnit documentation for available options.
|
||||||
*/
|
*/
|
||||||
public function test(array $args): Result {
|
public function test(array $args): Result {
|
||||||
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
|
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
*
|
*
|
||||||
* This includes pedantic tests which may help to identify problems.
|
* This includes pedantic tests which may help to identify problems.
|
||||||
* See help for the "test" task for more details.
|
* See help for the "test" task for more details.
|
||||||
*/
|
*/
|
||||||
public function testFull(array $args): Result {
|
public function testFull(array $args): Result {
|
||||||
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
|
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* Runs a quick subset of the test suite
|
* Runs a quick subset of the test suite
|
||||||
*
|
*
|
||||||
* See help for the "test" task for more details.
|
* See help for the "test" task for more details.
|
||||||
*/
|
*/
|
||||||
public function testQuick(array $args): Result {
|
public function testQuick(array $args): Result {
|
||||||
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
|
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
|
||||||
}
|
}
|
||||||
|
@ -53,10 +53,10 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* tests/coverage/. Additional reports may be produced by passing
|
* tests/coverage/. Additional reports may be produced by passing
|
||||||
* arguments to this task as one would to PHPUnit.
|
* arguments to this task as one would to PHPUnit.
|
||||||
*
|
*
|
||||||
* Robo first tries to use phpdbg and will fall back to Xdebug if available.
|
* Robo first tries to use pcov and will fall back first to xdebug then
|
||||||
* Because Xdebug slows down non-coverage tasks, however, phpdbg is highly
|
* phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
|
||||||
* recommended if debugging facilities are not otherwise needed.
|
* only need to be present in the extension load path to be used.
|
||||||
*/
|
*/
|
||||||
public function coverage(array $args): Result {
|
public function coverage(array $args): Result {
|
||||||
// run tests with code coverage reporting enabled
|
// run tests with code coverage reporting enabled
|
||||||
$exec = $this->findCoverageEngine();
|
$exec = $this->findCoverageEngine();
|
||||||
|
@ -71,7 +71,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* run all tests which may cover code.
|
* run all tests which may cover code.
|
||||||
*
|
*
|
||||||
* See also help for the "coverage" task for more details.
|
* See also help for the "coverage" task for more details.
|
||||||
*/
|
*/
|
||||||
public function coverageFull(array $args): Result {
|
public function coverageFull(array $args): Result {
|
||||||
// run tests with code coverage reporting enabled
|
// run tests with code coverage reporting enabled
|
||||||
$exec = $this->findCoverageEngine();
|
$exec = $this->findCoverageEngine();
|
||||||
|
@ -89,17 +89,30 @@ class RoboFile extends \Robo\Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function findCoverageEngine(): string {
|
protected function findCoverageEngine(): string {
|
||||||
if (IS_WIN) {
|
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
|
||||||
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
|
$ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
|
||||||
$dbg = file_exists($dbg) ? $dbg : "";
|
$php = escapeshellarg(\PHP_BINARY);
|
||||||
|
$code = escapeshellarg(BASE."lib");
|
||||||
|
if (extension_loaded("pcov")) {
|
||||||
|
return "$php -d pcov.enabled=1 -d pcov.directory=$code";
|
||||||
|
} elseif (extension_loaded("xdebug")) {
|
||||||
|
return $php;
|
||||||
|
} elseif (file_exists($dir."pcov.$ext")) {
|
||||||
|
return "$php -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
|
||||||
|
} elseif (file_exists($dir."pcov.$ext")) {
|
||||||
|
return "$php -d zend_extension=xdebug.$ext";
|
||||||
} else {
|
} else {
|
||||||
$dbg = trim(`which phpdbg 2>/dev/null`);
|
if (IS_WIN) {
|
||||||
}
|
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
|
||||||
if ($dbg) {
|
$dbg = file_exists($dbg) ? $dbg : "";
|
||||||
return escapeshellarg($dbg)." -qrr";
|
} else {
|
||||||
} else {
|
$dbg = trim(`which phpdbg 2>/dev/null`);
|
||||||
$ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
|
}
|
||||||
return escapeshellarg(\PHP_BINARY)." -d zend_extension=xdebug.$ext";
|
if ($dbg) {
|
||||||
|
return escapeshellarg($dbg)." -qrr";
|
||||||
|
} else {
|
||||||
|
return $php;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +121,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
return $all ? ">$hole 2>&1" : "2>$hole";
|
return $all ? ">$hole 2>&1" : "2>$hole";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function runTests(string $executor, string $set, array $args) : Result {
|
protected function runTests(string $executor, string $set, array $args): Result {
|
||||||
switch ($set) {
|
switch ($set) {
|
||||||
case "typical":
|
case "typical":
|
||||||
$set = ["--exclude-group", "optional"];
|
$set = ["--exclude-group", "optional"];
|
||||||
|
@ -140,7 +153,7 @@ class RoboFile extends \Robo\Tasks {
|
||||||
* Note that while it is possible to re-package old versions, the resultant tarball
|
* Note that while it is possible to re-package old versions, the resultant tarball
|
||||||
* may not be equivalent due to subsequent changes in the exclude list, or because
|
* may not be equivalent due to subsequent changes in the exclude list, or because
|
||||||
* of new tooling.
|
* of new tooling.
|
||||||
*/
|
*/
|
||||||
public function package(string $version = null): Result {
|
public function package(string $version = null): Result {
|
||||||
// establish which commit to package
|
// establish which commit to package
|
||||||
$version = $version ?? $this->askDefault("Commit to package:", "HEAD");
|
$version = $version ?? $this->askDefault("Commit to package:", "HEAD");
|
||||||
|
|
24
UPGRADING
24
UPGRADING
|
@ -11,6 +11,30 @@ usually prudent:
|
||||||
`composer install -o --no-dev`
|
`composer install -o --no-dev`
|
||||||
|
|
||||||
|
|
||||||
|
Upgrading from 0.8.4 to 0.8.5
|
||||||
|
=============================
|
||||||
|
|
||||||
|
- The sample configuration for Nginx has changed, to correct the omission of
|
||||||
|
the QUERY_STRING FastCGI parameter in those passed to PHP. The omission
|
||||||
|
affects the Fever protocol in particular (the parameter is required for
|
||||||
|
Fever to function at all), though it could potentially affect some
|
||||||
|
Nextcloud News clients as well
|
||||||
|
|
||||||
|
|
||||||
|
Upgrading from 0.8.2 to 0.8.3
|
||||||
|
=============================
|
||||||
|
|
||||||
|
- PHP 7.1 is now required
|
||||||
|
- The following Composer dependencies have been added:
|
||||||
|
- nicolus/picofeed
|
||||||
|
- laminas/laminas-diactoros
|
||||||
|
- laminas/laminas-httphandlerrunner
|
||||||
|
- The following Composer dependencies have been removed:
|
||||||
|
- p3k/picofeed
|
||||||
|
- zendframework/zend-diactoros
|
||||||
|
- zendframework/zend-httphandlerrunner
|
||||||
|
|
||||||
|
|
||||||
Upgrading from 0.8.1 to 0.8.2
|
Upgrading from 0.8.1 to 0.8.2
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
* See LICENSE and AUTHORS files for details */
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
const BASE = __DIR__.DIRECTORY_SEPARATOR;
|
const BASE = __DIR__.DIRECTORY_SEPARATOR;
|
||||||
|
@ -13,7 +14,6 @@ ignore_user_abort(true);
|
||||||
ini_set("memory_limit", "-1");
|
ini_set("memory_limit", "-1");
|
||||||
ini_set("max_execution_time", "0");
|
ini_set("max_execution_time", "0");
|
||||||
|
|
||||||
|
|
||||||
if (\PHP_SAPI === "cli") {
|
if (\PHP_SAPI === "cli") {
|
||||||
// initialize the CLI; this automatically handles --help and --version
|
// initialize the CLI; this automatically handles --help and --version
|
||||||
$cli = new CLI;
|
$cli = new CLI;
|
||||||
|
@ -25,7 +25,7 @@ if (\PHP_SAPI === "cli") {
|
||||||
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
|
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
|
||||||
Arsse::load($conf);
|
Arsse::load($conf);
|
||||||
// handle Web requests
|
// handle Web requests
|
||||||
$emitter = new \Zend\HttpHandlerRunner\Emitter\SapiEmitter;
|
$emitter = new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||||
$response = (new REST)->dispatch();
|
$response = (new REST)->dispatch();
|
||||||
$emitter->emit($response);
|
$emitter->emit($response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,21 +18,26 @@
|
||||||
|
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "7.*",
|
"php": "^7.1",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-hash": "*",
|
"ext-hash": "*",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"p3k/picofeed": "0.1.*",
|
"nicolus/picofeed": "^0.1.43",
|
||||||
"hosteurope/password-generator": "1.*",
|
"hosteurope/password-generator": "1.*",
|
||||||
"docopt/docopt": "1.*",
|
"docopt/docopt": "1.*",
|
||||||
"jkingweb/druuid": "3.*",
|
"jkingweb/druuid": "3.*",
|
||||||
"zendframework/zend-diactoros": "2.*",
|
"laminas/laminas-diactoros": "2.*",
|
||||||
"zendframework/zend-httphandlerrunner": "1.*"
|
"laminas/laminas-httphandlerrunner": "1.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "*"
|
"bamarni/composer-bin-plugin": "*"
|
||||||
},
|
},
|
||||||
|
"config": {
|
||||||
|
"platform": {
|
||||||
|
"php": "7.1.33"
|
||||||
|
}
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"post-install-cmd": ["@composer bin all install"],
|
"post-install-cmd": ["@composer bin all install"],
|
||||||
"post-update-cmd": ["@composer bin all update"]
|
"post-update-cmd": ["@composer bin all update"]
|
||||||
|
|
957
composer.lock
generated
957
composer.lock
generated
File diff suppressed because it is too large
Load diff
1
dist/nginx.conf
vendored
1
dist/nginx.conf
vendored
|
@ -19,6 +19,7 @@ server {
|
||||||
fastcgi_param CONTENT_TYPE $content_type;
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
fastcgi_param CONTENT_LENGTH $content_length;
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
fastcgi_param REQUEST_URI $uri;
|
fastcgi_param REQUEST_URI $uri;
|
||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
fastcgi_param HTTPS $https if_not_empty;
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
fastcgi_param REMOTE_USER $remote_user;
|
fastcgi_param REMOTE_USER $remote_user;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
The Arsse has the following requirements:
|
The Arsse has the following requirements:
|
||||||
|
|
||||||
- A Linux server running Nginx or Apache 2.4 (tested on Ubuntu 16.04 and 18.04)
|
- A Linux server running Nginx or Apache 2.4
|
||||||
- PHP 7.0.7 or later with the following extensions:
|
- PHP 7.1.0 or later with the following extensions:
|
||||||
- [intl](http://php.net/manual/en/book.intl.php), [json](http://php.net/manual/en/book.json.php), [hash](http://php.net/manual/en/book.hash.php), and [dom](http://php.net/manual/en/book.dom.php)
|
- [intl](http://php.net/manual/en/book.intl.php), [json](http://php.net/manual/en/book.json.php), [hash](http://php.net/manual/en/book.hash.php), and [dom](http://php.net/manual/en/book.dom.php)
|
||||||
- [simplexml](http://php.net/manual/en/book.simplexml.php), and [iconv](http://php.net/manual/en/book.iconv.php)
|
- [simplexml](http://php.net/manual/en/book.simplexml.php), and [iconv](http://php.net/manual/en/book.iconv.php)
|
||||||
- One of:
|
- One of:
|
||||||
- [sqlite3](http://php.net/manual/en/book.sqlite3.php) or [pdo_sqlite](http://php.net/manual/en/ref.pdo-sqlite.php) for SQLite databases
|
- [sqlite3](http://php.net/manual/en/book.sqlite3.php) or [pdo_sqlite](http://php.net/manual/en/ref.pdo-sqlite.php) for SQLite databases
|
||||||
- [pgsql](http://php.net/manual/en/book.pgsql.php) or [pdo_pgsql](http://php.net/manual/en/ref.pdo-pgsql.php) for PostgreSQL 10 or later databases
|
- [pgsql](http://php.net/manual/en/book.pgsql.php) or [pdo_pgsql](http://php.net/manual/en/ref.pdo-pgsql.php) for PostgreSQL 10 or later databases
|
||||||
- [mysqli](http://php.net/manual/en/book.mysqli.php) or [pdo_mysql](http://php.net/manual/en/ref.pdo-mysql.php) for MySQL/Percona 8.0.11 or later databases
|
- [mysqli](http://php.net/manual/en/book.mysqli.php) or [pdo_mysql](http://php.net/manual/en/ref.pdo-mysql.php) for MySQL/Percona 8.0.11 or later databases
|
||||||
|
- [curl](http://php.net/manual/en/book.curl.php) (optional)
|
||||||
- Privileges either to create and run systemd services, or to run cron jobs
|
- Privileges either to create and run systemd services, or to run cron jobs
|
||||||
|
|
||||||
Instructions for how to satisfy the PHP extension requirements for Debian systems are included in the next section.
|
Instructions for how to satisfy the PHP extension requirements for Debian systems are included in the next section.
|
||||||
|
|
|
@ -30,6 +30,7 @@ server {
|
||||||
fastcgi_param CONTENT_TYPE $content_type;
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
fastcgi_param CONTENT_LENGTH $content_length;
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
fastcgi_param REQUEST_URI $uri;
|
fastcgi_param REQUEST_URI $uri;
|
||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
fastcgi_param HTTPS $https if_not_empty;
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
fastcgi_param REMOTE_USER $remote_user;
|
fastcgi_param REMOTE_USER $remote_user;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
|
|
||||||
While MySQL can be used as a database for The Arsse, this is **not recommended** due to MySQL's technical limitations. It is fully functional, but may fail with some newsfeeds where other database systems do not. Additionally, it is particularly important before upgrading from one version of The Arsse to the next to back up your database: a failure in a database upgrade can corrupt your database much more easily than when using other database systems.
|
While MySQL can be used as a database for The Arsse, this is **not recommended** due to MySQL's technical limitations. It is fully functional, but may fail with some newsfeeds where other database systems do not. Additionally, it is particularly important before upgrading from one version of The Arsse to the next to back up your database: a failure in a database upgrade can corrupt your database much more easily than when using other database systems.
|
||||||
|
|
||||||
You are therefore strongly advised not to use MySQL. Though our MySQL test suite ensures functionally identical behaviour to SQLite and PostgreSQL for the supplied test data in a default MySQL configuration, there are [many other subtle ways in which it can fail](https://grimoire.ca/mysql/choose-something-else), and we do not have the manpower to account for most of these with certainty.
|
You are therefore strongly advised not to use MySQL. Though our MySQL test suite ensures functionally identical behaviour to SQLite and PostgreSQL for the supplied test data in a default MySQL configuration, there are [many other subtle ways in which it can fail](https://web.archive.org/web/20190929090114/https://grimoire.ca/mysql/choose-something-else), and we do not have the manpower to account for most of these with certainty.
|
||||||
|
|
||||||
Also please note that as of this writing MariaDB cannot be used in place of MySQL as it lacks features of MySQL 8 which The Arsse requires. The awkwardly-named [_Percona Server for MySQL_](https://www.percona.com/software/mysql-database/percona-server), on the other hand, will work.
|
Also please note that as of this writing MariaDB cannot be used in place of MySQL as it lacks features of MySQL 8 which The Arsse requires (see the [relevant MariaDB issue](https://jira.mariadb.org/browse/MDEV-18511) for details). The awkwardly-named [_Percona Server for MySQL_](https://www.percona.com/software/mysql-database/percona-server), on the other hand, will work.
|
||||||
|
|
||||||
# Set-up
|
# Set-up
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,17 @@ The Arsse does not at this time have any first party clients. However, because T
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://apps.apple.com/app/id1449412482">Reeder</a></td>
|
<td><a href="https://gitlab.com/news-flash/news_flash_gtk">NewsFlash</a></td>
|
||||||
|
<td>Linux</td>
|
||||||
|
<td class="N">✘</td>
|
||||||
|
<td class="N">✘</td>
|
||||||
|
<td class="Y">✔</td>
|
||||||
|
<td>
|
||||||
|
<p>Successor to FeedReader.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://reeder.app/">Reeder</a></td>
|
||||||
<td>macOS</td>
|
<td>macOS</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
|
@ -155,7 +165,7 @@ The Arsse does not at this time have any first party clients. However, because T
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://apps.apple.com/app/id1449412357">Reeder</a></td>
|
<td><a href="https://reeder.app/">Reeder</a></td>
|
||||||
<td>iOS</td>
|
<td>iOS</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
|
@ -185,7 +195,7 @@ The Arsse does not at this time have any first party clients. However, because T
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://apps.apple.com/app/id1252376153">Unread</a></td>
|
<td><a href="https://www.goldenhillsoftware.com/unread/">Unread</a></td>
|
||||||
<td>iOS</td>
|
<td>iOS</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
|
@ -225,7 +235,17 @@ The Arsse does not at this time have any first party clients. However, because T
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://apps.apple.com/app/id588726889">ReadKit</a></td>
|
<td><a href="https://open-store.io/app/newsie.martinferretti">Newsie</a></td>
|
||||||
|
<td>Ubuntu Touch</td>
|
||||||
|
<td class="Y">✔</td>
|
||||||
|
<td class="N">✘</td>
|
||||||
|
<td class="N">✘</td>
|
||||||
|
<td>
|
||||||
|
<p>Does not support HTTP authentication.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://readkitapp.com/">ReadKit</a></td>
|
||||||
<td>macOS</td>
|
<td>macOS</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
<td class="N">✘</td>
|
<td class="N">✘</td>
|
||||||
|
|
2
docs/theme/arsse/arsse.css
vendored
2
docs/theme/arsse/arsse.css
vendored
File diff suppressed because one or more lines are too long
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
abstract class AbstractException extends \Exception {
|
abstract class AbstractException extends \Exception {
|
||||||
const CODES = [
|
public const CODES = [
|
||||||
"Exception.uncoded" => -1,
|
"Exception.uncoded" => -1,
|
||||||
"Exception.unknown" => 10000,
|
"Exception.unknown" => 10000,
|
||||||
"Exception.constantUnknown" => 10001,
|
"Exception.constantUnknown" => 10001,
|
||||||
|
@ -75,6 +75,7 @@ abstract class AbstractException extends \Exception {
|
||||||
"User/Exception.authFailed" => 10412,
|
"User/Exception.authFailed" => 10412,
|
||||||
"User/ExceptionAuthz.notAuthorized" => 10421,
|
"User/ExceptionAuthz.notAuthorized" => 10421,
|
||||||
"User/ExceptionSession.invalid" => 10431,
|
"User/ExceptionSession.invalid" => 10431,
|
||||||
|
"Feed/Exception.internalError" => 10500,
|
||||||
"Feed/Exception.invalidCertificate" => 10501,
|
"Feed/Exception.invalidCertificate" => 10501,
|
||||||
"Feed/Exception.invalidUrl" => 10502,
|
"Feed/Exception.invalidUrl" => 10502,
|
||||||
"Feed/Exception.maxRedirect" => 10503,
|
"Feed/Exception.maxRedirect" => 10503,
|
||||||
|
@ -82,6 +83,8 @@ abstract class AbstractException extends \Exception {
|
||||||
"Feed/Exception.timeout" => 10505,
|
"Feed/Exception.timeout" => 10505,
|
||||||
"Feed/Exception.forbidden" => 10506,
|
"Feed/Exception.forbidden" => 10506,
|
||||||
"Feed/Exception.unauthorized" => 10507,
|
"Feed/Exception.unauthorized" => 10507,
|
||||||
|
"Feed/Exception.transmissionError" => 10508,
|
||||||
|
"Feed/Exception.connectionFailed" => 10509,
|
||||||
"Feed/Exception.malformedXml" => 10511,
|
"Feed/Exception.malformedXml" => 10511,
|
||||||
"Feed/Exception.xmlEntity" => 10512,
|
"Feed/Exception.xmlEntity" => 10512,
|
||||||
"Feed/Exception.subscriptionNotFound" => 10521,
|
"Feed/Exception.subscriptionNotFound" => 10521,
|
||||||
|
|
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class Arsse {
|
class Arsse {
|
||||||
const VERSION = "0.8.2";
|
public const VERSION = "0.8.5";
|
||||||
|
|
||||||
/** @var Lang */
|
/** @var Lang */
|
||||||
public static $lang;
|
public static $lang;
|
||||||
|
@ -18,7 +18,7 @@ class Arsse {
|
||||||
/** @var User */
|
/** @var User */
|
||||||
public static $user;
|
public static $user;
|
||||||
|
|
||||||
public static function load(Conf $conf) {
|
public static function load(Conf $conf): void {
|
||||||
static::$lang = static::$lang ?? new Lang;
|
static::$lang = static::$lang ?? new Lang;
|
||||||
static::$conf = $conf;
|
static::$conf = $conf;
|
||||||
static::$lang->set($conf->lang);
|
static::$lang->set($conf->lang);
|
||||||
|
|
16
lib/CLI.php
16
lib/CLI.php
|
@ -10,7 +10,7 @@ use JKingWeb\Arsse\REST\Fever\User as Fever;
|
||||||
use JKingWeb\Arsse\ImportExport\OPML;
|
use JKingWeb\Arsse\ImportExport\OPML;
|
||||||
|
|
||||||
class CLI {
|
class CLI {
|
||||||
const USAGE = <<<USAGE_TEXT
|
public const USAGE = <<<USAGE_TEXT
|
||||||
Usage:
|
Usage:
|
||||||
arsse.php daemon
|
arsse.php daemon
|
||||||
arsse.php feed refresh-all
|
arsse.php feed refresh-all
|
||||||
|
@ -147,6 +147,7 @@ USAGE_TEXT;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
protected function loadConf(): bool {
|
protected function loadConf(): bool {
|
||||||
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
|
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
|
||||||
Arsse::load($conf);
|
Arsse::load($conf);
|
||||||
|
@ -159,7 +160,7 @@ USAGE_TEXT;
|
||||||
return ($file === "-" ? null : $file) ?? $stdinOrStdout;
|
return ($file === "-" ? null : $file) ?? $stdinOrStdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch(array $argv = null) {
|
public function dispatch(array $argv = null): int {
|
||||||
$argv = $argv ?? $_SERVER['argv'];
|
$argv = $argv ?? $_SERVER['argv'];
|
||||||
$argv0 = array_shift($argv);
|
$argv0 = array_shift($argv);
|
||||||
$args = \Docopt::handle($this->usage($argv0), [
|
$args = \Docopt::handle($this->usage($argv0), [
|
||||||
|
@ -209,7 +210,7 @@ USAGE_TEXT;
|
||||||
} // @codeCoverageIgnore
|
} // @codeCoverageIgnore
|
||||||
|
|
||||||
/** @codeCoverageIgnore */
|
/** @codeCoverageIgnore */
|
||||||
protected function logError(string $msg) {
|
protected function logError(string $msg): void {
|
||||||
fwrite(STDERR, $msg.\PHP_EOL);
|
fwrite(STDERR, $msg.\PHP_EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +220,8 @@ USAGE_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function userManage($args): int {
|
protected function userManage($args): int {
|
||||||
switch ($this->command(["add", "remove", "set-pass", "unset-pass", "list", "auth"], $args)) {
|
$cmd = $this->command(["add", "remove", "set-pass", "unset-pass", "list", "auth"], $args);
|
||||||
|
switch ($cmd) {
|
||||||
case "add":
|
case "add":
|
||||||
return $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
|
return $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
|
||||||
case "set-pass":
|
case "set-pass":
|
||||||
|
@ -247,8 +249,10 @@ USAGE_TEXT;
|
||||||
case "list":
|
case "list":
|
||||||
case "":
|
case "":
|
||||||
return $this->userList();
|
return $this->userList();
|
||||||
|
default:
|
||||||
|
throw new Exception("constantUnknown", $cmd); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
} // @codeCoverageIgnore
|
}
|
||||||
|
|
||||||
protected function userAddOrSetPassword(string $method, string $user, string $password = null, string $oldpass = null): int {
|
protected function userAddOrSetPassword(string $method, string $user, string $password = null, string $oldpass = null): int {
|
||||||
$passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1));
|
$passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1));
|
||||||
|
@ -267,7 +271,7 @@ USAGE_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function userAuthenticate(string $user, string $password, bool $fever = false): int {
|
protected function userAuthenticate(string $user, string $password, bool $fever = false): int {
|
||||||
$result = $fever ? $this->getInstance(Fever::class)->authenticate($user, $password) : Arsse::$user->auth($user, $password);
|
$result = $fever ? $this->getInstance(Fever::class)->authenticate($user, $password) : Arsse::$user->auth($user, $password);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL;
|
echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
102
lib/Conf.php
102
lib/Conf.php
|
@ -15,113 +15,113 @@ use JKingWeb\Arsse\Misc\ValueInfo as Value;
|
||||||
* All public properties are configuration parameters that may be set by the server administrator. */
|
* All public properties are configuration parameters that may be set by the server administrator. */
|
||||||
class Conf {
|
class Conf {
|
||||||
/** @var string Default language to use for logging and errors */
|
/** @var string Default language to use for logging and errors */
|
||||||
public $lang = "en";
|
public $lang = "en";
|
||||||
|
|
||||||
/** @var string The database driver to use, one of "sqlite3", "postgresql", or "mysql". A fully-qualified class name may also be used for custom drivers */
|
/** @var string The database driver to use, one of "sqlite3", "postgresql", or "mysql". A fully-qualified class name may also be used for custom drivers */
|
||||||
public $dbDriver = "sqlite3";
|
public $dbDriver = "sqlite3";
|
||||||
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
|
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
|
||||||
public $dbAutoUpdate = true;
|
public $dbAutoUpdate = true;
|
||||||
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when connecting to a database (null waits forever; not applicable to SQLite) */
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when connecting to a database (null waits forever; not applicable to SQLite) */
|
||||||
public $dbTimeoutConnect = 5.0;
|
public $dbTimeoutConnect = 5.0;
|
||||||
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when executing a database operation (null waits forever; not applicable to SQLite) */
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when executing a database operation (null waits forever; not applicable to SQLite) */
|
||||||
public $dbTimeoutExec = null;
|
public $dbTimeoutExec = null;
|
||||||
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when acquiring a database lock (null waits forever) */
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when acquiring a database lock (null waits forever) */
|
||||||
public $dbTimeoutLock = 60.0;
|
public $dbTimeoutLock = 60.0;
|
||||||
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
|
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
|
||||||
public $dbSQLite3File = null;
|
public $dbSQLite3File = null;
|
||||||
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
|
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
|
||||||
public $dbSQLite3Key = "";
|
public $dbSQLite3Key = "";
|
||||||
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLHost = "";
|
public $dbPostgreSQLHost = "";
|
||||||
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLUser = "arsse";
|
public $dbPostgreSQLUser = "arsse";
|
||||||
/** @var string Log-in password for PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Log-in password for PostgreSQL database server (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLPass = "";
|
public $dbPostgreSQLPass = "";
|
||||||
/** @var integer Listening port for PostgreSQL database server (if using PostgreSQL over TCP) */
|
/** @var integer Listening port for PostgreSQL database server (if using PostgreSQL over TCP) */
|
||||||
public $dbPostgreSQLPort = 5432;
|
public $dbPostgreSQLPort = 5432;
|
||||||
/** @var string Database name on PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Database name on PostgreSQL database server (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLDb = "arsse";
|
public $dbPostgreSQLDb = "arsse";
|
||||||
/** @var string Schema name in PostgreSQL database (if using PostgreSQL) */
|
/** @var string Schema name in PostgreSQL database (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLSchema = "";
|
public $dbPostgreSQLSchema = "";
|
||||||
/** @var string Service file entry to use (if using PostgreSQL); if using a service entry all above parameters except schema are ignored */
|
/** @var string Service file entry to use (if using PostgreSQL); if using a service entry all above parameters except schema are ignored */
|
||||||
public $dbPostgreSQLService = "";
|
public $dbPostgreSQLService = "";
|
||||||
/** @var string Host name or address of MySQL database server (if using MySQL) */
|
/** @var string Host name or address of MySQL database server (if using MySQL) */
|
||||||
public $dbMySQLHost = "localhost";
|
public $dbMySQLHost = "localhost";
|
||||||
/** @var string Log-in user name for MySQL database server (if using MySQL) */
|
/** @var string Log-in user name for MySQL database server (if using MySQL) */
|
||||||
public $dbMySQLUser = "arsse";
|
public $dbMySQLUser = "arsse";
|
||||||
/** @var string Log-in password for MySQL database server (if using MySQL) */
|
/** @var string Log-in password for MySQL database server (if using MySQL) */
|
||||||
public $dbMySQLPass = "";
|
public $dbMySQLPass = "";
|
||||||
/** @var integer Listening port for MySQL database server (if using MySQL over TCP) */
|
/** @var integer Listening port for MySQL database server (if using MySQL over TCP) */
|
||||||
public $dbMySQLPort = 3306;
|
public $dbMySQLPort = 3306;
|
||||||
/** @var string Database name on MySQL database server (if using MySQL) */
|
/** @var string Database name on MySQL database server (if using MySQL) */
|
||||||
public $dbMySQLDb = "arsse";
|
public $dbMySQLDb = "arsse";
|
||||||
/** @var string Unix domain socket or named pipe to use for MySQL when not connecting over TCP */
|
/** @var string Unix domain socket or named pipe to use for MySQL when not connecting over TCP */
|
||||||
public $dbMySQLSocket = "";
|
public $dbMySQLSocket = "";
|
||||||
|
|
||||||
/** @var string The user management driver to use, currently only "internal". A fully-qualified class name may also be used for custom drivers */
|
/** @var string The user management driver to use, currently only "internal". A fully-qualified class name may also be used for custom drivers */
|
||||||
public $userDriver = "internal";
|
public $userDriver = "internal";
|
||||||
/** @var boolean Whether users are already authenticated by the Web server before the application is executed */
|
/** @var boolean Whether users are already authenticated by the Web server before the application is executed */
|
||||||
public $userPreAuth = false;
|
public $userPreAuth = false;
|
||||||
/** @var boolean Whether to require successful HTTP authentication before processing API-level authentication for protocols which have any. Normally the Tiny Tiny RSS relies on its own session-token authentication scheme, for example */
|
/** @var boolean Whether to require successful HTTP authentication before processing API-level authentication for protocols which have any. Normally the Tiny Tiny RSS relies on its own session-token authentication scheme, for example */
|
||||||
public $userHTTPAuthRequired = false;
|
public $userHTTPAuthRequired = false;
|
||||||
/** @var integer Desired length of temporary user passwords */
|
/** @var integer Desired length of temporary user passwords */
|
||||||
public $userTempPasswordLength = 20;
|
public $userTempPasswordLength = 20;
|
||||||
/** @var boolean Whether invalid or expired API session tokens should prevent logging in when HTTP authentication is used, for protocol which implement their own authentication */
|
/** @var boolean Whether invalid or expired API session tokens should prevent logging in when HTTP authentication is used, for protocol which implement their own authentication */
|
||||||
public $userSessionEnforced = true;
|
public $userSessionEnforced = true;
|
||||||
/** @var \DateInterval Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 24 hours)
|
/** @var \DateInterval Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 24 hours)
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $userSessionTimeout = "PT24H";
|
public $userSessionTimeout = "PT24H";
|
||||||
/** @var \DateInterval Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 7 days);
|
/** @var \DateInterval Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 7 days);
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $userSessionLifetime = "P7D";
|
public $userSessionLifetime = "P7D";
|
||||||
|
|
||||||
/** @var string Feed update service driver to use, one of "serial" or "subprocess". A fully-qualified class name may also be used for custom drivers */
|
/** @var string Feed update service driver to use, one of "serial" or "subprocess". A fully-qualified class name may also be used for custom drivers */
|
||||||
public $serviceDriver = "subprocess";
|
public $serviceDriver = "subprocess";
|
||||||
/** @var \DateInterval The interval between checks for new articles, as an ISO 8601 duration
|
/** @var \DateInterval The interval between checks for new articles, as an ISO 8601 duration
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $serviceFrequency = "PT2M";
|
public $serviceFrequency = "PT2M";
|
||||||
/** @var integer Number of concurrent feed updates to perform */
|
/** @var integer Number of concurrent feed updates to perform */
|
||||||
public $serviceQueueWidth = 5;
|
public $serviceQueueWidth = 5;
|
||||||
|
|
||||||
/** @var \DateInterval Number of seconds to wait for data when fetching feeds from foreign servers */
|
/** @var \DateInterval Number of seconds to wait for data when fetching feeds from foreign servers */
|
||||||
public $fetchTimeout = 10.0;
|
public $fetchTimeout = 10.0;
|
||||||
/** @var integer Maximum size, in bytes, of data when fetching feeds from foreign servers */
|
/** @var integer Maximum size, in bytes, of data when fetching feeds from foreign servers */
|
||||||
public $fetchSizeLimit = 2 * 1024 * 1024;
|
public $fetchSizeLimit = 2 * 1024 * 1024;
|
||||||
/** @var boolean Whether to allow the possibility of fetching full article contents using an item's URL. Whether fetching will actually happen is also governed by a per-feed setting */
|
/** @var boolean Whether to allow the possibility of fetching full article contents using an item's URL. Whether fetching will actually happen is also governed by a per-feed setting */
|
||||||
public $fetchEnableScraping = true;
|
public $fetchEnableScraping = true;
|
||||||
/** @var string|null User-Agent string to use when fetching feeds from foreign servers */
|
/** @var string|null User-Agent string to use when fetching feeds from foreign servers */
|
||||||
public $fetchUserAgentString = null;
|
public $fetchUserAgentString = null;
|
||||||
|
|
||||||
/** @var \DateInterval|null When to delete a feed from the database after all its subscriptions have been deleted, as an ISO 8601 duration (default: 24 hours; null for never)
|
/** @var \DateInterval|null When to delete a feed from the database after all its subscriptions have been deleted, as an ISO 8601 duration (default: 24 hours; null for never)
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $purgeFeeds = "PT24H";
|
public $purgeFeeds = "PT24H";
|
||||||
/** @var \DateInterval|null When to delete an unstarred article in the database after it has been marked read by all users, as an ISO 8601 duration (default: 7 days; null for never)
|
/** @var \DateInterval|null When to delete an unstarred article in the database after it has been marked read by all users, as an ISO 8601 duration (default: 7 days; null for never)
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $purgeArticlesRead = "P7D";
|
public $purgeArticlesRead = "P7D";
|
||||||
/** @var \DateInterval|null When to delete an unstarred article in the database regardless of its read state, as an ISO 8601 duration (default: 21 days; null for never)
|
/** @var \DateInterval|null When to delete an unstarred article in the database regardless of its read state, as an ISO 8601 duration (default: 21 days; null for never)
|
||||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||||
public $purgeArticlesUnread = "P21D";
|
public $purgeArticlesUnread = "P21D";
|
||||||
|
|
||||||
/** @var string Application name to present to clients during authentication */
|
/** @var string Application name to present to clients during authentication */
|
||||||
public $httpRealm = "The Advanced RSS Environment";
|
public $httpRealm = "The Advanced RSS Environment";
|
||||||
/** @var string Space-separated list of origins from which to allow cross-origin resource sharing */
|
/** @var string Space-separated list of origins from which to allow cross-origin resource sharing */
|
||||||
public $httpOriginsAllowed = "*";
|
public $httpOriginsAllowed = "*";
|
||||||
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
|
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
|
||||||
public $httpOriginsDenied = "";
|
public $httpOriginsDenied = "";
|
||||||
|
|
||||||
### OBSOLETE SETTINGS
|
### OBSOLETE SETTINGS
|
||||||
|
|
||||||
/** @var \DateInterval|null (OBSOLETE) Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
|
/** @var \DateInterval|null (OBSOLETE) Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
|
||||||
public $dbSQLite3Timeout = null; // previously 60.0
|
public $dbSQLite3Timeout = null; // previously 60.0
|
||||||
|
|
||||||
const TYPE_NAMES = [
|
protected const TYPE_NAMES = [
|
||||||
Value::T_BOOL => "boolean",
|
Value::T_BOOL => "boolean",
|
||||||
Value::T_STRING => "string",
|
Value::T_STRING => "string",
|
||||||
Value::T_FLOAT => "float",
|
Value::T_FLOAT => "float",
|
||||||
VALUE::T_INT => "integer",
|
VALUE::T_INT => "integer",
|
||||||
Value::T_INTERVAL => "interval",
|
Value::T_INTERVAL => "interval",
|
||||||
];
|
];
|
||||||
const EXPECTED_TYPES = [
|
protected const EXPECTED_TYPES = [
|
||||||
'dbTimeoutExec' => "double",
|
'dbTimeoutExec' => "double",
|
||||||
'dbTimeoutLock' => "double",
|
'dbTimeoutLock' => "double",
|
||||||
'dbTimeoutConnect' => "double",
|
'dbTimeoutConnect' => "double",
|
||||||
|
@ -254,10 +254,10 @@ class Conf {
|
||||||
$match = explode("|", $match[1]);
|
$match = explode("|", $match[1]);
|
||||||
$nullable = (sizeof($match) > 1);
|
$nullable = (sizeof($match) > 1);
|
||||||
$type = [
|
$type = [
|
||||||
'string' => Value::T_STRING | Value::M_STRICT,
|
'string' => Value::T_STRING | Value::M_STRICT,
|
||||||
'integer' => Value::T_INT | Value::M_STRICT,
|
'integer' => Value::T_INT | Value::M_STRICT,
|
||||||
'boolean' => Value::T_BOOL | Value::M_STRICT,
|
'boolean' => Value::T_BOOL | Value::M_STRICT,
|
||||||
'float' => Value::T_FLOAT | Value::M_STRICT,
|
'float' => Value::T_FLOAT | Value::M_STRICT,
|
||||||
'\\DateInterval' => Value::T_INTERVAL | Value::M_LOOSE,
|
'\\DateInterval' => Value::T_INTERVAL | Value::M_LOOSE,
|
||||||
][$match[0]];
|
][$match[0]];
|
||||||
if ($nullable) {
|
if ($nullable) {
|
||||||
|
@ -283,7 +283,7 @@ class Conf {
|
||||||
// it is first converted to an interval and then converted to the numeric type if necessary
|
// it is first converted to an interval and then converted to the numeric type if necessary
|
||||||
$mode = $nullable ? Value::M_STRICT | Value::M_NULL : Value::M_STRICT;
|
$mode = $nullable ? Value::M_STRICT | Value::M_NULL : Value::M_STRICT;
|
||||||
if (is_string($value)) {
|
if (is_string($value)) {
|
||||||
$value = Value::normalize($value, Value::T_INTERVAL | $mode);
|
$value = Value::normalize($value, Value::T_INTERVAL | $mode);
|
||||||
}
|
}
|
||||||
switch (self::EXPECTED_TYPES[$key] ?? gettype($this->$key)) {
|
switch (self::EXPECTED_TYPES[$key] ?? gettype($this->$key)) {
|
||||||
case "integer":
|
case "integer":
|
||||||
|
@ -299,7 +299,7 @@ class Conf {
|
||||||
throw new Conf\Exception("ambiguousDefault", ['param' => $key]); // @codeCoverageIgnore
|
throw new Conf\Exception("ambiguousDefault", ['param' => $key]); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$value = Value::normalize($value, $typeConst);
|
$value = Value::normalize($value, $typeConst);
|
||||||
switch ($key) {
|
switch ($key) {
|
||||||
case "dbDriver":
|
case "dbDriver":
|
||||||
$driver = $driver ?? Database::DRIVER_NAMES[strtolower($value)] ?? $value;
|
$driver = $driver ?? Database::DRIVER_NAMES[strtolower($value)] ?? $value;
|
||||||
|
@ -319,7 +319,7 @@ class Conf {
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
} catch (ExceptionType $e) {
|
} catch (ExceptionType $e) {
|
||||||
$type = static::$types[$key]['const'] & ~(Value::M_STRICT | Value::M_DROP | Value::M_NULL | Value::M_ARRAY);
|
$type = static::$types[$key]['const'] & ~(Value::M_STRICT | Value::M_DROP | Value::M_NULL | Value::M_ARRAY);
|
||||||
throw new Conf\Exception("typeMismatch", ['param' => $key, 'type' => self::TYPE_NAMES[$type], 'file' => $file, 'nullable' => $nullable]);
|
throw new Conf\Exception("typeMismatch", ['param' => $key, 'type' => self::TYPE_NAMES[$type], 'file' => $file, 'nullable' => $nullable]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
162
lib/Database.php
162
lib/Database.php
|
@ -40,22 +40,22 @@ use JKingWeb\Arsse\Misc\URL;
|
||||||
class Database {
|
class Database {
|
||||||
/** The version number of the latest schema the interface is aware of */
|
/** The version number of the latest schema the interface is aware of */
|
||||||
const SCHEMA_VERSION = 7;
|
const SCHEMA_VERSION = 7;
|
||||||
/** The size of a set of values beyond which the set will be embedded into the query text */
|
|
||||||
const LIMIT_SET_SIZE = 25;
|
|
||||||
/** The length of a string in an embedded set beyond which a parameter placeholder will be used for the string */
|
|
||||||
const LIMIT_SET_STRING_LENGTH = 200;
|
|
||||||
/** Makes tag/label association change operations remove members */
|
/** Makes tag/label association change operations remove members */
|
||||||
const ASSOC_REMOVE = 0;
|
public const ASSOC_REMOVE = 0;
|
||||||
/** Makes tag/label association change operations add members */
|
/** Makes tag/label association change operations add members */
|
||||||
const ASSOC_ADD = 1;
|
public const ASSOC_ADD = 1;
|
||||||
/** Makes tag/label association change operations replace members */
|
/** Makes tag/label association change operations replace members */
|
||||||
const ASSOC_REPLACE = 2;
|
public const ASSOC_REPLACE = 2;
|
||||||
/** A map of database driver short-names and their associated class names */
|
/** A map of database driver short-names and their associated class names */
|
||||||
const DRIVER_NAMES = [
|
public const DRIVER_NAMES = [
|
||||||
'sqlite3' => \JKingWeb\Arsse\Db\SQLite3\Driver::class,
|
'sqlite3' => \JKingWeb\Arsse\Db\SQLite3\Driver::class,
|
||||||
'postgresql' => \JKingWeb\Arsse\Db\PostgreSQL\Driver::class,
|
'postgresql' => \JKingWeb\Arsse\Db\PostgreSQL\Driver::class,
|
||||||
'mysql' => \JKingWeb\Arsse\Db\MySQL\Driver::class,
|
'mysql' => \JKingWeb\Arsse\Db\MySQL\Driver::class,
|
||||||
];
|
];
|
||||||
|
/** The size of a set of values beyond which the set will be embedded into the query text */
|
||||||
|
protected const LIMIT_SET_SIZE = 25;
|
||||||
|
/** The length of a string in an embedded set beyond which a parameter placeholder will be used for the string */
|
||||||
|
protected const LIMIT_SET_STRING_LENGTH = 200;
|
||||||
|
|
||||||
/** @var Db\Driver */
|
/** @var Db\Driver */
|
||||||
public $db;
|
public $db;
|
||||||
|
@ -218,7 +218,7 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieve a value from the metadata table. If the key is not set null is returned */
|
/** Retrieve a value from the metadata table. If the key is not set null is returned */
|
||||||
public function metaGet(string $key) {
|
public function metaGet(string $key): ?string {
|
||||||
return $this->db->prepare("SELECT value from arsse_meta where \"key\" = ?", "str")->run($key)->getValue();
|
return $this->db->prepare("SELECT value from arsse_meta where \"key\" = ?", "str")->run($key)->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves the hashed password of a user */
|
/** Retrieves the hashed password of a user */
|
||||||
public function userPasswordGet(string $user) {
|
public function userPasswordGet(string $user): ?string {
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
} elseif (!$this->userExists($user)) {
|
} elseif (!$this->userExists($user)) {
|
||||||
|
@ -379,7 +379,7 @@ class Database {
|
||||||
$max = Date::add(Arsse::$conf->userSessionTimeout, $now)->getTimestamp();
|
$max = Date::add(Arsse::$conf->userSessionTimeout, $now)->getTimestamp();
|
||||||
$diff = intdiv($max - $now, 2);
|
$diff = intdiv($max - $now, 2);
|
||||||
// determine if the expiry time is less than half the session timeout into the future
|
// determine if the expiry time is less than half the session timeout into the future
|
||||||
return (($now + $diff) >= $expiry->getTimestamp());
|
return ($now + $diff) >= $expiry->getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a new token for the given user in the given class
|
/** Creates a new token for the given user in the given class
|
||||||
|
@ -500,7 +500,7 @@ class Database {
|
||||||
$q->setWhere("owner = ?", "str", $user);
|
$q->setWhere("owner = ?", "str", $user);
|
||||||
$q->setWhere("coalesce(arsse_folders.parent,0) = ?", "strict int", $parent);
|
$q->setWhere("coalesce(arsse_folders.parent,0) = ?", "strict int", $parent);
|
||||||
} else {
|
} else {
|
||||||
$q->setCTE("folders", "SELECT id from arsse_folders where owner = ? and coalesce(parent,0) = ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id", ["str", "strict int"], [$user, $parent]);
|
$q->setCTE("folders", "SELECT id from arsse_folders where owner = ? and coalesce(parent,0) = ? union all select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id", ["str", "strict int"], [$user, $parent]);
|
||||||
$q->setWhere("id in (SELECT id from folders)");
|
$q->setWhere("id in (SELECT id from folders)");
|
||||||
}
|
}
|
||||||
$q->setOrder("name");
|
$q->setOrder("name");
|
||||||
|
@ -585,10 +585,10 @@ class Database {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$valid = [
|
$valid = [
|
||||||
'name' => "str",
|
'name' => "str",
|
||||||
'parent' => "int",
|
'parent' => "int",
|
||||||
];
|
];
|
||||||
list($setClause, $setTypes, $setValues) = $this->generateSet($in, $valid);
|
[$setClause, $setTypes, $setValues] = $this->generateSet($in, $valid);
|
||||||
return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause, modified = CURRENT_TIMESTAMP where owner = ? and id = ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
|
return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause, modified = CURRENT_TIMESTAMP where owner = ? and id = ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensures an operation to rename and/or move a folder does not result in a conflict or circular dependence, and raises an exception otherwise */
|
/** Ensures an operation to rename and/or move a folder does not result in a conflict or circular dependence, and raises an exception otherwise */
|
||||||
protected function folderValidateMove(string $user, $id = null, $parent = null, string $name = null) {
|
protected function folderValidateMove(string $user, $id = null, $parent = null, string $name = null): ?int {
|
||||||
$errData = ["action" => $this->caller(), "field" => "parent", 'id' => $parent];
|
$errData = ["action" => $this->caller(), "field" => "parent", 'id' => $parent];
|
||||||
if (!$id) {
|
if (!$id) {
|
||||||
// the root cannot be moved
|
// the root cannot be moved
|
||||||
|
@ -645,7 +645,7 @@ class Database {
|
||||||
$p = $this->db->prepare(
|
$p = $this->db->prepare(
|
||||||
"WITH RECURSIVE
|
"WITH RECURSIVE
|
||||||
target as (select ? as userid, ? as source, ? as dest, ? as new_name),
|
target as (select ? as userid, ? as source, ? as dest, ? as new_name),
|
||||||
folders as (SELECT id from arsse_folders join target on owner = userid and coalesce(parent,0) = source union select arsse_folders.id as id from arsse_folders join folders on arsse_folders.parent=folders.id)
|
folders as (SELECT id from arsse_folders join target on owner = userid and coalesce(parent,0) = source union all select arsse_folders.id as id from arsse_folders join folders on arsse_folders.parent=folders.id)
|
||||||
".
|
".
|
||||||
"SELECT
|
"SELECT
|
||||||
case when ((select dest from target) is null or exists(select id from arsse_folders join target on owner = userid and coalesce(id,0) = coalesce(dest,0))) then 1 else 0 end as extant,
|
case when ((select dest from target) is null or exists(select id from arsse_folders join target on owner = userid and coalesce(id,0) = coalesce(dest,0))) then 1 else 0 end as extant,
|
||||||
|
@ -750,14 +750,14 @@ class Database {
|
||||||
$nocase = $this->db->sqlToken("nocase");
|
$nocase = $this->db->sqlToken("nocase");
|
||||||
$q->setOrder("pinned desc, coalesce(arsse_subscriptions.title, arsse_feeds.title) collate $nocase");
|
$q->setOrder("pinned desc, coalesce(arsse_subscriptions.title, arsse_feeds.title) collate $nocase");
|
||||||
// topmost folders belonging to the user
|
// topmost folders belonging to the user
|
||||||
$q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders where owner = ? and parent is null union select id,top from arsse_folders join topmost on parent=f_id", ["str"], [$user]);
|
$q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders where owner = ? and parent is null union all select id,top from arsse_folders join topmost on parent=f_id", ["str"], [$user]);
|
||||||
if ($id) {
|
if ($id) {
|
||||||
// this condition facilitates the implementation of subscriptionPropertiesGet, which would otherwise have to duplicate the complex query; it takes precedence over a specified folder
|
// this condition facilitates the implementation of subscriptionPropertiesGet, which would otherwise have to duplicate the complex query; it takes precedence over a specified folder
|
||||||
// if an ID is specified, add a suitable WHERE condition and bindings
|
// if an ID is specified, add a suitable WHERE condition and bindings
|
||||||
$q->setWhere("arsse_subscriptions.id = ?", "int", $id);
|
$q->setWhere("arsse_subscriptions.id = ?", "int", $id);
|
||||||
} elseif ($folder && $recursive) {
|
} elseif ($folder && $recursive) {
|
||||||
// if a folder is specified and we're listing recursively, add a common table expression to list it and its children so that we select from the entire subtree
|
// if a folder is specified and we're listing recursively, add a common table expression to list it and its children so that we select from the entire subtree
|
||||||
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $folder);
|
$q->setCTE("folders(folder)", "SELECT ? union all select id from arsse_folders join folders on parent = folder", "int", $folder);
|
||||||
// add a suitable WHERE condition
|
// add a suitable WHERE condition
|
||||||
$q->setWhere("folder in (select folder from folders)");
|
$q->setWhere("folder in (select folder from folders)");
|
||||||
} elseif (!$recursive) {
|
} elseif (!$recursive) {
|
||||||
|
@ -779,7 +779,7 @@ class Database {
|
||||||
$q->setWhere("owner = ?", "str", $user);
|
$q->setWhere("owner = ?", "str", $user);
|
||||||
if ($folder) {
|
if ($folder) {
|
||||||
// if the specified folder exists, add a common table expression to list it and its children so that we select from the entire subtree
|
// if the specified folder exists, add a common table expression to list it and its children so that we select from the entire subtree
|
||||||
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $folder);
|
$q->setCTE("folders(folder)", "SELECT ? union all select id from arsse_folders join folders on parent = folder", "int", $folder);
|
||||||
// add a suitable WHERE condition
|
// add a suitable WHERE condition
|
||||||
$q->setWhere("folder in (select folder from folders)");
|
$q->setWhere("folder in (select folder from folders)");
|
||||||
}
|
}
|
||||||
|
@ -882,7 +882,7 @@ class Database {
|
||||||
'order_type' => "strict int",
|
'order_type' => "strict int",
|
||||||
'pinned' => "strict bool",
|
'pinned' => "strict bool",
|
||||||
];
|
];
|
||||||
list($setClause, $setTypes, $setValues) = $this->generateSet($data, $valid);
|
[$setClause, $setTypes, $setValues] = $this->generateSet($data, $valid);
|
||||||
if (!$setClause) {
|
if (!$setClause) {
|
||||||
// if no changes would actually be applied, just return
|
// if no changes would actually be applied, just return
|
||||||
return false;
|
return false;
|
||||||
|
@ -933,7 +933,7 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the time at which any of a user's subscriptions (or a specific subscription) was last refreshed, as a DateTimeImmutable object */
|
/** Returns the time at which any of a user's subscriptions (or a specific subscription) was last refreshed, as a DateTimeImmutable object */
|
||||||
public function subscriptionRefreshed(string $user, int $id = null) {
|
public function subscriptionRefreshed(string $user, int $id = null): ?\DateTimeImmutable {
|
||||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
|
@ -1145,7 +1145,7 @@ class Database {
|
||||||
'str',
|
'str',
|
||||||
'str',
|
'str',
|
||||||
'datetime',
|
'datetime',
|
||||||
'str',
|
'strict str',
|
||||||
'datetime',
|
'datetime',
|
||||||
'int',
|
'int',
|
||||||
'int'
|
'int'
|
||||||
|
@ -1221,10 +1221,10 @@ class Database {
|
||||||
*/
|
*/
|
||||||
public function feedMatchIds(int $feedID, array $ids = [], array $hashesUT = [], array $hashesUC = [], array $hashesTC = []): Db\Result {
|
public function feedMatchIds(int $feedID, array $ids = [], array $hashesUT = [], array $hashesUC = [], array $hashesTC = []): Db\Result {
|
||||||
// compile SQL IN() clauses and necessary type bindings for the four identifier lists
|
// compile SQL IN() clauses and necessary type bindings for the four identifier lists
|
||||||
list($cId, $tId, $vId) = $this->generateIn($ids, "str");
|
[$cId, $tId, $vId] = $this->generateIn($ids, "str");
|
||||||
list($cHashUT, $tHashUT, $vHashUT) = $this->generateIn($hashesUT, "str");
|
[$cHashUT, $tHashUT, $vHashUT] = $this->generateIn($hashesUT, "str");
|
||||||
list($cHashUC, $tHashUC, $vHashUC) = $this->generateIn($hashesUC, "str");
|
[$cHashUC, $tHashUC, $vHashUC] = $this->generateIn($hashesUC, "str");
|
||||||
list($cHashTC, $tHashTC, $vHashTC) = $this->generateIn($hashesTC, "str");
|
[$cHashTC, $tHashTC, $vHashTC] = $this->generateIn($hashesTC, "str");
|
||||||
// perform the query
|
// perform the query
|
||||||
return $articles = $this->db->prepare(
|
return $articles = $this->db->prepare(
|
||||||
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed = ? and (guid in($cId) or url_title_hash in($cHashUT) or url_content_hash in($cHashUC) or title_content_hash in($cHashTC))",
|
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed = ? and (guid in($cId) or url_title_hash in($cHashUT) or url_content_hash in($cHashUC) or title_content_hash in($cHashTC))",
|
||||||
|
@ -1243,27 +1243,27 @@ class Database {
|
||||||
protected function articleColumns(): array {
|
protected function articleColumns(): array {
|
||||||
$greatest = $this->db->sqlToken("greatest");
|
$greatest = $this->db->sqlToken("greatest");
|
||||||
return [
|
return [
|
||||||
'id' => "arsse_articles.id",
|
'id' => "arsse_articles.id",
|
||||||
'edition' => "latest_editions.edition",
|
'edition' => "latest_editions.edition",
|
||||||
'url' => "arsse_articles.url",
|
'url' => "arsse_articles.url",
|
||||||
'title' => "arsse_articles.title",
|
'title' => "arsse_articles.title",
|
||||||
'author' => "arsse_articles.author",
|
'author' => "arsse_articles.author",
|
||||||
'content' => "arsse_articles.content",
|
'content' => "arsse_articles.content",
|
||||||
'guid' => "arsse_articles.guid",
|
'guid' => "arsse_articles.guid",
|
||||||
'fingerprint' => "arsse_articles.url_title_hash || ':' || arsse_articles.url_content_hash || ':' || arsse_articles.title_content_hash",
|
'fingerprint' => "arsse_articles.url_title_hash || ':' || arsse_articles.url_content_hash || ':' || arsse_articles.title_content_hash",
|
||||||
'folder' => "coalesce(arsse_subscriptions.folder,0)",
|
'folder' => "coalesce(arsse_subscriptions.folder,0)",
|
||||||
'subscription' => "arsse_subscriptions.id",
|
'subscription' => "arsse_subscriptions.id",
|
||||||
'feed' => "arsse_subscriptions.feed",
|
'feed' => "arsse_subscriptions.feed",
|
||||||
'starred' => "coalesce(arsse_marks.starred,0)",
|
'starred' => "coalesce(arsse_marks.starred,0)",
|
||||||
'unread' => "abs(coalesce(arsse_marks.read,0) - 1)",
|
'unread' => "abs(coalesce(arsse_marks.read,0) - 1)",
|
||||||
'note' => "coalesce(arsse_marks.note,'')",
|
'note' => "coalesce(arsse_marks.note,'')",
|
||||||
'published_date' => "arsse_articles.published",
|
'published_date' => "arsse_articles.published",
|
||||||
'edited_date' => "arsse_articles.edited",
|
'edited_date' => "arsse_articles.edited",
|
||||||
'modified_date' => "arsse_articles.modified",
|
'modified_date' => "arsse_articles.modified",
|
||||||
'marked_date' => "$greatest(arsse_articles.modified, coalesce(arsse_marks.modified, '0001-01-01 00:00:00'), coalesce(label_stats.modified, '0001-01-01 00:00:00'))",
|
'marked_date' => "$greatest(arsse_articles.modified, coalesce(arsse_marks.modified, '0001-01-01 00:00:00'), coalesce(label_stats.modified, '0001-01-01 00:00:00'))",
|
||||||
'subscription_title' => "coalesce(arsse_subscriptions.title, arsse_feeds.title)",
|
'subscription_title' => "coalesce(arsse_subscriptions.title, arsse_feeds.title)",
|
||||||
'media_url' => "arsse_enclosures.url",
|
'media_url' => "arsse_enclosures.url",
|
||||||
'media_type' => "arsse_enclosures.type",
|
'media_type' => "arsse_enclosures.type",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1360,7 +1360,7 @@ class Database {
|
||||||
"unread" => ["unread", "=", "bool", ""],
|
"unread" => ["unread", "=", "bool", ""],
|
||||||
"starred" => ["starred", "=", "bool", ""],
|
"starred" => ["starred", "=", "bool", ""],
|
||||||
];
|
];
|
||||||
foreach ($options as $m => list($col, $op, $type, $pair)) {
|
foreach ($options as $m => [$col, $op, $type, $pair]) {
|
||||||
if (!$context->$m()) {
|
if (!$context->$m()) {
|
||||||
// context is not being used
|
// context is not being used
|
||||||
continue;
|
continue;
|
||||||
|
@ -1369,7 +1369,7 @@ class Database {
|
||||||
if (!$context->$m) {
|
if (!$context->$m) {
|
||||||
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||||
}
|
}
|
||||||
list($clause, $types, $values) = $this->generateIn($context->$m, $type);
|
[$clause, $types, $values] = $this->generateIn($context->$m, $type);
|
||||||
$q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values);
|
$q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values);
|
||||||
} elseif ($pair && $context->$pair()) {
|
} elseif ($pair && $context->$pair()) {
|
||||||
// option is paired with another which is also being used
|
// option is paired with another which is also being used
|
||||||
|
@ -1384,7 +1384,7 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// further handle exclusionary options if specified
|
// further handle exclusionary options if specified
|
||||||
foreach ($options as $m => list($col, $op, $type, $pair)) {
|
foreach ($options as $m => [$col, $op, $type, $pair]) {
|
||||||
if (!method_exists($context->not, $m) || !$context->not->$m()) {
|
if (!method_exists($context->not, $m) || !$context->not->$m()) {
|
||||||
// context option is not being used
|
// context option is not being used
|
||||||
continue;
|
continue;
|
||||||
|
@ -1393,7 +1393,7 @@ class Database {
|
||||||
// for exclusions we don't care if the array is empty
|
// for exclusions we don't care if the array is empty
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
list($clause, $types, $values) = $this->generateIn($context->not->$m, $type);
|
[$clause, $types, $values] = $this->generateIn($context->not->$m, $type);
|
||||||
$q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values);
|
$q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values);
|
||||||
} elseif ($pair && $context->not->$pair()) {
|
} elseif ($pair && $context->not->$pair()) {
|
||||||
// option is paired with another which is also being used
|
// option is paired with another which is also being used
|
||||||
|
@ -1410,13 +1410,13 @@ class Database {
|
||||||
// handle labels and tags
|
// handle labels and tags
|
||||||
$options = [
|
$options = [
|
||||||
'label' => [
|
'label' => [
|
||||||
'match_col' => "arsse_articles.id",
|
'match_col' => "arsse_articles.id",
|
||||||
'cte_name' => "labelled",
|
'cte_name' => "labelled",
|
||||||
'cte_cols' => ["article", "label_id", "label_name"],
|
'cte_cols' => ["article", "label_id", "label_name"],
|
||||||
'cte_body' => "SELECT m.article, l.id, l.name from arsse_label_members as m join arsse_labels as l on l.id = m.label where l.owner = ? and m.assigned = 1",
|
'cte_body' => "SELECT m.article, l.id, l.name from arsse_label_members as m join arsse_labels as l on l.id = m.label where l.owner = ? and m.assigned = 1",
|
||||||
'cte_types' => ["str"],
|
'cte_types' => ["str"],
|
||||||
'cte_values' => [$user],
|
'cte_values' => [$user],
|
||||||
'options' => [
|
'options' => [
|
||||||
'label' => ['use_name' => false, 'multi' => false],
|
'label' => ['use_name' => false, 'multi' => false],
|
||||||
'labels' => ['use_name' => false, 'multi' => true],
|
'labels' => ['use_name' => false, 'multi' => true],
|
||||||
'labelName' => ['use_name' => true, 'multi' => false],
|
'labelName' => ['use_name' => true, 'multi' => false],
|
||||||
|
@ -1424,13 +1424,13 @@ class Database {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'tag' => [
|
'tag' => [
|
||||||
'match_col' => "arsse_subscriptions.id",
|
'match_col' => "arsse_subscriptions.id",
|
||||||
'cte_name' => "tagged",
|
'cte_name' => "tagged",
|
||||||
'cte_cols' => ["subscription", "tag_id", "tag_name"],
|
'cte_cols' => ["subscription", "tag_id", "tag_name"],
|
||||||
'cte_body' => "SELECT m.subscription, t.id, t.name from arsse_tag_members as m join arsse_tags as t on t.id = m.tag where t.owner = ? and m.assigned = 1",
|
'cte_body' => "SELECT m.subscription, t.id, t.name from arsse_tag_members as m join arsse_tags as t on t.id = m.tag where t.owner = ? and m.assigned = 1",
|
||||||
'cte_types' => ["str"],
|
'cte_types' => ["str"],
|
||||||
'cte_values' => [$user],
|
'cte_values' => [$user],
|
||||||
'options' => [
|
'options' => [
|
||||||
'tag' => ['use_name' => false, 'multi' => false],
|
'tag' => ['use_name' => false, 'multi' => false],
|
||||||
'tags' => ['use_name' => false, 'multi' => true],
|
'tags' => ['use_name' => false, 'multi' => true],
|
||||||
'tagName' => ['use_name' => true, 'multi' => false],
|
'tagName' => ['use_name' => true, 'multi' => false],
|
||||||
|
@ -1453,7 +1453,7 @@ class Database {
|
||||||
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
|
||||||
}
|
}
|
||||||
if ($multi) {
|
if ($multi) {
|
||||||
list($test, $types, $values) = $this->generateIn($context->$m, $named ? "str" : "int");
|
[$test, $types, $values] = $this->generateIn($context->$m, $named ? "str" : "int");
|
||||||
$test = "in ($test)";
|
$test = "in ($test)";
|
||||||
} else {
|
} else {
|
||||||
$test = "= ?";
|
$test = "= ?";
|
||||||
|
@ -1465,7 +1465,7 @@ class Database {
|
||||||
if ($context->not->$m()) {
|
if ($context->not->$m()) {
|
||||||
$seen = true;
|
$seen = true;
|
||||||
if ($multi) {
|
if ($multi) {
|
||||||
list($test, $types, $values) = $this->generateIn($context->not->$m, $named ? "str" : "int");
|
[$test, $types, $values] = $this->generateIn($context->not->$m, $named ? "str" : "int");
|
||||||
$test = "in ($test)";
|
$test = "in ($test)";
|
||||||
} else {
|
} else {
|
||||||
$test = "= ?";
|
$test = "= ?";
|
||||||
|
@ -1492,27 +1492,27 @@ class Database {
|
||||||
}
|
}
|
||||||
if ($context->folder()) {
|
if ($context->folder()) {
|
||||||
// add a common table expression to list the folder and its children so that we select from the entire subtree
|
// add a common table expression to list the folder and its children so that we select from the entire subtree
|
||||||
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on coalesce(parent,0) = folder", "int", $context->folder);
|
$q->setCTE("folders(folder)", "SELECT ? union all select id from arsse_folders join folders on coalesce(parent,0) = folder", "int", $context->folder);
|
||||||
// limit subscriptions to the listed folders
|
// limit subscriptions to the listed folders
|
||||||
$q->setWhere("coalesce(arsse_subscriptions.folder,0) in (select folder from folders)");
|
$q->setWhere("coalesce(arsse_subscriptions.folder,0) in (select folder from folders)");
|
||||||
}
|
}
|
||||||
if ($context->folders()) {
|
if ($context->folders()) {
|
||||||
list($inClause, $inTypes, $inValues) = $this->generateIn($context->folders, "int");
|
[$inClause, $inTypes, $inValues] = $this->generateIn($context->folders, "int");
|
||||||
// add a common table expression to list the folders and their children so that we select from the entire subtree
|
// add a common table expression to list the folders and their children so that we select from the entire subtree
|
||||||
$q->setCTE("folders_multi(folder)", "SELECT id as folder from (select id from (select 0 as id union select id from arsse_folders where owner = ?) as f where id in ($inClause)) as folders_multi union select id from arsse_folders join folders_multi on coalesce(parent,0) = folder", ["str", $inTypes], [$user, $inValues]);
|
$q->setCTE("folders_multi(folder)", "SELECT id as folder from (select id from (select 0 as id union all select id from arsse_folders where owner = ?) as f where id in ($inClause)) as folders_multi union select id from arsse_folders join folders_multi on coalesce(parent,0) = folder", ["str", $inTypes], [$user, $inValues]);
|
||||||
// limit subscriptions to the listed folders
|
// limit subscriptions to the listed folders
|
||||||
$q->setWhere("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_multi)");
|
$q->setWhere("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_multi)");
|
||||||
}
|
}
|
||||||
if ($context->not->folder()) {
|
if ($context->not->folder()) {
|
||||||
// add a common table expression to list the folder and its children so that we exclude from the entire subtree
|
// add a common table expression to list the folder and its children so that we exclude from the entire subtree
|
||||||
$q->setCTE("folders_excluded(folder)", "SELECT ? union select id from arsse_folders join folders_excluded on coalesce(parent,0) = folder", "int", $context->not->folder);
|
$q->setCTE("folders_excluded(folder)", "SELECT ? union all select id from arsse_folders join folders_excluded on coalesce(parent,0) = folder", "int", $context->not->folder);
|
||||||
// excluded any subscriptions in the listed folders
|
// excluded any subscriptions in the listed folders
|
||||||
$q->setWhereNot("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_excluded)");
|
$q->setWhereNot("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_excluded)");
|
||||||
}
|
}
|
||||||
if ($context->not->folders()) {
|
if ($context->not->folders()) {
|
||||||
list($inClause, $inTypes, $inValues) = $this->generateIn($context->not->folders, "int");
|
[$inClause, $inTypes, $inValues] = $this->generateIn($context->not->folders, "int");
|
||||||
// add a common table expression to list the folders and their children so that we select from the entire subtree
|
// add a common table expression to list the folders and their children so that we select from the entire subtree
|
||||||
$q->setCTE("folders_multi_excluded(folder)", "SELECT id as folder from (select id from (select 0 as id union select id from arsse_folders where owner = ?) as f where id in ($inClause)) as folders_multi_excluded union select id from arsse_folders join folders_multi_excluded on coalesce(parent,0) = folder", ["str", $inTypes], [$user, $inValues]);
|
$q->setCTE("folders_multi_excluded(folder)", "SELECT id as folder from (select id from (select 0 as id union all select id from arsse_folders where owner = ?) as f where id in ($inClause)) as folders_multi_excluded union select id from arsse_folders join folders_multi_excluded on coalesce(parent,0) = folder", ["str", $inTypes], [$user, $inValues]);
|
||||||
// limit subscriptions to the listed folders
|
// limit subscriptions to the listed folders
|
||||||
$q->setWhereNot("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_multi_excluded)");
|
$q->setWhereNot("coalesce(arsse_subscriptions.folder,0) in (select folder from folders_multi_excluded)");
|
||||||
}
|
}
|
||||||
|
@ -1624,9 +1624,9 @@ class Database {
|
||||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
$data = [
|
$data = [
|
||||||
'read' => $data['read'] ?? null,
|
'read' => $data['read'] ?? null,
|
||||||
'starred' => $data['starred'] ?? null,
|
'starred' => $data['starred'] ?? null,
|
||||||
'note' => $data['note'] ?? null,
|
'note' => $data['note'] ?? null,
|
||||||
];
|
];
|
||||||
if (!isset($data['read']) && !isset($data['starred']) && !isset($data['note'])) {
|
if (!isset($data['read']) && !isset($data['starred']) && !isset($data['note'])) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1665,7 +1665,7 @@ class Database {
|
||||||
$data = array_filter($data, function($v) {
|
$data = array_filter($data, function($v) {
|
||||||
return isset($v);
|
return isset($v);
|
||||||
});
|
});
|
||||||
list($set, $setTypes, $setValues) = $this->generateSet($data, ['starred' => "bool", 'note' => "str"]);
|
[$set, $setTypes, $setValues] = $this->generateSet($data, ['starred' => "bool", 'note' => "str"]);
|
||||||
$q->setBody("UPDATE arsse_marks set touched = 1, $set where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues);
|
$q->setBody("UPDATE arsse_marks set touched = 1, $set where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues);
|
||||||
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
|
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
|
||||||
}
|
}
|
||||||
|
@ -1689,7 +1689,7 @@ class Database {
|
||||||
$data = array_filter($data, function($v) {
|
$data = array_filter($data, function($v) {
|
||||||
return isset($v);
|
return isset($v);
|
||||||
});
|
});
|
||||||
list($set, $setTypes, $setValues) = $this->generateSet($data, ['read' => "bool", 'starred' => "bool", 'note' => "str"]);
|
[$set, $setTypes, $setValues] = $this->generateSet($data, ['read' => "bool", 'starred' => "bool", 'note' => "str"]);
|
||||||
$q->setBody("UPDATE arsse_marks set $set, modified = CURRENT_TIMESTAMP where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues);
|
$q->setBody("UPDATE arsse_marks set $set, modified = CURRENT_TIMESTAMP where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues);
|
||||||
$out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->changes();
|
$out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->changes();
|
||||||
}
|
}
|
||||||
|
@ -1861,7 +1861,7 @@ class Database {
|
||||||
public function editionArticle(int ...$edition): array {
|
public function editionArticle(int ...$edition): array {
|
||||||
$out = [];
|
$out = [];
|
||||||
$context = (new Context)->editions($edition);
|
$context = (new Context)->editions($edition);
|
||||||
list($in, $inTypes, $inValues) = $this->generateIn($context->editions, "int");
|
[$in, $inTypes, $inValues] = $this->generateIn($context->editions, "int");
|
||||||
$out = $this->db->prepare("SELECT id as edition, article from arsse_editions where id in($in)", $inTypes)->run($inValues)->getAll();
|
$out = $this->db->prepare("SELECT id as edition, article from arsse_editions where id in($in)", $inTypes)->run($inValues)->getAll();
|
||||||
return $out ? array_combine(array_column($out, "edition"), array_column($out, "article")) : [];
|
return $out ? array_combine(array_column($out, "edition"), array_column($out, "article")) : [];
|
||||||
}
|
}
|
||||||
|
@ -2019,7 +2019,7 @@ class Database {
|
||||||
$valid = [
|
$valid = [
|
||||||
'name' => "str",
|
'name' => "str",
|
||||||
];
|
];
|
||||||
list($setClause, $setTypes, $setValues) = $this->generateSet($data, $valid);
|
[$setClause, $setTypes, $setValues] = $this->generateSet($data, $valid);
|
||||||
if (!$setClause) {
|
if (!$setClause) {
|
||||||
// if no changes would actually be applied, just return
|
// if no changes would actually be applied, just return
|
||||||
return false;
|
return false;
|
||||||
|
@ -2087,7 +2087,7 @@ class Database {
|
||||||
$articles = array_column($articles, "id");
|
$articles = array_column($articles, "id");
|
||||||
}
|
}
|
||||||
// prepare up to three queries: removing requires one, adding two, and replacing three
|
// prepare up to three queries: removing requires one, adding two, and replacing three
|
||||||
list($inClause, $inTypes, $inValues) = $this->generateIn($articles, "int");
|
[$inClause, $inTypes, $inValues] = $this->generateIn($articles, "int");
|
||||||
$updateQ = "UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label = ? and assigned <> ? and article %in% ($inClause)";
|
$updateQ = "UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label = ? and assigned <> ? and article %in% ($inClause)";
|
||||||
$updateT = ["bool", "int", "bool", $inTypes];
|
$updateT = ["bool", "int", "bool", $inTypes];
|
||||||
$insertQ = "INSERT INTO arsse_label_members(label,article,subscription) SELECT ?,a.id,s.id from arsse_articles as a join arsse_subscriptions as s on a.feed = s.feed where s.owner = ? and a.id not in (select article from arsse_label_members where label = ?) and a.id in ($inClause)";
|
$insertQ = "INSERT INTO arsse_label_members(label,article,subscription) SELECT ?,a.id,s.id from arsse_articles as a join arsse_subscriptions as s on a.feed = s.feed where s.owner = ? and a.id not in (select article from arsse_label_members where label = ?) and a.id in ($inClause)";
|
||||||
|
@ -2113,7 +2113,7 @@ class Database {
|
||||||
// execute them in a transaction
|
// execute them in a transaction
|
||||||
$out = 0;
|
$out = 0;
|
||||||
$tr = $this->begin();
|
$tr = $this->begin();
|
||||||
foreach ($qList as list($q, $t, $v)) {
|
foreach ($qList as [$q, $t, $v]) {
|
||||||
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
||||||
}
|
}
|
||||||
$tr->commit();
|
$tr->commit();
|
||||||
|
@ -2322,7 +2322,7 @@ class Database {
|
||||||
$valid = [
|
$valid = [
|
||||||
'name' => "str",
|
'name' => "str",
|
||||||
];
|
];
|
||||||
list($setClause, $setTypes, $setValues) = $this->generateSet($data, $valid);
|
[$setClause, $setTypes, $setValues] = $this->generateSet($data, $valid);
|
||||||
if (!$setClause) {
|
if (!$setClause) {
|
||||||
// if no changes would actually be applied, just return
|
// if no changes would actually be applied, just return
|
||||||
return false;
|
return false;
|
||||||
|
@ -2386,7 +2386,7 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// prepare up to three queries: removing requires one, adding two, and replacing three
|
// prepare up to three queries: removing requires one, adding two, and replacing three
|
||||||
list($inClause, $inTypes, $inValues) = $this->generateIn($subscriptions, "int");
|
[$inClause, $inTypes, $inValues] = $this->generateIn($subscriptions, "int");
|
||||||
$updateQ = "UPDATE arsse_tag_members set assigned = ?, modified = CURRENT_TIMESTAMP where tag = ? and assigned <> ? and subscription in (select id from arsse_subscriptions where owner = ? and id %in% ($inClause))";
|
$updateQ = "UPDATE arsse_tag_members set assigned = ?, modified = CURRENT_TIMESTAMP where tag = ? and assigned <> ? and subscription in (select id from arsse_subscriptions where owner = ? and id %in% ($inClause))";
|
||||||
$updateT = ["bool", "int", "bool", "str", $inTypes];
|
$updateT = ["bool", "int", "bool", "str", $inTypes];
|
||||||
$insertQ = "INSERT INTO arsse_tag_members(tag,subscription) SELECT ?,id from arsse_subscriptions where id not in (select subscription from arsse_tag_members where tag = ?) and owner = ? and id in ($inClause)";
|
$insertQ = "INSERT INTO arsse_tag_members(tag,subscription) SELECT ?,id from arsse_subscriptions where id not in (select subscription from arsse_tag_members where tag = ?) and owner = ? and id in ($inClause)";
|
||||||
|
@ -2412,7 +2412,7 @@ class Database {
|
||||||
// execute them in a transaction
|
// execute them in a transaction
|
||||||
$out = 0;
|
$out = 0;
|
||||||
$tr = $this->begin();
|
$tr = $this->begin();
|
||||||
foreach ($qList as list($q, $t, $v)) {
|
foreach ($qList as [$q, $t, $v]) {
|
||||||
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
$out += $this->db->prepare($q, ...$t)->run(...$v)->changes();
|
||||||
}
|
}
|
||||||
$tr->commit();
|
$tr->commit();
|
||||||
|
|
|
@ -40,9 +40,9 @@ abstract class AbstractDriver implements Driver {
|
||||||
throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
}
|
}
|
||||||
$sql = @file_get_contents($file);
|
$sql = @file_get_contents($file);
|
||||||
if ($sql===false) {
|
if ($sql === false) {
|
||||||
throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); // @codeCoverageIgnore
|
throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); // @codeCoverageIgnore
|
||||||
} elseif ($sql==="") {
|
} elseif ($sql === "") {
|
||||||
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -50,7 +50,7 @@ abstract class AbstractDriver implements Driver {
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $e->getMessage()]);
|
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
if ($this->schemaVersion() != $a+1) {
|
if ($this->schemaVersion() != $a + 1) {
|
||||||
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ abstract class AbstractResult implements Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRow() {
|
public function getRow(): ?array {
|
||||||
if ($this->valid()) {
|
if ($this->valid()) {
|
||||||
$out = $this->cur;
|
$out = $this->cur;
|
||||||
$this->next();
|
$this->next();
|
||||||
|
|
|
@ -12,13 +12,13 @@ use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
abstract class AbstractStatement implements Statement {
|
abstract class AbstractStatement implements Statement {
|
||||||
use SQLState;
|
use SQLState;
|
||||||
|
|
||||||
const TYPE_NORM_MAP = [
|
public const TYPE_NORM_MAP = [
|
||||||
self::T_INTEGER => ValueInfo::M_NULL | ValueInfo::T_INT,
|
self::T_INTEGER => ValueInfo::M_NULL | ValueInfo::T_INT,
|
||||||
self::T_STRING => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
self::T_STRING => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
||||||
self::T_BOOLEAN => ValueInfo::M_NULL | ValueInfo::T_BOOL,
|
self::T_BOOLEAN => ValueInfo::M_NULL | ValueInfo::T_BOOL,
|
||||||
self::T_DATETIME => ValueInfo::M_NULL | ValueInfo::T_DATE,
|
self::T_DATETIME => ValueInfo::M_NULL | ValueInfo::T_DATE,
|
||||||
self::T_FLOAT => ValueInfo::M_NULL | ValueInfo::T_FLOAT,
|
self::T_FLOAT => ValueInfo::M_NULL | ValueInfo::T_FLOAT,
|
||||||
self::T_BINARY => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
self::T_BINARY => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
||||||
self::T_NOT_NULL + self::T_INTEGER => ValueInfo::T_INT,
|
self::T_NOT_NULL + self::T_INTEGER => ValueInfo::T_INT,
|
||||||
self::T_NOT_NULL + self::T_STRING => ValueInfo::T_STRING,
|
self::T_NOT_NULL + self::T_STRING => ValueInfo::T_STRING,
|
||||||
self::T_NOT_NULL + self::T_BOOLEAN => ValueInfo::T_BOOL,
|
self::T_NOT_NULL + self::T_BOOLEAN => ValueInfo::T_BOOL,
|
||||||
|
@ -78,7 +78,7 @@ abstract class AbstractStatement implements Statement {
|
||||||
$value = $this->cast($value, $this->types[$a]);
|
$value = $this->cast($value, $this->types[$a]);
|
||||||
$this->bindValue($value, $this->types[$a] % self::T_NOT_NULL, ++$a);
|
$this->bindValue($value, $this->types[$a] % self::T_NOT_NULL, ++$a);
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("paramTypeMissing", $a+1);
|
throw new Exception("paramTypeMissing", $a + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// once all values are bound, check that all parameters have been supplied values and bind null for any missing ones
|
// once all values are bound, check that all parameters have been supplied values and bind null for any missing ones
|
||||||
|
|
|
@ -7,11 +7,11 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db;
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
interface Driver {
|
interface Driver {
|
||||||
const TR_PEND = 0;
|
public const TR_PEND = 0;
|
||||||
const TR_COMMIT = 1;
|
public const TR_COMMIT = 1;
|
||||||
const TR_ROLLBACK = 2;
|
public const TR_ROLLBACK = 2;
|
||||||
const TR_PEND_COMMIT = -1;
|
public const TR_PEND_COMMIT = -1;
|
||||||
const TR_PEND_ROLLBACK = -2;
|
public const TR_PEND_ROLLBACK = -2;
|
||||||
|
|
||||||
/** Creates and returns an instance of the class; this is so that either a native or PDO driver may be returned depending on what is available on the server */
|
/** Creates and returns an instance of the class; this is so that either a native or PDO driver may be returned depending on what is available on the server */
|
||||||
public static function create(): Driver;
|
public static function create(): Driver;
|
||||||
|
|
|
@ -12,8 +12,8 @@ use JKingWeb\Arsse\Db\Exception;
|
||||||
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
use ExceptionBuilder;
|
use ExceptionBuilder;
|
||||||
|
|
||||||
const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
|
protected const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
|
||||||
const TRANSACTIONAL_LOCKS = false;
|
protected const TRANSACTIONAL_LOCKS = false;
|
||||||
|
|
||||||
/** @var \mysqli */
|
/** @var \mysqli */
|
||||||
protected $db;
|
protected $db;
|
||||||
|
@ -158,12 +158,12 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return class_exists("mysqli");
|
return class_exists("mysqli");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
|
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket): void {
|
||||||
$this->db = mysqli_init();
|
$this->db = mysqli_init();
|
||||||
$this->db->options(\MYSQLI_OPT_CONNECT_TIMEOUT, ceil(Arsse::$conf->dbTimeoutConnect));
|
$this->db->options(\MYSQLI_OPT_CONNECT_TIMEOUT, ceil(Arsse::$conf->dbTimeoutConnect));
|
||||||
@$this->db->real_connect($host, $user, $password, $db, $port, $socket);
|
@$this->db->real_connect($host, $user, $password, $db, $port, $socket);
|
||||||
if ($this->db->connect_errno) {
|
if ($this->db->connect_errno) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildConnectionException($this->db->connect_errno, $this->db->connect_error);
|
[$excClass, $excMsg, $excData] = $this->buildConnectionException($this->db->connect_errno, $this->db->connect_error);
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
$this->db->set_charset("utf8mb4");
|
$this->db->set_charset("utf8mb4");
|
||||||
|
@ -184,11 +184,11 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
do {
|
do {
|
||||||
if ($this->db->sqlstate !== "00000") {
|
if ($this->db->sqlstate !== "00000") {
|
||||||
if ($this->db->sqlstate === "HY000") {
|
if ($this->db->sqlstate === "HY000") {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error);
|
[$excClass, $excMsg, $excData] = $this->buildEngineException($this->db->errno, $this->db->error);
|
||||||
} else {
|
} else {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildStandardException($this->db->sqlstate, $this->db->error);
|
[$excClass, $excMsg, $excData] = $this->buildStandardException($this->db->sqlstate, $this->db->error);
|
||||||
}
|
}
|
||||||
$e = new $excClass($excMsg, $excData, $e);
|
$e = new $excClass($excMsg, $excData, $e);
|
||||||
}
|
}
|
||||||
$r = $this->db->store_result();
|
$r = $this->db->store_result();
|
||||||
} while ($this->db->more_results() && $this->db->next_result());
|
} while ($this->db->more_results() && $this->db->next_result());
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PDODriver extends Driver {
|
||||||
return class_exists("PDO") && in_array("mysql", \PDO::getAvailableDrivers());
|
return class_exists("PDO") && in_array("mysql", \PDO::getAvailableDrivers());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
|
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket): void {
|
||||||
$dsn = "mysql:".implode(";", [
|
$dsn = "mysql:".implode(";", [
|
||||||
"charset=utf8mb4",
|
"charset=utf8mb4",
|
||||||
"dbname=$db",
|
"dbname=$db",
|
||||||
|
@ -34,7 +34,7 @@ class PDODriver extends Driver {
|
||||||
$msg = $e->getMessage();
|
$msg = $e->getMessage();
|
||||||
$code = (int) substr($msg, 17, 4);
|
$code = (int) substr($msg, 17, 4);
|
||||||
$msg = substr($msg, 23);
|
$msg = substr($msg, 23);
|
||||||
list($excClass, $excMsg, $excData) = $this->buildConnectionException($code, $msg);
|
[$excClass, $excMsg, $excData] = $this->buildConnectionException($code, $msg);
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,6 @@ class Result extends \JKingWeb\Arsse\Db\AbstractResult {
|
||||||
|
|
||||||
public function valid() {
|
public function valid() {
|
||||||
$this->cur = $this->set ? $this->set->fetch_assoc() : null;
|
$this->cur = $this->set ? $this->set->fetch_assoc() : null;
|
||||||
return ($this->cur !== null);
|
return $this->cur !== null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\Db\MySQL;
|
||||||
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
use ExceptionBuilder;
|
use ExceptionBuilder;
|
||||||
|
|
||||||
const BINDINGS = [
|
protected const BINDINGS = [
|
||||||
self::T_INTEGER => "i",
|
self::T_INTEGER => "i",
|
||||||
self::T_FLOAT => "d",
|
self::T_FLOAT => "d",
|
||||||
self::T_DATETIME => "s",
|
self::T_DATETIME => "s",
|
||||||
|
@ -37,7 +37,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
protected function prepare(string $query): bool {
|
protected function prepare(string $query): bool {
|
||||||
$this->st = $this->db->prepare($query);
|
$this->st = $this->db->prepare($query);
|
||||||
if (!$this->st) { // @codeCoverageIgnore
|
if (!$this->st) { // @codeCoverageIgnore
|
||||||
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error); // @codeCoverageIgnore
|
[$excClass, $excMsg, $excData] = $this->buildEngineException($this->db->errno, $this->db->error); // @codeCoverageIgnore
|
||||||
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -76,9 +76,9 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
// check for errors
|
// check for errors
|
||||||
if ($this->st->sqlstate !== "00000") {
|
if ($this->st->sqlstate !== "00000") {
|
||||||
if ($this->st->sqlstate === "HY000") {
|
if ($this->st->sqlstate === "HY000") {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->st->errno, $this->st->error);
|
[$excClass, $excMsg, $excData] = $this->buildEngineException($this->st->errno, $this->st->error);
|
||||||
} else {
|
} else {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildStandardException($this->st->sqlstate, $this->st->error);
|
[$excClass, $excMsg, $excData] = $this->buildStandardException($this->st->sqlstate, $this->st->error);
|
||||||
}
|
}
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ trait PDODriver {
|
||||||
$this->db->exec($query);
|
$this->db->exec($query);
|
||||||
return true;
|
return true;
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildPDOException();
|
[$excClass, $excMsg, $excData] = $this->buildPDOException();
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ trait PDODriver {
|
||||||
try {
|
try {
|
||||||
$r = $this->db->query($query);
|
$r = $this->db->query($query);
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildPDOException();
|
[$excClass, $excMsg, $excData] = $this->buildPDOException();
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
return new PDOResult($this->db, $r);
|
return new PDOResult($this->db, $r);
|
||||||
|
|
|
@ -45,6 +45,6 @@ class PDOResult extends AbstractResult {
|
||||||
|
|
||||||
public function valid() {
|
public function valid() {
|
||||||
$this->cur = $this->set->fetch(\PDO::FETCH_ASSOC);
|
$this->cur = $this->set->fetch(\PDO::FETCH_ASSOC);
|
||||||
return ($this->cur !== false);
|
return $this->cur !== false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\Db;
|
||||||
abstract class PDOStatement extends AbstractStatement {
|
abstract class PDOStatement extends AbstractStatement {
|
||||||
use PDOError;
|
use PDOError;
|
||||||
|
|
||||||
const BINDINGS = [
|
protected const BINDINGS = [
|
||||||
self::T_INTEGER => \PDO::PARAM_INT,
|
self::T_INTEGER => \PDO::PARAM_INT,
|
||||||
self::T_FLOAT => \PDO::PARAM_STR,
|
self::T_FLOAT => \PDO::PARAM_STR,
|
||||||
self::T_DATETIME => \PDO::PARAM_STR,
|
self::T_DATETIME => \PDO::PARAM_STR,
|
||||||
|
@ -34,7 +34,7 @@ abstract class PDOStatement extends AbstractStatement {
|
||||||
$this->st = $this->db->prepare($query);
|
$this->st = $this->db->prepare($query);
|
||||||
return true;
|
return true;
|
||||||
} catch (\PDOException $e) { // @codeCoverageIgnore
|
} catch (\PDOException $e) { // @codeCoverageIgnore
|
||||||
list($excClass, $excMsg, $excData) = $this->buildPDOException(); // @codeCoverageIgnore
|
[$excClass, $excMsg, $excData] = $this->buildPDOException(); // @codeCoverageIgnore
|
||||||
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ abstract class PDOStatement extends AbstractStatement {
|
||||||
try {
|
try {
|
||||||
$this->st->execute();
|
$this->st->execute();
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildPDOException(true);
|
[$excClass, $excMsg, $excData] = $this->buildPDOException(true);
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
return new PDOResult($this->db, $this->st);
|
return new PDOResult($this->db, $this->st);
|
||||||
|
|
|
@ -12,7 +12,7 @@ use JKingWeb\Arsse\Db\Exception;
|
||||||
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
use Dispatch;
|
use Dispatch;
|
||||||
|
|
||||||
const TRANSACTIONAL_LOCKS = true;
|
protected const TRANSACTIONAL_LOCKS = true;
|
||||||
|
|
||||||
protected $db;
|
protected $db;
|
||||||
protected $transStart = 0;
|
protected $transStart = 0;
|
||||||
|
@ -37,9 +37,9 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
|
|
||||||
public static function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service): string {
|
public static function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service): string {
|
||||||
$base = [
|
$base = [
|
||||||
'client_encoding' => "UTF8",
|
'client_encoding' => "UTF8",
|
||||||
'application_name' => "arsse",
|
'application_name' => "arsse",
|
||||||
'connect_timeout' => (string) (int) ceil(Arsse::$conf->dbTimeoutConnect),
|
'connect_timeout' => (string) (int) ceil(Arsse::$conf->dbTimeoutConnect),
|
||||||
];
|
];
|
||||||
$out = [];
|
$out = [];
|
||||||
if ($service != "") {
|
if ($service != "") {
|
||||||
|
@ -181,7 +181,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return \extension_loaded("pgsql");
|
return \extension_loaded("pgsql");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service) {
|
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service): void {
|
||||||
$dsn = $this->makeconnectionString(false, $user, $pass, $db, $host, $port, $service);
|
$dsn = $this->makeconnectionString(false, $user, $pass, $db, $host, $port, $service);
|
||||||
set_error_handler(function(int $code, string $msg) {
|
set_error_handler(function(int $code, string $msg) {
|
||||||
$msg = substr($msg, 62);
|
$msg = substr($msg, 62);
|
||||||
|
@ -198,7 +198,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
pg_send_query($this->db, $query);
|
pg_send_query($this->db, $query);
|
||||||
while ($result = pg_get_result($this->db)) {
|
while ($result = pg_get_result($this->db)) {
|
||||||
if (($code = pg_result_error_field($result, \PGSQL_DIAG_SQLSTATE)) && isset($code) && $code) {
|
if (($code = pg_result_error_field($result, \PGSQL_DIAG_SQLSTATE)) && isset($code) && $code) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildStandardException($code, pg_result_error($result));
|
[$excClass, $excMsg, $excData] = $this->buildStandardException($code, pg_result_error($result));
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
if (is_resource($r)) {
|
if (is_resource($r)) {
|
||||||
return new Result($this->db, $r);
|
return new Result($this->db, $r);
|
||||||
} else {
|
} else {
|
||||||
list($excClass, $excMsg, $excData) = $r;
|
[$excClass, $excMsg, $excData] = $r;
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,11 @@ class PDODriver extends Driver {
|
||||||
return class_exists("PDO") && in_array("pgsql", \PDO::getAvailableDrivers());
|
return class_exists("PDO") && in_array("pgsql", \PDO::getAvailableDrivers());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service) {
|
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service): void {
|
||||||
$dsn = $this->makeconnectionString(true, $user, $pass, $db, $host, $port, $service);
|
$dsn = $this->makeconnectionString(true, $user, $pass, $db, $host, $port, $service);
|
||||||
try {
|
try {
|
||||||
$this->db = new \PDO("pgsql:$dsn", $user, $pass, [
|
$this->db = new \PDO("pgsql:$dsn", $user, $pass, [
|
||||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||||
\PDO::ATTR_PERSISTENT => true,
|
\PDO::ATTR_PERSISTENT => true,
|
||||||
]);
|
]);
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
|
@ -53,7 +53,6 @@ class PDODriver extends Driver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function driverName(): string {
|
public static function driverName(): string {
|
||||||
return Arsse::$lang->msg("Driver.Db.PostgreSQLPDO.Name");
|
return Arsse::$lang->msg("Driver.Db.PostgreSQLPDO.Name");
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,6 @@ class Result extends \JKingWeb\Arsse\Db\AbstractResult {
|
||||||
|
|
||||||
public function valid() {
|
public function valid() {
|
||||||
$this->cur = pg_fetch_row($this->r, null, \PGSQL_ASSOC);
|
$this->cur = pg_fetch_row($this->r, null, \PGSQL_ASSOC);
|
||||||
return ($this->cur !== false);
|
return $this->cur !== false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\Db\PostgreSQL;
|
||||||
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
use Dispatch;
|
use Dispatch;
|
||||||
|
|
||||||
const BINDINGS = [
|
protected const BINDINGS = [
|
||||||
self::T_INTEGER => "bigint",
|
self::T_INTEGER => "bigint",
|
||||||
self::T_FLOAT => "decimal",
|
self::T_FLOAT => "decimal",
|
||||||
self::T_DATETIME => "timestamp(0) without time zone",
|
self::T_DATETIME => "timestamp(0) without time zone",
|
||||||
|
@ -38,7 +38,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
if (is_resource($r)) {
|
if (is_resource($r)) {
|
||||||
return new Result($this->db, $r);
|
return new Result($this->db, $r);
|
||||||
} else {
|
} else {
|
||||||
list($excClass, $excMsg, $excData) = $r;
|
[$excClass, $excMsg, $excData] = $r;
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\Db;
|
||||||
class ResultEmpty extends AbstractResult {
|
class ResultEmpty extends AbstractResult {
|
||||||
protected $changes = 0;
|
protected $changes = 0;
|
||||||
protected $id = 0;
|
protected $id = 0;
|
||||||
|
|
||||||
public function __construct(int $changes = 0, int $id = 0) {
|
public function __construct(int $changes = 0, int $id = 0) {
|
||||||
$this->changes = $changes;
|
$this->changes = $changes;
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
|
|
|
@ -6,10 +6,6 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db;
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Db\Exception;
|
|
||||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
|
||||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
|
||||||
|
|
||||||
trait SQLState {
|
trait SQLState {
|
||||||
protected static function buildStandardException(string $code, string $msg): array {
|
protected static function buildStandardException(string $code, string $msg): array {
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
|
|
|
@ -12,12 +12,12 @@ use JKingWeb\Arsse\Db\Exception;
|
||||||
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
use ExceptionBuilder;
|
use ExceptionBuilder;
|
||||||
|
|
||||||
const TRANSACTIONAL_LOCKS = true;
|
protected const TRANSACTIONAL_LOCKS = true;
|
||||||
|
|
||||||
const SQLITE_BUSY = 5;
|
public const SQLITE_BUSY = 5;
|
||||||
const SQLITE_SCHEMA = 17;
|
public const SQLITE_SCHEMA = 17;
|
||||||
const SQLITE_CONSTRAINT = 19;
|
public const SQLITE_CONSTRAINT = 19;
|
||||||
const SQLITE_MISMATCH = 20;
|
public const SQLITE_MISMATCH = 20;
|
||||||
|
|
||||||
protected $db;
|
protected $db;
|
||||||
|
|
||||||
|
@ -69,13 +69,13 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return class_exists("SQLite3");
|
return class_exists("SQLite3");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $file, string $key) {
|
protected function makeConnection(string $file, string $key): void {
|
||||||
$this->db = new \SQLite3($file, \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE, $key);
|
$this->db = new \SQLite3($file, \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE, $key);
|
||||||
// enable exceptions
|
// enable exceptions
|
||||||
$this->db->enableExceptions(true);
|
$this->db->enableExceptions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setTimeout(int $msec) {
|
protected function setTimeout(int $msec): void {
|
||||||
$this->exec("PRAGMA busy_timeout = $msec");
|
$this->exec("PRAGMA busy_timeout = $msec");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function driverName(): string {
|
public static function driverName(): string {
|
||||||
return Arsse::$lang->msg("Driver.Db.SQLite3.Name");
|
return Arsse::$lang->msg("Driver.Db.SQLite3.Name");
|
||||||
}
|
}
|
||||||
|
@ -146,7 +145,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
try {
|
try {
|
||||||
return (bool) $this->db->exec($query);
|
return (bool) $this->db->exec($query);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildException();
|
[$excClass, $excMsg, $excData] = $this->buildException();
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +154,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
try {
|
try {
|
||||||
$r = $this->db->query($query);
|
$r = $this->db->query($query);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildException();
|
[$excClass, $excMsg, $excData] = $this->buildException();
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
$changes = $this->db->changes();
|
$changes = $this->db->changes();
|
||||||
|
|
|
@ -16,7 +16,7 @@ class PDODriver extends AbstractPDODriver {
|
||||||
return class_exists("PDO") && in_array("sqlite", \PDO::getAvailableDrivers());
|
return class_exists("PDO") && in_array("sqlite", \PDO::getAvailableDrivers());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $file, string $key) {
|
protected function makeConnection(string $file, string $key): void {
|
||||||
$this->db = new \PDO("sqlite:".$file, "", "", [
|
$this->db = new \PDO("sqlite:".$file, "", "", [
|
||||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||||
]);
|
]);
|
||||||
|
@ -37,7 +37,6 @@ class PDODriver extends AbstractPDODriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function driverName(): string {
|
public static function driverName(): string {
|
||||||
return Arsse::$lang->msg("Driver.Db.SQLite3PDO.Name");
|
return Arsse::$lang->msg("Driver.Db.SQLite3PDO.Name");
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,6 @@ class Result extends \JKingWeb\Arsse\Db\AbstractResult {
|
||||||
|
|
||||||
public function valid() {
|
public function valid() {
|
||||||
$this->cur = $this->set->fetchArray(\SQLITE3_ASSOC);
|
$this->cur = $this->set->fetchArray(\SQLITE3_ASSOC);
|
||||||
return ($this->cur !== false);
|
return $this->cur !== false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ namespace JKingWeb\Arsse\Db\SQLite3;
|
||||||
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
use ExceptionBuilder;
|
use ExceptionBuilder;
|
||||||
|
|
||||||
const SQLITE_BUSY = 5;
|
public const SQLITE_BUSY = 5;
|
||||||
const SQLITE_CONSTRAINT = 19;
|
public const SQLITE_CONSTRAINT = 19;
|
||||||
const SQLITE_MISMATCH = 20;
|
public const SQLITE_MISMATCH = 20;
|
||||||
const BINDINGS = [
|
protected const BINDINGS = [
|
||||||
self::T_INTEGER => \SQLITE3_INTEGER,
|
self::T_INTEGER => \SQLITE3_INTEGER,
|
||||||
self::T_FLOAT => \SQLITE3_FLOAT,
|
self::T_FLOAT => \SQLITE3_FLOAT,
|
||||||
self::T_DATETIME => \SQLITE3_TEXT,
|
self::T_DATETIME => \SQLITE3_TEXT,
|
||||||
|
@ -37,7 +37,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
$this->st = $this->db->prepare($query);
|
$this->st = $this->db->prepare($query);
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) { // @codeCoverageIgnore
|
} catch (\Exception $e) { // @codeCoverageIgnore
|
||||||
list($excClass, $excMsg, $excData) = $this->buildException(); // @codeCoverageIgnore
|
[$excClass, $excMsg, $excData] = $this->buildException(); // @codeCoverageIgnore
|
||||||
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
|
||||||
try {
|
try {
|
||||||
$r = $this->st->execute();
|
$r = $this->st->execute();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildException();
|
[$excClass, $excMsg, $excData] = $this->buildException();
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
}
|
}
|
||||||
$changes = $this->db->changes();
|
$changes = $this->db->changes();
|
||||||
|
|
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db;
|
namespace JKingWeb\Arsse\Db;
|
||||||
|
|
||||||
interface Statement {
|
interface Statement {
|
||||||
const TYPES = [
|
public const TYPES = [
|
||||||
'int' => self::T_INTEGER,
|
'int' => self::T_INTEGER,
|
||||||
'integer' => self::T_INTEGER,
|
'integer' => self::T_INTEGER,
|
||||||
'float' => self::T_FLOAT,
|
'float' => self::T_FLOAT,
|
||||||
|
@ -43,13 +43,13 @@ interface Statement {
|
||||||
'strict boolean' => self::T_NOT_NULL + self::T_BOOLEAN,
|
'strict boolean' => self::T_NOT_NULL + self::T_BOOLEAN,
|
||||||
'strict bit' => self::T_NOT_NULL + self::T_BOOLEAN,
|
'strict bit' => self::T_NOT_NULL + self::T_BOOLEAN,
|
||||||
];
|
];
|
||||||
const T_INTEGER = 1;
|
public const T_INTEGER = 1;
|
||||||
const T_STRING = 2;
|
public const T_STRING = 2;
|
||||||
const T_BOOLEAN = 3;
|
public const T_BOOLEAN = 3;
|
||||||
const T_DATETIME = 4;
|
public const T_DATETIME = 4;
|
||||||
const T_FLOAT = 5;
|
public const T_FLOAT = 5;
|
||||||
const T_BINARY = 6;
|
public const T_BINARY = 6;
|
||||||
const T_NOT_NULL = 100;
|
public const T_NOT_NULL = 100;
|
||||||
|
|
||||||
public function run(...$values): Result;
|
public function run(...$values): Result;
|
||||||
public function runArray(array $values = []): Result;
|
public function runArray(array $values = []): Result;
|
||||||
|
|
28
lib/Feed.php
28
lib/Feed.php
|
@ -50,7 +50,7 @@ class Feed {
|
||||||
$this->resource = self::download($url, $lastModified, $etag, $username, $password);
|
$this->resource = self::download($url, $lastModified, $etag, $username, $password);
|
||||||
// format the HTTP Last-Modified date returned
|
// format the HTTP Last-Modified date returned
|
||||||
$lastMod = $this->resource->getLastModified();
|
$lastMod = $this->resource->getLastModified();
|
||||||
if (strlen($lastMod)) {
|
if (strlen($lastMod ?? "")) {
|
||||||
$this->lastModified = Date::normalize($lastMod, "http");
|
$this->lastModified = Date::normalize($lastMod, "http");
|
||||||
}
|
}
|
||||||
$this->modified = $this->resource->isModified();
|
$this->modified = $this->resource->isModified();
|
||||||
|
@ -100,6 +100,8 @@ class Feed {
|
||||||
$client->reader = $reader;
|
$client->reader = $reader;
|
||||||
return $client;
|
return $client;
|
||||||
} catch (PicoFeedException $e) {
|
} catch (PicoFeedException $e) {
|
||||||
|
throw new Feed\Exception($url, $e); // @codeCoverageIgnore
|
||||||
|
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
|
||||||
throw new Feed\Exception($url, $e);
|
throw new Feed\Exception($url, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,12 +117,10 @@ class Feed {
|
||||||
// Some feeds might use a different domain (eg: feedburner), so the site url is
|
// Some feeds might use a different domain (eg: feedburner), so the site url is
|
||||||
// used instead of the feed's url.
|
// used instead of the feed's url.
|
||||||
$this->favicon = (new Favicon)->find($feed->siteUrl);
|
$this->favicon = (new Favicon)->find($feed->siteUrl);
|
||||||
// work around a PicoFeed memory leak
|
|
||||||
libxml_use_internal_errors(false);
|
|
||||||
} catch (PicoFeedException $e) {
|
} catch (PicoFeedException $e) {
|
||||||
// work around a PicoFeed memory leak
|
|
||||||
libxml_use_internal_errors(false);
|
|
||||||
throw new Feed\Exception($this->resource->getUrl(), $e);
|
throw new Feed\Exception($this->resource->getUrl(), $e);
|
||||||
|
} catch (\GuzzleHttp\Exception\GuzzleException $e) { // @codeCoverageIgnore
|
||||||
|
throw new Feed\Exception($this->resource->getUrl(), $e); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
// PicoFeed does not provide valid ids when there is no id element. Its solution
|
// PicoFeed does not provide valid ids when there is no id element. Its solution
|
||||||
|
@ -221,8 +221,8 @@ class Feed {
|
||||||
// if the two items have the same ID or any one hash matches, they are two versions of the same item
|
// if the two items have the same ID or any one hash matches, they are two versions of the same item
|
||||||
if (
|
if (
|
||||||
($item->id && $check->id && $item->id === $check->id) ||
|
($item->id && $check->id && $item->id === $check->id) ||
|
||||||
($item->urlTitleHash && $item->urlTitleHash === $check->urlTitleHash) ||
|
($item->urlTitleHash && $item->urlTitleHash === $check->urlTitleHash) ||
|
||||||
($item->urlContentHash && $item->urlContentHash === $check->urlContentHash) ||
|
($item->urlContentHash && $item->urlContentHash === $check->urlContentHash) ||
|
||||||
($item->titleContentHash && $item->titleContentHash === $check->titleContentHash)
|
($item->titleContentHash && $item->titleContentHash === $check->titleContentHash)
|
||||||
) {
|
) {
|
||||||
if (// because newsfeeds are usually order newest-first, the later item should only be used if...
|
if (// because newsfeeds are usually order newest-first, the later item should only be used if...
|
||||||
|
@ -259,7 +259,7 @@ class Feed {
|
||||||
// get as many of the latest articles in the database as there are in the feed
|
// get as many of the latest articles in the database as there are in the feed
|
||||||
$articles = Arsse::$db->feedMatchLatest($feedID, sizeof($items))->getAll();
|
$articles = Arsse::$db->feedMatchLatest($feedID, sizeof($items))->getAll();
|
||||||
// perform a first pass matching the latest articles against items in the feed
|
// perform a first pass matching the latest articles against items in the feed
|
||||||
list($this->newItems, $this->changedItems) = $this->matchItems($items, $articles);
|
[$this->newItems, $this->changedItems] = $this->matchItems($items, $articles);
|
||||||
if (sizeof($this->newItems) && sizeof($items) <= sizeof($articles)) {
|
if (sizeof($this->newItems) && sizeof($items) <= sizeof($articles)) {
|
||||||
// if we need to, perform a second pass on the database looking specifically for IDs and hashes of the new items
|
// if we need to, perform a second pass on the database looking specifically for IDs and hashes of the new items
|
||||||
$ids = $hashesUT = $hashesUC = $hashesTC = [];
|
$ids = $hashesUT = $hashesUC = $hashesTC = [];
|
||||||
|
@ -278,7 +278,7 @@ class Feed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$articles = Arsse::$db->feedMatchIds($feedID, $ids, $hashesUT, $hashesUC, $hashesTC)->getAll();
|
$articles = Arsse::$db->feedMatchIds($feedID, $ids, $hashesUT, $hashesUC, $hashesTC)->getAll();
|
||||||
list($this->newItems, $changed) = $this->matchItems($this->newItems, $articles);
|
[$this->newItems, $changed] = $this->matchItems($this->newItems, $articles);
|
||||||
// merge the two change-lists, preserving keys
|
// merge the two change-lists, preserving keys
|
||||||
$this->changedItems = array_combine(array_merge(array_keys($this->changedItems), array_keys($changed)), array_merge($this->changedItems, $changed));
|
$this->changedItems = array_combine(array_merge(array_keys($this->changedItems), array_keys($changed)), array_merge($this->changedItems, $changed));
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,7 @@ class Feed {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function matchItems(array $items, array $articles): array {
|
protected function matchItems(array $items, array $articles): array {
|
||||||
$new = $edited = [];
|
$new = $edited = [];
|
||||||
// iterate through the articles and for each determine whether it is existing, edited, or entirely new
|
// iterate through the articles and for each determine whether it is existing, edited, or entirely new
|
||||||
foreach ($items as $i) {
|
foreach ($items as $i) {
|
||||||
$found = false;
|
$found = false;
|
||||||
|
@ -299,8 +299,8 @@ class Feed {
|
||||||
// the item matches if the GUID matches...
|
// the item matches if the GUID matches...
|
||||||
($i->id && $i->id === $a['guid']) ||
|
($i->id && $i->id === $a['guid']) ||
|
||||||
// ... or if any one of the hashes match
|
// ... or if any one of the hashes match
|
||||||
($i->urlTitleHash && $i->urlTitleHash === $a['url_title_hash']) ||
|
($i->urlTitleHash && $i->urlTitleHash === $a['url_title_hash']) ||
|
||||||
($i->urlContentHash && $i->urlContentHash === $a['url_content_hash']) ||
|
($i->urlContentHash && $i->urlContentHash === $a['url_content_hash']) ||
|
||||||
($i->titleContentHash && $i->titleContentHash === $a['title_content_hash'])
|
($i->titleContentHash && $i->titleContentHash === $a['title_content_hash'])
|
||||||
) {
|
) {
|
||||||
if ($i->updatedDate && Date::transform($i->updatedDate, "sql") !== $a['edited']) {
|
if ($i->updatedDate && Date::transform($i->updatedDate, "sql") !== $a['edited']) {
|
||||||
|
@ -348,7 +348,7 @@ class Feed {
|
||||||
$dates = $this->gatherDates();
|
$dates = $this->gatherDates();
|
||||||
if (sizeof($dates) > 3) {
|
if (sizeof($dates) > 3) {
|
||||||
for ($a = 0; $a < 3; $a++) {
|
for ($a = 0; $a < 3; $a++) {
|
||||||
$diff = $dates[$a] - $dates[$a+1];
|
$diff = $dates[$a] - $dates[$a + 1];
|
||||||
$offsets[] = $this->normalizeDateDiff($diff);
|
$offsets[] = $this->normalizeDateDiff($diff);
|
||||||
}
|
}
|
||||||
if ($offsets[0] === $offsets[1] || $offsets[0] === $offsets[2]) {
|
if ($offsets[0] === $offsets[1] || $offsets[0] === $offsets[2]) {
|
||||||
|
@ -390,7 +390,7 @@ class Feed {
|
||||||
return $offset;
|
return $offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function computeLastModified() {
|
protected function computeLastModified(): ?\DateTimeImmutable {
|
||||||
if (!$this->modified) {
|
if (!$this->modified) {
|
||||||
return $this->lastModified; // @codeCoverageIgnore
|
return $this->lastModified; // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,39 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Feed;
|
namespace JKingWeb\Arsse\Feed;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\BadResponseException;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||||
|
use PicoFeed\PicoFeedException;
|
||||||
|
|
||||||
class Exception extends \JKingWeb\Arsse\AbstractException {
|
class Exception extends \JKingWeb\Arsse\AbstractException {
|
||||||
|
protected const CURL_ERROR_MAP = [1 => "invalidUrl",3 => "invalidUrl",5 => "transmissionError","connectionFailed","connectionFailed","transmissionError","forbidden","unauthorized","transmissionError","transmissionError","transmissionError","transmissionError","connectionFailed","connectionFailed","transmissionError","transmissionError","transmissionError","transmissionError","transmissionError","invalidUrl","transmissionError","transmissionError","transmissionError","transmissionError",28 => "timeout","transmissionError","transmissionError","transmissionError","transmissionError","transmissionError",35 => "invalidCertificate","transmissionError","transmissionError","transmissionError","transmissionError",45 => "transmissionError","unauthorized","maxRedirect",52 => "transmissionError","invalidCertificate","invalidCertificate","transmissionError","transmissionError",58 => "invalidCertificate","invalidCertificate","invalidCertificate","transmissionError","invalidUrl","transmissionError","invalidCertificate","transmissionError","invalidCertificate","forbidden","invalidUrl","forbidden","transmissionError",73 => "transmissionError","transmissionError",77 => "invalidCertificate","invalidUrl",90 => "invalidCertificate","invalidCertificate","transmissionError",94 => "unauthorized","transmissionError","connectionFailed"];
|
||||||
|
protected const HTTP_ERROR_MAP = [401 => "unauthorized",403 => "forbidden",404 => "invalidUrl",408 => "timeout",410 => "invalidUrl",414 => "invalidUrl",451 => "invalidUrl"];
|
||||||
|
|
||||||
public function __construct($url, \Throwable $e) {
|
public function __construct($url, \Throwable $e) {
|
||||||
$className = get_class($e);
|
if ($e instanceof BadResponseException) {
|
||||||
// Convert the exception thrown by PicoFeed to the one to be thrown here.
|
$msgID = self::HTTP_ERROR_MAP[$e->getCode()] ?? "transmissionError";
|
||||||
$msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className);
|
} elseif ($e instanceof TooManyRedirectsException) {
|
||||||
// If the message ID doesn't change then it's unknown.
|
$msgID = "maxRedirect";
|
||||||
$msgID = ($msgID !== $className) ? lcfirst($msgID) : '';
|
} elseif ($e instanceof GuzzleException) {
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
if (preg_match("/^Error creating resource:/", $msg)) {
|
||||||
|
// PHP stream error; the class of error is ambiguous
|
||||||
|
$msgID = "transmissionError";
|
||||||
|
} elseif (preg_match("/^cURL error (\d+):/", $msg, $match)) {
|
||||||
|
$msgID = self::CURL_ERROR_MAP[(int) $match[1]] ?? "internalError";
|
||||||
|
} else {
|
||||||
|
$msgID = "internalError";
|
||||||
|
}
|
||||||
|
} elseif ($e instanceof PicoFeedException) {
|
||||||
|
$className = get_class($e);
|
||||||
|
// Convert the exception thrown by PicoFeed to the one to be thrown here.
|
||||||
|
$msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className);
|
||||||
|
// If the message ID doesn't change then it's unknown.
|
||||||
|
$msgID = ($msgID !== $className) ? lcfirst($msgID) : "internalError";
|
||||||
|
} else {
|
||||||
|
$msgID = "internalError";
|
||||||
|
}
|
||||||
parent::__construct($msgID, ['url' => $url], $e);
|
parent::__construct($msgID, ['url' => $url], $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ abstract class AbstractImportExport {
|
||||||
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
// first extract useful information from the input
|
// first extract useful information from the input
|
||||||
list($feeds, $folders) = $this->parse($data, $flat);
|
[$feeds, $folders] = $this->parse($data, $flat);
|
||||||
$folderMap = [];
|
$folderMap = [];
|
||||||
foreach ($folders as $f) {
|
foreach ($folders as $f) {
|
||||||
// check to make sure folder names are all valid
|
// check to make sure folder names are all valid
|
||||||
|
@ -42,7 +42,7 @@ abstract class AbstractImportExport {
|
||||||
$tr = Arsse::$db->begin();
|
$tr = Arsse::$db->begin();
|
||||||
// get current state of database
|
// get current state of database
|
||||||
$foldersDb = iterator_to_array(Arsse::$db->folderList($user));
|
$foldersDb = iterator_to_array(Arsse::$db->folderList($user));
|
||||||
$feedsDb = iterator_to_array(Arsse::$db->subscriptionList($user));
|
$feedsDb = iterator_to_array(Arsse::$db->subscriptionList($user));
|
||||||
$tagsDb = iterator_to_array(Arsse::$db->tagList($user));
|
$tagsDb = iterator_to_array(Arsse::$db->tagList($user));
|
||||||
// reconcile folders
|
// reconcile folders
|
||||||
$folderMap = [0 => 0];
|
$folderMap = [0 => 0];
|
||||||
|
|
10
lib/Lang.php
10
lib/Lang.php
|
@ -7,8 +7,8 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class Lang {
|
class Lang {
|
||||||
const DEFAULT = "en"; // fallback locale
|
public const DEFAULT = "en"; // fallback locale
|
||||||
const REQUIRED = [ // collection of absolutely required strings to handle pathological errors
|
protected const REQUIRED = [ // collection of absolutely required strings to handle pathological errors
|
||||||
'Exception.JKingWeb/Arsse/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in AbstractException.php',
|
'Exception.JKingWeb/Arsse/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in AbstractException.php',
|
||||||
'Exception.JKingWeb/Arsse/Exception.unknown' => 'An unknown error has occurred',
|
'Exception.JKingWeb/Arsse/Exception.unknown' => 'An unknown error has occurred',
|
||||||
'Exception.JKingWeb/Arsse/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
'Exception.JKingWeb/Arsse/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
||||||
|
@ -97,7 +97,7 @@ class Lang {
|
||||||
}
|
}
|
||||||
$msg = $this->strings[$msgID];
|
$msg = $this->strings[$msgID];
|
||||||
// variables fed to MessageFormatter must be contained in an array
|
// variables fed to MessageFormatter must be contained in an array
|
||||||
if ($vars===null) {
|
if ($vars === null) {
|
||||||
// even though strings not given parameters will not get formatted, we do not optimize this case away: we still want to catch invalid strings
|
// even though strings not given parameters will not get formatted, we do not optimize this case away: we still want to catch invalid strings
|
||||||
$vars = [];
|
$vars = [];
|
||||||
} elseif (!is_array($vars)) {
|
} elseif (!is_array($vars)) {
|
||||||
|
@ -108,7 +108,7 @@ class Lang {
|
||||||
throw new Lang\Exception("stringInvalid", ['error' => $this->formatter->getErrorMessage(), 'msgID' => $msgID, 'fileList' => implode(", ", $this->loaded)]);
|
throw new Lang\Exception("stringInvalid", ['error' => $this->formatter->getErrorMessage(), 'msgID' => $msgID, 'fileList' => implode(", ", $this->loaded)]);
|
||||||
}
|
}
|
||||||
$msg = $this->formatter->format($vars);
|
$msg = $this->formatter->format($vars);
|
||||||
if ($msg===false) {
|
if ($msg === false) {
|
||||||
throw new Lang\Exception("dataInvalid", ['error' => $this->formatter->getErrorMessage(), 'msgID' => $msgID, 'fileList' => implode(", ", $this->loaded)]); // @codeCoverageIgnore
|
throw new Lang\Exception("dataInvalid", ['error' => $this->formatter->getErrorMessage(), 'msgID' => $msgID, 'fileList' => implode(", ", $this->loaded)]); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
return $msg;
|
return $msg;
|
||||||
|
@ -148,7 +148,7 @@ class Lang {
|
||||||
// trim the returned file paths to return just the language tag
|
// trim the returned file paths to return just the language tag
|
||||||
$out = array_map(function($file) {
|
$out = array_map(function($file) {
|
||||||
$file = str_replace(DIRECTORY_SEPARATOR, "/", $file); // we replace the directory separator because we don't use native paths in testing
|
$file = str_replace(DIRECTORY_SEPARATOR, "/", $file); // we replace the directory separator because we don't use native paths in testing
|
||||||
$file = substr($file, strrpos($file, "/")+1);
|
$file = substr($file, strrpos($file, "/") + 1);
|
||||||
return strtolower(substr($file, 0, strrpos($file, ".")));
|
return strtolower(substr($file, 0, strrpos($file, ".")));
|
||||||
}, $out);
|
}, $out);
|
||||||
// sort the results
|
// sort the results
|
||||||
|
|
|
@ -21,19 +21,19 @@ class Date {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function normalize($date, string $inFormat = null) {
|
public static function normalize($date, string $inFormat = null): ?\DateTimeImmutable {
|
||||||
return ValueInfo::normalize($date, ValueInfo::T_DATE, $inFormat);
|
return ValueInfo::normalize($date, ValueInfo::T_DATE, $inFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function add($interval, $date = "now") {
|
public static function add($interval, $date = "now"): ?\DateTimeImmutable {
|
||||||
return self::modify("add", $interval, $date);
|
return self::modify("add", $interval, $date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function sub($interval, $date = "now") {
|
public static function sub($interval, $date = "now"): ?\DateTimeImmutable {
|
||||||
return self::modify("sub", $interval, $date);
|
return self::modify("sub", $interval, $date);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function modify(string $func, $interval, $date) {
|
protected static function modify(string $func, $interval, $date): ?\DateTimeImmutable {
|
||||||
$date = self::normalize($date);
|
$date = self::normalize($date);
|
||||||
$interval = (!$interval instanceof \DateInterval) ? ValueInfo::normalize($interval, ValueInfo::T_INTERVAL) : $interval;
|
$interval = (!$interval instanceof \DateInterval) ? ValueInfo::normalize($interval, ValueInfo::T_INTERVAL) : $interval;
|
||||||
return $date ? $date->$func($interval) : null;
|
return $date ? $date->$func($interval) : null;
|
||||||
|
|
|
@ -24,7 +24,6 @@ class Query {
|
||||||
protected $limit = 0;
|
protected $limit = 0;
|
||||||
protected $offset = 0;
|
protected $offset = 0;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(string $body = "", $types = null, $values = null) {
|
public function __construct(string $body = "", $types = null, $values = null) {
|
||||||
$this->setBody($body, $types, $values);
|
$this->setBody($body, $types, $values);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ class URL {
|
||||||
$c = $part[$pos];
|
$c = $part[$pos];
|
||||||
if ($c === "%") {
|
if ($c === "%") {
|
||||||
// the % character signals an encoded character...
|
// the % character signals an encoded character...
|
||||||
$d = substr($part, $pos+1, 2);
|
$d = substr($part, $pos + 1, 2);
|
||||||
if (!preg_match("/^[0-9a-fA-F]{2}$/", $d)) {
|
if (!preg_match("/^[0-9a-fA-F]{2}$/", $d)) {
|
||||||
// unless there are fewer than two characters left in the string or the two characters are not hex digits
|
// unless there are fewer than two characters left in the string or the two characters are not hex digits
|
||||||
$d = ord($c);
|
$d = ord($c);
|
||||||
|
|
|
@ -10,33 +10,33 @@ use JKingWeb\Arsse\ExceptionType;
|
||||||
|
|
||||||
class ValueInfo {
|
class ValueInfo {
|
||||||
// universal
|
// universal
|
||||||
const VALID = 1 << 0;
|
public const VALID = 1 << 0;
|
||||||
const NULL = 1 << 1;
|
public const NULL = 1 << 1;
|
||||||
// integers
|
// integers
|
||||||
const ZERO = 1 << 2;
|
public const ZERO = 1 << 2;
|
||||||
const NEG = 1 << 3;
|
public const NEG = 1 << 3;
|
||||||
const FLOAT = 1 << 4;
|
public const FLOAT = 1 << 4;
|
||||||
// strings
|
// strings
|
||||||
const EMPTY = 1 << 2;
|
public const EMPTY = 1 << 2;
|
||||||
const WHITE = 1 << 3;
|
public const WHITE = 1 << 3;
|
||||||
// normalization types
|
// normalization types
|
||||||
const T_MIXED = 0; // pass through unchanged
|
public const T_MIXED = 0; // pass through unchanged
|
||||||
const T_NULL = 1; // convert to null
|
public const T_NULL = 1; // convert to null
|
||||||
const T_BOOL = 2; // convert to boolean
|
public const T_BOOL = 2; // convert to boolean
|
||||||
const T_INT = 3; // convert to integer
|
public const T_INT = 3; // convert to integer
|
||||||
const T_FLOAT = 4; // convert to floating point
|
public const T_FLOAT = 4; // convert to floating point
|
||||||
const T_DATE = 5; // convert to DateTimeInterface instance
|
public const T_DATE = 5; // convert to DateTimeInterface instance
|
||||||
const T_STRING = 6; // convert to string
|
public const T_STRING = 6; // convert to string
|
||||||
const T_ARRAY = 7; // convert to array
|
public const T_ARRAY = 7; // convert to array
|
||||||
const T_INTERVAL = 8; // convert to time interval
|
public const T_INTERVAL = 8; // convert to time interval
|
||||||
// normalization modes
|
// normalization modes
|
||||||
const M_LOOSE = 0;
|
public const M_LOOSE = 0;
|
||||||
const M_NULL = 1 << 28; // pass nulls through regardless of target type
|
public const M_NULL = 1 << 28; // pass nulls through regardless of target type
|
||||||
const M_DROP = 1 << 29; // drop the value (return null) if the type doesn't match
|
public const M_DROP = 1 << 29; // drop the value (return null) if the type doesn't match
|
||||||
const M_STRICT = 1 << 30; // throw an exception if the type doesn't match
|
public const M_STRICT = 1 << 30; // throw an exception if the type doesn't match
|
||||||
const M_ARRAY = 1 << 31; // the value should be a flat array of values of the specified type; indexed and associative are both acceptable
|
public const M_ARRAY = 1 << 31; // the value should be a flat array of values of the specified type; indexed and associative are both acceptable
|
||||||
// symbolic date and time formats
|
// symbolic date and time formats
|
||||||
const DATE_FORMATS = [ // in out
|
protected const DATE_FORMATS = [ // in out
|
||||||
'iso8601' => ["!Y-m-d\TH:i:s", "Y-m-d\TH:i:s\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
|
'iso8601' => ["!Y-m-d\TH:i:s", "Y-m-d\TH:i:s\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
|
||||||
'iso8601m' => ["!Y-m-d\TH:i:s.u", "Y-m-d\TH:i:s.u\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
|
'iso8601m' => ["!Y-m-d\TH:i:s.u", "Y-m-d\TH:i:s.u\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
|
||||||
'microtime' => ["U.u", "0.u00 U" ], // NOTE: the actual input format at the user level matches the output format; pre-processing is required for PHP not to fail
|
'microtime' => ["U.u", "0.u00 U" ], // NOTE: the actual input format at the user level matches the output format; pre-processing is required for PHP not to fail
|
||||||
|
@ -50,10 +50,10 @@ class ValueInfo {
|
||||||
|
|
||||||
public static function normalize($value, int $type, string $dateInFormat = null, $dateOutFormat = null) {
|
public static function normalize($value, int $type, string $dateInFormat = null, $dateOutFormat = null) {
|
||||||
$allowNull = ($type & self::M_NULL);
|
$allowNull = ($type & self::M_NULL);
|
||||||
$strict = ($type & (self::M_STRICT | self::M_DROP));
|
$strict = ($type & (self::M_STRICT | self::M_DROP));
|
||||||
$drop = ($type & self::M_DROP);
|
$drop = ($type & self::M_DROP);
|
||||||
$arrayVal = ($type & self::M_ARRAY);
|
$arrayVal = ($type & self::M_ARRAY);
|
||||||
$type = ($type & ~(self::M_NULL | self::M_DROP | self::M_STRICT | self::M_ARRAY));
|
$type = ($type & ~(self::M_NULL | self::M_DROP | self::M_STRICT | self::M_ARRAY));
|
||||||
// if the value is null and this is allowed, simply return
|
// if the value is null and this is allowed, simply return
|
||||||
if ($allowNull && is_null($value)) {
|
if ($allowNull && is_null($value)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -166,7 +166,7 @@ class ValueInfo {
|
||||||
throw new ExceptionType("strictFailure", $type);
|
throw new ExceptionType("strictFailure", $type);
|
||||||
}
|
}
|
||||||
$out = filter_var($value, \FILTER_VALIDATE_FLOAT);
|
$out = filter_var($value, \FILTER_VALIDATE_FLOAT);
|
||||||
if ($strict && $out===false) {
|
if ($strict && $out === false) {
|
||||||
// if strict and input is not a float, this is an error
|
// if strict and input is not a float, this is an error
|
||||||
if ($drop) {
|
if ($drop) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -499,7 +499,7 @@ class ValueInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function bool($value, bool $default = null) {
|
public static function bool($value, bool $default = null): ?bool {
|
||||||
if (is_null($value) || ValueInfo::str($value) & ValueInfo::WHITE) {
|
if (is_null($value) || ValueInfo::str($value) & ValueInfo::WHITE) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
15
lib/REST.php
15
lib/REST.php
|
@ -6,17 +6,16 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Arsse;
|
|
||||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
use JKingWeb\Arsse\Misc\URL;
|
use JKingWeb\Arsse\Misc\URL;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Laminas\Diactoros\ServerRequestFactory;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class REST {
|
class REST {
|
||||||
const API_LIST = [
|
public const API_LIST = [
|
||||||
'ncn' => [ // Nextcloud News version enumerator
|
'ncn' => [ // Nextcloud News version enumerator
|
||||||
'match' => '/index.php/apps/news/api',
|
'match' => '/index.php/apps/news/api',
|
||||||
'strip' => '/index.php/apps/news/api',
|
'strip' => '/index.php/apps/news/api',
|
||||||
|
@ -66,7 +65,7 @@ class REST {
|
||||||
// Proprietary (centralized) entities:
|
// Proprietary (centralized) entities:
|
||||||
// Feedly https://developer.feedly.com/
|
// Feedly https://developer.feedly.com/
|
||||||
];
|
];
|
||||||
const DEFAULT_PORTS = [
|
protected const DEFAULT_PORTS = [
|
||||||
'http' => 80,
|
'http' => 80,
|
||||||
'https' => 443,
|
'https' => 443,
|
||||||
];
|
];
|
||||||
|
@ -81,7 +80,7 @@ class REST {
|
||||||
$req = $req ?? ServerRequestFactory::fromGlobals();
|
$req = $req ?? ServerRequestFactory::fromGlobals();
|
||||||
// find the API to handle
|
// find the API to handle
|
||||||
try {
|
try {
|
||||||
list($api, $target, $class) = $this->apiMatch($req->getRequestTarget(), $this->apis);
|
[$api, $target, $class] = $this->apiMatch($req->getRequestTarget(), $this->apis);
|
||||||
// authenticate the request pre-emptively
|
// authenticate the request pre-emptively
|
||||||
$req = $this->authenticateRequest($req);
|
$req = $this->authenticateRequest($req);
|
||||||
// modify the request to have an uppercase method and a stripped target
|
// modify the request to have an uppercase method and a stripped target
|
||||||
|
@ -118,7 +117,7 @@ class REST {
|
||||||
// find a match
|
// find a match
|
||||||
foreach ($map as $id => $api) {
|
foreach ($map as $id => $api) {
|
||||||
// first try a simple substring match
|
// first try a simple substring match
|
||||||
if (strpos($url, $api['match'])===0) {
|
if (strpos($url, $api['match']) === 0) {
|
||||||
// if it matches, perform a more rigorous match and then strip off any defined prefix
|
// if it matches, perform a more rigorous match and then strip off any defined prefix
|
||||||
$pattern = "<^".preg_quote($api['match'])."([/\?#]|$)>";
|
$pattern = "<^".preg_quote($api['match'])."([/\?#]|$)>";
|
||||||
if ($url === $api['match'] || in_array(substr($api['match'], -1, 1), ["/", "?", "#"]) || preg_match($pattern, $url)) {
|
if ($url === $api['match'] || in_array(substr($api['match'], -1, 1), ["/", "?", "#"]) || preg_match($pattern, $url)) {
|
||||||
|
@ -213,7 +212,7 @@ class REST {
|
||||||
if ($req->hasHeader("Access-Control-Request-Headers")) {
|
if ($req->hasHeader("Access-Control-Request-Headers")) {
|
||||||
$res = $res->withHeader("Access-Control-Allow-Headers", $req->getHeaderLine("Access-Control-Request-Headers"));
|
$res = $res->withHeader("Access-Control-Allow-Headers", $req->getHeaderLine("Access-Control-Request-Headers"));
|
||||||
}
|
}
|
||||||
$res = $res->withHeader("Access-Control-Max-Age", (string) (60 *60 *24)); // one day
|
$res = $res->withHeader("Access-Control-Max-Age", (string) (60 * 60 * 24)); // one day
|
||||||
}
|
}
|
||||||
$res = $res->withHeader("Access-Control-Allow-Origin", $req->getHeaderLine("Origin"));
|
$res = $res->withHeader("Access-Control-Allow-Origin", $req->getHeaderLine("Origin"));
|
||||||
$res = $res->withHeader("Access-Control-Allow-Credentials", "true");
|
$res = $res->withHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
|
|
@ -14,20 +14,20 @@ use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
use JKingWeb\Arsse\Misc\HTTP;
|
use JKingWeb\Arsse\Misc\HTTP;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
use Zend\Diactoros\Response\XmlResponse;
|
use Laminas\Diactoros\Response\XmlResponse;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
const LEVEL = 3;
|
public const LEVEL = 3;
|
||||||
const GENERIC_ICON_TYPE = "image/png;base64";
|
protected const GENERIC_ICON_TYPE = "image/png;base64";
|
||||||
const GENERIC_ICON_DATA = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==";
|
protected const GENERIC_ICON_DATA = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==";
|
||||||
const ACCEPTED_TYPE = "application/x-www-form-urlencoded";
|
protected const ACCEPTED_TYPES = ["application/x-www-form-urlencoded", "multipart/form-data"];
|
||||||
|
|
||||||
// GET parameters for which we only check presence: these will be converted to booleans
|
// GET parameters for which we only check presence: these will be converted to booleans
|
||||||
const PARAM_BOOL = ["groups", "feeds", "items", "favicons", "links", "unread_item_ids", "saved_item_ids"];
|
protected const PARAM_BOOL = ["groups", "feeds", "items", "favicons", "links", "unread_item_ids", "saved_item_ids"];
|
||||||
// GET parameters which contain meaningful values
|
// GET parameters which contain meaningful values
|
||||||
const PARAM_GET = [
|
protected const PARAM_GET = [
|
||||||
'api' => V::T_STRING, // this parameter requires special handling
|
'api' => V::T_STRING, // this parameter requires special handling
|
||||||
'page' => V::T_INT, // parameter for hot links
|
'page' => V::T_INT, // parameter for hot links
|
||||||
'range' => V::T_INT, // parameter for hot links
|
'range' => V::T_INT, // parameter for hot links
|
||||||
|
@ -45,7 +45,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
'unread_recently_read' => V::T_BOOL,
|
'unread_recently_read' => V::T_BOOL,
|
||||||
];
|
];
|
||||||
// POST parameters, all of which contain meaningful values
|
// POST parameters, all of which contain meaningful values
|
||||||
const PARAM_POST = [
|
protected const PARAM_POST = [
|
||||||
'api_key' => V::T_STRING,
|
'api_key' => V::T_STRING,
|
||||||
'mark' => V::T_STRING,
|
'mark' => V::T_STRING,
|
||||||
'as' => V::T_STRING,
|
'as' => V::T_STRING,
|
||||||
|
@ -67,16 +67,17 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
switch ($req->getMethod()) {
|
switch ($req->getMethod()) {
|
||||||
case "OPTIONS":
|
case "OPTIONS":
|
||||||
return new EmptyResponse(204, [
|
return new EmptyResponse(204, [
|
||||||
'Allow' => "POST",
|
'Allow' => "POST",
|
||||||
'Accept' => self::ACCEPTED_TYPE,
|
'Accept' => implode(", ", self::ACCEPTED_TYPES),
|
||||||
]);
|
]);
|
||||||
|
case "GET": // HTTP violation required for client "Unread" on iOS
|
||||||
case "POST":
|
case "POST":
|
||||||
if (!HTTP::matchType($req, self::ACCEPTED_TYPE, "")) {
|
if (!HTTP::matchType($req, "", ...self::ACCEPTED_TYPES)) {
|
||||||
return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]);
|
return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES)]);
|
||||||
}
|
}
|
||||||
$out = [
|
$out = [
|
||||||
'api_version' => self::LEVEL,
|
'api_version' => self::LEVEL,
|
||||||
'auth' => 0,
|
'auth' => 0,
|
||||||
];
|
];
|
||||||
if ($req->getAttribute("authenticated", false)) {
|
if ($req->getAttribute("authenticated", false)) {
|
||||||
// if HTTP authentication was successfully used, set the expected user ID
|
// if HTTP authentication was successfully used, set the expected user ID
|
||||||
|
@ -153,7 +154,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// we provide a single blank favicon for now
|
// we provide a single blank favicon for now
|
||||||
$out['favicons'] = [
|
$out['favicons'] = [
|
||||||
[
|
[
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'data' => self::GENERIC_ICON_TYPE.",".self::GENERIC_ICON_DATA,
|
'data' => self::GENERIC_ICON_TYPE.",".self::GENERIC_ICON_DATA,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -178,7 +179,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
protected function baseResponse(bool $authenticated): array {
|
protected function baseResponse(bool $authenticated): array {
|
||||||
$out = [
|
$out = [
|
||||||
'api_version' => self::LEVEL,
|
'api_version' => self::LEVEL,
|
||||||
'auth' => (int) $authenticated,
|
'auth' => (int) $authenticated,
|
||||||
];
|
];
|
||||||
if ($authenticated) {
|
if ($authenticated) {
|
||||||
// authenticated requests always include the most recent feed refresh
|
// authenticated requests always include the most recent feed refresh
|
||||||
|
@ -306,7 +307,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return $listSaved;
|
return $listSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setUnread() {
|
protected function setUnread(): void {
|
||||||
$lastUnread = Arsse::$db->articleList(Arsse::$user->id, (new Context)->limit(1), ["marked_date"], ["marked_date desc"])->getValue();
|
$lastUnread = Arsse::$db->articleList(Arsse::$user->id, (new Context)->limit(1), ["marked_date"], ["marked_date desc"])->getValue();
|
||||||
if (!$lastUnread) {
|
if (!$lastUnread) {
|
||||||
// there are no articles
|
// there are no articles
|
||||||
|
@ -322,7 +323,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
Arsse::$db->articleMark(Arsse::$user->id, ['read' => false], $c);
|
Arsse::$db->articleMark(Arsse::$user->id, ['read' => false], $c);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getRefreshTime() {
|
protected function getRefreshTime(): ?int {
|
||||||
return Date::transform(Arsse::$db->subscriptionRefreshed(Arsse::$user->id), "unix");
|
return Date::transform(Arsse::$db->subscriptionRefreshed(Arsse::$user->id), "unix");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +347,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$out = [];
|
$out = [];
|
||||||
foreach (Arsse::$db->tagList(Arsse::$user->id) as $member) {
|
foreach (Arsse::$db->tagList(Arsse::$user->id) as $member) {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'id' => (int) $member['id'],
|
'id' => (int) $member['id'],
|
||||||
'title' => $member['name'],
|
'title' => $member['name'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,18 @@ use JKingWeb\Arsse\AbstractException;
|
||||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
||||||
use JKingWeb\Arsse\Misc\HTTP;
|
use JKingWeb\Arsse\Misc\HTTP;
|
||||||
|
use JKingWeb\Arsse\REST\Exception;
|
||||||
use JKingWeb\Arsse\REST\Exception404;
|
use JKingWeb\Arsse\REST\Exception404;
|
||||||
use JKingWeb\Arsse\REST\Exception405;
|
use JKingWeb\Arsse\REST\Exception405;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\JsonResponse as Response;
|
use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
const REALM = "Nextcloud News API v1-2";
|
public const VERSION = "11.0.5";
|
||||||
const VERSION = "11.0.5";
|
protected const REALM = "Nextcloud News API v1-2";
|
||||||
const ACCEPTED_TYPE = "application/json";
|
protected const ACCEPTED_TYPE = "application/json";
|
||||||
|
|
||||||
protected $dateFormat = "unix";
|
protected $dateFormat = "unix";
|
||||||
|
|
||||||
|
@ -214,7 +215,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return $feed;
|
return $feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function articleTranslate(array $article) :array {
|
protected function articleTranslate(array $article): array {
|
||||||
// map fields to proper names
|
// map fields to proper names
|
||||||
$article = $this->fieldMapNames($article, [
|
$article = $this->fieldMapNames($article, [
|
||||||
'id' => "edition",
|
'id' => "edition",
|
||||||
|
@ -615,7 +616,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$c = new Context;
|
$c = new Context;
|
||||||
$c->article((int) $url[2]);
|
$c->article((int) $url[2]);
|
||||||
// determine whether to mark read or unread
|
// determine whether to mark read or unread
|
||||||
$set = ($url[3] ==="star");
|
$set = ($url[3] === "star");
|
||||||
try {
|
try {
|
||||||
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
|
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
|
||||||
} catch (ExceptionInput $e) {
|
} catch (ExceptionInput $e) {
|
||||||
|
@ -628,7 +629,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// mark an array of articles as read
|
// mark an array of articles as read
|
||||||
protected function articleMarkReadMulti(array $url, array $data): ResponseInterface {
|
protected function articleMarkReadMulti(array $url, array $data): ResponseInterface {
|
||||||
// determine whether to mark read or unread
|
// determine whether to mark read or unread
|
||||||
$set = ($url[1] ==="read");
|
$set = ($url[1] === "read");
|
||||||
// initialize the matching context
|
// initialize the matching context
|
||||||
$c = new Context;
|
$c = new Context;
|
||||||
$c->editions($data['items'] ?? []);
|
$c->editions($data['items'] ?? []);
|
||||||
|
@ -642,7 +643,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// mark an array of articles as starred
|
// mark an array of articles as starred
|
||||||
protected function articleMarkStarredMulti(array $url, array $data): ResponseInterface {
|
protected function articleMarkStarredMulti(array $url, array $data): ResponseInterface {
|
||||||
// determine whether to mark starred or unstarred
|
// determine whether to mark starred or unstarred
|
||||||
$set = ($url[1] ==="star");
|
$set = ($url[1] === "star");
|
||||||
// initialize the matching context
|
// initialize the matching context
|
||||||
$c = new Context;
|
$c = new Context;
|
||||||
$c->articles(array_column($data['items'] ?? [], "guidHash"));
|
$c->articles(array_column($data['items'] ?? [], "guidHash"));
|
||||||
|
@ -655,10 +656,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
|
|
||||||
protected function userStatus(array $url, array $data): ResponseInterface {
|
protected function userStatus(array $url, array $data): ResponseInterface {
|
||||||
return new Response([
|
return new Response([
|
||||||
'userId' => (string) Arsse::$user->id,
|
'userId' => (string) Arsse::$user->id,
|
||||||
'displayName' => (string) Arsse::$user->id,
|
'displayName' => (string) Arsse::$user->id,
|
||||||
'lastLoginTimestamp' => time(),
|
'lastLoginTimestamp' => time(),
|
||||||
'avatar' => null,
|
'avatar' => null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,19 +676,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// return the server version
|
// return the server version
|
||||||
protected function serverVersion(array $url, array $data): ResponseInterface {
|
protected function serverVersion(array $url, array $data): ResponseInterface {
|
||||||
return new Response([
|
return new Response([
|
||||||
'version' => self::VERSION,
|
'version' => self::VERSION,
|
||||||
'arsse_version' => Arsse::VERSION,
|
'arsse_version' => Arsse::VERSION,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(array $url, array $data): ResponseInterface {
|
protected function serverStatus(array $url, array $data): ResponseInterface {
|
||||||
return new Response([
|
return new Response([
|
||||||
'version' => self::VERSION,
|
'version' => self::VERSION,
|
||||||
'arsse_version' => Arsse::VERSION,
|
'arsse_version' => Arsse::VERSION,
|
||||||
'warnings' => [
|
'warnings' => [
|
||||||
'improperlyConfiguredCron' => !Service::hasCheckedIn(),
|
'improperlyConfiguredCron' => !Service::hasCheckedIn(),
|
||||||
'incorrectDbCharset' => !Arsse::$db->driverCharsetAcceptable(),
|
'incorrectDbCharset' => !Arsse::$db->driverCharsetAcceptable(),
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ namespace JKingWeb\Arsse\REST\NextcloudNews;
|
||||||
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\JsonResponse as Response;
|
use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class Versions implements \JKingWeb\Arsse\REST\Handler {
|
class Versions implements \JKingWeb\Arsse\REST\Handler {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
@ -29,7 +29,7 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
|
||||||
$out = [
|
$out = [
|
||||||
'apiLevels' => [
|
'apiLevels' => [
|
||||||
'v1-2',
|
'v1-2',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
return new Response($out);
|
return new Response($out);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -20,31 +20,31 @@ use JKingWeb\Arsse\Db\ResultEmpty;
|
||||||
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\JsonResponse as Response;
|
use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
const LEVEL = 14; // emulated API level
|
public const LEVEL = 14; // emulated API level
|
||||||
const VERSION = "17.4"; // emulated TT-RSS version
|
public const VERSION = "17.4"; // emulated TT-RSS version
|
||||||
const LABEL_OFFSET = 1024; // offset below zero at which labels begin, counting down
|
protected const LABEL_OFFSET = 1024; // offset below zero at which labels begin, counting down
|
||||||
const LIMIT_ARTICLES = 200; // maximum number of articles returned by getHeadlines
|
protected const LIMIT_ARTICLES = 200; // maximum number of articles returned by getHeadlines
|
||||||
const LIMIT_EXCERPT = 100; // maximum length of excerpts in getHeadlines, counted in grapheme units
|
protected const LIMIT_EXCERPT = 100; // maximum length of excerpts in getHeadlines, counted in grapheme units
|
||||||
// special feeds
|
// special feeds
|
||||||
const FEED_ARCHIVED = 0;
|
protected const FEED_ARCHIVED = 0;
|
||||||
const FEED_STARRED = -1;
|
protected const FEED_STARRED = -1;
|
||||||
const FEED_PUBLISHED = -2;
|
protected const FEED_PUBLISHED = -2;
|
||||||
const FEED_FRESH = -3;
|
protected const FEED_FRESH = -3;
|
||||||
const FEED_ALL = -4;
|
protected const FEED_ALL = -4;
|
||||||
const FEED_READ = -6;
|
protected const FEED_READ = -6;
|
||||||
// special categories
|
// special categories
|
||||||
const CAT_UNCATEGORIZED = 0;
|
protected const CAT_UNCATEGORIZED = 0;
|
||||||
const CAT_SPECIAL = -1;
|
protected const CAT_SPECIAL = -1;
|
||||||
const CAT_LABELS = -2;
|
protected const CAT_LABELS = -2;
|
||||||
const CAT_NOT_SPECIAL = -3;
|
protected const CAT_NOT_SPECIAL = -3;
|
||||||
const CAT_ALL = -4;
|
protected const CAT_ALL = -4;
|
||||||
// valid input
|
// valid input
|
||||||
const ACCEPTED_TYPES = ["application/json", "text/json"];
|
protected const ACCEPTED_TYPES = ["application/json", "text/json"];
|
||||||
const VALID_INPUT = [
|
protected const VALID_INPUT = [
|
||||||
'op' => ValueInfo::T_STRING, // the function ("operation") to perform
|
'op' => ValueInfo::T_STRING, // the function ("operation") to perform
|
||||||
'sid' => ValueInfo::T_STRING, // session ID
|
'sid' => ValueInfo::T_STRING, // session ID
|
||||||
'seq' => ValueInfo::T_INT, // request number from client
|
'seq' => ValueInfo::T_INT, // request number from client
|
||||||
|
@ -82,7 +82,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
'data' => ValueInfo::T_STRING, // note text in `updateArticle` if setting a note
|
'data' => ValueInfo::T_STRING, // note text in `updateArticle` if setting a note
|
||||||
];
|
];
|
||||||
// generic error construct
|
// generic error construct
|
||||||
const FATAL_ERR = [
|
protected const FATAL_ERR = [
|
||||||
'seq' => null,
|
'seq' => null,
|
||||||
'status' => 1,
|
'status' => 1,
|
||||||
'content' => ['error' => "MALFORMED_INPUT"],
|
'content' => ['error' => "MALFORMED_INPUT"],
|
||||||
|
@ -135,14 +135,14 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
throw new Exception("UNKNOWN_METHOD", ['method' => $data['op']]);
|
throw new Exception("UNKNOWN_METHOD", ['method' => $data['op']]);
|
||||||
}
|
}
|
||||||
return new Response([
|
return new Response([
|
||||||
'seq' => $data['seq'],
|
'seq' => $data['seq'],
|
||||||
'status' => 0,
|
'status' => 0,
|
||||||
'content' => $this->$method($data),
|
'content' => $this->$method($data),
|
||||||
]);
|
]);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return new Response([
|
return new Response([
|
||||||
'seq' => $data['seq'],
|
'seq' => $data['seq'],
|
||||||
'status' => 1,
|
'status' => 1,
|
||||||
'content' => $e->getData(),
|
'content' => $e->getData(),
|
||||||
]);
|
]);
|
||||||
} catch (AbstractException $e) {
|
} catch (AbstractException $e) {
|
||||||
|
@ -190,7 +190,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// are not enforced, create a session for the HTTP user regardless
|
// are not enforced, create a session for the HTTP user regardless
|
||||||
// of which user the API call mentions
|
// of which user the API call mentions
|
||||||
$id = Arsse::$db->sessionCreate(Arsse::$user->id);
|
$id = Arsse::$db->sessionCreate(Arsse::$user->id);
|
||||||
} elseif ((!Arsse::$conf->userPreAuth && (Arsse::$user->auth($user, $pass) || Arsse::$user->auth($user, base64_decode($pass)))) || (Arsse::$conf->userPreAuth && Arsse::$user->id===$user)) {
|
} elseif ((!Arsse::$conf->userPreAuth && (Arsse::$user->auth($user, $pass) || Arsse::$user->auth($user, base64_decode($pass)))) || (Arsse::$conf->userPreAuth && Arsse::$user->id === $user)) {
|
||||||
// otherwise both cleartext and base64 passwords are accepted
|
// otherwise both cleartext and base64 passwords are accepted
|
||||||
// if pre-authentication is in use, just make sure the user names match
|
// if pre-authentication is in use, just make sure the user names match
|
||||||
$id = Arsse::$db->sessionCreate($user);
|
$id = Arsse::$db->sessionCreate($user);
|
||||||
|
@ -199,7 +199,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
'session_id' => $id,
|
'session_id' => $id,
|
||||||
'api_level' => self::LEVEL
|
'api_level' => self::LEVEL,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,10 +215,10 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
|
|
||||||
public function opGetConfig(array $data): array {
|
public function opGetConfig(array $data): array {
|
||||||
return [
|
return [
|
||||||
'icons_dir' => "feed-icons",
|
'icons_dir' => "feed-icons",
|
||||||
'icons_url' => "feed-icons",
|
'icons_url' => "feed-icons",
|
||||||
'daemon_is_running' => Service::hasCheckedIn(),
|
'daemon_is_running' => Service::hasCheckedIn(),
|
||||||
'num_feeds' => Arsse::$db->subscriptionCount(Arsse::$user->id),
|
'num_feeds' => Arsse::$db->subscriptionCount(Arsse::$user->id),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return array_merge($special, $labels, $feeds, $cats);
|
return array_merge($special, $labels, $feeds, $cats);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function opGetFeedTree(array $data) : array {
|
public function opGetFeedTree(array $data): array {
|
||||||
$all = $data['include_empty'] ?? false;
|
$all = $data['include_empty'] ?? false;
|
||||||
$user = Arsse::$user->id;
|
$user = Arsse::$user->id;
|
||||||
$tSpecial = [
|
$tSpecial = [
|
||||||
|
@ -319,55 +319,55 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$subs = Arsse::$db->subscriptionList($user)->getAll();
|
$subs = Arsse::$db->subscriptionList($user)->getAll();
|
||||||
// start with the special feeds
|
// start with the special feeds
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Category.Special"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Category.Special"),
|
||||||
'id' => "CAT:".self::CAT_SPECIAL,
|
'id' => "CAT:".self::CAT_SPECIAL,
|
||||||
'bare_id' => self::CAT_SPECIAL,
|
'bare_id' => self::CAT_SPECIAL,
|
||||||
'type' => "category",
|
'type' => "category",
|
||||||
'unread' => 0,
|
'unread' => 0,
|
||||||
'items' => [
|
'items' => [
|
||||||
array_merge([ // All articles
|
array_merge([ // All articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.All"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.All"),
|
||||||
'id' => "FEED:".self::FEED_ALL,
|
'id' => "FEED:".self::FEED_ALL,
|
||||||
'bare_id' => self::FEED_ALL,
|
'bare_id' => self::FEED_ALL,
|
||||||
'icon' => "images/folder.png",
|
'icon' => "images/folder.png",
|
||||||
'unread' => array_reduce($subs, function($sum, $value) {
|
'unread' => array_reduce($subs, function($sum, $value) {
|
||||||
return $sum + $value['unread'];
|
return $sum + $value['unread'];
|
||||||
}, 0), // the sum of all feeds' unread is the total unread
|
}, 0), // the sum of all feeds' unread is the total unread
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Fresh articles
|
array_merge([ // Fresh articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Fresh"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Fresh"),
|
||||||
'id' => "FEED:".self::FEED_FRESH,
|
'id' => "FEED:".self::FEED_FRESH,
|
||||||
'bare_id' => self::FEED_FRESH,
|
'bare_id' => self::FEED_FRESH,
|
||||||
'icon' => "images/fresh.png",
|
'icon' => "images/fresh.png",
|
||||||
'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))),
|
'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H"))),
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Starred articles
|
array_merge([ // Starred articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"),
|
||||||
'id' => "FEED:".self::FEED_STARRED,
|
'id' => "FEED:".self::FEED_STARRED,
|
||||||
'bare_id' => self::FEED_STARRED,
|
'bare_id' => self::FEED_STARRED,
|
||||||
'icon' => "images/star.png",
|
'icon' => "images/star.png",
|
||||||
'unread' => (int) Arsse::$db->articleStarred($user)['unread'],
|
'unread' => (int) Arsse::$db->articleStarred($user)['unread'],
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Published articles
|
array_merge([ // Published articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Published"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Published"),
|
||||||
'id' => "FEED:".self::FEED_PUBLISHED,
|
'id' => "FEED:".self::FEED_PUBLISHED,
|
||||||
'bare_id' => self::FEED_PUBLISHED,
|
'bare_id' => self::FEED_PUBLISHED,
|
||||||
'icon' => "images/feed.png",
|
'icon' => "images/feed.png",
|
||||||
'unread' => 0, // TODO: unread count should be populated if the Published feed is ever implemented
|
'unread' => 0, // TODO: unread count should be populated if the Published feed is ever implemented
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Archived articles
|
array_merge([ // Archived articles
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Archived"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Archived"),
|
||||||
'id' => "FEED:".self::FEED_ARCHIVED,
|
'id' => "FEED:".self::FEED_ARCHIVED,
|
||||||
'bare_id' => self::FEED_ARCHIVED,
|
'bare_id' => self::FEED_ARCHIVED,
|
||||||
'icon' => "images/archive.png",
|
'icon' => "images/archive.png",
|
||||||
'unread' => 0, // Article archiving is not exposed by the API, so this is always zero
|
'unread' => 0, // Article archiving is not exposed by the API, so this is always zero
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
array_merge([ // Recently read
|
array_merge([ // Recently read
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Read"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Read"),
|
||||||
'id' => "FEED:".self::FEED_READ,
|
'id' => "FEED:".self::FEED_READ,
|
||||||
'bare_id' => self::FEED_READ,
|
'bare_id' => self::FEED_READ,
|
||||||
'icon' => "images/time.png",
|
'icon' => "images/time.png",
|
||||||
'unread' => 0, // this is by definition zero; unread articles do not appear in this feed
|
'unread' => 0, // this is by definition zero; unread articles do not appear in this feed
|
||||||
], $tSpecial),
|
], $tSpecial),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -394,12 +394,12 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// if there are labels, all the label category,
|
// if there are labels, all the label category,
|
||||||
if ($items) {
|
if ($items) {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'name' => Arsse::$lang->msg("API.TTRSS.Category.Labels"),
|
'name' => Arsse::$lang->msg("API.TTRSS.Category.Labels"),
|
||||||
'id' => "CAT:".self::CAT_LABELS,
|
'id' => "CAT:".self::CAT_LABELS,
|
||||||
'bare_id' => self::CAT_LABELS,
|
'bare_id' => self::CAT_LABELS,
|
||||||
'type' => "category",
|
'type' => "category",
|
||||||
'unread' => $unread,
|
'unread' => $unread,
|
||||||
'items' => $items,
|
'items' => $items,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// get the lists of categories and feeds
|
// get the lists of categories and feeds
|
||||||
|
@ -459,7 +459,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// if the category is the wrong level, or if it's empty and we're not including empties, skip it
|
// if the category is the wrong level, or if it's empty and we're not including empties, skip it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$children = $c['children'] ? $this->enumerateCategories($cats, $subs, $c['id'], $all) : ['list' => [], 'feeds' => 0];
|
$children = $c['children'] ? $this->enumerateCategories($cats, $subs, $c['id'], $all) : ['list' => [], 'feeds' => 0];
|
||||||
$feeds = $c['feeds'] ? $this->enumerateFeeds($subs, $c['id']) : [];
|
$feeds = $c['feeds'] ? $this->enumerateFeeds($subs, $c['id']) : [];
|
||||||
$count = sizeof($feeds) + (int) $children['feeds'];
|
$count = sizeof($feeds) + (int) $children['feeds'];
|
||||||
$out[] = [
|
$out[] = [
|
||||||
|
@ -843,7 +843,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$subs = Arsse::$db->subscriptionList(Arsse::$user->id);
|
$subs = Arsse::$db->subscriptionList(Arsse::$user->id);
|
||||||
$id = false;
|
$id = false;
|
||||||
foreach ($subs as $sub) {
|
foreach ($subs as $sub) {
|
||||||
if ($sub['url']===$url) {
|
if ($sub['url'] === $url) {
|
||||||
$id = (int) $sub['id'];
|
$id = (int) $sub['id'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -940,11 +940,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (abs($id) - self::LABEL_OFFSET);
|
return abs($id) - self::LABEL_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function labelOut($id): int {
|
protected function labelOut($id): int {
|
||||||
return ((int) $id * -1 - self::LABEL_OFFSET);
|
return (int) $id * -1 - self::LABEL_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function opGetLabels(array $data): array {
|
public function opGetLabels(array $data): array {
|
||||||
|
@ -1201,32 +1201,32 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
];
|
];
|
||||||
foreach (Arsse::$db->articleList(Arsse::$user->id, (new Context)->articles($articles), $columns) as $article) {
|
foreach (Arsse::$db->articleList(Arsse::$user->id, (new Context)->articles($articles), $columns) as $article) {
|
||||||
$out[] = [
|
$out[] = [
|
||||||
'id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
'id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
||||||
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : null,
|
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : null,
|
||||||
'title' => $article['title'],
|
'title' => $article['title'],
|
||||||
'link' => $article['url'],
|
'link' => $article['url'],
|
||||||
'labels' => $this->articleLabelList($labels, $article['id']),
|
'labels' => $this->articleLabelList($labels, $article['id']),
|
||||||
'unread' => (bool) $article['unread'],
|
'unread' => (bool) $article['unread'],
|
||||||
'marked' => (bool) $article['starred'],
|
'marked' => (bool) $article['starred'],
|
||||||
'published' => false, // TODO: if the Published feed is implemented, the getArticle operation should be amended accordingly
|
'published' => false, // TODO: if the Published feed is implemented, the getArticle operation should be amended accordingly
|
||||||
'comments' => "", // FIXME: What is this?
|
'comments' => "", // FIXME: What is this?
|
||||||
'author' => $article['author'],
|
'author' => $article['author'],
|
||||||
'updated' => Date::transform($article['edited_date'], "unix", "sql"),
|
'updated' => Date::transform($article['edited_date'], "unix", "sql"),
|
||||||
'feed_id' => (string) $article['subscription'], // string cast to be consistent with TTRSS
|
'feed_id' => (string) $article['subscription'], // string cast to be consistent with TTRSS
|
||||||
'feed_title' => $article['subscription_title'],
|
'feed_title' => $article['subscription_title'],
|
||||||
'attachments' => $article['media_url'] ? [[
|
'attachments' => $article['media_url'] ? [[
|
||||||
'id' => (string) 0, // string cast to be consistent with TTRSS; nonsense ID because we don't use them for enclosures
|
'id' => (string) 0, // string cast to be consistent with TTRSS; nonsense ID because we don't use them for enclosures
|
||||||
'content_url' => $article['media_url'],
|
'content_url' => $article['media_url'],
|
||||||
'content_type' => $article['media_type'],
|
'content_type' => $article['media_type'],
|
||||||
'title' => "",
|
'title' => "",
|
||||||
'duration' => "",
|
'duration' => "",
|
||||||
'width' => "",
|
'width' => "",
|
||||||
'height' => "",
|
'height' => "",
|
||||||
'post_id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
'post_id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
||||||
]] : [], // TODO: We need to support multiple enclosures
|
]] : [], // TODO: We need to support multiple enclosures
|
||||||
'score' => 0, // score is not implemented as it is not modifiable from the TTRSS API
|
'score' => 0, // score is not implemented as it is not modifiable from the TTRSS API
|
||||||
'note' => strlen((string) $article['note']) ? $article['note'] : null,
|
'note' => strlen((string) $article['note']) ? $article['note'] : null,
|
||||||
'lang' => "", // FIXME: picoFeed should be able to retrieve this information
|
'lang' => "", // FIXME: picoFeed should be able to retrieve this information
|
||||||
'content' => $article['content'],
|
'content' => $article['content'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1302,25 +1302,25 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
];
|
];
|
||||||
foreach ($this->fetchArticles($data, $columns) as $article) {
|
foreach ($this->fetchArticles($data, $columns) as $article) {
|
||||||
$row = [
|
$row = [
|
||||||
'id' => (int) $article['id'],
|
'id' => (int) $article['id'],
|
||||||
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : "",
|
'guid' => $article['guid'] ? "SHA256:".$article['guid'] : "",
|
||||||
'title' => $article['title'],
|
'title' => $article['title'],
|
||||||
'link' => $article['url'],
|
'link' => $article['url'],
|
||||||
'labels' => $this->articleLabelList($labels, $article['id']),
|
'labels' => $this->articleLabelList($labels, $article['id']),
|
||||||
'unread' => (bool) $article['unread'],
|
'unread' => (bool) $article['unread'],
|
||||||
'marked' => (bool) $article['starred'],
|
'marked' => (bool) $article['starred'],
|
||||||
'published' => false, // TODO: if the Published feed is implemented, the getHeadlines operation should be amended accordingly
|
'published' => false, // TODO: if the Published feed is implemented, the getHeadlines operation should be amended accordingly
|
||||||
'author' => $article['author'],
|
'author' => $article['author'],
|
||||||
'updated' => Date::transform($article['edited_date'], "unix", "sql"),
|
'updated' => Date::transform($article['edited_date'], "unix", "sql"),
|
||||||
'is_updated' => ($article['published_date'] < $article['edited_date']),
|
'is_updated' => ($article['published_date'] < $article['edited_date']),
|
||||||
'feed_id' => (string) $article['subscription'], // string cast to be consistent with TTRSS
|
'feed_id' => (string) $article['subscription'], // string cast to be consistent with TTRSS
|
||||||
'feed_title' => $article['subscription_title'],
|
'feed_title' => $article['subscription_title'],
|
||||||
'score' => 0, // score is not implemented as it is not modifiable from the TTRSS API
|
'score' => 0, // score is not implemented as it is not modifiable from the TTRSS API
|
||||||
'note' => strlen((string) $article['note']) ? $article['note'] : null,
|
'note' => strlen((string) $article['note']) ? $article['note'] : null,
|
||||||
'lang' => "", // FIXME: picoFeed should be able to retrieve this information
|
'lang' => "", // FIXME: picoFeed should be able to retrieve this information
|
||||||
'tags' => Arsse::$db->articleCategoriesGet(Arsse::$user->id, $article['id']),
|
'tags' => Arsse::$db->articleCategoriesGet(Arsse::$user->id, $article['id']),
|
||||||
'comments_count' => 0,
|
'comments_count' => 0,
|
||||||
'comments_link' => "",
|
'comments_link' => "",
|
||||||
'always_display_attachments' => false,
|
'always_display_attachments' => false,
|
||||||
];
|
];
|
||||||
if ($data['show_content']) {
|
if ($data['show_content']) {
|
||||||
|
@ -1336,14 +1336,14 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
if ($data['include_attachments']) {
|
if ($data['include_attachments']) {
|
||||||
$row['attachments'] = $article['media_url'] ? [[
|
$row['attachments'] = $article['media_url'] ? [[
|
||||||
'id' => (string) 0, // string cast to be consistent with TTRSS; nonsense ID because we don't use them for enclosures
|
'id' => (string) 0, // string cast to be consistent with TTRSS; nonsense ID because we don't use them for enclosures
|
||||||
'content_url' => $article['media_url'],
|
'content_url' => $article['media_url'],
|
||||||
'content_type' => $article['media_type'],
|
'content_type' => $article['media_type'],
|
||||||
'title' => "",
|
'title' => "",
|
||||||
'duration' => "",
|
'duration' => "",
|
||||||
'width' => "",
|
'width' => "",
|
||||||
'height' => "",
|
'height' => "",
|
||||||
'post_id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
'post_id' => (string) $article['id'], // string cast to be consistent with TTRSS
|
||||||
]] : []; // TODO: We need to support multiple enclosures
|
]] : []; // TODO: We need to support multiple enclosures
|
||||||
}
|
}
|
||||||
$out[] = $row;
|
$out[] = $row;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\REST\TinyTinyRSS;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\EmptyResponse as Response;
|
use Laminas\Diactoros\Response\EmptyResponse as Response;
|
||||||
|
|
||||||
class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
|
|
@ -10,33 +10,32 @@ use JKingWeb\Arsse\Context\Context;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
|
|
||||||
class Search {
|
class Search {
|
||||||
const STATE_BEFORE_TOKEN = 0;
|
protected const STATE_BEFORE_TOKEN = 0;
|
||||||
const STATE_BEFORE_TOKEN_QUOTED = 1;
|
protected const STATE_BEFORE_TOKEN_QUOTED = 1;
|
||||||
const STATE_IN_DATE = 2;
|
protected const STATE_IN_DATE = 2;
|
||||||
const STATE_IN_DATE_QUOTED = 3;
|
protected const STATE_IN_DATE_QUOTED = 3;
|
||||||
const STATE_IN_TOKEN_OR_TAG = 4;
|
protected const STATE_IN_TOKEN_OR_TAG = 4;
|
||||||
const STATE_IN_TOKEN_OR_TAG_QUOTED = 5;
|
protected const STATE_IN_TOKEN_OR_TAG_QUOTED = 5;
|
||||||
const STATE_IN_TOKEN = 6;
|
protected const STATE_IN_TOKEN = 6;
|
||||||
const STATE_IN_TOKEN_QUOTED = 7;
|
protected const STATE_IN_TOKEN_QUOTED = 7;
|
||||||
|
|
||||||
const FIELDS_BOOLEAN = [
|
protected const FIELDS_BOOLEAN = [
|
||||||
"unread" => "unread",
|
"unread" => "unread",
|
||||||
"star" => "starred",
|
"star" => "starred",
|
||||||
"note" => "annotated",
|
"note" => "annotated",
|
||||||
"pub" => "published", // TODO: not implemented
|
"pub" => "published", // TODO: not implemented
|
||||||
];
|
];
|
||||||
const FIELDS_TEXT = [
|
protected const FIELDS_TEXT = [
|
||||||
"title" => "titleTerms",
|
"title" => "titleTerms",
|
||||||
"author" => "authorTerms",
|
"author" => "authorTerms",
|
||||||
"note" => "annotationTerms",
|
"note" => "annotationTerms",
|
||||||
"" => "searchTerms",
|
"" => "searchTerms",
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function parse(string $search, Context $context = null) {
|
public static function parse(string $search, Context $context = null): ?Context {
|
||||||
// normalize the input
|
// normalize the input
|
||||||
$search = strtolower(trim(preg_replace("<\s+>", " ", $search)));
|
$search = strtolower(trim(preg_replace("<\s+>", " ", $search)));
|
||||||
// set initial state
|
// set initial state
|
||||||
$tokens = [];
|
|
||||||
$pos = -1;
|
$pos = -1;
|
||||||
$stop = strlen($search);
|
$stop = strlen($search);
|
||||||
$state = self::STATE_BEFORE_TOKEN;
|
$state = self::STATE_BEFORE_TOKEN;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
|
|
||||||
class Service {
|
class Service {
|
||||||
const DRIVER_NAMES = [
|
public const DRIVER_NAMES = [
|
||||||
'serial' => \JKingWeb\Arsse\Service\Serial\Driver::class,
|
'serial' => \JKingWeb\Arsse\Service\Serial\Driver::class,
|
||||||
'subprocess' => \JKingWeb\Arsse\Service\Subprocess\Driver::class,
|
'subprocess' => \JKingWeb\Arsse\Service\Subprocess\Driver::class,
|
||||||
];
|
];
|
||||||
|
@ -66,7 +66,7 @@ class Service {
|
||||||
$limit->sub($int);
|
$limit->sub($int);
|
||||||
$limit->sub($int);
|
$limit->sub($int);
|
||||||
// return whether the check-in time is within the acceptable limit
|
// return whether the check-in time is within the acceptable limit
|
||||||
return ($checkin >= $limit);
|
return $checkin >= $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cleanupPre(): bool {
|
public static function cleanupPre(): bool {
|
||||||
|
|
|
@ -9,15 +9,13 @@ namespace JKingWeb\Arsse;
|
||||||
use PasswordGenerator\Generator as PassGen;
|
use PasswordGenerator\Generator as PassGen;
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
const DRIVER_NAMES = [
|
public const DRIVER_NAMES = [
|
||||||
'internal' => \JKingWeb\Arsse\User\Internal\Driver::class,
|
'internal' => \JKingWeb\Arsse\User\Internal\Driver::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public $id = null;
|
public $id = null;
|
||||||
|
|
||||||
/**
|
/** @var User\Driver */
|
||||||
* @var User\Driver
|
|
||||||
*/
|
|
||||||
protected $u;
|
protected $u;
|
||||||
|
|
||||||
public function __construct(\JKingWeb\Arsse\User\Driver $driver = null) {
|
public function __construct(\JKingWeb\Arsse\User\Driver $driver = null) {
|
||||||
|
|
|
@ -7,9 +7,9 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\User;
|
namespace JKingWeb\Arsse\User;
|
||||||
|
|
||||||
interface Driver {
|
interface Driver {
|
||||||
const FUNC_NOT_IMPLEMENTED = 0;
|
public const FUNC_NOT_IMPLEMENTED = 0;
|
||||||
const FUNC_INTERNAL = 1;
|
public const FUNC_INTERNAL = 1;
|
||||||
const FUNC_EXTERNAL = 2;
|
public const FUNC_EXTERNAL = 2;
|
||||||
|
|
||||||
// returns an instance of a class implementing this interface.
|
// returns an instance of a class implementing this interface.
|
||||||
public function __construct();
|
public function __construct();
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($password==="" && $hash==="") {
|
if ($password === "" && $hash === "") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return password_verify($password, $hash);
|
return password_verify($password, $hash);
|
||||||
|
@ -40,7 +40,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
return Arsse::$db->userExists($user);
|
return Arsse::$db->userExists($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userAdd(string $user, string $password = null) {
|
public function userAdd(string $user, string $password = null): ?string {
|
||||||
if (isset($password)) {
|
if (isset($password)) {
|
||||||
// only add the user if the password is not null; the user manager will retry with a generated password if null is returned
|
// only add the user if the password is not null; the user manager will retry with a generated password if null is returned
|
||||||
Arsse::$db->userAdd($user, $password);
|
Arsse::$db->userAdd($user, $password);
|
||||||
|
@ -56,7 +56,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
return Arsse::$db->userList();
|
return Arsse::$db->userList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null) {
|
public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): ?string {
|
||||||
// do nothing: the internal database is updated regardless of what the driver does (assuming it does not throw an exception)
|
// do nothing: the internal database is updated regardless of what the driver does (assuming it does not throw an exception)
|
||||||
return $newPassword;
|
return $newPassword;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function userPasswordGet(string $user) {
|
protected function userPasswordGet(string $user): ?string {
|
||||||
return Arsse::$db->userPasswordGet($user);
|
return Arsse::$db->userPasswordGet($user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,7 @@ return [
|
||||||
other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}}
|
other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}}
|
||||||
}',
|
}',
|
||||||
'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist',
|
'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist',
|
||||||
|
'Exception.JKingWeb/Arsse/Feed/Exception.internalError' => 'Could not download feed "{url}" because of an internal error which is probably a bug',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
|
'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.invalidUrl' => 'Feed URL "{url}" is invalid',
|
'Exception.JKingWeb/Arsse/Feed/Exception.invalidUrl' => 'Feed URL "{url}" is invalid',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
|
'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
|
||||||
|
@ -151,6 +152,8 @@ return [
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out',
|
'Exception.JKingWeb/Arsse/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',
|
'Exception.JKingWeb/Arsse/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials',
|
'Exception.JKingWeb/Arsse/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials',
|
||||||
|
'Exception.JKingWeb/Arsse/Feed/Exception.transmissionError' => 'Could not download feed "{url}" because of a network error',
|
||||||
|
'Exception.JKingWeb/Arsse/Feed/Exception.connectionFailed' => 'Could not download feed "{url}" because its server could not be reached',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.malformedXml' => 'Could not parse feed "{url}" because it is malformed',
|
'Exception.JKingWeb/Arsse/Feed/Exception.malformedXml' => 'Could not parse feed "{url}" because it is malformed',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack',
|
'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack',
|
||||||
'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
|
'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
|
||||||
|
|
29
package.json
29
package.json
|
@ -1,16 +1,17 @@
|
||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^9.6.1",
|
"autoprefixer": "^9.6.1",
|
||||||
"postcss-cli": "^6.1.3",
|
"postcss": "^7.0.0",
|
||||||
"postcss-color-function": "^4.1.0",
|
"postcss-cli": "^7.1.1",
|
||||||
"postcss-csso": "^3.0.0",
|
"postcss-color-function": "^4.1.0",
|
||||||
"postcss-custom-media": "^7.0.8",
|
"postcss-csso": "^4.0.0",
|
||||||
"postcss-custom-properties": "^9.0.2",
|
"postcss-custom-media": "^7.0.8",
|
||||||
"postcss-discard-comments": "^4.0.2",
|
"postcss-custom-properties": "^9.0.2",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-discard-comments": "^4.0.2",
|
||||||
"postcss-media-minmax": "^4.0.0",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-nested": "^4.1.2",
|
"postcss-media-minmax": "^4.0.0",
|
||||||
"postcss-sassy-mixins": "^2.1.0",
|
"postcss-nested": "^4.1.2",
|
||||||
"postcss-scss": "^2.0.0"
|
"postcss-sassy-mixins": "^2.1.0",
|
||||||
}
|
"postcss-scss": "^2.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\CLI;
|
namespace JKingWeb\Arsse\TestCase\CLI;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Conf;
|
use JKingWeb\Arsse\Conf;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
|
@ -18,12 +19,13 @@ use JKingWeb\Arsse\ImportExport\OPML;
|
||||||
/** @covers \JKingWeb\Arsse\CLI */
|
/** @covers \JKingWeb\Arsse\CLI */
|
||||||
class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData(false);
|
self::clearData();
|
||||||
$this->cli = \Phake::partialMock(CLI::class);
|
$this->cli = \Phake::partialMock(CLI::class);
|
||||||
\Phake::when($this->cli)->logError->thenReturn(null);
|
\Phake::when($this->cli)->logError->thenReturn(null);
|
||||||
|
\Phake::when($this->cli)->loadConf->thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assertConsole(CLI $cli, string $command, int $exitStatus, string $output = "", bool $pattern = false) {
|
public function assertConsole(CLI $cli, string $command, int $exitStatus, string $output = "", bool $pattern = false): void {
|
||||||
$argv = \Clue\Arguments\split($command);
|
$argv = \Clue\Arguments\split($command);
|
||||||
$output = strlen($output) ? $output.\PHP_EOL : "";
|
$output = strlen($output) ? $output.\PHP_EOL : "";
|
||||||
if ($pattern) {
|
if ($pattern) {
|
||||||
|
@ -34,27 +36,15 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($exitStatus, $cli->dispatch($argv));
|
$this->assertSame($exitStatus, $cli->dispatch($argv));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assertLoaded(bool $loaded) {
|
public function testPrintVersion(): void {
|
||||||
$r = new \ReflectionClass(Arsse::class);
|
|
||||||
$props = array_keys($r->getStaticProperties());
|
|
||||||
foreach ($props as $prop) {
|
|
||||||
if ($loaded) {
|
|
||||||
$this->assertNotNull(Arsse::$$prop, "Global $prop object should be loaded");
|
|
||||||
} else {
|
|
||||||
$this->assertNull(Arsse::$$prop, "Global $prop object should not be loaded");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPrintVersion() {
|
|
||||||
$this->assertConsole($this->cli, "arsse.php --version", 0, Arsse::VERSION);
|
$this->assertConsole($this->cli, "arsse.php --version", 0, Arsse::VERSION);
|
||||||
$this->assertLoaded(false);
|
\Phake::verify($this->cli, \Phake::times(0))->loadConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideHelpText */
|
/** @dataProvider provideHelpText */
|
||||||
public function testPrintHelp(string $cmd, string $name) {
|
public function testPrintHelp(string $cmd, string $name): void {
|
||||||
$this->assertConsole($this->cli, $cmd, 0, str_replace("arsse.php", $name, CLI::USAGE));
|
$this->assertConsole($this->cli, $cmd, 0, str_replace("arsse.php", $name, CLI::USAGE));
|
||||||
$this->assertLoaded(false);
|
\Phake::verify($this->cli, \Phake::times(0))->loadConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideHelpText(): iterable {
|
public function provideHelpText(): iterable {
|
||||||
|
@ -68,33 +58,33 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStartTheDaemon() {
|
public function testStartTheDaemon(): void {
|
||||||
$srv = \Phake::mock(Service::class);
|
$srv = \Phake::mock(Service::class);
|
||||||
\Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
|
\Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
|
||||||
\Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv);
|
\Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv);
|
||||||
$this->assertConsole($this->cli, "arsse.php daemon", 0);
|
$this->assertConsole($this->cli, "arsse.php daemon", 0);
|
||||||
$this->assertLoaded(true);
|
\Phake::verify($this->cli)->loadConf;
|
||||||
\Phake::verify($srv)->watch(true);
|
\Phake::verify($srv)->watch(true);
|
||||||
\Phake::verify($this->cli)->getInstance(Service::class);
|
\Phake::verify($this->cli)->getInstance(Service::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRefreshAllFeeds() {
|
public function testRefreshAllFeeds(): void {
|
||||||
$srv = \Phake::mock(Service::class);
|
$srv = \Phake::mock(Service::class);
|
||||||
\Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
|
\Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
|
||||||
\Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv);
|
\Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv);
|
||||||
$this->assertConsole($this->cli, "arsse.php feed refresh-all", 0);
|
$this->assertConsole($this->cli, "arsse.php feed refresh-all", 0);
|
||||||
$this->assertLoaded(true);
|
\Phake::verify($this->cli)->loadConf;
|
||||||
\Phake::verify($srv)->watch(false);
|
\Phake::verify($srv)->watch(false);
|
||||||
\Phake::verify($this->cli)->getInstance(Service::class);
|
\Phake::verify($this->cli)->getInstance(Service::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideFeedUpdates */
|
/** @dataProvider provideFeedUpdates */
|
||||||
public function testRefreshAFeed(string $cmd, int $exitStatus, string $output) {
|
public function testRefreshAFeed(string $cmd, int $exitStatus, string $output): void {
|
||||||
Arsse::$db = \Phake::mock(Database::class);
|
Arsse::$db = \Phake::mock(Database::class);
|
||||||
\Phake::when(Arsse::$db)->feedUpdate(1, true)->thenReturn(true);
|
\Phake::when(Arsse::$db)->feedUpdate(1, true)->thenReturn(true);
|
||||||
\Phake::when(Arsse::$db)->feedUpdate(2, true)->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/", new \PicoFeed\Client\InvalidUrlException));
|
\Phake::when(Arsse::$db)->feedUpdate(2, true)->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/", $this->mockGuzzleException(ClientException::class, "", 404)));
|
||||||
$this->assertConsole($this->cli, $cmd, $exitStatus, $output);
|
$this->assertConsole($this->cli, $cmd, $exitStatus, $output);
|
||||||
$this->assertLoaded(true);
|
\Phake::verify($this->cli)->loadConf;
|
||||||
\Phake::verify(Arsse::$db)->feedUpdate;
|
\Phake::verify(Arsse::$db)->feedUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,14 +96,14 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideDefaultConfigurationSaves */
|
/** @dataProvider provideDefaultConfigurationSaves */
|
||||||
public function testSaveTheDefaultConfiguration(string $cmd, int $exitStatus, string $file) {
|
public function testSaveTheDefaultConfiguration(string $cmd, int $exitStatus, string $file): void {
|
||||||
$conf = \Phake::mock(Conf::class);
|
$conf = \Phake::mock(Conf::class);
|
||||||
\Phake::when($conf)->exportFile("php://output", true)->thenReturn(true);
|
\Phake::when($conf)->exportFile("php://output", true)->thenReturn(true);
|
||||||
\Phake::when($conf)->exportFile("good.conf", true)->thenReturn(true);
|
\Phake::when($conf)->exportFile("good.conf", true)->thenReturn(true);
|
||||||
\Phake::when($conf)->exportFile("bad.conf", true)->thenThrow(new \JKingWeb\Arsse\Conf\Exception("fileUnwritable"));
|
\Phake::when($conf)->exportFile("bad.conf", true)->thenThrow(new \JKingWeb\Arsse\Conf\Exception("fileUnwritable"));
|
||||||
\Phake::when($this->cli)->getInstance(Conf::class)->thenReturn($conf);
|
\Phake::when($this->cli)->getInstance(Conf::class)->thenReturn($conf);
|
||||||
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
||||||
$this->assertLoaded(false);
|
\Phake::verify($this->cli, \Phake::times(0))->loadConf;
|
||||||
\Phake::verify($conf)->exportFile($file, true);
|
\Phake::verify($conf)->exportFile($file, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +117,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserList */
|
/** @dataProvider provideUserList */
|
||||||
public function testListUsers(string $cmd, array $list, int $exitStatus, string $output) {
|
public function testListUsers(string $cmd, array $list, int $exitStatus, string $output): void {
|
||||||
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
||||||
Arsse::$user = $this->createMock(User::class);
|
Arsse::$user = $this->createMock(User::class);
|
||||||
Arsse::$user->method("list")->willReturn($list);
|
Arsse::$user->method("list")->willReturn($list);
|
||||||
|
@ -146,7 +136,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserAdditions */
|
/** @dataProvider provideUserAdditions */
|
||||||
public function testAddAUser(string $cmd, int $exitStatus, string $output) {
|
public function testAddAUser(string $cmd, int $exitStatus, string $output): void {
|
||||||
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
||||||
Arsse::$user = $this->createMock(User::class);
|
Arsse::$user = $this->createMock(User::class);
|
||||||
Arsse::$user->method("add")->will($this->returnCallback(function($user, $pass = null) {
|
Arsse::$user->method("add")->will($this->returnCallback(function($user, $pass = null) {
|
||||||
|
@ -169,14 +159,14 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserAuthentication */
|
/** @dataProvider provideUserAuthentication */
|
||||||
public function testAuthenticateAUser(string $cmd, int $exitStatus, string $output) {
|
public function testAuthenticateAUser(string $cmd, int $exitStatus, string $output): void {
|
||||||
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
||||||
Arsse::$user = $this->createMock(User::class);
|
Arsse::$user = $this->createMock(User::class);
|
||||||
Arsse::$user->method("auth")->will($this->returnCallback(function($user, $pass) {
|
Arsse::$user->method("auth")->will($this->returnCallback(function($user, $pass) {
|
||||||
return (
|
return
|
||||||
($user === "john.doe@example.com" && $pass === "secret") ||
|
($user === "john.doe@example.com" && $pass === "secret") ||
|
||||||
($user === "jane.doe@example.com" && $pass === "superman")
|
($user === "jane.doe@example.com" && $pass === "superman")
|
||||||
);
|
;
|
||||||
}));
|
}));
|
||||||
$fever = \Phake::mock(FeverUser::class);
|
$fever = \Phake::mock(FeverUser::class);
|
||||||
\Phake::when($fever)->authenticate->thenReturn(false);
|
\Phake::when($fever)->authenticate->thenReturn(false);
|
||||||
|
@ -203,7 +193,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserRemovals */
|
/** @dataProvider provideUserRemovals */
|
||||||
public function testRemoveAUser(string $cmd, int $exitStatus, string $output) {
|
public function testRemoveAUser(string $cmd, int $exitStatus, string $output): void {
|
||||||
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
||||||
Arsse::$user = $this->createMock(User::class);
|
Arsse::$user = $this->createMock(User::class);
|
||||||
Arsse::$user->method("remove")->will($this->returnCallback(function($user) {
|
Arsse::$user->method("remove")->will($this->returnCallback(function($user) {
|
||||||
|
@ -223,7 +213,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserPasswordChanges */
|
/** @dataProvider provideUserPasswordChanges */
|
||||||
public function testChangeAUserPassword(string $cmd, int $exitStatus, string $output) {
|
public function testChangeAUserPassword(string $cmd, int $exitStatus, string $output): void {
|
||||||
$passwordChange = function($user, $pass = null) {
|
$passwordChange = function($user, $pass = null) {
|
||||||
switch ($user) {
|
switch ($user) {
|
||||||
case "jane.doe@example.com":
|
case "jane.doe@example.com":
|
||||||
|
@ -253,7 +243,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserPasswordClearings */
|
/** @dataProvider provideUserPasswordClearings */
|
||||||
public function testClearAUserPassword(string $cmd, int $exitStatus, string $output) {
|
public function testClearAUserPassword(string $cmd, int $exitStatus, string $output): void {
|
||||||
$passwordClear = function($user) {
|
$passwordClear = function($user) {
|
||||||
switch ($user) {
|
switch ($user) {
|
||||||
case "jane.doe@example.com":
|
case "jane.doe@example.com":
|
||||||
|
@ -281,14 +271,14 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideOpmlExports */
|
/** @dataProvider provideOpmlExports */
|
||||||
public function testExportToOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat) {
|
public function testExportToOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat): void {
|
||||||
$opml = \Phake::mock(OPML::class);
|
$opml = \Phake::mock(OPML::class);
|
||||||
\Phake::when($opml)->exportFile("php://output", $user, $flat)->thenReturn(true);
|
\Phake::when($opml)->exportFile("php://output", $user, $flat)->thenReturn(true);
|
||||||
\Phake::when($opml)->exportFile("good.opml", $user, $flat)->thenReturn(true);
|
\Phake::when($opml)->exportFile("good.opml", $user, $flat)->thenReturn(true);
|
||||||
\Phake::when($opml)->exportFile("bad.opml", $user, $flat)->thenThrow(new \JKingWeb\Arsse\ImportExport\Exception("fileUnwritable"));
|
\Phake::when($opml)->exportFile("bad.opml", $user, $flat)->thenThrow(new \JKingWeb\Arsse\ImportExport\Exception("fileUnwritable"));
|
||||||
\Phake::when($this->cli)->getInstance(OPML::class)->thenReturn($opml);
|
\Phake::when($this->cli)->getInstance(OPML::class)->thenReturn($opml);
|
||||||
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
||||||
$this->assertLoaded(true);
|
\Phake::verify($this->cli)->loadConf;
|
||||||
\Phake::verify($opml)->exportFile($file, $user, $flat);
|
\Phake::verify($opml)->exportFile($file, $user, $flat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,14 +312,14 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideOpmlImports */
|
/** @dataProvider provideOpmlImports */
|
||||||
public function testImportFromOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat, bool $replace) {
|
public function testImportFromOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat, bool $replace): void {
|
||||||
$opml = \Phake::mock(OPML::class);
|
$opml = \Phake::mock(OPML::class);
|
||||||
\Phake::when($opml)->importFile("php://input", $user, $flat, $replace)->thenReturn(true);
|
\Phake::when($opml)->importFile("php://input", $user, $flat, $replace)->thenReturn(true);
|
||||||
\Phake::when($opml)->importFile("good.opml", $user, $flat, $replace)->thenReturn(true);
|
\Phake::when($opml)->importFile("good.opml", $user, $flat, $replace)->thenReturn(true);
|
||||||
\Phake::when($opml)->importFile("bad.opml", $user, $flat, $replace)->thenThrow(new \JKingWeb\Arsse\ImportExport\Exception("fileUnreadable"));
|
\Phake::when($opml)->importFile("bad.opml", $user, $flat, $replace)->thenThrow(new \JKingWeb\Arsse\ImportExport\Exception("fileUnreadable"));
|
||||||
\Phake::when($this->cli)->getInstance(OPML::class)->thenReturn($opml);
|
\Phake::when($this->cli)->getInstance(OPML::class)->thenReturn($opml);
|
||||||
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
$this->assertConsole($this->cli, $cmd, $exitStatus);
|
||||||
$this->assertLoaded(true);
|
\Phake::verify($this->cli)->loadConf;
|
||||||
\Phake::verify($opml)->importFile($file, $user, $flat, $replace);
|
\Phake::verify($opml)->importFile($file, $user, $flat, $replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,14 +38,14 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadDefaultValues() {
|
public function testLoadDefaultValues(): void {
|
||||||
$this->assertInstanceOf(Conf::class, new Conf);
|
$this->assertInstanceOf(Conf::class, new Conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testLoadDefaultValues */
|
/** @depends testLoadDefaultValues */
|
||||||
public function testImportFromArray() {
|
public function testImportFromArray(): void {
|
||||||
$arr = [
|
$arr = [
|
||||||
'lang' => "xx",
|
'lang' => "xx",
|
||||||
'purgeFeeds' => "P2D",
|
'purgeFeeds' => "P2D",
|
||||||
];
|
];
|
||||||
$conf = new Conf;
|
$conf = new Conf;
|
||||||
|
@ -54,7 +54,7 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromArray */
|
/** @depends testImportFromArray */
|
||||||
public function testImportFromFile() {
|
public function testImportFromFile(): void {
|
||||||
$conf = new Conf;
|
$conf = new Conf;
|
||||||
$conf->importFile(self::$path."confGood");
|
$conf->importFile(self::$path."confGood");
|
||||||
$this->assertEquals("xx", $conf->lang);
|
$this->assertEquals("xx", $conf->lang);
|
||||||
|
@ -63,43 +63,43 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromMissingFile() {
|
public function testImportFromMissingFile(): void {
|
||||||
$this->assertException("fileMissing", "Conf");
|
$this->assertException("fileMissing", "Conf");
|
||||||
$conf = new Conf(self::$path."confMissing");
|
$conf = new Conf(self::$path."confMissing");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromEmptyFile() {
|
public function testImportFromEmptyFile(): void {
|
||||||
$this->assertException("fileCorrupt", "Conf");
|
$this->assertException("fileCorrupt", "Conf");
|
||||||
$conf = new Conf(self::$path."confEmpty");
|
$conf = new Conf(self::$path."confEmpty");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromFileWithoutReadPermission() {
|
public function testImportFromFileWithoutReadPermission(): void {
|
||||||
$this->assertException("fileUnreadable", "Conf");
|
$this->assertException("fileUnreadable", "Conf");
|
||||||
$conf = new Conf(self::$path."confUnreadable");
|
$conf = new Conf(self::$path."confUnreadable");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromFileWhichIsNotAnArray() {
|
public function testImportFromFileWhichIsNotAnArray(): void {
|
||||||
$this->assertException("fileCorrupt", "Conf");
|
$this->assertException("fileCorrupt", "Conf");
|
||||||
$conf = new Conf(self::$path."confNotArray");
|
$conf = new Conf(self::$path."confNotArray");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromFileWhichIsNotPhp() {
|
public function testImportFromFileWhichIsNotPhp(): void {
|
||||||
$this->assertException("fileCorrupt", "Conf");
|
$this->assertException("fileCorrupt", "Conf");
|
||||||
// this should not print the output of the non-PHP file
|
// this should not print the output of the non-PHP file
|
||||||
$conf = new Conf(self::$path."confNotPHP");
|
$conf = new Conf(self::$path."confNotPHP");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testImportFromFile */
|
/** @depends testImportFromFile */
|
||||||
public function testImportFromCorruptFile() {
|
public function testImportFromCorruptFile(): void {
|
||||||
$this->assertException("fileCorrupt", "Conf");
|
$this->assertException("fileCorrupt", "Conf");
|
||||||
$conf = new Conf(self::$path."confCorrupt");
|
$conf = new Conf(self::$path."confCorrupt");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportBogusValue() {
|
public function testImportBogusValue(): void {
|
||||||
$arr = [
|
$arr = [
|
||||||
'dbAutoUpdate' => "yes, please",
|
'dbAutoUpdate' => "yes, please",
|
||||||
];
|
];
|
||||||
|
@ -108,7 +108,7 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$conf->import($arr);
|
$conf->import($arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportBogusDriver() {
|
public function testImportBogusDriver(): void {
|
||||||
$arr = [
|
$arr = [
|
||||||
'dbDriver' => "this driver does not exist",
|
'dbDriver' => "this driver does not exist",
|
||||||
];
|
];
|
||||||
|
@ -117,7 +117,7 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$conf->import($arr);
|
$conf->import($arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToArray() {
|
public function testExportToArray(): void {
|
||||||
$conf = new Conf;
|
$conf = new Conf;
|
||||||
$conf->lang = ["en", "fr"]; // should not be exported: not scalar
|
$conf->lang = ["en", "fr"]; // should not be exported: not scalar
|
||||||
$conf->dbSQLite3File = "test.db"; // should be exported: value changed
|
$conf->dbSQLite3File = "test.db"; // should be exported: value changed
|
||||||
|
@ -125,9 +125,9 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$conf->serviceFrequency = new \DateInterval("PT1H"); // should be exported (as string): value changed
|
$conf->serviceFrequency = new \DateInterval("PT1H"); // should be exported (as string): value changed
|
||||||
$conf->someCustomProperty = "Look at me!"; // should be exported: unknown property
|
$conf->someCustomProperty = "Look at me!"; // should be exported: unknown property
|
||||||
$exp = [
|
$exp = [
|
||||||
'dbSQLite3File' => "test.db",
|
'dbSQLite3File' => "test.db",
|
||||||
'userDriver' => null,
|
'userDriver' => null,
|
||||||
'serviceFrequency' => "PT1H",
|
'serviceFrequency' => "PT1H",
|
||||||
'someCustomProperty' => "Look at me!",
|
'someCustomProperty' => "Look at me!",
|
||||||
];
|
];
|
||||||
$this->assertSame($exp, $conf->export());
|
$this->assertSame($exp, $conf->export());
|
||||||
|
@ -138,7 +138,7 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
/** @depends testExportToArray
|
/** @depends testExportToArray
|
||||||
* @depends testImportFromFile */
|
* @depends testImportFromFile */
|
||||||
public function testExportToFile() {
|
public function testExportToFile(): void {
|
||||||
$conf = new Conf;
|
$conf = new Conf;
|
||||||
$conf->lang = ["en", "fr"]; // should not be exported: not scalar
|
$conf->lang = ["en", "fr"]; // should not be exported: not scalar
|
||||||
$conf->dbSQLite3File = "test.db"; // should be exported: value changed
|
$conf->dbSQLite3File = "test.db"; // should be exported: value changed
|
||||||
|
@ -147,8 +147,8 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$conf->exportFile(self::$path."confNotArray");
|
$conf->exportFile(self::$path."confNotArray");
|
||||||
$arr = (include self::$path."confNotArray");
|
$arr = (include self::$path."confNotArray");
|
||||||
$exp = [
|
$exp = [
|
||||||
'dbSQLite3File' => "test.db",
|
'dbSQLite3File' => "test.db",
|
||||||
'userDriver' => null,
|
'userDriver' => null,
|
||||||
'someCustomProperty' => "Look at me!",
|
'someCustomProperty' => "Look at me!",
|
||||||
];
|
];
|
||||||
$this->assertSame($exp, $arr);
|
$this->assertSame($exp, $arr);
|
||||||
|
@ -159,19 +159,19 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testExportToFile */
|
/** @depends testExportToFile */
|
||||||
public function testExportToStdout() {
|
public function testExportToStdout(): void {
|
||||||
$conf = new Conf(self::$path."confGood");
|
$conf = new Conf(self::$path."confGood");
|
||||||
$conf->exportFile(self::$path."confGood");
|
$conf->exportFile(self::$path."confGood");
|
||||||
$this->expectOutputString(file_get_contents(self::$path."confGood"));
|
$this->expectOutputString(file_get_contents(self::$path."confGood"));
|
||||||
$conf->exportFile("php://output");
|
$conf->exportFile("php://output");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToFileWithoutWritePermission() {
|
public function testExportToFileWithoutWritePermission(): void {
|
||||||
$this->assertException("fileUnwritable", "Conf");
|
$this->assertException("fileUnwritable", "Conf");
|
||||||
(new Conf)->exportFile(self::$path."confUnreadable");
|
(new Conf)->exportFile(self::$path."confUnreadable");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToFileWithoutCreatePermission() {
|
public function testExportToFileWithoutCreatePermission(): void {
|
||||||
$this->assertException("fileUncreatable", "Conf");
|
$this->assertException("fileUncreatable", "Conf");
|
||||||
(new Conf)->exportFile(self::$path."confForbidden/conf");
|
(new Conf)->exportFile(self::$path."confForbidden/conf");
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,22 +7,24 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\Database;
|
namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use DateTimeImmutable as Date;
|
||||||
|
|
||||||
trait SeriesCleanup {
|
trait SeriesCleanup {
|
||||||
protected function setUpSeriesCleanup() {
|
protected function setUpSeriesCleanup(): void {
|
||||||
// set up the configuration
|
// set up the configuration
|
||||||
Arsse::$conf->import([
|
Arsse::$conf->import([
|
||||||
'userSessionTimeout' => "PT1H",
|
'userSessionTimeout' => "PT1H",
|
||||||
'userSessionLifetime' => "PT24H",
|
'userSessionLifetime' => "PT24H",
|
||||||
]);
|
]);
|
||||||
// set up the test data
|
// set up the test data
|
||||||
$nowish = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
$tz = new \DateTimeZone("UTC");
|
||||||
$yesterday = gmdate("Y-m-d H:i:s", strtotime("now - 1 day"));
|
$nowish = (new Date("now - 1 minute", $tz))->format("Y-m-d H:i:s");
|
||||||
$daybefore = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
$yesterday = (new Date("now - 1 day", $tz))->format("Y-m-d H:i:s");
|
||||||
$daysago = gmdate("Y-m-d H:i:s", strtotime("now - 7 days"));
|
$daybefore = (new Date("now - 2 days", $tz))->format("Y-m-d H:i:s");
|
||||||
$weeksago = gmdate("Y-m-d H:i:s", strtotime("now - 21 days"));
|
$daysago = (new Date("now - 7 days", $tz))->format("Y-m-d H:i:s");
|
||||||
$soon = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
$weeksago = (new Date("now - 21 days", $tz))->format("Y-m-d H:i:s");
|
||||||
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
$soon = (new Date("now + 1 minute", $tz))->format("Y-m-d H:i:s");
|
||||||
|
$faroff = (new Date("now + 1 hour", $tz))->format("Y-m-d H:i:s");
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -53,7 +55,7 @@ trait SeriesCleanup {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => "str",
|
'id' => "str",
|
||||||
'class' => "str",
|
'class' => "str",
|
||||||
'user' => "str",
|
'user' => "str",
|
||||||
'expires' => "datetime",
|
'expires' => "datetime",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
|
@ -76,7 +78,7 @@ trait SeriesCleanup {
|
||||||
[2,"http://example.com/2","",$yesterday,0],
|
[2,"http://example.com/2","",$yesterday,0],
|
||||||
[3,"http://example.com/3","",null,0],
|
[3,"http://example.com/3","",null,0],
|
||||||
[4,"http://example.com/4","",$nowish,0],
|
[4,"http://example.com/4","",$nowish,0],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -89,7 +91,7 @@ trait SeriesCleanup {
|
||||||
[1,'jane.doe@example.com',1],
|
[1,'jane.doe@example.com',1],
|
||||||
// other subscriptions exist for article cleanup tests
|
// other subscriptions exist for article cleanup tests
|
||||||
[2,'john.doe@example.com',1],
|
[2,'john.doe@example.com',1],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_articles' => [
|
'arsse_articles' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -110,7 +112,7 @@ trait SeriesCleanup {
|
||||||
[7,1,"","","",$weeksago], // meets the unread threshold without marks, thus is deleted
|
[7,1,"","","",$weeksago], // meets the unread threshold without marks, thus is deleted
|
||||||
[8,1,"","","",$weeksago], // meets the unread threshold even with marks, thus is deleted
|
[8,1,"","","",$weeksago], // meets the unread threshold even with marks, thus is deleted
|
||||||
[9,1,"","","",$weeksago], // meets the read threshold, thus is deleted
|
[9,1,"","","",$weeksago], // meets the read threshold, thus is deleted
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_editions' => [
|
'arsse_editions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -124,7 +126,7 @@ trait SeriesCleanup {
|
||||||
[4,4],
|
[4,4],
|
||||||
[201,1],
|
[201,1],
|
||||||
[102,2],
|
[102,2],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_marks' => [
|
'arsse_marks' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -142,20 +144,20 @@ trait SeriesCleanup {
|
||||||
[8,1,1,0,$weeksago],
|
[8,1,1,0,$weeksago],
|
||||||
[9,1,1,0,$daysago],
|
[9,1,1,0,$daysago],
|
||||||
[9,2,1,0,$daysago],
|
[9,2,1,0,$daysago],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesCleanup() {
|
protected function tearDownSeriesCleanup(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOrphanedFeeds() {
|
public function testCleanUpOrphanedFeeds(): void {
|
||||||
Arsse::$db->feedCleanup();
|
Arsse::$db->feedCleanup();
|
||||||
$now = gmdate("Y-m-d H:i:s");
|
$now = gmdate("Y-m-d H:i:s");
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_feeds' => ["id","orphaned"]
|
'arsse_feeds' => ["id","orphaned"],
|
||||||
]);
|
]);
|
||||||
$state['arsse_feeds']['rows'][0][1] = null;
|
$state['arsse_feeds']['rows'][0][1] = null;
|
||||||
unset($state['arsse_feeds']['rows'][1]);
|
unset($state['arsse_feeds']['rows'][1]);
|
||||||
|
@ -163,24 +165,24 @@ trait SeriesCleanup {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOrphanedFeedsWithUnlimitedRetention() {
|
public function testCleanUpOrphanedFeedsWithUnlimitedRetention(): void {
|
||||||
Arsse::$conf->import([
|
Arsse::$conf->import([
|
||||||
'purgeFeeds' => null,
|
'purgeFeeds' => null,
|
||||||
]);
|
]);
|
||||||
Arsse::$db->feedCleanup();
|
Arsse::$db->feedCleanup();
|
||||||
$now = gmdate("Y-m-d H:i:s");
|
$now = gmdate("Y-m-d H:i:s");
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_feeds' => ["id","orphaned"]
|
'arsse_feeds' => ["id","orphaned"],
|
||||||
]);
|
]);
|
||||||
$state['arsse_feeds']['rows'][0][1] = null;
|
$state['arsse_feeds']['rows'][0][1] = null;
|
||||||
$state['arsse_feeds']['rows'][2][1] = $now;
|
$state['arsse_feeds']['rows'][2][1] = $now;
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOldArticlesWithStandardRetention() {
|
public function testCleanUpOldArticlesWithStandardRetention(): void {
|
||||||
Arsse::$db->articleCleanup();
|
Arsse::$db->articleCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_articles' => ["id"]
|
'arsse_articles' => ["id"],
|
||||||
]);
|
]);
|
||||||
foreach ([7,8,9] as $id) {
|
foreach ([7,8,9] as $id) {
|
||||||
unset($state['arsse_articles']['rows'][$id - 1]);
|
unset($state['arsse_articles']['rows'][$id - 1]);
|
||||||
|
@ -188,13 +190,13 @@ trait SeriesCleanup {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOldArticlesWithUnlimitedReadRetention() {
|
public function testCleanUpOldArticlesWithUnlimitedReadRetention(): void {
|
||||||
Arsse::$conf->import([
|
Arsse::$conf->import([
|
||||||
'purgeArticlesRead' => null,
|
'purgeArticlesRead' => null,
|
||||||
]);
|
]);
|
||||||
Arsse::$db->articleCleanup();
|
Arsse::$db->articleCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_articles' => ["id"]
|
'arsse_articles' => ["id"],
|
||||||
]);
|
]);
|
||||||
foreach ([7,8] as $id) {
|
foreach ([7,8] as $id) {
|
||||||
unset($state['arsse_articles']['rows'][$id - 1]);
|
unset($state['arsse_articles']['rows'][$id - 1]);
|
||||||
|
@ -202,13 +204,13 @@ trait SeriesCleanup {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOldArticlesWithUnlimitedUnreadRetention() {
|
public function testCleanUpOldArticlesWithUnlimitedUnreadRetention(): void {
|
||||||
Arsse::$conf->import([
|
Arsse::$conf->import([
|
||||||
'purgeArticlesUnread' => null,
|
'purgeArticlesUnread' => null,
|
||||||
]);
|
]);
|
||||||
Arsse::$db->articleCleanup();
|
Arsse::$db->articleCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_articles' => ["id"]
|
'arsse_articles' => ["id"],
|
||||||
]);
|
]);
|
||||||
foreach ([9] as $id) {
|
foreach ([9] as $id) {
|
||||||
unset($state['arsse_articles']['rows'][$id - 1]);
|
unset($state['arsse_articles']['rows'][$id - 1]);
|
||||||
|
@ -216,22 +218,22 @@ trait SeriesCleanup {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpOldArticlesWithUnlimitedRetention() {
|
public function testCleanUpOldArticlesWithUnlimitedRetention(): void {
|
||||||
Arsse::$conf->import([
|
Arsse::$conf->import([
|
||||||
'purgeArticlesRead' => null,
|
'purgeArticlesRead' => null,
|
||||||
'purgeArticlesUnread' => null,
|
'purgeArticlesUnread' => null,
|
||||||
]);
|
]);
|
||||||
Arsse::$db->articleCleanup();
|
Arsse::$db->articleCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_articles' => ["id"]
|
'arsse_articles' => ["id"],
|
||||||
]);
|
]);
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpExpiredSessions() {
|
public function testCleanUpExpiredSessions(): void {
|
||||||
Arsse::$db->sessionCleanup();
|
Arsse::$db->sessionCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_sessions' => ["id"]
|
'arsse_sessions' => ["id"],
|
||||||
]);
|
]);
|
||||||
foreach ([3,4,5] as $id) {
|
foreach ([3,4,5] as $id) {
|
||||||
unset($state['arsse_sessions']['rows'][$id - 1]);
|
unset($state['arsse_sessions']['rows'][$id - 1]);
|
||||||
|
@ -239,10 +241,10 @@ trait SeriesCleanup {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCleanUpExpiredTokens() {
|
public function testCleanUpExpiredTokens(): void {
|
||||||
Arsse::$db->tokenCleanup();
|
Arsse::$db->tokenCleanup();
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_tokens' => ["id", "class"]
|
'arsse_tokens' => ["id", "class"],
|
||||||
]);
|
]);
|
||||||
foreach ([2] as $id) {
|
foreach ([2] as $id) {
|
||||||
unset($state['arsse_tokens']['rows'][$id - 1]);
|
unset($state['arsse_tokens']['rows'][$id - 1]);
|
||||||
|
|
|
@ -9,9 +9,9 @@ namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
trait SeriesFeed {
|
trait SeriesFeed {
|
||||||
protected function setUpSeriesFeed() {
|
protected function setUpSeriesFeed(): void {
|
||||||
// set up the test data
|
// set up the test data
|
||||||
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
||||||
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
||||||
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
|
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
|
||||||
$this->data = [
|
$this->data = [
|
||||||
|
@ -42,7 +42,7 @@ trait SeriesFeed {
|
||||||
[3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0],
|
[3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0],
|
||||||
[4,"http://localhost:8000/Feed/NextFetch/NotModified?t=".time(),"Ooook",0,"",$past,$past,0],
|
[4,"http://localhost:8000/Feed/NextFetch/NotModified?t=".time(),"Ooook",0,"",$past,$past,0],
|
||||||
[5,"http://localhost:8000/Feed/Parsing/Valid","Ooook",0,"",$past,$future,0],
|
[5,"http://localhost:8000/Feed/Parsing/Valid","Ooook",0,"",$past,$future,0],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -57,7 +57,7 @@ trait SeriesFeed {
|
||||||
[4,'john.doe@example.com',4],
|
[4,'john.doe@example.com',4],
|
||||||
[5,'john.doe@example.com',5],
|
[5,'john.doe@example.com',5],
|
||||||
[6,'jane.doe@example.com',1],
|
[6,'jane.doe@example.com',1],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_articles' => [
|
'arsse_articles' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -82,8 +82,8 @@ trait SeriesFeed {
|
||||||
[4,1,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:00','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',$past],
|
[4,1,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:00','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',$past],
|
||||||
[5,1,'http://example.com/5','Article title 5','','2000-01-05 00:00:00','2000-01-05 00:00:00','<p>Article content 5</p>','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41','d40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022','834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900','43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba',$past],
|
[5,1,'http://example.com/5','Article title 5','','2000-01-05 00:00:00','2000-01-05 00:00:00','<p>Article content 5</p>','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41','d40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022','834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900','43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba',$past],
|
||||||
[6,2,'http://example.com/1','Article title 1','','2000-01-01 00:00:00','2000-01-01 00:00:00','<p>Article content 1</p>','e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda','f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6','fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4','18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',$past],
|
[6,2,'http://example.com/1','Article title 1','','2000-01-01 00:00:00','2000-01-01 00:00:00','<p>Article content 1</p>','e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda','f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6','fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4','18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',$past],
|
||||||
[7,5,'' ,'' ,'','2000-01-01 00:00:00','2000-01-01 00:00:00','' ,'205e986f4f8b3acfa281227beadb14f5e8c32c8dae4737f888c94c0df49c56f8','' ,'' ,'' ,$past],
|
[7,5,'', '', '','2000-01-01 00:00:00','2000-01-01 00:00:00','', '205e986f4f8b3acfa281227beadb14f5e8c32c8dae4737f888c94c0df49c56f8','', '', '', $past],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_editions' => [
|
'arsse_editions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -97,7 +97,7 @@ trait SeriesFeed {
|
||||||
[3,3,$past],
|
[3,3,$past],
|
||||||
[4,4,$past],
|
[4,4,$past],
|
||||||
[5,5,$past],
|
[5,5,$past],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_marks' => [
|
'arsse_marks' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -118,7 +118,7 @@ trait SeriesFeed {
|
||||||
[1,1,1,0,$past],
|
[1,1,1,0,$past],
|
||||||
[3,1,1,0,$past],
|
[3,1,1,0,$past],
|
||||||
[4,1,0,1,$past],
|
[4,1,0,1,$past],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_enclosures' => [
|
'arsse_enclosures' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -128,16 +128,16 @@ trait SeriesFeed {
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[7,'http://example.com/png','image/png'],
|
[7,'http://example.com/png','image/png'],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_categories' => [
|
'arsse_categories' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'article' => "int",
|
'article' => "int",
|
||||||
'name' => "str",
|
'name' => "str",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[7,'Syrinx']
|
[7,'Syrinx'],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
$this->matches = [
|
$this->matches = [
|
||||||
|
@ -160,15 +160,15 @@ trait SeriesFeed {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesFeed() {
|
protected function tearDownSeriesFeed(): void {
|
||||||
unset($this->data, $this->matches);
|
unset($this->data, $this->matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLatestItems() {
|
public function testListLatestItems(): void {
|
||||||
$this->assertResult($this->matches, Arsse::$db->feedMatchLatest(1, 2));
|
$this->assertResult($this->matches, Arsse::$db->feedMatchLatest(1, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMatchItemsById() {
|
public function testMatchItemsById(): void {
|
||||||
$this->assertResult($this->matches, Arsse::$db->feedMatchIds(1, ['804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41']));
|
$this->assertResult($this->matches, Arsse::$db->feedMatchIds(1, ['804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41']));
|
||||||
foreach ($this->matches as $m) {
|
foreach ($this->matches as $m) {
|
||||||
$exp = [$m];
|
$exp = [$m];
|
||||||
|
@ -179,7 +179,7 @@ trait SeriesFeed {
|
||||||
$this->assertResult([['id' => 1]], Arsse::$db->feedMatchIds(1, ['e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda'])); // this ID appears in both feed 1 and feed 2; only one result should be returned
|
$this->assertResult([['id' => 1]], Arsse::$db->feedMatchIds(1, ['e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda'])); // this ID appears in both feed 1 and feed 2; only one result should be returned
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateAFeed() {
|
public function testUpdateAFeed(): void {
|
||||||
// update a valid feed with both new and changed items
|
// update a valid feed with both new and changed items
|
||||||
Arsse::$db->feedUpdate(1);
|
Arsse::$db->feedUpdate(1);
|
||||||
$now = gmdate("Y-m-d H:i:s");
|
$now = gmdate("Y-m-d H:i:s");
|
||||||
|
@ -191,7 +191,7 @@ trait SeriesFeed {
|
||||||
]);
|
]);
|
||||||
$state['arsse_articles']['rows'][2] = [3,1,'http://example.com/3','Article title 3 (updated)','','2000-01-03 00:00:00','2000-01-03 00:00:00','<p>Article content 3</p>','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','6cc99be662ef3486fef35a890123f18d74c29a32d714802d743c5b4ef713315a','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','d5faccc13bf8267850a1e8e61f95950a0f34167df2c8c58011c0aaa6367026ac',$now];
|
$state['arsse_articles']['rows'][2] = [3,1,'http://example.com/3','Article title 3 (updated)','','2000-01-03 00:00:00','2000-01-03 00:00:00','<p>Article content 3</p>','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','6cc99be662ef3486fef35a890123f18d74c29a32d714802d743c5b4ef713315a','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','d5faccc13bf8267850a1e8e61f95950a0f34167df2c8c58011c0aaa6367026ac',$now];
|
||||||
$state['arsse_articles']['rows'][3] = [4,1,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:01','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',$now];
|
$state['arsse_articles']['rows'][3] = [4,1,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:01','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',$now];
|
||||||
$state['arsse_articles']['rows'][] = [8,1,'http://example.com/6','Article title 6','','2000-01-06 00:00:00','2000-01-06 00:00:00','<p>Article content 6</p>','b3461ab8e8759eeb1d65a818c65051ec00c1dfbbb32a3c8f6999434e3e3b76ab','91d051a8e6749d014506848acd45e959af50bf876427c4f0e3a1ec0f04777b51','211d78b1a040d40d17e747a363cc283f58767b2e502630d8de9b8f1d5e941d18','5ed68ccb64243b8c1931241d2c9276274c3b1d87f223634aa7a1ab0141292ca7',$now];
|
$state['arsse_articles']['rows'][] = [8,1,'http://example.com/6','Article title 6','','2000-01-06 00:00:00','2000-01-06 00:00:00','<p>Article content 6</p>','b3461ab8e8759eeb1d65a818c65051ec00c1dfbbb32a3c8f6999434e3e3b76ab','91d051a8e6749d014506848acd45e959af50bf876427c4f0e3a1ec0f04777b51','211d78b1a040d40d17e747a363cc283f58767b2e502630d8de9b8f1d5e941d18','5ed68ccb64243b8c1931241d2c9276274c3b1d87f223634aa7a1ab0141292ca7',$now];
|
||||||
$state['arsse_editions']['rows'] = array_merge($state['arsse_editions']['rows'], [
|
$state['arsse_editions']['rows'] = array_merge($state['arsse_editions']['rows'], [
|
||||||
[6,8,$now],
|
[6,8,$now],
|
||||||
[7,3,$now],
|
[7,3,$now],
|
||||||
|
@ -219,22 +219,22 @@ trait SeriesFeed {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateAMissingFeed() {
|
public function testUpdateAMissingFeed(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->feedUpdate(2112);
|
Arsse::$db->feedUpdate(2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateAnInvalidFeed() {
|
public function testUpdateAnInvalidFeed(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->feedUpdate(-1);
|
Arsse::$db->feedUpdate(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateAFeedThrowingExceptions() {
|
public function testUpdateAFeedThrowingExceptions(): void {
|
||||||
$this->assertException("invalidUrl", "Feed");
|
$this->assertException("invalidUrl", "Feed");
|
||||||
Arsse::$db->feedUpdate(3, true);
|
Arsse::$db->feedUpdate(3, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateAFeedWithEnclosuresAndCategories() {
|
public function testUpdateAFeedWithEnclosuresAndCategories(): void {
|
||||||
Arsse::$db->feedUpdate(5);
|
Arsse::$db->feedUpdate(5);
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
'arsse_enclosures' => ["url","type"],
|
'arsse_enclosures' => ["url","type"],
|
||||||
|
@ -254,7 +254,7 @@ trait SeriesFeed {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListStaleFeeds() {
|
public function testListStaleFeeds(): void {
|
||||||
$this->assertEquals([1,3,4], Arsse::$db->feedListStale());
|
$this->assertEquals([1,3,4], Arsse::$db->feedListStale());
|
||||||
Arsse::$db->feedUpdate(3);
|
Arsse::$db->feedUpdate(3);
|
||||||
Arsse::$db->feedUpdate(4);
|
Arsse::$db->feedUpdate(4);
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
trait SeriesFolder {
|
trait SeriesFolder {
|
||||||
protected function setUpSeriesFolder() {
|
protected function setUpSeriesFolder(): void {
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -45,7 +45,7 @@ trait SeriesFolder {
|
||||||
[4, "jane.doe@example.com", null, "Politics"],
|
[4, "jane.doe@example.com", null, "Politics"],
|
||||||
[5, "john.doe@example.com", null, "Politics"],
|
[5, "john.doe@example.com", null, "Politics"],
|
||||||
[6, "john.doe@example.com", 2, "Politics"],
|
[6, "john.doe@example.com", 2, "Politics"],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -67,7 +67,7 @@ trait SeriesFolder {
|
||||||
[11,"http://example.com/11", "Feed 11"],
|
[11,"http://example.com/11", "Feed 11"],
|
||||||
[12,"http://example.com/12", "Feed 12"],
|
[12,"http://example.com/12", "Feed 12"],
|
||||||
[13,"http://example.com/13", "Feed 13"],
|
[13,"http://example.com/13", "Feed 13"],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -88,16 +88,16 @@ trait SeriesFolder {
|
||||||
[9, "jane.doe@example.com",2, 4],
|
[9, "jane.doe@example.com",2, 4],
|
||||||
[10,"jane.doe@example.com",3, 4],
|
[10,"jane.doe@example.com",3, 4],
|
||||||
[11,"jane.doe@example.com",4, 4],
|
[11,"jane.doe@example.com",4, 4],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesFolder() {
|
protected function tearDownSeriesFolder(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddARootFolder() {
|
public function testAddARootFolder(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$folderID = $this->nextID("arsse_folders");
|
$folderID = $this->nextID("arsse_folders");
|
||||||
$this->assertSame($folderID, Arsse::$db->folderAdd($user, ['name' => "Entertainment"]));
|
$this->assertSame($folderID, Arsse::$db->folderAdd($user, ['name' => "Entertainment"]));
|
||||||
|
@ -107,12 +107,12 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateRootFolder() {
|
public function testAddADuplicateRootFolder(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Politics"]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Politics"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANestedFolder() {
|
public function testAddANestedFolder(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$folderID = $this->nextID("arsse_folders");
|
$folderID = $this->nextID("arsse_folders");
|
||||||
$this->assertSame($folderID, Arsse::$db->folderAdd($user, ['name' => "GNOME", 'parent' => 2]));
|
$this->assertSame($folderID, Arsse::$db->folderAdd($user, ['name' => "GNOME", 'parent' => 2]));
|
||||||
|
@ -122,43 +122,43 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANestedFolderToAMissingParent() {
|
public function testAddANestedFolderToAMissingParent(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 2112]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 2112]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANestedFolderToAnInvalidParent() {
|
public function testAddANestedFolderToAnInvalidParent(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => "stringFolderId"]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => "stringFolderId"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANestedFolderForTheWrongOwner() {
|
public function testAddANestedFolderForTheWrongOwner(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 4]); // folder ID 4 belongs to Jane
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 4]); // folder ID 4 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAFolderWithAMissingName() {
|
public function testAddAFolderWithAMissingName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", []);
|
Arsse::$db->folderAdd("john.doe@example.com", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAFolderWithABlankName() {
|
public function testAddAFolderWithABlankName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => ""]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => ""]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAFolderWithAWhitespaceName() {
|
public function testAddAFolderWithAWhitespaceName(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => " "]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => " "]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAFolderWithoutAuthority() {
|
public function testAddAFolderWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology"]);
|
Arsse::$db->folderAdd("john.doe@example.com", ['name' => "Sociology"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListRootFolders() {
|
public function testListRootFolders(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => 5, 'name' => "Politics", 'parent' => null, 'children' => 0, 'feeds' => 2],
|
['id' => 5, 'name' => "Politics", 'parent' => null, 'children' => 0, 'feeds' => 2],
|
||||||
['id' => 1, 'name' => "Technology", 'parent' => null, 'children' => 2, 'feeds' => 1],
|
['id' => 1, 'name' => "Technology", 'parent' => null, 'children' => 2, 'feeds' => 1],
|
||||||
|
@ -175,7 +175,7 @@ trait SeriesFolder {
|
||||||
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "folderList");
|
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "folderList");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListFoldersRecursively() {
|
public function testListFoldersRecursively(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => 5, 'name' => "Politics", 'parent' => null, 'children' => 0, 'feeds' => 2],
|
['id' => 5, 'name' => "Politics", 'parent' => null, 'children' => 0, 'feeds' => 2],
|
||||||
['id' => 6, 'name' => "Politics", 'parent' => 2, 'children' => 0, 'feeds' => 1],
|
['id' => 6, 'name' => "Politics", 'parent' => 2, 'children' => 0, 'feeds' => 1],
|
||||||
|
@ -196,23 +196,23 @@ trait SeriesFolder {
|
||||||
\Phake::verify(Arsse::$user)->authorize("jane.doe@example.com", "folderList");
|
\Phake::verify(Arsse::$user)->authorize("jane.doe@example.com", "folderList");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListFoldersOfAMissingParent() {
|
public function testListFoldersOfAMissingParent(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderList("john.doe@example.com", 2112);
|
Arsse::$db->folderList("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListFoldersOfTheWrongOwner() {
|
public function testListFoldersOfTheWrongOwner(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderList("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
Arsse::$db->folderList("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListFoldersWithoutAuthority() {
|
public function testListFoldersWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->folderList("john.doe@example.com");
|
Arsse::$db->folderList("john.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAFolder() {
|
public function testRemoveAFolder(): void {
|
||||||
$this->assertTrue(Arsse::$db->folderRemove("john.doe@example.com", 6));
|
$this->assertTrue(Arsse::$db->folderRemove("john.doe@example.com", 6));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderRemove");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
||||||
|
@ -220,7 +220,7 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAFolderTree() {
|
public function testRemoveAFolderTree(): void {
|
||||||
$this->assertTrue(Arsse::$db->folderRemove("john.doe@example.com", 1));
|
$this->assertTrue(Arsse::$db->folderRemove("john.doe@example.com", 1));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderRemove");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
||||||
|
@ -230,28 +230,28 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAMissingFolder() {
|
public function testRemoveAMissingFolder(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderRemove("john.doe@example.com", 2112);
|
Arsse::$db->folderRemove("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidFolder() {
|
public function testRemoveAnInvalidFolder(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderRemove("john.doe@example.com", -1);
|
Arsse::$db->folderRemove("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAFolderOfTheWrongOwner() {
|
public function testRemoveAFolderOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderRemove("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
Arsse::$db->folderRemove("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAFolderWithoutAuthority() {
|
public function testRemoveAFolderWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->folderRemove("john.doe@example.com", 1);
|
Arsse::$db->folderRemove("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAFolder() {
|
public function testGetThePropertiesOfAFolder(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
'id' => 6,
|
'id' => 6,
|
||||||
'name' => "Politics",
|
'name' => "Politics",
|
||||||
|
@ -261,32 +261,32 @@ trait SeriesFolder {
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesGet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesGet");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAMissingFolder() {
|
public function testGetThePropertiesOfAMissingFolder(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesGet("john.doe@example.com", 2112);
|
Arsse::$db->folderPropertiesGet("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidFolder() {
|
public function testGetThePropertiesOfAnInvalidFolder(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesGet("john.doe@example.com", -1);
|
Arsse::$db->folderPropertiesGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAFolderOfTheWrongOwner() {
|
public function testGetThePropertiesOfAFolderOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesGet("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
Arsse::$db->folderPropertiesGet("john.doe@example.com", 4); // folder ID 4 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAFolderWithoutAuthority() {
|
public function testGetThePropertiesOfAFolderWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->folderPropertiesGet("john.doe@example.com", 1);
|
Arsse::$db->folderPropertiesGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeNoChangesToAFolder() {
|
public function testMakeNoChangesToAFolder(): void {
|
||||||
$this->assertFalse(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, []));
|
$this->assertFalse(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameAFolder() {
|
public function testRenameAFolder(): void {
|
||||||
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => "Opinion"]));
|
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => "Opinion"]));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
||||||
|
@ -294,26 +294,26 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameTheRootFolder() {
|
public function testRenameTheRootFolder(): void {
|
||||||
$this->assertFalse(Arsse::$db->folderPropertiesSet("john.doe@example.com", null, ['name' => "Opinion"]));
|
$this->assertFalse(Arsse::$db->folderPropertiesSet("john.doe@example.com", null, ['name' => "Opinion"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameAFolderToTheEmptyString() {
|
public function testRenameAFolderToTheEmptyString(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => ""]));
|
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => ""]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameAFolderToWhitespaceOnly() {
|
public function testRenameAFolderToWhitespaceOnly(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => " "]));
|
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => " "]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameAFolderToAnInvalidValue() {
|
public function testRenameAFolderToAnInvalidValue(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => []]));
|
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['name' => []]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveAFolder() {
|
public function testMoveAFolder(): void {
|
||||||
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['parent' => 5]));
|
$this->assertTrue(Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['parent' => 5]));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
$state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]);
|
||||||
|
@ -321,57 +321,57 @@ trait SeriesFolder {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveTheRootFolder() {
|
public function testMoveTheRootFolder(): void {
|
||||||
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 0, ['parent' => 1]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 0, ['parent' => 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveAFolderToItsDescendant() {
|
public function testMoveAFolderToItsDescendant(): void {
|
||||||
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 3]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveAFolderToItself() {
|
public function testMoveAFolderToItself(): void {
|
||||||
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
$this->assertException("circularDependence", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 1]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveAFolderToAMissingParent() {
|
public function testMoveAFolderToAMissingParent(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 2112]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => 2112]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveAFolderToAnInvalidParent() {
|
public function testMoveAFolderToAnInvalidParent(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => "ThisFolderDoesNotExist"]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => "ThisFolderDoesNotExist"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCauseAFolderCollision() {
|
public function testCauseAFolderCollision(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['parent' => null]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 6, ['parent' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCauseACompoundFolderCollision() {
|
public function testCauseACompoundFolderCollision(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 3, ['parent' => null, 'name' => "Technology"]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 3, ['parent' => null, 'name' => "Technology"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAMissingFolder() {
|
public function testSetThePropertiesOfAMissingFolder(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 2112, ['parent' => null]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 2112, ['parent' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidFolder() {
|
public function testSetThePropertiesOfAnInvalidFolder(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", -1, ['parent' => null]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", -1, ['parent' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAFolderForTheWrongOwner() {
|
public function testSetThePropertiesOfAFolderForTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 4, ['parent' => null]); // folder ID 4 belongs to Jane
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 4, ['parent' => null]); // folder ID 4 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAFolderWithoutAuthority() {
|
public function testSetThePropertiesOfAFolderWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => null]);
|
Arsse::$db->folderPropertiesSet("john.doe@example.com", 1, ['parent' => null]);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\Context\Context;
|
use JKingWeb\Arsse\Context\Context;
|
||||||
|
|
||||||
trait SeriesLabel {
|
trait SeriesLabel {
|
||||||
protected function setUpSeriesLabel() {
|
protected function setUpSeriesLabel(): void {
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -42,7 +42,7 @@ trait SeriesLabel {
|
||||||
[7, "john.doe@example.net", null, "Technology"],
|
[7, "john.doe@example.net", null, "Technology"],
|
||||||
[8, "john.doe@example.net", 7, "Software"],
|
[8, "john.doe@example.net", 7, "Software"],
|
||||||
[9, "john.doe@example.net", null, "Politics"],
|
[9, "john.doe@example.net", null, "Politics"],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -63,7 +63,7 @@ trait SeriesLabel {
|
||||||
[11,"http://example.com/11"],
|
[11,"http://example.com/11"],
|
||||||
[12,"http://example.com/12"],
|
[12,"http://example.com/12"],
|
||||||
[13,"http://example.com/13"],
|
[13,"http://example.com/13"],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -87,7 +87,7 @@ trait SeriesLabel {
|
||||||
[12,"john.doe@example.net",2,9],
|
[12,"john.doe@example.net",2,9],
|
||||||
[13,"john.doe@example.net",3,8],
|
[13,"john.doe@example.net",3,8],
|
||||||
[14,"john.doe@example.net",4,7],
|
[14,"john.doe@example.net",4,7],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_articles' => [
|
'arsse_articles' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -131,7 +131,7 @@ trait SeriesLabel {
|
||||||
[103,12,'http://example.com/3','Article title 3','','2000-01-03 00:00:00','2000-01-03 00:00:03','<p>Article content 3</p>','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b','2000-01-03 03:00:00'],
|
[103,12,'http://example.com/3','Article title 3','','2000-01-03 00:00:00','2000-01-03 00:00:03','<p>Article content 3</p>','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b','2000-01-03 03:00:00'],
|
||||||
[104,12,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:04','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9','2000-01-04 04:00:00'],
|
[104,12,'http://example.com/4','Article title 4','','2000-01-04 00:00:00','2000-01-04 00:00:04','<p>Article content 4</p>','804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180','f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8','f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3','ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9','2000-01-04 04:00:00'],
|
||||||
[105,13,'http://example.com/5','Article title 5','','2000-01-05 00:00:00','2000-01-05 00:00:05','<p>Article content 5</p>','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41','d40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022','834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900','43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba','2000-01-05 05:00:00'],
|
[105,13,'http://example.com/5','Article title 5','','2000-01-05 00:00:00','2000-01-05 00:00:05','<p>Article content 5</p>','db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41','d40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022','834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900','43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba','2000-01-05 05:00:00'],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_enclosures' => [
|
'arsse_enclosures' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -145,7 +145,7 @@ trait SeriesLabel {
|
||||||
[104,"http://example.com/image","image/svg+xml"],
|
[104,"http://example.com/image","image/svg+xml"],
|
||||||
[105,"http://example.com/audio","audio/ogg"],
|
[105,"http://example.com/audio","audio/ogg"],
|
||||||
|
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_editions' => [
|
'arsse_editions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -184,7 +184,7 @@ trait SeriesLabel {
|
||||||
[205,105],
|
[205,105],
|
||||||
[305,105],
|
[305,105],
|
||||||
[1001,20],
|
[1001,20],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_marks' => [
|
'arsse_marks' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -192,7 +192,7 @@ trait SeriesLabel {
|
||||||
'article' => "int",
|
'article' => "int",
|
||||||
'read' => "bool",
|
'read' => "bool",
|
||||||
'starred' => "bool",
|
'starred' => "bool",
|
||||||
'modified' => "datetime"
|
'modified' => "datetime",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[1, 1,1,1,'2000-01-01 00:00:00'],
|
[1, 1,1,1,'2000-01-01 00:00:00'],
|
||||||
|
@ -207,7 +207,7 @@ trait SeriesLabel {
|
||||||
[11, 20,1,0,'2017-01-01 00:00:00'],
|
[11, 20,1,0,'2017-01-01 00:00:00'],
|
||||||
[12, 3,0,1,'2017-01-01 00:00:00'],
|
[12, 3,0,1,'2017-01-01 00:00:00'],
|
||||||
[12, 4,1,1,'2017-01-01 00:00:00'],
|
[12, 4,1,1,'2017-01-01 00:00:00'],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_labels' => [
|
'arsse_labels' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -224,10 +224,10 @@ trait SeriesLabel {
|
||||||
],
|
],
|
||||||
'arsse_label_members' => [
|
'arsse_label_members' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'label' => "int",
|
'label' => "int",
|
||||||
'article' => "int",
|
'article' => "int",
|
||||||
'subscription' => "int",
|
'subscription' => "int",
|
||||||
'assigned' => "bool",
|
'assigned' => "bool",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[1, 1,1,1],
|
[1, 1,1,1],
|
||||||
|
@ -244,11 +244,11 @@ trait SeriesLabel {
|
||||||
$this->user = "john.doe@example.com";
|
$this->user = "john.doe@example.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesLabel() {
|
protected function tearDownSeriesLabel(): void {
|
||||||
unset($this->data, $this->checkLabels, $this->checkMembers, $this->user);
|
unset($this->data, $this->checkLabels, $this->checkMembers, $this->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddALabel() {
|
public function testAddALabel(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$labelID = $this->nextID("arsse_labels");
|
$labelID = $this->nextID("arsse_labels");
|
||||||
$this->assertSame($labelID, Arsse::$db->labelAdd($user, ['name' => "Entertaining"]));
|
$this->assertSame($labelID, Arsse::$db->labelAdd($user, ['name' => "Entertaining"]));
|
||||||
|
@ -258,33 +258,33 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateLabel() {
|
public function testAddADuplicateLabel(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelAdd("john.doe@example.com", ['name' => "Interesting"]);
|
Arsse::$db->labelAdd("john.doe@example.com", ['name' => "Interesting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddALabelWithAMissingName() {
|
public function testAddALabelWithAMissingName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelAdd("john.doe@example.com", []);
|
Arsse::$db->labelAdd("john.doe@example.com", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddALabelWithABlankName() {
|
public function testAddALabelWithABlankName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelAdd("john.doe@example.com", ['name' => ""]);
|
Arsse::$db->labelAdd("john.doe@example.com", ['name' => ""]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddALabelWithAWhitespaceName() {
|
public function testAddALabelWithAWhitespaceName(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelAdd("john.doe@example.com", ['name' => " "]);
|
Arsse::$db->labelAdd("john.doe@example.com", ['name' => " "]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddALabelWithoutAuthority() {
|
public function testAddALabelWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelAdd("john.doe@example.com", ['name' => "Boring"]);
|
Arsse::$db->labelAdd("john.doe@example.com", ['name' => "Boring"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabels() {
|
public function testListLabels(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => 2, 'name' => "Fascinating", 'articles' => 3, 'read' => 1],
|
['id' => 2, 'name' => "Fascinating", 'articles' => 3, 'read' => 1],
|
||||||
['id' => 1, 'name' => "Interesting", 'articles' => 2, 'read' => 2],
|
['id' => 1, 'name' => "Interesting", 'articles' => 2, 'read' => 2],
|
||||||
|
@ -300,13 +300,13 @@ trait SeriesLabel {
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelList");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelList");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabelsWithoutAuthority() {
|
public function testListLabelsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelList("john.doe@example.com");
|
Arsse::$db->labelList("john.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveALabel() {
|
public function testRemoveALabel(): void {
|
||||||
$this->assertTrue(Arsse::$db->labelRemove("john.doe@example.com", 1));
|
$this->assertTrue(Arsse::$db->labelRemove("john.doe@example.com", 1));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
||||||
|
@ -314,7 +314,7 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveALabelByName() {
|
public function testRemoveALabelByName(): void {
|
||||||
$this->assertTrue(Arsse::$db->labelRemove("john.doe@example.com", "Interesting", true));
|
$this->assertTrue(Arsse::$db->labelRemove("john.doe@example.com", "Interesting", true));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
||||||
|
@ -322,33 +322,33 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAMissingLabel() {
|
public function testRemoveAMissingLabel(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelRemove("john.doe@example.com", 2112);
|
Arsse::$db->labelRemove("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidLabel() {
|
public function testRemoveAnInvalidLabel(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelRemove("john.doe@example.com", -1);
|
Arsse::$db->labelRemove("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidLabelByName() {
|
public function testRemoveAnInvalidLabelByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelRemove("john.doe@example.com", [], true);
|
Arsse::$db->labelRemove("john.doe@example.com", [], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveALabelOfTheWrongOwner() {
|
public function testRemoveALabelOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelRemove("john.doe@example.com", 3); // label ID 3 belongs to Jane
|
Arsse::$db->labelRemove("john.doe@example.com", 3); // label ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveALabelWithoutAuthority() {
|
public function testRemoveALabelWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelRemove("john.doe@example.com", 1);
|
Arsse::$db->labelRemove("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfALabel() {
|
public function testGetThePropertiesOfALabel(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'name' => "Fascinating",
|
'name' => "Fascinating",
|
||||||
|
@ -360,37 +360,37 @@ trait SeriesLabel {
|
||||||
\Phake::verify(Arsse::$user, \Phake::times(2))->authorize("john.doe@example.com", "labelPropertiesGet");
|
\Phake::verify(Arsse::$user, \Phake::times(2))->authorize("john.doe@example.com", "labelPropertiesGet");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAMissingLabel() {
|
public function testGetThePropertiesOfAMissingLabel(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesGet("john.doe@example.com", 2112);
|
Arsse::$db->labelPropertiesGet("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidLabel() {
|
public function testGetThePropertiesOfAnInvalidLabel(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesGet("john.doe@example.com", -1);
|
Arsse::$db->labelPropertiesGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidLabelByName() {
|
public function testGetThePropertiesOfAnInvalidLabelByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesGet("john.doe@example.com", [], true);
|
Arsse::$db->labelPropertiesGet("john.doe@example.com", [], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfALabelOfTheWrongOwner() {
|
public function testGetThePropertiesOfALabelOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesGet("john.doe@example.com", 3); // label ID 3 belongs to Jane
|
Arsse::$db->labelPropertiesGet("john.doe@example.com", 3); // label ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfALabelWithoutAuthority() {
|
public function testGetThePropertiesOfALabelWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelPropertiesGet("john.doe@example.com", 1);
|
Arsse::$db->labelPropertiesGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeNoChangesToALabel() {
|
public function testMakeNoChangesToALabel(): void {
|
||||||
$this->assertFalse(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, []));
|
$this->assertFalse(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameALabel() {
|
public function testRenameALabel(): void {
|
||||||
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Curious"]));
|
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Curious"]));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
||||||
|
@ -398,7 +398,7 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameALabelByName() {
|
public function testRenameALabelByName(): void {
|
||||||
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", "Interesting", ['name' => "Curious"], true));
|
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", "Interesting", ['name' => "Curious"], true));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
$state = $this->primeExpectations($this->data, $this->checkLabels);
|
||||||
|
@ -406,53 +406,53 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameALabelToTheEmptyString() {
|
public function testRenameALabelToTheEmptyString(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => ""]));
|
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => ""]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameALabelToWhitespaceOnly() {
|
public function testRenameALabelToWhitespaceOnly(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => " "]));
|
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => " "]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameALabelToAnInvalidValue() {
|
public function testRenameALabelToAnInvalidValue(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => []]));
|
$this->assertTrue(Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => []]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCauseALabelCollision() {
|
public function testCauseALabelCollision(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Fascinating"]);
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Fascinating"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAMissingLabel() {
|
public function testSetThePropertiesOfAMissingLabel(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", 2112, ['name' => "Exciting"]);
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", 2112, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidLabel() {
|
public function testSetThePropertiesOfAnInvalidLabel(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", -1, ['name' => "Exciting"]);
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", -1, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidLabelByName() {
|
public function testSetThePropertiesOfAnInvalidLabelByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", [], ['name' => "Exciting"], true);
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", [], ['name' => "Exciting"], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfALabelForTheWrongOwner() {
|
public function testSetThePropertiesOfALabelForTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", 3, ['name' => "Exciting"]); // label ID 3 belongs to Jane
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", 3, ['name' => "Exciting"]); // label ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfALabelWithoutAuthority() {
|
public function testSetThePropertiesOfALabelWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
Arsse::$db->labelPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabelledArticles() {
|
public function testListLabelledArticles(): void {
|
||||||
$exp = [1,19];
|
$exp = [1,19];
|
||||||
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", 1));
|
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", 1));
|
||||||
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", "Interesting", true));
|
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", "Interesting", true));
|
||||||
|
@ -464,23 +464,23 @@ trait SeriesLabel {
|
||||||
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", "Lonely", true));
|
$this->assertEquals($exp, Arsse::$db->labelArticlesGet("john.doe@example.com", "Lonely", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabelledArticlesForAMissingLabel() {
|
public function testListLabelledArticlesForAMissingLabel(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelArticlesGet("john.doe@example.com", 3);
|
Arsse::$db->labelArticlesGet("john.doe@example.com", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabelledArticlesForAnInvalidLabel() {
|
public function testListLabelledArticlesForAnInvalidLabel(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->labelArticlesGet("john.doe@example.com", -1);
|
Arsse::$db->labelArticlesGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListLabelledArticlesWithoutAuthority() {
|
public function testListLabelledArticlesWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelArticlesGet("john.doe@example.com", 1);
|
Arsse::$db->labelArticlesGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyALabelToArticles() {
|
public function testApplyALabelToArticles(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]));
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]));
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][4][3] = 1;
|
$state['arsse_label_members']['rows'][4][3] = 1;
|
||||||
|
@ -488,14 +488,14 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearALabelFromArticles() {
|
public function testClearALabelFromArticles(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([1,5]), Database::ASSOC_REMOVE);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([1,5]), Database::ASSOC_REMOVE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyALabelToArticlesByName() {
|
public function testApplyALabelToArticlesByName(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([2,5]), Database::ASSOC_ADD, true);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([2,5]), Database::ASSOC_ADD, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][4][3] = 1;
|
$state['arsse_label_members']['rows'][4][3] = 1;
|
||||||
|
@ -503,26 +503,26 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearALabelFromArticlesByName() {
|
public function testClearALabelFromArticlesByName(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([1,5]), Database::ASSOC_REMOVE, true);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([1,5]), Database::ASSOC_REMOVE, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyALabelToNoArticles() {
|
public function testApplyALabelToNoArticles(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]));
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]));
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearALabelFromNoArticles() {
|
public function testClearALabelFromNoArticles(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REMOVE);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REMOVE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReplaceArticlesOfALabel() {
|
public function testReplaceArticlesOfALabel(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]), Database::ASSOC_REPLACE);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]), Database::ASSOC_REPLACE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
|
@ -532,7 +532,7 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPurgeArticlesOfALabel() {
|
public function testPurgeArticlesOfALabel(): void {
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REPLACE);
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REPLACE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_label_members']['rows'][0][3] = 0;
|
$state['arsse_label_members']['rows'][0][3] = 0;
|
||||||
|
@ -540,7 +540,7 @@ trait SeriesLabel {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyALabelToArticlesWithoutAuthority() {
|
public function testApplyALabelToArticlesWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]));
|
Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([2,5]));
|
||||||
|
|
|
@ -10,7 +10,7 @@ use JKingWeb\Arsse\Test\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
trait SeriesMeta {
|
trait SeriesMeta {
|
||||||
protected function setUpSeriesMeta() {
|
protected function setUpSeriesMeta(): void {
|
||||||
$dataBare = [
|
$dataBare = [
|
||||||
'arsse_meta' => [
|
'arsse_meta' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -31,18 +31,18 @@ trait SeriesMeta {
|
||||||
$this->primeDatabase(static::$drv, $dataBare);
|
$this->primeDatabase(static::$drv, $dataBare);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesMeta() {
|
protected function tearDownSeriesMeta(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANewValue() {
|
public function testAddANewValue(): void {
|
||||||
$this->assertTrue(Arsse::$db->metaSet("favourite", "Cygnus X-1"));
|
$this->assertTrue(Arsse::$db->metaSet("favourite", "Cygnus X-1"));
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
||||||
$state['arsse_meta']['rows'][] = ["favourite","Cygnus X-1"];
|
$state['arsse_meta']['rows'][] = ["favourite","Cygnus X-1"];
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANewTypedValue() {
|
public function testAddANewTypedValue(): void {
|
||||||
$this->assertTrue(Arsse::$db->metaSet("answer", 42, "int"));
|
$this->assertTrue(Arsse::$db->metaSet("answer", 42, "int"));
|
||||||
$this->assertTrue(Arsse::$db->metaSet("true", true, "bool"));
|
$this->assertTrue(Arsse::$db->metaSet("true", true, "bool"));
|
||||||
$this->assertTrue(Arsse::$db->metaSet("false", false, "bool"));
|
$this->assertTrue(Arsse::$db->metaSet("false", false, "bool"));
|
||||||
|
@ -55,14 +55,14 @@ trait SeriesMeta {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testChangeAnExistingValue() {
|
public function testChangeAnExistingValue(): void {
|
||||||
$this->assertTrue(Arsse::$db->metaSet("album", "Hemispheres"));
|
$this->assertTrue(Arsse::$db->metaSet("album", "Hemispheres"));
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
||||||
$state['arsse_meta']['rows'][1][1] = "Hemispheres";
|
$state['arsse_meta']['rows'][1][1] = "Hemispheres";
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAValue() {
|
public function testRemoveAValue(): void {
|
||||||
$this->assertTrue(Arsse::$db->metaRemove("album"));
|
$this->assertTrue(Arsse::$db->metaRemove("album"));
|
||||||
$this->assertFalse(Arsse::$db->metaRemove("album"));
|
$this->assertFalse(Arsse::$db->metaRemove("album"));
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
$state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]);
|
||||||
|
@ -70,7 +70,7 @@ trait SeriesMeta {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRetrieveAValue() {
|
public function testRetrieveAValue(): void {
|
||||||
$this->assertSame("".Database::SCHEMA_VERSION, Arsse::$db->metaGet("schema_version"));
|
$this->assertSame("".Database::SCHEMA_VERSION, Arsse::$db->metaGet("schema_version"));
|
||||||
$this->assertSame("A Farewell to Kings", Arsse::$db->metaGet("album"));
|
$this->assertSame("A Farewell to Kings", Arsse::$db->metaGet("album"));
|
||||||
$this->assertSame(null, Arsse::$db->metaGet("this_key_does_not_exist"));
|
$this->assertSame(null, Arsse::$db->metaGet("this_key_does_not_exist"));
|
||||||
|
|
|
@ -10,22 +10,22 @@ use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
|
|
||||||
trait SeriesMiscellany {
|
trait SeriesMiscellany {
|
||||||
protected function setUpSeriesMiscellany() {
|
protected function setUpSeriesMiscellany(): void {
|
||||||
static::setConf([
|
static::setConf([
|
||||||
'dbDriver' => static::$dbDriverClass,
|
'dbDriver' => static::$dbDriverClass,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesMiscellany() {
|
protected function tearDownSeriesMiscellany(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInitializeDatabase() {
|
public function testInitializeDatabase(): void {
|
||||||
static::dbRaze(static::$drv);
|
static::dbRaze(static::$drv);
|
||||||
$d = new Database(true);
|
$d = new Database(true);
|
||||||
$this->assertSame(Database::SCHEMA_VERSION, $d->driverSchemaVersion());
|
$this->assertSame(Database::SCHEMA_VERSION, $d->driverSchemaVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManuallyInitializeDatabase() {
|
public function testManuallyInitializeDatabase(): void {
|
||||||
static::dbRaze(static::$drv);
|
static::dbRaze(static::$drv);
|
||||||
$d = new Database(false);
|
$d = new Database(false);
|
||||||
$this->assertSame(0, $d->driverSchemaVersion());
|
$this->assertSame(0, $d->driverSchemaVersion());
|
||||||
|
@ -34,11 +34,11 @@ trait SeriesMiscellany {
|
||||||
$this->assertFalse($d->driverSchemaUpdate());
|
$this->assertFalse($d->driverSchemaUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckCharacterSetAcceptability() {
|
public function testCheckCharacterSetAcceptability(): void {
|
||||||
$this->assertIsBool(Arsse::$db->driverCharsetAcceptable());
|
$this->assertIsBool(Arsse::$db->driverCharsetAcceptable());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformMaintenance() {
|
public function testPerformMaintenance(): void {
|
||||||
$this->assertTrue(Arsse::$db->driverMaintenance());
|
$this->assertTrue(Arsse::$db->driverMaintenance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,14 @@ use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
|
|
||||||
trait SeriesSession {
|
trait SeriesSession {
|
||||||
protected function setUpSeriesSession() {
|
protected function setUpSeriesSession(): void {
|
||||||
// set up the configuration
|
// set up the configuration
|
||||||
static::setConf([
|
static::setConf([
|
||||||
'userSessionTimeout' => "PT1H",
|
'userSessionTimeout' => "PT1H",
|
||||||
'userSessionLifetime' => "PT24H",
|
'userSessionLifetime' => "PT24H",
|
||||||
]);
|
]);
|
||||||
// set up the test data
|
// set up the test data
|
||||||
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
||||||
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
||||||
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
||||||
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
||||||
|
@ -49,18 +49,18 @@ trait SeriesSession {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesSession() {
|
protected function tearDownSeriesSession(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testResumeAValidSession() {
|
public function testResumeAValidSession(): void {
|
||||||
$exp1 = [
|
$exp1 = [
|
||||||
'id' => "80fa94c1a11f11e78667001e673b2560",
|
'id' => "80fa94c1a11f11e78667001e673b2560",
|
||||||
'user' => "jane.doe@example.com"
|
'user' => "jane.doe@example.com",
|
||||||
];
|
];
|
||||||
$exp2 = [
|
$exp2 = [
|
||||||
'id' => "da772f8fa13c11e78667001e673b2560",
|
'id' => "da772f8fa13c11e78667001e673b2560",
|
||||||
'user' => "john.doe@example.com"
|
'user' => "john.doe@example.com",
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($exp1, Arsse::$db->sessionResume("80fa94c1a11f11e78667001e673b2560"));
|
$this->assertArraySubset($exp1, Arsse::$db->sessionResume("80fa94c1a11f11e78667001e673b2560"));
|
||||||
$this->assertArraySubset($exp2, Arsse::$db->sessionResume("da772f8fa13c11e78667001e673b2560"));
|
$this->assertArraySubset($exp2, Arsse::$db->sessionResume("da772f8fa13c11e78667001e673b2560"));
|
||||||
|
@ -74,22 +74,22 @@ trait SeriesSession {
|
||||||
$this->assertArraySubset($exp1, Arsse::$db->sessionResume("80fa94c1a11f11e78667001e673b2560"));
|
$this->assertArraySubset($exp1, Arsse::$db->sessionResume("80fa94c1a11f11e78667001e673b2560"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testResumeAMissingSession() {
|
public function testResumeAMissingSession(): void {
|
||||||
$this->assertException("invalid", "User", "ExceptionSession");
|
$this->assertException("invalid", "User", "ExceptionSession");
|
||||||
Arsse::$db->sessionResume("thisSessionDoesNotExist");
|
Arsse::$db->sessionResume("thisSessionDoesNotExist");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testResumeAnExpiredSession() {
|
public function testResumeAnExpiredSession(): void {
|
||||||
$this->assertException("invalid", "User", "ExceptionSession");
|
$this->assertException("invalid", "User", "ExceptionSession");
|
||||||
Arsse::$db->sessionResume("27c6de8da13311e78667001e673b2560");
|
Arsse::$db->sessionResume("27c6de8da13311e78667001e673b2560");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testResumeAStaleSession() {
|
public function testResumeAStaleSession(): void {
|
||||||
$this->assertException("invalid", "User", "ExceptionSession");
|
$this->assertException("invalid", "User", "ExceptionSession");
|
||||||
Arsse::$db->sessionResume("ab3b3eb8a13311e78667001e673b2560");
|
Arsse::$db->sessionResume("ab3b3eb8a13311e78667001e673b2560");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateASession() {
|
public function testCreateASession(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$id = Arsse::$db->sessionCreate($user);
|
$id = Arsse::$db->sessionCreate($user);
|
||||||
$now = time();
|
$now = time();
|
||||||
|
@ -98,13 +98,13 @@ trait SeriesSession {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateASessionWithoutAuthority() {
|
public function testCreateASessionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->sessionCreate("jane.doe@example.com");
|
Arsse::$db->sessionCreate("jane.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDestroyASession() {
|
public function testDestroyASession(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$id = "80fa94c1a11f11e78667001e673b2560";
|
$id = "80fa94c1a11f11e78667001e673b2560";
|
||||||
$this->assertTrue(Arsse::$db->sessionDestroy($user, $id));
|
$this->assertTrue(Arsse::$db->sessionDestroy($user, $id));
|
||||||
|
@ -115,7 +115,7 @@ trait SeriesSession {
|
||||||
$this->assertFalse(Arsse::$db->sessionDestroy($user, $id));
|
$this->assertFalse(Arsse::$db->sessionDestroy($user, $id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDestroyAllSessions() {
|
public function testDestroyAllSessions(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$this->assertTrue(Arsse::$db->sessionDestroy($user));
|
$this->assertTrue(Arsse::$db->sessionDestroy($user));
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_sessions' => ["id", "created", "expires", "user"]]);
|
$state = $this->primeExpectations($this->data, ['arsse_sessions' => ["id", "created", "expires", "user"]]);
|
||||||
|
@ -125,13 +125,13 @@ trait SeriesSession {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDestroyASessionForTheWrongUser() {
|
public function testDestroyASessionForTheWrongUser(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$id = "80fa94c1a11f11e78667001e673b2560";
|
$id = "80fa94c1a11f11e78667001e673b2560";
|
||||||
$this->assertFalse(Arsse::$db->sessionDestroy($user, $id));
|
$this->assertFalse(Arsse::$db->sessionDestroy($user, $id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDestroyASessionWithoutAuthority() {
|
public function testDestroyASessionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->sessionDestroy("jane.doe@example.com", "80fa94c1a11f11e78667001e673b2560");
|
Arsse::$db->sessionDestroy("jane.doe@example.com", "80fa94c1a11f11e78667001e673b2560");
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\Database;
|
namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Test\Database;
|
use JKingWeb\Arsse\Test\Database;
|
||||||
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
||||||
|
|
||||||
trait SeriesSubscription {
|
trait SeriesSubscription {
|
||||||
public function setUpSeriesSubscription() {
|
public function setUpSeriesSubscription(): void {
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -37,7 +38,7 @@ trait SeriesSubscription {
|
||||||
[4, "jane.doe@example.com", null, "Politics"],
|
[4, "jane.doe@example.com", null, "Politics"],
|
||||||
[5, "john.doe@example.com", null, "Politics"],
|
[5, "john.doe@example.com", null, "Politics"],
|
||||||
[6, "john.doe@example.com", 2, "Politics"],
|
[6, "john.doe@example.com", 2, "Politics"],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -50,7 +51,7 @@ trait SeriesSubscription {
|
||||||
'next_fetch' => "datetime",
|
'next_fetch' => "datetime",
|
||||||
'favicon' => "str",
|
'favicon' => "str",
|
||||||
],
|
],
|
||||||
'rows' => [] // filled in the series setup
|
'rows' => [], // filled in the series setup
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -66,7 +67,7 @@ trait SeriesSubscription {
|
||||||
[1,"john.doe@example.com",2,null,null,1,2],
|
[1,"john.doe@example.com",2,null,null,1,2],
|
||||||
[2,"jane.doe@example.com",2,null,null,0,0],
|
[2,"jane.doe@example.com",2,null,null,0,0],
|
||||||
[3,"john.doe@example.com",3,"Ook",2,0,1],
|
[3,"john.doe@example.com",3,"Ook",2,0,1],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_tags' => [
|
'arsse_tags' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -83,9 +84,9 @@ trait SeriesSubscription {
|
||||||
],
|
],
|
||||||
'arsse_tag_members' => [
|
'arsse_tag_members' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'tag' => "int",
|
'tag' => "int",
|
||||||
'subscription' => "int",
|
'subscription' => "int",
|
||||||
'assigned' => "bool",
|
'assigned' => "bool",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[1,1,1],
|
[1,1,1],
|
||||||
|
@ -112,7 +113,7 @@ trait SeriesSubscription {
|
||||||
[6,3,"","",""],
|
[6,3,"","",""],
|
||||||
[7,3,"","",""],
|
[7,3,"","",""],
|
||||||
[8,3,"","",""],
|
[8,3,"","",""],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_marks' => [
|
'arsse_marks' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -130,7 +131,7 @@ trait SeriesSubscription {
|
||||||
[1,1,1,0],
|
[1,1,1,0],
|
||||||
[7,3,1,0],
|
[7,3,1,0],
|
||||||
[8,3,0,0],
|
[8,3,0,0],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
$this->data['arsse_feeds']['rows'] = [
|
$this->data['arsse_feeds']['rows'] = [
|
||||||
|
@ -143,11 +144,11 @@ trait SeriesSubscription {
|
||||||
$this->user = "john.doe@example.com";
|
$this->user = "john.doe@example.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesSubscription() {
|
protected function tearDownSeriesSubscription(): void {
|
||||||
unset($this->data, $this->user);
|
unset($this->data, $this->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddASubscriptionToAnExistingFeed() {
|
public function testAddASubscriptionToAnExistingFeed(): void {
|
||||||
$url = "http://example.com/feed1";
|
$url = "http://example.com/feed1";
|
||||||
$subID = $this->nextID("arsse_subscriptions");
|
$subID = $this->nextID("arsse_subscriptions");
|
||||||
\Phake::when(Arsse::$db)->feedUpdate->thenReturn(true);
|
\Phake::when(Arsse::$db)->feedUpdate->thenReturn(true);
|
||||||
|
@ -162,7 +163,7 @@ trait SeriesSubscription {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddASubscriptionToANewFeed() {
|
public function testAddASubscriptionToANewFeed(): void {
|
||||||
$url = "http://example.org/feed1";
|
$url = "http://example.org/feed1";
|
||||||
$feedID = $this->nextID("arsse_feeds");
|
$feedID = $this->nextID("arsse_feeds");
|
||||||
$subID = $this->nextID("arsse_subscriptions");
|
$subID = $this->nextID("arsse_subscriptions");
|
||||||
|
@ -179,7 +180,7 @@ trait SeriesSubscription {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddASubscriptionToANewFeedViaDiscovery() {
|
public function testAddASubscriptionToANewFeedViaDiscovery(): void {
|
||||||
$url = "http://localhost:8000/Feed/Discovery/Valid";
|
$url = "http://localhost:8000/Feed/Discovery/Valid";
|
||||||
$discovered = "http://localhost:8000/Feed/Discovery/Feed";
|
$discovered = "http://localhost:8000/Feed/Discovery/Feed";
|
||||||
$feedID = $this->nextID("arsse_feeds");
|
$feedID = $this->nextID("arsse_feeds");
|
||||||
|
@ -197,10 +198,10 @@ trait SeriesSubscription {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddASubscriptionToAnInvalidFeed() {
|
public function testAddASubscriptionToAnInvalidFeed(): void {
|
||||||
$url = "http://example.org/feed1";
|
$url = "http://example.org/feed1";
|
||||||
$feedID = $this->nextID("arsse_feeds");
|
$feedID = $this->nextID("arsse_feeds");
|
||||||
\Phake::when(Arsse::$db)->feedUpdate->thenThrow(new FeedException($url, new \PicoFeed\Client\InvalidUrlException()));
|
\Phake::when(Arsse::$db)->feedUpdate->thenThrow(new FeedException($url, $this->mockGuzzleException(ClientException::class, "", 404)));
|
||||||
$this->assertException("invalidUrl", "Feed");
|
$this->assertException("invalidUrl", "Feed");
|
||||||
try {
|
try {
|
||||||
Arsse::$db->subscriptionAdd($this->user, $url, "", "", false);
|
Arsse::$db->subscriptionAdd($this->user, $url, "", "", false);
|
||||||
|
@ -215,19 +216,19 @@ trait SeriesSubscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateSubscription() {
|
public function testAddADuplicateSubscription(): void {
|
||||||
$url = "http://example.com/feed2";
|
$url = "http://example.com/feed2";
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionAdd($this->user, $url);
|
Arsse::$db->subscriptionAdd($this->user, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateSubscriptionWithEquivalentUrl() {
|
public function testAddADuplicateSubscriptionWithEquivalentUrl(): void {
|
||||||
$url = "http://EXAMPLE.COM/feed2";
|
$url = "http://EXAMPLE.COM/feed2";
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionAdd($this->user, $url);
|
Arsse::$db->subscriptionAdd($this->user, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateSubscriptionViaRedirection() {
|
public function testAddADuplicateSubscriptionViaRedirection(): void {
|
||||||
$url = "http://localhost:8000/Feed/Parsing/Valid";
|
$url = "http://localhost:8000/Feed/Parsing/Valid";
|
||||||
Arsse::$db->subscriptionAdd($this->user, $url);
|
Arsse::$db->subscriptionAdd($this->user, $url);
|
||||||
$subID = $this->nextID("arsse_subscriptions");
|
$subID = $this->nextID("arsse_subscriptions");
|
||||||
|
@ -235,14 +236,14 @@ trait SeriesSubscription {
|
||||||
$this->assertSame($subID, Arsse::$db->subscriptionAdd($this->user, $url));
|
$this->assertSame($subID, Arsse::$db->subscriptionAdd($this->user, $url));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddASubscriptionWithoutAuthority() {
|
public function testAddASubscriptionWithoutAuthority(): void {
|
||||||
$url = "http://example.com/feed1";
|
$url = "http://example.com/feed1";
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionAdd($this->user, $url);
|
Arsse::$db->subscriptionAdd($this->user, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveASubscription() {
|
public function testRemoveASubscription(): void {
|
||||||
$this->assertTrue(Arsse::$db->subscriptionRemove($this->user, 1));
|
$this->assertTrue(Arsse::$db->subscriptionRemove($this->user, 1));
|
||||||
\Phake::verify(Arsse::$user)->authorize($this->user, "subscriptionRemove");
|
\Phake::verify(Arsse::$user)->authorize($this->user, "subscriptionRemove");
|
||||||
$state = $this->primeExpectations($this->data, [
|
$state = $this->primeExpectations($this->data, [
|
||||||
|
@ -253,29 +254,29 @@ trait SeriesSubscription {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAMissingSubscription() {
|
public function testRemoveAMissingSubscription(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionRemove($this->user, 2112);
|
Arsse::$db->subscriptionRemove($this->user, 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidSubscription() {
|
public function testRemoveAnInvalidSubscription(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionRemove($this->user, -1);
|
Arsse::$db->subscriptionRemove($this->user, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveASubscriptionForTheWrongOwner() {
|
public function testRemoveASubscriptionForTheWrongOwner(): void {
|
||||||
$this->user = "jane.doe@example.com";
|
$this->user = "jane.doe@example.com";
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionRemove($this->user, 1);
|
Arsse::$db->subscriptionRemove($this->user, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveASubscriptionWithoutAuthority() {
|
public function testRemoveASubscriptionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionRemove($this->user, 1);
|
Arsse::$db->subscriptionRemove($this->user, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListSubscriptions() {
|
public function testListSubscriptions(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
[
|
[
|
||||||
'url' => "http://example.com/feed2",
|
'url' => "http://example.com/feed2",
|
||||||
|
@ -303,7 +304,7 @@ trait SeriesSubscription {
|
||||||
$this->assertArraySubset($exp[1], Arsse::$db->subscriptionPropertiesGet($this->user, 3));
|
$this->assertArraySubset($exp[1], Arsse::$db->subscriptionPropertiesGet($this->user, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListSubscriptionsInAFolder() {
|
public function testListSubscriptionsInAFolder(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
[
|
[
|
||||||
'url' => "http://example.com/feed2",
|
'url' => "http://example.com/feed2",
|
||||||
|
@ -318,7 +319,7 @@ trait SeriesSubscription {
|
||||||
$this->assertResult($exp, Arsse::$db->subscriptionList($this->user, null, false));
|
$this->assertResult($exp, Arsse::$db->subscriptionList($this->user, null, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListSubscriptionsWithoutRecursion() {
|
public function testListSubscriptionsWithoutRecursion(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
[
|
[
|
||||||
'url' => "http://example.com/feed3",
|
'url' => "http://example.com/feed3",
|
||||||
|
@ -333,54 +334,54 @@ trait SeriesSubscription {
|
||||||
$this->assertResult($exp, Arsse::$db->subscriptionList($this->user, 2));
|
$this->assertResult($exp, Arsse::$db->subscriptionList($this->user, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListSubscriptionsInAMissingFolder() {
|
public function testListSubscriptionsInAMissingFolder(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionList($this->user, 4);
|
Arsse::$db->subscriptionList($this->user, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListSubscriptionsWithoutAuthority() {
|
public function testListSubscriptionsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionList($this->user);
|
Arsse::$db->subscriptionList($this->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCountSubscriptions() {
|
public function testCountSubscriptions(): void {
|
||||||
$this->assertSame(2, Arsse::$db->subscriptionCount($this->user));
|
$this->assertSame(2, Arsse::$db->subscriptionCount($this->user));
|
||||||
$this->assertSame(1, Arsse::$db->subscriptionCount($this->user, 2));
|
$this->assertSame(1, Arsse::$db->subscriptionCount($this->user, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCountSubscriptionsInAMissingFolder() {
|
public function testCountSubscriptionsInAMissingFolder(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionCount($this->user, 4);
|
Arsse::$db->subscriptionCount($this->user, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCountSubscriptionsWithoutAuthority() {
|
public function testCountSubscriptionsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionCount($this->user);
|
Arsse::$db->subscriptionCount($this->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAMissingSubscription() {
|
public function testGetThePropertiesOfAMissingSubscription(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesGet($this->user, 2112);
|
Arsse::$db->subscriptionPropertiesGet($this->user, 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidSubscription() {
|
public function testGetThePropertiesOfAnInvalidSubscription(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesGet($this->user, -1);
|
Arsse::$db->subscriptionPropertiesGet($this->user, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfASubscriptionWithoutAuthority() {
|
public function testGetThePropertiesOfASubscriptionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionPropertiesGet($this->user, 1);
|
Arsse::$db->subscriptionPropertiesGet($this->user, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfASubscription() {
|
public function testSetThePropertiesOfASubscription(): void {
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, [
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, [
|
||||||
'title' => "Ook Ook",
|
'title' => "Ook Ook",
|
||||||
'folder' => 3,
|
'folder' => 3,
|
||||||
'pinned' => false,
|
'pinned' => false,
|
||||||
'order_type' => 0,
|
'order_type' => 0,
|
||||||
]);
|
]);
|
||||||
\Phake::verify(Arsse::$user)->authorize($this->user, "subscriptionPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize($this->user, "subscriptionPropertiesSet");
|
||||||
|
@ -400,56 +401,56 @@ trait SeriesSubscription {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveASubscriptionToAMissingFolder() {
|
public function testMoveASubscriptionToAMissingFolder(): void {
|
||||||
$this->assertException("idMissing", "Db", "ExceptionInput");
|
$this->assertException("idMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => 4]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => 4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMoveASubscriptionToTheRootFolder() {
|
public function testMoveASubscriptionToTheRootFolder(): void {
|
||||||
$this->assertTrue(Arsse::$db->subscriptionPropertiesSet($this->user, 3, ['folder' => null]));
|
$this->assertTrue(Arsse::$db->subscriptionPropertiesSet($this->user, 3, ['folder' => null]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameASubscriptionToABlankTitle() {
|
public function testRenameASubscriptionToABlankTitle(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => ""]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => ""]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameASubscriptionToAWhitespaceTitle() {
|
public function testRenameASubscriptionToAWhitespaceTitle(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => " "]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => " "]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameASubscriptionToFalse() {
|
public function testRenameASubscriptionToFalse(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => false]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameASubscriptionToZero() {
|
public function testRenameASubscriptionToZero(): void {
|
||||||
$this->assertTrue(Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => 0]));
|
$this->assertTrue(Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => 0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameASubscriptionToAnArray() {
|
public function testRenameASubscriptionToAnArray(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => []]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['title' => []]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAMissingSubscription() {
|
public function testSetThePropertiesOfAMissingSubscription(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 2112, ['folder' => null]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 2112, ['folder' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidSubscription() {
|
public function testSetThePropertiesOfAnInvalidSubscription(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, -1, ['folder' => null]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, -1, ['folder' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfASubscriptionWithoutAuthority() {
|
public function testSetThePropertiesOfASubscriptionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => null]);
|
Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['folder' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRetrieveTheFaviconOfASubscription() {
|
public function testRetrieveTheFaviconOfASubscription(): void {
|
||||||
$exp = "http://example.com/favicon.ico";
|
$exp = "http://example.com/favicon.ico";
|
||||||
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1));
|
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1));
|
||||||
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(2));
|
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(2));
|
||||||
|
@ -465,7 +466,7 @@ trait SeriesSubscription {
|
||||||
$this->assertSame('', Arsse::$db->subscriptionFavicon(-2112));
|
$this->assertSame('', Arsse::$db->subscriptionFavicon(-2112));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRetrieveTheFaviconOfASubscriptionWithUser() {
|
public function testRetrieveTheFaviconOfASubscriptionWithUser(): void {
|
||||||
$exp = "http://example.com/favicon.ico";
|
$exp = "http://example.com/favicon.ico";
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1, $user));
|
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1, $user));
|
||||||
|
@ -479,7 +480,7 @@ trait SeriesSubscription {
|
||||||
$this->assertSame('', Arsse::$db->subscriptionFavicon(4, $user));
|
$this->assertSame('', Arsse::$db->subscriptionFavicon(4, $user));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRetrieveTheFaviconOfASubscriptionWithUserWithoutAuthority() {
|
public function testRetrieveTheFaviconOfASubscriptionWithUserWithoutAuthority(): void {
|
||||||
$exp = "http://example.com/favicon.ico";
|
$exp = "http://example.com/favicon.ico";
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
|
@ -487,36 +488,36 @@ trait SeriesSubscription {
|
||||||
Arsse::$db->subscriptionFavicon(-2112, $user);
|
Arsse::$db->subscriptionFavicon(-2112, $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTheTagsOfASubscription() {
|
public function testListTheTagsOfASubscription(): void {
|
||||||
$this->assertEquals([1,2], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1));
|
$this->assertEquals([1,2], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1));
|
||||||
$this->assertEquals([2], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 3));
|
$this->assertEquals([2], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 3));
|
||||||
$this->assertEquals(["Fascinating","Interesting"], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1, true));
|
$this->assertEquals(["Fascinating","Interesting"], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1, true));
|
||||||
$this->assertEquals(["Fascinating"], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 3, true));
|
$this->assertEquals(["Fascinating"], Arsse::$db->subscriptionTagsGet("john.doe@example.com", 3, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTheTagsOfAMissingSubscription() {
|
public function testListTheTagsOfAMissingSubscription(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->subscriptionTagsGet($this->user, 101);
|
Arsse::$db->subscriptionTagsGet($this->user, 101);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTheTagsOfASubscriptionWithoutAuthority() {
|
public function testListTheTagsOfASubscriptionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1);
|
Arsse::$db->subscriptionTagsGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRefreshTimeOfASubscription() {
|
public function testGetRefreshTimeOfASubscription(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$this->assertTime(strtotime("now + 1 hour"), Arsse::$db->subscriptionRefreshed($user));
|
$this->assertTime(strtotime("now + 1 hour"), Arsse::$db->subscriptionRefreshed($user));
|
||||||
$this->assertTime(strtotime("now - 1 hour"), Arsse::$db->subscriptionRefreshed($user, 1));
|
$this->assertTime(strtotime("now - 1 hour"), Arsse::$db->subscriptionRefreshed($user, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRefreshTimeOfAMissingSubscription() {
|
public function testGetRefreshTimeOfAMissingSubscription(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
$this->assertTime(strtotime("now - 1 hour"), Arsse::$db->subscriptionRefreshed("john.doe@example.com", 2));
|
$this->assertTime(strtotime("now - 1 hour"), Arsse::$db->subscriptionRefreshed("john.doe@example.com", 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRefreshTimeOfASubscriptionWithoutAuthority() {
|
public function testGetRefreshTimeOfASubscriptionWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
$this->assertTime(strtotime("now + 1 hour"), Arsse::$db->subscriptionRefreshed("john.doe@example.com"));
|
$this->assertTime(strtotime("now + 1 hour"), Arsse::$db->subscriptionRefreshed("john.doe@example.com"));
|
||||||
|
|
|
@ -10,7 +10,7 @@ use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
|
|
||||||
trait SeriesTag {
|
trait SeriesTag {
|
||||||
protected function setUpSeriesTag() {
|
protected function setUpSeriesTag(): void {
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -44,7 +44,7 @@ trait SeriesTag {
|
||||||
[11,"http://example.com/11",""],
|
[11,"http://example.com/11",""],
|
||||||
[12,"http://example.com/12",""],
|
[12,"http://example.com/12",""],
|
||||||
[13,"http://example.com/13",""],
|
[13,"http://example.com/13",""],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_subscriptions' => [
|
'arsse_subscriptions' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -68,7 +68,7 @@ trait SeriesTag {
|
||||||
[12,"john.doe@example.net", 2,null],
|
[12,"john.doe@example.net", 2,null],
|
||||||
[13,"john.doe@example.net", 3,null],
|
[13,"john.doe@example.net", 3,null],
|
||||||
[14,"john.doe@example.net", 4,null],
|
[14,"john.doe@example.net", 4,null],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'arsse_tags' => [
|
'arsse_tags' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -85,9 +85,9 @@ trait SeriesTag {
|
||||||
],
|
],
|
||||||
'arsse_tag_members' => [
|
'arsse_tag_members' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'tag' => "int",
|
'tag' => "int",
|
||||||
'subscription' => "int",
|
'subscription' => "int",
|
||||||
'assigned' => "bool",
|
'assigned' => "bool",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[1,1,1],
|
[1,1,1],
|
||||||
|
@ -104,11 +104,11 @@ trait SeriesTag {
|
||||||
$this->user = "john.doe@example.com";
|
$this->user = "john.doe@example.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesTag() {
|
protected function tearDownSeriesTag(): void {
|
||||||
unset($this->data, $this->checkTags, $this->checkMembers, $this->user);
|
unset($this->data, $this->checkTags, $this->checkMembers, $this->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddATag() {
|
public function testAddATag(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$tagID = $this->nextID("arsse_tags");
|
$tagID = $this->nextID("arsse_tags");
|
||||||
$this->assertSame($tagID, Arsse::$db->tagAdd($user, ['name' => "Entertaining"]));
|
$this->assertSame($tagID, Arsse::$db->tagAdd($user, ['name' => "Entertaining"]));
|
||||||
|
@ -118,33 +118,33 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddADuplicateTag() {
|
public function testAddADuplicateTag(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagAdd("john.doe@example.com", ['name' => "Interesting"]);
|
Arsse::$db->tagAdd("john.doe@example.com", ['name' => "Interesting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddATagWithAMissingName() {
|
public function testAddATagWithAMissingName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagAdd("john.doe@example.com", []);
|
Arsse::$db->tagAdd("john.doe@example.com", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddATagWithABlankName() {
|
public function testAddATagWithABlankName(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagAdd("john.doe@example.com", ['name' => ""]);
|
Arsse::$db->tagAdd("john.doe@example.com", ['name' => ""]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddATagWithAWhitespaceName() {
|
public function testAddATagWithAWhitespaceName(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagAdd("john.doe@example.com", ['name' => " "]);
|
Arsse::$db->tagAdd("john.doe@example.com", ['name' => " "]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddATagWithoutAuthority() {
|
public function testAddATagWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagAdd("john.doe@example.com", ['name' => "Boring"]);
|
Arsse::$db->tagAdd("john.doe@example.com", ['name' => "Boring"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTags() {
|
public function testListTags(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => 2, 'name' => "Fascinating"],
|
['id' => 2, 'name' => "Fascinating"],
|
||||||
['id' => 1, 'name' => "Interesting"],
|
['id' => 1, 'name' => "Interesting"],
|
||||||
|
@ -160,13 +160,13 @@ trait SeriesTag {
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagList");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagList");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTagsWithoutAuthority() {
|
public function testListTagsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagList("john.doe@example.com");
|
Arsse::$db->tagList("john.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveATag() {
|
public function testRemoveATag(): void {
|
||||||
$this->assertTrue(Arsse::$db->tagRemove("john.doe@example.com", 1));
|
$this->assertTrue(Arsse::$db->tagRemove("john.doe@example.com", 1));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkTags);
|
$state = $this->primeExpectations($this->data, $this->checkTags);
|
||||||
|
@ -174,7 +174,7 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveATagByName() {
|
public function testRemoveATagByName(): void {
|
||||||
$this->assertTrue(Arsse::$db->tagRemove("john.doe@example.com", "Interesting", true));
|
$this->assertTrue(Arsse::$db->tagRemove("john.doe@example.com", "Interesting", true));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkTags);
|
$state = $this->primeExpectations($this->data, $this->checkTags);
|
||||||
|
@ -182,33 +182,33 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAMissingTag() {
|
public function testRemoveAMissingTag(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagRemove("john.doe@example.com", 2112);
|
Arsse::$db->tagRemove("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidTag() {
|
public function testRemoveAnInvalidTag(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagRemove("john.doe@example.com", -1);
|
Arsse::$db->tagRemove("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAnInvalidTagByName() {
|
public function testRemoveAnInvalidTagByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagRemove("john.doe@example.com", [], true);
|
Arsse::$db->tagRemove("john.doe@example.com", [], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveATagOfTheWrongOwner() {
|
public function testRemoveATagOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagRemove("john.doe@example.com", 3); // tag ID 3 belongs to Jane
|
Arsse::$db->tagRemove("john.doe@example.com", 3); // tag ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveATagWithoutAuthority() {
|
public function testRemoveATagWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagRemove("john.doe@example.com", 1);
|
Arsse::$db->tagRemove("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfATag() {
|
public function testGetThePropertiesOfATag(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'name' => "Fascinating",
|
'name' => "Fascinating",
|
||||||
|
@ -218,37 +218,37 @@ trait SeriesTag {
|
||||||
\Phake::verify(Arsse::$user, \Phake::times(2))->authorize("john.doe@example.com", "tagPropertiesGet");
|
\Phake::verify(Arsse::$user, \Phake::times(2))->authorize("john.doe@example.com", "tagPropertiesGet");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAMissingTag() {
|
public function testGetThePropertiesOfAMissingTag(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesGet("john.doe@example.com", 2112);
|
Arsse::$db->tagPropertiesGet("john.doe@example.com", 2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidTag() {
|
public function testGetThePropertiesOfAnInvalidTag(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesGet("john.doe@example.com", -1);
|
Arsse::$db->tagPropertiesGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfAnInvalidTagByName() {
|
public function testGetThePropertiesOfAnInvalidTagByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesGet("john.doe@example.com", [], true);
|
Arsse::$db->tagPropertiesGet("john.doe@example.com", [], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfATagOfTheWrongOwner() {
|
public function testGetThePropertiesOfATagOfTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesGet("john.doe@example.com", 3); // tag ID 3 belongs to Jane
|
Arsse::$db->tagPropertiesGet("john.doe@example.com", 3); // tag ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePropertiesOfATagWithoutAuthority() {
|
public function testGetThePropertiesOfATagWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagPropertiesGet("john.doe@example.com", 1);
|
Arsse::$db->tagPropertiesGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeNoChangesToATag() {
|
public function testMakeNoChangesToATag(): void {
|
||||||
$this->assertFalse(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, []));
|
$this->assertFalse(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameATag() {
|
public function testRenameATag(): void {
|
||||||
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Curious"]));
|
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Curious"]));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkTags);
|
$state = $this->primeExpectations($this->data, $this->checkTags);
|
||||||
|
@ -256,7 +256,7 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameATagByName() {
|
public function testRenameATagByName(): void {
|
||||||
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", "Interesting", ['name' => "Curious"], true));
|
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", "Interesting", ['name' => "Curious"], true));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet");
|
||||||
$state = $this->primeExpectations($this->data, $this->checkTags);
|
$state = $this->primeExpectations($this->data, $this->checkTags);
|
||||||
|
@ -264,53 +264,53 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameATagToTheEmptyString() {
|
public function testRenameATagToTheEmptyString(): void {
|
||||||
$this->assertException("missing", "Db", "ExceptionInput");
|
$this->assertException("missing", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => ""]));
|
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => ""]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameATagToWhitespaceOnly() {
|
public function testRenameATagToWhitespaceOnly(): void {
|
||||||
$this->assertException("whitespace", "Db", "ExceptionInput");
|
$this->assertException("whitespace", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => " "]));
|
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => " "]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRenameATagToAnInvalidValue() {
|
public function testRenameATagToAnInvalidValue(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => []]));
|
$this->assertTrue(Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => []]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCauseATagCollision() {
|
public function testCauseATagCollision(): void {
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Fascinating"]);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Fascinating"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAMissingTag() {
|
public function testSetThePropertiesOfAMissingTag(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", 2112, ['name' => "Exciting"]);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", 2112, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidTag() {
|
public function testSetThePropertiesOfAnInvalidTag(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", -1, ['name' => "Exciting"]);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", -1, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfAnInvalidTagByName() {
|
public function testSetThePropertiesOfAnInvalidTagByName(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", [], ['name' => "Exciting"], true);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", [], ['name' => "Exciting"], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfATagForTheWrongOwner() {
|
public function testSetThePropertiesOfATagForTheWrongOwner(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", 3, ['name' => "Exciting"]); // tag ID 3 belongs to Jane
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", 3, ['name' => "Exciting"]); // tag ID 3 belongs to Jane
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePropertiesOfATagWithoutAuthority() {
|
public function testSetThePropertiesOfATagWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
Arsse::$db->tagPropertiesSet("john.doe@example.com", 1, ['name' => "Exciting"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTaggedSubscriptions() {
|
public function testListTaggedSubscriptions(): void {
|
||||||
$exp = [1,5];
|
$exp = [1,5];
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1));
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Interesting", true));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Interesting", true));
|
||||||
|
@ -322,23 +322,23 @@ trait SeriesTag {
|
||||||
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Lonely", true));
|
$this->assertEquals($exp, Arsse::$db->tagSubscriptionsGet("john.doe@example.com", "Lonely", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTaggedSubscriptionsForAMissingTag() {
|
public function testListTaggedSubscriptionsForAMissingTag(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 3);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTaggedSubscriptionsForAnInvalidTag() {
|
public function testListTaggedSubscriptionsForAnInvalidTag(): void {
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", -1);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListTaggedSubscriptionsWithoutAuthority() {
|
public function testListTaggedSubscriptionsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1);
|
Arsse::$db->tagSubscriptionsGet("john.doe@example.com", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyATagToSubscriptions() {
|
public function testApplyATagToSubscriptions(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4]);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4]);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][1][2] = 1;
|
$state['arsse_tag_members']['rows'][1][2] = 1;
|
||||||
|
@ -346,14 +346,14 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearATagFromSubscriptions() {
|
public function testClearATagFromSubscriptions(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [1,3], Database::ASSOC_REMOVE);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [1,3], Database::ASSOC_REMOVE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyATagToSubscriptionsByName() {
|
public function testApplyATagToSubscriptionsByName(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [3,4], Database::ASSOC_ADD, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [3,4], Database::ASSOC_ADD, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][1][2] = 1;
|
$state['arsse_tag_members']['rows'][1][2] = 1;
|
||||||
|
@ -361,26 +361,26 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearATagFromSubscriptionsByName() {
|
public function testClearATagFromSubscriptionsByName(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [1,3], Database::ASSOC_REMOVE, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [1,3], Database::ASSOC_REMOVE, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyATagToNoSubscriptionsByName() {
|
public function testApplyATagToNoSubscriptionsByName(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_ADD, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_ADD, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClearATagFromNoSubscriptionsByName() {
|
public function testClearATagFromNoSubscriptionsByName(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_REMOVE, true);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_REMOVE, true);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReplaceSubscriptionsOfATag() {
|
public function testReplaceSubscriptionsOfATag(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4], Database::ASSOC_REPLACE);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4], Database::ASSOC_REPLACE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
|
@ -390,7 +390,7 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPurgeSubscriptionsOfATag() {
|
public function testPurgeSubscriptionsOfATag(): void {
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [], Database::ASSOC_REPLACE);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [], Database::ASSOC_REPLACE);
|
||||||
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
$state = $this->primeExpectations($this->data, $this->checkMembers);
|
||||||
$state['arsse_tag_members']['rows'][0][2] = 0;
|
$state['arsse_tag_members']['rows'][0][2] = 0;
|
||||||
|
@ -398,13 +398,13 @@ trait SeriesTag {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplyATagToSubscriptionsWithoutAuthority() {
|
public function testApplyATagToSubscriptionsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4]);
|
Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [3,4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSummarizeTags() {
|
public function testSummarizeTags(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['id' => 1, 'name' => "Interesting", 'subscription' => 1],
|
['id' => 1, 'name' => "Interesting", 'subscription' => 1],
|
||||||
['id' => 1, 'name' => "Interesting", 'subscription' => 5],
|
['id' => 1, 'name' => "Interesting", 'subscription' => 5],
|
||||||
|
@ -415,7 +415,7 @@ trait SeriesTag {
|
||||||
$this->assertResult($exp, Arsse::$db->tagSummarize("john.doe@example.com"));
|
$this->assertResult($exp, Arsse::$db->tagSummarize("john.doe@example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSummarizeTagsWithoutAuthority() {
|
public function testSummarizeTagsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tagSummarize("john.doe@example.com");
|
Arsse::$db->tagSummarize("john.doe@example.com");
|
||||||
|
|
|
@ -9,9 +9,9 @@ namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
trait SeriesToken {
|
trait SeriesToken {
|
||||||
protected function setUpSeriesToken() {
|
protected function setUpSeriesToken(): void {
|
||||||
// set up the test data
|
// set up the test data
|
||||||
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
||||||
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
||||||
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
||||||
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
||||||
|
@ -30,7 +30,7 @@ trait SeriesToken {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => "str",
|
'id' => "str",
|
||||||
'class' => "str",
|
'class' => "str",
|
||||||
'user' => "str",
|
'user' => "str",
|
||||||
'expires' => "datetime",
|
'expires' => "datetime",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
|
@ -43,25 +43,25 @@ trait SeriesToken {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesToken() {
|
protected function tearDownSeriesToken(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLookUpAValidToken() {
|
public function testLookUpAValidToken(): void {
|
||||||
$exp1 = [
|
$exp1 = [
|
||||||
'id' => "80fa94c1a11f11e78667001e673b2560",
|
'id' => "80fa94c1a11f11e78667001e673b2560",
|
||||||
'class' => "fever.login",
|
'class' => "fever.login",
|
||||||
'user' => "jane.doe@example.com"
|
'user' => "jane.doe@example.com",
|
||||||
];
|
];
|
||||||
$exp2 = [
|
$exp2 = [
|
||||||
'id' => "da772f8fa13c11e78667001e673b2560",
|
'id' => "da772f8fa13c11e78667001e673b2560",
|
||||||
'class' => "class.class",
|
'class' => "class.class",
|
||||||
'user' => "john.doe@example.com"
|
'user' => "john.doe@example.com",
|
||||||
];
|
];
|
||||||
$exp3 = [
|
$exp3 = [
|
||||||
'id' => "ab3b3eb8a13311e78667001e673b2560",
|
'id' => "ab3b3eb8a13311e78667001e673b2560",
|
||||||
'class' => "class.class",
|
'class' => "class.class",
|
||||||
'user' => "jane.doe@example.com"
|
'user' => "jane.doe@example.com",
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
||||||
$this->assertArraySubset($exp2, Arsse::$db->tokenLookup("class.class", "da772f8fa13c11e78667001e673b2560"));
|
$this->assertArraySubset($exp2, Arsse::$db->tokenLookup("class.class", "da772f8fa13c11e78667001e673b2560"));
|
||||||
|
@ -71,22 +71,22 @@ trait SeriesToken {
|
||||||
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLookUpAMissingToken() {
|
public function testLookUpAMissingToken(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tokenLookup("class", "thisTokenDoesNotExist");
|
Arsse::$db->tokenLookup("class", "thisTokenDoesNotExist");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLookUpAnExpiredToken() {
|
public function testLookUpAnExpiredToken(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tokenLookup("fever.login", "27c6de8da13311e78667001e673b2560");
|
Arsse::$db->tokenLookup("fever.login", "27c6de8da13311e78667001e673b2560");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLookUpATokenOfTheWrongClass() {
|
public function testLookUpATokenOfTheWrongClass(): void {
|
||||||
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
Arsse::$db->tokenLookup("some.class", "80fa94c1a11f11e78667001e673b2560");
|
Arsse::$db->tokenLookup("some.class", "80fa94c1a11f11e78667001e673b2560");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateAToken() {
|
public function testCreateAToken(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "class", "expires", "user"]]);
|
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "class", "expires", "user"]]);
|
||||||
$id = Arsse::$db->tokenCreate($user, "fever.login");
|
$id = Arsse::$db->tokenCreate($user, "fever.login");
|
||||||
|
@ -100,18 +100,18 @@ trait SeriesToken {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateATokenForAMissingUser() {
|
public function testCreateATokenForAMissingUser(): void {
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
Arsse::$db->tokenCreate("fever.login", "jane.doe@example.biz");
|
Arsse::$db->tokenCreate("fever.login", "jane.doe@example.biz");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateATokenWithoutAuthority() {
|
public function testCreateATokenWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tokenCreate("fever.login", "jane.doe@example.com");
|
Arsse::$db->tokenCreate("fever.login", "jane.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRevokeAToken() {
|
public function testRevokeAToken(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$id = "80fa94c1a11f11e78667001e673b2560";
|
$id = "80fa94c1a11f11e78667001e673b2560";
|
||||||
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
||||||
|
@ -122,7 +122,7 @@ trait SeriesToken {
|
||||||
$this->assertFalse(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
$this->assertFalse(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRevokeAllTokens() {
|
public function testRevokeAllTokens(): void {
|
||||||
$user = "jane.doe@example.com";
|
$user = "jane.doe@example.com";
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "expires", "user"]]);
|
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "expires", "user"]]);
|
||||||
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login"));
|
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login"));
|
||||||
|
@ -136,7 +136,7 @@ trait SeriesToken {
|
||||||
$this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class"));
|
$this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRevokeATokenWithoutAuthority() {
|
public function testRevokeATokenWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->tokenRevoke("jane.doe@example.com", "fever.login");
|
Arsse::$db->tokenRevoke("jane.doe@example.com", "fever.login");
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
|
||||||
trait SeriesUser {
|
trait SeriesUser {
|
||||||
protected function setUpSeriesUser() {
|
protected function setUpSeriesUser(): void {
|
||||||
$this->data = [
|
$this->data = [
|
||||||
'arsse_users' => [
|
'arsse_users' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
|
@ -25,11 +25,11 @@ trait SeriesUser {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDownSeriesUser() {
|
protected function tearDownSeriesUser(): void {
|
||||||
unset($this->data);
|
unset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckThatAUserExists() {
|
public function testCheckThatAUserExists(): void {
|
||||||
$this->assertTrue(Arsse::$db->userExists("jane.doe@example.com"));
|
$this->assertTrue(Arsse::$db->userExists("jane.doe@example.com"));
|
||||||
$this->assertFalse(Arsse::$db->userExists("jane.doe@example.org"));
|
$this->assertFalse(Arsse::$db->userExists("jane.doe@example.org"));
|
||||||
\Phake::verify(Arsse::$user)->authorize("jane.doe@example.com", "userExists");
|
\Phake::verify(Arsse::$user)->authorize("jane.doe@example.com", "userExists");
|
||||||
|
@ -37,31 +37,31 @@ trait SeriesUser {
|
||||||
$this->compareExpectations(static::$drv, $this->data);
|
$this->compareExpectations(static::$drv, $this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckThatAUserExistsWithoutAuthority() {
|
public function testCheckThatAUserExistsWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userExists("jane.doe@example.com");
|
Arsse::$db->userExists("jane.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAPassword() {
|
public function testGetAPassword(): void {
|
||||||
$hash = Arsse::$db->userPasswordGet("admin@example.net");
|
$hash = Arsse::$db->userPasswordGet("admin@example.net");
|
||||||
$this->assertSame('$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', $hash);
|
$this->assertSame('$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', $hash);
|
||||||
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "userPasswordGet");
|
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "userPasswordGet");
|
||||||
$this->assertTrue(password_verify("secret", $hash));
|
$this->assertTrue(password_verify("secret", $hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetThePasswordOfAMissingUser() {
|
public function testGetThePasswordOfAMissingUser(): void {
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
Arsse::$db->userPasswordGet("john.doe@example.org");
|
Arsse::$db->userPasswordGet("john.doe@example.org");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAPasswordWithoutAuthority() {
|
public function testGetAPasswordWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userPasswordGet("admin@example.net");
|
Arsse::$db->userPasswordGet("admin@example.net");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANewUser() {
|
public function testAddANewUser(): void {
|
||||||
$this->assertTrue(Arsse::$db->userAdd("john.doe@example.org", ""));
|
$this->assertTrue(Arsse::$db->userAdd("john.doe@example.org", ""));
|
||||||
\Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd");
|
\Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]);
|
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]);
|
||||||
|
@ -69,18 +69,18 @@ trait SeriesUser {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddAnExistingUser() {
|
public function testAddAnExistingUser(): void {
|
||||||
$this->assertException("alreadyExists", "User");
|
$this->assertException("alreadyExists", "User");
|
||||||
Arsse::$db->userAdd("john.doe@example.com", "");
|
Arsse::$db->userAdd("john.doe@example.com", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddANewUserWithoutAuthority() {
|
public function testAddANewUserWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userAdd("john.doe@example.org", "");
|
Arsse::$db->userAdd("john.doe@example.org", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAUser() {
|
public function testRemoveAUser(): void {
|
||||||
$this->assertTrue(Arsse::$db->userRemove("admin@example.net"));
|
$this->assertTrue(Arsse::$db->userRemove("admin@example.net"));
|
||||||
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "userRemove");
|
\Phake::verify(Arsse::$user)->authorize("admin@example.net", "userRemove");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]);
|
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]);
|
||||||
|
@ -88,24 +88,24 @@ trait SeriesUser {
|
||||||
$this->compareExpectations(static::$drv, $state);
|
$this->compareExpectations(static::$drv, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAMissingUser() {
|
public function testRemoveAMissingUser(): void {
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
Arsse::$db->userRemove("john.doe@example.org");
|
Arsse::$db->userRemove("john.doe@example.org");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRemoveAUserWithoutAuthority() {
|
public function testRemoveAUserWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userRemove("admin@example.net");
|
Arsse::$db->userRemove("admin@example.net");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListAllUsers() {
|
public function testListAllUsers(): void {
|
||||||
$users = ["admin@example.net", "jane.doe@example.com", "john.doe@example.com"];
|
$users = ["admin@example.net", "jane.doe@example.com", "john.doe@example.com"];
|
||||||
$this->assertSame($users, Arsse::$db->userList());
|
$this->assertSame($users, Arsse::$db->userList());
|
||||||
\Phake::verify(Arsse::$user)->authorize("", "userList");
|
\Phake::verify(Arsse::$user)->authorize("", "userList");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListAllUsersWithoutAuthority() {
|
public function testListAllUsersWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userList();
|
Arsse::$db->userList();
|
||||||
|
@ -114,7 +114,7 @@ trait SeriesUser {
|
||||||
/**
|
/**
|
||||||
* @depends testGetAPassword
|
* @depends testGetAPassword
|
||||||
*/
|
*/
|
||||||
public function testSetAPassword() {
|
public function testSetAPassword(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$pass = "secret";
|
$pass = "secret";
|
||||||
$this->assertEquals("", Arsse::$db->userPasswordGet($user));
|
$this->assertEquals("", Arsse::$db->userPasswordGet($user));
|
||||||
|
@ -125,19 +125,19 @@ trait SeriesUser {
|
||||||
$this->assertTrue(password_verify($pass, $hash), "Failed verifying password of $user '$pass' against hash '$hash'.");
|
$this->assertTrue(password_verify($pass, $hash), "Failed verifying password of $user '$pass' against hash '$hash'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnsetAPassword() {
|
public function testUnsetAPassword(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$this->assertEquals("", Arsse::$db->userPasswordGet($user));
|
$this->assertEquals("", Arsse::$db->userPasswordGet($user));
|
||||||
$this->assertTrue(Arsse::$db->userPasswordSet($user, null));
|
$this->assertTrue(Arsse::$db->userPasswordSet($user, null));
|
||||||
$this->assertNull(Arsse::$db->userPasswordGet($user));
|
$this->assertNull(Arsse::$db->userPasswordGet($user));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetThePasswordOfAMissingUser() {
|
public function testSetThePasswordOfAMissingUser(): void {
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
Arsse::$db->userPasswordSet("john.doe@example.org", "secret");
|
Arsse::$db->userPasswordSet("john.doe@example.org", "secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetAPasswordWithoutAuthority() {
|
public function testSetAPasswordWithoutAuthority(): void {
|
||||||
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
\Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
Arsse::$db->userPasswordSet("john.doe@example.com", "secret");
|
Arsse::$db->userPasswordSet("john.doe@example.com", "secret");
|
||||||
|
|
|
@ -11,7 +11,7 @@ use JKingWeb\Arsse\Database;
|
||||||
/** @covers \JKingWeb\Arsse\Database */
|
/** @covers \JKingWeb\Arsse\Database */
|
||||||
class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $db = null;
|
protected $db = null;
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
self::setConf();
|
self::setConf();
|
||||||
|
@ -28,17 +28,17 @@ class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideInClauses */
|
/** @dataProvider provideInClauses */
|
||||||
public function testGenerateInClause(string $clause, array $values, array $inV, string $inT) {
|
public function testGenerateInClause(string $clause, array $values, array $inV, string $inT): void {
|
||||||
$types = array_fill(0, sizeof($values), $inT);
|
$types = array_fill(0, sizeof($values), $inT);
|
||||||
$exp = [$clause, $types, $values];
|
$exp = [$clause, $types, $values];
|
||||||
$this->assertSame($exp, $this->db->generateIn($inV, $inT));
|
$this->assertSame($exp, $this->db->generateIn($inV, $inT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideInClauses(): iterable {
|
public function provideInClauses(): iterable {
|
||||||
$l = Database::LIMIT_SET_SIZE + 1;
|
$l = (new \ReflectionClassConstant(Database::class, "LIMIT_SET_SIZE"))->getValue() + 1;
|
||||||
$strings = array_fill(0, $l, "");
|
$strings = array_fill(0, $l, "");
|
||||||
$ints = range(1, $l);
|
$ints = range(1, $l);
|
||||||
$longString = str_repeat("0", Database::LIMIT_SET_STRING_LENGTH + 1);
|
$longString = str_repeat("0", (new \ReflectionClassConstant(Database::class, "LIMIT_SET_STRING_LENGTH"))->getValue() + 1);
|
||||||
$params = implode(",", array_fill(0, $l, "?"));
|
$params = implode(",", array_fill(0, $l, "?"));
|
||||||
$intList = implode(",", $ints);
|
$intList = implode(",", $ints);
|
||||||
$stringList = implode(",", array_fill(0, $l, "''"));
|
$stringList = implode(",", array_fill(0, $l, "''"));
|
||||||
|
@ -49,7 +49,7 @@ class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
["?,?", [null, null], [null, null], "str"],
|
["?,?", [null, null], [null, null], "str"],
|
||||||
["null", [], array_fill(0, $l, null), "str"],
|
["null", [], array_fill(0, $l, null), "str"],
|
||||||
["$intList", [], $ints, "int"],
|
["$intList", [], $ints, "int"],
|
||||||
["$intList,".($l+1), [], array_merge($ints, [$l+1]), "int"],
|
["$intList,".($l + 1), [], array_merge($ints, [$l + 1]), "int"],
|
||||||
["$intList,0", [], array_merge($ints, ["OOK"]), "int"],
|
["$intList,0", [], array_merge($ints, ["OOK"]), "int"],
|
||||||
["$intList", [], array_merge($ints, [null]), "int"],
|
["$intList", [], array_merge($ints, [null]), "int"],
|
||||||
["$stringList,''", [], array_merge($strings, [""]), "str"],
|
["$stringList,''", [], array_merge($strings, [""]), "str"],
|
||||||
|
@ -62,7 +62,7 @@ class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideSearchClauses */
|
/** @dataProvider provideSearchClauses */
|
||||||
public function testGenerateSearchClause(string $clause, array $values, array $inV, array $inC, bool $inAny) {
|
public function testGenerateSearchClause(string $clause, array $values, array $inV, array $inC, bool $inAny): void {
|
||||||
// this is not an exhaustive test; integration tests already cover the ins and outs of the functionality
|
// this is not an exhaustive test; integration tests already cover the ins and outs of the functionality
|
||||||
$types = array_fill(0, sizeof($values), "str");
|
$types = array_fill(0, sizeof($values), "str");
|
||||||
$exp = [$clause, $types, $values];
|
$exp = [$clause, $types, $values];
|
||||||
|
@ -70,9 +70,10 @@ class TestDatabase extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideSearchClauses(): iterable {
|
public function provideSearchClauses(): iterable {
|
||||||
$terms = array_fill(0, Database::LIMIT_SET_SIZE + 1, "a");
|
$setSize = (new \ReflectionClassConstant(Database::class, "LIMIT_SET_SIZE"))->getValue();
|
||||||
$clause = array_fill(0, Database::LIMIT_SET_SIZE + 1, "test like '%a%' escape '^'");
|
$terms = array_fill(0, $setSize + 1, "a");
|
||||||
$longString = str_repeat("0", Database::LIMIT_SET_STRING_LENGTH + 1);
|
$clause = array_fill(0, $setSize + 1, "test like '%a%' escape '^'");
|
||||||
|
$longString = str_repeat("0", (new \ReflectionClassConstant(Database::class, "LIMIT_SET_STRING_LENGTH"))->getValue() + 1);
|
||||||
return [
|
return [
|
||||||
["test like ? escape '^'", ["%a%"], ["a"], ["test"], true],
|
["test like ? escape '^'", ["%a%"], ["a"], ["test"], true],
|
||||||
["(col1 like ? escape '^' or col2 like ? escape '^')", ["%a%", "%a%"], ["a"], ["col1", "col2"], true],
|
["(col1 like ? escape '^' or col2 like ? escape '^')", ["%a%", "%a%"], ["a"], ["col1", "col2"], true],
|
||||||
|
|
|
@ -17,8 +17,8 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $lock;
|
protected $lock;
|
||||||
protected $setVersion;
|
protected $setVersion;
|
||||||
protected static $conf = [
|
protected static $conf = [
|
||||||
'dbTimeoutExec' => 0.5,
|
'dbTimeoutExec' => 0.5,
|
||||||
'dbTimeoutLock' => 0.001,
|
'dbTimeoutLock' => 0.001,
|
||||||
'dbSQLite3Timeout' => 0,
|
'dbSQLite3Timeout' => 0,
|
||||||
//'dbSQLite3File' => "(temporary file)",
|
//'dbSQLite3File' => "(temporary file)",
|
||||||
];
|
];
|
||||||
|
@ -29,7 +29,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
static::setConf(static::$conf);
|
static::setConf(static::$conf);
|
||||||
static::$interface = static::dbInterface();
|
static::$interface = static::dbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
self::setConf(static::$conf);
|
self::setConf(static::$conf);
|
||||||
|
@ -75,113 +75,113 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
# TESTS
|
# TESTS
|
||||||
|
|
||||||
public function testFetchDriverName() {
|
public function testFetchDriverName(): void {
|
||||||
$class = get_class($this->drv);
|
$class = get_class($this->drv);
|
||||||
$this->assertTrue(strlen($class::driverName()) > 0);
|
$this->assertTrue(strlen($class::driverName()) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFetchSchemaId() {
|
public function testFetchSchemaId(): void {
|
||||||
$class = get_class($this->drv);
|
$class = get_class($this->drv);
|
||||||
$this->assertTrue(strlen($class::schemaID()) > 0);
|
$this->assertTrue(strlen($class::schemaID()) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckCharacterSetAcceptability() {
|
public function testCheckCharacterSetAcceptability(): void {
|
||||||
$this->assertTrue($this->drv->charsetAcceptable());
|
$this->assertTrue($this->drv->charsetAcceptable());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTranslateAToken() {
|
public function testTranslateAToken(): void {
|
||||||
$this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("greatest"));
|
$this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("greatest"));
|
||||||
$this->assertRegExp("/^\"?[a-z][a-z0-9_\-]*\"?$/i", $this->drv->sqlToken("nocase"));
|
$this->assertRegExp("/^\"?[a-z][a-z0-9_\-]*\"?$/i", $this->drv->sqlToken("nocase"));
|
||||||
$this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("like"));
|
$this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("like"));
|
||||||
$this->assertSame("distinct", $this->drv->sqlToken("distinct"));
|
$this->assertSame("distinct", $this->drv->sqlToken("distinct"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecAValidStatement() {
|
public function testExecAValidStatement(): void {
|
||||||
$this->assertTrue($this->drv->exec($this->create));
|
$this->assertTrue($this->drv->exec($this->create));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecAnInvalidStatement() {
|
public function testExecAnInvalidStatement(): void {
|
||||||
$this->assertException("engineErrorGeneral", "Db");
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
$this->drv->exec("And the meek shall inherit the earth...");
|
$this->drv->exec("And the meek shall inherit the earth...");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecMultipleStatements() {
|
public function testExecMultipleStatements(): void {
|
||||||
$this->assertTrue($this->drv->exec("$this->create; INSERT INTO arsse_test(id) values(2112)"));
|
$this->assertTrue($this->drv->exec("$this->create; INSERT INTO arsse_test(id) values(2112)"));
|
||||||
$this->assertEquals(2112, $this->query("SELECT id from arsse_test"));
|
$this->assertEquals(2112, $this->query("SELECT id from arsse_test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecTimeout() {
|
public function testExecTimeout(): void {
|
||||||
$this->exec($this->create);
|
$this->exec($this->create);
|
||||||
$this->exec($this->lock);
|
$this->exec($this->lock);
|
||||||
$this->assertException("general", "Db", "ExceptionTimeout");
|
$this->assertException("general", "Db", "ExceptionTimeout");
|
||||||
$this->drv->exec("INSERT INTO arsse_meta(\"key\", value) values('lock', '1')");
|
$this->drv->exec("INSERT INTO arsse_meta(\"key\", value) values('lock', '1')");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecConstraintViolation() {
|
public function testExecConstraintViolation(): void {
|
||||||
$this->drv->exec("CREATE TABLE arsse_test(id varchar(255) not null)");
|
$this->drv->exec("CREATE TABLE arsse_test(id varchar(255) not null)");
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
$this->drv->exec(static::$insertDefaultValues);
|
$this->drv->exec(static::$insertDefaultValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExecTypeViolation() {
|
public function testExecTypeViolation(): void {
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
$this->drv->exec("INSERT INTO arsse_test(id) values('ook')");
|
$this->drv->exec("INSERT INTO arsse_test(id) values('ook')");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeAValidQuery() {
|
public function testMakeAValidQuery(): void {
|
||||||
$this->assertInstanceOf(Result::class, $this->drv->query("SELECT 1"));
|
$this->assertInstanceOf(Result::class, $this->drv->query("SELECT 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeAnInvalidQuery() {
|
public function testMakeAnInvalidQuery(): void {
|
||||||
$this->assertException("engineErrorGeneral", "Db");
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
|
$this->drv->query("Apollo was astonished; Dionysus thought me mad");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testQueryConstraintViolation() {
|
public function testQueryConstraintViolation(): void {
|
||||||
$this->drv->exec("CREATE TABLE arsse_test(id integer not null)");
|
$this->drv->exec("CREATE TABLE arsse_test(id integer not null)");
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
$this->drv->query(static::$insertDefaultValues);
|
$this->drv->query(static::$insertDefaultValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testQueryTypeViolation() {
|
public function testQueryTypeViolation(): void {
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
$this->drv->query("INSERT INTO arsse_test(id) values('ook')");
|
$this->drv->query("INSERT INTO arsse_test(id) values('ook')");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPrepareAValidQuery() {
|
public function testPrepareAValidQuery(): void {
|
||||||
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
|
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
|
||||||
$this->assertInstanceOf(Statement::class, $s);
|
$this->assertInstanceOf(Statement::class, $s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPrepareAnInvalidQuery() {
|
public function testPrepareAnInvalidQuery(): void {
|
||||||
$this->assertException("engineErrorGeneral", "Db");
|
$this->assertException("engineErrorGeneral", "Db");
|
||||||
$s = $this->drv->prepare("This is an invalid query", "int", "int")->run();
|
$s = $this->drv->prepare("This is an invalid query", "int", "int")->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateASavepoint() {
|
public function testCreateASavepoint(): void {
|
||||||
$this->assertEquals(1, $this->drv->savepointCreate());
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(2, $this->drv->savepointCreate());
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(3, $this->drv->savepointCreate());
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReleaseASavepoint() {
|
public function testReleaseASavepoint(): void {
|
||||||
$this->assertEquals(1, $this->drv->savepointCreate());
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(true, $this->drv->savepointRelease());
|
$this->assertEquals(true, $this->drv->savepointRelease());
|
||||||
$this->assertException("savepointInvalid", "Db");
|
$this->assertException("savepointInvalid", "Db");
|
||||||
$this->drv->savepointRelease();
|
$this->drv->savepointRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUndoASavepoint() {
|
public function testUndoASavepoint(): void {
|
||||||
$this->assertEquals(1, $this->drv->savepointCreate());
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(true, $this->drv->savepointUndo());
|
$this->assertEquals(true, $this->drv->savepointUndo());
|
||||||
$this->assertException("savepointInvalid", "Db");
|
$this->assertException("savepointInvalid", "Db");
|
||||||
$this->drv->savepointUndo();
|
$this->drv->savepointUndo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManipulateSavepoints() {
|
public function testManipulateSavepoints(): void {
|
||||||
$this->assertEquals(1, $this->drv->savepointCreate());
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(2, $this->drv->savepointCreate());
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(3, $this->drv->savepointCreate());
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
@ -198,7 +198,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->drv->savepointRelease(2);
|
$this->drv->savepointRelease(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManipulateSavepointsSomeMore() {
|
public function testManipulateSavepointsSomeMore(): void {
|
||||||
$this->assertEquals(1, $this->drv->savepointCreate());
|
$this->assertEquals(1, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(2, $this->drv->savepointCreate());
|
$this->assertEquals(2, $this->drv->savepointCreate());
|
||||||
$this->assertEquals(3, $this->drv->savepointCreate());
|
$this->assertEquals(3, $this->drv->savepointCreate());
|
||||||
|
@ -209,7 +209,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->drv->savepointUndo(2);
|
$this->drv->savepointUndo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBeginATransaction() {
|
public function testBeginATransaction(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr = $this->drv->begin();
|
$tr = $this->drv->begin();
|
||||||
|
@ -221,7 +221,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(0, $this->query($select));
|
$this->assertEquals(0, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCommitATransaction() {
|
public function testCommitATransaction(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr = $this->drv->begin();
|
$tr = $this->drv->begin();
|
||||||
|
@ -233,7 +233,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(1, $this->query($select));
|
$this->assertEquals(1, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRollbackATransaction() {
|
public function testRollbackATransaction(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr = $this->drv->begin();
|
$tr = $this->drv->begin();
|
||||||
|
@ -245,7 +245,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(0, $this->query($select));
|
$this->assertEquals(0, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBeginChainedTransactions() {
|
public function testBeginChainedTransactions(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -258,7 +258,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(0, $this->query($select));
|
$this->assertEquals(0, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCommitChainedTransactions() {
|
public function testCommitChainedTransactions(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -275,7 +275,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(2, $this->query($select));
|
$this->assertEquals(2, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCommitChainedTransactionsOutOfOrder() {
|
public function testCommitChainedTransactionsOutOfOrder(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -291,7 +291,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$tr2->commit();
|
$tr2->commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRollbackChainedTransactions() {
|
public function testRollbackChainedTransactions(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -310,7 +310,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(0, $this->query($select));
|
$this->assertEquals(0, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRollbackChainedTransactionsOutOfOrder() {
|
public function testRollbackChainedTransactionsOutOfOrder(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -329,7 +329,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(0, $this->query($select));
|
$this->assertEquals(0, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPartiallyRollbackChainedTransactions() {
|
public function testPartiallyRollbackChainedTransactions(): void {
|
||||||
$select = "SELECT count(*) FROM arsse_test";
|
$select = "SELECT count(*) FROM arsse_test";
|
||||||
$this->drv->exec($this->create);
|
$this->drv->exec($this->create);
|
||||||
$tr1 = $this->drv->begin();
|
$tr1 = $this->drv->begin();
|
||||||
|
@ -348,7 +348,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(1, $this->query($select));
|
$this->assertEquals(1, $this->query($select));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFetchSchemaVersion() {
|
public function testFetchSchemaVersion(): void {
|
||||||
$this->assertSame(0, $this->drv->schemaVersion());
|
$this->assertSame(0, $this->drv->schemaVersion());
|
||||||
$this->drv->exec(str_replace("#", "1", $this->setVersion));
|
$this->drv->exec(str_replace("#", "1", $this->setVersion));
|
||||||
$this->assertSame(1, $this->drv->schemaVersion());
|
$this->assertSame(1, $this->drv->schemaVersion());
|
||||||
|
@ -361,7 +361,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($exp, $this->drv->schemaVersion());
|
$this->assertSame($exp, $this->drv->schemaVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLockTheDatabase() {
|
public function testLockTheDatabase(): void {
|
||||||
// PostgreSQL doesn't actually lock the whole database, only the metadata table
|
// PostgreSQL doesn't actually lock the whole database, only the metadata table
|
||||||
// normally the application will first query this table to ensure the schema version is correct,
|
// normally the application will first query this table to ensure the schema version is correct,
|
||||||
// so the effect is usually the same
|
// so the effect is usually the same
|
||||||
|
@ -370,7 +370,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->exec($this->lock);
|
$this->exec($this->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnlockTheDatabase() {
|
public function testUnlockTheDatabase(): void {
|
||||||
$this->drv->savepointCreate(true);
|
$this->drv->savepointCreate(true);
|
||||||
$this->drv->savepointRelease();
|
$this->drv->savepointRelease();
|
||||||
$this->drv->savepointCreate(true);
|
$this->drv->savepointCreate(true);
|
||||||
|
@ -378,11 +378,11 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertTrue($this->exec(str_replace("#", "3", $this->setVersion)));
|
$this->assertTrue($this->exec(str_replace("#", "3", $this->setVersion)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testProduceAStringLiteral() {
|
public function testProduceAStringLiteral(): void {
|
||||||
$this->assertSame("'It''s a string!'", $this->drv->literalString("It's a string!"));
|
$this->assertSame("'It''s a string!'", $this->drv->literalString("It's a string!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformMaintenance() {
|
public function testPerformMaintenance(): void {
|
||||||
// this performs maintenance in the absence of tables; see BaseUpdate.php for another test with tables
|
// this performs maintenance in the absence of tables; see BaseUpdate.php for another test with tables
|
||||||
$this->assertTrue($this->drv->maintenance());
|
$this->assertTrue($this->drv->maintenance());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
static::setConf();
|
static::setConf();
|
||||||
static::$interface = static::dbInterface();
|
static::$interface = static::dbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
self::setConf();
|
self::setConf();
|
||||||
|
@ -46,18 +46,18 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConstructResult() {
|
public function testConstructResult(): void {
|
||||||
$this->assertInstanceOf(Result::class, new $this->resultClass(...$this->makeResult("SELECT 1")));
|
$this->assertInstanceOf(Result::class, new $this->resultClass(...$this->makeResult("SELECT 1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetChangeCountAndLastInsertId() {
|
public function testGetChangeCountAndLastInsertId(): void {
|
||||||
$this->makeResult(static::$createMeta);
|
$this->makeResult(static::$createMeta);
|
||||||
$r = new $this->resultClass(...$this->makeResult("INSERT INTO arsse_meta(\"key\",value) values('test', 1)"));
|
$r = new $this->resultClass(...$this->makeResult("INSERT INTO arsse_meta(\"key\",value) values('test', 1)"));
|
||||||
$this->assertSame(1, $r->changes());
|
$this->assertSame(1, $r->changes());
|
||||||
$this->assertSame(0, $r->lastId());
|
$this->assertSame(0, $r->lastId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetChangeCountAndLastInsertIdBis() {
|
public function testGetChangeCountAndLastInsertIdBis(): void {
|
||||||
$this->makeResult(static::$createTest);
|
$this->makeResult(static::$createTest);
|
||||||
$r = new $this->resultClass(...$this->makeResult(static::$insertDefault));
|
$r = new $this->resultClass(...$this->makeResult(static::$insertDefault));
|
||||||
$this->assertSame(1, $r->changes());
|
$this->assertSame(1, $r->changes());
|
||||||
|
@ -67,7 +67,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(2, $r->lastId());
|
$this->assertSame(2, $r->lastId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIterateOverResults() {
|
public function testIterateOverResults(): void {
|
||||||
$exp = [0 => 1, 1 => 2, 2 => 3];
|
$exp = [0 => 1, 1 => 2, 2 => 3];
|
||||||
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
||||||
foreach (new $this->resultClass(...$this->makeResult("SELECT 1 as col union select 2 as col union select 3 as col")) as $index => $row) {
|
foreach (new $this->resultClass(...$this->makeResult("SELECT 1 as col union select 2 as col union select 3 as col")) as $index => $row) {
|
||||||
|
@ -76,7 +76,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($exp, $rows);
|
$this->assertSame($exp, $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIterateOverResultsTwice() {
|
public function testIterateOverResultsTwice(): void {
|
||||||
$exp = [0 => 1, 1 => 2, 2 => 3];
|
$exp = [0 => 1, 1 => 2, 2 => 3];
|
||||||
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
||||||
$result = new $this->resultClass(...$this->makeResult("SELECT 1 as col union select 2 as col union select 3 as col"));
|
$result = new $this->resultClass(...$this->makeResult("SELECT 1 as col union select 2 as col union select 3 as col"));
|
||||||
|
@ -90,7 +90,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetSingleValues() {
|
public function testGetSingleValues(): void {
|
||||||
$exp = [1867, 1970, 2112];
|
$exp = [1867, 1970, 2112];
|
||||||
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
||||||
$test = new $this->resultClass(...$this->makeResult("SELECT 1867 as year union all select 1970 as year union all select 2112 as year"));
|
$test = new $this->resultClass(...$this->makeResult("SELECT 1867 as year union all select 1970 as year union all select 2112 as year"));
|
||||||
|
@ -100,7 +100,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getValue());
|
$this->assertSame(null, $test->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFirstValuesOnly() {
|
public function testGetFirstValuesOnly(): void {
|
||||||
$exp = [1867, 1970, 2112];
|
$exp = [1867, 1970, 2112];
|
||||||
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
$exp = static::$stringOutput ? $this->stringify($exp) : $exp;
|
||||||
$test = new $this->resultClass(...$this->makeResult("SELECT 1867 as year, 19 as century union all select 1970 as year, 20 as century union all select 2112 as year, 22 as century"));
|
$test = new $this->resultClass(...$this->makeResult("SELECT 1867 as year, 19 as century union all select 1970 as year, 20 as century union all select 2112 as year, 22 as century"));
|
||||||
|
@ -110,7 +110,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getValue());
|
$this->assertSame(null, $test->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRows() {
|
public function testGetRows(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['album' => '2112', 'track' => '2112'],
|
['album' => '2112', 'track' => '2112'],
|
||||||
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
||||||
|
@ -121,7 +121,7 @@ abstract class BaseResult extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getRow());
|
$this->assertSame(null, $test->getRow());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAllRows() {
|
public function testGetAllRows(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
['album' => '2112', 'track' => '2112'],
|
['album' => '2112', 'track' => '2112'],
|
||||||
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
|
||||||
|
|
|
@ -21,7 +21,7 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
static::setConf();
|
static::setConf();
|
||||||
static::$interface = static::dbInterface();
|
static::$interface = static::dbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
self::setConf();
|
self::setConf();
|
||||||
|
@ -46,12 +46,12 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConstructStatement() {
|
public function testConstructStatement(): void {
|
||||||
$this->assertInstanceOf(Statement::class, new $this->statementClass(...$this->makeStatement("SELECT ? as value")));
|
$this->assertInstanceOf(Statement::class, new $this->statementClass(...$this->makeStatement("SELECT ? as value")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideBindings */
|
/** @dataProvider provideBindings */
|
||||||
public function testBindATypedValue($value, string $type, string $exp) {
|
public function testBindATypedValue($value, string $type, string $exp): void {
|
||||||
if ($exp === "null") {
|
if ($exp === "null") {
|
||||||
$query = "SELECT (? is null) as pass";
|
$query = "SELECT (? is null) as pass";
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,7 +65,7 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideBinaryBindings */
|
/** @dataProvider provideBinaryBindings */
|
||||||
public function testHandleBinaryData($value, string $type, string $exp) {
|
public function testHandleBinaryData($value, string $type, string $exp): void {
|
||||||
if (in_array(static::$implementation, ["PostgreSQL", "PDO PostgreSQL"])) {
|
if (in_array(static::$implementation, ["PostgreSQL", "PDO PostgreSQL"])) {
|
||||||
$this->markTestIncomplete("Correct handling of binary data with PostgreSQL is not currently implemented");
|
$this->markTestIncomplete("Correct handling of binary data with PostgreSQL is not currently implemented");
|
||||||
}
|
}
|
||||||
|
@ -81,13 +81,13 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertTrue((bool) $act);
|
$this->assertTrue((bool) $act);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBindMissingValue() {
|
public function testBindMissingValue(): void {
|
||||||
$s = new $this->statementClass(...$this->makeStatement("SELECT ? as value", ["int"]));
|
$s = new $this->statementClass(...$this->makeStatement("SELECT ? as value", ["int"]));
|
||||||
$val = $s->runArray()->getRow()['value'];
|
$val = $s->runArray()->getRow()['value'];
|
||||||
$this->assertSame(null, $val);
|
$this->assertSame(null, $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBindMultipleValues() {
|
public function testBindMultipleValues(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
'one' => "A",
|
'one' => "A",
|
||||||
'two' => "B",
|
'two' => "B",
|
||||||
|
@ -97,7 +97,7 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($exp, $val);
|
$this->assertSame($exp, $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBindRecursively() {
|
public function testBindRecursively(): void {
|
||||||
$exp = [
|
$exp = [
|
||||||
'one' => "A",
|
'one' => "A",
|
||||||
'two' => "B",
|
'two' => "B",
|
||||||
|
@ -109,20 +109,20 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($exp, $val);
|
$this->assertSame($exp, $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBindWithoutType() {
|
public function testBindWithoutType(): void {
|
||||||
$this->assertException("paramTypeMissing", "Db");
|
$this->assertException("paramTypeMissing", "Db");
|
||||||
$s = new $this->statementClass(...$this->makeStatement("SELECT ? as value", []));
|
$s = new $this->statementClass(...$this->makeStatement("SELECT ? as value", []));
|
||||||
$s->runArray([1]);
|
$s->runArray([1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testViolateConstraint() {
|
public function testViolateConstraint(): void {
|
||||||
(new $this->statementClass(...$this->makeStatement("CREATE TABLE if not exists arsse_meta(\"key\" varchar(255) primary key not null, value text)")))->run();
|
(new $this->statementClass(...$this->makeStatement("CREATE TABLE if not exists arsse_meta(\"key\" varchar(255) primary key not null, value text)")))->run();
|
||||||
$s = new $this->statementClass(...$this->makeStatement("INSERT INTO arsse_meta(\"key\") values(?)", ["str"]));
|
$s = new $this->statementClass(...$this->makeStatement("INSERT INTO arsse_meta(\"key\") values(?)", ["str"]));
|
||||||
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
$this->assertException("constraintViolation", "Db", "ExceptionInput");
|
||||||
$s->runArray([null]);
|
$s->runArray([null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMismatchTypes() {
|
public function testMismatchTypes(): void {
|
||||||
(new $this->statementClass(...$this->makeStatement("CREATE TABLE if not exists arsse_feeds(id integer primary key not null, url text not null)")))->run();
|
(new $this->statementClass(...$this->makeStatement("CREATE TABLE if not exists arsse_feeds(id integer primary key not null, url text not null)")))->run();
|
||||||
$s = new $this->statementClass(...$this->makeStatement("INSERT INTO arsse_feeds(id,url) values(?,?)", ["str", "str"]));
|
$s = new $this->statementClass(...$this->makeStatement("INSERT INTO arsse_feeds(id,url) values(?,?)", ["str", "str"]));
|
||||||
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
$this->assertException("typeViolation", "Db", "ExceptionInput");
|
||||||
|
@ -134,138 +134,138 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
$tests = [
|
$tests = [
|
||||||
'Null as integer' => [null, "integer", "null"],
|
'Null as integer' => [null, "integer", "null"],
|
||||||
'Null as float' => [null, "float", "null"],
|
'Null as float' => [null, "float", "null"],
|
||||||
'Null as string' => [null, "string", "null"],
|
'Null as string' => [null, "string", "null"],
|
||||||
'Null as datetime' => [null, "datetime", "null"],
|
'Null as datetime' => [null, "datetime", "null"],
|
||||||
'Null as boolean' => [null, "boolean", "null"],
|
'Null as boolean' => [null, "boolean", "null"],
|
||||||
'Null as strict integer' => [null, "strict integer", "0"],
|
'Null as strict integer' => [null, "strict integer", "0"],
|
||||||
'Null as strict float' => [null, "strict float", "0.0"],
|
'Null as strict float' => [null, "strict float", "0.0"],
|
||||||
'Null as strict string' => [null, "strict string", "''"],
|
'Null as strict string' => [null, "strict string", "''"],
|
||||||
'Null as strict datetime' => [null, "strict datetime", "'0001-01-01 00:00:00'"],
|
'Null as strict datetime' => [null, "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'Null as strict boolean' => [null, "strict boolean", "0"],
|
'Null as strict boolean' => [null, "strict boolean", "0"],
|
||||||
'True as integer' => [true, "integer", "1"],
|
'True as integer' => [true, "integer", "1"],
|
||||||
'True as float' => [true, "float", "1.0"],
|
'True as float' => [true, "float", "1.0"],
|
||||||
'True as string' => [true, "string", "'1'"],
|
'True as string' => [true, "string", "'1'"],
|
||||||
'True as datetime' => [true, "datetime", "null"],
|
'True as datetime' => [true, "datetime", "null"],
|
||||||
'True as boolean' => [true, "boolean", "1"],
|
'True as boolean' => [true, "boolean", "1"],
|
||||||
'True as strict integer' => [true, "strict integer", "1"],
|
'True as strict integer' => [true, "strict integer", "1"],
|
||||||
'True as strict float' => [true, "strict float", "1.0"],
|
'True as strict float' => [true, "strict float", "1.0"],
|
||||||
'True as strict string' => [true, "strict string", "'1'"],
|
'True as strict string' => [true, "strict string", "'1'"],
|
||||||
'True as strict datetime' => [true, "strict datetime", "'0001-01-01 00:00:00'"],
|
'True as strict datetime' => [true, "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'True as strict boolean' => [true, "strict boolean", "1"],
|
'True as strict boolean' => [true, "strict boolean", "1"],
|
||||||
'False as integer' => [false, "integer", "0"],
|
'False as integer' => [false, "integer", "0"],
|
||||||
'False as float' => [false, "float", "0.0"],
|
'False as float' => [false, "float", "0.0"],
|
||||||
'False as string' => [false, "string", "''"],
|
'False as string' => [false, "string", "''"],
|
||||||
'False as datetime' => [false, "datetime", "null"],
|
'False as datetime' => [false, "datetime", "null"],
|
||||||
'False as boolean' => [false, "boolean", "0"],
|
'False as boolean' => [false, "boolean", "0"],
|
||||||
'False as strict integer' => [false, "strict integer", "0"],
|
'False as strict integer' => [false, "strict integer", "0"],
|
||||||
'False as strict float' => [false, "strict float", "0.0"],
|
'False as strict float' => [false, "strict float", "0.0"],
|
||||||
'False as strict string' => [false, "strict string", "''"],
|
'False as strict string' => [false, "strict string", "''"],
|
||||||
'False as strict datetime' => [false, "strict datetime", "'0001-01-01 00:00:00'"],
|
'False as strict datetime' => [false, "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'False as strict boolean' => [false, "strict boolean", "0"],
|
'False as strict boolean' => [false, "strict boolean", "0"],
|
||||||
'Integer as integer' => [2112, "integer", "2112"],
|
'Integer as integer' => [2112, "integer", "2112"],
|
||||||
'Integer as float' => [2112, "float", "2112.0"],
|
'Integer as float' => [2112, "float", "2112.0"],
|
||||||
'Integer as string' => [2112, "string", "'2112'"],
|
'Integer as string' => [2112, "string", "'2112'"],
|
||||||
'Integer as datetime' => [2112, "datetime", "'1970-01-01 00:35:12'"],
|
'Integer as datetime' => [2112, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
'Integer as boolean' => [2112, "boolean", "1"],
|
'Integer as boolean' => [2112, "boolean", "1"],
|
||||||
'Integer as strict integer' => [2112, "strict integer", "2112"],
|
'Integer as strict integer' => [2112, "strict integer", "2112"],
|
||||||
'Integer as strict float' => [2112, "strict float", "2112.0"],
|
'Integer as strict float' => [2112, "strict float", "2112.0"],
|
||||||
'Integer as strict string' => [2112, "strict string", "'2112'"],
|
'Integer as strict string' => [2112, "strict string", "'2112'"],
|
||||||
'Integer as strict datetime' => [2112, "strict datetime", "'1970-01-01 00:35:12'"],
|
'Integer as strict datetime' => [2112, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
'Integer as strict boolean' => [2112, "strict boolean", "1"],
|
'Integer as strict boolean' => [2112, "strict boolean", "1"],
|
||||||
'Integer zero as integer' => [0, "integer", "0"],
|
'Integer zero as integer' => [0, "integer", "0"],
|
||||||
'Integer zero as float' => [0, "float", "0.0"],
|
'Integer zero as float' => [0, "float", "0.0"],
|
||||||
'Integer zero as string' => [0, "string", "'0'"],
|
'Integer zero as string' => [0, "string", "'0'"],
|
||||||
'Integer zero as datetime' => [0, "datetime", "'1970-01-01 00:00:00'"],
|
'Integer zero as datetime' => [0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
'Integer zero as boolean' => [0, "boolean", "0"],
|
'Integer zero as boolean' => [0, "boolean", "0"],
|
||||||
'Integer zero as strict integer' => [0, "strict integer", "0"],
|
'Integer zero as strict integer' => [0, "strict integer", "0"],
|
||||||
'Integer zero as strict float' => [0, "strict float", "0.0"],
|
'Integer zero as strict float' => [0, "strict float", "0.0"],
|
||||||
'Integer zero as strict string' => [0, "strict string", "'0'"],
|
'Integer zero as strict string' => [0, "strict string", "'0'"],
|
||||||
'Integer zero as strict datetime' => [0, "strict datetime", "'1970-01-01 00:00:00'"],
|
'Integer zero as strict datetime' => [0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
'Integer zero as strict boolean' => [0, "strict boolean", "0"],
|
'Integer zero as strict boolean' => [0, "strict boolean", "0"],
|
||||||
'Float as integer' => [2112.5, "integer", "2112"],
|
'Float as integer' => [2112.5, "integer", "2112"],
|
||||||
'Float as float' => [2112.5, "float", "2112.5"],
|
'Float as float' => [2112.5, "float", "2112.5"],
|
||||||
'Float as string' => [2112.5, "string", "'2112.5'"],
|
'Float as string' => [2112.5, "string", "'2112.5'"],
|
||||||
'Float as datetime' => [2112.5, "datetime", "'1970-01-01 00:35:12'"],
|
'Float as datetime' => [2112.5, "datetime", "'1970-01-01 00:35:12'"],
|
||||||
'Float as boolean' => [2112.5, "boolean", "1"],
|
'Float as boolean' => [2112.5, "boolean", "1"],
|
||||||
'Float as strict integer' => [2112.5, "strict integer", "2112"],
|
'Float as strict integer' => [2112.5, "strict integer", "2112"],
|
||||||
'Float as strict float' => [2112.5, "strict float", "2112.5"],
|
'Float as strict float' => [2112.5, "strict float", "2112.5"],
|
||||||
'Float as strict string' => [2112.5, "strict string", "'2112.5'"],
|
'Float as strict string' => [2112.5, "strict string", "'2112.5'"],
|
||||||
'Float as strict datetime' => [2112.5, "strict datetime", "'1970-01-01 00:35:12'"],
|
'Float as strict datetime' => [2112.5, "strict datetime", "'1970-01-01 00:35:12'"],
|
||||||
'Float as strict boolean' => [2112.5, "strict boolean", "1"],
|
'Float as strict boolean' => [2112.5, "strict boolean", "1"],
|
||||||
'Float zero as integer' => [0.0, "integer", "0"],
|
'Float zero as integer' => [0.0, "integer", "0"],
|
||||||
'Float zero as float' => [0.0, "float", "0.0"],
|
'Float zero as float' => [0.0, "float", "0.0"],
|
||||||
'Float zero as string' => [0.0, "string", "'0'"],
|
'Float zero as string' => [0.0, "string", "'0'"],
|
||||||
'Float zero as datetime' => [0.0, "datetime", "'1970-01-01 00:00:00'"],
|
'Float zero as datetime' => [0.0, "datetime", "'1970-01-01 00:00:00'"],
|
||||||
'Float zero as boolean' => [0.0, "boolean", "0"],
|
'Float zero as boolean' => [0.0, "boolean", "0"],
|
||||||
'Float zero as strict integer' => [0.0, "strict integer", "0"],
|
'Float zero as strict integer' => [0.0, "strict integer", "0"],
|
||||||
'Float zero as strict float' => [0.0, "strict float", "0.0"],
|
'Float zero as strict float' => [0.0, "strict float", "0.0"],
|
||||||
'Float zero as strict string' => [0.0, "strict string", "'0'"],
|
'Float zero as strict string' => [0.0, "strict string", "'0'"],
|
||||||
'Float zero as strict datetime' => [0.0, "strict datetime", "'1970-01-01 00:00:00'"],
|
'Float zero as strict datetime' => [0.0, "strict datetime", "'1970-01-01 00:00:00'"],
|
||||||
'Float zero as strict boolean' => [0.0, "strict boolean", "0"],
|
'Float zero as strict boolean' => [0.0, "strict boolean", "0"],
|
||||||
'ASCII string as integer' => ["Random string", "integer", "0"],
|
'ASCII string as integer' => ["Random string", "integer", "0"],
|
||||||
'ASCII string as float' => ["Random string", "float", "0.0"],
|
'ASCII string as float' => ["Random string", "float", "0.0"],
|
||||||
'ASCII string as string' => ["Random string", "string", "'Random string'"],
|
'ASCII string as string' => ["Random string", "string", "'Random string'"],
|
||||||
'ASCII string as datetime' => ["Random string", "datetime", "null"],
|
'ASCII string as datetime' => ["Random string", "datetime", "null"],
|
||||||
'ASCII string as boolean' => ["Random string", "boolean", "1"],
|
'ASCII string as boolean' => ["Random string", "boolean", "1"],
|
||||||
'ASCII string as strict integer' => ["Random string", "strict integer", "0"],
|
'ASCII string as strict integer' => ["Random string", "strict integer", "0"],
|
||||||
'ASCII string as strict float' => ["Random string", "strict float", "0.0"],
|
'ASCII string as strict float' => ["Random string", "strict float", "0.0"],
|
||||||
'ASCII string as strict string' => ["Random string", "strict string", "'Random string'"],
|
'ASCII string as strict string' => ["Random string", "strict string", "'Random string'"],
|
||||||
'ASCII string as strict datetime' => ["Random string", "strict datetime", "'0001-01-01 00:00:00'"],
|
'ASCII string as strict datetime' => ["Random string", "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'ASCII string as strict boolean' => ["Random string", "strict boolean", "1"],
|
'ASCII string as strict boolean' => ["Random string", "strict boolean", "1"],
|
||||||
'UTF-8 string as integer' => ["\u{e9}", "integer", "0"],
|
'UTF-8 string as integer' => ["\u{e9}", "integer", "0"],
|
||||||
'UTF-8 string as float' => ["\u{e9}", "float", "0.0"],
|
'UTF-8 string as float' => ["\u{e9}", "float", "0.0"],
|
||||||
'UTF-8 string as string' => ["\u{e9}", "string", "char(233)"],
|
'UTF-8 string as string' => ["\u{e9}", "string", "char(233)"],
|
||||||
'UTF-8 string as datetime' => ["\u{e9}", "datetime", "null"],
|
'UTF-8 string as datetime' => ["\u{e9}", "datetime", "null"],
|
||||||
'UTF-8 string as boolean' => ["\u{e9}", "boolean", "1"],
|
'UTF-8 string as boolean' => ["\u{e9}", "boolean", "1"],
|
||||||
'UTF-8 string as strict integer' => ["\u{e9}", "strict integer", "0"],
|
'UTF-8 string as strict integer' => ["\u{e9}", "strict integer", "0"],
|
||||||
'UTF-8 string as strict float' => ["\u{e9}", "strict float", "0.0"],
|
'UTF-8 string as strict float' => ["\u{e9}", "strict float", "0.0"],
|
||||||
'UTF-8 string as strict string' => ["\u{e9}", "strict string", "char(233)"],
|
'UTF-8 string as strict string' => ["\u{e9}", "strict string", "char(233)"],
|
||||||
'UTF-8 string as strict datetime' => ["\u{e9}", "strict datetime", "'0001-01-01 00:00:00'"],
|
'UTF-8 string as strict datetime' => ["\u{e9}", "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'UTF-8 string as strict boolean' => ["\u{e9}", "strict boolean", "1"],
|
'UTF-8 string as strict boolean' => ["\u{e9}", "strict boolean", "1"],
|
||||||
'ISO 8601 string as integer' => ["2017-01-09T13:11:17", "integer", "0"],
|
'ISO 8601 string as integer' => ["2017-01-09T13:11:17", "integer", "0"],
|
||||||
'ISO 8601 string as float' => ["2017-01-09T13:11:17", "float", "0.0"],
|
'ISO 8601 string as float' => ["2017-01-09T13:11:17", "float", "0.0"],
|
||||||
'ISO 8601 string as string' => ["2017-01-09T13:11:17", "string", "'2017-01-09T13:11:17'"],
|
'ISO 8601 string as string' => ["2017-01-09T13:11:17", "string", "'2017-01-09T13:11:17'"],
|
||||||
'ISO 8601 string as datetime' => ["2017-01-09T13:11:17", "datetime", "'2017-01-09 13:11:17'"],
|
'ISO 8601 string as datetime' => ["2017-01-09T13:11:17", "datetime", "'2017-01-09 13:11:17'"],
|
||||||
'ISO 8601 string as boolean' => ["2017-01-09T13:11:17", "boolean", "1"],
|
'ISO 8601 string as boolean' => ["2017-01-09T13:11:17", "boolean", "1"],
|
||||||
'ISO 8601 string as strict integer' => ["2017-01-09T13:11:17", "strict integer", "0"],
|
'ISO 8601 string as strict integer' => ["2017-01-09T13:11:17", "strict integer", "0"],
|
||||||
'ISO 8601 string as strict float' => ["2017-01-09T13:11:17", "strict float", "0.0"],
|
'ISO 8601 string as strict float' => ["2017-01-09T13:11:17", "strict float", "0.0"],
|
||||||
'ISO 8601 string as strict string' => ["2017-01-09T13:11:17", "strict string", "'2017-01-09T13:11:17'"],
|
'ISO 8601 string as strict string' => ["2017-01-09T13:11:17", "strict string", "'2017-01-09T13:11:17'"],
|
||||||
'ISO 8601 string as strict datetime' => ["2017-01-09T13:11:17", "strict datetime", "'2017-01-09 13:11:17'"],
|
'ISO 8601 string as strict datetime' => ["2017-01-09T13:11:17", "strict datetime", "'2017-01-09 13:11:17'"],
|
||||||
'ISO 8601 string as strict boolean' => ["2017-01-09T13:11:17", "strict boolean", "1"],
|
'ISO 8601 string as strict boolean' => ["2017-01-09T13:11:17", "strict boolean", "1"],
|
||||||
'Arbitrary date string as integer' => ["Today", "integer", "0"],
|
'Arbitrary date string as integer' => ["Today", "integer", "0"],
|
||||||
'Arbitrary date string as float' => ["Today", "float", "0.0"],
|
'Arbitrary date string as float' => ["Today", "float", "0.0"],
|
||||||
'Arbitrary date string as string' => ["Today", "string", "'Today'"],
|
'Arbitrary date string as string' => ["Today", "string", "'Today'"],
|
||||||
'Arbitrary date string as datetime' => ["Today", "datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
'Arbitrary date string as datetime' => ["Today", "datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
'Arbitrary date string as boolean' => ["Today", "boolean", "1"],
|
'Arbitrary date string as boolean' => ["Today", "boolean", "1"],
|
||||||
'Arbitrary date string as strict integer' => ["Today", "strict integer", "0"],
|
'Arbitrary date string as strict integer' => ["Today", "strict integer", "0"],
|
||||||
'Arbitrary date string as strict float' => ["Today", "strict float", "0.0"],
|
'Arbitrary date string as strict float' => ["Today", "strict float", "0.0"],
|
||||||
'Arbitrary date string as strict string' => ["Today", "strict string", "'Today'"],
|
'Arbitrary date string as strict string' => ["Today", "strict string", "'Today'"],
|
||||||
'Arbitrary date string as strict datetime' => ["Today", "strict datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
'Arbitrary date string as strict datetime' => ["Today", "strict datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"],
|
||||||
'Arbitrary date string as strict boolean' => ["Today", "strict boolean", "1"],
|
'Arbitrary date string as strict boolean' => ["Today", "strict boolean", "1"],
|
||||||
'DateTime as integer' => [$dateMutable, "integer", (string) $dateUTC->getTimestamp()],
|
'DateTime as integer' => [$dateMutable, "integer", (string) $dateUTC->getTimestamp()],
|
||||||
'DateTime as float' => [$dateMutable, "float", $dateUTC->getTimestamp().".0"],
|
'DateTime as float' => [$dateMutable, "float", $dateUTC->getTimestamp().".0"],
|
||||||
'DateTime as string' => [$dateMutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTime as string' => [$dateMutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTime as datetime' => [$dateMutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTime as datetime' => [$dateMutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTime as boolean' => [$dateMutable, "boolean", "1"],
|
'DateTime as boolean' => [$dateMutable, "boolean", "1"],
|
||||||
'DateTime as strict integer' => [$dateMutable, "strict integer", (string) $dateUTC->getTimestamp()],
|
'DateTime as strict integer' => [$dateMutable, "strict integer", (string) $dateUTC->getTimestamp()],
|
||||||
'DateTime as strict float' => [$dateMutable, "strict float", $dateUTC->getTimestamp().".0"],
|
'DateTime as strict float' => [$dateMutable, "strict float", $dateUTC->getTimestamp().".0"],
|
||||||
'DateTime as strict string' => [$dateMutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTime as strict string' => [$dateMutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTime as strict datetime' => [$dateMutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTime as strict datetime' => [$dateMutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTime as strict boolean' => [$dateMutable, "strict boolean", "1"],
|
'DateTime as strict boolean' => [$dateMutable, "strict boolean", "1"],
|
||||||
'DateTimeImmutable as integer' => [$dateImmutable, "integer", (string) $dateUTC->getTimestamp()],
|
'DateTimeImmutable as integer' => [$dateImmutable, "integer", (string) $dateUTC->getTimestamp()],
|
||||||
'DateTimeImmutable as float' => [$dateImmutable, "float", $dateUTC->getTimestamp().".0"],
|
'DateTimeImmutable as float' => [$dateImmutable, "float", $dateUTC->getTimestamp().".0"],
|
||||||
'DateTimeImmutable as string' => [$dateImmutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTimeImmutable as string' => [$dateImmutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTimeImmutable as datetime' => [$dateImmutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTimeImmutable as datetime' => [$dateImmutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTimeImmutable as boolean' => [$dateImmutable, "boolean", "1"],
|
'DateTimeImmutable as boolean' => [$dateImmutable, "boolean", "1"],
|
||||||
'DateTimeImmutable as strict integer' => [$dateImmutable, "strict integer", (string) $dateUTC->getTimestamp()],
|
'DateTimeImmutable as strict integer' => [$dateImmutable, "strict integer", (string) $dateUTC->getTimestamp()],
|
||||||
'DateTimeImmutable as strict float' => [$dateImmutable, "strict float", $dateUTC->getTimestamp().".0"],
|
'DateTimeImmutable as strict float' => [$dateImmutable, "strict float", $dateUTC->getTimestamp().".0"],
|
||||||
'DateTimeImmutable as strict string' => [$dateImmutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTimeImmutable as strict string' => [$dateImmutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTimeImmutable as strict datetime' => [$dateImmutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
'DateTimeImmutable as strict datetime' => [$dateImmutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"],
|
||||||
'DateTimeImmutable as strict boolean' => [$dateImmutable, "strict boolean", "1"],
|
'DateTimeImmutable as strict boolean' => [$dateImmutable, "strict boolean", "1"],
|
||||||
];
|
];
|
||||||
foreach ($tests as $index => list($value, $type, $exp)) {
|
foreach ($tests as $index => [$value, $type, $exp]) {
|
||||||
$t = preg_replace("<^strict >", "", $type);
|
$t = preg_replace("<^strict >", "", $type);
|
||||||
$exp = ($exp === "null") ? $exp : $this->decorateTypeSyntax($exp, $t);
|
$exp = ($exp === "null") ? $exp : $this->decorateTypeSyntax($exp, $t);
|
||||||
yield $index => [$value, $type, $exp];
|
yield $index => [$value, $type, $exp];
|
||||||
|
@ -277,46 +277,46 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
$tests = [
|
$tests = [
|
||||||
'Null as binary' => [null, "binary", "null"],
|
'Null as binary' => [null, "binary", "null"],
|
||||||
'Null as strict binary' => [null, "strict binary", "x''"],
|
'Null as strict binary' => [null, "strict binary", "x''"],
|
||||||
'True as binary' => [true, "binary", "x'31'"],
|
'True as binary' => [true, "binary", "x'31'"],
|
||||||
'True as strict binary' => [true, "strict binary", "x'31'"],
|
'True as strict binary' => [true, "strict binary", "x'31'"],
|
||||||
'False as binary' => [false, "binary", "x''"],
|
'False as binary' => [false, "binary", "x''"],
|
||||||
'False as strict binary' => [false, "strict binary", "x''"],
|
'False as strict binary' => [false, "strict binary", "x''"],
|
||||||
'Integer as binary' => [2112, "binary", "x'32313132'"],
|
'Integer as binary' => [2112, "binary", "x'32313132'"],
|
||||||
'Integer as strict binary' => [2112, "strict binary", "x'32313132'"],
|
'Integer as strict binary' => [2112, "strict binary", "x'32313132'"],
|
||||||
'Integer zero as binary' => [0, "binary", "x'30'"],
|
'Integer zero as binary' => [0, "binary", "x'30'"],
|
||||||
'Integer zero as strict binary' => [0, "strict binary", "x'30'"],
|
'Integer zero as strict binary' => [0, "strict binary", "x'30'"],
|
||||||
'Float as binary' => [2112.5, "binary", "x'323131322e35'"],
|
'Float as binary' => [2112.5, "binary", "x'323131322e35'"],
|
||||||
'Float as strict binary' => [2112.5, "strict binary", "x'323131322e35'"],
|
'Float as strict binary' => [2112.5, "strict binary", "x'323131322e35'"],
|
||||||
'Float zero as binary' => [0.0, "binary", "x'30'"],
|
'Float zero as binary' => [0.0, "binary", "x'30'"],
|
||||||
'Float zero as strict binary' => [0.0, "strict binary", "x'30'"],
|
'Float zero as strict binary' => [0.0, "strict binary", "x'30'"],
|
||||||
'ASCII string as binary' => ["Random string", "binary", "x'52616e646f6d20737472696e67'"],
|
'ASCII string as binary' => ["Random string", "binary", "x'52616e646f6d20737472696e67'"],
|
||||||
'ASCII string as strict binary' => ["Random string", "strict binary", "x'52616e646f6d20737472696e67'"],
|
'ASCII string as strict binary' => ["Random string", "strict binary", "x'52616e646f6d20737472696e67'"],
|
||||||
'UTF-8 string as binary' => ["\u{e9}", "binary", "x'c3a9'"],
|
'UTF-8 string as binary' => ["\u{e9}", "binary", "x'c3a9'"],
|
||||||
'UTF-8 string as strict binary' => ["\u{e9}", "strict binary", "x'c3a9'"],
|
'UTF-8 string as strict binary' => ["\u{e9}", "strict binary", "x'c3a9'"],
|
||||||
'Binary string as integer' => [chr(233).chr(233), "integer", "0"],
|
'Binary string as integer' => [chr(233).chr(233), "integer", "0"],
|
||||||
'Binary string as float' => [chr(233).chr(233), "float", "0.0"],
|
'Binary string as float' => [chr(233).chr(233), "float", "0.0"],
|
||||||
'Binary string as string' => [chr(233).chr(233), "string", "'".chr(233).chr(233)."'"],
|
'Binary string as string' => [chr(233).chr(233), "string", "'".chr(233).chr(233)."'"],
|
||||||
'Binary string as binary' => [chr(233).chr(233), "binary", "x'e9e9'"],
|
'Binary string as binary' => [chr(233).chr(233), "binary", "x'e9e9'"],
|
||||||
'Binary string as datetime' => [chr(233).chr(233), "datetime", "null"],
|
'Binary string as datetime' => [chr(233).chr(233), "datetime", "null"],
|
||||||
'Binary string as boolean' => [chr(233).chr(233), "boolean", "1"],
|
'Binary string as boolean' => [chr(233).chr(233), "boolean", "1"],
|
||||||
'Binary string as strict integer' => [chr(233).chr(233), "strict integer", "0"],
|
'Binary string as strict integer' => [chr(233).chr(233), "strict integer", "0"],
|
||||||
'Binary string as strict float' => [chr(233).chr(233), "strict float", "0.0"],
|
'Binary string as strict float' => [chr(233).chr(233), "strict float", "0.0"],
|
||||||
'Binary string as strict string' => [chr(233).chr(233), "strict string", "'".chr(233).chr(233)."'"],
|
'Binary string as strict string' => [chr(233).chr(233), "strict string", "'".chr(233).chr(233)."'"],
|
||||||
'Binary string as strict binary' => [chr(233).chr(233), "strict binary", "x'e9e9'"],
|
'Binary string as strict binary' => [chr(233).chr(233), "strict binary", "x'e9e9'"],
|
||||||
'Binary string as strict datetime' => [chr(233).chr(233), "strict datetime", "'0001-01-01 00:00:00'"],
|
'Binary string as strict datetime' => [chr(233).chr(233), "strict datetime", "'0001-01-01 00:00:00'"],
|
||||||
'Binary string as strict boolean' => [chr(233).chr(233), "strict boolean", "1"],
|
'Binary string as strict boolean' => [chr(233).chr(233), "strict boolean", "1"],
|
||||||
'ISO 8601 string as binary' => ["2017-01-09T13:11:17", "binary", "x'323031372d30312d30395431333a31313a3137'"],
|
'ISO 8601 string as binary' => ["2017-01-09T13:11:17", "binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
'ISO 8601 string as strict binary' => ["2017-01-09T13:11:17", "strict binary", "x'323031372d30312d30395431333a31313a3137'"],
|
'ISO 8601 string as strict binary' => ["2017-01-09T13:11:17", "strict binary", "x'323031372d30312d30395431333a31313a3137'"],
|
||||||
'Arbitrary date string as binary' => ["Today", "binary", "x'546f646179'"],
|
'Arbitrary date string as binary' => ["Today", "binary", "x'546f646179'"],
|
||||||
'Arbitrary date string as strict binary' => ["Today", "strict binary", "x'546f646179'"],
|
'Arbitrary date string as strict binary' => ["Today", "strict binary", "x'546f646179'"],
|
||||||
'DateTime as binary' => [$dateMutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
'DateTime as binary' => [$dateMutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
'DateTime as strict binary' => [$dateMutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
'DateTime as strict binary' => [$dateMutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
'DateTimeImmutable as binary' => [$dateImmutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
'DateTimeImmutable as binary' => [$dateImmutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
'DateTimeImmutable as strict binary' => [$dateImmutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
'DateTimeImmutable as strict binary' => [$dateImmutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"],
|
||||||
];
|
];
|
||||||
foreach ($tests as $index => list($value, $type, $exp)) {
|
foreach ($tests as $index => [$value, $type, $exp]) {
|
||||||
$t = preg_replace("<^strict >", "", $type);
|
$t = preg_replace("<^strict >", "", $type);
|
||||||
$exp = ($exp === "null") ? $exp : $this->decorateTypeSyntax($exp, $t);
|
$exp = ($exp === "null") ? $exp : $this->decorateTypeSyntax($exp, $t);
|
||||||
yield $index => [$value, $type, $exp];
|
yield $index => [$value, $type, $exp];
|
||||||
|
|
|
@ -24,7 +24,7 @@ class BaseUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
static::setConf();
|
static::setConf();
|
||||||
static::$interface = static::dbInterface();
|
static::$interface = static::dbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
if (!static::$interface) {
|
if (!static::$interface) {
|
||||||
$this->markTestSkipped(static::$implementation." database driver not available");
|
$this->markTestSkipped(static::$implementation." database driver not available");
|
||||||
|
@ -58,43 +58,43 @@ class BaseUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadMissingFile() {
|
public function testLoadMissingFile(): void {
|
||||||
$this->assertException("updateFileMissing", "Db");
|
$this->assertException("updateFileMissing", "Db");
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadUnreadableFile() {
|
public function testLoadUnreadableFile(): void {
|
||||||
touch($this->path."0.sql");
|
touch($this->path."0.sql");
|
||||||
chmod($this->path."0.sql", 0000);
|
chmod($this->path."0.sql", 0000);
|
||||||
$this->assertException("updateFileUnreadable", "Db");
|
$this->assertException("updateFileUnreadable", "Db");
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadCorruptFile() {
|
public function testLoadCorruptFile(): void {
|
||||||
file_put_contents($this->path."0.sql", "This is a corrupt file");
|
file_put_contents($this->path."0.sql", "This is a corrupt file");
|
||||||
$this->assertException("updateFileError", "Db");
|
$this->assertException("updateFileError", "Db");
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadIncompleteFile() {
|
public function testLoadIncompleteFile(): void {
|
||||||
file_put_contents($this->path."0.sql", "create table arsse_meta(\"key\" varchar(255) primary key not null, value text);");
|
file_put_contents($this->path."0.sql", "create table arsse_meta(\"key\" varchar(255) primary key not null, value text);");
|
||||||
$this->assertException("updateFileIncomplete", "Db");
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadEmptyFile() {
|
public function testLoadEmptyFile(): void {
|
||||||
file_put_contents($this->path."0.sql", "");
|
file_put_contents($this->path."0.sql", "");
|
||||||
$this->assertException("updateFileIncomplete", "Db");
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadCorrectFile() {
|
public function testLoadCorrectFile(): void {
|
||||||
file_put_contents($this->path."0.sql", static::$minimal1);
|
file_put_contents($this->path."0.sql", static::$minimal1);
|
||||||
$this->drv->schemaUpdate(1, $this->base);
|
$this->drv->schemaUpdate(1, $this->base);
|
||||||
$this->assertEquals(1, $this->drv->schemaVersion());
|
$this->assertEquals(1, $this->drv->schemaVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformPartialUpdate() {
|
public function testPerformPartialUpdate(): void {
|
||||||
file_put_contents($this->path."0.sql", static::$minimal1);
|
file_put_contents($this->path."0.sql", static::$minimal1);
|
||||||
file_put_contents($this->path."1.sql", "UPDATE arsse_meta set value = '1' where \"key\" = 'schema_version'");
|
file_put_contents($this->path."1.sql", "UPDATE arsse_meta set value = '1' where \"key\" = 'schema_version'");
|
||||||
$this->assertException("updateFileIncomplete", "Db");
|
$this->assertException("updateFileIncomplete", "Db");
|
||||||
|
@ -106,31 +106,31 @@ class BaseUpdate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformSequentialUpdate() {
|
public function testPerformSequentialUpdate(): void {
|
||||||
file_put_contents($this->path."0.sql", static::$minimal1);
|
file_put_contents($this->path."0.sql", static::$minimal1);
|
||||||
file_put_contents($this->path."1.sql", static::$minimal2);
|
file_put_contents($this->path."1.sql", static::$minimal2);
|
||||||
$this->drv->schemaUpdate(2, $this->base);
|
$this->drv->schemaUpdate(2, $this->base);
|
||||||
$this->assertEquals(2, $this->drv->schemaVersion());
|
$this->assertEquals(2, $this->drv->schemaVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformActualUpdate() {
|
public function testPerformActualUpdate(): void {
|
||||||
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
||||||
$this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion());
|
$this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeclineManualUpdate() {
|
public function testDeclineManualUpdate(): void {
|
||||||
// turn auto-updating off
|
// turn auto-updating off
|
||||||
Arsse::$conf->dbAutoUpdate = false;
|
Arsse::$conf->dbAutoUpdate = false;
|
||||||
$this->assertException("updateManual", "Db");
|
$this->assertException("updateManual", "Db");
|
||||||
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeclineDowngrade() {
|
public function testDeclineDowngrade(): void {
|
||||||
$this->assertException("updateTooNew", "Db");
|
$this->assertException("updateTooNew", "Db");
|
||||||
$this->drv->schemaUpdate(-1, $this->base);
|
$this->drv->schemaUpdate(-1, $this->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPerformMaintenance() {
|
public function testPerformMaintenance(): void {
|
||||||
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION);
|
||||||
$this->assertTrue($this->drv->maintenance());
|
$this->assertTrue($this->drv->maintenance());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToConnect() {
|
public function testFailToConnect(): void {
|
||||||
// for the sake of simplicity we don't distinguish between failure modes, but the MySQL-supplied error messages do
|
// for the sake of simplicity we don't distinguish between failure modes, but the MySQL-supplied error messages do
|
||||||
self::setConf([
|
self::setConf([
|
||||||
'dbMySQLHost' => "example.invalid",
|
'dbMySQLHost' => "example.invalid",
|
||||||
|
|
|
@ -34,7 +34,7 @@ class TestStatement extends \JKingWeb\Arsse\TestCase\Db\BaseStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBindLongString() {
|
public function testBindLongString(): void {
|
||||||
// this test requires some set-up to be effective
|
// this test requires some set-up to be effective
|
||||||
static::$interface->query("CREATE TABLE arsse_test(`value` longtext not null) character set utf8mb4");
|
static::$interface->query("CREATE TABLE arsse_test(`value` longtext not null) character set utf8mb4");
|
||||||
// we'll use an unrealistic packet size of 1 byte to trigger special handling for strings which are too long for the maximum packet size
|
// we'll use an unrealistic packet size of 1 byte to trigger special handling for strings which are too long for the maximum packet size
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToConnect() {
|
public function testFailToConnect(): void {
|
||||||
// for the sake of simplicity we don't distinguish between failure modes, but the MySQL-supplied error messages do
|
// for the sake of simplicity we don't distinguish between failure modes, but the MySQL-supplied error messages do
|
||||||
self::setConf([
|
self::setConf([
|
||||||
'dbMySQLHost' => "example.invalid",
|
'dbMySQLHost' => "example.invalid",
|
||||||
|
|
|
@ -18,9 +18,9 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->markTestSkipped("PostgreSQL extension not loaded");
|
$this->markTestSkipped("PostgreSQL extension not loaded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideConnectionStrings */
|
/** @dataProvider provideConnectionStrings */
|
||||||
public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp) {
|
public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp): void {
|
||||||
self::setConf();
|
self::setConf();
|
||||||
$timeout = (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0);
|
$timeout = (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0);
|
||||||
$postfix = "application_name='arsse' client_encoding='UTF8' connect_timeout='$timeout'";
|
$postfix = "application_name='arsse' client_encoding='UTF8' connect_timeout='$timeout'";
|
||||||
|
@ -62,7 +62,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToConnect() {
|
public function testFailToConnect(): void {
|
||||||
// we cannnot distinguish between different connection failure modes
|
// we cannnot distinguish between different connection failure modes
|
||||||
self::setConf([
|
self::setConf([
|
||||||
'dbPostgreSQLHost' => "example.invalid",
|
'dbPostgreSQLHost' => "example.invalid",
|
||||||
|
|
|
@ -20,7 +20,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideConnectionStrings */
|
/** @dataProvider provideConnectionStrings */
|
||||||
public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp) {
|
public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp): void {
|
||||||
self::setConf();
|
self::setConf();
|
||||||
$timeout = (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0);
|
$timeout = (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0);
|
||||||
$postfix = "application_name='arsse' client_encoding='UTF8' connect_timeout='$timeout'";
|
$postfix = "application_name='arsse' client_encoding='UTF8' connect_timeout='$timeout'";
|
||||||
|
@ -62,7 +62,7 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToConnect() {
|
public function testFailToConnect(): void {
|
||||||
// PDO dies not distinguish between different connection failure modes
|
// PDO dies not distinguish between different connection failure modes
|
||||||
self::setConf([
|
self::setConf([
|
||||||
'dbPostgreSQLHost' => "example.invalid",
|
'dbPostgreSQLHost' => "example.invalid",
|
||||||
|
|
|
@ -27,8 +27,8 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->files = [
|
$this->files = [
|
||||||
// cannot create files
|
// cannot create files
|
||||||
'Cmain' => [],
|
'Cmain' => [],
|
||||||
'Cshm' => [
|
'Cshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
],
|
],
|
||||||
'Cwal' => [
|
'Cwal' => [
|
||||||
|
@ -36,55 +36,55 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
],
|
],
|
||||||
// cannot write to files
|
// cannot write to files
|
||||||
'Wmain' => [
|
'Wmain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Wwal' => [
|
'Wwal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Wshm' => [
|
'Wshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// cannot read from files
|
// cannot read from files
|
||||||
'Rmain' => [
|
'Rmain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Rwal' => [
|
'Rwal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Rshm' => [
|
'Rshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// can neither read from or write to files
|
// can neither read from or write to files
|
||||||
'Amain' => [
|
'Amain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Awal' => [
|
'Awal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Ashm' => [
|
'Ashm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// non-filesystem errors
|
// non-filesystem errors
|
||||||
'corrupt' => [
|
'corrupt' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
|
@ -112,79 +112,79 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateDatabase() {
|
public function testFailToCreateDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateJournal() {
|
public function testFailToCreateJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateSharedMmeory() {
|
public function testFailToCreateSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadDatabase() {
|
public function testFailToReadDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadJournal() {
|
public function testFailToReadJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadSharedMmeory() {
|
public function testFailToReadSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToDatabase() {
|
public function testFailToWriteToDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToJournal() {
|
public function testFailToWriteToJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToSharedMmeory() {
|
public function testFailToWriteToSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessDatabase() {
|
public function testFailToAccessDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessJournal() {
|
public function testFailToAccessJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessSharedMmeory() {
|
public function testFailToAccessSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAssumeDatabaseCorruption() {
|
public function testAssumeDatabaseCorruption(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db";
|
||||||
$this->assertException("fileCorrupt", "Db");
|
$this->assertException("fileCorrupt", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
|
|
|
@ -29,8 +29,8 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->files = [
|
$this->files = [
|
||||||
// cannot create files
|
// cannot create files
|
||||||
'Cmain' => [],
|
'Cmain' => [],
|
||||||
'Cshm' => [
|
'Cshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
],
|
],
|
||||||
'Cwal' => [
|
'Cwal' => [
|
||||||
|
@ -38,55 +38,55 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
],
|
],
|
||||||
// cannot write to files
|
// cannot write to files
|
||||||
'Wmain' => [
|
'Wmain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Wwal' => [
|
'Wwal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Wshm' => [
|
'Wshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// cannot read from files
|
// cannot read from files
|
||||||
'Rmain' => [
|
'Rmain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Rwal' => [
|
'Rwal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Rshm' => [
|
'Rshm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// can neither read from or write to files
|
// can neither read from or write to files
|
||||||
'Amain' => [
|
'Amain' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Awal' => [
|
'Awal' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
'Ashm' => [
|
'Ashm' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
// non-filesystem errors
|
// non-filesystem errors
|
||||||
'corrupt' => [
|
'corrupt' => [
|
||||||
'arsse.db' => "",
|
'arsse.db' => "",
|
||||||
'arsse.db-wal' => "",
|
'arsse.db-wal' => "",
|
||||||
'arsse.db-shm' => "",
|
'arsse.db-shm' => "",
|
||||||
],
|
],
|
||||||
|
@ -114,79 +114,79 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateDatabase() {
|
public function testFailToCreateDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateJournal() {
|
public function testFailToCreateJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToCreateSharedMmeory() {
|
public function testFailToCreateSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db";
|
||||||
$this->assertException("fileUncreatable", "Db");
|
$this->assertException("fileUncreatable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadDatabase() {
|
public function testFailToReadDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadJournal() {
|
public function testFailToReadJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToReadSharedMmeory() {
|
public function testFailToReadSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db";
|
||||||
$this->assertException("fileUnreadable", "Db");
|
$this->assertException("fileUnreadable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToDatabase() {
|
public function testFailToWriteToDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToJournal() {
|
public function testFailToWriteToJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToWriteToSharedMmeory() {
|
public function testFailToWriteToSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db";
|
||||||
$this->assertException("fileUnwritable", "Db");
|
$this->assertException("fileUnwritable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessDatabase() {
|
public function testFailToAccessDatabase(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessJournal() {
|
public function testFailToAccessJournal(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailToAccessSharedMmeory() {
|
public function testFailToAccessSharedMmeory(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db";
|
||||||
$this->assertException("fileUnusable", "Db");
|
$this->assertException("fileUnusable", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAssumeDatabaseCorruption() {
|
public function testAssumeDatabaseCorruption(): void {
|
||||||
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db";
|
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db";
|
||||||
$this->assertException("fileCorrupt", "Db");
|
$this->assertException("fileCorrupt", "Db");
|
||||||
new Driver;
|
new Driver;
|
||||||
|
|
|
@ -7,7 +7,7 @@ use JKingWeb\Arsse\Test\Result;
|
||||||
|
|
||||||
/** @covers \JKingWeb\Arsse\Db\ResultAggregate<extended> */
|
/** @covers \JKingWeb\Arsse\Db\ResultAggregate<extended> */
|
||||||
class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function testGetChangeCountAndLastInsertId() {
|
public function testGetChangeCountAndLastInsertId(): void {
|
||||||
$in = [
|
$in = [
|
||||||
new Result([], 3, 4),
|
new Result([], 3, 4),
|
||||||
new Result([], 27, 10),
|
new Result([], 27, 10),
|
||||||
|
@ -18,7 +18,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals(2112, $r->lastId());
|
$this->assertEquals(2112, $r->lastId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIterateOverResults() {
|
public function testIterateOverResults(): void {
|
||||||
$in = [
|
$in = [
|
||||||
new Result([['col' => 1]]),
|
new Result([['col' => 1]]),
|
||||||
new Result([['col' => 2]]),
|
new Result([['col' => 2]]),
|
||||||
|
@ -31,7 +31,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals([0 => 1, 1 => 2, 2 => 3], $rows);
|
$this->assertEquals([0 => 1, 1 => 2, 2 => 3], $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIterateOverResultsTwice() {
|
public function testIterateOverResultsTwice(): void {
|
||||||
$in = [
|
$in = [
|
||||||
new Result([['col' => 1]]),
|
new Result([['col' => 1]]),
|
||||||
new Result([['col' => 2]]),
|
new Result([['col' => 2]]),
|
||||||
|
@ -49,7 +49,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetSingleValues() {
|
public function testGetSingleValues(): void {
|
||||||
$test = new ResultAggregate(...[
|
$test = new ResultAggregate(...[
|
||||||
new Result([['year' => 1867]]),
|
new Result([['year' => 1867]]),
|
||||||
new Result([['year' => 1970]]),
|
new Result([['year' => 1970]]),
|
||||||
|
@ -61,7 +61,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getValue());
|
$this->assertSame(null, $test->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFirstValuesOnly() {
|
public function testGetFirstValuesOnly(): void {
|
||||||
$test = new ResultAggregate(...[
|
$test = new ResultAggregate(...[
|
||||||
new Result([['year' => 1867, 'century' => 19]]),
|
new Result([['year' => 1867, 'century' => 19]]),
|
||||||
new Result([['year' => 1970, 'century' => 20]]),
|
new Result([['year' => 1970, 'century' => 20]]),
|
||||||
|
@ -73,7 +73,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getValue());
|
$this->assertSame(null, $test->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRows() {
|
public function testGetRows(): void {
|
||||||
$test = new ResultAggregate(...[
|
$test = new ResultAggregate(...[
|
||||||
new Result([['album' => '2112', 'track' => '2112']]),
|
new Result([['album' => '2112', 'track' => '2112']]),
|
||||||
new Result([['album' => 'Clockwork Angels', 'track' => 'The Wreckers']]),
|
new Result([['album' => 'Clockwork Angels', 'track' => 'The Wreckers']]),
|
||||||
|
@ -87,7 +87,7 @@ class TestResultAggregate extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame(null, $test->getRow());
|
$this->assertSame(null, $test->getRow());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAllRows() {
|
public function testGetAllRows(): void {
|
||||||
$test = new ResultAggregate(...[
|
$test = new ResultAggregate(...[
|
||||||
new Result([['album' => '2112', 'track' => '2112']]),
|
new Result([['album' => '2112', 'track' => '2112']]),
|
||||||
new Result([['album' => 'Clockwork Angels', 'track' => 'The Wreckers']]),
|
new Result([['album' => 'Clockwork Angels', 'track' => 'The Wreckers']]),
|
||||||
|
|
|
@ -6,13 +6,13 @@ use JKingWeb\Arsse\Db\ResultEmpty;
|
||||||
|
|
||||||
/** @covers \JKingWeb\Arsse\Db\ResultEmpty<extended> */
|
/** @covers \JKingWeb\Arsse\Db\ResultEmpty<extended> */
|
||||||
class TestResultEmpty extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestResultEmpty extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function testGetChangeCountAndLastInsertId() {
|
public function testGetChangeCountAndLastInsertId(): void {
|
||||||
$r = new ResultEmpty;
|
$r = new ResultEmpty;
|
||||||
$this->assertEquals(0, $r->changes());
|
$this->assertEquals(0, $r->changes());
|
||||||
$this->assertEquals(0, $r->lastId());
|
$this->assertEquals(0, $r->lastId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIterateOverResults() {
|
public function testIterateOverResults(): void {
|
||||||
$rows = [];
|
$rows = [];
|
||||||
foreach (new ResultEmpty as $index => $row) {
|
foreach (new ResultEmpty as $index => $row) {
|
||||||
$rows[$index] = $row['col'];
|
$rows[$index] = $row['col'];
|
||||||
|
@ -20,17 +20,17 @@ class TestResultEmpty extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertEquals([], $rows);
|
$this->assertEquals([], $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetSingleValues() {
|
public function testGetSingleValues(): void {
|
||||||
$test = new ResultEmpty;
|
$test = new ResultEmpty;
|
||||||
$this->assertSame(null, $test->getValue());
|
$this->assertSame(null, $test->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRows() {
|
public function testGetRows(): void {
|
||||||
$test = new ResultEmpty;
|
$test = new ResultEmpty;
|
||||||
$this->assertSame(null, $test->getRow());
|
$this->assertSame(null, $test->getRow());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAllRows() {
|
public function testGetAllRows(): void {
|
||||||
$test = new ResultEmpty;
|
$test = new ResultEmpty;
|
||||||
$rows = [];
|
$rows = [];
|
||||||
$this->assertEquals($rows, $test->getAll());
|
$this->assertEquals($rows, $test->getAll());
|
||||||
|
|
|
@ -23,7 +23,7 @@ class TestTransaction extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->drv = $drv;
|
$this->drv = $drv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManipulateTransactions() {
|
public function testManipulateTransactions(): void {
|
||||||
$tr1 = new Transaction($this->drv);
|
$tr1 = new Transaction($this->drv);
|
||||||
$tr2 = new Transaction($this->drv);
|
$tr2 = new Transaction($this->drv);
|
||||||
\Phake::verify($this->drv, \Phake::times(2))->savepointCreate;
|
\Phake::verify($this->drv, \Phake::times(2))->savepointCreate;
|
||||||
|
@ -35,7 +35,7 @@ class TestTransaction extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verify($this->drv)->savepointUndo(2);
|
\Phake::verify($this->drv)->savepointUndo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCloseTransactions() {
|
public function testCloseTransactions(): void {
|
||||||
$tr1 = new Transaction($this->drv);
|
$tr1 = new Transaction($this->drv);
|
||||||
$tr2 = new Transaction($this->drv);
|
$tr2 = new Transaction($this->drv);
|
||||||
$this->assertTrue($tr1->isPending());
|
$this->assertTrue($tr1->isPending());
|
||||||
|
@ -50,7 +50,7 @@ class TestTransaction extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verify($this->drv)->savepointUndo(2);
|
\Phake::verify($this->drv)->savepointUndo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIgnoreRollbackErrors() {
|
public function testIgnoreRollbackErrors(): void {
|
||||||
\Phake::when($this->drv)->savepointUndo->thenThrow(new Exception("savepointStale"));
|
\Phake::when($this->drv)->savepointUndo->thenThrow(new Exception("savepointStale"));
|
||||||
$tr1 = new Transaction($this->drv);
|
$tr1 = new Transaction($this->drv);
|
||||||
$tr2 = new Transaction($this->drv);
|
$tr2 = new Transaction($this->drv);
|
||||||
|
|
|
@ -28,7 +28,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData(true);
|
self::clearData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBaseClass() {
|
public function testBaseClass(): void {
|
||||||
$this->assertException("unknown");
|
$this->assertException("unknown");
|
||||||
throw new Exception("unknown");
|
throw new Exception("unknown");
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testBaseClass
|
* @depends testBaseClass
|
||||||
*/
|
*/
|
||||||
public function testBaseClassWithoutMessage() {
|
public function testBaseClassWithoutMessage(): void {
|
||||||
$this->assertException("unknown");
|
$this->assertException("unknown");
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testBaseClass
|
* @depends testBaseClass
|
||||||
*/
|
*/
|
||||||
public function testDerivedClass() {
|
public function testDerivedClass(): void {
|
||||||
$this->assertException("fileMissing", "Lang");
|
$this->assertException("fileMissing", "Lang");
|
||||||
throw new LangException("fileMissing");
|
throw new LangException("fileMissing");
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testDerivedClass
|
* @depends testDerivedClass
|
||||||
*/
|
*/
|
||||||
public function testDerivedClassWithMessageParameters() {
|
public function testDerivedClassWithMessageParameters(): void {
|
||||||
$this->assertException("fileMissing", "Lang");
|
$this->assertException("fileMissing", "Lang");
|
||||||
throw new LangException("fileMissing", "en");
|
throw new LangException("fileMissing", "en");
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testBaseClass
|
* @depends testBaseClass
|
||||||
*/
|
*/
|
||||||
public function testBaseClassWithUnknownCode() {
|
public function testBaseClassWithUnknownCode(): void {
|
||||||
$this->assertException("uncoded");
|
$this->assertException("uncoded");
|
||||||
throw new Exception("testThisExceptionMessageDoesNotExist");
|
throw new Exception("testThisExceptionMessageDoesNotExist");
|
||||||
}
|
}
|
||||||
|
@ -68,13 +68,13 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testBaseClassWithUnknownCode
|
* @depends testBaseClassWithUnknownCode
|
||||||
*/
|
*/
|
||||||
public function testDerivedClassWithMissingMessage() {
|
public function testDerivedClassWithMissingMessage(): void {
|
||||||
$this->assertException("uncoded");
|
$this->assertException("uncoded");
|
||||||
throw new LangException("testThisExceptionMessageDoesNotExist");
|
throw new LangException("testThisExceptionMessageDoesNotExist");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @covers \JKingWeb\Arsse\ExceptionFatal */
|
/** @covers \JKingWeb\Arsse\ExceptionFatal */
|
||||||
public function testFatalException() {
|
public function testFatalException(): void {
|
||||||
$this->expectException('JKingWeb\Arsse\ExceptionFatal');
|
$this->expectException('JKingWeb\Arsse\ExceptionFatal');
|
||||||
throw new \JKingWeb\Arsse\ExceptionFatal("");
|
throw new \JKingWeb\Arsse\ExceptionFatal("");
|
||||||
}
|
}
|
||||||
|
|
177
tests/cases/Feed/TestException.php
Normal file
177
tests/cases/Feed/TestException.php
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Feed;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\BadResponseException;
|
||||||
|
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||||
|
use GuzzleHttp\Exception\TransferException;
|
||||||
|
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
||||||
|
use PicoFeed\PicoFeedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \JKingWeb\Arsse\Feed\Exception
|
||||||
|
* @group slow */
|
||||||
|
class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
/** @dataProvider provideCurlErrors */
|
||||||
|
public function testHandleCurlErrors(int $code, string $message): void {
|
||||||
|
$e = $this->mockGuzzleException(TransferException::class, "cURL error $code: Some message", 0);
|
||||||
|
$this->assertException($message, "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideCurlErrors() {
|
||||||
|
return [
|
||||||
|
'CURLE_UNSUPPORTED_PROTOCOL' => [1, "invalidUrl"],
|
||||||
|
'CURLE_FAILED_INIT' => [2, "internalError"],
|
||||||
|
'CURLE_URL_MALFORMAT' => [3, "invalidUrl"],
|
||||||
|
'CURLE_URL_MALFORMAT_USER' => [4, "internalError"],
|
||||||
|
'CURLE_COULDNT_RESOLVE_PROXY' => [5, "transmissionError"],
|
||||||
|
'CURLE_COULDNT_RESOLVE_HOST' => [6, "connectionFailed"],
|
||||||
|
'CURLE_COULDNT_CONNECT' => [7, "connectionFailed"],
|
||||||
|
'CURLE_WEIRD_SERVER_REPLY' => [8, "transmissionError"],
|
||||||
|
'CURLE_FTP_ACCESS_DENIED' => [9, "forbidden"],
|
||||||
|
'CURLE_FTP_USER_PASSWORD_INCORRECT' => [10, "unauthorized"],
|
||||||
|
'CURLE_FTP_WEIRD_PASS_REPLY' => [11, "transmissionError"],
|
||||||
|
'CURLE_FTP_WEIRD_USER_REPLY' => [12, "transmissionError"],
|
||||||
|
'CURLE_FTP_WEIRD_PASV_REPLY' => [13, "transmissionError"],
|
||||||
|
'CURLE_FTP_WEIRD_227_FORMAT' => [14, "transmissionError"],
|
||||||
|
'CURLE_FTP_CANT_GET_HOST' => [15, "connectionFailed"],
|
||||||
|
'CURLE_FTP_CANT_RECONNECT' => [16, "connectionFailed"],
|
||||||
|
'CURLE_FTP_COULDNT_SET_BINARY' => [17, "transmissionError"],
|
||||||
|
'CURLE_PARTIAL_FILE' => [18, "transmissionError"],
|
||||||
|
'CURLE_FTP_COULDNT_RETR_FILE' => [19, "transmissionError"],
|
||||||
|
'CURLE_FTP_WRITE_ERROR' => [20, "transmissionError"],
|
||||||
|
'CURLE_FTP_QUOTE_ERROR' => [21, "transmissionError"],
|
||||||
|
'CURLE_HTTP_NOT_FOUND' => [22, "invalidUrl"],
|
||||||
|
'CURLE_WRITE_ERROR' => [23, "transmissionError"],
|
||||||
|
'CURLE_MALFORMAT_USER' => [24, "transmissionError"],
|
||||||
|
'CURLE_FTP_COULDNT_STOR_FILE' => [25, "transmissionError"],
|
||||||
|
'CURLE_READ_ERROR' => [26, "transmissionError"],
|
||||||
|
'CURLE_OUT_OF_MEMORY' => [27, "internalError"],
|
||||||
|
'CURLE_OPERATION_TIMEDOUT' => [28, "timeout"],
|
||||||
|
'CURLE_FTP_COULDNT_SET_ASCII' => [29, "transmissionError"],
|
||||||
|
'CURLE_FTP_PORT_FAILED' => [30, "transmissionError"],
|
||||||
|
'CURLE_FTP_COULDNT_USE_REST' => [31, "transmissionError"],
|
||||||
|
'CURLE_FTP_COULDNT_GET_SIZE' => [32, "transmissionError"],
|
||||||
|
'CURLE_HTTP_RANGE_ERROR' => [33, "transmissionError"],
|
||||||
|
'CURLE_HTTP_POST_ERROR' => [34, "internalError"],
|
||||||
|
'CURLE_SSL_CONNECT_ERROR' => [35, "invalidCertificate"],
|
||||||
|
'CURLE_BAD_DOWNLOAD_RESUME' => [36, "transmissionError"],
|
||||||
|
'CURLE_FILE_COULDNT_READ_FILE' => [37, "transmissionError"],
|
||||||
|
'CURLE_LDAP_CANNOT_BIND' => [38, "transmissionError"],
|
||||||
|
'CURLE_LDAP_SEARCH_FAILED' => [39, "transmissionError"],
|
||||||
|
'CURLE_LIBRARY_NOT_FOUND' => [40, "internalError"],
|
||||||
|
'CURLE_FUNCTION_NOT_FOUND' => [41, "internalError"],
|
||||||
|
'CURLE_ABORTED_BY_CALLBACK' => [42, "internalError"],
|
||||||
|
'CURLE_BAD_FUNCTION_ARGUMENT' => [43, "internalError"],
|
||||||
|
'CURLE_BAD_CALLING_ORDER' => [44, "internalError"],
|
||||||
|
'CURLE_HTTP_PORT_FAILED' => [45, "transmissionError"],
|
||||||
|
'CURLE_BAD_PASSWORD_ENTERED' => [46, "unauthorized"],
|
||||||
|
'CURLE_TOO_MANY_REDIRECTS' => [47, "maxRedirect"],
|
||||||
|
'CURLE_UNKNOWN_TELNET_OPTION' => [48, "internalError"],
|
||||||
|
'CURLE_TELNET_OPTION_SYNTAX' => [49, "internalError"],
|
||||||
|
'Unknown error 50' => [50, "internalError"],
|
||||||
|
'Unknown error 51' => [51, "internalError"],
|
||||||
|
'CURLE_GOT_NOTHING' => [52, "transmissionError"],
|
||||||
|
'CURLE_SSL_ENGINE_NOTFOUND' => [53, "invalidCertificate"],
|
||||||
|
'CURLE_SSL_ENGINE_SETFAILED' => [54, "invalidCertificate"],
|
||||||
|
'CURLE_SEND_ERROR' => [55, "transmissionError"],
|
||||||
|
'CURLE_RECV_ERROR' => [56, "transmissionError"],
|
||||||
|
'CURLE_SHARE_IN_USE' => [57, "internalError"],
|
||||||
|
'CURLE_SSL_CERTPROBLEM' => [58, "invalidCertificate"],
|
||||||
|
'CURLE_SSL_CIPHER' => [59, "invalidCertificate"],
|
||||||
|
'CURLE_SSL_CACERT' => [60, "invalidCertificate"],
|
||||||
|
'CURLE_BAD_CONTENT_ENCODING' => [61, "transmissionError"],
|
||||||
|
'CURLE_LDAP_INVALID_URL' => [62, "invalidUrl"],
|
||||||
|
'CURLE_FILESIZE_EXCEEDED' => [63, "transmissionError"],
|
||||||
|
'CURLE_USE_SSL_FAILED' => [64, "invalidCertificate"],
|
||||||
|
'CURLE_SEND_FAIL_REWIND' => [65, "transmissionError"],
|
||||||
|
'CURLE_SSL_ENGINE_INITFAILED' => [66, "invalidCertificate"],
|
||||||
|
'CURLE_LOGIN_DENIED' => [67, "forbidden"],
|
||||||
|
'CURLE_TFTP_NOTFOUND' => [68, "invalidUrl"],
|
||||||
|
'CURLE_TFTP_PERM' => [69, "forbidden"],
|
||||||
|
'CURLE_REMOTE_DISK_FULL' => [70, "transmissionError"],
|
||||||
|
'CURLE_TFTP_ILLEGAL' => [71, "internalError"],
|
||||||
|
'CURLE_TFTP_UNKNOWNID' => [72, "internalError"],
|
||||||
|
'CURLE_REMOTE_FILE_EXISTS' => [73, "transmissionError"],
|
||||||
|
'CURLE_TFTP_NOSUCHUSER' => [74, "transmissionError"],
|
||||||
|
'CURLE_CONV_FAILED' => [75, "internalError"],
|
||||||
|
'CURLE_CONV_REQD' => [76, "internalError"],
|
||||||
|
'CURLE_SSL_CACERT_BADFILE' => [77, "invalidCertificate"],
|
||||||
|
'CURLE_REMOTE_FILE_NOT_FOUND' => [78, "invalidUrl"],
|
||||||
|
'CURLE_SSH' => [79, "internalError"],
|
||||||
|
'CURLE_SSL_PINNEDPUBKEYNOTMATCH' => [90, "invalidCertificate"],
|
||||||
|
'CURLE_SSL_INVALIDCERTSTATUS' => [91, "invalidCertificate"],
|
||||||
|
'CURLE_HTTP2_STREAM' => [92, "transmissionError"],
|
||||||
|
'CURLE_RECURSIVE_API_CALL' => [93, "internalError"],
|
||||||
|
'CURLE_AUTH_ERROR' => [94, "unauthorized"],
|
||||||
|
'CURLE_HTTP3' => [95, "transmissionError"],
|
||||||
|
'CURLE_QUIC_CONNECT_ERROR' => [96, "connectionFailed"],
|
||||||
|
'Hypothetical error 2112' => [2112, "internalError"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideHTTPErrors */
|
||||||
|
public function testHandleHttpErrors(int $code, string $message): void {
|
||||||
|
$e = $this->mockGuzzleException(BadResponseException::class, "Irrelevant message", $code);
|
||||||
|
$this->assertException($message, "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideHTTPErrors() {
|
||||||
|
$specials = [
|
||||||
|
401 => "unauthorized",
|
||||||
|
403 => "forbidden",
|
||||||
|
404 => "invalidUrl",
|
||||||
|
408 => "timeout",
|
||||||
|
410 => "invalidUrl",
|
||||||
|
414 => "invalidUrl",
|
||||||
|
451 => "invalidUrl",
|
||||||
|
];
|
||||||
|
$out = array_fill(400, (600 - 400), "transmissionError");
|
||||||
|
foreach ($specials as $k => $t) {
|
||||||
|
$out[$k] = $t;
|
||||||
|
}
|
||||||
|
foreach ($out as $k => $t) {
|
||||||
|
$out[$k] = [$k, $t];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider providePicoFeedException */
|
||||||
|
public function testHandlePicofeedException(PicoFeedException $e, string $message) {
|
||||||
|
$this->assertException($message, "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function providePicoFeedException() {
|
||||||
|
return [
|
||||||
|
'Failed feed discovery' => [new \PicoFeed\Reader\SubscriptionNotFoundException(), "subscriptionNotFound"],
|
||||||
|
'Unsupported format' => [new \PicoFeed\Reader\UnsupportedFeedFormatException(), "unsupportedFeedFormat"],
|
||||||
|
'Malformed XML' => [new \PicoFeed\Parser\MalformedXmlException(), "malformedXml"],
|
||||||
|
'XML entity expansion' => [new \PicoFeed\Parser\XmlEntityException(), "xmlEntity"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleExcessRedirections() {
|
||||||
|
$e = $this->mockGuzzleException(TooManyRedirectsException::class, "Irrelevant message", 404);
|
||||||
|
$this->assertException("maxRedirect", "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleGenericStreamErrors() {
|
||||||
|
$e = $this->mockGuzzleException(TransferException::class, "Error creating resource: Irrelevant message", 403);
|
||||||
|
$this->assertException("transmissionError", "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleUnexpectedError() {
|
||||||
|
$e = new \Exception;
|
||||||
|
$this->assertException("internalError", "Feed");
|
||||||
|
throw new FeedException("https://example.com/", $e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,76 +14,75 @@ use JKingWeb\Arsse\Test\Result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \JKingWeb\Arsse\Feed
|
* @covers \JKingWeb\Arsse\Feed
|
||||||
* @covers \JKingWeb\Arsse\Feed\Exception
|
|
||||||
* @group slow */
|
* @group slow */
|
||||||
class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected static $host = "http://localhost:8000/";
|
protected static $host = "http://localhost:8000/";
|
||||||
protected $base = "";
|
protected $base = "";
|
||||||
protected $latest = [
|
protected $latest = [
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
'edited' => '2000-01-01 00:00:00',
|
'edited' => '2000-01-01 00:00:00',
|
||||||
'guid' => 'e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda',
|
'guid' => 'e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda',
|
||||||
'url_title_hash' => 'f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6',
|
'url_title_hash' => 'f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6',
|
||||||
'url_content_hash' => 'fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4',
|
'url_content_hash' => 'fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4',
|
||||||
'title_content_hash' => '18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',
|
'title_content_hash' => '18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'edited' => '2000-01-02 00:00:00',
|
'edited' => '2000-01-02 00:00:00',
|
||||||
'guid' => '5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7',
|
'guid' => '5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7',
|
||||||
'url_title_hash' => '0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153',
|
'url_title_hash' => '0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153',
|
||||||
'url_content_hash' => '13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9',
|
'url_content_hash' => '13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9',
|
||||||
'title_content_hash' => '2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',
|
'title_content_hash' => '2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 3,
|
'id' => 3,
|
||||||
'edited' => '2000-01-03 00:00:00',
|
'edited' => '2000-01-03 00:00:00',
|
||||||
'guid' => '31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92',
|
'guid' => '31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92',
|
||||||
'url_title_hash' => 'f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b',
|
'url_title_hash' => 'f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b',
|
||||||
'url_content_hash' => 'b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406',
|
'url_content_hash' => 'b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406',
|
||||||
'title_content_hash' => 'ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b',
|
'title_content_hash' => 'ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 4,
|
'id' => 4,
|
||||||
'edited' => '2000-01-04 00:00:00',
|
'edited' => '2000-01-04 00:00:00',
|
||||||
'guid' => '804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180',
|
'guid' => '804e517d623390e71497982c77cf6823180342ebcd2e7d5e32da1e55b09dd180',
|
||||||
'url_title_hash' => 'f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8',
|
'url_title_hash' => 'f3615c7f16336d3ea242d35cf3fc17dbc4ee3afb78376bf49da2dd7a5a25dec8',
|
||||||
'url_content_hash' => 'f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3',
|
'url_content_hash' => 'f11c2b4046f207579aeb9c69a8c20ca5461cef49756ccfa5ba5e2344266da3b3',
|
||||||
'title_content_hash' => 'ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',
|
'title_content_hash' => 'ab2da63276acce431250b18d3d49b988b226a99c7faadf275c90b751aee05be9',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 5,
|
'id' => 5,
|
||||||
'edited' => '2000-01-05 00:00:00',
|
'edited' => '2000-01-05 00:00:00',
|
||||||
'guid' => 'db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41',
|
'guid' => 'db3e736c2c492f5def5c5da33ddcbea1824040e9ced2142069276b0a6e291a41',
|
||||||
'url_title_hash' => 'd40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022',
|
'url_title_hash' => 'd40da96e39eea6c55948ccbe9b3d275b5f931298288dbe953990c5f496097022',
|
||||||
'url_content_hash' => '834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900',
|
'url_content_hash' => '834240f84501b5341d375414718204ec421561f3825d34c22bf9182203e42900',
|
||||||
'title_content_hash' => '43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba',
|
'title_content_hash' => '43b970ac6ec5f8a9647b2c7e4eed8b1d7f62e154a95eed748b0294c1256764ba',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
protected $others = [
|
protected $others = [
|
||||||
[
|
[
|
||||||
'id' => 6,
|
'id' => 6,
|
||||||
'edited' => '2000-01-06 00:00:00',
|
'edited' => '2000-01-06 00:00:00',
|
||||||
'guid' => 'b3461ab8e8759eeb1d65a818c65051ec00c1dfbbb32a3c8f6999434e3e3b76ab',
|
'guid' => 'b3461ab8e8759eeb1d65a818c65051ec00c1dfbbb32a3c8f6999434e3e3b76ab',
|
||||||
'url_title_hash' => '91d051a8e6749d014506848acd45e959af50bf876427c4f0e3a1ec0f04777b51',
|
'url_title_hash' => '91d051a8e6749d014506848acd45e959af50bf876427c4f0e3a1ec0f04777b51',
|
||||||
'url_content_hash' => '211d78b1a040d40d17e747a363cc283f58767b2e502630d8de9b8f1d5e941d18',
|
'url_content_hash' => '211d78b1a040d40d17e747a363cc283f58767b2e502630d8de9b8f1d5e941d18',
|
||||||
'title_content_hash' => '5ed68ccb64243b8c1931241d2c9276274c3b1d87f223634aa7a1ab0141292ca7',
|
'title_content_hash' => '5ed68ccb64243b8c1931241d2c9276274c3b1d87f223634aa7a1ab0141292ca7',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 7,
|
'id' => 7,
|
||||||
'edited' => '2000-01-07 00:00:00',
|
'edited' => '2000-01-07 00:00:00',
|
||||||
'guid' => 'f4fae999d6531747523f4ff0c74f3f0c7c588b67e4f32d8f7dba5f6f36e8a45d',
|
'guid' => 'f4fae999d6531747523f4ff0c74f3f0c7c588b67e4f32d8f7dba5f6f36e8a45d',
|
||||||
'url_title_hash' => 'b92f805f0d0643dad1d6c0bb5cbaec24729f5f71b37b831cf7ad31f6c9403ac8',
|
'url_title_hash' => 'b92f805f0d0643dad1d6c0bb5cbaec24729f5f71b37b831cf7ad31f6c9403ac8',
|
||||||
'url_content_hash' => '4fc8789b787246e9be08ca1bac0d4a1ac4db1984f0db07f7142417598cf7211f',
|
'url_content_hash' => '4fc8789b787246e9be08ca1bac0d4a1ac4db1984f0db07f7142417598cf7211f',
|
||||||
'title_content_hash' => '491df9338740b5297b3a3e8292be992ac112eb676c34595f7a38f3ee646ffe84',
|
'title_content_hash' => '491df9338740b5297b3a3e8292be992ac112eb676c34595f7a38f3ee646ffe84',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 8,
|
'id' => 8,
|
||||||
'edited' => '2000-01-08 00:00:00',
|
'edited' => '2000-01-08 00:00:00',
|
||||||
'guid' => 'b9d2d58e3172096b1d23b42a59961fabc89962836c3cd5de54f3d3a98ff08e6c',
|
'guid' => 'b9d2d58e3172096b1d23b42a59961fabc89962836c3cd5de54f3d3a98ff08e6c',
|
||||||
'url_title_hash' => '53a6cbcfeb66b46d09cbb7b25035df0562da35786933319c83b04be29acfb6f4',
|
'url_title_hash' => '53a6cbcfeb66b46d09cbb7b25035df0562da35786933319c83b04be29acfb6f4',
|
||||||
'url_content_hash' => 'c6f3722b4445b49d19d39c3bf5b11a7cf23dd69873e2a0a458aab662f1cd9438',
|
'url_content_hash' => 'c6f3722b4445b49d19d39c3bf5b11a7cf23dd69873e2a0a458aab662f1cd9438',
|
||||||
'title_content_hash' => '607d2da48807ca984ce2a9faa1d291bd9e3de9e912f83306167f4f5cd3c23bbd',
|
'title_content_hash' => '607d2da48807ca984ce2a9faa1d291bd9e3de9e912f83306167f4f5cd3c23bbd',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -98,7 +97,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
Arsse::$db = \Phake::mock(Database::class);
|
Arsse::$db = \Phake::mock(Database::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseAFeed() {
|
public function testParseAFeed(): void {
|
||||||
// test that various properties are set on the feed and on items
|
// test that various properties are set on the feed and on items
|
||||||
$f = new Feed(null, $this->base."Parsing/Valid");
|
$f = new Feed(null, $this->base."Parsing/Valid");
|
||||||
$this->assertTrue(isset($f->lastModified));
|
$this->assertTrue(isset($f->lastModified));
|
||||||
|
@ -141,37 +140,37 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame($categories, $f->data->items[5]->categories);
|
$this->assertSame($categories, $f->data->items[5]->categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDiscoverAFeedSuccessfully() {
|
public function testDiscoverAFeedSuccessfully(): void {
|
||||||
$this->assertSame($this->base."Discovery/Feed", Feed::discover($this->base."Discovery/Valid"));
|
$this->assertSame($this->base."Discovery/Feed", Feed::discover($this->base."Discovery/Valid"));
|
||||||
$this->assertSame($this->base."Discovery/Feed", Feed::discover($this->base."Discovery/Feed"));
|
$this->assertSame($this->base."Discovery/Feed", Feed::discover($this->base."Discovery/Feed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDiscoverAFeedUnsuccessfully() {
|
public function testDiscoverAFeedUnsuccessfully(): void {
|
||||||
$this->assertException("subscriptionNotFound", "Feed");
|
$this->assertException("subscriptionNotFound", "Feed");
|
||||||
Feed::discover($this->base."Discovery/Invalid");
|
Feed::discover($this->base."Discovery/Invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseEntityExpansionAttack() {
|
public function testParseEntityExpansionAttack(): void {
|
||||||
$this->assertException("xmlEntity", "Feed");
|
$this->assertException("xmlEntity", "Feed");
|
||||||
new Feed(null, $this->base."Parsing/XEEAttack");
|
new Feed(null, $this->base."Parsing/XEEAttack");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseExternalEntityAttack() {
|
public function testParseExternalEntityAttack(): void {
|
||||||
$this->assertException("xmlEntity", "Feed");
|
$this->assertException("xmlEntity", "Feed");
|
||||||
new Feed(null, $this->base."Parsing/XXEAttack");
|
new Feed(null, $this->base."Parsing/XXEAttack");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseAnUnsupportedFeed() {
|
public function testParseAnUnsupportedFeed(): void {
|
||||||
$this->assertException("unsupportedFeedFormat", "Feed");
|
$this->assertException("unsupportedFeedFormat", "Feed");
|
||||||
new Feed(null, $this->base."Parsing/Unsupported");
|
new Feed(null, $this->base."Parsing/Unsupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseAMalformedFeed() {
|
public function testParseAMalformedFeed(): void {
|
||||||
$this->assertException("malformedXml", "Feed");
|
$this->assertException("malformedXml", "Feed");
|
||||||
new Feed(null, $this->base."Parsing/Malformed");
|
new Feed(null, $this->base."Parsing/Malformed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeduplicateFeedItems() {
|
public function testDeduplicateFeedItems(): void {
|
||||||
// duplicates with dates lead to the newest match being kept
|
// duplicates with dates lead to the newest match being kept
|
||||||
$t = strtotime("2002-05-19T15:21:36Z");
|
$t = strtotime("2002-05-19T15:21:36Z");
|
||||||
$f = new Feed(null, $this->base."Deduplication/Permalink-Dates");
|
$f = new Feed(null, $this->base."Deduplication/Permalink-Dates");
|
||||||
|
@ -198,25 +197,27 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame("http://example.com/1", $f->newItems[0]->url);
|
$this->assertSame("http://example.com/1", $f->newItems[0]->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleCacheHeadersOn304() {
|
/** @dataProvider provide304ResponseURLs */
|
||||||
// upon 304, the client should re-use the caching header values it supplied the server
|
public function testHandleCacheHeadersOn304(string $url): void {
|
||||||
$t = time();
|
// upon 304, the client should re-use the caching header values it supplied to the server
|
||||||
|
$t = Date::transform("2010-01-01T00:00:00Z", "unix");
|
||||||
$e = "78567a";
|
$e = "78567a";
|
||||||
$f = new Feed(null, $this->base."Caching/304Random", Date::transform($t, "http"), $e);
|
$f = new Feed(null, $this->base.$url."?t=$t&e=$e", Date::transform($t, "http"), $e);
|
||||||
$this->assertTime($t, $f->lastModified);
|
|
||||||
$this->assertSame($e, $f->resource->getETag());
|
|
||||||
$f = new Feed(null, $this->base."Caching/304ETagOnly", Date::transform($t, "http"), $e);
|
|
||||||
$this->assertTime($t, $f->lastModified);
|
|
||||||
$this->assertSame($e, $f->resource->getETag());
|
|
||||||
$f = new Feed(null, $this->base."Caching/304LastModOnly", Date::transform($t, "http"), $e);
|
|
||||||
$this->assertTime($t, $f->lastModified);
|
|
||||||
$this->assertSame($e, $f->resource->getETag());
|
|
||||||
$f = new Feed(null, $this->base."Caching/304None", Date::transform($t, "http"), $e);
|
|
||||||
$this->assertTime($t, $f->lastModified);
|
$this->assertTime($t, $f->lastModified);
|
||||||
$this->assertSame($e, $f->resource->getETag());
|
$this->assertSame($e, $f->resource->getETag());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleCacheHeadersOn200() {
|
public function provide304ResponseURLs() {
|
||||||
|
return [
|
||||||
|
'Control' => ["Caching/304Conditional"],
|
||||||
|
'Random last-mod and ETag' => ["Caching/304Random"],
|
||||||
|
'ETag only' => ["Caching/304ETagOnly"],
|
||||||
|
'Last-mod only' => ["Caching/304LastModOnly"],
|
||||||
|
'Neither last-mod nor ETag' => ["Caching/304None"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleCacheHeadersOn200(): void {
|
||||||
// these tests should trust the server-returned time, even in cases of obviously incorrect results
|
// these tests should trust the server-returned time, even in cases of obviously incorrect results
|
||||||
$t = time() - 2000;
|
$t = time() - 2000;
|
||||||
$f = new Feed(null, $this->base."Caching/200Past");
|
$f = new Feed(null, $this->base."Caching/200Past");
|
||||||
|
@ -244,7 +245,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertTime($t, $f->lastModified);
|
$this->assertTime($t, $f->lastModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testComputeNextFetchOnError() {
|
public function testComputeNextFetchOnError(): void {
|
||||||
for ($a = 0; $a < 100; $a++) {
|
for ($a = 0; $a < 100; $a++) {
|
||||||
if ($a < 3) {
|
if ($a < 3) {
|
||||||
$this->assertTime("now + 5 minutes", Feed::nextFetchOnError($a));
|
$this->assertTime("now + 5 minutes", Feed::nextFetchOnError($a));
|
||||||
|
@ -257,7 +258,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provide304Timestamps */
|
/** @dataProvider provide304Timestamps */
|
||||||
public function testComputeNextFetchFrom304(string $t, string $exp) {
|
public function testComputeNextFetchFrom304(string $t, string $exp): void {
|
||||||
$t = $t ? strtotime($t) : "";
|
$t = $t ? strtotime($t) : "";
|
||||||
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", Date::transform($t, "http"));
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", Date::transform($t, "http"));
|
||||||
$exp = strtotime($exp);
|
$exp = strtotime($exp);
|
||||||
|
@ -266,26 +267,26 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
public function provide304Timestamps(): iterable {
|
public function provide304Timestamps(): iterable {
|
||||||
return [
|
return [
|
||||||
'less than half an hour 1' => ["now", "now + 15 minutes"],
|
'less than half an hour 1' => ["now", "now + 15 minutes"],
|
||||||
'less than half an hour 2' => ["now - 29 minutes", "now + 15 minutes"],
|
'less than half an hour 2' => ["now - 29 minutes", "now + 15 minutes"],
|
||||||
'less than one hour 1' => ["now - 30 minutes", "now + 30 minutes"],
|
'less than one hour 1' => ["now - 30 minutes", "now + 30 minutes"],
|
||||||
'less than one hour 2' => ["now - 59 minutes", "now + 30 minutes"],
|
'less than one hour 2' => ["now - 59 minutes", "now + 30 minutes"],
|
||||||
'less than three hours 1' => ["now - 1 hour", "now + 1 hour"],
|
'less than three hours 1' => ["now - 1 hour", "now + 1 hour"],
|
||||||
'less than three hours 2' => ["now - 2 hours 59 minutes", "now + 1 hour"],
|
'less than three hours 2' => ["now - 2 hours 59 minutes", "now + 1 hour"],
|
||||||
'more than thirty-six hours 1' => ["now - 36 hours", "now + 1 day"],
|
'more than thirty-six hours 1' => ["now - 36 hours", "now + 1 day"],
|
||||||
'more than thirty-six hours 2' => ["now - 2 years", "now + 1 day"],
|
'more than thirty-six hours 2' => ["now - 2 years", "now + 1 day"],
|
||||||
'fallback 1' => ["now - 3 hours", "now + 3 hours"],
|
'fallback 1' => ["now - 3 hours", "now + 3 hours"],
|
||||||
'fallback 2' => ["now - 35 hours", "now + 3 hours"],
|
'fallback 2' => ["now - 35 hours", "now + 3 hours"],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testComputeNextFetchFrom304WithoutDate() {
|
public function testComputeNextFetchFrom304WithoutDate(): void {
|
||||||
$f = new Feed(null, $this->base."NextFetch/NotModifiedEtag");
|
$f = new Feed(null, $this->base."NextFetch/NotModifiedEtag");
|
||||||
$exp = strtotime("now + 3 hours");
|
$exp = strtotime("now + 3 hours");
|
||||||
$this->assertTime($exp, $f->nextFetch);
|
$this->assertTime($exp, $f->nextFetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testComputeNextFetchFrom200() {
|
public function testComputeNextFetchFrom200(): void {
|
||||||
// if less than half an hour, check in 15 minutes
|
// if less than half an hour, check in 15 minutes
|
||||||
$f = new Feed(null, $this->base."NextFetch/30m");
|
$f = new Feed(null, $this->base."NextFetch/30m");
|
||||||
$exp = strtotime("now + 15 minutes");
|
$exp = strtotime("now + 15 minutes");
|
||||||
|
@ -312,7 +313,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertTime($exp, $f->nextFetch);
|
$this->assertTime($exp, $f->nextFetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMatchLatestArticles() {
|
public function testMatchLatestArticles(): void {
|
||||||
\Phake::when(Arsse::$db)->feedMatchLatest(1, $this->anything())->thenReturn(new Result($this->latest));
|
\Phake::when(Arsse::$db)->feedMatchLatest(1, $this->anything())->thenReturn(new Result($this->latest));
|
||||||
$f = new Feed(1, $this->base."Matching/1");
|
$f = new Feed(1, $this->base."Matching/1");
|
||||||
$this->assertCount(0, $f->newItems);
|
$this->assertCount(0, $f->newItems);
|
||||||
|
@ -328,7 +329,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertCount(2, $f->changedItems);
|
$this->assertCount(2, $f->changedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMatchHistoricalArticles() {
|
public function testMatchHistoricalArticles(): void {
|
||||||
\Phake::when(Arsse::$db)->feedMatchLatest(1, $this->anything())->thenReturn(new Result($this->latest));
|
\Phake::when(Arsse::$db)->feedMatchLatest(1, $this->anything())->thenReturn(new Result($this->latest));
|
||||||
\Phake::when(Arsse::$db)->feedMatchIds(1, $this->anything(), $this->anything(), $this->anything(), $this->anything())->thenReturn(new Result($this->others));
|
\Phake::when(Arsse::$db)->feedMatchIds(1, $this->anything(), $this->anything(), $this->anything(), $this->anything())->thenReturn(new Result($this->others));
|
||||||
$f = new Feed(1, $this->base."Matching/5");
|
$f = new Feed(1, $this->base."Matching/5");
|
||||||
|
@ -336,7 +337,7 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertCount(0, $f->changedItems);
|
$this->assertCount(0, $f->changedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testScrapeFullContent() {
|
public function testScrapeFullContent(): void {
|
||||||
// first make sure that the absence of scraping works as expected
|
// first make sure that the absence of scraping works as expected
|
||||||
$f = new Feed(null, $this->base."Scraping/Feed");
|
$f = new Feed(null, $this->base."Scraping/Feed");
|
||||||
$exp = "<p>Partial content</p>";
|
$exp = "<p>Partial content</p>";
|
||||||
|
|
|
@ -27,48 +27,49 @@ class TestFetching extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::setConf();
|
self::setConf();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandle400() {
|
public function testHandle400(): void {
|
||||||
$this->assertException("unsupportedFeedFormat", "Feed");
|
$this->assertException("transmissionError", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Error?code=400");
|
new Feed(null, $this->base."Fetching/Error?code=400");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandle401() {
|
public function testHandle401(): void {
|
||||||
$this->assertException("unauthorized", "Feed");
|
$this->assertException("unauthorized", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Error?code=401");
|
new Feed(null, $this->base."Fetching/Error?code=401");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandle403() {
|
public function testHandle403(): void {
|
||||||
$this->assertException("forbidden", "Feed");
|
$this->assertException("forbidden", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Error?code=403");
|
new Feed(null, $this->base."Fetching/Error?code=403");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandle404() {
|
public function testHandle404(): void {
|
||||||
$this->assertException("invalidUrl", "Feed");
|
$this->assertException("invalidUrl", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Error?code=404");
|
new Feed(null, $this->base."Fetching/Error?code=404");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandle500() {
|
public function testHandle500(): void {
|
||||||
$this->assertException("unsupportedFeedFormat", "Feed");
|
$this->assertException("transmissionError", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Error?code=500");
|
new Feed(null, $this->base."Fetching/Error?code=500");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleARedirectLoop() {
|
public function testHandleARedirectLoop(): void {
|
||||||
$this->assertException("maxRedirect", "Feed");
|
$this->assertException("maxRedirect", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/EndlessLoop?i=0");
|
new Feed(null, $this->base."Fetching/EndlessLoop?i=0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleAnOverlyLargeFeed() {
|
public function testHandleAnOverlyLargeFeed(): void {
|
||||||
|
$this->markTestIncomplete("The nicolus/picofeed library does not implement miniflux/picofeed's max-size setting");
|
||||||
Arsse::$conf->fetchSizeLimit = 512;
|
Arsse::$conf->fetchSizeLimit = 512;
|
||||||
$this->assertException("maxSize", "Feed");
|
$this->assertException("maxSize", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/TooLarge");
|
new Feed(null, $this->base."Fetching/TooLarge");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleACertificateError() {
|
public function testHandleACertificateError(): void {
|
||||||
$this->assertException("invalidCertificate", "Feed");
|
$this->assertException("invalidCertificate", "Feed");
|
||||||
new Feed(null, "https://localhost:8000/");
|
new Feed(null, "https://localhost:8000/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandleATimeout() {
|
public function testHandleATimeout(): void {
|
||||||
Arsse::$conf->fetchTimeout = 1;
|
Arsse::$conf->fetchTimeout = 1;
|
||||||
$this->assertException("timeout", "Feed");
|
$this->assertException("timeout", "Feed");
|
||||||
new Feed(null, $this->base."Fetching/Timeout");
|
new Feed(null, $this->base."Fetching/Timeout");
|
||||||
|
|
|
@ -45,7 +45,7 @@ class TestFile extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideFileExports */
|
/** @dataProvider provideFileExports */
|
||||||
public function testExportToAFile(string $file, string $user, bool $flat, $exp) {
|
public function testExportToAFile(string $file, string $user, bool $flat, $exp): void {
|
||||||
$path = $this->path.$file;
|
$path = $this->path.$file;
|
||||||
try {
|
try {
|
||||||
if ($exp instanceof \JKingWeb\Arsse\AbstractException) {
|
if ($exp instanceof \JKingWeb\Arsse\AbstractException) {
|
||||||
|
@ -84,7 +84,7 @@ class TestFile extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideFileImports */
|
/** @dataProvider provideFileImports */
|
||||||
public function testImportFromAFile(string $file, string $user, bool $flat, bool $replace, $exp) {
|
public function testImportFromAFile(string $file, string $user, bool $flat, bool $replace, $exp): void {
|
||||||
$path = $this->path.$file;
|
$path = $this->path.$file;
|
||||||
try {
|
try {
|
||||||
if ($exp instanceof \JKingWeb\Arsse\AbstractException) {
|
if ($exp instanceof \JKingWeb\Arsse\AbstractException) {
|
||||||
|
|
|
@ -117,9 +117,9 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
],
|
],
|
||||||
'arsse_tag_members' => [
|
'arsse_tag_members' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'tag' => "int",
|
'tag' => "int",
|
||||||
'subscription' => "int",
|
'subscription' => "int",
|
||||||
'assigned' => "bool",
|
'assigned' => "bool",
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
[1, 2, 1],
|
[1, 2, 1],
|
||||||
|
@ -146,13 +146,13 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportForAMissingUser() {
|
public function testImportForAMissingUser(): void {
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
$this->proc->import("john.doe@example.com", "", false, false);
|
$this->proc->import("john.doe@example.com", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportWithInvalidFolder() {
|
public function testImportWithInvalidFolder(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
], [1 =>
|
], [1 =>
|
||||||
['id' => 1, 'name' => "", 'parent' => 0],
|
['id' => 1, 'name' => "", 'parent' => 0],
|
||||||
|
@ -162,7 +162,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->proc->import("john.doe@example.com", "", false, false);
|
$this->proc->import("john.doe@example.com", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportWithDuplicateFolder() {
|
public function testImportWithDuplicateFolder(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
], [1 =>
|
], [1 =>
|
||||||
['id' => 1, 'name' => "New", 'parent' => 0],
|
['id' => 1, 'name' => "New", 'parent' => 0],
|
||||||
|
@ -173,7 +173,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->proc->import("john.doe@example.com", "", false, false);
|
$this->proc->import("john.doe@example.com", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMakeNoEffectiveChanges() {
|
public function testMakeNoEffectiveChanges(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
['url' => "http://localhost:8000/Import/nasa-jpl", 'title' => "NASA JPL", 'folder' => 3, 'tags' => ["tech"]],
|
['url' => "http://localhost:8000/Import/nasa-jpl", 'title' => "NASA JPL", 'folder' => 3, 'tags' => ["tech"]],
|
||||||
['url' => "http://localhost:8000/Import/ars", 'title' => "Ars Technica", 'folder' => 2, 'tags' => ["frequent", "tech"]],
|
['url' => "http://localhost:8000/Import/ars", 'title' => "Ars Technica", 'folder' => 2, 'tags' => ["frequent", "tech"]],
|
||||||
|
@ -181,7 +181,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
['url' => "http://localhost:8000/Import/citizen", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
['url' => "http://localhost:8000/Import/citizen", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
||||||
['url' => "http://localhost:8000/Import/eurogamer", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
['url' => "http://localhost:8000/Import/eurogamer", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
||||||
['url' => "http://localhost:8000/Import/cbc", 'title' => "CBC News", 'folder' => 6, 'tags' => ["news", "canada"]],
|
['url' => "http://localhost:8000/Import/cbc", 'title' => "CBC News", 'folder' => 6, 'tags' => ["news", "canada"]],
|
||||||
], [1 =>
|
], [1 =>
|
||||||
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
||||||
['id' => 2, 'name' => "Science", 'parent' => 0],
|
['id' => 2, 'name' => "Science", 'parent' => 0],
|
||||||
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
||||||
|
@ -197,7 +197,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->compareExpectations($this->drv, $exp);
|
$this->compareExpectations($this->drv, $exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testModifyASubscription() {
|
public function testModifyASubscription(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
['url' => "http://localhost:8000/Import/nasa-jpl", 'title' => "NASA JPL", 'folder' => 3, 'tags' => ["tech"]],
|
['url' => "http://localhost:8000/Import/nasa-jpl", 'title' => "NASA JPL", 'folder' => 3, 'tags' => ["tech"]],
|
||||||
['url' => "http://localhost:8000/Import/ars", 'title' => "Ars Technica", 'folder' => 2, 'tags' => ["frequent", "tech"]],
|
['url' => "http://localhost:8000/Import/ars", 'title' => "Ars Technica", 'folder' => 2, 'tags' => ["frequent", "tech"]],
|
||||||
|
@ -205,7 +205,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
['url' => "http://localhost:8000/Import/citizen", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
['url' => "http://localhost:8000/Import/citizen", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
||||||
['url' => "http://localhost:8000/Import/eurogamer", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
['url' => "http://localhost:8000/Import/eurogamer", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
||||||
['url' => "http://localhost:8000/Import/cbc", 'title' => "CBC", 'folder' => 0, 'tags' => ["news", "canada"]], // moved to root and renamed
|
['url' => "http://localhost:8000/Import/cbc", 'title' => "CBC", 'folder' => 0, 'tags' => ["news", "canada"]], // moved to root and renamed
|
||||||
], [1 =>
|
], [1 =>
|
||||||
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
||||||
['id' => 2, 'name' => "Science", 'parent' => 0],
|
['id' => 2, 'name' => "Science", 'parent' => 0],
|
||||||
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
||||||
|
@ -222,7 +222,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->compareExpectations($this->drv, $exp);
|
$this->compareExpectations($this->drv, $exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportAFeed() {
|
public function testImportAFeed(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 0, 'tags' => ["frequent", "cryptic"]], //one existing tag and one new one
|
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 0, 'tags' => ["frequent", "cryptic"]], //one existing tag and one new one
|
||||||
], []];
|
], []];
|
||||||
|
@ -237,7 +237,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->compareExpectations($this->drv, $exp);
|
$this->compareExpectations($this->drv, $exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportAFeedWithAnInvalidTag() {
|
public function testImportAFeedWithAnInvalidTag(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 0, 'tags' => [""]],
|
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 0, 'tags' => [""]],
|
||||||
], []];
|
], []];
|
||||||
|
@ -246,7 +246,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->proc->import("john.doe@example.com", "", false, false);
|
$this->proc->import("john.doe@example.com", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReplaceData() {
|
public function testReplaceData(): void {
|
||||||
$in = [[
|
$in = [[
|
||||||
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 1, 'tags' => ["frequent", "cryptic"]],
|
['url' => "http://localhost:8000/Import/some-feed", 'title' => "Some Feed", 'folder' => 1, 'tags' => ["frequent", "cryptic"]],
|
||||||
], [1 =>
|
], [1 =>
|
||||||
|
|
|
@ -86,28 +86,28 @@ OPML_EXPORT_SERIALIZATION;
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(true);
|
\Phake::when(Arsse::$user)->exists->thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToOpml() {
|
public function testExportToOpml(): void {
|
||||||
\Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders));
|
\Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders));
|
||||||
\Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions));
|
\Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions));
|
||||||
\Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags));
|
\Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags));
|
||||||
$this->assertXmlStringEqualsXmlString($this->serialization, (new OPML)->export("john.doe@example.com"));
|
$this->assertXmlStringEqualsXmlString($this->serialization, (new OPML)->export("john.doe@example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToFlatOpml() {
|
public function testExportToFlatOpml(): void {
|
||||||
\Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders));
|
\Phake::when(Arsse::$db)->folderList("john.doe@example.com")->thenReturn(new Result($this->folders));
|
||||||
\Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions));
|
\Phake::when(Arsse::$db)->subscriptionList("john.doe@example.com")->thenReturn(new Result($this->subscriptions));
|
||||||
\Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags));
|
\Phake::when(Arsse::$db)->tagSummarize("john.doe@example.com")->thenReturn(new Result($this->tags));
|
||||||
$this->assertXmlStringEqualsXmlString($this->serializationFlat, (new OPML)->export("john.doe@example.com", true));
|
$this->assertXmlStringEqualsXmlString($this->serializationFlat, (new OPML)->export("john.doe@example.com", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToOpmlAMissingUser() {
|
public function testExportToOpmlAMissingUser(): void {
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
(new OPML)->export("john.doe@example.com");
|
(new OPML)->export("john.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideParserData */
|
/** @dataProvider provideParserData */
|
||||||
public function testParseOpmlForImport(string $file, bool $flat, $exp) {
|
public function testParseOpmlForImport(string $file, bool $flat, $exp): void {
|
||||||
$data = file_get_contents(\JKingWeb\Arsse\DOCROOT."Import/OPML/$file");
|
$data = file_get_contents(\JKingWeb\Arsse\DOCROOT."Import/OPML/$file");
|
||||||
// set up a partial mock to make the ImportExport::parse() method visible
|
// set up a partial mock to make the ImportExport::parse() method visible
|
||||||
$parser = \Phake::makeVisible(\Phake::partialMock(OPML::class));
|
$parser = \Phake::makeVisible(\Phake::partialMock(OPML::class));
|
||||||
|
@ -152,7 +152,7 @@ OPML_EXPORT_SERIALIZATION;
|
||||||
['url' => "https://www.thestar.com/content/thestar/feed.RSSManagerServlet.topstories.rss", 'title' => "Toronto Star", 'folder' => 5, 'tags' => ["news", "canada", "toronto"]],
|
['url' => "https://www.thestar.com/content/thestar/feed.RSSManagerServlet.topstories.rss", 'title' => "Toronto Star", 'folder' => 5, 'tags' => ["news", "canada", "toronto"]],
|
||||||
['url' => "http://rss.canada.com/get/?F239", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
['url' => "http://rss.canada.com/get/?F239", 'title' => "Ottawa Citizen", 'folder' => 6, 'tags' => ["news", "canada"]],
|
||||||
['url' => "https://www.eurogamer.net/?format=rss", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
['url' => "https://www.eurogamer.net/?format=rss", 'title' => "Eurogamer", 'folder' => 0, 'tags' => ["gaming", "frequent"]],
|
||||||
], [1 =>
|
], [1 =>
|
||||||
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
['id' => 1, 'name' => "Photography", 'parent' => 0],
|
||||||
['id' => 2, 'name' => "Science", 'parent' => 0],
|
['id' => 2, 'name' => "Science", 'parent' => 0],
|
||||||
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
['id' => 3, 'name' => "Rocketry", 'parent' => 2],
|
||||||
|
|
|
@ -16,14 +16,14 @@ class TestBasic extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public $path;
|
public $path;
|
||||||
public $l;
|
public $l;
|
||||||
|
|
||||||
public function testListLanguages() {
|
public function testListLanguages(): void {
|
||||||
$this->assertCount(sizeof($this->files), $this->l->list("en"));
|
$this->assertCount(sizeof($this->files), $this->l->list("en"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @depends testListLanguages
|
* @depends testListLanguages
|
||||||
*/
|
*/
|
||||||
public function testSetLanguage() {
|
public function testSetLanguage(): void {
|
||||||
$this->assertEquals("en", $this->l->set("en"));
|
$this->assertEquals("en", $this->l->set("en"));
|
||||||
$this->assertEquals("en_ca", $this->l->set("en_ca"));
|
$this->assertEquals("en_ca", $this->l->set("en_ca"));
|
||||||
$this->assertEquals("de", $this->l->set("de_ch"));
|
$this->assertEquals("de", $this->l->set("de_ch"));
|
||||||
|
@ -36,15 +36,16 @@ class TestBasic extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testSetLanguage
|
* @depends testSetLanguage
|
||||||
*/
|
*/
|
||||||
public function testLoadInternalStrings() {
|
public function testLoadInternalStrings(): void {
|
||||||
|
$exp = (new \ReflectionClassConstant(TestClass::class, "REQUIRED"))->getValue();
|
||||||
$this->assertEquals("", $this->l->set("", true));
|
$this->assertEquals("", $this->l->set("", true));
|
||||||
$this->assertCount(sizeof(TestClass::REQUIRED), $this->l->dump());
|
$this->assertCount(sizeof($exp), $this->l->dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @depends testLoadInternalStrings
|
* @depends testLoadInternalStrings
|
||||||
*/
|
*/
|
||||||
public function testLoadDefaultLanguage() {
|
public function testLoadDefaultLanguage(): void {
|
||||||
$this->assertEquals(TestClass::DEFAULT, $this->l->set(TestClass::DEFAULT, true));
|
$this->assertEquals(TestClass::DEFAULT, $this->l->set(TestClass::DEFAULT, true));
|
||||||
$str = $this->l->dump();
|
$str = $this->l->dump();
|
||||||
$this->assertArrayHasKey('Exception.JKingWeb/Arsse/Exception.uncoded', $str);
|
$this->assertArrayHasKey('Exception.JKingWeb/Arsse/Exception.uncoded', $str);
|
||||||
|
@ -54,7 +55,7 @@ class TestBasic extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testLoadDefaultLanguage
|
* @depends testLoadDefaultLanguage
|
||||||
*/
|
*/
|
||||||
public function testLoadSupplementaryLanguage() {
|
public function testLoadSupplementaryLanguage(): void {
|
||||||
$this->l->set(TestClass::DEFAULT, true);
|
$this->l->set(TestClass::DEFAULT, true);
|
||||||
$this->assertEquals("ja", $this->l->set("ja", true));
|
$this->assertEquals("ja", $this->l->set("ja", true));
|
||||||
$str = $this->l->dump();
|
$str = $this->l->dump();
|
||||||
|
|
|
@ -16,11 +16,11 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public $path;
|
public $path;
|
||||||
public $l;
|
public $l;
|
||||||
|
|
||||||
public function setUpSeries() {
|
public function setUpSeries(): void {
|
||||||
$this->l->set(TestClass::DEFAULT, true);
|
$this->l->set(TestClass::DEFAULT, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLazyLoad() {
|
public function testLazyLoad(): void {
|
||||||
$this->l->set("ja");
|
$this->l->set("ja");
|
||||||
$this->assertArrayNotHasKey('Test.absentText', $this->l->dump());
|
$this->assertArrayNotHasKey('Test.absentText', $this->l->dump());
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,14 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testLazyLoad
|
* @depends testLazyLoad
|
||||||
*/
|
*/
|
||||||
public function testGetWantedAndLoadedLocale() {
|
public function testGetWantedAndLoadedLocale(): void {
|
||||||
$this->l->set("en", true);
|
$this->l->set("en", true);
|
||||||
$this->l->set("ja");
|
$this->l->set("ja");
|
||||||
$this->assertEquals("ja", $this->l->get());
|
$this->assertEquals("ja", $this->l->get());
|
||||||
$this->assertEquals("en", $this->l->get(true));
|
$this->assertEquals("en", $this->l->get(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadCascadeOfFiles() {
|
public function testLoadCascadeOfFiles(): void {
|
||||||
$this->l->set("ja", true);
|
$this->l->set("ja", true);
|
||||||
$this->assertEquals("de", $this->l->set("de", true));
|
$this->assertEquals("de", $this->l->set("de", true));
|
||||||
$str = $this->l->dump();
|
$str = $this->l->dump();
|
||||||
|
@ -46,11 +46,11 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testLoadCascadeOfFiles
|
* @depends testLoadCascadeOfFiles
|
||||||
*/
|
*/
|
||||||
public function testLoadSubtag() {
|
public function testLoadSubtag(): void {
|
||||||
$this->assertEquals("en_ca", $this->l->set("en_ca", true));
|
$this->assertEquals("en_ca", $this->l->set("en_ca", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFetchAMessage() {
|
public function testFetchAMessage(): void {
|
||||||
$this->l->set("de");
|
$this->l->set("de");
|
||||||
$this->assertEquals('und der Stein der Weisen', $this->l->msg('Test.presentText'));
|
$this->assertEquals('und der Stein der Weisen', $this->l->msg('Test.presentText'));
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testFetchAMessageWithMissingParameters() {
|
public function testFetchAMessageWithMissingParameters(): void {
|
||||||
$this->l->set("en_ca", true);
|
$this->l->set("en_ca", true);
|
||||||
$this->assertEquals('{0} and {1}', $this->l->msg('Test.presentText'));
|
$this->assertEquals('{0} and {1}', $this->l->msg('Test.presentText'));
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testFetchAMessageWithSingleNumericParameter() {
|
public function testFetchAMessageWithSingleNumericParameter(): void {
|
||||||
$this->l->set("en_ca", true);
|
$this->l->set("en_ca", true);
|
||||||
$this->assertEquals('Default language file "en" missing', $this->l->msg('Exception.JKingWeb/Arsse/Lang/Exception.defaultFileMissing', TestClass::DEFAULT));
|
$this->assertEquals('Default language file "en" missing', $this->l->msg('Exception.JKingWeb/Arsse/Lang/Exception.defaultFileMissing', TestClass::DEFAULT));
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testFetchAMessageWithMultipleNumericParameters() {
|
public function testFetchAMessageWithMultipleNumericParameters(): void {
|
||||||
$this->l->set("en_ca", true);
|
$this->l->set("en_ca", true);
|
||||||
$this->assertEquals('Happy Rotter and the Philosopher\'s Stone', $this->l->msg('Test.presentText', ['Happy Rotter', 'the Philosopher\'s Stone']));
|
$this->assertEquals('Happy Rotter and the Philosopher\'s Stone', $this->l->msg('Test.presentText', ['Happy Rotter', 'the Philosopher\'s Stone']));
|
||||||
}
|
}
|
||||||
|
@ -82,14 +82,14 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testFetchAMessageWithNamedParameters() {
|
public function testFetchAMessageWithNamedParameters(): void {
|
||||||
$this->assertEquals('Message string "Test.absentText" missing from all loaded language files (en)', $this->l->msg('Exception.JKingWeb/Arsse/Lang/Exception.stringMissing', ['msgID' => 'Test.absentText', 'fileList' => 'en']));
|
$this->assertEquals('Message string "Test.absentText" missing from all loaded language files (en)', $this->l->msg('Exception.JKingWeb/Arsse/Lang/Exception.stringMissing', ['msgID' => 'Test.absentText', 'fileList' => 'en']));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testReloadDefaultStrings() {
|
public function testReloadDefaultStrings(): void {
|
||||||
$this->l->set("de", true);
|
$this->l->set("de", true);
|
||||||
$this->l->set("en", true);
|
$this->l->set("en", true);
|
||||||
$this->assertEquals('and the Philosopher\'s Stone', $this->l->msg('Test.presentText'));
|
$this->assertEquals('and the Philosopher\'s Stone', $this->l->msg('Test.presentText'));
|
||||||
|
@ -98,7 +98,7 @@ class TestComplex extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
/**
|
/**
|
||||||
* @depends testFetchAMessage
|
* @depends testFetchAMessage
|
||||||
*/
|
*/
|
||||||
public function testReloadGeneralTagAfterSubtag() {
|
public function testReloadGeneralTagAfterSubtag(): void {
|
||||||
$this->l->set("en", true);
|
$this->l->set("en", true);
|
||||||
$this->l->set("en_us", true);
|
$this->l->set("en_us", true);
|
||||||
$this->assertEquals('and the Sorcerer\'s Stone', $this->l->msg('Test.presentText'));
|
$this->assertEquals('and the Sorcerer\'s Stone', $this->l->msg('Test.presentText'));
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue