diff --git a/.env.dist b/.env.dist
index a25e8c2c..d6271d57 100644
--- a/.env.dist
+++ b/.env.dist
@@ -8,3 +8,7 @@ SHORTCODE_CHARS=
DB_USER=
DB_PASSWORD=
DB_NAME=
+
+# Rest authentication
+REST_USER=
+REST_PASSWORD=
diff --git a/.travis.yml b/.travis.yml
index 77f1332b..eb2ead28 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,6 @@ branches:
- develop
php:
- - 5.5
- 5.6
- 7
- hhvm
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..c08baeb7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+## CHANGELOG
+
+### 0.2.0
+
+**Enhancements:**
+
+* [9: Use symfony/console to dispatch console requests, instead of trying to integrate the process with expressive](https://github.com/acelaya/url-shortener/issues/9)
+* [8: Create a REST API](https://github.com/acelaya/url-shortener/issues/8)
+* [10: Add more CLI functionality](https://github.com/acelaya/url-shortener/issues/10)
+
+**Tasks**
+
+* [5: Create CHANGELOG file](https://github.com/acelaya/url-shortener/issues/5)
diff --git a/bin/cli b/bin/cli
index 0aca7dd0..e400bef8 100755
--- a/bin/cli
+++ b/bin/cli
@@ -1,17 +1,10 @@
#!/usr/bin/env php
get(Application::class);
-$command = count($_SERVER['argv']) > 1 ? $_SERVER['argv'][1] : '';
-$request = ServerRequestFactory::fromGlobals()
- ->withMethod('CLI')
- ->withUri(new Uri(sprintf('/%s', $command)));
-$app->run($request);
+$app = $container->get(CliApp::class);
+$app->run();
diff --git a/composer.json b/composer.json
index d43d7fbb..374fed26 100644
--- a/composer.json
+++ b/composer.json
@@ -11,19 +11,21 @@
}
],
"require": {
- "php": "^5.5 || ^7.0",
+ "php": "^5.6 || ^7.0",
"zendframework/zend-expressive": "^1.0",
"zendframework/zend-expressive-helpers": "^2.0",
"zendframework/zend-expressive-fastroute": "^1.1",
"zendframework/zend-expressive-twigrenderer": "^1.0",
"zendframework/zend-stdlib": "^2.7",
"zendframework/zend-servicemanager": "^3.0",
+ "zendframework/zend-paginator": "^2.6",
"doctrine/orm": "^2.5",
"guzzlehttp/guzzle": "^6.2",
- "acelaya/zsm-annotated-services": "^0.2.0"
+ "acelaya/zsm-annotated-services": "^0.2.0",
+ "symfony/console": "^3.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.8",
+ "phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "^2.3",
"roave/security-advisories": "dev-master",
"filp/whoops": "^2.0",
diff --git a/config/autoload/cli-routes.global.php b/config/autoload/cli-routes.global.php
deleted file mode 100644
index c2aafdf1..00000000
--- a/config/autoload/cli-routes.global.php
+++ /dev/null
@@ -1,15 +0,0 @@
- [
- [
- 'name' => 'cli-generate-shortcode',
- 'path' => '/generate-shortcode',
- 'middleware' => CliRoutable\GenerateShortcodeMiddleware::class,
- 'allowed_methods' => ['CLI'],
- ],
- ],
-
-];
diff --git a/config/autoload/cli.global.php b/config/autoload/cli.global.php
new file mode 100644
index 00000000..1276cc9e
--- /dev/null
+++ b/config/autoload/cli.global.php
@@ -0,0 +1,15 @@
+ [
+ 'commands' => [
+ Command\GenerateShortcodeCommand::class,
+ Command\ResolveUrlCommand::class,
+ Command\ListShortcodesCommand::class,
+ Command\GetVisitsCommand::class,
+ ]
+ ],
+
+];
diff --git a/config/autoload/errorhandler.local.php.dist b/config/autoload/errorhandler.local.php.dist
new file mode 100644
index 00000000..92a92497
--- /dev/null
+++ b/config/autoload/errorhandler.local.php.dist
@@ -0,0 +1,21 @@
+ [
+ 'invokables' => [
+ 'Zend\Expressive\Whoops' => Whoops\Run::class,
+ 'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
+ ],
+ 'factories' => [
+ 'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class,
+ ],
+ ],
+
+ 'whoops' => [
+ 'json_exceptions' => [
+ 'display' => true,
+ 'show_trace' => true,
+ 'ajax_only' => true,
+ ],
+ ],
+];
diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php
index d033f9b2..fc6f85f0 100644
--- a/config/autoload/middleware-pipeline.global.php
+++ b/config/autoload/middleware-pipeline.global.php
@@ -1,5 +1,5 @@
[
'middleware' => [
ApplicationFactory::ROUTING_MIDDLEWARE,
- CliParamsMiddleware::class,
+ ],
+ 'priority' => 10,
+ ],
+
+ 'rest' => [
+ 'path' => '/rest',
+ 'middleware' => [
+ Middleware\CheckAuthenticationMiddleware::class,
+ Middleware\CrossDomainMiddleware::class,
+ ],
+ 'priority' => 5,
+ ],
+
+ 'post-routing' => [
+ 'middleware' => [
Helper\UrlHelperMiddleware::class,
ApplicationFactory::DISPATCH_MIDDLEWARE,
],
diff --git a/config/autoload/rest.global.php b/config/autoload/rest.global.php
new file mode 100644
index 00000000..6e3fc216
--- /dev/null
+++ b/config/autoload/rest.global.php
@@ -0,0 +1,9 @@
+ [
+ 'username' => getenv('REST_USER'),
+ 'password' => getenv('REST_PASSWORD'),
+ ],
+
+];
diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 40a3d20b..87133f09 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -1,5 +1,6 @@
Routable\RedirectMiddleware::class,
'allowed_methods' => ['GET'],
],
+
+ // Rest
+ [
+ 'name' => 'rest-authenticate',
+ 'path' => '/rest/authenticate',
+ 'middleware' => Rest\AuthenticateMiddleware::class,
+ 'allowed_methods' => ['POST', 'OPTIONS'],
+ ],
+ [
+ 'name' => 'rest-create-shortcode',
+ 'path' => '/rest/short-codes',
+ 'middleware' => Rest\CreateShortcodeMiddleware::class,
+ 'allowed_methods' => ['POST', 'OPTIONS'],
+ ],
+ [
+ 'name' => 'rest-resolve-url',
+ 'path' => '/rest/short-codes/{shortCode}',
+ 'middleware' => Rest\ResolveUrlMiddleware::class,
+ 'allowed_methods' => ['GET', 'OPTIONS'],
+ ],
+ [
+ 'name' => 'rest-list-shortened-url',
+ 'path' => '/rest/short-codes',
+ 'middleware' => Rest\ListShortcodesMiddleware::class,
+ 'allowed_methods' => ['GET'],
+ ],
+ [
+ 'name' => 'rest-get-visits',
+ 'path' => '/rest/visits/{shortCode}',
+ 'middleware' => Rest\GetVisitsMiddleware::class,
+ 'allowed_methods' => ['GET', 'OPTIONS'],
+ ],
],
];
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 300f1bf3..4d6cc40e 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -1,4 +1,5 @@
[
'factories' => [
- Application::class => Container\ApplicationFactory::class,
+ Expressive\Application::class => Container\ApplicationFactory::class,
+ Console\Application::class => CLI\Factory\ApplicationFactory::class,
// Url helpers
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
@@ -36,17 +39,31 @@ return [
GuzzleHttp\Client::class => InvokableFactory::class,
Service\UrlShortener::class => AnnotatedFactory::class,
Service\VisitsTracker::class => AnnotatedFactory::class,
+ Service\ShortUrlService::class => AnnotatedFactory::class,
+ Service\RestTokenService::class => AnnotatedFactory::class,
Cache::class => CacheFactory::class,
+ // Cli commands
+ CLI\Command\GenerateShortcodeCommand::class => AnnotatedFactory::class,
+ CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class,
+ CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class,
+ CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class,
+
// Middleware
- Middleware\CliRoutable\GenerateShortcodeMiddleware::class => AnnotatedFactory::class,
Middleware\Routable\RedirectMiddleware::class => AnnotatedFactory::class,
- Middleware\CliParamsMiddleware::class => Middleware\Factory\CliParamsMiddlewareFactory::class,
+ Middleware\Rest\AuthenticateMiddleware::class => AnnotatedFactory::class,
+ Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
+ Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
+ Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
+ Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class,
+ Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
+ Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
],
'aliases' => [
'em' => EntityManager::class,
'httpClient' => GuzzleHttp\Client::class,
Router\RouterInterface::class => Router\FastRouteRouter::class,
+ AnnotatedFactory::CACHE_SERVICE => Cache::class,
]
],
diff --git a/data/docs/rest.md b/data/docs/rest.md
new file mode 100644
index 00000000..f93f6c48
--- /dev/null
+++ b/data/docs/rest.md
@@ -0,0 +1,278 @@
+
+# REST API documentation
+
+## Error management
+
+Statuses:
+
+* 400 -> controlled error
+* 401 -> authentication error
+* 500 -> unexpected error
+
+[TODO]
+
+## Authentication
+
+[TODO]
+
+## Endpoints
+
+#### Authenticate
+
+**REQUEST**
+
+* `POST` -> `/rest/authenticate`
+* Params:
+ * username: `string`
+ * password: `string`
+
+**SUCCESS RESPONSE**
+
+```json
+{
+ "token": "9f741eb0-33d7-4c56-b8f7-3719e9929946"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+ "error": "INVALID_ARGUMENT",
+ "message": "You have to provide both \"username\" and \"password\""
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: Username or password were not provided.
+* **INVALID_CREDENTIALS**: Username or password are incorrect.
+
+
+#### Create shortcode
+
+**REQUEST**
+
+* `POST` -> `/rest/short-codes`
+* Params:
+ * longUrl: `string` -> The URL to shorten
+* Headers:
+ * X-Auth-Token: `string` -> The token provided in the authentication request
+
+**SUCCESS RESPONSE**
+
+```json
+{
+ "longUrl": "https://www.facebook.com/something/something",
+ "shortUrl": "https://doma.in/rY9Kr",
+ "shortCode": "rY9Kr"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+ "error": "INVALID_URL",
+ "message": "Provided URL \"wfwef\" is invalid. Try with a different one."
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: The longUrl was not provided.
+* **INVALID_URL**: Provided longUrl has an invalid format or does not resolve.
+* **UNKNOWN_ERROR**: Something unexpected happened.
+
+
+#### Resolve URL
+
+**REQUEST**
+
+* `GET` -> `/rest/short-codes/{shortCode}`
+* Route params:
+ * shortCode: `string` -> The short code we want to resolve
+* Headers:
+ * X-Auth-Token: `string` -> The token provided in the authentication request
+
+**SUCCESS RESPONSE**
+
+```json
+{
+ "longUrl": "https://www.facebook.com/something/something"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+ "error": "INVALID_SHORTCODE",
+ "message": "Provided short code \"abc123\" has an invalid format"
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: No longUrl was found for provided shortCode.
+* **INVALID_SHORTCODE**: Provided shortCode does not match the character set used by the app to generate short codes.
+* **UNKNOWN_ERROR**: Something unexpected happened.
+
+
+#### List shortened URLs
+
+**REQUEST**
+
+* `GET` -> `/rest/short-codes`
+* Query params:
+ * page: `integer` -> The page to list. Defaults to 1 if not provided.
+* Headers:
+ * X-Auth-Token: `string` -> The token provided in the authentication request
+
+**SUCCESS RESPONSE**
+
+```json
+{
+ "shortUrls": {
+ "data": [
+ {
+ "shortCode": "abc123",
+ "originalUrl": "http://www.alejandrocelaya.com",
+ "dateCreated": "2016-04-30T18:01:47+0200",
+ "visitsCount": 4
+ },
+ {
+ "shortCode": "def456",
+ "originalUrl": "http://www.alejandrocelaya.com/en",
+ "dateCreated": "2016-04-30T18:03:43+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "ghi789",
+ "originalUrl": "http://www.alejandrocelaya.com/es",
+ "dateCreated": "2016-04-30T18:10:38+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "jkl987",
+ "originalUrl": "http://www.alejandrocelaya.com/es/",
+ "dateCreated": "2016-04-30T18:10:57+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "mno654",
+ "originalUrl": "http://blog.alejandrocelaya.com/2016/04/09/improving-zend-service-manager-workflow-with-annotations/",
+ "dateCreated": "2016-04-30T19:21:05+0200",
+ "visitsCount": 1
+ },
+ {
+ "shortCode": "pqr321",
+ "originalUrl": "http://www.google.com",
+ "dateCreated": "2016-05-01T11:19:53+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "stv159",
+ "originalUrl": "http://www.acelaya.com",
+ "dateCreated": "2016-06-12T17:49:21+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "wxy753",
+ "originalUrl": "http://www.atomic-reader.com",
+ "dateCreated": "2016-06-12T17:50:27+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "zab852",
+ "originalUrl": "http://foo.com",
+ "dateCreated": "2016-07-03T09:07:36+0200",
+ "visitsCount": 0
+ },
+ {
+ "shortCode": "cde963",
+ "originalUrl": "https://www.facebook.com.com",
+ "dateCreated": "2016-07-03T09:12:35+0200",
+ "visitsCount": 0
+ }
+ ],
+ "pagination": {
+ "currentPage": 4,
+ "pagesCount": 15
+ }
+ }
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+ "error": "UNKNOWN_ERROR",
+ "message": "Unexpected error occured"
+}
+```
+
+Posible errors:
+
+* **UNKNOWN_ERROR**: Something unexpected happened.
+
+
+#### Get visits
+
+**REQUEST**
+
+* `GET` -> `/rest/visits/{shortCode}`
+* Route params:
+ * shortCode: `string` -> The shortCode from which we eant to get the visits.
+* Headers:
+ * X-Auth-Token: `string` -> The token provided in the authentication request
+
+**SUCCESS RESPONSE**
+
+```json
+{
+ "shortUrls": {
+ "data": [
+ {
+ "referer": null,
+ "date": "2016-06-18T09:32:22+0200",
+ "remoteAddr": "127.0.0.1",
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+ },
+ {
+ "referer": null,
+ "date": "2016-04-30T19:20:06+0200",
+ "remoteAddr": "127.0.0.1",
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+ },
+ {
+ "referer": "google.com",
+ "date": "2016-04-30T19:19:57+0200",
+ "remoteAddr": "1.2.3.4",
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+ },
+ {
+ "referer": null,
+ "date": "2016-04-30T19:17:35+0200",
+ "remoteAddr": "127.0.0.1",
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+ }
+ ],
+ }
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+ "error": "INVALID_ARGUMENT",
+ "message": "Provided short code \"abc123\" is invalid"
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: The shortcode does not belong to any short URL
+* **UNKNOWN_ERROR**: Something unexpected happened.
diff --git a/src/CLI/Command/GenerateShortcodeCommand.php b/src/CLI/Command/GenerateShortcodeCommand.php
new file mode 100644
index 00000000..ffc355db
--- /dev/null
+++ b/src/CLI/Command/GenerateShortcodeCommand.php
@@ -0,0 +1,93 @@
+urlShortener = $urlShortener;
+ $this->domainConfig = $domainConfig;
+ }
+
+ public function configure()
+ {
+ $this->setName('shortcode:generate')
+ ->setDescription('Generates a shortcode for provided URL and returns the short URL')
+ ->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to parse');
+ }
+
+ public function interact(InputInterface $input, OutputInterface $output)
+ {
+ $longUrl = $input->getArgument('longUrl');
+ if (! empty($longUrl)) {
+ return;
+ }
+
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+ $question = new Question(
+ '
+ * The return value is cast to an integer. + * @since 5.1.0 + */ + public function count() + { + return $this->paginableRepository->countList($this->searchTerm); + } +} diff --git a/src/Paginator/Util/PaginatorUtilsTrait.php b/src/Paginator/Util/PaginatorUtilsTrait.php new file mode 100644 index 00000000..270aaea4 --- /dev/null +++ b/src/Paginator/Util/PaginatorUtilsTrait.php @@ -0,0 +1,30 @@ + ArrayUtils::iteratorToArray($paginator->getCurrentItems()), + 'pagination' => [ + 'currentPage' => $paginator->getCurrentPageNumber(), + 'pagesCount' => $paginator->count(), + ], + ]; + } + + /** + * Checks if provided paginator is in last page + * + * @param Paginator $paginator + * @return bool + */ + protected function isLastPage(Paginator $paginator) + { + return $paginator->getCurrentPageNumber() >= $paginator->count(); + } +} diff --git a/src/Repository/PaginableRepositoryInterface.php b/src/Repository/PaginableRepositoryInterface.php new file mode 100644 index 00000000..99c8696e --- /dev/null +++ b/src/Repository/PaginableRepositoryInterface.php @@ -0,0 +1,24 @@ +createQueryBuilder('s'); + + if (isset($limit)) { + $qb->setMaxResults($limit); + } + if (isset($offset)) { + $qb->setFirstResult($offset); + } + if (isset($searchTerm)) { + // TODO + } + if (isset($orderBy)) { + if (is_string($orderBy)) { + $qb->orderBy($orderBy); + } elseif (is_array($orderBy)) { + $key = key($orderBy); + $qb->orderBy($key, $orderBy[$key]); + } + } else { + $qb->orderBy('s.dateCreated'); + } + + return $qb->getQuery()->getResult(); + } + + /** + * Counts the number of elements in a list using provided filtering data + * + * @param null $searchTerm + * @return int + */ + public function countList($searchTerm = null) + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('COUNT(s)') + ->from(ShortUrl::class, 's'); + + if (isset($searchTerm)) { + // TODO + } + + return (int) $qb->getQuery()->getSingleScalarResult(); + } +} diff --git a/src/Repository/ShortUrlRepositoryInterface.php b/src/Repository/ShortUrlRepositoryInterface.php new file mode 100644 index 00000000..be7f3fff --- /dev/null +++ b/src/Repository/ShortUrlRepositoryInterface.php @@ -0,0 +1,8 @@ +em = $em; + $this->restConfig = $restConfig; + } + + /** + * @param string $token + * @return RestToken + * @throws InvalidArgumentException + */ + public function getByToken($token) + { + $restToken = $this->em->getRepository(RestToken::class)->findOneBy([ + 'token' => $token, + ]); + if (! isset($restToken)) { + throw new InvalidArgumentException(sprintf('RestToken not found for token "%s"', $token)); + } + + return $restToken; + } + + /** + * Creates and returns a new RestToken if username and password are correct + * @param $username + * @param $password + * @return RestToken + * @throws AuthenticationException + */ + public function createToken($username, $password) + { + $this->processCredentials($username, $password); + + $restToken = new RestToken(); + $this->em->persist($restToken); + $this->em->flush(); + + return $restToken; + } + + /** + * @param string $username + * @param string $password + */ + protected function processCredentials($username, $password) + { + $configUsername = strtolower(trim($this->restConfig['username'])); + $providedUsername = strtolower(trim($username)); + $configPassword = trim($this->restConfig['password']); + $providedPassword = trim($password); + + if ($configUsername === $providedUsername && $configPassword === $providedPassword) { + return; + } + + // If credentials are not correct, throw exception + throw AuthenticationException::fromCredentials($providedUsername, $providedPassword); + } + + /** + * Updates the expiration of provided token, extending its life + * + * @param RestToken $token + */ + public function updateExpiration(RestToken $token) + { + $token->updateExpiration(); + $this->em->flush(); + } +} diff --git a/src/Service/RestTokenServiceInterface.php b/src/Service/RestTokenServiceInterface.php new file mode 100644 index 00000000..0cdec822 --- /dev/null +++ b/src/Service/RestTokenServiceInterface.php @@ -0,0 +1,32 @@ +em = $em; + } + + /** + * @param int $page + * @return Paginator|ShortUrl[] + */ + public function listShortUrls($page = 1) + { + /** @var ShortUrlRepository $repo */ + $repo = $this->em->getRepository(ShortUrl::class); + $paginator = new Paginator(new PaginableRepositoryAdapter($repo)); + $paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE) + ->setCurrentPageNumber($page); + + return $paginator; + } +} diff --git a/src/Service/ShortUrlServiceInterface.php b/src/Service/ShortUrlServiceInterface.php new file mode 100644 index 00000000..a9d182d2 --- /dev/null +++ b/src/Service/ShortUrlServiceInterface.php @@ -0,0 +1,14 @@ +em->getRepository(ShortUrl::class)->findOneBy([ + 'shortCode' => $shortCode, + ]); + if (! isset($shortUrl)) { + throw new InvalidArgumentException(sprintf('Short code "%s" not found', $shortCode)); + } + + return $this->em->getRepository(Visit::class)->findBy([ + 'shortUrl' => $shortUrl, + ], [ + 'date' => 'DESC' + ]); + } } diff --git a/src/Service/VisitsTrackerInterface.php b/src/Service/VisitsTrackerInterface.php index 3b2fc874..ce0a61cf 100644 --- a/src/Service/VisitsTrackerInterface.php +++ b/src/Service/VisitsTrackerInterface.php @@ -1,6 +1,9 @@ __invoke( - ServerRequestFactory::fromGlobals(), - $originalResponse, - function ($req, $resp) use (&$invoked) { - $invoked = true; - return $resp; - } - ); - - $this->assertSame($originalResponse, $response); - $this->assertTrue($invoked); - } - - /** - * @test - */ - public function nonSuccessRouteResultJustInvokesNextMiddleware() - { - $middleware = new CliParamsMiddleware([], 'cli'); - - $invoked = false; - $originalResponse = new Response(); - $routeResult = $this->prophesize(RouteResult::class); - $routeResult->isSuccess()->willReturn(false)->shouldBeCalledTimes(1); - - $response = $middleware->__invoke( - ServerRequestFactory::fromGlobals()->withAttribute(RouteResult::class, $routeResult->reveal()), - $originalResponse, - function ($req, $resp) use (&$invoked) { - $invoked = true; - return $resp; - } - ); - - $this->assertSame($originalResponse, $response); - $this->assertTrue($invoked); - } - - /** - * @test - */ - public function properRouteWillInjectAttributeInResponse() - { - $expectedLongUrl = 'http://www.google.com'; - $middleware = new CliParamsMiddleware(['foo', 'bar', $expectedLongUrl], 'cli'); - - $invoked = false; - $originalResponse = new Response(); - $routeResult = $this->prophesize(RouteResult::class); - $routeResult->isSuccess()->willReturn(true)->shouldBeCalledTimes(1); - $routeResult->getMatchedRouteName()->willReturn('cli-generate-shortcode')->shouldBeCalledTimes(1); - /** @var ServerRequestInterface $request */ - $request = null; - - $response = $middleware->__invoke( - ServerRequestFactory::fromGlobals()->withAttribute(RouteResult::class, $routeResult->reveal()), - $originalResponse, - function ($req, $resp) use (&$invoked, &$request) { - $invoked = true; - $request = $req; - return $resp; - } - ); - - $this->assertSame($originalResponse, $response); - $this->assertEquals($expectedLongUrl, $request->getAttribute('longUrl')); - $this->assertTrue($invoked); - } -} diff --git a/tests/Middleware/Factory/CliParamsMiddlewareFactoryTest.php b/tests/Middleware/Factory/CliParamsMiddlewareFactoryTest.php deleted file mode 100644 index 3fad4bab..00000000 --- a/tests/Middleware/Factory/CliParamsMiddlewareFactoryTest.php +++ /dev/null @@ -1,29 +0,0 @@ -factory = new CliParamsMiddlewareFactory(); - } - - /** - * @test - */ - public function serviceIsCreated() - { - $instance = $this->factory->__invoke(new ServiceManager(), ''); - $this->assertInstanceOf(CliParamsMiddleware::class, $instance); - } -} diff --git a/tests/Service/ShortUrlServiceTest.php b/tests/Service/ShortUrlServiceTest.php new file mode 100644 index 00000000..3b77ee6a --- /dev/null +++ b/tests/Service/ShortUrlServiceTest.php @@ -0,0 +1,49 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->service = new ShortUrlService($this->em->reveal()); + } + + /** + * @test + */ + public function listedUrlsAreReturnedFromEntityManager() + { + $list = [ + new ShortUrl(), + new ShortUrl(), + new ShortUrl(), + new ShortUrl(), + ]; + + $repo = $this->prophesize(ShortUrlRepository::class); + $repo->findList(Argument::cetera())->willReturn($list)->shouldBeCalledTimes(1); + $repo->countList(Argument::cetera())->willReturn(count($list))->shouldBeCalledTimes(1); + $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $list = $this->service->listShortUrls(); + $this->assertEquals(4, $list->getCurrentItemCount()); + } +}