Compare commits

...

12 Commits

Author SHA1 Message Date
Alejandro Celaya
f563e777cc Merge pull request #369 from acelaya/feature/postgres-query-error
Feature/postgres query error
2019-03-05 14:26:36 +01:00
Alejandro Celaya
a63447b12b Updated changelog 2019-03-05 14:17:47 +01:00
Alejandro Celaya
0f81c3ab92 Fixed error when using postgres in a SELECT count query where a ORDER BY was added by mistake 2019-03-05 13:50:44 +01:00
Alejandro Celaya
425f254453 Added posgres container for development 2019-03-05 13:39:45 +01:00
Alejandro Celaya
a9d9ec5bf9 Merge pull request #365 from acelaya/feature/coding-styles
Feature/coding styles
2019-02-26 23:06:08 +01:00
Alejandro Celaya
0c5c752ffe Updated changelog 2019-02-26 22:58:03 +01:00
Alejandro Celaya
4b556cd79f Updated to shlinkio coding standard 1.1.0 2019-02-26 22:56:43 +01:00
Alejandro Celaya
3d32a90f8e Merge pull request #364 from acelaya/bugfix/non-locatable-addresses
Bugfix/non locatable addresses
2019-02-26 22:53:07 +01:00
Alejandro Celaya
0b4c334163 Fixed typo 2019-02-26 22:42:33 +01:00
Alejandro Celaya
312fc0984b Fixed mutation score by provideing more tests 2019-02-26 22:41:04 +01:00
Alejandro Celaya
30bf1c2641 Added tests for new cases with non-locatable addresses 2019-02-26 22:31:07 +01:00
Alejandro Celaya
2d1d7357a3 Given more semantic cases in which a visit cannot be located 2019-02-26 21:39:45 +01:00
120 changed files with 403 additions and 35 deletions

View File

