diff --git a/composer.json b/composer.json index c4d91da..1537463 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "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", @@ -65,12 +66,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/src/Debug/Provider/DebugApiProvider.php b/src/Debug/Provider/DebugApiProvider.php index d62d5be..2a0977a 100644 --- a/src/Debug/Provider/DebugApiProvider.php +++ b/src/Debug/Provider/DebugApiProvider.php @@ -4,7 +4,6 @@ namespace Yiisoft\Yii\Debug\Api\Debug\Provider; -use Psr\Container\ContainerInterface; use Yiisoft\Di\ServiceProviderInterface; use Yiisoft\Router\RouteCollectorInterface; use Yiisoft\Yii\Debug\Api\Debug\Middleware\DebugHeaders; @@ -22,7 +21,7 @@ public function getDefinitions(): array public function getExtensions(): array { return [ - RouteCollectorInterface::class => static function (ContainerInterface $container, RouteCollectorInterface $routeCollector) { + RouteCollectorInterface::class => static function (RouteCollectorInterface $routeCollector) { $routeCollector->prependMiddleware(DebugHeaders::class); return $routeCollector; }, 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/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 @@ +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 @@ +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 @@ +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..0ca8861 --- /dev/null +++ b/tests/Unit/Debug/Provider/DebugApiProviderTest.php @@ -0,0 +1,36 @@ +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); + + $routeCollector = $this->createMock(RouteCollectorInterface::class); + $routeCollector->expects($this->once()) + ->method('prependMiddleware') + ->with(DebugHeaders::class) + ->willReturn($routeCollector); + + $this->assertSame($routeCollector, $routeCollectorDecorator($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 @@ + '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 @@ +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 @@ + __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 @@ +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;