Browse Source

Partial CLI tests

J. King 1 month ago
parent
commit
ba8e208d79

+ 6
- 1
RoboFile.php View File

@@ -76,6 +76,10 @@ class RoboFile extends \Robo\Tasks {
76 76
         }
77 77
     }
78 78
 
79
+    protected function isWindows(): bool {
80
+        return defined("PHP_WINDOWS_VERSION_MAJOR");
81
+    }
82
+
79 83
     protected function runTests(string $executor, string $set, array $args) : Result {
80 84
         switch ($set) {
81 85
             case "typical":
@@ -92,8 +96,9 @@ class RoboFile extends \Robo\Tasks {
92 96
         }
93 97
         $execpath = realpath(self::BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
94 98
         $confpath = realpath(self::BASE_TEST."phpunit.xml");
99
+        $blackhole = $this->isWindows() ? "nul" : "/dev/null";
95 100
         $this->taskServer(8000)->host("localhost")->dir(self::BASE_TEST."docroot")->rawArg("-n")->arg(self::BASE_TEST."server.php")->background()->run();
96
-        return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
101
+        return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->rawArg("2>$blackhole")->run();
97 102
     }
98 103
 
99 104
     /** Packages a given commit of the software into a release tarball

+ 3
- 3
lib/Arsse.php View File

@@ -19,10 +19,10 @@ class Arsse {
19 19
     public static $user;
20 20
 
21 21
     public static function load(Conf $conf) {
22
-        static::$lang = new Lang();
22
+        static::$lang = static::$lang ?? new Lang;
23 23
         static::$conf = $conf;
24 24
         static::$lang->set($conf->lang);
25
-        static::$db = new Database();
26
-        static::$user = new User();
25
+        static::$db = static::$db ?? new Database;
26
+        static::$user = static::$user ?? new User;
27 27
     }
28 28
 }

+ 42
- 44
lib/CLI.php View File

@@ -9,36 +9,27 @@ namespace JKingWeb\Arsse;
9 9
 use Docopt\Response as Opts;
10 10
 
11 11
 class CLI {
12
-    protected $args = [];
13
-
14
-    protected function usage(): string {
15
-        $prog = basename($_SERVER['argv'][0]);
16
-        return <<<USAGE_TEXT
12
+    const USAGE = <<<USAGE_TEXT
17 13
 Usage:
18
-    $prog daemon
19
-    $prog feed refresh <n>
20
-    $prog conf save-defaults [<file>]
21
-    $prog user [list]
22
-    $prog user add <username> [<password>]
23
-    $prog user remove <username>
24
-    $prog user set-pass [--oldpass=<pass>] <username> [<password>]
25
-    $prog user auth <username> <password>
26
-    $prog --version
27
-    $prog --help | -h
14
+    arsse.php daemon
15
+    arsse.php feed refresh <n>
16
+    arsse.php conf save-defaults [<file>]
17
+    arsse.php user [list]
18
+    arsse.php user add <username> [<password>]
19
+    arsse.php user remove <username>
20
+    arsse.php user set-pass [--oldpass=<pass>] <username> [<password>]
21
+    arsse.php user auth <username> <password>
22
+    arsse.php --version
23
+    arsse.php --help | -h
28 24
 
29 25
 The Arsse command-line interface currently allows you to start the refresh
30 26
 daemon, refresh a specific feed by numeric ID, manage users, or save default
31 27
 configuration to a sample file.
32 28
 USAGE_TEXT;
33
-    }
34 29
 
35
-    public function __construct(array $argv = null) {
36
-        $argv = $argv ?? array_slice($_SERVER['argv'], 1);
37
-        $this->args = \Docopt::handle($this->usage(), [
38
-            'argv' => $argv,
39
-            'help' => true,
40
-            'version' => Arsse::VERSION,
41
-        ]);
30
+    protected function usage($prog): string {
31
+        $prog = basename($prog);
32
+        return str_replace("arsse.php", $prog, self::USAGE);
42 33
     }
43 34
 
44 35
     protected function command(array $options, $args): string {
@@ -61,19 +52,32 @@ USAGE_TEXT;
61 52
         return true;
62 53
     }
63 54
 
64
-    public function dispatch(array $args = null): int {
65
-        // act on command line
66
-        $args = $args ?? $this->args;
55
+    public function dispatch(array $argv = null) {
56
+        $argv = $argv ?? $_SERVER['argv'];
57
+        $argv0 = array_shift($argv);
58
+        $args = \Docopt::handle($this->usage($argv0), [
59
+            'argv' => $argv,
60
+            'help' => false,
61
+        ]);
67 62
         try {
68
-            switch ($this->command(["daemon", "feed refresh", "conf save-defaults", "user"], $args)) {
63
+            switch ($this->command(["--help", "--version", "daemon", "feed refresh", "conf save-defaults", "user"], $args)) {
64
+                case "--help":
65
+                    echo $this->usage($argv0).\PHP_EOL;
66
+                    return 0;
67
+                case "--version":
68
+                    echo Arsse::VERSION.\PHP_EOL;
69
+                    return 0;
69 70
                 case "daemon":
70 71
                     $this->loadConf();
71
-                    return $this->daemon();
72
+                    $this->getService()->watch(true);
73
+                    return 0;
72 74
                 case "feed refresh":
73 75
                     $this->loadConf();
74
-                    return $this->feedRefresh((int) $args['<n>']);
76
+                    return (int) !Arsse::$db->feedUpdate((int) $args['<n>'], true);
75 77
                 case "conf save-defaults":
76
-                    return $this->confSaveDefaults($args['<file>']);
78
+                    $file = $args['<file>'];
79
+                    $file = ($file=="-" ? null : $file) ?? "php://output";
80
+                    return (int) !($this->getConf())->exportFile($file, true);
77 81
                 case "user":
78 82
                     $this->loadConf();
79 83
                     return $this->userManage($args);
@@ -84,21 +88,17 @@ USAGE_TEXT;
84 88
         }
85 89
     }
86 90
 
87
-    public function daemon(bool $loop = true): int {
88
-        (new Service)->watch($loop);
89
-        return 0; // FIXME: should return the exception code of thrown exceptions
90
-    }
91
-
92
-    public function feedRefresh(int $id): int {
93
-        return (int) !Arsse::$db->feedUpdate($id); // FIXME: exception error codes should be returned here
91
+    /** @codeCoverageIgnore */
92
+    protected function getService(): Service {
93
+        return new Service;
94 94
     }
95 95
 
96
-    public function confSaveDefaults(string $file = null): int {
97
-        $file = ($file=="-" ? null : $file) ?? STDOUT;
98
-        return (int) !(new Conf)->exportFile($file, true);
96
+    /** @codeCoverageIgnore */
97
+    protected function getConf(): Conf {
98
+        return new Conf;
99 99
     }
100 100
 
101
-    public function userManage($args): int {
101
+    protected function userManage($args): int {
102 102
         switch ($this->command(["add", "remove", "set-pass", "list", "auth"], $args)) {
103 103
             case "add":
104 104
                 return $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
@@ -115,9 +115,7 @@ USAGE_TEXT;
115 115
     }
116 116
 
117 117
     protected function userAddOrSetPassword(string $method, string $user, string $password = null, string $oldpass = null): int {
118
-        $args = \func_get_args();
119
-        array_shift($args);
120
-        $passwd = Arsse::$user->$method(...$args);
118
+        $passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1));
121 119
         if (is_null($password)) {
122 120
             echo $passwd.\PHP_EOL;
123 121
         }

+ 136
- 0
tests/cases/CLI/TestCLI.php View File

@@ -0,0 +1,136 @@
1
+<?php
2
+/** @license MIT
3
+ * Copyright 2017 J. King, Dustin Wilson et al.
4
+ * See LICENSE and AUTHORS files for details */
5
+
6
+declare(strict_types=1);
7
+namespace JKingWeb\Arsse\TestCase\CLI;
8
+
9
+use JKingWeb\Arsse\Arsse;
10
+use JKingWeb\Arsse\Conf;
11
+use JKingWeb\Arsse\User;
12
+use JKingWeb\Arsse\Database;
13
+use JKingWeb\Arsse\Service;
14
+use JKingWeb\Arsse\CLI;
15
+use Phake;
16
+
17
+/** @covers \JKingWeb\Arsse\CLI */
18
+class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
19
+
20
+    public function setUp() {
21
+        $this->clearData(false);
22
+    }
23
+
24
+    public function assertConsole(CLI $cli, string $command, int $exitStatus, string $output = "", bool $pattern = false) {
25
+        $argv = \Clue\Arguments\split($command);
26
+        $output = strlen($output) ? $output.\PHP_EOL : "";
27
+        if ($pattern) {
28
+            $this->expectOutputRegex($output);   
29
+        } else {
30
+            $this->expectOutputString($output);
31
+        }
32
+        $this->assertSame($exitStatus, $cli->dispatch($argv));
33
+    }
34
+
35
+    public function assertLoaded(bool $loaded) {
36
+        $r = new \ReflectionClass(Arsse::class);
37
+        $props = array_keys($r->getStaticProperties());
38
+        foreach ($props as $prop) {
39
+            if ($loaded) {
40
+                $this->assertNotNull(Arsse::$$prop, "Global $prop object should be loaded");
41
+            } else {
42
+                $this->assertNull(Arsse::$$prop, "Global $prop object should not be loaded");
43
+            }
44
+        }
45
+    }
46
+
47
+    public function testPrintVersion() {
48
+        $this->assertConsole(new CLI, "arsse.php --version", 0, Arsse::VERSION);
49
+        $this->assertLoaded(false);
50
+    }
51
+
52
+    /** @dataProvider provideHelpText */
53
+    public function testPrintHelp(string $cmd, string $name) {
54
+        $this->assertConsole(new CLI, $cmd, 0, str_replace("arsse.php", $name, CLI::USAGE));
55
+        $this->assertLoaded(false);
56
+    }
57
+
58
+    public function provideHelpText() {
59
+        return [
60
+            ["arsse.php --help", "arsse.php"],
61
+            ["arsse     --help", "arsse"],
62
+            ["thearsse  --help", "thearsse"],
63
+        ];
64
+    }
65
+
66
+    public function testStartTheDaemon() {
67
+        $srv = Phake::mock(Service::class);
68
+        $cli = Phake::partialMock(CLI::class);
69
+        Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable);
70
+        Phake::when($cli)->getService->thenReturn($srv);
71
+        $this->assertConsole($cli, "arsse.php daemon", 0);
72
+        $this->assertLoaded(true);
73
+        Phake::verify($srv)->watch(true);
74
+        Phake::verify($cli)->getService;
75
+    }
76
+
77
+    /** @dataProvider provideFeedUpdates */
78
+    public function testRefreshAFeed(string $cmd, int $exitStatus, string $output) {
79
+        Arsse::$db = Phake::mock(Database::class);
80
+        Phake::when(Arsse::$db)->feedUpdate(1, true)->thenReturn(true);
81
+        Phake::when(Arsse::$db)->feedUpdate(2, true)->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/", new \PicoFeed\Client\InvalidUrlException));
82
+        $this->assertConsole(new CLI, $cmd, $exitStatus, $output);
83
+        $this->assertLoaded(true);
84
+        Phake::verify(Arsse::$db)->feedUpdate;
85
+    }
86
+
87
+    public function provideFeedUpdates() {
88
+        return [
89
+            ["arsse.php feed refresh 1", 0,     ""],
90
+            ["arsse.php feed refresh 2", 10502, ""],
91
+        ];
92
+    }
93
+
94
+    /** @dataProvider provideDefaultConfigurationSaves */
95
+    public function testSaveTheDefaultConfiguration(string $cmd, int $exitStatus, string $file) {
96
+        $conf = Phake::mock(Conf::class);
97
+        $cli = Phake::partialMock(CLI::class);
98
+        Phake::when($conf)->exportFile("php://output", true)->thenReturn(true);
99
+        Phake::when($conf)->exportFile("good.conf", true)->thenReturn(true);
100
+        Phake::when($conf)->exportFile("bad.conf", true)->thenThrow(new \JKingWeb\Arsse\Conf\Exception("fileUnwritable"));
101
+        Phake::when($cli)->getConf->thenReturn($conf);
102
+        $this->assertConsole($cli, $cmd, $exitStatus);
103
+        $this->assertLoaded(false);
104
+        Phake::verify($conf)->exportFile($file, true);
105
+    }
106
+
107
+    public function provideDefaultConfigurationSaves() {
108
+        return [
109
+            ["arsse.php conf save-defaults",           0,     "php://output"],
110
+            ["arsse.php conf save-defaults -",         0,     "php://output"],
111
+            ["arsse.php conf save-defaults good.conf", 0,     "good.conf"],
112
+            ["arsse.php conf save-defaults bad.conf",  10304, "bad.conf"],
113
+        ];
114
+    }
115
+
116
+    /** @dataProvider provideUserList */
117
+    public function testListUsers(string $cmd, array $list, int $exitStatus, string $output) {
118
+        Arsse::$user = Phake::mock(User::class);
119
+        Phake::when(Arsse::$user)->list()->thenReturn($list);
120
+        $this->assertConsole(new CLI, $cmd, $exitStatus, $output);
121
+        $this->assertLoaded(true);
122
+        Phake::verify(Arsse::$user)->list;
123
+    }
124
+
125
+    public function provideUserList() {
126
+        return [];
127
+        $list = ["john.doe@example.com", "jane.doe@example.com"];
128
+        $str = implode(PHP_EOL, $list);
129
+        return [
130
+            ["arsse.php user list", $list, 0, $str],
131
+            ["arsse.php user",      $list, 0, $str],
132
+            ["arsse.php user list", [],    0, ""],
133
+            ["arsse.php user",      [],    0, ""],
134
+        ];
135
+    }
136
+}

+ 8
- 0
tests/cases/Conf/TestConf.php View File

@@ -135,6 +135,14 @@ class TestConf extends \JKingWeb\Arsse\Test\AbstractTest {
135 135
         $this->assertArraySubset($exp, $arr);
136 136
     }
137 137
 
138
+    /** @depends testExportToFile */
139
+    public function testExportToStdout() {
140
+        $conf = new Conf(self::$path."confGood");
141
+        $conf->exportFile(self::$path."confGood");
142
+        $this->expectOutputString(file_get_contents(self::$path."confGood"));
143
+        $conf->exportFile("php://output");
144
+    }
145
+
138 146
     public function testExportToFileWithoutWritePermission() {
139 147
         $this->assertException("fileUnwritable", "Conf");
140 148
         (new Conf)->exportFile(self::$path."confUnreadable");

+ 20
- 20
tests/lib/AbstractTest.php View File

@@ -9,6 +9,7 @@ namespace JKingWeb\Arsse\Test;
9 9
 use JKingWeb\Arsse\Exception;
10 10
 use JKingWeb\Arsse\Arsse;
11 11
 use JKingWeb\Arsse\Conf;
12
+use JKingWeb\Arsse\CLI;
12 13
 use JKingWeb\Arsse\Misc\Date;
13 14
 use Psr\Http\Message\MessageInterface;
14 15
 use Psr\Http\Message\RequestInterface;
@@ -27,6 +28,18 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
27 28
         $this->clearData();
28 29
     }
29 30
 
31
+    public function clearData(bool $loadLang = true) {
32
+        date_default_timezone_set("America/Toronto");
33
+        $r = new \ReflectionClass(\JKingWeb\Arsse\Arsse::class);
34
+        $props = array_keys($r->getStaticProperties());
35
+        foreach ($props as $prop) {
36
+            Arsse::$$prop = null;
37
+        }
38
+        if ($loadLang) {
39
+            Arsse::$lang = new \JKingWeb\Arsse\Lang();
40
+        }
41
+    }
42
+
30 43
     public function setConf(array $conf = []) {
31 44
         Arsse::$conf = (new Conf)->import($conf);
32 45
     }
@@ -70,6 +83,13 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
70 83
         $this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
71 84
     }
72 85
 
86
+    public function assertTime($exp, $test, string $msg = null) {
87
+        $test = $this->approximateTime($exp, $test);
88
+        $exp  = Date::transform($exp, "iso8601");
89
+        $test = Date::transform($test, "iso8601");
90
+        $this->assertSame($exp, $test, $msg);
91
+    }
92
+
73 93
     public function approximateTime($exp, $act) {
74 94
         if (is_null($act)) {
75 95
             return null;
@@ -85,24 +105,4 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
85 105
             return $act;
86 106
         }
87 107
     }
88
-
89
-    public function assertTime($exp, $test, string $msg = null) {
90
-        $test = $this->approximateTime($exp, $test);
91
-        $exp  = Date::transform($exp, "iso8601");
92
-        $test = Date::transform($test, "iso8601");
93
-        $this->assertSame($exp, $test, $msg);
94
-    }
95
-
96
-    public function clearData(bool $loadLang = true): bool {
97
-        date_default_timezone_set("America/Toronto");
98
-        $r = new \ReflectionClass(\JKingWeb\Arsse\Arsse::class);
99
-        $props = array_keys($r->getStaticProperties());
100
-        foreach ($props as $prop) {
101
-            Arsse::$$prop = null;
102
-        }
103
-        if ($loadLang) {
104
-            Arsse::$lang = new \JKingWeb\Arsse\Lang();
105
-        }
106
-        return true;
107
-    }
108 108
 }