@@ -4,6 +4,52 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
## 1.16.2 - 2019-03-05
#### Added
* *Nothing*
#### Changed
* *Nothing*
#### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* [#368](https://github.com/shlinkio/shlink/issues/368) Fixed error produced when running a `SELECT COUNT(...)` with `ORDER BY` in PostgreSQL databases.
## 1.16.1 - 2019-02-26
#### Added
* *Nothing*
#### Changed
* [#363](https://github.com/shlinkio/shlink/issues/363) Updated to `shlinkio/php-coding-standard` version 1.1.0
#### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* [#362](https://github.com/shlinkio/shlink/issues/362) Fixed all visits without an IP address being processed every time the `visit:process` command is executed.
## 1.16.0 - 2019-02-23 ## 1.16.0 - 2019-02-23
#### Added #### Added

View File

@@ -58,7 +58,7 @@
"phpunit/phpcov": "^6.0@dev || ^5.0", "phpunit/phpcov": "^6.0@dev || ^5.0",
"phpunit/phpunit": "^8.0 || ^7.5", "phpunit/phpunit": "^8.0 || ^7.5",
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~1.0.0", "shlinkio/php-coding-standard": "~1.1.0",
"symfony/dotenv": "^4.2", "symfony/dotenv": "^4.2",
"symfony/var-dumper": "^4.2", "symfony/var-dumper": "^4.2",
"zendframework/zend-component-installer": "^2.1", "zendframework/zend-component-installer": "^2.1",

View File

@@ -7,6 +7,7 @@ use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Logger; use Monolog\Logger;
use Monolog\Processor; use Monolog\Processor;
use const PHP_EOL; use const PHP_EOL;
return [ return [

View File

@@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use function Shlinkio\Shlink\Common\env; use function Shlinkio\Shlink\Common\env;
return [ return [

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink;
use Acelaya\ExpressiveErrorHandler; use Acelaya\ExpressiveErrorHandler;
use Zend\ConfigAggregator; use Zend\ConfigAggregator;
use Zend\Expressive; use Zend\Expressive;
use function Shlinkio\Shlink\Common\env; use function Shlinkio\Shlink\Common\env;
return (new ConfigAggregator\ConfigAggregator([ return (new ConfigAggregator\ConfigAggregator([

View File

@@ -5,6 +5,7 @@ namespace ShlinkioTest\Shlink\Common;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use function file_exists; use function file_exists;
use function touch; use function touch;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common; namespace ShlinkioTest\Shlink\Common;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use function file_exists; use function file_exists;
use function touch; use function touch;

View File

@@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Zend\ConfigAggregator\ConfigAggregator; use Zend\ConfigAggregator\ConfigAggregator;
use Zend\ServiceManager\Factory\InvokableFactory; use Zend\ServiceManager\Factory\InvokableFactory;
use function sprintf; use function sprintf;
use function sys_get_temp_dir; use function sys_get_temp_dir;

2
data/infra/database_pg/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,6 +0,0 @@
FROM mysql:5.7
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
# Enable remote access (default is localhost only, we change this
# otherwise our database would not be reachable from outside the container)
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf

View File

@@ -1,5 +0,0 @@
FROM nginx:1.11.6-alpine
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
# Delete default nginx vhost
RUN rm /etc/nginx/conf.d/default.conf

View File

@@ -28,6 +28,9 @@ RUN docker-php-ext-install zip
RUN apk add --no-cache --virtual libpng-dev RUN apk add --no-cache --virtual libpng-dev
RUN docker-php-ext-install gd RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
# Install redis extension # Install redis extension
ADD https://github.com/phpredis/phpredis/archive/$PREDIS_VERSION.tar.gz /tmp/phpredis.tar.gz ADD https://github.com/phpredis/phpredis/archive/$PREDIS_VERSION.tar.gz /tmp/phpredis.tar.gz
RUN mkdir -p /usr/src/php/ext/redis\ RUN mkdir -p /usr/src/php/ext/redis\

View File

@@ -27,6 +27,9 @@ RUN docker-php-ext-install zip
RUN apk add --no-cache --virtual libpng-dev RUN apk add --no-cache --virtual libpng-dev
RUN docker-php-ext-install gd RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
# Install redis extension # Install redis extension
ADD https://github.com/phpredis/phpredis/archive/$PREDIS_VERSION.tar.gz /tmp/phpredis.tar.gz ADD https://github.com/phpredis/phpredis/archive/$PREDIS_VERSION.tar.gz /tmp/phpredis.tar.gz
RUN mkdir -p /usr/src/php/ext/redis\ RUN mkdir -p /usr/src/php/ext/redis\

View File

@@ -1,4 +1,4 @@
version: '2' version: '3'
services: services:
shlink_php: shlink_php:
@@ -12,3 +12,15 @@ services:
volumes: volumes:
- /etc/passwd:/etc/passwd:ro - /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro - /etc/group:/etc/group:ro
shlink_db:
user: 1000:1000
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
shlink_db_postgres:
user: 1000:1000
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro

View File

@@ -1,17 +1,15 @@
version: '2' version: '3'
services: services:
shlink_nginx: shlink_nginx:
container_name: shlink_nginx container_name: shlink_nginx
build: image: nginx:1.15.9-alpine
context: .
dockerfile: ./data/infra/nginx.Dockerfile
ports: ports:
- "8000:80" - "8000:80"
volumes: volumes:
- ./:/home/shlink/www - ./:/home/shlink/www
- ./docs:/home/shlink/www/public/docs - ./docs:/home/shlink/www/public/docs
- ./data/infra/vhost.conf:/etc/nginx/conf.d/shlink-vhost.conf - ./data/infra/vhost.conf:/etc/nginx/conf.d/default.conf
links: links:
- shlink_php - shlink_php
@@ -25,6 +23,7 @@ services:
- ./data/infra/php.ini:/usr/local/etc/php/php.ini - ./data/infra/php.ini:/usr/local/etc/php/php.ini
links: links:
- shlink_db - shlink_db
- shlink_db_postgres
shlink_swoole: shlink_swoole:
container_name: shlink_swoole container_name: shlink_swoole
@@ -37,12 +36,11 @@ services:
- ./:/home/shlink - ./:/home/shlink
links: links:
- shlink_db - shlink_db
- shlink_db_postgres
shlink_db: shlink_db:
container_name: shlink_db container_name: shlink_db
build: image: mysql:5.7
context: .
dockerfile: ./data/infra/db.Dockerfile
ports: ports:
- "3307:3306" - "3307:3306"
volumes: volumes:
@@ -51,3 +49,16 @@ services:
environment: environment:
MYSQL_ROOT_PASSWORD: root MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shlink MYSQL_DATABASE: shlink
shlink_db_postgres:
container_name: shlink_db_postgres
image: postgres:10.7-alpine
ports:
- "5433:5432"
volumes:
- ./:/home/shlink/www
- ./data/infra/database_pg:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: root
POSTGRES_DB: shlink
PGDATA: /var/lib/postgresql/data/pgdata

View File

@@ -11,6 +11,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class DisableKeyCommand extends Command class DisableKeyCommand extends Command

View File

@@ -11,6 +11,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class GenerateKeyCommand extends Command class GenerateKeyCommand extends Command

View File

@@ -11,6 +11,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use function array_filter; use function array_filter;
use function array_map; use function array_map;
use function sprintf; use function sprintf;

View File

@@ -9,6 +9,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
use function str_shuffle; use function str_shuffle;

View File

@@ -9,6 +9,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
/** @deprecated */ /** @deprecated */

View File

@@ -12,6 +12,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class DeleteShortUrlCommand extends Command class DeleteShortUrlCommand extends Command

View File

@@ -11,6 +11,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class GeneratePreviewCommand extends Command class GeneratePreviewCommand extends Command

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\Diactoros\Uri; use Zend\Diactoros\Uri;
use function array_map; use function array_map;
use function Functional\curry; use function Functional\curry;
use function Functional\flatten; use function Functional\flatten;

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\Stdlib\ArrayUtils; use Zend\Stdlib\ArrayUtils;
use function array_map; use function array_map;
use function Functional\select_keys; use function Functional\select_keys;

View File

@@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use function array_values; use function array_values;
use function count; use function count;
use function explode; use function explode;

View File

@@ -12,6 +12,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class ResolveUrlCommand extends Command class ResolveUrlCommand extends Command

View File

@@ -10,6 +10,7 @@ use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use function Functional\map; use function Functional\map;
class ListTagsCommand extends Command class ListTagsCommand extends Command

View File

@@ -11,6 +11,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf; use function sprintf;
class RenameTagCommand extends Command class RenameTagCommand extends Command

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Lock\Factory as Locker; use Symfony\Component\Lock\Factory as Locker;
use function sprintf; use function sprintf;
class ProcessVisitsCommand extends Command class ProcessVisitsCommand extends Command
@@ -65,7 +66,11 @@ class ProcessVisitsCommand extends Command
$this->visitService->locateUnlocatedVisits( $this->visitService->locateUnlocatedVisits(
[$this, 'getGeolocationDataForVisit'], [$this, 'getGeolocationDataForVisit'],
function (VisitLocation $location) use ($output) { function (VisitLocation $location) use ($output) {
$output->writeln(sprintf(' [<info>Address located at "%s"</info>]', $location->getCountryName())); if (! $location->isEmpty()) {
$output->writeln(
sprintf(' [<info>Address located at "%s"</info>]', $location->getCountryName())
);
}
} }
); );
@@ -83,14 +88,14 @@ class ProcessVisitsCommand extends Command
'<comment>Ignored visit with no IP address</comment>', '<comment>Ignored visit with no IP address</comment>',
OutputInterface::VERBOSITY_VERBOSE OutputInterface::VERBOSITY_VERBOSE
); );
throw new IpCannotBeLocatedException('Ignored visit with no IP address'); throw IpCannotBeLocatedException::forEmptyAddress();
} }
$ipAddr = $visit->getRemoteAddr(); $ipAddr = $visit->getRemoteAddr();
$this->output->write(sprintf('Processing IP <fg=blue>%s</>', $ipAddr)); $this->output->write(sprintf('Processing IP <fg=blue>%s</>', $ipAddr));
if ($ipAddr === IpAddress::LOCALHOST) { if ($ipAddr === IpAddress::LOCALHOST) {
$this->output->writeln(' [<comment>Ignored localhost address</comment>]'); $this->output->writeln(' [<comment>Ignored localhost address</comment>]');
throw new IpCannotBeLocatedException('Ignored localhost address'); throw IpCannotBeLocatedException::forLocalhost();
} }
try { try {
@@ -101,7 +106,7 @@ class ProcessVisitsCommand extends Command
$this->getApplication()->renderException($e, $this->output); $this->getApplication()->renderException($e, $this->output);
} }
throw new IpCannotBeLocatedException('An error occurred while locating IP', $e->getCode(), $e); throw IpCannotBeLocatedException::forError($e);
} }
} }
} }

View File

@@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand; use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use function implode; use function implode;
use function sort; use function sort;
use function str_split; use function str_split;

View File

@@ -11,7 +11,9 @@ use Shlinkio\Shlink\Core\Exception;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use const PHP_EOL; use const PHP_EOL;
use function array_pop; use function array_pop;
use function sprintf; use function sprintf;

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use Zend\Paginator\Adapter\ArrayAdapter; use Zend\Paginator\Adapter\ArrayAdapter;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use function count; use function count;
use function substr_count; use function substr_count;

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\UrlShortener;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use const PHP_EOL; use const PHP_EOL;
class ResolveUrlCommandTest extends TestCase class ResolveUrlCommandTest extends TestCase

View File

@@ -20,6 +20,7 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Lock; use Symfony\Component\Lock;
use function array_shift; use function array_shift;
use function sprintf; use function sprintf;
@@ -116,6 +117,11 @@ class ProcessVisitsCommandTest extends TestCase
$output = $this->commandTester->getDisplay(); $output = $this->commandTester->getDisplay();
$this->assertStringContainsString($message, $output); $this->assertStringContainsString($message, $output);
if (empty($address)) {
$this->assertStringNotContainsString('Processing IP', $output);
} else {
$this->assertStringContainsString('Processing IP', $output);
}
$locateVisits->shouldHaveBeenCalledOnce(); $locateVisits->shouldHaveBeenCalledOnce();
$resolveIpLocation->shouldNotHaveBeenCalled(); $resolveIpLocation->shouldNotHaveBeenCalled();
} }

