diff --git a/lib/Arsse.php b/lib/Arsse.php index 7d53a42..0cd7d6c 100644 --- a/lib/Arsse.php +++ b/lib/Arsse.php @@ -7,8 +7,10 @@ declare(strict_types=1); namespace JKingWeb\Arsse; class Arsse { - public const VERSION = "0.8.5"; + public const VERSION = "0.9.0"; + /** @var Factory */ + public static $obj; /** @var Lang */ public static $lang; /** @var Conf */ @@ -19,6 +21,7 @@ class Arsse { public static $user; public static function load(Conf $conf): void { + static::$obj = static::$obj ?? new Factory; static::$lang = static::$lang ?? new Lang; static::$conf = $conf; static::$lang->set($conf->lang); diff --git a/lib/CLI.php b/lib/CLI.php index c9a5967..bc96f46 100644 --- a/lib/CLI.php +++ b/lib/CLI.php @@ -182,26 +182,26 @@ USAGE_TEXT; echo Arsse::VERSION.\PHP_EOL; return 0; case "daemon": - $this->getInstance(Service::class)->watch(true); + Arsse::$obj->get(Service::class)->watch(true); return 0; case "feed refresh": return (int) !Arsse::$db->feedUpdate((int) $args[''], true); case "feed refresh-all": - $this->getInstance(Service::class)->watch(false); + Arsse::$obj->get(Service::class)->watch(false); return 0; case "conf save-defaults": $file = $this->resolveFile($args[''], "w"); - return (int) !$this->getInstance(Conf::class)->exportFile($file, true); + return (int) !Arsse::$obj->get(Conf::class)->exportFile($file, true); case "user": return $this->userManage($args); case "export": $u = $args['']; $file = $this->resolveFile($args[''], "w"); - return (int) !$this->getInstance(OPML::class)->exportFile($file, $u, ($args['--flat'] || $args['-f'])); + return (int) !Arsse::$obj->get(OPML::class)->exportFile($file, $u, ($args['--flat'] || $args['-f'])); case "import": $u = $args['']; $file = $this->resolveFile($args[''], "r"); - return (int) !$this->getInstance(OPML::class)->importFile($file, $u, ($args['--flat'] || $args['-f']), ($args['--replace'] || $args['-r'])); + return (int) !Arsse::$obj->get(OPML::class)->importFile($file, $u, ($args['--flat'] || $args['-f']), ($args['--replace'] || $args['-r'])); } } catch (AbstractException $e) { $this->logError($e->getMessage()); @@ -214,11 +214,6 @@ USAGE_TEXT; fwrite(STDERR, $msg.\PHP_EOL); } - /** @codeCoverageIgnore */ - protected function getInstance(string $class) { - return new $class; - } - protected function userManage($args): int { $cmd = $this->command(["add", "remove", "set-pass", "unset-pass", "list", "auth"], $args); switch ($cmd) { @@ -226,7 +221,7 @@ USAGE_TEXT; return $this->userAddOrSetPassword("add", $args[""], $args[""]); case "set-pass": if ($args['--fever']) { - $passwd = $this->getInstance(Fever::class)->register($args[""], $args[""]); + $passwd = Arsse::$obj->get(Fever::class)->register($args[""], $args[""]); if (is_null($args[""])) { echo $passwd.\PHP_EOL; } @@ -237,7 +232,7 @@ USAGE_TEXT; // no break case "unset-pass": if ($args['--fever']) { - $this->getInstance(Fever::class)->unregister($args[""]); + Arsse::$obj->get(Fever::class)->unregister($args[""]); } else { Arsse::$user->passwordUnset($args[""], $args["--oldpass"]); } @@ -271,7 +266,7 @@ USAGE_TEXT; } 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 ? Arsse::$obj->get(Fever::class)->authenticate($user, $password) : Arsse::$user->auth($user, $password); if ($result) { echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL; return 0; diff --git a/lib/Factory.php b/lib/Factory.php new file mode 100644 index 0000000..9698902 --- /dev/null +++ b/lib/Factory.php @@ -0,0 +1,13 @@ +withMethod(strtoupper($req->getMethod()))->withRequestTarget($target); // fetch the correct handler - $drv = $this->getHandler($class); + $drv = Arsse::$obj->get($class); // generate a response if ($req->getMethod() === "HEAD") { // if the request is a HEAD request, we act exactly as if it were a GET request, and simply remove the response body later @@ -105,11 +105,6 @@ class REST { return $this->normalizeResponse($res, $req); } - public function getHandler(string $className): REST\Handler { - // instantiate the API handler - return new $className(); - } - public function apiMatch(string $url): array { $map = $this->apis; // sort the API list so the longest URL prefixes come first diff --git a/lib/REST/AbstractHandler.php b/lib/REST/AbstractHandler.php index f0e39e7..7103c34 100644 --- a/lib/REST/AbstractHandler.php +++ b/lib/REST/AbstractHandler.php @@ -16,9 +16,8 @@ abstract class AbstractHandler implements Handler { abstract public function __construct(); abstract public function dispatch(ServerRequestInterface $req): ResponseInterface; - /** @codeCoverageIgnore */ protected function now(): \DateTimeImmutable { - return Date::normalize("now"); + return Arsse::$obj->get(\DateTimeImmutable::class)->setTimezone(new \DateTimeZone("UTC")); } protected function isAdmin(): bool { diff --git a/lib/REST/Miniflux/V1.php b/lib/REST/Miniflux/V1.php index 00c58f0..e82d590 100644 --- a/lib/REST/Miniflux/V1.php +++ b/lib/REST/Miniflux/V1.php @@ -214,11 +214,6 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { public function __construct() { } - /** @codeCoverageIgnore */ - protected function getInstance(string $class) { - return new $class; - } - protected function authenticate(ServerRequestInterface $req): bool { // first check any tokens; this is what Miniflux does if ($req->hasHeader("X-Auth-Token")) { @@ -1183,7 +1178,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { protected function opmlImport(string $data): ResponseInterface { try { - $this->getInstance(OPML::class)->import(Arsse::$user->id, $data); + Arsse::$obj->get(OPML::class)->import(Arsse::$user->id, $data); } catch (ImportException $e) { switch ($e->getCode()) { case 10611: @@ -1204,7 +1199,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { } protected function opmlExport(): ResponseInterface { - return new GenericResponse($this->getInstance(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]); + return new GenericResponse(Arsse::$obj->get(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]); } public static function tokenGenerate(string $user, string $label): string { diff --git a/tests/cases/CLI/TestCLI.php b/tests/cases/CLI/TestCLI.php index 3d0d6f1..3023775 100644 --- a/tests/cases/CLI/TestCLI.php +++ b/tests/cases/CLI/TestCLI.php @@ -60,22 +60,20 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { public function testStartTheDaemon(): void { $srv = \Phake::mock(Service::class); + \Phake::when(Arsse::$obj)->get(Service::class)->thenReturn($srv); \Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable); - \Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv); $this->assertConsole($this->cli, "arsse.php daemon", 0); \Phake::verify($this->cli)->loadConf; \Phake::verify($srv)->watch(true); - \Phake::verify($this->cli)->getInstance(Service::class); } public function testRefreshAllFeeds(): void { $srv = \Phake::mock(Service::class); + \Phake::when(Arsse::$obj)->get(Service::class)->thenReturn($srv); \Phake::when($srv)->watch->thenReturn(new \DateTimeImmutable); - \Phake::when($this->cli)->getInstance(Service::class)->thenReturn($srv); $this->assertConsole($this->cli, "arsse.php feed refresh-all", 0); \Phake::verify($this->cli)->loadConf; \Phake::verify($srv)->watch(false); - \Phake::verify($this->cli)->getInstance(Service::class); } /** @dataProvider provideFeedUpdates */ @@ -98,10 +96,10 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideDefaultConfigurationSaves */ public function testSaveTheDefaultConfiguration(string $cmd, int $exitStatus, string $file): void { $conf = \Phake::mock(Conf::class); + \Phake::when(Arsse::$obj)->get(Conf::class)->thenReturn($conf); \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($this->cli)->getInstance(Conf::class)->thenReturn($conf); $this->assertConsole($this->cli, $cmd, $exitStatus); \Phake::verify($this->cli, \Phake::times(0))->loadConf; \Phake::verify($conf)->exportFile($file, true); @@ -169,10 +167,10 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { ; })); $fever = \Phake::mock(FeverUser::class); + \Phake::when(Arsse::$obj)->get(FeverUser::class)->thenReturn($fever); \Phake::when($fever)->authenticate->thenReturn(false); \Phake::when($fever)->authenticate("john.doe@example.com", "ashalla")->thenReturn(true); \Phake::when($fever)->authenticate("jane.doe@example.com", "thx1138")->thenReturn(true); - \Phake::when($this->cli)->getInstance(FeverUser::class)->thenReturn($fever); $this->assertConsole($this->cli, $cmd, $exitStatus, $output); } @@ -226,8 +224,8 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { Arsse::$user = $this->createMock(User::class); Arsse::$user->method("passwordSet")->will($this->returnCallback($passwordChange)); $fever = \Phake::mock(FeverUser::class); + \Phake::when(Arsse::$obj)->get(FeverUser::class)->thenReturn($fever); \Phake::when($fever)->register->thenReturnCallback($passwordChange); - \Phake::when($this->cli)->getInstance(FeverUser::class)->thenReturn($fever); $this->assertConsole($this->cli, $cmd, $exitStatus, $output); } @@ -256,8 +254,8 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { Arsse::$user = $this->createMock(User::class); Arsse::$user->method("passwordUnset")->will($this->returnCallback($passwordClear)); $fever = \Phake::mock(FeverUser::class); + \Phake::when(Arsse::$obj)->get(FeverUser::class)->thenReturn($fever); \Phake::when($fever)->unregister->thenReturnCallback($passwordClear); - \Phake::when($this->cli)->getInstance(FeverUser::class)->thenReturn($fever); $this->assertConsole($this->cli, $cmd, $exitStatus, $output); } @@ -273,10 +271,10 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideOpmlExports */ public function testExportToOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat): void { $opml = \Phake::mock(OPML::class); + \Phake::when(Arsse::$obj)->get(OPML::class)->thenReturn($opml); \Phake::when($opml)->exportFile("php://output", $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($this->cli)->getInstance(OPML::class)->thenReturn($opml); $this->assertConsole($this->cli, $cmd, $exitStatus); \Phake::verify($this->cli)->loadConf; \Phake::verify($opml)->exportFile($file, $user, $flat); @@ -314,10 +312,10 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideOpmlImports */ public function testImportFromOpml(string $cmd, int $exitStatus, string $file, string $user, bool $flat, bool $replace): void { $opml = \Phake::mock(OPML::class); + \Phake::when(Arsse::$obj)->get(OPML::class)->thenReturn($opml); \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("bad.opml", $user, $flat, $replace)->thenThrow(new \JKingWeb\Arsse\ImportExport\Exception("fileUnreadable")); - \Phake::when($this->cli)->getInstance(OPML::class)->thenReturn($opml); $this->assertConsole($this->cli, $cmd, $exitStatus); \Phake::verify($this->cli)->loadConf; \Phake::verify($opml)->importFile($file, $user, $flat, $replace); diff --git a/tests/cases/Misc/TestFactory.php b/tests/cases/Misc/TestFactory.php new file mode 100644 index 0000000..e400c2f --- /dev/null +++ b/tests/cases/Misc/TestFactory.php @@ -0,0 +1,17 @@ +assertInstanceOf(\stdClass::class, $f->get(\stdClass::class)); + } +} \ No newline at end of file diff --git a/tests/cases/REST/Miniflux/TestV1.php b/tests/cases/REST/Miniflux/TestV1.php index 22a53db..fc08dea 100644 --- a/tests/cases/REST/Miniflux/TestV1.php +++ b/tests/cases/REST/Miniflux/TestV1.php @@ -211,8 +211,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { throw $u[2]; } }); - $this->h = $this->createPartialMock(V1::class, ["now"]); - $this->h->method("now")->willReturn(Date::normalize(self::NOW)); + \Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); $this->assertMessage($exp, $this->req("GET", $route, "", [], $user)); } @@ -240,8 +239,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideUserModifications */ public function testModifyAUser(bool $admin, string $url, array $body, $in1, $out1, $in2, $out2, $in3, $out3, ResponseInterface $exp): void { - $this->h = $this->createPartialMock(V1::class, ["now"]); - $this->h->method("now")->willReturn(Date::normalize(self::NOW)); + \Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); Arsse::$user = $this->createMock(User::class); Arsse::$user->method("begin")->willReturn($this->transaction); Arsse::$user->method("propertiesGet")->willReturnCallback(function(string $u, bool $includeLarge) use ($admin) { @@ -321,8 +319,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider provideUserAdditions */ public function testAddAUser(array $body, $in1, $out1, $in2, $out2, ResponseInterface $exp): void { - $this->h = $this->createPartialMock(V1::class, ["now"]); - $this->h->method("now")->willReturn(Date::normalize(self::NOW)); + \Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); Arsse::$user = $this->createMock(User::class); Arsse::$user->method("begin")->willReturn($this->transaction); Arsse::$user->method("propertiesGet")->willReturnCallback(function(string $u, bool $includeLarge) { diff --git a/tests/cases/REST/TestREST.php b/tests/cases/REST/TestREST.php index da45893..1864215 100644 --- a/tests/cases/REST/TestREST.php +++ b/tests/cases/REST/TestREST.php @@ -286,14 +286,6 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { ]; } - public function testCreateHandlers(): void { - $r = new REST(); - foreach (REST::API_LIST as $api) { - $class = $api['class']; - $this->assertInstanceOf(Handler::class, $r->getHandler($class)); - } - } - /** @dataProvider provideMockRequests */ public function testDispatchRequests(ServerRequest $req, string $method, bool $called, string $class = "", string $target = ""): void { $r = \Phake::partialMock(REST::class); @@ -305,7 +297,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { }); if ($called) { $h = \Phake::mock($class); - \Phake::when($r)->getHandler($class)->thenReturn($h); + \Phake::when(Arsse::$obj)->get($class)->thenReturn($h); \Phake::when($h)->dispatch->thenReturn(new EmptyResponse(204)); } $out = $r->dispatch($req); diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index 6191d84..874a103 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -1700,8 +1700,7 @@ LONG_STRING; public function testRetrieveHeadlines(bool $full, array $in, $out, Context $c, array $fields, array $order, ResponseInterface $exp): void { $base = ['op' => $full ? "getHeadlines" : "getCompactHeadlines", 'sid' => "PriestsOfSyrinx"]; $in = array_merge($base, $in); - $this->h = \Phake::partialMock(API::class); - \Phake::when($this->h)->now->thenReturn(Date::normalize(self::NOW)); + \Phake::when(Arsse::$obj)->get(\DateTimeImmutable::class)->thenReturn(new \DateTimeImmutable(self::NOW)); \Phake::when(Arsse::$db)->labelList->thenReturn(new Result($this->v($this->labels))); \Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->v($this->usedLabels))); \Phake::when(Arsse::$db)->articleLabelsGet->thenReturn([]); diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php index 54cde18..e096ca1 100644 --- a/tests/lib/AbstractTest.php +++ b/tests/lib/AbstractTest.php @@ -13,6 +13,7 @@ use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Conf; use JKingWeb\Arsse\Db\Driver; use JKingWeb\Arsse\Db\Result; +use JKingWeb\Arsse\Factory; use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\ValueInfo; use JKingWeb\Arsse\Misc\URL; @@ -45,6 +46,11 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { } if ($loadLang) { Arsse::$lang = new \JKingWeb\Arsse\Lang(); + // also create the object factory as a mock + Arsse::$obj = \Phake::mock(Factory::class); + \Phake::when(Arsse::$obj)->get->thenReturnCallback(function(string $class) { + return new $class; + }); } } diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index 0875bf5..3d57606 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -45,6 +45,7 @@ cases/Conf/TestConf.php + cases/Misc/TestFactory.php cases/Misc/TestValueInfo.php cases/Misc/TestDate.php cases/Misc/TestQuery.php