+ 3
- 2
tests/phpunit.xml View File

@@ -96,8 +96,9 @@
96 96
         <file>cases/REST/TinyTinyRSS/TestIcon.php</file>
97 97
         <file>cases/REST/TinyTinyRSS/PDO/TestAPI.php</file>
98 98
     </testsuite>
99
-    <testsuite name="Refresh service">
99
+    <testsuite name="Admin tools">
100 100
         <file>cases/Service/TestService.php</file>
101
+        <file>cases/CLI/TestCLI.php</file>
101 102
     </testsuite>
102 103
 </testsuites>
103
-</phpunit>
104
+</phpunit>

+ 1
- 0
vendor-bin/phpunit/composer.json View File

@@ -2,6 +2,7 @@
2 2
     "require": {
3 3
         "phpunit/phpunit": "^6.5",
4 4
         "phake/phake": "^3.0",
5
+        "clue/arguments": "^2.0",
5 6
         "mikey179/vfsStream": "^1.6",
6 7
         "webmozart/glob": "^4.1"
7 8
     }

+ 51
- 1
vendor-bin/phpunit/composer.lock View File

@@ -4,8 +4,58 @@
4 4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 5
         "This file is @generated automatically"
6 6
     ],
7
-    "content-hash": "2feb94beae7c769e2df081af57c89fed",
7
+    "content-hash": "4252b3d7817c9a4a5f60ac81f28202e2",
8 8
     "packages": [
9
+        {
10
+            "name": "clue/arguments",
11
+            "version": "v2.0.0",
12
+            "source": {
13
+                "type": "git",
14
+                "url": "https://github.com/clue/php-arguments.git",
15
+                "reference": "eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2"
16
+            },
17
+            "dist": {
18
+                "type": "zip",
19
+                "url": "https://api.github.com/repos/clue/php-arguments/zipball/eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2",
20
+                "reference": "eb8356918bc51ac7e595e4ad92a2bc1c1d2754c2",
21
+                "shasum": ""
22
+            },
23
+            "require": {
24
+                "php": ">=5.3"
25
+            },
26
+            "type": "library",
27
+            "autoload": {
28
+                "files": [
29
+                    "src/functions.php"
30
+                ],
31
+                "psr-4": {
32
+                    "Clue\\Arguments\\": "src/"
33
+                }
34
+            },
35
+            "notification-url": "https://packagist.org/downloads/",
36
+            "license": [
37
+                "MIT"
38
+            ],
39
+            "authors": [
40
+                {
41
+                    "name": "Christian Lück",
42
+                    "email": "christian@lueck.tv"
43
+                }
44
+            ],
45
+            "description": "The simple way to split your command line string into an array of command arguments in PHP.",
46
+            "homepage": "https://github.com/clue/php-arguments",
47
+            "keywords": [
48
+                "args",
49
+                "arguments",
50
+                "argv",
51
+                "command",
52
+                "command line",
53
+                "explode",
54
+                "parse",
55
+                "split"
56
+            ],
57
+            "time": "2016-12-18T14:37:39+00:00"
58
+        },
9 59
         {
10 60
             "name": "doctrine/instantiator",
11 61
             "version": "1.0.5",

Loading…
Cancel
Save