View File

@@ -11,6 +11,7 @@ use Shlinkio\Shlink\Core\Options\AppOptions;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Zend\ServiceManager\ServiceManager; use Zend\ServiceManager\ServiceManager;
use function array_merge; use function array_merge;
class ApplicationFactoryTest extends TestCase class ApplicationFactoryTest extends TestCase

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common; namespace Shlinkio\Shlink\Common;
use const JSON_ERROR_NONE; use const JSON_ERROR_NONE;
use function getenv; use function getenv;
use function json_decode as spl_json_decode; use function json_decode as spl_json_decode;
use function json_last_error; use function json_last_error;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common\Exception; namespace Shlinkio\Shlink\Common\Exception;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class WrongIpException extends RuntimeException class WrongIpException extends RuntimeException

View File

@@ -11,6 +11,7 @@ use Shlinkio\Shlink\Core\Options\AppOptions;
use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface; use Zend\ServiceManager\Factory\FactoryInterface;
use function Functional\contains; use function Functional\contains;
use function Shlinkio\Shlink\Common\env; use function Shlinkio\Shlink\Common\env;
use function sys_get_temp_dir; use function sys_get_temp_dir;

View File

@@ -10,6 +10,7 @@ use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\AbstractFactoryInterface; use Zend\ServiceManager\Factory\AbstractFactoryInterface;
use function array_shift; use function array_shift;
use function explode; use function explode;
use function is_array; use function is_array;

