From 9b170b2fb8bcda9fa36e80302e88690cc99a977a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D?= Date: Wed, 8 May 2024 19:58:15 +0200 Subject: [PATCH] test(tests): UT for JsonResponse class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén D --- .../web/Controllers/ControllerBase.php | 41 ++-- .../web/Controllers/SimpleControllerBase.php | 6 +- .../UserPassReset/IndexController.php | 2 +- .../UserPassReset/ResetController.php | 2 +- .../UserPassReset/UserPassResetSaveBase.php | 2 +- .../Domain/Api/Services/JsonRpcResponse.php | 16 +- .../Domain/Http/Ports/JsonResponseService.php | 4 +- lib/SP/Domain/Http/Services/JsonResponse.php | 45 +---- lib/SP/Mvc/Controller/ControllerTrait.php | 18 +- .../Mvc/Controller/SimpleControllerHelper.php | 12 +- lib/SP/Mvc/Controller/WebControllerHelper.php | 40 ++-- .../Mvc/View/Components/SelectItemAdapter.php | 7 +- .../Domain/Http/Services/JsonResponseTest.php | 185 ++++++++++++++++++ 13 files changed, 264 insertions(+), 116 deletions(-) create mode 100644 tests/SP/Domain/Http/Services/JsonResponseTest.php diff --git a/app/modules/web/Controllers/ControllerBase.php b/app/modules/web/Controllers/ControllerBase.php index 88237cd8..c947a890 100644 --- a/app/modules/web/Controllers/ControllerBase.php +++ b/app/modules/web/Controllers/ControllerBase.php @@ -24,8 +24,6 @@ namespace SP\Modules\Web\Controllers; -defined('APP_ROOT') || die(); - use Exception; use SP\Core\Application; use SP\Core\Bootstrap\BootstrapBase; @@ -36,9 +34,9 @@ use SP\Domain\Auth\Services\AuthException; use SP\Domain\Config\Ports\ConfigDataInterface; use SP\Domain\Config\Ports\ConfigFileService; use SP\Domain\Core\Acl\AclInterface; +use SP\Domain\Core\Bootstrap\RouteContextData; use SP\Domain\Core\Bootstrap\UriContextInterface; use SP\Domain\Core\Context\SessionContext; -use SP\Domain\Core\Exceptions\FileNotFoundException; use SP\Domain\Core\Exceptions\SessionTimeout; use SP\Domain\Core\Exceptions\SPException; use SP\Domain\Core\PhpExtensionCheckerService; @@ -51,7 +49,6 @@ use SP\Modules\Web\Controllers\Traits\WebControllerTrait; use SP\Mvc\Controller\WebControllerHelper; use SP\Mvc\View\TemplateInterface; -use function SP\__; use function SP\logger; use function SP\processException; @@ -70,22 +67,23 @@ abstract class ControllerBase protected readonly ThemeInterface $theme; protected readonly AclInterface $acl; protected readonly ConfigDataInterface $configData; - protected readonly RequestService $request; + protected readonly RequestService $request; protected readonly PhpExtensionCheckerService $extensionChecker; protected readonly TemplateInterface $view; protected readonly LayoutHelper $layoutHelper; protected readonly UriContextInterface $uriContext; - protected ?UserDataDto $userData = null; - protected ?ProfileData $userProfileData = null; - protected bool $isAjax; - protected string $actionName; + protected readonly ?UserDataDto $userData; + protected readonly ProfileData $userProfileData; + protected readonly bool $isAjax; + protected readonly string $actionName; + protected readonly RouteContextData $routeContextData; + protected readonly string $controllerName; private readonly BrowserAuthService $browser; - public function __construct( - Application $application, - WebControllerHelper $webControllerHelper - ) { - $this->controllerName = $this->getControllerName(); + public function __construct(Application $application, WebControllerHelper $webControllerHelper) + { + $this->routeContextData = $webControllerHelper->getRouteContextData(); + $this->controllerName = $this->routeContextData->getController(); $this->config = $application->getConfig(); $this->configData = $this->config->getConfigData(); $this->eventDispatcher = $application->getEventDispatcher(); @@ -125,8 +123,9 @@ abstract class ControllerBase $this->view->assign('timeStart', $this->request->getServer('REQUEST_TIME_FLOAT')); $this->view->assign('queryTimeStart', microtime()); $this->view->assign('isDemo', $this->configData->isDemoEnabled()); - $this->view->assign('themeUri', $this->view->getTheme()->getUri()); + $this->view->assign('themeUri', $this->theme->getUri()); $this->view->assign('configData', $this->configData); + $this->view->assign('action', $this->actionName); if ($loggedIn) { $this->view->assignWithScope('userId', $this->userData->getId(), 'ctx'); @@ -134,8 +133,6 @@ abstract class ControllerBase $this->view->assignWithScope('userIsAdminApp', $this->userData->getIsAdminApp(), 'ctx'); $this->view->assignWithScope('userIsAdminAcc', $this->userData->getIsAdminAcc(), 'ctx'); } - - $this->view->assign('action', $this->actionName); } /** @@ -143,13 +140,7 @@ abstract class ControllerBase */ protected function view(): void { - try { - $this->router->response()->body($this->view->render())->send(); - } catch (FileNotFoundException $e) { - processException($e); - - $this->router->response()->body(__($e->getMessage()))->send(true); - } + $this->router->response()->body($this->view->render())->send(); } /** @@ -166,7 +157,7 @@ abstract class ControllerBase protected function upgradeView(?string $page = null): void { $this->view->upgrade(); - $this->view->assign('contentPage', $page ?: strtolower($this->getViewBaseName())); + $this->view->assign('contentPage', $page ?: strtolower($this->routeContextData->getActionName())); try { $this->layoutHelper->getFullLayout('main', $this->acl); diff --git a/app/modules/web/Controllers/SimpleControllerBase.php b/app/modules/web/Controllers/SimpleControllerBase.php index 64bcc23b..e2d588fd 100644 --- a/app/modules/web/Controllers/SimpleControllerBase.php +++ b/app/modules/web/Controllers/SimpleControllerBase.php @@ -31,6 +31,7 @@ use SP\Core\PhpExtensionChecker; use SP\Domain\Config\Ports\ConfigDataInterface; use SP\Domain\Config\Services\ConfigFile; use SP\Domain\Core\Acl\UnauthorizedPageException; +use SP\Domain\Core\Bootstrap\RouteContextData; use SP\Domain\Core\Bootstrap\UriContextInterface; use SP\Domain\Core\Context\Context; use SP\Domain\Core\Exceptions\SessionTimeout; @@ -56,6 +57,8 @@ abstract class SimpleControllerBase protected readonly PhpExtensionChecker $extensionChecker; protected readonly ConfigDataInterface $configData; protected readonly UriContextInterface $uriContext; + protected readonly RouteContextData $routeContextData; + protected string $controllerName; /** * @throws SessionTimeout @@ -70,7 +73,8 @@ abstract class SimpleControllerBase $this->request = $simpleControllerHelper->getRequest(); $this->extensionChecker = $simpleControllerHelper->getExtensionChecker(); $this->uriContext = $simpleControllerHelper->getUriContext(); - $this->controllerName = $this->getControllerName(); + $this->routeContextData = $simpleControllerHelper->getRouteContextData(); + $this->controllerName = $this->routeContextData->getController(); $this->config = $application->getConfig(); $this->configData = $this->config->getConfigData(); $this->eventDispatcher = $application->getEventDispatcher(); diff --git a/app/modules/web/Controllers/UserPassReset/IndexController.php b/app/modules/web/Controllers/UserPassReset/IndexController.php index d5e1d1d3..980e1df5 100644 --- a/app/modules/web/Controllers/UserPassReset/IndexController.php +++ b/app/modules/web/Controllers/UserPassReset/IndexController.php @@ -36,7 +36,7 @@ final class IndexController extends ControllerBase { public function indexAction(): void { - $this->layoutHelper->getCustomLayout('request', strtolower($this->getViewBaseName())); + $this->layoutHelper->getCustomLayout('request', strtolower($this->routeContextData->getActionName())); if (!$this->configData->isMailEnabled()) { ErrorUtil::showErrorInView($this->view, self::ERR_UNAVAILABLE, true, 'request'); diff --git a/app/modules/web/Controllers/UserPassReset/ResetController.php b/app/modules/web/Controllers/UserPassReset/ResetController.php index 72a7a24e..dd595a9b 100644 --- a/app/modules/web/Controllers/UserPassReset/ResetController.php +++ b/app/modules/web/Controllers/UserPassReset/ResetController.php @@ -39,7 +39,7 @@ final class ResetController extends ControllerBase */ public function resetAction(?string $hash = null): void { - $this->layoutHelper->getCustomLayout('reset', strtolower($this->getViewBaseName())); + $this->layoutHelper->getCustomLayout('reset', strtolower($this->routeContextData->getActionName())); if ($hash !== null && $this->configData->isMailEnabled()) { $this->view->assign('hash', $hash); diff --git a/app/modules/web/Controllers/UserPassReset/UserPassResetSaveBase.php b/app/modules/web/Controllers/UserPassReset/UserPassResetSaveBase.php index 6d28c28b..41ba7bc4 100644 --- a/app/modules/web/Controllers/UserPassReset/UserPassResetSaveBase.php +++ b/app/modules/web/Controllers/UserPassReset/UserPassResetSaveBase.php @@ -70,7 +70,7 @@ abstract class UserPassResetSaveBase extends ControllerBase $this->mailService = $mailService; $this->trackService = $trackService; - $this->trackRequest = $this->trackService->buildTrackRequest($this->getViewBaseName()); + $this->trackRequest = $this->trackService->buildTrackRequest($this->routeContextData->getActionName()); } /** diff --git a/lib/SP/Domain/Api/Services/JsonRpcResponse.php b/lib/SP/Domain/Api/Services/JsonRpcResponse.php index 33e93d1d..5d5230fb 100644 --- a/lib/SP/Domain/Api/Services/JsonRpcResponse.php +++ b/lib/SP/Domain/Api/Services/JsonRpcResponse.php @@ -1,4 +1,5 @@ '2.0', - 'result' => $apiResponse->getResponse(), - 'id' => $id, - ], JSON_UNESCAPED_SLASHES); + return Serde::serializeJson( + [ + 'jsonrpc' => '2.0', + 'result' => $apiResponse->getResponse(), + 'id' => $id, + ], + JSON_UNESCAPED_SLASHES + ); } public static function getResponseException(Exception $e, int $id): string diff --git a/lib/SP/Domain/Http/Ports/JsonResponseService.php b/lib/SP/Domain/Http/Ports/JsonResponseService.php index 71939d20..7763a21d 100644 --- a/lib/SP/Domain/Http/Ports/JsonResponseService.php +++ b/lib/SP/Domain/Http/Ports/JsonResponseService.php @@ -34,7 +34,7 @@ use SP\Domain\Http\Dtos\JsonMessage; interface JsonResponseService { /** - * Devuelve una respuesta en formato JSON + * Return a response with JSON headers * * @param string $data JSON string * @@ -43,7 +43,7 @@ interface JsonResponseService public function sendRaw(string $data): bool; /** - * Devuelve una respuesta en formato JSON con el estado y el mensaje. + * Return a JSON formatted response * * @param JsonMessage $jsonMessage * diff --git a/lib/SP/Domain/Http/Services/JsonResponse.php b/lib/SP/Domain/Http/Services/JsonResponse.php index 1795a266..5d4f3f12 100644 --- a/lib/SP/Domain/Http/Services/JsonResponse.php +++ b/lib/SP/Domain/Http/Services/JsonResponse.php @@ -1,4 +1,5 @@ response->header(Header::CONTENT_TYPE->value, Header::CONTENT_TYPE_JSON->value); try { - $this->response->body(self::buildJsonFrom($jsonMessage)); + $this->response->body(Serde::serializeJson($jsonMessage)); } catch (SPException $e) { $jsonMessage = new JsonMessage($e->getMessage()); - $jsonMessage->addMessage($e->getHint()); - $this->response->body(self::buildJsonFrom($jsonMessage)); + if ($e->getHint()) { + $jsonMessage->addMessage($e->getHint()); + } + + $this->response->body(Serde::serializeJson($jsonMessage)); } return $this->response->send(true)->isSent(); } - - /** - * Devuelve una cadena en formato JSON - * - * @param mixed $data - * @param int $flags JSON_* flags - * - * @return string - * @throws SPException - */ - public static function buildJsonFrom(mixed $data, int $flags = 0): string - { - try { - return json_encode($data, JSON_THROW_ON_ERROR | $flags); - } catch (JsonException $e) { - throw new SPException(__u('Encoding error'), SPException::ERROR, $e->getMessage()); - } - } } diff --git a/lib/SP/Mvc/Controller/ControllerTrait.php b/lib/SP/Mvc/Controller/ControllerTrait.php index a4afde70..5a2cb4bd 100644 --- a/lib/SP/Mvc/Controller/ControllerTrait.php +++ b/lib/SP/Mvc/Controller/ControllerTrait.php @@ -27,7 +27,6 @@ declare(strict_types=1); namespace SP\Mvc\Controller; use Closure; -use JetBrains\PhpStorm\NoReturn; use Klein\Klein; use SP\Domain\Config\Ports\ConfigDataInterface; use SP\Domain\Core\Exceptions\SPException; @@ -44,20 +43,7 @@ use function SP\processException; */ trait ControllerTrait { - protected Klein $router; - protected string $controllerName; - - protected function getControllerName(): string - { - $class = static::class; - - return substr($class, strrpos($class, '\\') + 1, -strlen('Controller')) ?: ''; - } - - protected function getViewBaseName(): string - { - return strtolower(array_slice(explode('\\', static::class), -2, 1)[0]); - } + protected Klein $router; /** * Logout from current session @@ -110,7 +96,7 @@ trait ControllerTrait /** * Realiza el proceso de logout. */ - #[NoReturn] private static function logout(): void + private static function logout(): never { exit(''); } diff --git a/lib/SP/Mvc/Controller/SimpleControllerHelper.php b/lib/SP/Mvc/Controller/SimpleControllerHelper.php index 3de01eab..bcbe098c 100644 --- a/lib/SP/Mvc/Controller/SimpleControllerHelper.php +++ b/lib/SP/Mvc/Controller/SimpleControllerHelper.php @@ -1,4 +1,5 @@ uriContext; } + + public function getRouteContextData(): RouteContextData + { + return $this->routeContextData; + } } diff --git a/lib/SP/Mvc/Controller/WebControllerHelper.php b/lib/SP/Mvc/Controller/WebControllerHelper.php index 0f80ef1b..850aebcc 100644 --- a/lib/SP/Mvc/Controller/WebControllerHelper.php +++ b/lib/SP/Mvc/Controller/WebControllerHelper.php @@ -1,4 +1,5 @@ theme = $simpleControllerHelper->getTheme(); - $this->router = $simpleControllerHelper->getRouter(); - $this->acl = $simpleControllerHelper->getAcl(); - $this->request = $simpleControllerHelper->getRequest(); - $this->extensionChecker = $simpleControllerHelper->getExtensionChecker(); - $this->uriContext = $simpleControllerHelper->getUriContext(); } public function getTheme(): ThemeInterface { - return $this->theme; + return $this->simpleControllerHelper->getTheme(); } public function getRouter(): Klein { - return $this->router; + return $this->simpleControllerHelper->getRouter(); } public function getAcl(): AclInterface { - return $this->acl; + return $this->simpleControllerHelper->getAcl(); } public function getRequest(): RequestService { - return $this->request; + return $this->simpleControllerHelper->getRequest(); } public function getExtensionChecker(): PhpExtensionChecker { - return $this->extensionChecker; + return $this->simpleControllerHelper->getExtensionChecker(); } public function getUriContext(): UriContextInterface { - return $this->uriContext; + return $this->simpleControllerHelper->getUriContext(); } public function getTemplate(): TemplateInterface @@ -105,4 +94,9 @@ final readonly class WebControllerHelper { return $this->layoutHelper; } + + public function getRouteContextData(): RouteContextData + { + return $this->simpleControllerHelper->getRouteContextData(); + } } diff --git a/lib/SP/Mvc/View/Components/SelectItemAdapter.php b/lib/SP/Mvc/View/Components/SelectItemAdapter.php index c30a5a9d..42807973 100644 --- a/lib/SP/Mvc/View/Components/SelectItemAdapter.php +++ b/lib/SP/Mvc/View/Components/SelectItemAdapter.php @@ -1,4 +1,5 @@ ['id' => $item->getId(), 'name' => $item->getName()], array_filter($this->items, static fn(mixed $item) => $item instanceof ItemWithIdAndNameModel) @@ -86,7 +87,7 @@ final readonly class SelectItemAdapter implements ItemAdapterInterface $out[] = ['id' => $key, 'name' => $value]; } - return JsonResponse::buildJsonFrom($out); + return Serde::serializeJson($out); } /** diff --git a/tests/SP/Domain/Http/Services/JsonResponseTest.php b/tests/SP/Domain/Http/Services/JsonResponseTest.php new file mode 100644 index 00000000..42d847df --- /dev/null +++ b/tests/SP/Domain/Http/Services/JsonResponseTest.php @@ -0,0 +1,185 @@ +. + */ + +declare(strict_types=1); + +namespace SP\Tests\Domain\Http\Services; + +use Klein\Response; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf; +use PHPUnit\Framework\TestCase; +use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Http\Dtos\JsonMessage; +use SP\Domain\Http\Services\JsonResponse; +use SP\Tests\PHPUnitHelper; + +/** + * Class JsonResponseTest + */ +#[Group('unitary')] +class JsonResponseTest extends TestCase +{ + + use PHPUnitHelper; + + private MockObject|Response $response; + private JsonResponse $jsonResponse; + + #[TestWith([true])] + #[TestWith([false])] + public function testSendRaw(bool $isSent) + { + $this->response + ->expects($this->once()) + ->method('header') + ->with('Content-type', 'application/json; charset=utf-8') + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('body') + ->with('a_response') + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('send') + ->with(true) + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('isSent') + ->willReturn($isSent); + + $this->assertEquals($isSent, $this->jsonResponse->sendRaw('a_response')); + } + + #[DoesNotPerformAssertions] + public function testFactory() + { + JsonResponse::factory($this->response); + } + + /** + * @throws SPException + */ + #[TestWith([true])] + #[TestWith([false])] + public function testSend(bool $isSent) + { + $message = new JsonMessage('a_test'); + $message->setData(['test' => 'a_data']); + $message->addMessage('a_message'); + + $this->response + ->expects($this->once()) + ->method('header') + ->with('Content-type', 'application/json; charset=utf-8') + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('body') + ->with('{"status":1,"description":"a_test","data":{"test":"a_data"},"messages":["a_message"]}') + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('send') + ->with(true) + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('isSent') + ->willReturn($isSent); + + $this->assertEquals($isSent, $this->jsonResponse->send($message)); + } + + /** + * @throws SPException + */ + #[TestWith([true])] + #[TestWith([false])] + public function testSendWithException(bool $isSent) + { + $message = new JsonMessage('a_test'); + $message->setData(['test' => 'a_data']); + $message->addMessage('a_message'); + + $this->response + ->expects($this->once()) + ->method('header') + ->with('Content-type', 'application/json; charset=utf-8') + ->willReturnSelf(); + + $bodyOk = '{"status":1,"description":"a_test","data":{"test":"a_data"},"messages":["a_message"]}'; + $bodyError = '{"status":1,"description":"test","data":[],"messages":["a_hint"]}'; + + $this->response + ->expects($this->exactly(2)) + ->method('body') + ->with(...self::withConsecutive([$bodyOk], [$bodyError])) + ->will( + new ConsecutiveCalls( + [ + new \PHPUnit\Framework\MockObject\Stub\Exception(SPException::error('test', 'a_hint')), + new ReturnSelf(), + ] + ) + ); + + $this->response + ->expects($this->once()) + ->method('send') + ->with(true) + ->willReturnSelf(); + + $this->response + ->expects($this->once()) + ->method('isSent') + ->willReturn($isSent); + + $this->assertEquals($isSent, $this->jsonResponse->send($message)); + } + + /** + * @throws Exception + */ + protected function setUp(): void + { + parent::setUp(); + + $this->response = $this->createMock(Response::class); + $this->jsonResponse = new JsonResponse($this->response); + } +}