From 84094a51a27183c4e954d13011ba4cff372c9d12 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:45:05 +0100 Subject: [PATCH] Implemented EditShortCodeAction --- module/Rest/config/routes.config.php | 26 ++++-- .../Rest/src/Action/EditShortCodeAction.php | 50 ++++++++++ module/Rest/src/ConfigProvider.php | 18 +++- module/Rest/src/Util/RestUtils.php | 1 + .../test/Action/EditShortCodeActionTest.php | 93 +++++++++++++++++++ phpunit-func.xml | 7 +- phpunit.xml.dist | 7 +- 7 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 module/Rest/test/Action/EditShortCodeActionTest.php diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 742f904b..a1bd8dca 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -9,7 +9,7 @@ return [ 'routes' => [ [ 'name' => Action\AuthenticateAction::class, - 'path' => '/rest/v{version:1}/authenticate', + 'path' => '/authenticate', 'middleware' => Action\AuthenticateAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], @@ -17,25 +17,31 @@ return [ // Short codes [ 'name' => Action\CreateShortcodeAction::class, - 'path' => '/rest/v{version:1}/short-codes', + 'path' => '/short-codes', 'middleware' => Action\CreateShortcodeAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], + [ + 'name' => Action\EditShortCodeAction::class, + 'path' => '/short-codes/{shortCode}', + 'middleware' => Action\EditShortCodeAction::class, + 'allowed_methods' => [RequestMethod::METHOD_PUT], + ], [ 'name' => Action\ResolveUrlAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}', + 'path' => '/short-codes/{shortCode}', 'middleware' => Action\ResolveUrlAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\ListShortcodesAction::class, - 'path' => '/rest/v{version:1}/short-codes', + 'path' => '/short-codes', 'middleware' => Action\ListShortcodesAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\EditShortcodeTagsAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}/tags', + 'path' => '/short-codes/{shortCode}/tags', 'middleware' => Action\EditShortcodeTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_PUT], ], @@ -43,7 +49,7 @@ return [ // Visits [ 'name' => Action\GetVisitsAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}/visits', + 'path' => '/short-codes/{shortCode}/visits', 'middleware' => Action\GetVisitsAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], @@ -51,25 +57,25 @@ return [ // Tags [ 'name' => Action\Tag\ListTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\ListTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\Tag\DeleteTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\DeleteTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_DELETE], ], [ 'name' => Action\Tag\CreateTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\CreateTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], [ 'name' => Action\Tag\UpdateTagAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\UpdateTagAction::class, 'allowed_methods' => [RequestMethod::METHOD_PUT], ], diff --git a/module/Rest/src/Action/EditShortCodeAction.php b/module/Rest/src/Action/EditShortCodeAction.php index 84d3aaae..a8f12de6 100644 --- a/module/Rest/src/Action/EditShortCodeAction.php +++ b/module/Rest/src/Action/EditShortCodeAction.php @@ -6,9 +6,36 @@ namespace Shlinkio\Shlink\Rest\Action; use Interop\Http\ServerMiddleware\DelegateInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Core\Exception; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; +use Shlinkio\Shlink\Rest\Util\RestUtils; +use Zend\Diactoros\Response\EmptyResponse; +use Zend\Diactoros\Response\JsonResponse; +use Zend\I18n\Translator\TranslatorInterface; class EditShortCodeAction extends AbstractRestAction { + /** + * @var ShortUrlServiceInterface + */ + private $shortUrlService; + /** + * @var TranslatorInterface + */ + private $translator; + + public function __construct( + ShortUrlServiceInterface $shortUrlService, + TranslatorInterface $translator, + LoggerInterface $logger = null + ) { + parent::__construct($logger); + $this->shortUrlService = $shortUrlService; + $this->translator = $translator; + } + /** * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. @@ -17,8 +44,31 @@ class EditShortCodeAction extends AbstractRestAction * @param DelegateInterface $delegate * * @return ResponseInterface + * @throws \InvalidArgumentException */ public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface { + $postData = (array) $request->getParsedBody(); + $shortCode = $request->getAttribute('shortCode', ''); + + try { + $this->shortUrlService->updateMetadataByShortCode( + $shortCode, + ShortUrlMeta::createFromRawData($postData) + ); + return new EmptyResponse(); + } catch (Exception\InvalidShortCodeException $e) { + $this->logger->warning('Provided data is invalid.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::getRestErrorCodeFromException($e), + 'message' => \sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode), + ], self::STATUS_NOT_FOUND); + } catch (Exception\ValidationException $e) { + $this->logger->warning('Provided data is invalid.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::getRestErrorCodeFromException($e), + 'message' => $this->translator->translate('Provided data is invalid.'), + ], self::STATUS_BAD_REQUEST); + } } } diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index 3fc7a9be..2fc3c017 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -8,8 +8,24 @@ use Zend\Stdlib\Glob; class ConfigProvider { + const ROUTES_PREFIX = '/rest/v{version:1}'; + public function __invoke() { - return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)); + return $this->applyRoutesPrefix( + Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)) + ); + } + + private function applyRoutesPrefix(array $config): array + { + $routes =& $config['routes'] ?? []; + + // Prepend the routes prefix to every path + foreach ($routes as $key => $route) { + $routes[$key]['path'] = self::ROUTES_PREFIX . $route['path']; + } + + return $config; } } diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index 407b0845..b9d96891 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -30,6 +30,7 @@ class RestUtils case $e instanceof Core\NonUniqueSlugException: return self::INVALID_SLUG_ERROR; case $e instanceof Common\InvalidArgumentException: + case $e instanceof Core\ValidationException: return self::INVALID_ARGUMENT_ERROR; case $e instanceof Rest\AuthenticationException: return self::INVALID_CREDENTIALS_ERROR; diff --git a/module/Rest/test/Action/EditShortCodeActionTest.php b/module/Rest/test/Action/EditShortCodeActionTest.php new file mode 100644 index 00000000..2690811a --- /dev/null +++ b/module/Rest/test/Action/EditShortCodeActionTest.php @@ -0,0 +1,93 @@ +shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); + $this->action = new EditShortCodeAction($this->shortUrlService->reveal(), Translator::factory([])); + } + + /** + * @test + */ + public function invalidDataReturnsError() + { + $request = ServerRequestFactory::fromGlobals()->withParsedBody([ + 'maxVisits' => 'invalid', + ]); + + /** @var JsonResponse $resp */ + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + $payload = $resp->getPayload(); + + $this->assertEquals(400, $resp->getStatusCode()); + $this->assertEquals(RestUtils::INVALID_ARGUMENT_ERROR, $payload['error']); + $this->assertEquals('Provided data is invalid.', $payload['message']); + } + + /** + * @test + */ + public function incorrectShortCodeReturnsError() + { + $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123') + ->withParsedBody([ + 'maxVisits' => 5, + ]); + $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willThrow( + InvalidShortCodeException::class + ); + + /** @var JsonResponse $resp */ + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + $payload = $resp->getPayload(); + + $this->assertEquals(404, $resp->getStatusCode()); + $this->assertEquals(RestUtils::INVALID_SHORTCODE_ERROR, $payload['error']); + $this->assertEquals('No URL found for short code "abc123"', $payload['message']); + $updateMeta->shouldHaveBeenCalled(); + } + + /** + * @test + */ + public function correctShortCodeReturnsSuccess() + { + $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123') + ->withParsedBody([ + 'maxVisits' => 5, + ]); + $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(new ShortUrl()); + + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + + $this->assertEquals(204, $resp->getStatusCode()); + $updateMeta->shouldHaveBeenCalled(); + } +} diff --git a/phpunit-func.xml b/phpunit-func.xml index d125e506..f06c11ee 100644 --- a/phpunit-func.xml +++ b/phpunit-func.xml @@ -1,4 +1,9 @@ - + + ./module/*/test-func diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ddd4f42b..af71c7cd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,9 @@ - + + ./module/Common/test