View File

@@ -9,6 +9,7 @@ use Interop\Container\Exception\ContainerException;
use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface; use Zend\ServiceManager\Factory\FactoryInterface;
use function count; use function count;
use function explode; use function explode;

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Common\Exception\RuntimeException;
use Symfony\Component\Filesystem\Exception as FilesystemException; use Symfony\Component\Filesystem\Exception as FilesystemException;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class DbUpdater implements DbUpdaterInterface class DbUpdater implements DbUpdaterInterface

View File

@@ -9,6 +9,7 @@ use GeoIp2\Model\City;
use GeoIp2\Record\Subdivision; use GeoIp2\Record\Subdivision;
use MaxMind\Db\Reader\InvalidDatabaseException; use MaxMind\Db\Reader\InvalidDatabaseException;
use Shlinkio\Shlink\Common\Exception\WrongIpException; use Shlinkio\Shlink\Common\Exception\WrongIpException;
use function Functional\first; use function Functional\first;
class GeoLite2LocationResolver implements IpLocationResolverInterface class GeoLite2LocationResolver implements IpLocationResolverInterface

View File

@@ -7,6 +7,7 @@ use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Common\Exception\WrongIpException; use Shlinkio\Shlink\Common\Exception\WrongIpException;
use function Shlinkio\Shlink\Common\json_decode; use function Shlinkio\Shlink\Common\json_decode;
use function sprintf; use function sprintf;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common\Logger\Processor; namespace Shlinkio\Shlink\Common\Logger\Processor;
use const PHP_EOL; use const PHP_EOL;
use function str_replace; use function str_replace;
use function strpos; use function strpos;

View File

@@ -8,6 +8,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface; use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
use Zend\I18n\Translator\Translator; use Zend\I18n\Translator\Translator;
use function count; use function count;
use function explode; use function explode;

View File

@@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Common\Paginator\Adapter;
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface; use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
use Zend\Paginator\Adapter\AdapterInterface; use Zend\Paginator\Adapter\AdapterInterface;
use function strip_tags; use function strip_tags;
use function trim; use function trim;

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Common\Paginator\Util;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use Zend\Stdlib\ArrayUtils; use Zend\Stdlib\ArrayUtils;
use function array_map; use function array_map;
use function sprintf; use function sprintf;

View File

@@ -7,6 +7,7 @@ use Fig\Http\Message\StatusCodeInterface as StatusCode;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Response; use Zend\Diactoros\Response;
use Zend\Diactoros\Stream; use Zend\Diactoros\Stream;
use function base64_decode; use function base64_decode;
class PixelResponse extends Response class PixelResponse extends Response

View File

@@ -7,6 +7,7 @@ use mikehaertl\wkhtmlto\Image;
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException; use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
use Shlinkio\Shlink\Common\Image\ImageBuilderInterface; use Shlinkio\Shlink\Common\Image\ImageBuilderInterface;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use function sprintf; use function sprintf;
use function urlencode; use function urlencode;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common\Util; namespace Shlinkio\Shlink\Common\Util;
use Shlinkio\Shlink\Common\Exception\WrongIpException; use Shlinkio\Shlink\Common\Exception\WrongIpException;
use function count; use function count;
use function explode; use function explode;
use function implode; use function implode;

View File

@@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response; use Zend\Diactoros\Response;
use Zend\Diactoros\Stream; use Zend\Diactoros\Stream;
use Zend\Stdlib\ArrayUtils; use Zend\Stdlib\ArrayUtils;
use const FILEINFO_MIME; use const FILEINFO_MIME;
trait ResponseUtilsTrait trait ResponseUtilsTrait

View File

