diff --git a/CHANGELOG.md b/CHANGELOG.md index 6137523..7446c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Yii debug API Change Log +# Yii Debug API Change Log ## 1.0.0 under development diff --git a/LICENSE.md b/LICENSE.md index bc5674f..c48bcea 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright © 2008 by Yii Software (https://www.yiiframework.com/) +Copyright © 2008 by Yii Software (<https://www.yiiframework.com/>) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 27deffb..1da8802 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ <p align="center"> <a href="https://github.com/yiisoft" target="_blank"> - <img src="https://github.com/yiisoft.png" height="100px"> + <img src="https://yiisoft.github.io/docs/images/yii_logo.svg" height="100px" alt="Yii"> </a> - <h1 align="center">Yii debug API</h1> + <h1 align="center">Yii Debug API</h1> <br> </p> @@ -22,7 +22,7 @@ This extension provides an API for [Yii Debug](https://github.com/yiisoft/yii-de ## Installation -Add the package to your application: +The package could be installed with [Composer](https://getcomposer.org): ```shell composer require yiisoft/yii-debug-api @@ -33,31 +33,12 @@ composer require yiisoft/yii-debug-api Routes will be registered automatically within Yii application router. You can check if everything is OK by going to `/debug` URL. -## Testing +## Documentation -### Unit testing +- [Internals](docs/internals.md) -The package is tested with [PHPUnit](https://phpunit.de/). To run tests: - -```shell -./vendor/bin/phpunit -``` - -### Mutation testing - -The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it: - -```shell -./vendor/bin/infection -``` - -### Static analysis - -The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis: - -```shell -./vendor/bin/psalm -``` +If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that. +You may also check out other [Yii Community Resources](https://www.yiiframework.com/community). ## License @@ -65,3 +46,15 @@ The Yii Debug API is free software. It is released under the terms of the BSD Li Please see [`LICENSE`](./LICENSE.md) for more information. Maintained by [Yii Software](https://www.yiiframework.com/). + +## Support the project + +[](https://opencollective.com/yiisoft) + +## Follow updates + +[](https://www.yiiframework.com/) +[](https://twitter.com/yiiframework) +[](https://t.me/yii3en) +[](https://www.facebook.com/groups/yiitalk) +[](https://yiiframework.com/go/slack) diff --git a/composer-require-checker.json b/composer-require-checker.json index 23b3346..0528b49 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -4,6 +4,7 @@ "Yiisoft\\Assets\\AssetManager", "Yiisoft\\Assets\\AssetPublisherInterface", "Yiisoft\\Yii\\View\\ViewRenderer", + "Yiisoft\\Yii\\Http\\Application", "Codeception\\Event\\FailEvent", "Codeception\\Event\\PrintResultEvent", "Codeception\\Event\\TestEvent", diff --git a/composer.json b/composer.json index 8fba472..9823507 100644 --- a/composer.json +++ b/composer.json @@ -13,12 +13,22 @@ "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii-debug-api/issues?state=open", + "source": "https://github.com/yiisoft/yii-debug-api", "forum": "https://www.yiiframework.com/forum/", "wiki": "https://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "chat": "https://t.me/yii3en", - "source": "https://github.com/yiisoft/yii-debug-api" + "irc": "ircs://irc.libera.chat:6697/yii", + "chat": "https://t.me/yii3en" }, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/yiisoft" + }, + { + "type": "github", + "url": "https://github.com/sponsors/yiisoft" + } + ], "minimum-stability": "dev", "prefer-stable": true, "require": { @@ -27,9 +37,10 @@ "gitonomy/gitlib": "^1.3", "guzzlehttp/guzzle": "^7.5", "guzzlehttp/psr7": "^2.4", + "httpsoft/http-message": "^1.1", "psr/container": "^2.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0|^2.0", "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", "psr/simple-cache": "^2.0|^3.0", @@ -40,6 +51,7 @@ "yiisoft/di": "^1.0", "yiisoft/friendly-exception": "^1.1", "yiisoft/http": "^1.2", + "yiisoft/middleware-dispatcher": "^5.2", "yiisoft/router": "^3.0", "yiisoft/translator": "^3.0", "yiisoft/var-dumper": "^1.4", @@ -55,12 +67,14 @@ "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^5.22", - "yiisoft/active-record": "3.0.x-dev", + "yiisoft/active-record": "dev-master", "yiisoft/assets": "^4.0", "yiisoft/csrf": "^2.0", + "yiisoft/db": "1.2 as dev-master", "yiisoft/db-sqlite": "^1.0", "yiisoft/psr-dummy-provider": "^1.0", "yiisoft/router-fastroute": "^3.0", + "yiisoft/test-support": "^3.0", "yiisoft/yii-cycle": "dev-master", "yiisoft/yii-view": "^6.0" }, diff --git a/config/di-web.php b/config/di-web.php index 4836e98..1e17aec 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -5,6 +5,8 @@ use Cycle\Database\DatabaseProviderInterface; use Psr\Container\ContainerInterface; use Yiisoft\Db\Connection\ConnectionInterface; +use Yiisoft\Yii\Debug\Api\Debug\Http\HttpApplicationWrapper; +use Yiisoft\Yii\Debug\Api\Debug\Http\RouteCollectorWrapper; use Yiisoft\Yii\Debug\Api\Debug\Repository\CollectorRepository; use Yiisoft\Yii\Debug\Api\Debug\Repository\CollectorRepositoryInterface; use Yiisoft\Yii\Debug\Api\Inspector\Database\Cycle\CycleSchemaProvider; @@ -34,4 +36,14 @@ ) ); }, + HttpApplicationWrapper::class => [ + '__construct()' => [ + 'middlewareDefinitions' => $params['yiisoft/yii-debug-api']['middlewares'], + ], + ], + RouteCollectorWrapper::class => [ + '__construct()' => [ + 'middlewareDefinitions' => $params['yiisoft/yii-debug-api']['middlewares'], + ], + ], ]; diff --git a/config/params.php b/config/params.php index b6c42ba..9630fc8 100644 --- a/config/params.php +++ b/config/params.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Codeception\Extension; +use Yiisoft\Yii\Debug\Api\Debug\Middleware\DebugHeaders; use Yiisoft\Yii\Debug\Api\Inspector\Command\CodeceptionCommand; use Yiisoft\Yii\Debug\Api\Inspector\Command\PHPUnitCommand; use Yiisoft\Yii\Debug\Api\Inspector\Command\PsalmCommand; @@ -26,6 +27,9 @@ 'enabled' => true, 'allowedIPs' => ['127.0.0.1', '::1'], 'allowedHosts' => [], + 'middlewares' => [ + DebugHeaders::class, + ], 'inspector' => [ 'commandMap' => [ 'tests' => $testCommands, diff --git a/docs/internals.md b/docs/internals.md new file mode 100644 index 0000000..087a514 --- /dev/null +++ b/docs/internals.md @@ -0,0 +1,44 @@ +# Internals + +## Unit testing + +The package is tested with [PHPUnit](https://phpunit.de/). To run tests: + +```shell +./vendor/bin/phpunit +``` + +## Mutation testing + +The package tests are checked with [Infection](https://infection.github.io/) mutation framework with +[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it: + +```shell +./vendor/bin/roave-infection-static-analysis-plugin +``` + +## Static analysis + +The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis: + +```shell +./vendor/bin/psalm +``` + +## Code style + +Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or +use either newest or any specific version of PHP: + +```shell +./vendor/bin/rector +``` + +## Dependencies + +This package uses [composer-require-checker](https://github.com/maglnet/ComposerRequireChecker) to check if +all dependencies are correctly defined in `composer.json`. To run the checker, execute the following command: + +```shell +./vendor/bin/composer-require-checker +``` diff --git a/src/Debug/Http/HttpApplicationWrapper.php b/src/Debug/Http/HttpApplicationWrapper.php new file mode 100644 index 0000000..c3b9b5a --- /dev/null +++ b/src/Debug/Http/HttpApplicationWrapper.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Debug\Http; + +use Closure; +use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; +use Yiisoft\Yii\Debug\Api\Debug\Middleware\MiddlewareDispatcherMiddleware; +use Yiisoft\Yii\Http\Application; + +final readonly class HttpApplicationWrapper +{ + public function __construct( + private MiddlewareDispatcher $middlewareDispatcher, + private array $middlewareDefinitions, + ) { + } + + public function wrap(Application $application): void + { + $middlewareDispatcher = $this->middlewareDispatcher; + $middlewareDefinitions = $this->middlewareDefinitions; + + $closure = Closure::bind(static function (Application $application) use ( + $middlewareDispatcher, + $middlewareDefinitions, + ) { + $application->dispatcher = $middlewareDispatcher->withMiddlewares([ + ...$middlewareDefinitions, + ['class' => MiddlewareDispatcherMiddleware::class, '$middlewareDispatcher' => $application->dispatcher], + ]);; + }, null, $application); + + $closure($application); + } +} diff --git a/src/Debug/Http/RouteCollectorWrapper.php b/src/Debug/Http/RouteCollectorWrapper.php new file mode 100644 index 0000000..c31fe75 --- /dev/null +++ b/src/Debug/Http/RouteCollectorWrapper.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Debug\Http; + +use Yiisoft\Router\RouteCollectorInterface; + +final class RouteCollectorWrapper +{ + public function __construct( + private array $middlewareDefinitions, + ) { + } + + public function wrap(RouteCollectorInterface $routeCollector): void + { + $routeCollector->prependMiddleware(...$this->middlewareDefinitions); + } +} diff --git a/src/Debug/Middleware/MiddlewareDispatcherMiddleware.php b/src/Debug/Middleware/MiddlewareDispatcherMiddleware.php new file mode 100644 index 0000000..4cdaf74 --- /dev/null +++ b/src/Debug/Middleware/MiddlewareDispatcherMiddleware.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Debug\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; + +final class MiddlewareDispatcherMiddleware implements MiddlewareInterface +{ + public function __construct( + public MiddlewareDispatcher $middlewareDispatcher + ) { + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $this->middlewareDispatcher->dispatch($request, $handler); + } +} diff --git a/src/Debug/Provider/DebugApiProvider.php b/src/Debug/Provider/DebugApiProvider.php index d62d5be..7de2b6a 100644 --- a/src/Debug/Provider/DebugApiProvider.php +++ b/src/Debug/Provider/DebugApiProvider.php @@ -7,7 +7,9 @@ use Psr\Container\ContainerInterface; use Yiisoft\Di\ServiceProviderInterface; use Yiisoft\Router\RouteCollectorInterface; -use Yiisoft\Yii\Debug\Api\Debug\Middleware\DebugHeaders; +use Yiisoft\Yii\Debug\Api\Debug\Http\HttpApplicationWrapper; +use Yiisoft\Yii\Debug\Api\Debug\Http\RouteCollectorWrapper; +use Yiisoft\Yii\Http\Application; final class DebugApiProvider implements ServiceProviderInterface { @@ -22,10 +24,24 @@ public function getDefinitions(): array public function getExtensions(): array { return [ - RouteCollectorInterface::class => static function (ContainerInterface $container, RouteCollectorInterface $routeCollector) { - $routeCollector->prependMiddleware(DebugHeaders::class); + RouteCollectorInterface::class => static function ( + ContainerInterface $container, + RouteCollectorInterface $routeCollector + ) { + /** + * Register debug middlewares twice because a `Subfolder` middleware may rewrite base URL + */ + $routerCollectionWrapper = $container->get(RouteCollectorWrapper::class); + $routerCollectionWrapper->wrap($routeCollector); + return $routeCollector; }, + Application::class => static function (ContainerInterface $container, Application $application) { + $applicationWrapper = $container->get(HttpApplicationWrapper::class); + $applicationWrapper->wrap($application); + + return $application; + }, ]; } } diff --git a/src/Debug/Repository/CollectorRepository.php b/src/Debug/Repository/CollectorRepository.php index e37324b..777963e 100644 --- a/src/Debug/Repository/CollectorRepository.php +++ b/src/Debug/Repository/CollectorRepository.php @@ -38,7 +38,7 @@ public function getObject(string $id, string $objectId): array|null $dump = $this->loadData(StorageInterface::TYPE_OBJECTS, $id); foreach ($dump as $name => $value) { - if (($pos = strrpos($name, "#$objectId")) !== false) { + if (($pos = strrpos((string)$name, "#$objectId")) !== false) { return [substr($name, 0, $pos), $value]; } } diff --git a/src/Inspector/ApplicationState.php b/src/Inspector/ApplicationState.php index 9996b20..1b73fd2 100644 --- a/src/Inspector/ApplicationState.php +++ b/src/Inspector/ApplicationState.php @@ -4,7 +4,10 @@ namespace Yiisoft\Yii\Debug\Api\Inspector; -class ApplicationState +/** + * @internal + */ +final class ApplicationState { - public static $params; + public static array $params = []; } diff --git a/src/Inspector/Command/BashCommand.php b/src/Inspector/Command/BashCommand.php index 357e056..9485c99 100644 --- a/src/Inspector/Command/BashCommand.php +++ b/src/Inspector/Command/BashCommand.php @@ -38,7 +38,7 @@ public function run(): CommandResponse ->setTimeout(null) ->run(); - $processOutput = $process->getOutput(); + $processOutput = rtrim($process->getOutput()); if (!$process->getExitCode() > 1) { return new CommandResponse( diff --git a/src/Inspector/Controller/InspectController.php b/src/Inspector/Controller/InspectController.php index 860b3d0..cad40ef 100644 --- a/src/Inspector/Controller/InspectController.php +++ b/src/Inspector/Controller/InspectController.php @@ -307,7 +307,7 @@ public function routes(RouteCollectionInterface $routeCollection): ResponseInter 'methods' => $data['methods'], 'defaults' => $data['defaults'], 'override' => $data['override'], - 'middlewares' => $data['middlewareDefinitions'], + 'middlewares' => $data['middlewareDefinitions'] ?? [], ]; } $response = VarDumper::create($routes)->asPrimitives(5); diff --git a/tests/Support/Application/fail.sh b/tests/Support/Application/fail.sh new file mode 100644 index 0000000..4bbf6fa --- /dev/null +++ b/tests/Support/Application/fail.sh @@ -0,0 +1,2 @@ +echo 'failed' +exit $1 diff --git a/tests/Support/StubCollector.php b/tests/Support/StubCollector.php new file mode 100644 index 0000000..06abc04 --- /dev/null +++ b/tests/Support/StubCollector.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Support; + +use Yiisoft\Yii\Debug\Collector\CollectorInterface; + +final class StubCollector implements CollectorInterface +{ + public function __construct(private array $data = []) + { + } + + public function getName(): string + { + return 'stub'; + } + + public function startup(): void + { + } + + public function shutdown(): void + { + } + + public function getCollected(): array + { + return $this->data; + } +} diff --git a/tests/Unit/Debug/Middleware/DebugHeadersTest.php b/tests/Unit/Debug/Middleware/DebugHeadersTest.php new file mode 100644 index 0000000..f86459a --- /dev/null +++ b/tests/Unit/Debug/Middleware/DebugHeadersTest.php @@ -0,0 +1,43 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Debug\Middleware; + +use HttpSoft\Message\Response; +use HttpSoft\Message\ServerRequest; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Yiisoft\Router\UrlGeneratorInterface; +use Yiisoft\Yii\Debug\Api\Debug\Middleware\DebugHeaders; +use Yiisoft\Yii\Debug\DebuggerIdGenerator; + +final class DebugHeadersTest extends TestCase +{ + public function testHeaders(): void + { + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator->method('generate')->willReturnCallback( + fn (string $route, array $parameters) => $route . '?' . http_build_query($parameters) + ); + $idGenerator = new DebuggerIdGenerator(); + $expectedId = $idGenerator->getId(); + + $middleware = new DebugHeaders($idGenerator, $urlGenerator); + $response = $middleware->process(new ServerRequest(), $this->createRequestHandler()); + + $this->assertSame($expectedId, $response->getHeaderLine('X-Debug-Id')); + $this->assertSame('debug/api/view?id=' . $expectedId, $response->getHeaderLine('X-Debug-Link')); + } + + protected function createRequestHandler(): RequestHandlerInterface + { + return new class () implements RequestHandlerInterface { + public function handle($request): ResponseInterface + { + return new Response(200); + } + }; + } +} diff --git a/tests/Unit/Debug/Middleware/ResponseDataWrapperTest.php b/tests/Unit/Debug/Middleware/ResponseDataWrapperTest.php new file mode 100644 index 0000000..634cc66 --- /dev/null +++ b/tests/Unit/Debug/Middleware/ResponseDataWrapperTest.php @@ -0,0 +1,138 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Debug\Middleware; + +use HttpSoft\Message\Response; +use HttpSoft\Message\ResponseFactory; +use HttpSoft\Message\ServerRequest; +use HttpSoft\Message\StreamFactory; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Throwable; +use Yiisoft\DataResponse\DataResponse; +use Yiisoft\DataResponse\DataResponseFactory; +use Yiisoft\Router\CurrentRoute; +use Yiisoft\Yii\Debug\Api\Debug\Exception\NotFoundException; +use Yiisoft\Yii\Debug\Api\Debug\Middleware\ResponseDataWrapper; + +final class ResponseDataWrapperTest extends TestCase +{ + public function testNotDataResponse(): void + { + $middleware = $this->createMiddleware(); + $response = $middleware->process(new ServerRequest(), $this->createRequestHandler(new Response(200))); + + $this->assertInstanceOf(ResponseInterface::class, $response); + } + + public function testDataResponse(): void + { + $controllerRawResponse = ['id' => 1, 'name' => 'User name']; + $factory = $this->createDataResponseFactory(); + $response = $factory->createResponse($controllerRawResponse); + + $middleware = $this->createMiddleware(); + $response = $middleware->process(new ServerRequest(), $this->createRequestHandler($response)); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertInstanceOf(DataResponse::class, $response); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals([ + 'id' => null, + 'data' => $controllerRawResponse, + 'error' => null, + 'success' => true, + ], $response->getData()); + } + + public function testDataResponseErrorStatus(): void + { + $controllerRawResponse = ['id' => 1, 'name' => 'User name']; + $factory = $this->createDataResponseFactory(); + $response = $factory->createResponse($controllerRawResponse, 400); + + $middleware = $this->createMiddleware(); + $response = $middleware->process(new ServerRequest(), $this->createRequestHandler($response)); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertInstanceOf(DataResponse::class, $response); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals([ + 'id' => null, + 'data' => $controllerRawResponse, + 'error' => null, + 'success' => false, + ], $response->getData()); + } + + public function testDataResponseException(): void + { + $errorMessage = 'Test exception'; + $middleware = $this->createMiddleware(); + $response = $middleware->process( + new ServerRequest(), + $this->createExceptionRequestHandler(new NotFoundException($errorMessage)) + ); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertInstanceOf(DataResponse::class, $response); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals([ + 'id' => null, + 'data' => null, + 'error' => $errorMessage, + 'success' => false, + ], $response->getData()); + } + + private function createRequestHandler(ResponseInterface $response): RequestHandlerInterface + { + return new class ($response) implements RequestHandlerInterface { + public function __construct( + private ResponseInterface $response, + ) { + } + + public function handle($request): ResponseInterface + { + return $this->response; + } + }; + } + + private function createExceptionRequestHandler(Throwable $exception): RequestHandlerInterface + { + return new class ($exception) implements RequestHandlerInterface { + public function __construct( + private Throwable $exception, + ) { + } + + public function handle($request): ResponseInterface + { + throw $this->exception; + } + }; + } + + private function createMiddleware(): ResponseDataWrapper + { + $factory = $this->createDataResponseFactory(); + $currentRoute = new CurrentRoute(); + return new ResponseDataWrapper($factory, $currentRoute); + } + + private function createDataResponseFactory(): DataResponseFactory + { + return new DataResponseFactory( + new ResponseFactory(), + new StreamFactory(), + ); + } +} diff --git a/tests/Unit/Debug/Provider/DebugApiProviderTest.php b/tests/Unit/Debug/Provider/DebugApiProviderTest.php new file mode 100644 index 0000000..1d26116 --- /dev/null +++ b/tests/Unit/Debug/Provider/DebugApiProviderTest.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Debug\Provider; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Yiisoft\Router\RouteCollectorInterface; +use Yiisoft\Yii\Debug\Api\Debug\Http\RouteCollectorWrapper; +use Yiisoft\Yii\Debug\Api\Debug\Middleware\DebugHeaders; +use Yiisoft\Yii\Debug\Api\Debug\Provider\DebugApiProvider; + +final class DebugApiProviderTest extends TestCase +{ + public function testExtension(): void + { + $provider = new DebugApiProvider(); + + $this->assertIsArray($provider->getDefinitions()); + $this->assertIsArray($provider->getExtensions()); + $this->assertEmpty($provider->getDefinitions()); + + $extensions = $provider->getExtensions(); + $this->assertArrayHasKey(RouteCollectorInterface::class, $extensions); + + $routeCollectorDecorator = $extensions[RouteCollectorInterface::class]; + $this->assertIsCallable($routeCollectorDecorator); + + $middlewares = [DebugHeaders::class]; + + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->once()) + ->method('get') + ->with(RouteCollectorWrapper::class) + ->willReturn(new RouteCollectorWrapper($middlewares)); + + $routeCollector = $this->createMock(RouteCollectorInterface::class); + $routeCollector->expects($this->once()) + ->method('prependMiddleware') + ->with(...$middlewares) + ->willReturn($routeCollector); + + $this->assertSame($routeCollector, $routeCollectorDecorator($container, $routeCollector)); + } +} diff --git a/tests/Unit/Debug/Repository/CollectorRepositoryTest.php b/tests/Unit/Debug/Repository/CollectorRepositoryTest.php new file mode 100644 index 0000000..b62153b --- /dev/null +++ b/tests/Unit/Debug/Repository/CollectorRepositoryTest.php @@ -0,0 +1,100 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Debug\Repository; + +use PHPUnit\Framework\TestCase; +use Yiisoft\Yii\Debug\Api\Debug\Repository\CollectorRepository; +use Yiisoft\Yii\Debug\Api\Tests\Support\StubCollector; +use Yiisoft\Yii\Debug\DebuggerIdGenerator; +use Yiisoft\Yii\Debug\Storage\MemoryStorage; +use Yiisoft\Yii\Debug\Storage\StorageInterface; + +final class CollectorRepositoryTest extends TestCase +{ + public function testSummary(): void + { + $idGenerator = new DebuggerIdGenerator(); + $stubCollector = new StubCollector(['key' => 'value']); + + $storage = $this->createStorage($idGenerator); + $repository = new CollectorRepository($storage); + + $this->assertIsArray($repository->getSummary()); + $this->assertEquals([ + [ + 'id' => $idGenerator->getId(), + 'collectors' => [], + ], + ], $repository->getSummary()); + + $storage->addCollector($stubCollector); + + $this->assertIsArray($repository->getSummary()); + $this->assertEquals([ + [ + 'id' => $idGenerator->getId(), + 'collectors' => [$stubCollector->getName()], + ], + ], $repository->getSummary()); + } + + public function testDetail(): void + { + $idGenerator = new DebuggerIdGenerator(); + $stubCollector = new StubCollector(['key' => 'value']); + + $storage = $this->createStorage($idGenerator); + $storage->addCollector($stubCollector); + + $repository = new CollectorRepository($storage); + + $this->assertIsArray($repository->getDetail($idGenerator->getId())); + $this->assertEquals([ + $stubCollector->getName() => $stubCollector->getCollected(), + ], $repository->getDetail($idGenerator->getId())); + } + + public function testDumpObject(): void + { + $idGenerator = new DebuggerIdGenerator(); + $stubCollector = new StubCollector(['key' => 'value']); + + $storage = $this->createStorage($idGenerator); + $storage->addCollector($stubCollector); + + $repository = new CollectorRepository($storage); + + $this->assertIsArray($repository->getDumpObject($idGenerator->getId())); + $this->assertEquals([ + 'key' => 'value', + ], $repository->getDumpObject($idGenerator->getId())); + } + + public function testObject(): void + { + $idGenerator = new DebuggerIdGenerator(); + + $objectId = '123'; + $stubCollector = new StubCollector([ + 'stdClass#' . $objectId => 'value', + ]); + + $storage = $this->createStorage($idGenerator); + $storage->addCollector($stubCollector); + + $repository = new CollectorRepository($storage); + + $this->assertIsArray($repository->getObject($idGenerator->getId(), $objectId)); + $this->assertEquals([ + 'stdClass', + 'value', + ], $repository->getObject($idGenerator->getId(), $objectId)); + } + + private function createStorage(DebuggerIdGenerator $idGenerator): StorageInterface + { + return new MemoryStorage($idGenerator); + } +} diff --git a/tests/Unit/Inspector/ApplicationStateTest.php b/tests/Unit/Inspector/ApplicationStateTest.php new file mode 100644 index 0000000..2b8aed5 --- /dev/null +++ b/tests/Unit/Inspector/ApplicationStateTest.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Inspector; + +use PHPUnit\Framework\TestCase; +use Yiisoft\Yii\Debug\Api\Inspector\ApplicationState; + +final class ApplicationStateTest extends TestCase +{ + public function testStatus(): void + { + $this->assertEquals([], ApplicationState::$params); + + ApplicationState::$params = ['key' => 'value']; + $this->assertEquals(['key' => 'value'], ApplicationState::$params); + } +} diff --git a/tests/Unit/Inspector/Command/BashCommandTest.php b/tests/Unit/Inspector/Command/BashCommandTest.php new file mode 100644 index 0000000..5a327f7 --- /dev/null +++ b/tests/Unit/Inspector/Command/BashCommandTest.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Inspector\Command; + +use PHPUnit\Framework\TestCase; +use Yiisoft\Aliases\Aliases; +use Yiisoft\Yii\Debug\Api\Inspector\Command\BashCommand; +use Yiisoft\Yii\Debug\Api\Inspector\CommandResponse; + +final class BashCommandTest extends TestCase +{ + public function testSuccess(): void + { + $aliases = new Aliases([ + '@root' => __DIR__, + ]); + $command = new BashCommand($aliases, ['echo', 'test']); + + $response = $command->run(); + + $this->assertSame(CommandResponse::STATUS_OK, $response->getStatus()); + $this->assertSame('test', $response->getResult()); + $this->assertSame([], $response->getErrors()); + } + + public function testError(): void + { + $aliases = new Aliases([ + '@root' => dirname(__DIR__, 3) . '/Support/Application', + ]); + $command = new BashCommand($aliases, ['bash', 'fail.sh', '1']); + + $response = $command->run(); + + $this->assertSame(CommandResponse::STATUS_ERROR, $response->getStatus()); + $this->assertSame('failed', $response->getResult()); + $this->assertSame([], $response->getErrors()); + } +} diff --git a/tests/Unit/Inspector/CommandResponseTest.php b/tests/Unit/Inspector/CommandResponseTest.php new file mode 100644 index 0000000..68b6428 --- /dev/null +++ b/tests/Unit/Inspector/CommandResponseTest.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Inspector; + +use PHPUnit\Framework\TestCase; +use Yiisoft\Yii\Debug\Api\Inspector\CommandResponse; + +final class CommandResponseTest extends TestCase +{ + public function testStatus(): void + { + $response = new CommandResponse(CommandResponse::STATUS_OK, 'result', ['errors']); + + $this->assertSame(CommandResponse::STATUS_OK, $response->getStatus()); + $this->assertSame('result', $response->getResult()); + $this->assertSame(['errors'], $response->getErrors()); + } +} diff --git a/tests/Inspector/Database/DbSchemaProviderTest.php b/tests/Unit/Inspector/Database/DbSchemaProviderTest.php similarity index 98% rename from tests/Inspector/Database/DbSchemaProviderTest.php rename to tests/Unit/Inspector/Database/DbSchemaProviderTest.php index 0584e09..5a00506 100644 --- a/tests/Inspector/Database/DbSchemaProviderTest.php +++ b/tests/Unit/Inspector/Database/DbSchemaProviderTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Debug\Api\Tests\Inspector\Database; +namespace Yiisoft\Yii\Debug\Api\Tests\Unit\Inspector\Database; use PHPUnit\Framework\TestCase; use Yiisoft\Cache\NullCache;