mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Merge remote-tracking branch 'remotes/origin/cli-overhaul'
This commit is contained in:
commit
3a4100576a
11 changed files with 420 additions and 78 deletions
|
@ -76,6 +76,10 @@ class RoboFile extends \Robo\Tasks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function isWindows(): bool {
|
||||||
|
return defined("PHP_WINDOWS_VERSION_MAJOR");
|
||||||
|
}
|
||||||
|
|
||||||
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":
|
||||||
|
@ -92,8 +96,9 @@ class RoboFile extends \Robo\Tasks {
|
||||||
}
|
}
|
||||||
$execpath = realpath(self::BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
|
$execpath = realpath(self::BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
|
||||||
$confpath = realpath(self::BASE_TEST."phpunit.xml");
|
$confpath = realpath(self::BASE_TEST."phpunit.xml");
|
||||||
|
$blackhole = $this->isWindows() ? "nul" : "/dev/null";
|
||||||
$this->taskServer(8000)->host("localhost")->dir(self::BASE_TEST."docroot")->rawArg("-n")->arg(self::BASE_TEST."server.php")->background()->run();
|
$this->taskServer(8000)->host("localhost")->dir(self::BASE_TEST."docroot")->rawArg("-n")->arg(self::BASE_TEST."server.php")->background()->run();
|
||||||
return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
|
return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->rawArg("2>$blackhole")->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Packages a given commit of the software into a release tarball
|
/** Packages a given commit of the software into a release tarball
|
||||||
|
|
|
@ -18,7 +18,8 @@ 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;
|
||||||
// handle other CLI requests; some do not require configuration
|
// handle other CLI requests; some do not require configuration
|
||||||
$cli->dispatch();
|
$exitStatus = $cli->dispatch();
|
||||||
|
exit($exitStatus);
|
||||||
} else {
|
} else {
|
||||||
// load configuration
|
// load configuration
|
||||||
$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;
|
||||||
|
|
|
@ -19,10 +19,10 @@ class Arsse {
|
||||||
public static $user;
|
public static $user;
|
||||||
|
|
||||||
public static function load(Conf $conf) {
|
public static function load(Conf $conf) {
|
||||||
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);
|
||||||
static::$db = new Database();
|
static::$db = static::$db ?? new Database;
|
||||||
static::$user = new User();
|
static::$user = static::$user ?? new User;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
146
lib/CLI.php
146
lib/CLI.php
|
@ -6,33 +6,42 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class CLI {
|
use Docopt\Response as Opts;
|
||||||
protected $args = [];
|
|
||||||
|
|
||||||
protected function usage(): string {
|
class CLI {
|
||||||
$prog = basename($_SERVER['argv'][0]);
|
const USAGE = <<<USAGE_TEXT
|
||||||
return <<<USAGE_TEXT
|
|
||||||
Usage:
|
Usage:
|
||||||
$prog daemon
|
arsse.php daemon
|
||||||
$prog feed refresh <n>
|
arsse.php feed refresh <n>
|
||||||
$prog conf save-defaults <file>
|
arsse.php conf save-defaults [<file>]
|
||||||
$prog user add <username> [<password>]
|
arsse.php user [list]
|
||||||
$prog --version
|
arsse.php user add <username> [<password>]
|
||||||
$prog --help | -h
|
arsse.php user remove <username>
|
||||||
|
arsse.php user set-pass [--oldpass=<pass>] <username> [<password>]
|
||||||
|
arsse.php user auth <username> <password>
|
||||||
|
arsse.php --version
|
||||||
|
arsse.php --help | -h
|
||||||
|
|
||||||
The Arsse command-line interface currently allows you to start the refresh
|
The Arsse command-line interface currently allows you to start the refresh
|
||||||
daemon, refresh a specific feed by numeric ID, add a user, or save default
|
daemon, refresh a specific feed by numeric ID, manage users, or save default
|
||||||
configuration to a sample file.
|
configuration to a sample file.
|
||||||
USAGE_TEXT;
|
USAGE_TEXT;
|
||||||
|
|
||||||
|
protected function usage($prog): string {
|
||||||
|
$prog = basename($prog);
|
||||||
|
return str_replace("arsse.php", $prog, self::USAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(array $argv = null) {
|
protected function command(array $options, $args): string {
|
||||||
$argv = $argv ?? array_slice($_SERVER['argv'], 1);
|
foreach ($options as $cmd) {
|
||||||
$this->args = \Docopt::handle($this->usage(), [
|
foreach (explode(" ", $cmd) as $part) {
|
||||||
'argv' => $argv,
|
if (!$args[$part]) {
|
||||||
'help' => true,
|
continue 2;
|
||||||
'version' => Arsse::VERSION,
|
}
|
||||||
]);
|
}
|
||||||
|
return $cmd;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadConf(): bool {
|
protected function loadConf(): bool {
|
||||||
|
@ -43,50 +52,91 @@ USAGE_TEXT;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch(array $args = null): int {
|
public function dispatch(array $argv = null) {
|
||||||
// act on command line
|
$argv = $argv ?? $_SERVER['argv'];
|
||||||
$args = $args ?? $this->args;
|
$argv0 = array_shift($argv);
|
||||||
if ($this->command("daemon", $args)) {
|
$args = \Docopt::handle($this->usage($argv0), [
|
||||||
|
'argv' => $argv,
|
||||||
|
'help' => false,
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
switch ($this->command(["--help", "--version", "daemon", "feed refresh", "conf save-defaults", "user"], $args)) {
|
||||||
|
case "--help":
|
||||||
|
echo $this->usage($argv0).\PHP_EOL;
|
||||||
|
return 0;
|
||||||
|
case "--version":
|
||||||
|
echo Arsse::VERSION.\PHP_EOL;
|
||||||
|
return 0;
|
||||||
|
case "daemon":
|
||||||
$this->loadConf();
|
$this->loadConf();
|
||||||
return $this->daemon();
|
$this->getService()->watch(true);
|
||||||
} elseif ($this->command("feed refresh", $args)) {
|
return 0;
|
||||||
|
case "feed refresh":
|
||||||
$this->loadConf();
|
$this->loadConf();
|
||||||
return $this->feedRefresh((int) $args['<n>']);
|
return (int) !Arsse::$db->feedUpdate((int) $args['<n>'], true);
|
||||||
} elseif ($this->command("conf save-defaults", $args)) {
|
case "conf save-defaults":
|
||||||
return $this->confSaveDefaults($args['<file>']);
|
$file = $args['<file>'];
|
||||||
} elseif ($this->command("user add", $args)) {
|
$file = ($file=="-" ? null : $file) ?? "php://output";
|
||||||
|
return (int) !($this->getConf())->exportFile($file, true);
|
||||||
|
case "user":
|
||||||
$this->loadConf();
|
$this->loadConf();
|
||||||
return $this->userAdd($args['<username>'], $args['<password>']);
|
return $this->userManage($args);
|
||||||
|
}
|
||||||
|
} catch (AbstractException $e) {
|
||||||
|
fwrite(STDERR, $e->getMessage().\PHP_EOL);
|
||||||
|
return $e->getCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function command($cmd, $args): bool {
|
/** @codeCoverageIgnore */
|
||||||
foreach (explode(" ", $cmd) as $part) {
|
protected function getService(): Service {
|
||||||
if (!$args[$part]) {
|
return new Service;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function daemon(bool $loop = true): int {
|
/** @codeCoverageIgnore */
|
||||||
(new Service)->watch($loop);
|
protected function getConf(): Conf {
|
||||||
return 0; // FIXME: should return the exception code of thrown exceptions
|
return new Conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function feedRefresh(int $id): int {
|
protected function userManage($args): int {
|
||||||
return (int) !Arsse::$db->feedUpdate($id); // FIXME: exception error codes should be returned here
|
switch ($this->command(["add", "remove", "set-pass", "list", "auth"], $args)) {
|
||||||
|
case "add":
|
||||||
|
return $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
|
||||||
|
case "set-pass":
|
||||||
|
return $this->userAddOrSetPassword("passwordSet", $args["<username>"], $args["<password>"], $args["--oldpass"]);
|
||||||
|
case "remove":
|
||||||
|
return (int) !Arsse::$user->remove($args["<username>"]);
|
||||||
|
case "auth":
|
||||||
|
return $this->userAuthenticate($args["<username>"], $args["<password>"]);
|
||||||
|
case "list":
|
||||||
|
case "":
|
||||||
|
return $this->userList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function confSaveDefaults(string $file): int {
|
protected function userAddOrSetPassword(string $method, string $user, string $password = null, string $oldpass = null): int {
|
||||||
return (int) !(new Conf)->exportFile($file, true);
|
$passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1));
|
||||||
}
|
|
||||||
|
|
||||||
public function userAdd(string $user, string $password = null): int {
|
|
||||||
$passwd = Arsse::$user->add($user, $password);
|
|
||||||
if (is_null($password)) {
|
if (is_null($password)) {
|
||||||
echo $passwd.\PHP_EOL;
|
echo $passwd.\PHP_EOL;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function userList(): int {
|
||||||
|
$list = Arsse::$user->list();
|
||||||
|
if ($list) {
|
||||||
|
echo implode(\PHP_EOL, $list).\PHP_EOL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function userAuthenticate(string $user, string $password): int {
|
||||||
|
if (Arsse::$user->auth($user, $password)) {
|
||||||
|
echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
echo Arsse::$lang->msg("CLI.Auth.Failure").\PHP_EOL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
* See LICENSE and AUTHORS files for details */
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'CLI.Auth.Success' => 'Authentication successful',
|
||||||
|
'CLI.Auth.Failure' => 'Authentication failed',
|
||||||
|
|
||||||
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
||||||
'API.TTRSS.Category.Special' => 'Special',
|
'API.TTRSS.Category.Special' => 'Special',
|
||||||
'API.TTRSS.Category.Labels' => 'Labels',
|
'API.TTRSS.Category.Labels' => 'Labels',
|
||||||
|
|
223
tests/cases/CLI/TestCLI.php
Normal file
223
tests/cases/CLI/TestCLI.php
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
<?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\CLI;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Conf;
|
||||||
|
use JKingWeb\Arsse\User;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
|
use JKingWeb\Arsse\Service;
|
||||||
|
use JKingWeb\Arsse\CLI;
|
||||||
|
use Phake;
|
||||||
|
|
||||||
|
/** @covers \JKingWeb\Arsse\CLI */
|
||||||
|
class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->clearData(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertConsole(CLI $cli, string $command, int $exitStatus, string $output = "", bool $pattern = false) {
|
||||||
|
$argv = \Clue\Arguments\split($command);
|
||||||
|
$output = strlen($output) ? $output.\PHP_EOL : "";
|
||||||
|
if ($pattern) {
|
||||||
|
$this->expectOutputRegex($output);
|
||||||
|
} else {
|
||||||
|
$this->expectOutputString($output);
|
||||||
|
}
|
||||||
|
$this->assertSame($exitStatus, $cli->dispatch($argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertLoaded(bool $loaded) {
|
||||||
|
$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(new CLI, "arsse.php --version", 0, Arsse::VERSION);
|
||||||
|
$this->assertLoaded(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideHelpText */
|
||||||
|
public function testPrintHelp(string $cmd, string $name) {
|
||||||
|
$this->assertConsole(new CLI, $cmd, 0, str_replace("arsse.php", $name, CLI::USAGE));
|
||||||
|
$this->assertLoaded(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideHelpText() {
|
||||||
|
return [
|
||||||
|
["arsse.php --help", "arsse.php"],
|
||||||
|
["arsse --help", "arsse"],
|
||||||
|
["thearsse --help", "thearsse"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStartTheDaemon() {
|
||||||
|
$srv = Phake::mock(Service::class);
|
||||||
|
$cli = Phake::partialMock(CLI::class);
|
||||||
|
Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
|
||||||
|
Phake::when($cli)->getService->thenReturn($srv);
|
||||||
|
$this->assertConsole($cli, "arsse.php daemon", 0);
|
||||||
|
$this->assertLoaded(true);
|
||||||
|
Phake::verify($srv)->watch(true);
|
||||||
|
Phake::verify($cli)->getService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideFeedUpdates */
|
||||||
|
public function testRefreshAFeed(string $cmd, int $exitStatus, string $output) {
|
||||||
|
Arsse::$db = Phake::mock(Database::class);
|
||||||
|
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));
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
$this->assertLoaded(true);
|
||||||
|
Phake::verify(Arsse::$db)->feedUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideFeedUpdates() {
|
||||||
|
return [
|
||||||
|
["arsse.php feed refresh 1", 0, ""],
|
||||||
|
["arsse.php feed refresh 2", 10502, ""],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideDefaultConfigurationSaves */
|
||||||
|
public function testSaveTheDefaultConfiguration(string $cmd, int $exitStatus, string $file) {
|
||||||
|
$conf = Phake::mock(Conf::class);
|
||||||
|
$cli = Phake::partialMock(CLI::class);
|
||||||
|
Phake::when($conf)->exportFile("php://output", 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($cli)->getConf->thenReturn($conf);
|
||||||
|
$this->assertConsole($cli, $cmd, $exitStatus);
|
||||||
|
$this->assertLoaded(false);
|
||||||
|
Phake::verify($conf)->exportFile($file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideDefaultConfigurationSaves() {
|
||||||
|
return [
|
||||||
|
["arsse.php conf save-defaults", 0, "php://output"],
|
||||||
|
["arsse.php conf save-defaults -", 0, "php://output"],
|
||||||
|
["arsse.php conf save-defaults good.conf", 0, "good.conf"],
|
||||||
|
["arsse.php conf save-defaults bad.conf", 10304, "bad.conf"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideUserList */
|
||||||
|
public function testListUsers(string $cmd, array $list, int $exitStatus, string $output) {
|
||||||
|
// 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->method("list")->willReturn($list);
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideUserList() {
|
||||||
|
$list = ["john.doe@example.com", "jane.doe@example.com"];
|
||||||
|
$str = implode(PHP_EOL, $list);
|
||||||
|
return [
|
||||||
|
["arsse.php user list", $list, 0, $str],
|
||||||
|
["arsse.php user", $list, 0, $str],
|
||||||
|
["arsse.php user list", [], 0, ""],
|
||||||
|
["arsse.php user", [], 0, ""],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideUserAdditions */
|
||||||
|
public function testAddAUser(string $cmd, int $exitStatus, string $output) {
|
||||||
|
// 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->method("add")->will($this->returnCallback(function($user, $pass = null) {
|
||||||
|
switch ($user) {
|
||||||
|
case "john.doe@example.com":
|
||||||
|
throw new \JKingWeb\Arsse\User\Exception("alreadyExists");
|
||||||
|
case "jane.doe@example.com":
|
||||||
|
return is_null($pass) ? "random password" : $pass;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideUserAdditions() {
|
||||||
|
return [
|
||||||
|
["arsse.php user add john.doe@example.com", 10403, ""],
|
||||||
|
["arsse.php user add jane.doe@example.com", 0, "random password"],
|
||||||
|
["arsse.php user add jane.doe@example.com superman", 0, ""],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideUserAuthentication */
|
||||||
|
public function testAuthenticateAUser(string $cmd, int $exitStatus, string $output) {
|
||||||
|
// 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->method("auth")->will($this->returnCallback(function($user, $pass) {
|
||||||
|
return (
|
||||||
|
($user == "john.doe@example.com" && $pass == "secret") ||
|
||||||
|
($user == "jane.doe@example.com" && $pass == "superman")
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideUserAuthentication() {
|
||||||
|
$l = new \JKingWeb\Arsse\Lang;
|
||||||
|
return [
|
||||||
|
["arsse.php user auth john.doe@example.com secret", 0, $l("CLI.Auth.Success")],
|
||||||
|
["arsse.php user auth john.doe@example.com superman", 1, $l("CLI.Auth.Failure")],
|
||||||
|
["arsse.php user auth jane.doe@example.com secret", 1, $l("CLI.Auth.Failure")],
|
||||||
|
["arsse.php user auth jane.doe@example.com superman", 0, $l("CLI.Auth.Success")],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideUserRemovals */
|
||||||
|
public function testRemoveAUser(string $cmd, int $exitStatus, string $output) {
|
||||||
|
// 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->method("remove")->will($this->returnCallback(function($user) {
|
||||||
|
if ($user == "john.doe@example.com") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new \JKingWeb\Arsse\User\Exception("doesNotExist");
|
||||||
|
}));
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideUserRemovals() {
|
||||||
|
return [
|
||||||
|
["arsse.php user remove john.doe@example.com", 0, ""],
|
||||||
|
["arsse.php user remove jane.doe@example.com", 10402, ""],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideUserPasswordChanges */
|
||||||
|
public function testChangeAUserPassword(string $cmd, int $exitStatus, string $output) {
|
||||||
|
// 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->method("passwordSet")->will($this->returnCallback(function($user, $pass = null) {
|
||||||
|
switch ($user) {
|
||||||
|
case "jane.doe@example.com":
|
||||||
|
throw new \JKingWeb\Arsse\User\Exception("doesNotExist");
|
||||||
|
case "john.doe@example.com":
|
||||||
|
return is_null($pass) ? "random password" : $pass;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
$this->assertConsole(new CLI, $cmd, $exitStatus, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideUserPasswordChanges() {
|
||||||
|
return [
|
||||||
|
["arsse.php user set-pass john.doe@example.com", 0, "random password"],
|
||||||
|
["arsse.php user set-pass john.doe@example.com superman", 0, ""],
|
||||||
|
["arsse.php user set-pass jane.doe@example.com", 10402, ""],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,6 +135,14 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertArraySubset($exp, $arr);
|
$this->assertArraySubset($exp, $arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @depends testExportToFile */
|
||||||
|
public function testExportToStdout() {
|
||||||
|
$conf = new Conf(self::$path."confGood");
|
||||||
|
$conf->exportFile(self::$path."confGood");
|
||||||
|
$this->expectOutputString(file_get_contents(self::$path."confGood"));
|
||||||
|
$conf->exportFile("php://output");
|
||||||
|
}
|
||||||
|
|
||||||
public function testExportToFileWithoutWritePermission() {
|
public function testExportToFileWithoutWritePermission() {
|
||||||
$this->assertException("fileUnwritable", "Conf");
|
$this->assertException("fileUnwritable", "Conf");
|
||||||
(new Conf)->exportFile(self::$path."confUnreadable");
|
(new Conf)->exportFile(self::$path."confUnreadable");
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace JKingWeb\Arsse\Test;
|
||||||
use JKingWeb\Arsse\Exception;
|
use JKingWeb\Arsse\Exception;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Conf;
|
use JKingWeb\Arsse\Conf;
|
||||||
|
use JKingWeb\Arsse\CLI;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
use Psr\Http\Message\MessageInterface;
|
use Psr\Http\Message\MessageInterface;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
@ -27,6 +28,18 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clearData(bool $loadLang = true) {
|
||||||
|
date_default_timezone_set("America/Toronto");
|
||||||
|
$r = new \ReflectionClass(\JKingWeb\Arsse\Arsse::class);
|
||||||
|
$props = array_keys($r->getStaticProperties());
|
||||||
|
foreach ($props as $prop) {
|
||||||
|
Arsse::$$prop = null;
|
||||||
|
}
|
||||||
|
if ($loadLang) {
|
||||||
|
Arsse::$lang = new \JKingWeb\Arsse\Lang();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function setConf(array $conf = []) {
|
public function setConf(array $conf = []) {
|
||||||
Arsse::$conf = (new Conf)->import($conf);
|
Arsse::$conf = (new Conf)->import($conf);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +83,13 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
|
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function assertTime($exp, $test, string $msg = null) {
|
||||||
|
$test = $this->approximateTime($exp, $test);
|
||||||
|
$exp = Date::transform($exp, "iso8601");
|
||||||
|
$test = Date::transform($test, "iso8601");
|
||||||
|
$this->assertSame($exp, $test, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
public function approximateTime($exp, $act) {
|
public function approximateTime($exp, $act) {
|
||||||
if (is_null($act)) {
|
if (is_null($act)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -85,24 +105,4 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
return $act;
|
return $act;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assertTime($exp, $test, string $msg = null) {
|
|
||||||
$test = $this->approximateTime($exp, $test);
|
|
||||||
$exp = Date::transform($exp, "iso8601");
|
|
||||||
$test = Date::transform($test, "iso8601");
|
|
||||||
$this->assertSame($exp, $test, $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearData(bool $loadLang = true): bool {
|
|
||||||
date_default_timezone_set("America/Toronto");
|
|
||||||
$r = new \ReflectionClass(\JKingWeb\Arsse\Arsse::class);
|
|
||||||
$props = array_keys($r->getStaticProperties());
|
|
||||||
foreach ($props as $prop) {
|
|
||||||
Arsse::$$prop = null;
|
|
||||||
}
|
|
||||||
if ($loadLang) {
|
|
||||||
Arsse::$lang = new \JKingWeb\Arsse\Lang();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,8 +96,9 @@
|
||||||
<file>cases/REST/TinyTinyRSS/TestIcon.php</file>
|
<file>cases/REST/TinyTinyRSS/TestIcon.php</file>
|
||||||
<file>cases/REST/TinyTinyRSS/PDO/TestAPI.php</file>
|
<file>cases/REST/TinyTinyRSS/PDO/TestAPI.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Refresh service">
|
<testsuite name="Admin tools">
|
||||||
<file>cases/Service/TestService.php</file>
|
<file>cases/Service/TestService.php</file>
|
||||||
|
<file>cases/CLI/TestCLI.php</file>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
</phpunit>
|
</phpunit>
|
|
@ -2,6 +2,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"phpunit/phpunit": "^6.5",
|
"phpunit/phpunit": "^6.5",
|
||||||
"phake/phake": "^3.0",
|
"phake/phake": "^3.0",
|
||||||
|
"clue/arguments": "^2.0",
|
||||||
"mikey179/vfsStream": "^1.6",
|
"mikey179/vfsStream": "^1.6",
|
||||||
"webmozart/glob": "^4.1"
|
"webmozart/glob": "^4.1"
|
||||||
}
|
}
|
||||||
|
|
52
vendor-bin/phpunit/composer.lock
generated
52
vendor-bin/phpunit/composer.lock
generated
|
@ -4,8 +4,58 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "2feb94beae7c769e2df081af57c89fed",
|
"content-hash": "4252b3d7817c9a4a5f60ac81f28202e2",
|
||||||
"packages": [
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "clue/arguments",
|
||||||
|
"version": "v2.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/clue/php-arguments.git",
|
||||||
|
"reference": "eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/clue/php-arguments/zipball/eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2",
|
||||||
|
"reference": "eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Clue\\Arguments\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@lueck.tv"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The simple way to split your command line string into an array of command arguments in PHP.",
|
||||||
|
"homepage": "https://github.com/clue/php-arguments",
|
||||||
|
"keywords": [
|
||||||
|
"args",
|
||||||
|
"arguments",
|
||||||
|
"argv",
|
||||||
|
"command",
|
||||||
|
"command line",
|
||||||
|
"explode",
|
||||||
|
"parse",
|
||||||
|
"split"
|
||||||
|
],
|
||||||
|
"time": "2016-12-18T14:37:39+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/instantiator",
|
"name": "doctrine/instantiator",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
|
Loading…
Reference in a new issue