@@ -10,6 +10,7 @@ use GuzzleHttp\RequestOptions;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin;
use function Shlinkio\Shlink\Common\json_decode; use function Shlinkio\Shlink\Common\json_decode;
use function sprintf; use function sprintf;

View File

@@ -8,6 +8,7 @@ use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger; use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use function file_exists; use function file_exists;
use function unlink; use function unlink;

View File

@@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\Factory\CacheFactory; use Shlinkio\Shlink\Common\Factory\CacheFactory;
use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Options\AppOptions;
use Zend\ServiceManager\ServiceManager; use Zend\ServiceManager\ServiceManager;
use function count; use function count;
use function putenv; use function putenv;
use function realpath; use function realpath;

View File

@@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\IpGeolocation\EmptyIpLocationResolver; use Shlinkio\Shlink\Common\IpGeolocation\EmptyIpLocationResolver;
use Shlinkio\Shlink\Common\IpGeolocation\Model\Location; use Shlinkio\Shlink\Common\IpGeolocation\Model\Location;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use function Functional\map; use function Functional\map;
use function range; use function range;

View File

@@ -11,6 +11,7 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Exception\WrongIpException; use Shlinkio\Shlink\Common\Exception\WrongIpException;
use Shlinkio\Shlink\Common\IpGeolocation\IpApiLocationResolver; use Shlinkio\Shlink\Common\IpGeolocation\IpApiLocationResolver;
use Shlinkio\Shlink\Common\IpGeolocation\Model\Location; use Shlinkio\Shlink\Common\IpGeolocation\Model\Location;
use function json_encode; use function json_encode;
class IpApiLocationResolverTest extends TestCase class IpApiLocationResolverTest extends TestCase

View File

@@ -6,7 +6,9 @@ namespace ShlinkioTest\Shlink\Common\Logger\Processor;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\Logger\Processor\ExceptionWithNewLineProcessor; use Shlinkio\Shlink\Common\Logger\Processor\ExceptionWithNewLineProcessor;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use const PHP_EOL; use const PHP_EOL;
use function Functional\map; use function Functional\map;
use function range; use function range;

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Common\Image\ImageBuilder;
use Shlinkio\Shlink\Common\Service\PreviewGenerator; use Shlinkio\Shlink\Common\Service\PreviewGenerator;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use Zend\ServiceManager\ServiceManager; use Zend\ServiceManager\ServiceManager;
use function sprintf; use function sprintf;
use function urlencode; use function urlencode;

View File

@@ -5,6 +5,7 @@ namespace ShlinkioTest\Shlink\Common\Util;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use function Functional\map; use function Functional\map;
use function range; use function range;
use function strlen; use function strlen;

View File

@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
use function array_key_exists; use function array_key_exists;
abstract class AbstractTrackingAction implements MiddlewareInterface abstract class AbstractTrackingAction implements MiddlewareInterface

View File

@@ -8,6 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Entity\AbstractEntity;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use function count; use function count;
class ShortUrl extends AbstractEntity class ShortUrl extends AbstractEntity

View File

@@ -72,4 +72,16 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface
'timezone' => $this->timezone, 'timezone' => $this->timezone,
]; ];
} }
public function isEmpty(): bool
{
return
$this->countryCode === '' &&
$this->countryName === '' &&
$this->regionName === '' &&
$this->cityName === '' &&
((float) $this->latitude) === 0.0 &&
((float) $this->longitude) === 0.0 &&
$this->timezone === '';
}
} }

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception; namespace Shlinkio\Shlink\Core\Exception;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class DeleteShortUrlException extends RuntimeException class DeleteShortUrlException extends RuntimeException

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception; namespace Shlinkio\Shlink\Core\Exception;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class InvalidShortCodeException extends RuntimeException class InvalidShortCodeException extends RuntimeException

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception; namespace Shlinkio\Shlink\Core\Exception;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class InvalidUrlException extends RuntimeException class InvalidUrlException extends RuntimeException

View File

@@ -3,6 +3,43 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception; namespace Shlinkio\Shlink\Core\Exception;
use Throwable;
class IpCannotBeLocatedException extends RuntimeException class IpCannotBeLocatedException extends RuntimeException
{ {
/** @var bool */
private $isNonLocatableAddress;
public function __construct(
bool $isNonLocatableAddress,
string $message,
int $code = 0,
?Throwable $previous = null
) {
$this->isNonLocatableAddress = $isNonLocatableAddress;
parent::__construct($message, $code, $previous);
}
public static function forEmptyAddress(): self
{
return new self(true, 'Ignored visit with no IP address');
}
public static function forLocalhost(): self
{
return new self(true, 'Ignored localhost address');
}
public static function forError(Throwable $e): self
{
return new self(false, 'An error occurred while locating IP', $e->getCode(), $e);
}
/**
* Tells if this error belongs to an address that will never be possible locate
*/
public function isNonLocatableAddress(): bool
{
return $this->isNonLocatableAddress;
}
} }

View File

@@ -5,7 +5,9 @@ namespace Shlinkio\Shlink\Core\Exception;
use Throwable; use Throwable;
use Zend\InputFilter\InputFilterInterface; use Zend\InputFilter\InputFilterInterface;
use const PHP_EOL; use const PHP_EOL;
use function is_array; use function is_array;
use function print_r; use function print_r;
use function sprintf; use function sprintf;

View File

@@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Core\Options;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use Zend\Stdlib\AbstractOptions; use Zend\Stdlib\AbstractOptions;
use function sprintf; use function sprintf;
class AppOptions extends AbstractOptions class AppOptions extends AbstractOptions

View File

@@ -7,6 +7,7 @@ use Cake\Chronos\Chronos;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use function array_column; use function array_column;
use function array_key_exists; use function array_key_exists;
use function Functional\contains; use function Functional\contains;

View File

@@ -50,7 +50,8 @@ DQL;
?int $offset = null ?int $offset = null
): array { ): array {
$qb = $this->createVisitsByShortCodeQueryBuilder($shortCode, $dateRange); $qb = $this->createVisitsByShortCodeQueryBuilder($shortCode, $dateRange);
$qb->select('v'); $qb->select('v')
->orderBy('v.date', 'DESC');
if ($limit !== null) { if ($limit !== null) {
$qb->setMaxResults($limit); $qb->setMaxResults($limit);
@@ -76,8 +77,7 @@ DQL;
$qb->from(Visit::class, 'v') $qb->from(Visit::class, 'v')
->join('v.shortUrl', 'su') ->join('v.shortUrl', 'su')
->where($qb->expr()->eq('su.shortCode', ':shortCode')) ->where($qb->expr()->eq('su.shortCode', ':shortCode'))
->setParameter('shortCode', $shortCode) ->setParameter('shortCode', $shortCode);
->orderBy('v.date', 'DESC') ;
// Apply date range filtering // Apply date range filtering
if ($dateRange !== null && $dateRange->getStartDate() !== null) { if ($dateRange !== null && $dateRange->getStartDate() !== null) {

View File

@@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response; use Zend\Diactoros\Response;
use Zend\Expressive\Template\TemplateRendererInterface; use Zend\Expressive\Template\TemplateRendererInterface;
use function array_shift; use function array_shift;
use function explode; use function explode;
use function Functional\contains; use function Functional\contains;

View File

@@ -20,6 +20,7 @@ use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Throwable; use Throwable;
use function array_reduce; use function array_reduce;
use function count; use function count;
use function floor; use function floor;

View File

@@ -30,12 +30,18 @@ class VisitService implements VisitServiceInterface
foreach ($results as $visit) { foreach ($results as $visit) {
$count++; $count++;
try { try {
/** @var Location $location */ /** @var Location $location */
$location = $geolocateVisit($visit); $location = $geolocateVisit($visit);
} catch (IpCannotBeLocatedException $e) { } catch (IpCannotBeLocatedException $e) {
// Skip if the visit's IP could not be located if (! $e->isNonLocatableAddress()) {
continue; // Skip if the visit's IP could not be located because of an error
continue;
}
// If the IP address is non-locatable, locate it as empty to prevent next processes to pick it again
$location = Location::emptyInstance();
} }
$location = new VisitLocation($location); $location = new VisitLocation($location);

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsPaginatorAdapter; use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsPaginatorAdapter;
use Shlinkio\Shlink\Core\Repository\VisitRepository; use Shlinkio\Shlink\Core\Repository\VisitRepository;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use function sprintf; use function sprintf;
class VisitsTracker implements VisitsTrackerInterface class VisitsTracker implements VisitsTrackerInterface

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Transformer;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait; use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait;
use function Functional\invoke; use function Functional\invoke;
class ShortUrlDataTransformer implements DataTransformerInterface class ShortUrlDataTransformer implements DataTransformerInterface

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Util;
use Doctrine\Common\Collections; use Doctrine\Common\Collections;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use function str_replace; use function str_replace;
use function strtolower; use function strtolower;
use function trim; use function trim;

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase; use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase;
use function count; use function count;
class ShortUrlRepositoryTest extends DatabaseTestCase class ShortUrlRepositoryTest extends DatabaseTestCase

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\VisitRepository; use Shlinkio\Shlink\Core\Repository\VisitRepository;
use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase; use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase;
use function Functional\map; use function Functional\map;
use function range; use function range;
use function sprintf; use function sprintf;

View File

@@ -17,7 +17,9 @@ use Shlinkio\Shlink\Core\Service\UrlShortener;
use ShlinkioTest\Shlink\Common\Util\TestUtils; use ShlinkioTest\Shlink\Common\Util\TestUtils;
use Zend\Diactoros\Response; use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequest; use Zend\Diactoros\ServerRequest;
use const FILEINFO_MIME; use const FILEINFO_MIME;
use function filesize; use function filesize;
class PreviewActionTest extends TestCase class PreviewActionTest extends TestCase

View File

@@ -18,4 +18,28 @@ class VisitLocationTest extends TestCase
$this->assertSame('1000.7', $location->getLatitude()); $this->assertSame('1000.7', $location->getLatitude());
$this->assertSame('-2000.4', $location->getLongitude()); $this->assertSame('-2000.4', $location->getLongitude());
} }
/**
* @test
* @dataProvider provideArgs
*/
public function isEmptyReturnsTrueWhenAllValuesAreEmpty(array $args, bool $isEmpty): void
{
$payload = new Location(...$args);
$location = new VisitLocation($payload);
$this->assertEquals($isEmpty, $location->isEmpty());
}
public function provideArgs(): iterable
{
yield [['', '', '', '', 0.0, 0.0, ''], true];
yield [['', '', '', '', 0.0, 0.0, 'dd'], false];
yield [['', '', '', 'dd', 0.0, 0.0, ''], false];
yield [['', '', 'dd', '', 0.0, 0.0, ''], false];
yield [['', 'dd', '', '', 0.0, 0.0, ''], false];
yield [['dd', '', '', '', 0.0, 0.0, ''], false];
yield [['', '', '', '', 1.0, 0.0, ''], false];
yield [['', '', '', '', 0.0, 1.0, ''], false];
}
} }

View File

@@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Exception;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException; use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException;
use function Functional\map; use function Functional\map;
use function range; use function range;
use function sprintf; use function sprintf;

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Exception;
use Exception;
use LogicException;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
use Shlinkio\Shlink\Core\Exception\RuntimeException;
use Throwable;
use function count;
use function func_get_args;
use function random_int;
class IpCannotBeLocatedExceptionTest extends TestCase
{
/** @test */
public function forEmptyAddressInitializesException(): void
{
$e = IpCannotBeLocatedException::forEmptyAddress();
$this->assertTrue($e->isNonLocatableAddress());
$this->assertEquals('Ignored visit with no IP address', $e->getMessage());
$this->assertEquals(0, $e->getCode());
$this->assertNull($e->getPrevious());
}
/** @test */
public function forLocalhostInitializesException(): void
{
$e = IpCannotBeLocatedException::forLocalhost();
$this->assertTrue($e->isNonLocatableAddress());
$this->assertEquals('Ignored localhost address', $e->getMessage());
$this->assertEquals(0, $e->getCode());
$this->assertNull($e->getPrevious());
}
/**
* @test
* @dataProvider provideErrors
*/
public function forErrorInitializesException(Throwable $prev): void
{
$e = IpCannotBeLocatedException::forError($prev);
$this->assertFalse($e->isNonLocatableAddress());
$this->assertEquals('An error occurred while locating IP', $e->getMessage());
$this->assertEquals($prev->getCode(), $e->getCode());
$this->assertSame($prev, $e->getPrevious());
}
public function provideErrors(): iterable
{
yield 'Simple exception with positive code' => [new Exception('Some message', 100)];
yield 'Runtime exception with negative code' => [new RuntimeException('Something went wrong', -50)];
yield 'Logic exception with default code' => [new LogicException('Conditions unmet')];
}
/**
* @test
* @dataProvider provideConstructorArgs
*/
public function constructorInitializesException(): void
{
$args = func_get_args();
[$isNonLocatableAddress, $message] = $args;
$code = $args[2] ?? 0;
$prev = $args[3] ?? null;
switch (count($args)) {
case 2:
$e = new IpCannotBeLocatedException($isNonLocatableAddress, $message);
break;
case 3:
$e = new IpCannotBeLocatedException($isNonLocatableAddress, $message, $code);
break;
default:
$e = new IpCannotBeLocatedException($isNonLocatableAddress, $message, $code, $prev);
}
$this->assertEquals($isNonLocatableAddress, $e->isNonLocatableAddress());
$this->assertEquals($message, $e->getMessage());
$this->assertEquals($code, $e->getCode());
$this->assertEquals($prev, $e->getPrevious());
}
public function provideConstructorArgs(): iterable
{
yield 'without default args' => [true, 'Message'];
yield 'without prev' => [true, 'Message', random_int(1, 100)];
yield 'without all args' => [false, 'Foo', random_int(1, 100), new RuntimeException('Foo')];
}
}

View File

@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions; use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService;
use function Functional\map; use function Functional\map;
use function range; use function range;

View File

@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Service\ShortUrlService; use Shlinkio\Shlink\Core\Service\ShortUrlService;
use function count; use function count;
class ShortUrlServiceTest extends TestCase class ShortUrlServiceTest extends TestCase

View File

@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\VisitRepository; use Shlinkio\Shlink\Core\Repository\VisitRepository;
use Shlinkio\Shlink\Core\Service\VisitService; use Shlinkio\Shlink\Core\Service\VisitService;
use function array_shift; use function array_shift;
use function count; use function count;
use function floor; use function floor;
@@ -70,8 +71,11 @@ class VisitServiceTest extends TestCase
$clear->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1); $clear->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1);
} }
/** @test */ /**
public function visitsWhichCannotBeLocatedAreIgnored(): void * @test
* @dataProvider provideIsNonLocatableAddress
*/
public function visitsWhichCannotBeLocatedAreIgnoredOrLocatedAsEmpty(bool $isNonLocatableAddress): void
{ {
$unlocatedVisits = [ $unlocatedVisits = [
new Visit(new ShortUrl('foo'), Visitor::emptyInstance()), new Visit(new ShortUrl('foo'), Visitor::emptyInstance()),
@@ -88,14 +92,20 @@ class VisitServiceTest extends TestCase
$clear = $this->em->clear()->will(function () { $clear = $this->em->clear()->will(function () {
}); });
$this->visitService->locateUnlocatedVisits(function () { $this->visitService->locateUnlocatedVisits(function () use ($isNonLocatableAddress) {
throw new IpCannotBeLocatedException('Cannot be located'); throw new IpCannotBeLocatedException($isNonLocatableAddress, 'Cannot be located');
}); });
$findUnlocatedVisits->shouldHaveBeenCalledOnce(); $findUnlocatedVisits->shouldHaveBeenCalledOnce();
$getRepo->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce();
$persist->shouldNotHaveBeenCalled(); $persist->shouldHaveBeenCalledTimes($isNonLocatableAddress ? 1 : 0);
$flush->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce();
$clear->shouldHaveBeenCalledOnce(); $clear->shouldHaveBeenCalledOnce();
} }
public function provideIsNonLocatableAddress(): iterable
{
yield 'The address is locatable' => [false];
yield 'The address is non-locatable' => [true];
}
} }

View File

@@ -8,6 +8,7 @@ use Fig\Http\Message\StatusCodeInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use function array_merge; use function array_merge;
abstract class AbstractRestAction implements RequestHandlerInterface, RequestMethodInterface, StatusCodeInterface abstract class AbstractRestAction implements RequestHandlerInterface, RequestMethodInterface, StatusCodeInterface

View File

@@ -16,6 +16,7 @@ use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Throwable; use Throwable;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
abstract class AbstractCreateShortUrlAction extends AbstractRestAction abstract class AbstractCreateShortUrlAction extends AbstractRestAction

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class DeleteShortUrlAction extends AbstractRestAction class DeleteShortUrlAction extends AbstractRestAction

View File

@@ -13,6 +13,7 @@ use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class EditShortUrlAction extends AbstractRestAction class EditShortUrlAction extends AbstractRestAction

View File

@@ -11,6 +11,7 @@ use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class EditShortUrlTagsAction extends AbstractRestAction class EditShortUrlTagsAction extends AbstractRestAction

View File

@@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class ResolveShortUrlAction extends AbstractRestAction class ResolveShortUrlAction extends AbstractRestAction

View File

@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\EmptyResponse; use Zend\Diactoros\Response\EmptyResponse;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class UpdateTagAction extends AbstractRestAction class UpdateTagAction extends AbstractRestAction

View File

@@ -13,6 +13,7 @@ use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function sprintf; use function sprintf;
class GetVisitsAction extends AbstractRestAction class GetVisitsAction extends AbstractRestAction

View File

@@ -8,6 +8,7 @@ use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use Shlinkio\Shlink\Rest\Exception\AuthenticationException; use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
use UnexpectedValueException; use UnexpectedValueException;
use function time; use function time;
class JWTService implements JWTServiceInterface class JWTService implements JWTServiceInterface

View File

@@ -9,6 +9,7 @@ use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException; use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Throwable; use Throwable;
use function count; use function count;
use function explode; use function explode;
use function sprintf; use function sprintf;

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Rest\Authentication;
use Psr\Container; use Psr\Container;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException; use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
use function array_filter; use function array_filter;
use function array_reduce; use function array_reduce;
use function array_shift; use function array_shift;

View File

@@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Rest;
use Zend\Config\Factory; use Zend\Config\Factory;
use Zend\Stdlib\Glob; use Zend\Stdlib\Glob;
use function sprintf; use function sprintf;
class ConfigProvider class ConfigProvider

View File

@@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Throwable; use Throwable;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use function str_replace; use function str_replace;
use function strtoupper; use function strtoupper;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Exception; namespace Shlinkio\Shlink\Rest\Exception;
use Throwable; use Throwable;
use function sprintf; use function sprintf;
class VerifyAuthenticationException extends RuntimeException class VerifyAuthenticationException extends RuntimeException

View File

@@ -19,6 +19,7 @@ use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router\RouteResult; use Zend\Expressive\Router\RouteResult;
use function Functional\contains; use function Functional\contains;
use function implode; use function implode;
use function sprintf; use function sprintf;

Some files were not shown because too many files have changed in this diff Show More