mirror of
https://github.com/shlinkio/shlink.git
synced 2026-11-01 17:43:05 -06:00
Merge pull request #2607 from acelaya-forks/mago-poc
Mago proof of concept
This commit is contained in:
453 files changed
+6333
-4428
No files matched your search
@@ -29,7 +29,7 @@ use function gc_collect_cycles;
|
||||
// Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
|
||||
gc_collect_cycles();
|
||||
|
||||
if (! $keepRunning) {
|
||||
if (!$keepRunning) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ use function Shlinkio\Shlink\Config\env;
|
||||
} else {
|
||||
$requestIdMiddleware = $container->get(RequestIdMiddleware::class);
|
||||
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks(
|
||||
fn (string $requestId) => $requestIdMiddleware->setCurrentRequestId($requestId),
|
||||
$requestIdMiddleware->setCurrentRequestId(...),
|
||||
);
|
||||
}
|
||||
})();
|
||||
+7
-3
@@ -59,6 +59,7 @@
|
||||
"symfony/string": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"carthage-software/mago": "^1.27",
|
||||
"devizzent/cebe-php-openapi": "^1.1.2",
|
||||
"devster/ubench": "^2.1",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
@@ -68,7 +69,6 @@
|
||||
"phpunit/php-code-coverage": "^13.0",
|
||||
"phpunit/phpcov": "^12.0",
|
||||
"phpunit/phpunit": "^13.0",
|
||||
"shlinkio/php-coding-standard": "~2.5.0",
|
||||
"shlinkio/shlink-test-utils": "^4.5",
|
||||
"symfony/var-dumper": "^8.0",
|
||||
"veewee/composer-run-parallel": "^1.4"
|
||||
@@ -105,8 +105,12 @@
|
||||
"@parallel cs stan openapi:validate test:unit:ci test:db:sqlite:ci test:db:postgres test:db:mysql test:db:maria test:db:ms",
|
||||
"@parallel test:api:ci test:cli:ci"
|
||||
],
|
||||
"cs": "phpcs -s",
|
||||
"cs:fix": "phpcbf",
|
||||
"lint": "mago lint",
|
||||
"lint:fix": "mago lint --fix",
|
||||
"format": "mago format",
|
||||
"format:check": "mago format --check",
|
||||
"cs": ["@lint", "@format:check"],
|
||||
"cs:fix": ["@lint:fix", "@format"],
|
||||
"stan": ["@putenv APP_ENV=test", "phpstan analyse"],
|
||||
"test": [
|
||||
"@parallel test:unit test:db",
|
||||
|
||||
@@ -7,14 +7,16 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
return (static function (): array {
|
||||
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv()];
|
||||
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||
'redis' => [
|
||||
'servers' => $redisServers,
|
||||
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||
'username' => EnvVars::REDIS_SERVERS_USER->loadFromEnv(),
|
||||
'password' => EnvVars::REDIS_SERVERS_PASSWORD->loadFromEnv(),
|
||||
],
|
||||
];
|
||||
$cacheRedisBlock = $redisServers === null
|
||||
? []
|
||||
: [
|
||||
'redis' => [
|
||||
'servers' => $redisServers,
|
||||
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||
'username' => EnvVars::REDIS_SERVERS_USER->loadFromEnv(),
|
||||
'password' => EnvVars::REDIS_SERVERS_PASSWORD->loadFromEnv(),
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
'cache' => [
|
||||
|
||||
@@ -9,12 +9,10 @@ return (function () {
|
||||
$isDev = EnvVars::isDevEnv();
|
||||
|
||||
return [
|
||||
|
||||
'debug' => $isDev,
|
||||
|
||||
// Disabling config cache for cli, ensures it's never used for RoadRunner, and also that console
|
||||
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||
ConfigAggregator::ENABLE_CACHE => ! $isDev && PHP_SAPI !== 'cli',
|
||||
|
||||
ConfigAggregator::ENABLE_CACHE => !$isDev && PHP_SAPI !== 'cli',
|
||||
];
|
||||
})();
|
||||
@@ -17,7 +17,6 @@ use Spiral\RoadRunner\WorkerInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||
@@ -49,5 +48,4 @@ return [
|
||||
UploadedFileFactoryInterface::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -33,14 +33,18 @@ return (static function (): array {
|
||||
};
|
||||
$driverOptions = match ($driver) {
|
||||
'mssql' => ['TrustServerCertificate' => 'true'],
|
||||
'maria', 'mysql' => ! $useEncryption ? [] : [
|
||||
1007 => true, // PDO::MYSQL_ATTR_SSL_KEY: Require using SSL
|
||||
1014 => false, // PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT: Trust any certificate
|
||||
],
|
||||
'postgres' => ! $useEncryption ? [] : [
|
||||
'sslmode' => 'require', // Require connections to be encrypted
|
||||
'sslrootcert' => '', // Allow any certificate
|
||||
],
|
||||
'maria', 'mysql' => !$useEncryption
|
||||
? []
|
||||
: [
|
||||
1007 => true, // PDO::MYSQL_ATTR_SSL_KEY: Require using SSL
|
||||
1014 => false, // PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT: Trust any certificate
|
||||
],
|
||||
'postgres' => !$useEncryption
|
||||
? []
|
||||
: [
|
||||
'sslmode' => 'require', // Require connections to be encrypted
|
||||
'sslrootcert' => '', // Allow any certificate
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
$connection = match ($driver) {
|
||||
@@ -62,7 +66,6 @@ return (static function (): array {
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
@@ -75,6 +78,5 @@ return (static function (): array {
|
||||
],
|
||||
'connection' => $connection,
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
@@ -9,7 +9,6 @@ use Shlinkio\Shlink\Common\Logger;
|
||||
use function Shlinkio\Shlink\Core\toProblemDetailsType;
|
||||
|
||||
return [
|
||||
|
||||
'problem-details' => [
|
||||
'default_types_map' => [
|
||||
404 => toProblemDetailsType('not-found'),
|
||||
@@ -31,5 +30,4 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -5,11 +5,9 @@ declare(strict_types=1);
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => __DIR__ . '/../../data/temp-geolite',
|
||||
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -8,7 +8,6 @@ use Shlinkio\Shlink\Installer\Config\Option;
|
||||
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
|
||||
|
||||
return [
|
||||
|
||||
'installer' => [
|
||||
'enabled_options' => [
|
||||
Option\Server\RuntimeConfigOption::class,
|
||||
@@ -102,5 +101,4 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -15,7 +15,6 @@ return (static function (): array {
|
||||
$proxiesIsHopCount = is_numeric($trustedProxies);
|
||||
|
||||
return [
|
||||
|
||||
// Configuration for RKA\Middleware\IpAddress
|
||||
'rka' => [
|
||||
'ip_address' => [
|
||||
@@ -43,6 +42,5 @@ return (static function (): array {
|
||||
IpAddress::class => IpAddressFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
@@ -12,7 +12,6 @@ use Symfony\Component\Lock;
|
||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||
|
||||
return [
|
||||
|
||||
'locks' => [
|
||||
'locks_dir' => __DIR__ . '/../../data/locks',
|
||||
],
|
||||
@@ -46,5 +45,4 @@ return [
|
||||
Lock\LockFactory::class => ['lock_store'],
|
||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -39,20 +39,20 @@ return (static function (): array {
|
||||
$useStreamForShlinkLogger = $isDev || env('SHLINK_RUNTIME') !== null;
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'Shlink' => $useStreamForShlinkLogger ? [
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'destination' => 'php://stderr',
|
||||
...$buildCommonConfig(),
|
||||
] : [
|
||||
'type' => LoggerType::FILE->value,
|
||||
...$buildCommonConfig(),
|
||||
],
|
||||
'Shlink' => $useStreamForShlinkLogger
|
||||
? [
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'destination' => 'php://stderr',
|
||||
...$buildCommonConfig(),
|
||||
] : [
|
||||
'type' => LoggerType::FILE->value,
|
||||
...$buildCommonConfig(),
|
||||
],
|
||||
'Access' => [
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'destination' => 'php://stderr',
|
||||
...$buildCommonConfig(! runningInRoadRunner()),
|
||||
...$buildCommonConfig(!runningInRoadRunner()),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -75,6 +75,5 @@ return (static function (): array {
|
||||
ConfigAbstractFactory::class => [
|
||||
RequestIdProvider::class => [RequestIdMiddleware::class],
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
@@ -9,7 +9,6 @@ use Symfony\Component\Mercure\Hub;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
|
||||
return [
|
||||
|
||||
// This config is used by shlink-common. Do not delete
|
||||
'mercure' => [
|
||||
'enabled' => EnvVars::MERCURE_ENABLED->loadFromEnv(),
|
||||
@@ -35,5 +34,4 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -14,7 +14,6 @@ use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||
use Shlinkio\Shlink\Core\Geolocation\Middleware\IpGeolocationMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'error-handler' => [
|
||||
'middleware' => [
|
||||
@@ -82,5 +81,4 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return [
|
||||
|
||||
// This config is used by shlink-common. Do not delete
|
||||
'rabbitmq' => [
|
||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(),
|
||||
@@ -16,5 +15,4 @@ return [
|
||||
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -6,7 +6,6 @@ use Mezzio\Router\FastRouteRouter;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'base_path' => EnvVars::BASE_PATH->loadFromEnv(),
|
||||
|
||||
@@ -17,5 +16,4 @@ return [
|
||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -24,7 +24,6 @@ return (static function (): array {
|
||||
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv() ? '[/]' : '';
|
||||
|
||||
return [
|
||||
|
||||
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||
'routes' => [
|
||||
// Rest
|
||||
@@ -108,6 +107,5 @@ return (static function (): array {
|
||||
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
+257
-16
@@ -7,33 +7,274 @@ namespace Shlinkio\Shlink;
|
||||
use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||
|
||||
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||
|
||||
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||
|
||||
const MIN_SHORT_CODES_LENGTH = 4;
|
||||
|
||||
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
|
||||
|
||||
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||
|
||||
const DEFAULT_REDIRECT_CACHE_VISIBILITY = 'private';
|
||||
|
||||
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||
|
||||
const IP_ADDRESS_REQUEST_ATTRIBUTE = 'remote_address';
|
||||
|
||||
const REDIRECT_URL_REQUEST_ATTRIBUTE = 'redirect_url';
|
||||
|
||||
/**
|
||||
* List of ISO 3166-1 alpha-2 two-letter country codes https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
*/
|
||||
const ISO_COUNTRY_CODES = [
|
||||
'AF', 'AX', 'AL', 'DZ', 'AS', 'AD', 'AO', 'AI', 'AQ', 'AG', 'AR', 'AM', 'AW', 'AU', 'AT', 'AZ',
|
||||
'BS', 'BH', 'BD', 'BB', 'BY', 'BE', 'BZ', 'BJ', 'BM', 'BT', 'BO', 'BQ', 'BA', 'BW', 'BV', 'BR',
|
||||
'IO', 'BN', 'BG', 'BF', 'BI', 'CV', 'KH', 'CM', 'CA', 'KY', 'CF', 'TD', 'CL', 'CN', 'CX', 'CC',
|
||||
'CO', 'KM', 'CG', 'CD', 'CK', 'CR', 'CI', 'HR', 'CU', 'CW', 'CY', 'CZ', 'DK', 'DJ', 'DM', 'DO',
|
||||
'EC', 'EG', 'SV', 'GQ', 'ER', 'EE', 'SZ', 'ET', 'FK', 'FO', 'FJ', 'FI', 'FR', 'GF', 'PF', 'TF',
|
||||
'GA', 'GM', 'GE', 'DE', 'GH', 'GI', 'GR', 'GL', 'GD', 'GP', 'GU', 'GT', 'GG', 'GN', 'GW', 'GY',
|
||||
'HT', 'HM', 'VA', 'HN', 'HK', 'HU', 'IS', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IM', 'IL', 'IT', 'JM',
|
||||
'JP', 'JE', 'JO', 'KZ', 'KE', 'KI', 'KP', 'KR', 'KW', 'KG', 'LA', 'LV', 'LB', 'LS', 'LR', 'LY',
|
||||
'LI', 'LT', 'LU', 'MO', 'MG', 'MW', 'MY', 'MV', 'ML', 'MT', 'MH', 'MQ', 'MR', 'MU', 'YT', 'MX',
|
||||
'FM', 'MD', 'MC', 'MN', 'ME', 'MS', 'MA', 'MZ', 'MM', 'NA', 'NR', 'NP', 'NL', 'NC', 'NZ', 'NI',
|
||||
'NE', 'NG', 'NU', 'NF', 'MK', 'MP', 'NO', 'OM', 'PK', 'PW', 'PS', 'PA', 'PG', 'PY', 'PE', 'PH',
|
||||
'PN', 'PL', 'PT', 'PR', 'QA', 'RE', 'RO', 'RU', 'RW', 'BL', 'SH', 'KN', 'LC', 'MF', 'PM', 'VC',
|
||||
'WS', 'SM', 'ST', 'SA', 'SN', 'RS', 'SC', 'SL', 'SG', 'SX', 'SK', 'SI', 'SB', 'SO', 'ZA', 'GS',
|
||||
'SS', 'ES', 'LK', 'SD', 'SR', 'SJ', 'SE', 'CH', 'SY', 'TW', 'TJ', 'TZ', 'TH', 'TL', 'TG', 'TK',
|
||||
'TO', 'TT', 'TN', 'TR', 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU',
|
||||
'VE', 'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW',
|
||||
'AF',
|
||||
'AX',
|
||||
'AL',
|
||||
'DZ',
|
||||
'AS',
|
||||
'AD',
|
||||
'AO',
|
||||
'AI',
|
||||
'AQ',
|
||||
'AG',
|
||||
'AR',
|
||||
'AM',
|
||||
'AW',
|
||||
'AU',
|
||||
'AT',
|
||||
'AZ',
|
||||
'BS',
|
||||
'BH',
|
||||
'BD',
|
||||
'BB',
|
||||
'BY',
|
||||
'BE',
|
||||
'BZ',
|
||||
'BJ',
|
||||
'BM',
|
||||
'BT',
|
||||
'BO',
|
||||
'BQ',
|
||||
'BA',
|
||||
'BW',
|
||||
'BV',
|
||||
'BR',
|
||||
'IO',
|
||||
'BN',
|
||||
'BG',
|
||||
'BF',
|
||||
'BI',
|
||||
'CV',
|
||||
'KH',
|
||||
'CM',
|
||||
'CA',
|
||||
'KY',
|
||||
'CF',
|
||||
'TD',
|
||||
'CL',
|
||||
'CN',
|
||||
'CX',
|
||||
'CC',
|
||||
'CO',
|
||||
'KM',
|
||||
'CG',
|
||||
'CD',
|
||||
'CK',
|
||||
'CR',
|
||||
'CI',
|
||||
'HR',
|
||||
'CU',
|
||||
'CW',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DK',
|
||||
'DJ',
|
||||
'DM',
|
||||
'DO',
|
||||
'EC',
|
||||
'EG',
|
||||
'SV',
|
||||
'GQ',
|
||||
'ER',
|
||||
'EE',
|
||||
'SZ',
|
||||
'ET',
|
||||
'FK',
|
||||
'FO',
|
||||
'FJ',
|
||||
'FI',
|
||||
'FR',
|
||||
'GF',
|
||||
'PF',
|
||||
'TF',
|
||||
'GA',
|
||||
'GM',
|
||||
'GE',
|
||||
'DE',
|
||||
'GH',
|
||||
'GI',
|
||||
'GR',
|
||||
'GL',
|
||||
'GD',
|
||||
'GP',
|
||||
'GU',
|
||||
'GT',
|
||||
'GG',
|
||||
'GN',
|
||||
'GW',
|
||||
'GY',
|
||||
'HT',
|
||||
'HM',
|
||||
'VA',
|
||||
'HN',
|
||||
'HK',
|
||||
'HU',
|
||||
'IS',
|
||||
'IN',
|
||||
'ID',
|
||||
'IR',
|
||||
'IQ',
|
||||
'IE',
|
||||
'IM',
|
||||
'IL',
|
||||
'IT',
|
||||
'JM',
|
||||
'JP',
|
||||
'JE',
|
||||
'JO',
|
||||
'KZ',
|
||||
'KE',
|
||||
'KI',
|
||||
'KP',
|
||||
'KR',
|
||||
'KW',
|
||||
'KG',
|
||||
'LA',
|
||||
'LV',
|
||||
'LB',
|
||||
'LS',
|
||||
'LR',
|
||||
'LY',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'MO',
|
||||
'MG',
|
||||
'MW',
|
||||
'MY',
|
||||
'MV',
|
||||
'ML',
|
||||
'MT',
|
||||
'MH',
|
||||
'MQ',
|
||||
'MR',
|
||||
'MU',
|
||||
'YT',
|
||||
'MX',
|
||||
'FM',
|
||||
'MD',
|
||||
'MC',
|
||||
'MN',
|
||||
'ME',
|
||||
'MS',
|
||||
'MA',
|
||||
'MZ',
|
||||
'MM',
|
||||
'NA',
|
||||
'NR',
|
||||
'NP',
|
||||
'NL',
|
||||
'NC',
|
||||
'NZ',
|
||||
'NI',
|
||||
'NE',
|
||||
'NG',
|
||||
'NU',
|
||||
'NF',
|
||||
'MK',
|
||||
'MP',
|
||||
'NO',
|
||||
'OM',
|
||||
'PK',
|
||||
'PW',
|
||||
'PS',
|
||||
'PA',
|
||||
'PG',
|
||||
'PY',
|
||||
'PE',
|
||||
'PH',
|
||||
'PN',
|
||||
'PL',
|
||||
'PT',
|
||||
'PR',
|
||||
'QA',
|
||||
'RE',
|
||||
'RO',
|
||||
'RU',
|
||||
'RW',
|
||||
'BL',
|
||||
'SH',
|
||||
'KN',
|
||||
'LC',
|
||||
'MF',
|
||||
'PM',
|
||||
'VC',
|
||||
'WS',
|
||||
'SM',
|
||||
'ST',
|
||||
'SA',
|
||||
'SN',
|
||||
'RS',
|
||||
'SC',
|
||||
'SL',
|
||||
'SG',
|
||||
'SX',
|
||||
'SK',
|
||||
'SI',
|
||||
'SB',
|
||||
'SO',
|
||||
'ZA',
|
||||
'GS',
|
||||
'SS',
|
||||
'ES',
|
||||
'LK',
|
||||
'SD',
|
||||
'SR',
|
||||
'SJ',
|
||||
'SE',
|
||||
'CH',
|
||||
'SY',
|
||||
'TW',
|
||||
'TJ',
|
||||
'TZ',
|
||||
'TH',
|
||||
'TL',
|
||||
'TG',
|
||||
'TK',
|
||||
'TO',
|
||||
'TT',
|
||||
'TN',
|
||||
'TR',
|
||||
'TM',
|
||||
'TC',
|
||||
'TV',
|
||||
'UG',
|
||||
'UA',
|
||||
'AE',
|
||||
'GB',
|
||||
'US',
|
||||
'UM',
|
||||
'UY',
|
||||
'UZ',
|
||||
'VU',
|
||||
'VE',
|
||||
'VN',
|
||||
'VG',
|
||||
'VI',
|
||||
'WF',
|
||||
'EH',
|
||||
'YE',
|
||||
'ZM',
|
||||
'ZW',
|
||||
];
|
||||
@@ -32,7 +32,7 @@ date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv());
|
||||
|
||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||
if (!class_exists(LOCAL_LOCK_FACTORY)) {
|
||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||
}
|
||||
|
||||
|
||||
+30
-10
@@ -5,27 +5,47 @@ declare(strict_types=1);
|
||||
namespace ShlinkioTest\Shlink;
|
||||
|
||||
const API_TESTS_HOST = '127.0.0.1';
|
||||
|
||||
const API_TESTS_PORT = 9999;
|
||||
|
||||
const ANDROID_USER_AGENT = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
const ANDROID_USER_AGENT =
|
||||
'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/109.0.5414.86 Mobile Safari/537.36';
|
||||
const IOS_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 '
|
||||
|
||||
const IOS_USER_AGENT =
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 '
|
||||
. '(KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15';
|
||||
const WINDOWS_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
|
||||
const WINDOWS_USER_AGENT =
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/138.0.0.0 Safari/537.36 Edg/138.0.3351.95';
|
||||
const LINUX_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
|
||||
const LINUX_USER_AGENT =
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'HeadlessChrome/81.0.4044.113 Safari/537.36';
|
||||
const MACOS_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) '
|
||||
|
||||
const MACOS_USER_AGENT =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) '
|
||||
. 'Version/18.4 Safari/605.1.15';
|
||||
const CHROMEOS_USER_AGENT = 'Mozilla/5.0 (X11; CrOS x86_64 16181.61.0) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
|
||||
const CHROMEOS_USER_AGENT =
|
||||
'Mozilla/5.0 (X11; CrOS x86_64 16181.61.0) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/134.0.6998.198 Safari/537.36';
|
||||
|
||||
const BROWSER_CHROME_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
const BROWSER_CHROME_USER_AGENT =
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/146.0.0.0 Safari/537.36';
|
||||
|
||||
const BROWSER_FIREFOX_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0';
|
||||
const BROWSER_EDGE_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
|
||||
const BROWSER_EDGE_USER_AGENT =
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/146.0.0.0 Safari/537.36 Edg/146.0.3856.62';
|
||||
const BROWSER_SAFARI_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_4) AppleWebKit/605.1.15 '
|
||||
|
||||
const BROWSER_SAFARI_USER_AGENT =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_4) AppleWebKit/605.1.15 '
|
||||
. '(KHTML, like Gecko) Version/26.0 Safari/605.1.15';
|
||||
const BROWSER_OPERA_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
|
||||
const BROWSER_OPERA_USER_AGENT =
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
. 'Chrome/146.0.0.0 Safari/537.36 OPR/128.0.0.0';
|
||||
@@ -5,11 +5,9 @@ declare(strict_types=1);
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return [
|
||||
|
||||
EnvVars::APP_ENV->value => 'test',
|
||||
|
||||
// URL shortener
|
||||
EnvVars::DEFAULT_DOMAIN->value => 's.test',
|
||||
EnvVars::IS_HTTPS_ENABLED->value => false,
|
||||
|
||||
];
|
||||
@@ -31,13 +31,14 @@ $isE2eTest = $isApiTest || $isCliTest;
|
||||
|
||||
$coverageType = env('GENERATE_COVERAGE');
|
||||
$generateCoverage = $coverageType === 'yes';
|
||||
$coverage = $isE2eTest && $generateCoverage ? CoverageHelper::createCoverageForDirectories(
|
||||
[
|
||||
__DIR__ . '/../../module/Core/src',
|
||||
__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src',
|
||||
],
|
||||
__DIR__ . '/../../build/coverage-' . $testEnv,
|
||||
) : null;
|
||||
$coverage = $isE2eTest && $generateCoverage
|
||||
? CoverageHelper::createCoverageForDirectories(
|
||||
[
|
||||
__DIR__ . '/../../module/Core/src',
|
||||
__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src',
|
||||
],
|
||||
__DIR__ . '/../../build/coverage-' . $testEnv,
|
||||
) : null;
|
||||
|
||||
$buildDbConnection = static function (): array {
|
||||
$driver = env('DB_DRIVER', 'sqlite');
|
||||
@@ -88,7 +89,6 @@ $buildTestLoggerConfig = static fn (string $filename) => [
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
ConfigAggregator::ENABLE_CACHE => false,
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||
@@ -106,12 +106,14 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||
'capture_code_coverage' => [
|
||||
'middleware' => new CoverageMiddleware($coverage),
|
||||
'priority' => 9999,
|
||||
'middleware_pipeline' => !$isApiTest
|
||||
? []
|
||||
: [
|
||||
'capture_code_coverage' => [
|
||||
'middleware' => new CoverageMiddleware($coverage),
|
||||
'priority' => 9999,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'services' => [
|
||||
@@ -123,11 +125,12 @@ return [
|
||||
'factories' => [
|
||||
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
||||
],
|
||||
'delegators' => $isCliTest ? [
|
||||
Application::class => [
|
||||
new CliCoverageDelegator($coverage),
|
||||
],
|
||||
] : [],
|
||||
'delegators' => $isCliTest
|
||||
? [
|
||||
Application::class => [
|
||||
new CliCoverageDelegator($coverage),
|
||||
],
|
||||
] : [],
|
||||
],
|
||||
|
||||
'entity_manager' => [
|
||||
@@ -145,5 +148,4 @@ return [
|
||||
'Shlink' => $buildTestLoggerConfig('shlink.log'),
|
||||
'Access' => $buildTestLoggerConfig('access.log'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -22,6 +22,6 @@ final class <className> extends AbstractMigration
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
return !$this->connection->getDatabasePlatform() instanceof MySQLPlatform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#:schema https://mago.carthage.software/1.27.1/schema.json
|
||||
# Welcome to Mago!
|
||||
# For full documentation, see https://mago.carthage.software/tools/overview
|
||||
version = "1"
|
||||
php-version = "8.4.0"
|
||||
|
||||
[source]
|
||||
workspace = "."
|
||||
paths = ["bin", "config", "module"]
|
||||
excludes = ["vendor"]
|
||||
|
||||
[source.glob]
|
||||
literal-separator = true
|
||||
|
||||
[formatter]
|
||||
print-width = 120
|
||||
tab-width = 4
|
||||
use-tabs = false
|
||||
null-type-hint = "null_pipe_last"
|
||||
space-before-arrow-function-parameter-list-parenthesis = true
|
||||
break-promoted-properties-list = false
|
||||
parameter-attribute-on-new-line = false
|
||||
preserve-breaking-member-access-chain = true
|
||||
preserve-breaking-member-access-chain-first-method-on-same-line = true
|
||||
preserve-breaking-argument-list = true
|
||||
preserve-breaking-array-like = true
|
||||
preserve-breaking-parameter-list = true
|
||||
preserve-breaking-attribute-list = true
|
||||
preserve-breaking-conditional-expression = true
|
||||
preserve-breaking-condition-expression = true
|
||||
preserve-redundant-logical-binary-expression-parentheses = true
|
||||
|
||||
[linter]
|
||||
integrations = ["phpunit"]
|
||||
|
||||
[linter.rules]
|
||||
no-literal-password = {
|
||||
exclude = [
|
||||
"config/test",
|
||||
"module/CLI/test",
|
||||
"module/CLI/test-cli",
|
||||
"module/Core/test",
|
||||
"module/Core/test-api",
|
||||
"module/Core/test-db",
|
||||
"module/Rest/test",
|
||||
"module/Rest/test-api",
|
||||
"module/Rest/test-db"
|
||||
]
|
||||
}
|
||||
no-empty-catch-clause = {
|
||||
exclude = [
|
||||
"module/CLI/test",
|
||||
"module/CLI/test-cli",
|
||||
"module/Core/test",
|
||||
"module/Core/test-api",
|
||||
"module/Core/test-db",
|
||||
"module/Rest/test",
|
||||
"module/Rest/test-api",
|
||||
"module/Rest/test-db"
|
||||
]
|
||||
}
|
||||
no-empty-loop = {
|
||||
exclude = [
|
||||
"module/CLI/test",
|
||||
"module/CLI/test-cli",
|
||||
"module/Core/test",
|
||||
"module/Core/test-api",
|
||||
"module/Core/test-db",
|
||||
"module/Rest/test",
|
||||
"module/Rest/test-api",
|
||||
"module/Rest/test-db"
|
||||
]
|
||||
}
|
||||
|
||||
prefer-static-closure = { level = "warning" }
|
||||
array-style = { level = "warning" }
|
||||
prefer-early-continue = { max_allowed_statements = 2 }
|
||||
no-fully-qualified-global-class-like = { enabled = true, level = "warning" }
|
||||
no-fully-qualified-global-constant = { enabled = true, level = "warning" }
|
||||
no-fully-qualified-global-function = { enabled = true, level = "warning" }
|
||||
ambiguous-constant-access = { enabled = true, level = "warning" }
|
||||
ambiguous-function-call = {
|
||||
enabled = true,
|
||||
level = "warning",
|
||||
exclude = [
|
||||
"module/Core/functions",
|
||||
"module/Core/config/entities-mappings"
|
||||
]
|
||||
}
|
||||
|
||||
no-multi-assignments = { enabled = false }
|
||||
no-assign-in-condition = { enabled = false }
|
||||
literal-named-argument = { enabled = false }
|
||||
tagged-fixme = { enabled = false }
|
||||
tagged-todo = { enabled = false }
|
||||
no-boolean-flag-parameter = { enabled = false }
|
||||
no-else-clause = { enabled = false }
|
||||
no-isset = { enabled = false }
|
||||
no-empty = { enabled = false }
|
||||
no-shorthand-ternary = { enabled = false }
|
||||
no-ini-set = { enabled = false }
|
||||
too-many-enum-cases = { enabled = false }
|
||||
function-name = { enabled = false }
|
||||
|
||||
# TODO Fine tune these
|
||||
too-many-methods = { enabled = false }
|
||||
too-many-properties = { enabled = false }
|
||||
excessive-parameter-list = { enabled = false }
|
||||
cyclomatic-complexity = { enabled = false }
|
||||
kan-defect = { enabled = false }
|
||||
halstead = { effort-threshold = 7000, enabled = false }
|
||||
|
||||
# TODO Enable this
|
||||
sensitive-parameter = { enabled = false }
|
||||
|
||||
[analyzer]
|
||||
plugins = []
|
||||
find-unused-definitions = true
|
||||
find-unused-expressions = true
|
||||
analyze-dead-code = false
|
||||
memoize-properties = true
|
||||
check-throws = true
|
||||
unchecked-exceptions = ["Error", "LogicException"]
|
||||
unchecked-exception-classes = []
|
||||
check-missing-override = true
|
||||
find-unused-parameters = true
|
||||
strict-list-index-checks = true
|
||||
strict-array-index-existence = false
|
||||
allow-array-truthy-operand = false
|
||||
no-boolean-literal-comparison = false
|
||||
check-missing-type-hints = false
|
||||
register-super-globals = true
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI;
|
||||
|
||||
return [
|
||||
|
||||
'cli' => [
|
||||
'commands' => [
|
||||
Command\ShortUrl\CreateShortUrlCommand::NAME => Command\ShortUrl\CreateShortUrlCommand::class,
|
||||
@@ -51,5 +50,4 @@ return [
|
||||
Command\Config\ReadEnvVarCommand::NAME => Command\Config\ReadEnvVarCommand::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -24,7 +24,6 @@ use Symfony\Component\Lock\LockFactory;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
SymfonyCli\Application::class => Factory\ApplicationFactory::class,
|
||||
@@ -151,5 +150,4 @@ return [
|
||||
],
|
||||
Command\Db\MigrateDatabaseCommand::class => [LockFactory::class, Util\PhpProcessRunner::class],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -16,8 +16,7 @@ readonly class RoleResolver implements RoleResolverInterface
|
||||
public function __construct(
|
||||
private DomainServiceInterface $domainService,
|
||||
private UrlShortenerOptions $urlShortenerOptions,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function determineRoles(ApiKeyInput $input): iterable
|
||||
{
|
||||
|
||||
@@ -21,17 +21,17 @@ use function sprintf;
|
||||
name: DeleteKeyCommand::NAME,
|
||||
description: 'Deletes an API key by name',
|
||||
help: <<<HELP
|
||||
The <info>%command.name%</info> command allows you to delete an existing API key via its name.
|
||||
The <info>%command.name%</info> command allows you to delete an existing API key via its name.
|
||||
|
||||
If no arguments are provided, you will be prompted to select one of the existing API keys.
|
||||
If no arguments are provided, you will be prompted to select one of the existing API keys.
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
You can optionally pass the API key name to be disabled:
|
||||
You can optionally pass the API key name to be disabled:
|
||||
|
||||
<info>%command.full_name% the_key_name</info>
|
||||
<info>%command.full_name% the_key_name</info>
|
||||
|
||||
HELP,
|
||||
HELP,
|
||||
)]
|
||||
class DeleteKeyCommand extends Command
|
||||
{
|
||||
@@ -60,15 +60,14 @@ class DeleteKeyCommand extends Command
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
InputInterface $input,
|
||||
#[Argument(description: 'The API key to delete.')]
|
||||
string|null $name = null,
|
||||
#[Argument(description: 'The API key to delete.')] string|null $name = null,
|
||||
): int {
|
||||
if ($name === null) {
|
||||
$io->warning('An API key name was not provided.');
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
if (! $this->shouldProceed($io, $input)) {
|
||||
if (!$this->shouldProceed($io, $input)) {
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ class DeleteKeyCommand extends Command
|
||||
|
||||
private function shouldProceed(SymfonyStyle $io, InputInterface $input): bool
|
||||
{
|
||||
if (! $input->isInteractive()) {
|
||||
if (!$input->isInteractive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@ use function sprintf;
|
||||
name: DisableKeyCommand::NAME,
|
||||
description: 'Disables an API key by name',
|
||||
help: <<<HELP
|
||||
The <info>%command.name%</info> command allows you to disable an existing API key.
|
||||
The <info>%command.name%</info> command allows you to disable an existing API key.
|
||||
|
||||
If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys.
|
||||
If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys.
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
You can optionally pass the API key name to be disabled:
|
||||
You can optionally pass the API key name to be disabled:
|
||||
|
||||
<info>%command.full_name% the_key_name</info>
|
||||
<info>%command.full_name% the_key_name</info>
|
||||
|
||||
HELP,
|
||||
HELP,
|
||||
)]
|
||||
class DisableKeyCommand extends Command
|
||||
{
|
||||
|
||||
@@ -65,10 +65,13 @@ class GenerateKeyCommand extends Command
|
||||
$io->warning('Save the key in a secure location. You will not be able to get it afterwards.');
|
||||
}
|
||||
|
||||
if (! ApiKey::isAdmin($apiKey)) {
|
||||
if (!ApiKey::isAdmin($apiKey)) {
|
||||
ShlinkTable::default($io)->render(
|
||||
['Role name', 'Role metadata'],
|
||||
$apiKey->mapRoles(fn (Role $role, array $meta) => [$role->value, arrayToString($meta, indentSize: 0)]),
|
||||
$apiKey->mapRoles(static fn (Role $role, array $meta) => [
|
||||
$role->value,
|
||||
arrayToString($meta, indentSize: 0),
|
||||
]),
|
||||
headerTitle: 'Roles',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,26 +43,31 @@ class ListKeysCommand extends Command
|
||||
)]
|
||||
bool $enabledOnly = false,
|
||||
): int {
|
||||
$rows = array_map(function (ApiKey $apiKey) use ($enabledOnly) {
|
||||
$expiration = $apiKey->expirationDate;
|
||||
$messagePattern = $this->determineMessagePattern($apiKey);
|
||||
$rows = array_map(
|
||||
function (ApiKey $apiKey) use ($enabledOnly) {
|
||||
$expiration = $apiKey->expirationDate;
|
||||
$messagePattern = $this->determineMessagePattern($apiKey);
|
||||
|
||||
// Set columns for this row
|
||||
$rowData = [sprintf($messagePattern, $apiKey->name ?? '-')];
|
||||
if (! $enabledOnly) {
|
||||
$rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey));
|
||||
}
|
||||
$rowData[] = $expiration?->toAtomString() ?? '-';
|
||||
$rowData[] = ApiKey::isAdmin($apiKey) ? 'Admin' : implode("\n", $apiKey->mapRoles(
|
||||
fn (Role $role, array $meta) => $role->toFriendlyName($meta),
|
||||
));
|
||||
// Set columns for this row
|
||||
$rowData = [sprintf($messagePattern, $apiKey->name ?? '-')];
|
||||
if (!$enabledOnly) {
|
||||
$rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey));
|
||||
}
|
||||
$rowData[] = $expiration?->toAtomString() ?? '-';
|
||||
$rowData[] = ApiKey::isAdmin($apiKey)
|
||||
? 'Admin'
|
||||
: implode("\n", $apiKey->mapRoles(
|
||||
static fn (Role $role, array $meta) => $role->toFriendlyName($meta),
|
||||
));
|
||||
|
||||
return $rowData;
|
||||
}, $this->apiKeyService->listKeys($enabledOnly));
|
||||
return $rowData;
|
||||
},
|
||||
$this->apiKeyService->listKeys($enabledOnly),
|
||||
);
|
||||
|
||||
ShlinkTable::withRowSeparators($io)->render(array_filter([
|
||||
'Name',
|
||||
! $enabledOnly ? 'Is enabled' : null,
|
||||
!$enabledOnly ? 'Is enabled' : null,
|
||||
'Expiration date',
|
||||
'Roles',
|
||||
]), $rows);
|
||||
@@ -72,7 +77,7 @@ class ListKeysCommand extends Command
|
||||
|
||||
private function determineMessagePattern(ApiKey $apiKey): string
|
||||
{
|
||||
if (! $apiKey->isEnabled()) {
|
||||
if (!$apiKey->isEnabled()) {
|
||||
return self::ERROR_STRING_PATTERN;
|
||||
}
|
||||
|
||||
@@ -81,6 +86,6 @@ class ListKeysCommand extends Command
|
||||
|
||||
private function getEnabledSymbol(ApiKey $apiKey): string
|
||||
{
|
||||
return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
||||
return !$apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class ReadEnvVarCommand extends Command
|
||||
$envVar = $io->choice('Select the env var to read', $validEnvVars);
|
||||
}
|
||||
|
||||
if (! contains($envVar, $validEnvVars)) {
|
||||
if (!contains($envVar, $validEnvVars)) {
|
||||
throw new InvalidArgumentException(sprintf('%s is not a valid Shlink environment variable', $envVar));
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class DomainRedirectsCommand extends Command
|
||||
/** @var string[] $availableDomains */
|
||||
$availableDomains = array_map(
|
||||
static fn (DomainItem $item) => $item->toString(),
|
||||
array_filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault),
|
||||
array_filter($this->domainService->listDomains(), static fn (DomainItem $item) => !$item->isDefault),
|
||||
);
|
||||
if (empty($availableDomains)) {
|
||||
$input->setArgument('domain', $askNewDomain());
|
||||
|
||||
@@ -45,12 +45,14 @@ class ListDomainsCommand extends Command
|
||||
array_map(function (DomainItem $domain) use ($showRedirects) {
|
||||
$commonValues = [$domain->toString(), $domain->isDefault ? 'Yes' : 'No'];
|
||||
|
||||
return $showRedirects
|
||||
? [
|
||||
...$commonValues,
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig),
|
||||
]
|
||||
: $commonValues;
|
||||
return (
|
||||
$showRedirects
|
||||
? [
|
||||
...$commonValues,
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig),
|
||||
]
|
||||
: $commonValues
|
||||
);
|
||||
}, $domains),
|
||||
);
|
||||
|
||||
@@ -64,9 +66,9 @@ class ListDomainsCommand extends Command
|
||||
$invalidShortUrl = $config->invalidShortUrlRedirect ?? 'N/A';
|
||||
|
||||
return <<<EOL
|
||||
* Base URL: {$baseUrl}
|
||||
* Regular 404: {$regular404}
|
||||
* Invalid short URL: {$invalidShortUrl}
|
||||
EOL;
|
||||
* Base URL: {$baseUrl}
|
||||
* Regular 404: {$regular404}
|
||||
* Invalid short URL: {$invalidShortUrl}
|
||||
EOL;
|
||||
}
|
||||
}
|
||||
@@ -24,16 +24,16 @@ use function sprintf;
|
||||
description: 'Send existing visits to the configured matomo instance',
|
||||
help: <<<HELP
|
||||
This command allows you to send existing visits from this Shlink instance to the configured Matomo server.
|
||||
|
||||
|
||||
Its intention is to allow you to configure Matomo at some point in time, and still have your whole visits
|
||||
history tracked there.
|
||||
|
||||
|
||||
This command will unconditionally send to Matomo all visits for a specific date range, so make sure you
|
||||
provide the proper limits to avoid duplicated visits.
|
||||
|
||||
|
||||
Send all visits created so far:
|
||||
<info>%command.name%</info>
|
||||
|
||||
|
||||
Send all visits created before 2024:
|
||||
<info>%command.name% --until 2023-12-31</info>
|
||||
|
||||
@@ -67,7 +67,7 @@ class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTra
|
||||
): int {
|
||||
$this->io = $io;
|
||||
|
||||
if (! $this->matomoEnabled) {
|
||||
if (!$this->matomoEnabled) {
|
||||
$this->io->warning('Matomo integration is not enabled in this Shlink instance');
|
||||
return self::INVALID;
|
||||
}
|
||||
@@ -83,9 +83,9 @@ class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTra
|
||||
'You are about to send visits from this Shlink instance to Matomo',
|
||||
'Resolved date range -> ' . dateRangeToHumanFriendly($dateRange),
|
||||
'Shlink will not check for already sent visits, which could result in some duplications. Make sure '
|
||||
. 'you have verified only visits in the right date range are going to be sent.',
|
||||
. 'you have verified only visits in the right date range are going to be sent.',
|
||||
]);
|
||||
if (! $this->io->confirm('Continue?', default: false)) {
|
||||
if (!$this->io->confirm('Continue?', default: false)) {
|
||||
return self::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,13 @@ class CreateShortUrlCommand extends Command
|
||||
$this->treeMapper->map(ShortUrlCreation::class, $inputData->toArray($this->options)),
|
||||
);
|
||||
|
||||
$result->onEventDispatchingError(static fn () => $io->isVerbose() && $io->warning(
|
||||
'Short URL properly created, but the real-time updates cannot be notified when generating the '
|
||||
$result->onEventDispatchingError(
|
||||
static fn () => $io->isVerbose()
|
||||
&& $io->warning(
|
||||
'Short URL properly created, but the real-time updates cannot be notified when generating the '
|
||||
. 'short URL from the command line. Migrate to roadrunner in order to bypass this limitation.',
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
$io->writeln([
|
||||
sprintf('Processed long URL: <info>%s</info>', $result->shortUrl->getLongUrl()),
|
||||
|
||||
@@ -37,15 +37,15 @@ class DeleteExpiredShortUrlsCommand extends Command
|
||||
bool $dryRun = false,
|
||||
): int {
|
||||
$conditions = new ExpiredShortUrlsConditions(maxVisitsReached: $evaluateMaxVisits);
|
||||
$force = $force || ! $input->isInteractive();
|
||||
$force = $force || !$input->isInteractive();
|
||||
|
||||
if (! $force && ! $dryRun) {
|
||||
if (!$force && !$dryRun) {
|
||||
$io->warning([
|
||||
'Careful!',
|
||||
'You are about to perform a destructive operation that can result in deleted short URLs and visits.',
|
||||
'This action cannot be undone. Proceed at your own risk',
|
||||
]);
|
||||
if (! $io->confirm('Continue?', default: false)) {
|
||||
if (!$io->confirm('Continue?', default: false)) {
|
||||
return self::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ class GetShortUrlVisitsCommand extends Command
|
||||
#[Argument('The short code which visits we want to get'), Ask('Which short code do you want to use?')]
|
||||
string $shortCode,
|
||||
#[MapInput] VisitsListInput $input,
|
||||
#[Option('The domain for the short code', shortcut: 'd')]
|
||||
string|null $domain = null,
|
||||
#[Option('The domain for the short code', shortcut: 'd')] string|null $domain = null,
|
||||
): int {
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain);
|
||||
$dateRange = $input->dateRange();
|
||||
|
||||
@@ -28,9 +28,7 @@ final class ShortUrlsParamsInput
|
||||
)]
|
||||
public bool $all = false;
|
||||
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.Multiple
|
||||
public int $itemsPerPage {
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.ScopeMissing
|
||||
get => $this->all ? Paginator::ALL_ITEMS : ShortUrlsParams::DEFAULT_ITEMS_PER_PAGE;
|
||||
}
|
||||
|
||||
@@ -56,9 +54,7 @@ final class ShortUrlsParamsInput
|
||||
#[Option('If --tag is provided, returns only short URLs including ALL of them')]
|
||||
public bool $tagsAll = false;
|
||||
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.Multiple
|
||||
public TagsMode $tagsMode {
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.ScopeMissing
|
||||
get => $this->tagsAll ? TagsMode::ALL : TagsMode::ANY;
|
||||
}
|
||||
|
||||
@@ -69,9 +65,7 @@ final class ShortUrlsParamsInput
|
||||
#[Option('If --exclude-tag is provided, returns only short URLs not including ANY of them')]
|
||||
public bool $excludeTagsAll = false;
|
||||
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.Multiple
|
||||
public TagsMode $excludeTagsMode {
|
||||
// phpcs:disable PSR2.Classes.PropertyDeclaration.ScopeMissing
|
||||
get => $this->excludeTagsAll ? TagsMode::ALL : TagsMode::ANY;
|
||||
}
|
||||
|
||||
@@ -95,7 +89,9 @@ final class ShortUrlsParamsInput
|
||||
|
||||
#[Option(
|
||||
'Whether to display the domain or not. Those belonging to default domain will have value '
|
||||
. '"' . Domain::DEFAULT_AUTHORITY . '"',
|
||||
. '"'
|
||||
. Domain::DEFAULT_AUTHORITY
|
||||
. '"',
|
||||
)]
|
||||
public bool $showDomain = false;
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ class ListShortUrlsCommand extends Command
|
||||
$result = $this->renderPage($io, $columnsMap, $params, $paramsInput->all);
|
||||
$paramsInput->page += 1;
|
||||
|
||||
$continue = $result->hasNextPage() && $io->confirm(
|
||||
$continue = $result->hasNextPage()
|
||||
&& $io->confirm(
|
||||
sprintf('Continue with page <options=bold>%s</>?', $paramsInput->page),
|
||||
default: false,
|
||||
);
|
||||
@@ -75,7 +76,7 @@ class ListShortUrlsCommand extends Command
|
||||
|
||||
$rows = map([...$shortUrls], function (ShortUrlWithDeps $shortUrl) use ($columnsMap) {
|
||||
$serializedShortUrl = $this->transformer->transform($shortUrl);
|
||||
return map($columnsMap, fn (callable $call) => $call($serializedShortUrl, $shortUrl->shortUrl));
|
||||
return map($columnsMap, static fn (callable $call) => $call($serializedShortUrl, $shortUrl->shortUrl));
|
||||
});
|
||||
|
||||
ShlinkTable::default($output)->render(
|
||||
@@ -105,12 +106,15 @@ class ListShortUrlsCommand extends Command
|
||||
$columnsMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']);
|
||||
}
|
||||
if ($params->showDomain) {
|
||||
$columnsMap['Domain'] = static fn (array $_, ShortUrl $shortUrl): string =>
|
||||
$shortUrl->getDomain()->authority ?? Domain::DEFAULT_AUTHORITY;
|
||||
$columnsMap['Domain'] = static fn (array $_, ShortUrl $shortUrl): string => (
|
||||
$shortUrl->getDomain()->authority ?? Domain::DEFAULT_AUTHORITY
|
||||
);
|
||||
}
|
||||
if ($params->showApiKey) {
|
||||
$columnsMap['API Key Name'] = static fn (array $_, ShortUrl $shortUrl): string|null =>
|
||||
$shortUrl->authorApiKey?->name;
|
||||
$columnsMap['API Key Name'] = static fn (
|
||||
array $_,
|
||||
ShortUrl $shortUrl,
|
||||
): string|null => $shortUrl->authorApiKey?->name;
|
||||
}
|
||||
|
||||
return $columnsMap;
|
||||
|
||||
@@ -32,8 +32,9 @@ class GetTagVisitsCommand extends Command
|
||||
#[Argument('The tag which visits we want to get'), Ask('For what tag do you want to get visits')] string $tag,
|
||||
#[MapInput] VisitsListInput $input,
|
||||
#[Option(
|
||||
'Return visits that belong to this domain only. Use ' . Domain::DEFAULT_AUTHORITY . ' keyword for visits '
|
||||
. 'in default domain',
|
||||
'Return visits that belong to this domain only. Use '
|
||||
. Domain::DEFAULT_AUTHORITY
|
||||
. ' keyword for visits in default domain',
|
||||
shortcut: 'd',
|
||||
)]
|
||||
string|null $domain = null,
|
||||
|
||||
@@ -32,7 +32,7 @@ class RenameTagCommand extends Command
|
||||
$this->tagService->renameTag(Renaming::fromNames($oldName, $newName));
|
||||
$io->success('Tag properly renamed.');
|
||||
return Command::SUCCESS;
|
||||
} catch (TagNotFoundException | TagConflictException $e) {
|
||||
} catch (TagNotFoundException|TagConflictException $e) {
|
||||
$io->error($e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class CommandUtils
|
||||
public static function executeWithWarning(string $warning, SymfonyStyle $io, callable $callback): int
|
||||
{
|
||||
$io->warning($warning);
|
||||
if (! $io->confirm('<comment>Do you want to proceed?</comment>', default: false)) {
|
||||
if (!$io->confirm('<comment>Do you want to proceed?</comment>', default: false)) {
|
||||
$io->info('Operation aborted');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class CommandUtils
|
||||
callable $callback,
|
||||
): int {
|
||||
$lock = $locker->createLock($lockConfig->lockName, $lockConfig->ttl, $lockConfig->isBlocking);
|
||||
if (! $lock->acquire($lockConfig->isBlocking)) {
|
||||
if (!$lock->acquire($lockConfig->isBlocking)) {
|
||||
$io->writeln(
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName),
|
||||
);
|
||||
|
||||
@@ -12,8 +12,7 @@ final readonly class LockConfig
|
||||
public string $lockName,
|
||||
public bool $isBlocking,
|
||||
public float $ttl = self::DEFAULT_TTL,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public static function blocking(string $lockName): self
|
||||
{
|
||||
|
||||
@@ -28,8 +28,9 @@ class GetNonOrphanVisitsCommand extends Command
|
||||
SymfonyStyle $io,
|
||||
#[MapInput] VisitsListInput $input,
|
||||
#[Option(
|
||||
'Return visits that belong to this domain only. Use ' . Domain::DEFAULT_AUTHORITY . ' keyword for visits '
|
||||
. 'in default domain',
|
||||
'Return visits that belong to this domain only. Use '
|
||||
. Domain::DEFAULT_AUTHORITY
|
||||
. ' keyword for visits in default domain',
|
||||
shortcut: 'd',
|
||||
)]
|
||||
string|null $domain = null,
|
||||
|
||||
@@ -29,8 +29,9 @@ class GetOrphanVisitsCommand extends Command
|
||||
SymfonyStyle $io,
|
||||
#[MapInput] VisitsListInput $input,
|
||||
#[Option(
|
||||
'Return visits that belong to this domain only. Use ' . Domain::DEFAULT_AUTHORITY . ' keyword for visits '
|
||||
. 'in default domain',
|
||||
'Return visits that belong to this domain only. Use '
|
||||
. Domain::DEFAULT_AUTHORITY
|
||||
. ' keyword for visits in default domain',
|
||||
shortcut: 'd',
|
||||
)]
|
||||
string|null $domain = null,
|
||||
|
||||
@@ -68,7 +68,7 @@ class LocateVisitsCommand extends Command implements VisitGeolocationHelperInter
|
||||
);
|
||||
}
|
||||
|
||||
if ($all && $retry && ! $this->warnAndVerifyContinue()) {
|
||||
if ($all && $retry && !$this->warnAndVerifyContinue()) {
|
||||
throw new RuntimeException('Execution aborted');
|
||||
}
|
||||
|
||||
@@ -85,9 +85,9 @@ class LocateVisitsCommand extends Command implements VisitGeolocationHelperInter
|
||||
$this->io->warning([
|
||||
'You are about to process the location of all existing visits your short URLs received.',
|
||||
'Since shlink saves visitors IP addresses anonymized, you could end up losing precision on some of '
|
||||
. 'your visits.',
|
||||
. 'your visits.',
|
||||
'Also, if you have a large amount of visits, this can be a very time consuming process. '
|
||||
. 'Continue at your own risk.',
|
||||
. 'Continue at your own risk.',
|
||||
]);
|
||||
return $this->io->confirm('Do you want to proceed?', false);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class LocateVisitsCommand extends Command implements VisitGeolocationHelperInter
|
||||
|
||||
public function onVisitLocated(VisitLocation $visitLocation, Visit $visit): void
|
||||
{
|
||||
if (! $visitLocation->isEmpty) {
|
||||
if (!$visitLocation->isEmpty) {
|
||||
$this->io->writeln(sprintf(' [<info>Address located in "%s"</info>]', $visitLocation->countryName));
|
||||
} elseif ($visit->hasRemoteAddr() && $visit->remoteAddr !== IpAddress::LOCALHOST) {
|
||||
$this->io->writeln(' <comment>[Could not locate address]</comment>');
|
||||
|
||||
@@ -95,7 +95,7 @@ class VisitsCommandUtils
|
||||
'Redirect URL',
|
||||
'Type',
|
||||
];
|
||||
$rows = array_map(function (Visit $visit) {
|
||||
$rows = array_map(static function (Visit $visit) {
|
||||
$visitLocation = $visit->visitLocation;
|
||||
|
||||
return [
|
||||
|
||||
@@ -6,6 +6,4 @@ namespace Shlinkio\Shlink\CLI\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
||||
interface ExceptionInterface extends Throwable {}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Input;
|
||||
|
||||
enum VisitsListFormat: string
|
||||
|
||||
@@ -19,8 +19,13 @@ class VisitsListInput
|
||||
public string|null $endDate = null;
|
||||
|
||||
#[Option(
|
||||
'Output format ("' . VisitsListFormat::FULL->value . '", "' . VisitsListFormat::PAGINATED->value . '" or "'
|
||||
. VisitsListFormat::CSV->value . '")',
|
||||
'Output format ("'
|
||||
. VisitsListFormat::FULL->value
|
||||
. '", "'
|
||||
. VisitsListFormat::PAGINATED->value
|
||||
. '" or "'
|
||||
. VisitsListFormat::CSV->value
|
||||
. '")',
|
||||
shortcut: 'f',
|
||||
)]
|
||||
public VisitsListFormat $format = VisitsListFormat::FULL;
|
||||
|
||||
@@ -46,8 +46,8 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
} else {
|
||||
$listing = map(
|
||||
$rules,
|
||||
function (ShortUrlRedirectRule $rule, string|int|float $index) use ($amountOfRules): array {
|
||||
$priority = ((int) $index) + 1;
|
||||
static function (ShortUrlRedirectRule $rule, string|int|float $index) use ($amountOfRules): array {
|
||||
$priority = (int) $index + 1;
|
||||
$conditions = $rule->mapConditions(static fn (RedirectCondition $condition): string => sprintf(
|
||||
'<comment>%s</comment>',
|
||||
$condition->toHumanFriendly(),
|
||||
@@ -209,8 +209,8 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
return $io->ask(
|
||||
'Rule priority (the lower the value, the higher the priority)',
|
||||
(string) $max,
|
||||
function (string $answer) use ($max): int {
|
||||
if (! is_numeric($answer)) {
|
||||
static function (string $answer) use ($max): int {
|
||||
if (!is_numeric($answer)) {
|
||||
throw new InvalidArgumentException('The priority must be a numeric positive value');
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
{
|
||||
return $io->ask(
|
||||
'Long URL to redirect when the rule matches',
|
||||
validator: function (string|null $answer): string {
|
||||
validator: static function (string|null $answer): string {
|
||||
if ($answer === null) {
|
||||
throw new InvalidArgumentException('The long URL is mandatory');
|
||||
}
|
||||
@@ -240,7 +240,7 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
|
||||
private function askMandatory(string $message, StyleInterface $io): string
|
||||
{
|
||||
return $io->ask($message, validator: function (string|null $answer): string {
|
||||
return $io->ask($message, validator: static function (string|null $answer): string {
|
||||
if ($answer === null) {
|
||||
throw new InvalidArgumentException('The value is mandatory');
|
||||
}
|
||||
@@ -250,6 +250,6 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
|
||||
private function askOptional(string $message, StyleInterface $io): string
|
||||
{
|
||||
return $io->ask($message, validator: fn (string|null $answer) => $answer === null ? '' : trim($answer));
|
||||
return $io->ask($message, validator: static fn (string|null $answer) => $answer === null ? '' : trim($answer));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\RedirectRule;
|
||||
|
||||
enum RedirectRuleHandlerAction: string
|
||||
|
||||
@@ -48,10 +48,12 @@ class ProcessRunner implements ProcessRunnerInterface
|
||||
$process->mustRun($callback);
|
||||
|
||||
if ($output->isVeryVerbose()) {
|
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf(
|
||||
'%s Command did not run successfully',
|
||||
$process->getExitCode(),
|
||||
);
|
||||
$message = $process->isSuccessful()
|
||||
? 'Command ran successfully'
|
||||
: sprintf(
|
||||
'%s Command did not run successfully',
|
||||
$process->getExitCode(),
|
||||
);
|
||||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ final class ShlinkTable
|
||||
private const string DEFAULT_STYLE_NAME = 'default';
|
||||
private const string TABLE_TITLE_STYLE = '<options=bold> %s </>';
|
||||
|
||||
private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators = false)
|
||||
{
|
||||
}
|
||||
private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators = false) {}
|
||||
|
||||
public static function default(OutputInterface $output): self
|
||||
{
|
||||
@@ -42,16 +40,16 @@ final class ShlinkTable
|
||||
): void {
|
||||
$style = Table::getStyleDefinition(self::DEFAULT_STYLE_NAME);
|
||||
$style->setFooterTitleFormat(self::TABLE_TITLE_STYLE)
|
||||
->setHeaderTitleFormat(self::TABLE_TITLE_STYLE);
|
||||
->setHeaderTitleFormat(self::TABLE_TITLE_STYLE);
|
||||
$tableRows = $this->withRowSeparators ? $this->addRowSeparators($rows) : $rows;
|
||||
|
||||
$table = clone $this->baseTable;
|
||||
$table->setStyle($style)
|
||||
->setHeaders($headers)
|
||||
->setRows($tableRows)
|
||||
->setFooterTitle($footerTitle)
|
||||
->setHeaderTitle($headerTitle)
|
||||
->render();
|
||||
->setHeaders($headers)
|
||||
->setRows($tableRows)
|
||||
->setFooterTitle($footerTitle)
|
||||
->setHeaderTitle($headerTitle)
|
||||
->render();
|
||||
}
|
||||
|
||||
private function addRowSeparators(array $rows): array
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioCliTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
|
||||
@@ -25,22 +25,22 @@ class ImportShortUrlsTest extends CliTestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tempCsvFile = tempnam(sys_get_temp_dir(), 'shlink_csv');
|
||||
if (! $this->tempCsvFile) {
|
||||
if (!$this->tempCsvFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handle = fopen($this->tempCsvFile, 'w+');
|
||||
if (! $handle) {
|
||||
if (!$handle) {
|
||||
$this->fail('It was not possible to open the temporary file to write CSV on it');
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$handle,
|
||||
<<<CSV
|
||||
longURL;tags;domain;short code;Title
|
||||
https://shlink.io;foo,baz;s.test;testing-default-domain-import-1;
|
||||
https://example.com;foo;s.test;testing-default-domain-import-2;
|
||||
CSV,
|
||||
longURL;tags;domain;short code;Title
|
||||
https://shlink.io;foo,baz;s.test;testing-default-domain-import-1;
|
||||
https://example.com;foo;s.test;testing-default-domain-import-2;
|
||||
CSV,
|
||||
);
|
||||
fclose($handle);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class ImportShortUrlsTest extends CliTestCase
|
||||
#[Test]
|
||||
public function defaultDomainIsIgnoredWhenExplicitlyProvided(): void
|
||||
{
|
||||
if (! $this->tempCsvFile) {
|
||||
if (!$this->tempCsvFile) {
|
||||
$this->fail('It was not possible to create a temporary CSV file');
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ class InitialApiKeyTest extends CliTestCase
|
||||
|
||||
self::assertEquals(
|
||||
<<<OUT
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
|
||||
OUT,
|
||||
OUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,40 +26,40 @@ class ListApiKeysTest extends CliTestCase
|
||||
{
|
||||
$expiredApiKeyDate = Chronos::now()->subDays(1)->startOfDay()->toAtomString();
|
||||
$enabledOnlyOutput = <<<OUT
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| valid_api_key | - | Admin |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| expired_api_key | {$expiredApiKeyDate} | Admin |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| author_api_key | - | Author only |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| domain_api_key | - | Domain only: example.com |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| no_orphans_api_key | - | No orphan visits |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| valid_api_key | - | Admin |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| expired_api_key | {$expiredApiKeyDate} | Admin |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| author_api_key | - | Author only |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| domain_api_key | - | Domain only: example.com |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
| no_orphans_api_key | - | No orphan visits |
|
||||
+--------------------+---------------------------+--------------------------+
|
||||
|
||||
OUT;
|
||||
OUT;
|
||||
|
||||
yield 'no flags' => [[], <<<OUT
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| Name | Is enabled | Expiration date | Roles |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| valid_api_key | +++ | - | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| disabled_api_key | --- | - | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| expired_api_key | --- | {$expiredApiKeyDate} | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| author_api_key | +++ | - | Author only |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| domain_api_key | +++ | - | Domain only: example.com |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| no_orphans_api_key | +++ | - | No orphan visits |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| Name | Is enabled | Expiration date | Roles |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| valid_api_key | +++ | - | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| disabled_api_key | --- | - | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| expired_api_key | --- | {$expiredApiKeyDate} | Admin |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| author_api_key | +++ | - | Author only |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| domain_api_key | +++ | - | Domain only: example.com |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
| no_orphans_api_key | +++ | - | No orphan visits |
|
||||
+--------------------+------------+---------------------------+--------------------------+
|
||||
|
||||
OUT];
|
||||
OUT];
|
||||
yield '-e' => [['-e'], $enabledOnlyOutput];
|
||||
yield '--enabled-only' => [['--enabled-only'], $enabledOnlyOutput];
|
||||
}
|
||||
|
||||
@@ -20,82 +20,80 @@ class ListShortUrlsTest extends CliTestCase
|
||||
|
||||
public static function provideFlagsAndOutput(): iterable
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
yield 'no flags' => [[], <<<OUTPUT
|
||||
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+---------------+-------------------------------------------+---------------------------- Page 1 of 1 ------------------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+---------------+-------------------------------------------+---------------------------- Page 1 of 1 ------------------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'start date' => [['--start-date=2019-01'], <<<OUTPUT
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'end date' => [['-e 2018-12-01'], <<<OUTPUT
|
||||
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+---------------+----------------------------------- Page 1 of 1 ------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+---------------+----------------------------------- Page 1 of 1 ------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'start and end date' => [['-s 2018-06-20', '--end-date=2019-01-01T00:00:20+00:00'], <<<OUTPUT
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
+--------------------+-------+-------------------------------------------+----------------------------- Page 1 of 1 -----------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
+--------------------+-------+-------------------------------------------+----------------------------- Page 1 of 1 -----------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'expired excluded' => [['--exclude-max-visits-reached', '--exclude-past-valid-until'], <<<OUTPUT
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
+--------------------+-------+-------------------------------------------+-------------------------------- Page 1 of 1 --------------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
+--------------------+-------+-------------------------------------------+-------------------------------- Page 1 of 1 --------------------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'non-default domain' => [['--domain=example.com'], <<<OUTPUT
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
|
||||
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'default domain' => [['--domain=DEFAULT'], <<<OUTPUT
|
||||
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+------------+---------------+----------------------+--------------------------------------- Page 1 of 1 -------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
|
||||
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+------------+---------------+----------------------+--------------------------------------- Page 1 of 1 -------------------------------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
yield 'exclude tags' => [['--exclude-tag=foo'], <<<OUTPUT
|
||||
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+-------+--------------------------------------- Page 1 of 1 --------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
// phpcs:enable
|
||||
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
|
||||
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
|
||||
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
|
||||
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
|
||||
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
|
||||
+--------------------+-------+--------------------------------------- Page 1 of 1 --------------------------+---------------------------+--------------+
|
||||
OUTPUT];
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
|
||||
class RoleResolverTest extends TestCase
|
||||
{
|
||||
private RoleResolver $resolver;
|
||||
private MockObject & DomainServiceInterface $domainService;
|
||||
private MockObject&DomainServiceInterface $domainService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -33,9 +33,13 @@ class RoleResolverTest extends TestCase
|
||||
array $expectedRoles,
|
||||
int $expectedDomainCalls,
|
||||
): void {
|
||||
$this->domainService->expects($this->exactly($expectedDomainCalls))->method('getOrCreate')->with(
|
||||
'example.com',
|
||||
)->willReturn(self::domainWithId(Domain::withAuthority('example.com')));
|
||||
$this->domainService
|
||||
->expects($this->exactly($expectedDomainCalls))
|
||||
->method('getOrCreate')
|
||||
->with(
|
||||
'example.com',
|
||||
)
|
||||
->willReturn(self::domainWithId(Domain::withAuthority('example.com')));
|
||||
|
||||
$result = [...$this->resolver->determineRoles($input)];
|
||||
|
||||
@@ -52,7 +56,7 @@ class RoleResolverTest extends TestCase
|
||||
0,
|
||||
];
|
||||
yield 'domain role only' => [
|
||||
(function (): ApiKeyInput {
|
||||
(static function (): ApiKeyInput {
|
||||
$input = new ApiKeyInput();
|
||||
$input->domain = 'example.com';
|
||||
|
||||
@@ -62,7 +66,7 @@ class RoleResolverTest extends TestCase
|
||||
1,
|
||||
];
|
||||
yield 'author role only' => [
|
||||
(function (): ApiKeyInput {
|
||||
(static function (): ApiKeyInput {
|
||||
$input = new ApiKeyInput();
|
||||
$input->authorOnly = true;
|
||||
|
||||
@@ -72,7 +76,7 @@ class RoleResolverTest extends TestCase
|
||||
0,
|
||||
];
|
||||
yield 'all roles' => [
|
||||
(function (): ApiKeyInput {
|
||||
(static function (): ApiKeyInput {
|
||||
$input = new ApiKeyInput();
|
||||
$input->domain = 'example.com';
|
||||
$input->authorOnly = true;
|
||||
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DeleteKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -68,11 +68,14 @@ class DeleteKeyCommandTest extends TestCase
|
||||
{
|
||||
$name = 'the key to delete';
|
||||
$this->apiKeyService->expects($this->once())->method('deleteByName')->with($name);
|
||||
$this->apiKeyService->expects($this->once())->method('listKeys')->willReturn([
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'foo')),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: $name)),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'bar')),
|
||||
]);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('listKeys')
|
||||
->willReturn([
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'foo')),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: $name)),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'bar')),
|
||||
]);
|
||||
|
||||
$this->commandTester->setInputs([$name, 'y']);
|
||||
$exitCode = $this->commandTester->execute([]);
|
||||
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DisableKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -45,9 +45,13 @@ class DisableKeyCommandTest extends TestCase
|
||||
{
|
||||
$apiKey = 'abcd1234';
|
||||
$expectedMessage = 'API key "abcd1234" does not exist.';
|
||||
$this->apiKeyService->expects($this->once())->method('disableByName')->with($apiKey)->willThrowException(
|
||||
new InvalidArgumentException($expectedMessage),
|
||||
);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('disableByName')
|
||||
->with($apiKey)
|
||||
->willThrowException(
|
||||
new InvalidArgumentException($expectedMessage),
|
||||
);
|
||||
|
||||
$exitCode = $this->commandTester->execute(['name' => $apiKey]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -72,11 +76,14 @@ class DisableKeyCommandTest extends TestCase
|
||||
{
|
||||
$name = 'the key to delete';
|
||||
$this->apiKeyService->expects($this->once())->method('disableByName')->with($name);
|
||||
$this->apiKeyService->expects($this->once())->method('listKeys')->willReturn([
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'foo')),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: $name)),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'bar')),
|
||||
]);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('listKeys')
|
||||
->willReturn([
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'foo')),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: $name)),
|
||||
ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'bar')),
|
||||
]);
|
||||
|
||||
$this->commandTester->setInputs([$name]);
|
||||
$exitCode = $this->commandTester->execute([]);
|
||||
|
||||
@@ -20,7 +20,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class GenerateKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -35,9 +35,13 @@ class GenerateKeyCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function noExpirationDateIsDefinedIfNotProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->expirationDate === null),
|
||||
)->willReturn(ApiKey::create());
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with(
|
||||
$this->callback(static fn (ApiKeyMeta $meta) => $meta->expirationDate === null),
|
||||
)
|
||||
->willReturn(ApiKey::create());
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -48,9 +52,13 @@ class GenerateKeyCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function expirationDateIsDefinedIfProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->expirationDate instanceof Chronos),
|
||||
)->willReturn(ApiKey::create());
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with(
|
||||
$this->callback(static fn (ApiKeyMeta $meta) => $meta->expirationDate instanceof Chronos),
|
||||
)
|
||||
->willReturn(ApiKey::create());
|
||||
|
||||
$this->commandTester->execute([
|
||||
'--expiration-date' => '2016-01-01',
|
||||
@@ -60,9 +68,13 @@ class GenerateKeyCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function nameIsDefinedIfProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->name === 'Alice'),
|
||||
)->willReturn(ApiKey::create());
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with(
|
||||
$this->callback(static fn (ApiKeyMeta $meta) => $meta->name === 'Alice'),
|
||||
)
|
||||
->willReturn(ApiKey::create());
|
||||
|
||||
$exitCode = $this->commandTester->execute([
|
||||
'--name' => 'Alice',
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class InitialApiKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
@@ -49,9 +49,8 @@ class InitialApiKeyCommandTest extends TestCase
|
||||
yield 'api key created, verbose' => [ApiKey::create(), true, ''];
|
||||
yield 'no api key created, no verbose' => [null, false, ''];
|
||||
yield 'no api key created, verbose' => [null, true, <<<OUT
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
|
||||
OUT,
|
||||
];
|
||||
OUT];
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class ListKeysCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -52,31 +52,31 @@ class ListKeysCommandTest extends TestCase
|
||||
],
|
||||
false,
|
||||
<<<OUTPUT
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| Name | Is enabled | Expiration date | Roles |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey1->name} | --- | - | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey2->name} | --- | 2020-01-01T00:00:00+00:00 | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey3->name} | +++ | - | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| Name | Is enabled | Expiration date | Roles |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey1->name} | --- | - | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey2->name} | --- | 2020-01-01T00:00:00+00:00 | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
| {$apiKey3->name} | +++ | - | Admin |
|
||||
+--------------------------------------+------------+---------------------------+-------+
|
||||
|
||||
OUTPUT,
|
||||
OUTPUT,
|
||||
];
|
||||
yield 'enabled keys' => [
|
||||
[$apiKey1 = ApiKey::create()->disable(), $apiKey2 = ApiKey::create()],
|
||||
true,
|
||||
<<<OUTPUT
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey1->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey2->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey1->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey2->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
|
||||
OUTPUT,
|
||||
OUTPUT,
|
||||
];
|
||||
yield 'with roles' => [
|
||||
[
|
||||
@@ -94,24 +94,24 @@ class ListKeysCommandTest extends TestCase
|
||||
],
|
||||
true,
|
||||
<<<OUTPUT
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey1->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey2->name} | - | Author only |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey3->name} | - | Domain only: example.com |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey4->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey5->name} | - | Author only |
|
||||
| | | Domain only: example.com |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey6->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey1->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey2->name} | - | Author only |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey3->name} | - | Domain only: example.com |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey4->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey5->name} | - | Author only |
|
||||
| | | Domain only: example.com |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
| {$apiKey6->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+--------------------------+
|
||||
|
||||
OUTPUT,
|
||||
OUTPUT,
|
||||
];
|
||||
yield 'with names' => [
|
||||
[
|
||||
@@ -122,19 +122,19 @@ class ListKeysCommandTest extends TestCase
|
||||
],
|
||||
true,
|
||||
<<<OUTPUT
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Alice | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Alice and Bob | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey3->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey4->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Name | Expiration date | Roles |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Alice | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| Alice and Bob | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey3->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
| {$apiKey4->name} | - | Admin |
|
||||
+--------------------------------------+-----------------+-------+
|
||||
|
||||
OUTPUT,
|
||||
OUTPUT,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class RenameApiKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
private MockObject&ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -30,9 +30,12 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
$oldName = 'old name';
|
||||
$newName = 'new name';
|
||||
|
||||
$this->apiKeyService->expects($this->once())->method('renameApiKey')->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('renameApiKey')
|
||||
->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
|
||||
$this->commandTester->setInputs([$oldName]);
|
||||
$this->commandTester->execute([
|
||||
@@ -46,9 +49,12 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
$oldName = 'old name';
|
||||
$newName = 'new name';
|
||||
|
||||
$this->apiKeyService->expects($this->once())->method('renameApiKey')->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('renameApiKey')
|
||||
->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
|
||||
$this->commandTester->setInputs([$newName]);
|
||||
$this->commandTester->execute([
|
||||
@@ -62,9 +68,12 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
$oldName = 'old name';
|
||||
$newName = 'new name';
|
||||
|
||||
$this->apiKeyService->expects($this->once())->method('renameApiKey')->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
$this->apiKeyService
|
||||
->expects($this->once())
|
||||
->method('renameApiKey')
|
||||
->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'old-name' => $oldName,
|
||||
|
||||
@@ -29,12 +29,12 @@ use Symfony\Component\Lock\SharedLockInterface;
|
||||
class CreateDatabaseCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ProcessRunnerInterface $processHelper;
|
||||
private MockObject & Connection $regularConn;
|
||||
private Stub & ClassMetadataFactory $metadataFactory;
|
||||
private MockObject&ProcessRunnerInterface $processHelper;
|
||||
private MockObject&Connection $regularConn;
|
||||
private Stub&ClassMetadataFactory $metadataFactory;
|
||||
/** @var MockObject&AbstractSchemaManager<SQLitePlatform> */
|
||||
private MockObject & AbstractSchemaManager $schemaManager;
|
||||
private Stub & Driver $driver;
|
||||
private MockObject&AbstractSchemaManager $schemaManager;
|
||||
private Stub&Driver $driver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -107,11 +107,17 @@ class CreateDatabaseCommandTest extends TestCase
|
||||
$this->metadataFactory->method('getAllMetadata')->willReturn([$metadata]);
|
||||
$this->schemaManager->expects($this->never())->method('createDatabase');
|
||||
$this->schemaManager->expects($this->once())->method('listTableNames')->willReturn($tables);
|
||||
$this->processHelper->expects($this->once())->method('run')->with($this->isInstanceOf(OutputInterface::class), [
|
||||
CreateDatabaseCommand::SCRIPT,
|
||||
CreateDatabaseCommand::COMMAND,
|
||||
'--no-interaction',
|
||||
]);
|
||||
$this->processHelper
|
||||
->expects($this->once())
|
||||
->method('run')
|
||||
->with(
|
||||
$this->isInstanceOf(OutputInterface::class),
|
||||
[
|
||||
CreateDatabaseCommand::SCRIPT,
|
||||
CreateDatabaseCommand::COMMAND,
|
||||
'--no-interaction',
|
||||
],
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Lock\SharedLockInterface;
|
||||
class MigrateDatabaseCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ProcessRunnerInterface $processHelper;
|
||||
private MockObject&ProcessRunnerInterface $processHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -36,11 +36,17 @@ class MigrateDatabaseCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function migrationsCommandIsRunWithProperVerbosity(): void
|
||||
{
|
||||
$this->processHelper->expects($this->once())->method('run')->with($this->isInstanceOf(OutputInterface::class), [
|
||||
MigrateDatabaseCommand::SCRIPT,
|
||||
MigrateDatabaseCommand::COMMAND,
|
||||
'--no-interaction',
|
||||
]);
|
||||
$this->processHelper
|
||||
->expects($this->once())
|
||||
->method('run')
|
||||
->with(
|
||||
$this->isInstanceOf(OutputInterface::class),
|
||||
[
|
||||
MigrateDatabaseCommand::SCRIPT,
|
||||
MigrateDatabaseCommand::COMMAND,
|
||||
'--no-interaction',
|
||||
],
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -22,7 +22,7 @@ use function substr_count;
|
||||
class DomainRedirectsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & DomainServiceInterface $domainService;
|
||||
private MockObject&DomainServiceInterface $domainService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -34,13 +34,21 @@ class DomainRedirectsCommandTest extends TestCase
|
||||
public function onlyPlainQuestionsAreAskedForNewDomainsAndDomainsWithNoRedirects(Domain|null $domain): void
|
||||
{
|
||||
$domainAuthority = 'my-domain.com';
|
||||
$this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withRedirects('foo.com', null, 'baz.com'),
|
||||
)->willReturn(Domain::withAuthority(''));
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('findByAuthority')
|
||||
->with($domainAuthority)
|
||||
->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('configureNotFoundRedirects')
|
||||
->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withRedirects('foo.com', null, 'baz.com'),
|
||||
)
|
||||
->willReturn(Domain::withAuthority(''));
|
||||
$this->domainService->expects($this->never())->method('listDomains');
|
||||
|
||||
$this->commandTester->setInputs(['foo.com', '', 'baz.com']);
|
||||
@@ -70,13 +78,21 @@ class DomainRedirectsCommandTest extends TestCase
|
||||
$domain = Domain::withAuthority($domainAuthority);
|
||||
$domain->configureNotFoundRedirects(NotFoundRedirects::withRedirects('foo.com', 'bar.com', 'baz.com'));
|
||||
|
||||
$this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com'),
|
||||
)->willReturn($domain);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('findByAuthority')
|
||||
->with($domainAuthority)
|
||||
->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('configureNotFoundRedirects')
|
||||
->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com'),
|
||||
)
|
||||
->willReturn($domain);
|
||||
$this->domainService->expects($this->never())->method('listDomains');
|
||||
|
||||
$this->commandTester->setInputs(['2', '1', 'edited.com', '0']);
|
||||
@@ -99,13 +115,21 @@ class DomainRedirectsCommandTest extends TestCase
|
||||
$domain = Domain::withAuthority($domainAuthority);
|
||||
|
||||
$this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([]);
|
||||
$this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)->willReturn($domain);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('findByAuthority')
|
||||
->with($domainAuthority)
|
||||
->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('configureNotFoundRedirects')
|
||||
->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)
|
||||
->willReturn($domain);
|
||||
|
||||
$this->commandTester->setInputs([$domainAuthority, '', '', '']);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -120,18 +144,30 @@ class DomainRedirectsCommandTest extends TestCase
|
||||
$domainAuthority = 'existing-two.com';
|
||||
$domain = Domain::withAuthority($domainAuthority);
|
||||
|
||||
$this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([
|
||||
DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority($domainAuthority)),
|
||||
]);
|
||||
$this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)->willReturn($domain);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('listDomains')
|
||||
->with()
|
||||
->willReturn([
|
||||
DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority($domainAuthority)),
|
||||
]);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('findByAuthority')
|
||||
->with($domainAuthority)
|
||||
->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('configureNotFoundRedirects')
|
||||
->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)
|
||||
->willReturn($domain);
|
||||
|
||||
$this->commandTester->setInputs(['1', '', '', '']);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -149,18 +185,30 @@ class DomainRedirectsCommandTest extends TestCase
|
||||
$domainAuthority = 'new-domain.com';
|
||||
$domain = Domain::withAuthority($domainAuthority);
|
||||
|
||||
$this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([
|
||||
DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-two.com')),
|
||||
]);
|
||||
$this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)->willReturn($domain);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('listDomains')
|
||||
->with()
|
||||
->willReturn([
|
||||
DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-two.com')),
|
||||
]);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('findByAuthority')
|
||||
->with($domainAuthority)
|
||||
->willReturn(
|
||||
$domain,
|
||||
);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('configureNotFoundRedirects')
|
||||
->with(
|
||||
$domainAuthority,
|
||||
NotFoundRedirects::withoutRedirects(),
|
||||
)
|
||||
->willReturn($domain);
|
||||
|
||||
$this->commandTester->setInputs(['2', $domainAuthority, '', '', '']);
|
||||
$this->commandTester->execute([]);
|
||||
|
||||
@@ -23,7 +23,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class GetDomainVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject&VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -39,26 +39,28 @@ class GetDomainVisitsCommandTest extends TestCase
|
||||
VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')),
|
||||
);
|
||||
$domain = 's.test';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForDomain')->with(
|
||||
$domain,
|
||||
$this->anything(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForDomain')
|
||||
->with(
|
||||
$domain,
|
||||
$this->anything(),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
|
||||
$this->commandTester->execute(['domain' => $domain]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
OUTPUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class ListDomainsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & DomainServiceInterface $domainService;
|
||||
private MockObject&DomainServiceInterface $domainService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -39,14 +39,18 @@ class ListDomainsCommandTest extends TestCase
|
||||
'https://foo.com/baz-domain/invalid',
|
||||
));
|
||||
|
||||
$this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([
|
||||
DomainItem::forDefaultDomain('foo.com', new NotFoundRedirectOptions(
|
||||
invalidShortUrlRedirect: 'https://foo.com/default/invalid',
|
||||
baseUrlRedirect: 'https://foo.com/default/base',
|
||||
)),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com')),
|
||||
DomainItem::forNonDefaultDomain($bazDomain),
|
||||
]);
|
||||
$this->domainService
|
||||
->expects($this->once())
|
||||
->method('listDomains')
|
||||
->with()
|
||||
->willReturn([
|
||||
DomainItem::forDefaultDomain('foo.com', new NotFoundRedirectOptions(
|
||||
invalidShortUrlRedirect: 'https://foo.com/default/invalid',
|
||||
baseUrlRedirect: 'https://foo.com/default/base',
|
||||
)),
|
||||
DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com')),
|
||||
DomainItem::forNonDefaultDomain($bazDomain),
|
||||
]);
|
||||
|
||||
$this->commandTester->execute($input);
|
||||
|
||||
@@ -57,33 +61,33 @@ class ListDomainsCommandTest extends TestCase
|
||||
public static function provideInputsAndOutputs(): iterable
|
||||
{
|
||||
$withoutRedirectsOutput = <<<OUTPUT
|
||||
+---------+------------+
|
||||
| Domain | Is default |
|
||||
+---------+------------+
|
||||
| foo.com | Yes |
|
||||
| bar.com | No |
|
||||
| baz.com | No |
|
||||
+---------+------------+
|
||||
+---------+------------+
|
||||
| Domain | Is default |
|
||||
+---------+------------+
|
||||
| foo.com | Yes |
|
||||
| bar.com | No |
|
||||
| baz.com | No |
|
||||
+---------+------------+
|
||||
|
||||
OUTPUT;
|
||||
OUTPUT;
|
||||
$withRedirectsOutput = <<<OUTPUT
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| Domain | Is default | "Not found" redirects |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| foo.com | Yes | * Base URL: https://foo.com/default/base |
|
||||
| | | * Regular 404: N/A |
|
||||
| | | * Invalid short URL: https://foo.com/default/invalid |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| bar.com | No | * Base URL: N/A |
|
||||
| | | * Regular 404: N/A |
|
||||
| | | * Invalid short URL: N/A |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| baz.com | No | * Base URL: N/A |
|
||||
| | | * Regular 404: https://foo.com/baz-domain/regular |
|
||||
| | | * Invalid short URL: https://foo.com/baz-domain/invalid |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| Domain | Is default | "Not found" redirects |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| foo.com | Yes | * Base URL: https://foo.com/default/base |
|
||||
| | | * Regular 404: N/A |
|
||||
| | | * Invalid short URL: https://foo.com/default/invalid |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| bar.com | No | * Base URL: N/A |
|
||||
| | | * Regular 404: N/A |
|
||||
| | | * Invalid short URL: N/A |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
| baz.com | No | * Base URL: N/A |
|
||||
| | | * Regular 404: https://foo.com/baz-domain/regular |
|
||||
| | | * Invalid short URL: https://foo.com/baz-domain/invalid |
|
||||
+---------+------------+---------------------------------------------------------+
|
||||
|
||||
OUTPUT;
|
||||
OUTPUT;
|
||||
|
||||
yield 'no args' => [[], $withoutRedirectsOutput];
|
||||
yield 'no show redirects' => [['--show-redirects' => false], $withoutRedirectsOutput];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Integration;
|
||||
|
||||
use Exception;
|
||||
@@ -18,7 +20,7 @@ use Symfony\Component\Console\Command\Command;
|
||||
|
||||
class MatomoSendVisitsCommandTest extends TestCase
|
||||
{
|
||||
private MockObject & MatomoVisitSenderInterface $visitSender;
|
||||
private MockObject&MatomoVisitSenderInterface $visitSender;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -57,9 +59,12 @@ class MatomoSendVisitsCommandTest extends TestCase
|
||||
#[TestWith([false])]
|
||||
public function canCancelExecutionInInteractiveMode(bool $interactive): void
|
||||
{
|
||||
$this->visitSender->expects($this->exactly($interactive ? 0 : 1))->method('sendVisitsInDateRange')->willReturn(
|
||||
new SendVisitsResult(),
|
||||
);
|
||||
$this->visitSender
|
||||
->expects($this->exactly($interactive ? 0 : 1))
|
||||
->method('sendVisitsInDateRange')
|
||||
->willReturn(
|
||||
new SendVisitsResult(),
|
||||
);
|
||||
$this->executeCommand(['n'], ['interactive' => $interactive]);
|
||||
}
|
||||
|
||||
@@ -83,19 +88,22 @@ class MatomoSendVisitsCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function printsResultOfSendingVisits(): void
|
||||
{
|
||||
$this->visitSender->expects($this->once())->method('sendVisitsInDateRange')->willReturnCallback(
|
||||
function (DateRange $_, MatomoSendVisitsCommand $command): SendVisitsResult {
|
||||
// Call it a few times for an easier match of its result in the command putput
|
||||
$command->success(0);
|
||||
$command->success(1);
|
||||
$command->success(2);
|
||||
$command->error(3, new Exception('Error'));
|
||||
$command->success(4);
|
||||
$command->error(5, new Exception('Error'));
|
||||
$this->visitSender
|
||||
->expects($this->once())
|
||||
->method('sendVisitsInDateRange')
|
||||
->willReturnCallback(
|
||||
static function (DateRange $_, MatomoSendVisitsCommand $command): SendVisitsResult {
|
||||
// Call it a few times for an easier match of its result in the command putput
|
||||
$command->success(0);
|
||||
$command->success(1);
|
||||
$command->success(2);
|
||||
$command->error(3, new Exception('Error'));
|
||||
$command->success(4);
|
||||
$command->error(5, new Exception('Error'));
|
||||
|
||||
return new SendVisitsResult();
|
||||
},
|
||||
);
|
||||
return new SendVisitsResult();
|
||||
},
|
||||
);
|
||||
|
||||
[$output] = $this->executeCommand(['y']);
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class ManageRedirectRulesCommandTest extends TestCase
|
||||
{
|
||||
private ShortUrlResolverInterface & MockObject $shortUrlResolver;
|
||||
private ShortUrlRedirectRuleServiceInterface & MockObject $ruleService;
|
||||
private RedirectRuleHandlerInterface & MockObject $ruleHandler;
|
||||
private ShortUrlResolverInterface&MockObject $shortUrlResolver;
|
||||
private ShortUrlRedirectRuleServiceInterface&MockObject $ruleService;
|
||||
private RedirectRuleHandlerInterface&MockObject $ruleHandler;
|
||||
private CommandTester $commandTester;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -41,9 +41,13 @@ class ManageRedirectRulesCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function errorIsReturnedIfShortUrlCannotBeFound(): void
|
||||
{
|
||||
$this->shortUrlResolver->expects($this->once())->method('resolveShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)->willThrowException(new ShortUrlNotFoundException(''));
|
||||
$this->shortUrlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)
|
||||
->willThrowException(new ShortUrlNotFoundException(''));
|
||||
$this->ruleService->expects($this->never())->method('rulesForShortUrl');
|
||||
$this->ruleService->expects($this->never())->method('saveRulesForShortUrl');
|
||||
$this->ruleHandler->expects($this->never())->method('manageRules');
|
||||
@@ -60,9 +64,13 @@ class ManageRedirectRulesCommandTest extends TestCase
|
||||
{
|
||||
$shortUrl = ShortUrl::withLongUrl('https://example.com');
|
||||
|
||||
$this->shortUrlResolver->expects($this->once())->method('resolveShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)->willReturn($shortUrl);
|
||||
$this->shortUrlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)
|
||||
->willReturn($shortUrl);
|
||||
$this->ruleService->expects($this->once())->method('rulesForShortUrl')->with($shortUrl)->willReturn([]);
|
||||
$this->ruleHandler->expects($this->once())->method('manageRules')->willReturn(null);
|
||||
$this->ruleService->expects($this->never())->method('saveRulesForShortUrl');
|
||||
@@ -79,9 +87,13 @@ class ManageRedirectRulesCommandTest extends TestCase
|
||||
{
|
||||
$shortUrl = ShortUrl::withLongUrl('https://example.com');
|
||||
|
||||
$this->shortUrlResolver->expects($this->once())->method('resolveShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)->willReturn($shortUrl);
|
||||
$this->shortUrlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foo'),
|
||||
)
|
||||
->willReturn($shortUrl);
|
||||
$this->ruleService->expects($this->once())->method('rulesForShortUrl')->with($shortUrl)->willReturn([]);
|
||||
$this->ruleHandler->expects($this->once())->method('manageRules')->willReturn([]);
|
||||
$this->ruleService->expects($this->once())->method('saveRulesForShortUrl')->with($shortUrl, []);
|
||||
|
||||
@@ -28,8 +28,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class CreateShortUrlCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & UrlShortenerInterface $urlShortener;
|
||||
private Stub & ShortUrlStringifierInterface $stringifier;
|
||||
private MockObject&UrlShortenerInterface $urlShortener;
|
||||
private Stub&ShortUrlStringifierInterface $stringifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -49,9 +49,13 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void
|
||||
{
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn(
|
||||
UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl),
|
||||
);
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl),
|
||||
);
|
||||
$this->stringifier->method('stringify')->willReturnMap([[$shortUrl, 'stringified_short_url']]);
|
||||
|
||||
$this->commandTester->execute([
|
||||
@@ -69,9 +73,13 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
public function longUrlIsAskedIfNotProvided(): void
|
||||
{
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn(
|
||||
UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl),
|
||||
);
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl),
|
||||
);
|
||||
$this->stringifier->method('stringify')->willReturnMap([[$shortUrl, 'stringified_short_url']]);
|
||||
|
||||
$this->commandTester->setInputs([$shortUrl->getLongUrl()]);
|
||||
@@ -81,9 +89,13 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function providingNonUniqueSlugOutputsError(): void
|
||||
{
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willThrowException(
|
||||
NonUniqueSlugException::fromSlug('my-slug'),
|
||||
);
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->withAnyParameters()
|
||||
->willThrowException(
|
||||
NonUniqueSlugException::fromSlug('my-slug'),
|
||||
);
|
||||
$this->stringifier->method('stringify')->willReturn('');
|
||||
|
||||
$this->commandTester->execute(['long-url' => 'http://domain.com/invalid', '--custom-slug' => 'my-slug']);
|
||||
@@ -97,12 +109,16 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
public function properlyProcessesProvidedTags(): void
|
||||
{
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
||||
$this->callback(function (ShortUrlCreation $creation) {
|
||||
Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $creation->tags);
|
||||
return true;
|
||||
}),
|
||||
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->with(
|
||||
$this->callback(static function (ShortUrlCreation $creation) {
|
||||
Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $creation->tags);
|
||||
return true;
|
||||
}),
|
||||
)
|
||||
->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||
$this->stringifier->method('stringify')->willReturnMap([[$shortUrl, 'stringified_short_url']]);
|
||||
|
||||
$this->commandTester->execute([
|
||||
@@ -118,12 +134,16 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
#[Test, DataProvider('provideDomains')]
|
||||
public function properlyProcessesProvidedDomain(array $input, string|null $expectedDomain): void
|
||||
{
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
||||
$this->callback(function (ShortUrlCreation $meta) use ($expectedDomain) {
|
||||
Assert::assertEquals($expectedDomain, $meta->domain);
|
||||
return true;
|
||||
}),
|
||||
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching(ShortUrl::createFake()));
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->with(
|
||||
$this->callback(static function (ShortUrlCreation $meta) use ($expectedDomain) {
|
||||
Assert::assertEquals($expectedDomain, $meta->domain);
|
||||
return true;
|
||||
}),
|
||||
)
|
||||
->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching(ShortUrl::createFake()));
|
||||
$this->stringifier->method('stringify')->willReturn('');
|
||||
|
||||
$input['long-url'] = 'http://domain.com/foo/bar';
|
||||
@@ -145,12 +165,16 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
bool|null $expectedCrawlable,
|
||||
): void {
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
||||
$this->callback(function (ShortUrlCreation $meta) use ($expectedCrawlable) {
|
||||
Assert::assertEquals($expectedCrawlable, $meta->crawlable);
|
||||
return true;
|
||||
}),
|
||||
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->with(
|
||||
$this->callback(static function (ShortUrlCreation $meta) use ($expectedCrawlable) {
|
||||
Assert::assertEquals($expectedCrawlable, $meta->crawlable);
|
||||
return true;
|
||||
}),
|
||||
)
|
||||
->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||
$this->stringifier->method('stringify')->willReturn('');
|
||||
|
||||
$options['long-url'] = 'http://domain.com/foo/bar';
|
||||
@@ -170,9 +194,13 @@ class CreateShortUrlCommandTest extends TestCase
|
||||
public function warningIsPrintedInVerboseModeWhenDispatchErrors(int $verbosity, callable $assert): void
|
||||
{
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn(
|
||||
UrlShorteningResult::withErrorOnEventDispatching($shortUrl, new ServiceNotFoundException()),
|
||||
);
|
||||
$this->urlShortener
|
||||
->expects($this->once())
|
||||
->method('shorten')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
UrlShorteningResult::withErrorOnEventDispatching($shortUrl, new ServiceNotFoundException()),
|
||||
);
|
||||
$this->stringifier->method('stringify')->willReturn('stringified_short_url');
|
||||
|
||||
$this->commandTester->execute(['long-url' => 'http://domain.com/foo/bar'], ['verbosity' => $verbosity]);
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DeleteExpiredShortUrlsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & DeleteShortUrlServiceInterface $service;
|
||||
private MockObject&DeleteShortUrlServiceInterface $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ use const PHP_EOL;
|
||||
class DeleteShortUrlCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & DeleteShortUrlServiceInterface $service;
|
||||
private MockObject&DeleteShortUrlServiceInterface $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -34,10 +34,13 @@ class DeleteShortUrlCommandTest extends TestCase
|
||||
public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->service->expects($this->once())->method('deleteByShortCode')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->isFalse(),
|
||||
);
|
||||
$this->service
|
||||
->expects($this->once())
|
||||
->method('deleteByShortCode')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->isFalse(),
|
||||
);
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -53,10 +56,14 @@ class DeleteShortUrlCommandTest extends TestCase
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$this->service->expects($this->once())->method('deleteByShortCode')->with(
|
||||
$identifier,
|
||||
$this->isFalse(),
|
||||
)->willThrowException(Exception\ShortUrlNotFoundException::fromNotFound($identifier));
|
||||
$this->service
|
||||
->expects($this->once())
|
||||
->method('deleteByShortCode')
|
||||
->with(
|
||||
$identifier,
|
||||
$this->isFalse(),
|
||||
)
|
||||
->willThrowException(Exception\ShortUrlNotFoundException::fromNotFound($identifier));
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -75,26 +82,33 @@ class DeleteShortUrlCommandTest extends TestCase
|
||||
): void {
|
||||
$shortCode = 'abc123';
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$this->service->expects($this->exactly($expectedDeleteCalls))->method('deleteByShortCode')->with(
|
||||
$identifier,
|
||||
$this->isBool(),
|
||||
)->willReturnCallback(function ($_, bool $ignoreThreshold) use ($shortCode): void {
|
||||
if (!$ignoreThreshold) {
|
||||
throw Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
);
|
||||
}
|
||||
});
|
||||
$this->service
|
||||
->expects($this->exactly($expectedDeleteCalls))
|
||||
->method('deleteByShortCode')
|
||||
->with(
|
||||
$identifier,
|
||||
$this->isBool(),
|
||||
)
|
||||
->willReturnCallback(static function ($_, bool $ignoreThreshold) use ($shortCode): void {
|
||||
if (!$ignoreThreshold) {
|
||||
throw Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
);
|
||||
}
|
||||
});
|
||||
$this->commandTester->setInputs($retryAnswer);
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
self::assertStringContainsString(sprintf(
|
||||
'Impossible to delete short URL with short code "%s", since it has more than "10" visits.',
|
||||
$shortCode,
|
||||
), $output);
|
||||
self::assertStringContainsString(
|
||||
sprintf(
|
||||
'Impossible to delete short URL with short code "%s", since it has more than "10" visits.',
|
||||
$shortCode,
|
||||
),
|
||||
$output,
|
||||
);
|
||||
self::assertStringContainsString($expectedMessage, $output);
|
||||
}
|
||||
|
||||
@@ -109,22 +123,29 @@ class DeleteShortUrlCommandTest extends TestCase
|
||||
public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->service->expects($this->once())->method('deleteByShortCode')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->isFalse(),
|
||||
)->willThrowException(Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
));
|
||||
$this->service
|
||||
->expects($this->once())
|
||||
->method('deleteByShortCode')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->isFalse(),
|
||||
)
|
||||
->willThrowException(Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
));
|
||||
$this->commandTester->setInputs(['no']);
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
self::assertStringContainsString(sprintf(
|
||||
'Impossible to delete short URL with short code "%s", since it has more than "10" visits.',
|
||||
$shortCode,
|
||||
), $output);
|
||||
self::assertStringContainsString(
|
||||
sprintf(
|
||||
'Impossible to delete short URL with short code "%s", since it has more than "10" visits.',
|
||||
$shortCode,
|
||||
),
|
||||
$output,
|
||||
);
|
||||
self::assertStringContainsString('Short URL was not deleted.', $output);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DeleteShortUrlVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ShortUrlVisitsDeleterInterface $deleter;
|
||||
private MockObject&ShortUrlVisitsDeleterInterface $deleter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -53,9 +53,12 @@ class DeleteShortUrlVisitsCommandTest extends TestCase
|
||||
#[Test, DataProvider('provideErrorArgs')]
|
||||
public function warningIsPrintedInCaseOfNotFoundShortUrl(array $args, string $expectedError): void
|
||||
{
|
||||
$this->deleter->expects($this->once())->method('deleteShortUrlVisits')->willThrowException(
|
||||
new ShortUrlNotFoundException(),
|
||||
);
|
||||
$this->deleter
|
||||
->expects($this->once())
|
||||
->method('deleteShortUrlVisits')
|
||||
->willThrowException(
|
||||
new ShortUrlNotFoundException(),
|
||||
);
|
||||
$this->commandTester->setInputs(['yes']);
|
||||
|
||||
$exitCode = $this->commandTester->execute($args);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl;
|
||||
|
||||
use CuyZ\Valinor\MapperBuilder;
|
||||
@@ -22,8 +24,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class EditShortUrlCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ShortUrlServiceInterface $shortUrlService;
|
||||
private MockObject & ShortUrlStringifierInterface $stringifier;
|
||||
private MockObject&ShortUrlServiceInterface $shortUrlService;
|
||||
private MockObject&ShortUrlStringifierInterface $stringifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -38,12 +40,16 @@ class EditShortUrlCommandTest extends TestCase
|
||||
public function successMessageIsPrintedIfNoErrorOccurs(): void
|
||||
{
|
||||
$newLongUrl = 'https://example.com';
|
||||
$this->shortUrlService->expects($this->once())->method('updateShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foobar'),
|
||||
$this->callback(static fn (ShortUrlEdition $edition): bool => $edition->longUrl === $newLongUrl),
|
||||
)->willReturn(
|
||||
ShortUrl::createFake(),
|
||||
);
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('updateShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('foobar'),
|
||||
$this->callback(static fn (ShortUrlEdition $edition): bool => $edition->longUrl === $newLongUrl),
|
||||
)
|
||||
->willReturn(
|
||||
ShortUrl::createFake(),
|
||||
);
|
||||
$this->stringifier->expects($this->once())->method('stringify')->willReturn('https://s.test/foo');
|
||||
|
||||
$this->commandTester->execute(['short-code' => 'foobar', '--long-url' => $newLongUrl]);
|
||||
|
||||
@@ -30,7 +30,7 @@ use function Shlinkio\Shlink\Common\buildDateRange;
|
||||
class GetShortUrlVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject&VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -43,10 +43,14 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
public function noDateFlagsTriesToListWithoutDateRange(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::allTime()),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::allTime()),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
}
|
||||
@@ -55,10 +59,14 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
public function shortCodeIsAskedIfNotProvided(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->anything(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->anything(),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->setInputs([$shortCode]);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -70,10 +78,14 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$startDate = '2016-01-01';
|
||||
$endDate = '2016-02-01';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate))),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate))),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'short-code' => $shortCode,
|
||||
@@ -92,10 +104,14 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')),
|
||||
);
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->anything(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$this->anything(),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode, '--format' => $format->value]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -107,7 +123,6 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
{
|
||||
yield 'regular' => [
|
||||
VisitsListFormat::FULL,
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
static fn (Chronos $date) => <<<OUTPUT
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
@@ -116,7 +131,6 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
];
|
||||
yield 'CSV' => [
|
||||
VisitsListFormat::CSV,
|
||||
|
||||
@@ -30,7 +30,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class ListShortUrlsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ShortUrlListServiceInterface $shortUrlService;
|
||||
private MockObject&ShortUrlListServiceInterface $shortUrlService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -52,8 +52,11 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
$data[] = ShortUrlWithDeps::fromShortUrl(ShortUrl::withLongUrl('https://url_' . $i));
|
||||
}
|
||||
|
||||
$this->shortUrlService->expects($this->exactly(3))->method('listShortUrls')->withAnyParameters()
|
||||
->willReturnCallback(fn () => new Paginator(new ArrayAdapter($data)));
|
||||
$this->shortUrlService
|
||||
->expects($this->exactly(3))
|
||||
->method('listShortUrls')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback(static fn () => new Paginator(new ArrayAdapter($data)));
|
||||
|
||||
$this->commandTester->setInputs(['y', 'y', 'n']);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -74,9 +77,13 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
$data[] = ShortUrlWithDeps::fromShortUrl(ShortUrl::withLongUrl('https://url_' . $i));
|
||||
}
|
||||
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
||||
new ShortUrlsParams(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter($data)));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(
|
||||
new ShortUrlsParams(),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter($data)));
|
||||
|
||||
$this->commandTester->setInputs(['n']);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -95,9 +102,13 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
public function passingPageWillMakeListStartOnThatPage(): void
|
||||
{
|
||||
$page = 5;
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
||||
new ShortUrlsParams(page: $page),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(
|
||||
new ShortUrlsParams(page: $page),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->setInputs(['y']);
|
||||
$this->commandTester->execute(['--page' => $page]);
|
||||
@@ -109,11 +120,15 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
string $expectedOutput,
|
||||
ShortUrl $shortUrl,
|
||||
): void {
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
||||
new ShortUrlsParams(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([
|
||||
ShortUrlWithDeps::fromShortUrl($shortUrl),
|
||||
])));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(
|
||||
new ShortUrlsParams(),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([
|
||||
ShortUrlWithDeps::fromShortUrl($shortUrl),
|
||||
])));
|
||||
|
||||
$this->commandTester->setInputs(['y']);
|
||||
$this->commandTester->execute($input);
|
||||
@@ -132,74 +147,72 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
$shortCode = $shortUrl->getShortCode();
|
||||
$created = $shortUrl->dateCreated()->toAtomString();
|
||||
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
yield 'tags only' => [
|
||||
['--show-tags' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz |
|
||||
+------------+-------+-------------+-------------- Page 1 of 1 ------------------+--------------+---------------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz |
|
||||
+------------+-------+-------------+-------------- Page 1 of 1 ------------------+--------------+---------------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
yield 'domain only' => [
|
||||
['--show-domain' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Domain |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | DEFAULT |
|
||||
+------------+-------+-------------+----------- Page 1 of 1 ---------------------+--------------+---------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Domain |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | DEFAULT |
|
||||
+------------+-------+-------------+----------- Page 1 of 1 ---------------------+--------------+---------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
yield 'api key only' => [
|
||||
['--show-api-key' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | my api key |
|
||||
+------------+-------+-------------+------------- Page 1 of 1 -------------------+--------------+--------------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | my api key |
|
||||
+------------+-------+-------------+------------- Page 1 of 1 -------------------+--------------+--------------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
yield 'tags and api key' => [
|
||||
['--show-tags' => true, '--show-api-key' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | my api key |
|
||||
+------------+-------+-------------+-----------------+--- Page 1 of 1 -----------+--------------+---------------+--------------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | my api key |
|
||||
+------------+-------+-------------+-----------------+--- Page 1 of 1 -----------+--------------+---------------+--------------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
yield 'tags and domain' => [
|
||||
['--show-tags' => true, '--show-domain' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | Domain |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | DEFAULT |
|
||||
+------------+-------+-------------+-----------------+- Page 1 of 1 -------------+--------------+---------------+---------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | Domain |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | DEFAULT |
|
||||
+------------+-------+-------------+-----------------+- Page 1 of 1 -------------+--------------+---------------+---------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
yield 'all' => [
|
||||
['--show-tags' => true, '--show-domain' => true, '--show-api-key' => true],
|
||||
<<<OUTPUT
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | Domain | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | DEFAULT | my api key |
|
||||
+------------+-------+-------------+-----------------+-------- Page 1 of 1 ------+--------------+---------------+---------+--------------+
|
||||
OUTPUT,
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+--------------+
|
||||
| Short Code | Title | Short URL | Long URL | Date created | Visits count | Tags | Domain | API Key Name |
|
||||
+------------+-------+-------------+-----------------+---------------------------+--------------+---------------+---------+--------------+
|
||||
| {$shortCode} | | http:/{$shortCode} | https://foo.com | {$created} | 0 | foo, bar, baz | DEFAULT | my api key |
|
||||
+------------+-------+-------------+-----------------+-------- Page 1 of 1 ------+--------------+---------------+---------+--------------+
|
||||
OUTPUT,
|
||||
$shortUrl,
|
||||
];
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,9 +248,13 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
$expectedData['excludeTags'] = $excludeTags;
|
||||
}
|
||||
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(new ShortUrlsParams(
|
||||
...$expectedData,
|
||||
))->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(new ShortUrlsParams(
|
||||
...$expectedData,
|
||||
))
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->setInputs(['n']);
|
||||
$this->commandTester->execute($commandArgs);
|
||||
@@ -310,9 +327,13 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
#[Test, DataProvider('provideOrderBy')]
|
||||
public function orderByIsProperlyComputed(array $commandArgs, string|null $expectedOrderBy): void
|
||||
{
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(new ShortUrlsParams(
|
||||
orderBy: Ordering::fromOptionalString($expectedOrderBy),
|
||||
))->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(new ShortUrlsParams(
|
||||
orderBy: Ordering::fromOptionalString($expectedOrderBy),
|
||||
))
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->setInputs(['n']);
|
||||
$this->commandTester->execute($commandArgs);
|
||||
@@ -328,10 +349,14 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function requestingAllElementsWillSetItemsPerPage(): void
|
||||
{
|
||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(new ShortUrlsParams(
|
||||
itemsPerPage: Paginator::ALL_ITEMS,
|
||||
tagsMode: TagsMode::ANY,
|
||||
))->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$this->shortUrlService
|
||||
->expects($this->once())
|
||||
->method('listShortUrls')
|
||||
->with(new ShortUrlsParams(
|
||||
itemsPerPage: Paginator::ALL_ITEMS,
|
||||
tagsMode: TagsMode::ANY,
|
||||
))
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->execute(['--all' => true]);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use const PHP_EOL;
|
||||
class ResolveUrlCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ShortUrlResolverInterface $urlResolver;
|
||||
private MockObject&ShortUrlResolverInterface $urlResolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -36,9 +36,13 @@ class ResolveUrlCommandTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = ShortUrl::withLongUrl($expectedUrl);
|
||||
$this->urlResolver->expects($this->once())->method('resolveShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
)->willReturn($shortUrl);
|
||||
$this->urlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
)
|
||||
->willReturn($shortUrl);
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -50,9 +54,13 @@ class ResolveUrlCommandTest extends TestCase
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->urlResolver->expects($this->once())->method('resolveShortUrl')->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
)->willReturn($shortUrl);
|
||||
$this->urlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
)
|
||||
->willReturn($shortUrl);
|
||||
|
||||
$this->commandTester->setInputs([$shortCode]);
|
||||
$this->commandTester->execute([]);
|
||||
@@ -64,9 +72,13 @@ class ResolveUrlCommandTest extends TestCase
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain('abc123');
|
||||
$shortCode = $identifier->shortCode;
|
||||
|
||||
$this->urlResolver->expects($this->once())->method('resolveShortUrl')->with($identifier)->willThrowException(
|
||||
ShortUrlNotFoundException::fromNotFound($identifier),
|
||||
);
|
||||
$this->urlResolver
|
||||
->expects($this->once())
|
||||
->method('resolveShortUrl')
|
||||
->with($identifier)
|
||||
->willThrowException(
|
||||
ShortUrlNotFoundException::fromNotFound($identifier),
|
||||
);
|
||||
|
||||
$this->commandTester->execute(['short-code' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -15,7 +15,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DeleteTagsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & TagServiceInterface $tagService;
|
||||
private MockObject&TagServiceInterface $tagService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class GetTagVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject&VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -39,25 +39,27 @@ class GetTagVisitsCommandTest extends TestCase
|
||||
VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')),
|
||||
);
|
||||
$tag = 'abc123';
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForTag')->with($tag, $this->anything())->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('visitsForTag')
|
||||
->with($tag, $this->anything())
|
||||
->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
|
||||
$this->commandTester->execute(['tag' => $tag]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
OUTPUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class ListTagsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & TagServiceInterface $tagService;
|
||||
private MockObject&TagServiceInterface $tagService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -29,9 +29,13 @@ class ListTagsCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function noTagsPrintsEmptyMessage(): void
|
||||
{
|
||||
$this->tagService->expects($this->once())->method('tagsInfo')->withAnyParameters()->willReturn(
|
||||
new Paginator(new ArrayAdapter([])),
|
||||
);
|
||||
$this->tagService
|
||||
->expects($this->once())
|
||||
->method('tagsInfo')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
new Paginator(new ArrayAdapter([])),
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -42,26 +46,30 @@ class ListTagsCommandTest extends TestCase
|
||||
#[Test]
|
||||
public function listOfTagsIsPrinted(): void
|
||||
{
|
||||
$this->tagService->expects($this->once())->method('tagsInfo')->withAnyParameters()->willReturn(
|
||||
new Paginator(new ArrayAdapter([
|
||||
new TagInfo('foo', 10, 2),
|
||||
new TagInfo('bar', 7, 32),
|
||||
])),
|
||||
);
|
||||
$this->tagService
|
||||
->expects($this->once())
|
||||
->method('tagsInfo')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
new Paginator(new ArrayAdapter([
|
||||
new TagInfo('foo', 10, 2),
|
||||
new TagInfo('bar', 7, 32),
|
||||
])),
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
self::assertEquals(
|
||||
<<<OUTPUT
|
||||
+------+-------------+---------------+
|
||||
| Name | URLs amount | Visits amount |
|
||||
+------+-------------+---------------+
|
||||
| foo | 10 | 2 |
|
||||
| bar | 7 | 32 |
|
||||
+------+-------------+---------------+
|
||||
|
||||
OUTPUT,
|
||||
+------+-------------+---------------+
|
||||
| Name | URLs amount | Visits amount |
|
||||
+------+-------------+---------------+
|
||||
| foo | 10 | 2 |
|
||||
| bar | 7 | 32 |
|
||||
+------+-------------+---------------+
|
||||
|
||||
OUTPUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class RenameTagCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & TagServiceInterface $tagService;
|
||||
private MockObject&TagServiceInterface $tagService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -31,9 +31,13 @@ class RenameTagCommandTest extends TestCase
|
||||
{
|
||||
$oldName = 'foo';
|
||||
$newName = 'bar';
|
||||
$this->tagService->expects($this->once())->method('renameTag')->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
)->willThrowException(TagNotFoundException::fromTag('foo'));
|
||||
$this->tagService
|
||||
->expects($this->once())
|
||||
->method('renameTag')
|
||||
->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
)
|
||||
->willThrowException(TagNotFoundException::fromTag('foo'));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'old-name' => $oldName,
|
||||
@@ -49,9 +53,13 @@ class RenameTagCommandTest extends TestCase
|
||||
{
|
||||
$oldName = 'foo';
|
||||
$newName = 'bar';
|
||||
$this->tagService->expects($this->once())->method('renameTag')->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
)->willReturn(new Tag($newName));
|
||||
$this->tagService
|
||||
->expects($this->once())
|
||||
->method('renameTag')
|
||||
->with(
|
||||
Renaming::fromNames($oldName, $newName),
|
||||
)
|
||||
->willReturn(new Tag($newName));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'old-name' => $oldName,
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class DeleteOrphanVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsDeleterInterface $deleter;
|
||||
private MockObject&VisitsDeleterInterface $deleter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ use function sprintf;
|
||||
class DownloadGeoLiteDbCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & GeolocationDbUpdaterInterface $dbUpdater;
|
||||
private MockObject&GeolocationDbUpdaterInterface $dbUpdater;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -37,16 +37,20 @@ class DownloadGeoLiteDbCommandTest extends TestCase
|
||||
string $expectedMessage,
|
||||
int $expectedExitCode,
|
||||
): void {
|
||||
$this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback(
|
||||
function (GeolocationDownloadProgressHandlerInterface $handler) use ($olderDbExists): void {
|
||||
$handler->beforeDownload($olderDbExists);
|
||||
$handler->handleProgress(100, 50, $olderDbExists);
|
||||
$this->dbUpdater
|
||||
->expects($this->once())
|
||||
->method('checkDbUpdate')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback(
|
||||
static function (GeolocationDownloadProgressHandlerInterface $handler) use ($olderDbExists): void {
|
||||
$handler->beforeDownload($olderDbExists);
|
||||
$handler->handleProgress(100, 50, $olderDbExists);
|
||||
|
||||
throw $olderDbExists
|
||||
? GeolocationDbUpdateFailedException::withOlderDb()
|
||||
: GeolocationDbUpdateFailedException::withoutOlderDb();
|
||||
},
|
||||
);
|
||||
throw $olderDbExists
|
||||
? GeolocationDbUpdateFailedException::withOlderDb()
|
||||
: GeolocationDbUpdateFailedException::withoutOlderDb();
|
||||
},
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -93,9 +97,13 @@ class DownloadGeoLiteDbCommandTest extends TestCase
|
||||
#[Test, DataProvider('provideSuccessParams')]
|
||||
public function printsExpectedMessageWhenNoErrorOccurs(callable $checkUpdateBehavior, string $expectedMessage): void
|
||||
{
|
||||
$this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback(
|
||||
$checkUpdateBehavior,
|
||||
);
|
||||
$this->dbUpdater
|
||||
->expects($this->once())
|
||||
->method('checkDbUpdate')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback(
|
||||
$checkUpdateBehavior,
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -107,10 +115,16 @@ class DownloadGeoLiteDbCommandTest extends TestCase
|
||||
|
||||
public static function provideSuccessParams(): iterable
|
||||
{
|
||||
yield 'up to date db' => [fn () => GeolocationResult::CHECK_SKIPPED, '[INFO] GeoLite2 db file is up to date.'];
|
||||
yield 'outdated db' => [function (GeolocationDownloadProgressHandlerInterface $handler): GeolocationResult {
|
||||
$handler->beforeDownload(true);
|
||||
return GeolocationResult::DB_CREATED;
|
||||
}, '[OK] GeoLite2 db file properly downloaded.'];
|
||||
yield 'up to date db' => [
|
||||
static fn () => GeolocationResult::CHECK_SKIPPED,
|
||||
'[INFO] GeoLite2 db file is up to date.',
|
||||
];
|
||||
yield 'outdated db' => [
|
||||
static function (GeolocationDownloadProgressHandlerInterface $handler): GeolocationResult {
|
||||
$handler->beforeDownload(true);
|
||||
return GeolocationResult::DB_CREATED;
|
||||
},
|
||||
'[OK] GeoLite2 db file properly downloaded.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class GetNonOrphanVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject&VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -38,25 +38,27 @@ class GetNonOrphanVisitsCommandTest extends TestCase
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::fromParams('bar', 'foo', ''))->locate(
|
||||
VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')),
|
||||
);
|
||||
$this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->withAnyParameters()->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('nonOrphanVisits')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
OUTPUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
class GetOrphanVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject&VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -40,27 +40,30 @@ class GetOrphanVisitsCommandTest extends TestCase
|
||||
$visit = Visit::forBasePath(Visitor::fromParams('bar', 'foo', ''))->locate(
|
||||
VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')),
|
||||
);
|
||||
$this->visitsHelper->expects($this->once())->method('orphanVisits')->with($this->callback(
|
||||
fn (OrphanVisitsParams $param) => (
|
||||
($includesType && $param->type !== null) || (!$includesType && $param->type === null)
|
||||
),
|
||||
))->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
$this->visitsHelper
|
||||
->expects($this->once())
|
||||
->method('orphanVisits')
|
||||
->with($this->callback(
|
||||
static fn (OrphanVisitsParams $param) => (
|
||||
($includesType && $param->type !== null)
|
||||
|| (!$includesType && $param->type === null)
|
||||
),
|
||||
))
|
||||
->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
|
||||
$this->commandTester->execute($args);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = OrphanVisitType::BASE_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+--- Page 1 of 1 ---+--------+--------+-------------+--------------+----------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+--- Page 1 of 1 ---+--------+--------+-------------+--------------+----------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
OUTPUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ use const PHP_EOL;
|
||||
class LocateVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitLocatorInterface $visitService;
|
||||
private MockObject & VisitToLocationHelperInterface $visitToLocation;
|
||||
private Stub & Lock\LockInterface $lock;
|
||||
private Stub & Command $downloadDbCommand;
|
||||
private MockObject&VisitLocatorInterface $visitService;
|
||||
private MockObject&VisitToLocationHelperInterface $visitToLocation;
|
||||
private Stub&Lock\LockInterface $lock;
|
||||
private Stub&Command $downloadDbCommand;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -70,21 +70,28 @@ class LocateVisitsCommandTest extends TestCase
|
||||
$mockMethodBehavior = $this->invokeHelperMethods($visit, $location);
|
||||
|
||||
$this->lock->method('acquire')->willReturn(true);
|
||||
$this->visitService->expects($this->exactly($expectedUnlocatedCalls))
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitService->expects($this->exactly($expectedEmptyCalls))
|
||||
->method('locateVisitsWithEmptyLocation')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitService->expects($this->exactly($expectedAllCalls))
|
||||
->method('locateAllVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitToLocation->expects(
|
||||
$this->exactly($expectedUnlocatedCalls + $expectedEmptyCalls + $expectedAllCalls),
|
||||
)->method('resolveVisitLocation')->withAnyParameters()->willReturn(Location::empty());
|
||||
$this->visitService
|
||||
->expects($this->exactly($expectedUnlocatedCalls))
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitService
|
||||
->expects($this->exactly($expectedEmptyCalls))
|
||||
->method('locateVisitsWithEmptyLocation')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitService
|
||||
->expects($this->exactly($expectedAllCalls))
|
||||
->method('locateAllVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($mockMethodBehavior);
|
||||
$this->visitToLocation
|
||||
->expects(
|
||||
$this->exactly($expectedUnlocatedCalls + $expectedEmptyCalls + $expectedAllCalls),
|
||||
)
|
||||
->method('resolveVisitLocation')
|
||||
->withAnyParameters()
|
||||
->willReturn(Location::empty());
|
||||
$this->downloadDbCommand->method('run')->willReturn(Command::SUCCESS);
|
||||
|
||||
$this->commandTester->setInputs(['y']);
|
||||
@@ -113,10 +120,11 @@ class LocateVisitsCommandTest extends TestCase
|
||||
$location = VisitLocation::fromLocation(Location::empty());
|
||||
|
||||
$this->lock->method('acquire')->willReturn(true);
|
||||
$this->visitService->expects($this->once())
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($this->invokeHelperMethods($visit, $location));
|
||||
$this->visitService
|
||||
->expects($this->once())
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($this->invokeHelperMethods($visit, $location));
|
||||
$this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->willThrowException($e);
|
||||
$this->downloadDbCommand->method('run')->willReturn(Command::SUCCESS);
|
||||
|
||||
@@ -140,13 +148,17 @@ class LocateVisitsCommandTest extends TestCase
|
||||
$location = VisitLocation::fromLocation(Location::empty());
|
||||
|
||||
$this->lock->method('acquire')->willReturn(true);
|
||||
$this->visitService->expects($this->once())
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($this->invokeHelperMethods($visit, $location));
|
||||
$this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->willThrowException(
|
||||
IpCannotBeLocatedException::forError(WrongIpException::fromIpAddress('1.2.3.4')),
|
||||
);
|
||||
$this->visitService
|
||||
->expects($this->once())
|
||||
->method('locateUnlocatedVisits')
|
||||
->withAnyParameters()
|
||||
->willReturnCallback($this->invokeHelperMethods($visit, $location));
|
||||
$this->visitToLocation
|
||||
->expects($this->once())
|
||||
->method('resolveVisitLocation')
|
||||
->willThrowException(
|
||||
IpCannotBeLocatedException::forError(WrongIpException::fromIpAddress('1.2.3.4')),
|
||||
);
|
||||
$this->downloadDbCommand->method('run')->willReturn(Command::SUCCESS);
|
||||
|
||||
$this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
|
||||
|
||||
@@ -18,10 +18,13 @@ class InvalidRoleConfigExceptionTest extends TestCase
|
||||
{
|
||||
$e = InvalidRoleConfigException::forDomainOnlyWithDefaultDomain();
|
||||
|
||||
self::assertEquals(sprintf(
|
||||
'You cannot create an API key with the "%s" role attached to the default domain. '
|
||||
. 'The role is currently limited to non-default domains.',
|
||||
Role::DOMAIN_SPECIFIC->value,
|
||||
), $e->getMessage());
|
||||
self::assertEquals(
|
||||
sprintf(
|
||||
'You cannot create an API key with the "%s" role attached to the default domain. '
|
||||
. 'The role is currently limited to non-default domains.',
|
||||
Role::DOMAIN_SPECIFIC->value,
|
||||
),
|
||||
$e->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -42,11 +42,13 @@ class ApplicationFactoryTest extends TestCase
|
||||
|
||||
private function createServiceManager(array $config = []): ServiceManager
|
||||
{
|
||||
return new ServiceManager(['services' => [
|
||||
'config' => [
|
||||
'cli' => $config,
|
||||
return new ServiceManager([
|
||||
'services' => [
|
||||
'config' => [
|
||||
'cli' => $config,
|
||||
],
|
||||
AppOptions::class => new AppOptions(),
|
||||
],
|
||||
AppOptions::class => new AppOptions(),
|
||||
]]);
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ use function sprintf;
|
||||
class RedirectRuleHandlerTest extends TestCase
|
||||
{
|
||||
private RedirectRuleHandler $handler;
|
||||
private StyleInterface & MockObject $io;
|
||||
private StyleInterface&MockObject $io;
|
||||
private ShortUrl $shortUrl;
|
||||
private RedirectCondition $cond1;
|
||||
private RedirectCondition $cond2;
|
||||
@@ -57,7 +57,7 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
$this->handler = new RedirectRuleHandler();
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideExitActions')]
|
||||
#[Test, DataProvider('provideExitActions')]
|
||||
public function commentIsDisplayedWhenRulesListIsEmpty(
|
||||
RedirectRuleHandlerAction $action,
|
||||
array|null $expectedResult,
|
||||
@@ -77,24 +77,27 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
RedirectRuleHandlerAction $action,
|
||||
array|null $_,
|
||||
): void {
|
||||
$comment = fn (string $value) => sprintf('<comment>%s</comment>', $value);
|
||||
$comment = static fn (string $value) => sprintf('<comment>%s</comment>', $value);
|
||||
|
||||
$this->io->expects($this->once())->method('choice')->willReturn($action->value);
|
||||
$this->io->expects($this->never())->method('newLine');
|
||||
$this->io->expects($this->never())->method('text');
|
||||
$this->io->expects($this->once())->method('table')->with($this->isArray(), [
|
||||
['1', $comment($this->cond1->toHumanFriendly()), 'https://example.com/one'],
|
||||
[
|
||||
'2',
|
||||
$comment($this->cond2->toHumanFriendly()) . ' AND ' . $comment($this->cond3->toHumanFriendly()),
|
||||
'https://example.com/two',
|
||||
],
|
||||
[
|
||||
'3',
|
||||
$comment($this->cond1->toHumanFriendly()) . ' AND ' . $comment($this->cond3->toHumanFriendly()),
|
||||
'https://example.com/three',
|
||||
],
|
||||
]);
|
||||
$this->io
|
||||
->expects($this->once())
|
||||
->method('table')
|
||||
->with($this->isArray(), [
|
||||
['1', $comment($this->cond1->toHumanFriendly()), 'https://example.com/one'],
|
||||
[
|
||||
'2',
|
||||
$comment($this->cond2->toHumanFriendly()) . ' AND ' . $comment($this->cond3->toHumanFriendly()),
|
||||
'https://example.com/two',
|
||||
],
|
||||
[
|
||||
'3',
|
||||
$comment($this->cond1->toHumanFriendly()) . ' AND ' . $comment($this->cond3->toHumanFriendly()),
|
||||
'https://example.com/three',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->handler->manageRules($this->io, $this->shortUrl, $this->rules);
|
||||
}
|
||||
@@ -114,54 +117,68 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
array $expectedConditions,
|
||||
bool $continue = false,
|
||||
): void {
|
||||
$this->io->method('ask')->willReturnCallback(
|
||||
fn (string $message): string|int => match ($message) {
|
||||
'Rule priority (the lower the value, the higher the priority)' => 2, // Add in between existing rules
|
||||
'Long URL to redirect when the rule matches' => 'https://example.com/new-two',
|
||||
'Language to match?' => 'en-US',
|
||||
'Query param name?' => 'foo',
|
||||
'Query param value?' => 'bar',
|
||||
'IP address, CIDR block or wildcard-pattern (1.2.*.*)' => '1.2.3.4',
|
||||
'Country code to match?' => 'FR',
|
||||
'City name to match?' => 'Los angeles',
|
||||
'Date to match?' => '2016-05-01T20:34:16+02:00',
|
||||
default => '',
|
||||
},
|
||||
);
|
||||
$this->io->method('choice')->willReturnCallback(
|
||||
function (string $message) use (&$callIndex, $type): string {
|
||||
$callIndex++;
|
||||
$this->io
|
||||
->method('ask')
|
||||
->willReturnCallback(
|
||||
static fn (string $message): string|int => match ($message) {
|
||||
'Rule priority (the lower the value, the higher the priority)' => 2, // Add in between existing rules
|
||||
'Long URL to redirect when the rule matches' => 'https://example.com/new-two',
|
||||
'Language to match?' => 'en-US',
|
||||
'Query param name?' => 'foo',
|
||||
'Query param value?' => 'bar',
|
||||
'IP address, CIDR block or wildcard-pattern (1.2.*.*)' => '1.2.3.4',
|
||||
'Country code to match?' => 'FR',
|
||||
'City name to match?' => 'Los angeles',
|
||||
'Date to match?' => '2016-05-01T20:34:16+02:00',
|
||||
default => '',
|
||||
},
|
||||
);
|
||||
$this->io
|
||||
->method('choice')
|
||||
->willReturnCallback(
|
||||
static function (string $message) use (&$callIndex, $type): string {
|
||||
$callIndex++;
|
||||
|
||||
if ($message === 'Type of the condition?') {
|
||||
return $type->value;
|
||||
} elseif ($message === 'Device to match?') {
|
||||
return DeviceType::ANDROID->value;
|
||||
} elseif ($message === 'Browser to match?') {
|
||||
return Browser::CHROME->value;
|
||||
}
|
||||
if ($message === 'Type of the condition?') {
|
||||
return $type->value;
|
||||
} elseif ($message === 'Device to match?') {
|
||||
return DeviceType::ANDROID->value;
|
||||
} elseif ($message === 'Browser to match?') {
|
||||
return Browser::CHROME->value;
|
||||
}
|
||||
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::ADD : RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::ADD : RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
|
||||
$continueCallCount = 0;
|
||||
$this->io->method('confirm')->willReturnCallback(function () use (&$continueCallCount, $continue) {
|
||||
$continueCallCount++;
|
||||
return $continueCallCount < 2 && $continue;
|
||||
});
|
||||
$this->io
|
||||
->method('confirm')
|
||||
->willReturnCallback(static function () use (&$continueCallCount, $continue) {
|
||||
$continueCallCount++;
|
||||
return $continueCallCount < 2 && $continue;
|
||||
});
|
||||
|
||||
$result = $this->handler->manageRules($this->io, $this->shortUrl, $this->rules);
|
||||
|
||||
self::assertEquals([
|
||||
$this->rules[0],
|
||||
new ShortUrlRedirectRule($this->shortUrl, 2, 'https://example.com/new-two', new ArrayCollection(
|
||||
$expectedConditions,
|
||||
)),
|
||||
$this->rules[1],
|
||||
$this->rules[2],
|
||||
], $result);
|
||||
self::assertEquals(
|
||||
[
|
||||
$this->rules[0],
|
||||
new ShortUrlRedirectRule(
|
||||
$this->shortUrl,
|
||||
2,
|
||||
'https://example.com/new-two',
|
||||
new ArrayCollection(
|
||||
$expectedConditions,
|
||||
),
|
||||
),
|
||||
$this->rules[1],
|
||||
$this->rules[2],
|
||||
],
|
||||
$result,
|
||||
);
|
||||
}
|
||||
|
||||
public static function provideDeviceConditions(): iterable
|
||||
@@ -206,19 +223,22 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
public function existingRulesCanBeRemoved(): void
|
||||
{
|
||||
$callIndex = 0;
|
||||
$this->io->expects($this->exactly(3))->method('choice')->willReturnCallback(
|
||||
function (string $message) use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$this->io
|
||||
->expects($this->exactly(3))
|
||||
->method('choice')
|
||||
->willReturnCallback(
|
||||
static function (string $message) use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
|
||||
if ($message === 'What rule do you want to delete?') {
|
||||
return '2 - https://example.com/two'; // Second rule to be removed
|
||||
}
|
||||
if ($message === 'What rule do you want to delete?') {
|
||||
return '2 - https://example.com/two'; // Second rule to be removed
|
||||
}
|
||||
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::REMOVE : RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::REMOVE : RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io->expects($this->never())->method('warning');
|
||||
|
||||
$result = $this->handler->manageRules($this->io, $this->shortUrl, $this->rules);
|
||||
@@ -230,13 +250,16 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
public function warningIsPrintedWhenTryingToRemoveRuleFromEmptyList(): void
|
||||
{
|
||||
$callIndex = 0;
|
||||
$this->io->expects($this->exactly(2))->method('choice')->willReturnCallback(
|
||||
function () use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::REMOVE : RedirectRuleHandlerAction::DISCARD;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io
|
||||
->expects($this->exactly(2))
|
||||
->method('choice')
|
||||
->willReturnCallback(
|
||||
static function () use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::REMOVE : RedirectRuleHandlerAction::DISCARD;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io->expects($this->once())->method('warning')->with('There are no rules to remove');
|
||||
|
||||
$this->handler->manageRules($this->io, $this->shortUrl, []);
|
||||
@@ -245,25 +268,32 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
#[Test]
|
||||
public function existingRulesCanBeReArranged(): void
|
||||
{
|
||||
$this->io->method('ask')->willReturnCallback(
|
||||
fn (string $message): string|int => match ($message) {
|
||||
'Rule priority (the lower the value, the higher the priority)' => 1,
|
||||
default => '',
|
||||
},
|
||||
);
|
||||
$this->io->expects($this->exactly(3))->method('choice')->willReturnCallback(
|
||||
function (string $message) use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$this->io
|
||||
->method('ask')
|
||||
->willReturnCallback(
|
||||
static fn (string $message): string|int => match ($message) {
|
||||
'Rule priority (the lower the value, the higher the priority)' => 1,
|
||||
default => '',
|
||||
},
|
||||
);
|
||||
$this->io
|
||||
->expects($this->exactly(3))
|
||||
->method('choice')
|
||||
->willReturnCallback(
|
||||
static function (string $message) use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
|
||||
if ($message === 'What rule do you want to re-arrange?') {
|
||||
return '2 - https://example.com/two'; // Second rule to be re-arrange
|
||||
}
|
||||
if ($message === 'What rule do you want to re-arrange?') {
|
||||
return '2 - https://example.com/two'; // Second rule to be re-arrange
|
||||
}
|
||||
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::RE_ARRANGE : RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
// First we select remove action to trigger code branch, then save to finish execution
|
||||
$action = $callIndex === 1
|
||||
? RedirectRuleHandlerAction::RE_ARRANGE
|
||||
: RedirectRuleHandlerAction::SAVE;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io->expects($this->never())->method('warning');
|
||||
|
||||
$result = $this->handler->manageRules($this->io, $this->shortUrl, $this->rules);
|
||||
@@ -275,13 +305,18 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
public function warningIsPrintedWhenTryingToReArrangeRuleFromEmptyList(): void
|
||||
{
|
||||
$callIndex = 0;
|
||||
$this->io->expects($this->exactly(2))->method('choice')->willReturnCallback(
|
||||
function () use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$action = $callIndex === 1 ? RedirectRuleHandlerAction::RE_ARRANGE : RedirectRuleHandlerAction::DISCARD;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io
|
||||
->expects($this->exactly(2))
|
||||
->method('choice')
|
||||
->willReturnCallback(
|
||||
static function () use (&$callIndex): string {
|
||||
$callIndex++;
|
||||
$action = $callIndex === 1
|
||||
? RedirectRuleHandlerAction::RE_ARRANGE
|
||||
: RedirectRuleHandlerAction::DISCARD;
|
||||
return $action->value;
|
||||
},
|
||||
);
|
||||
$this->io->expects($this->once())->method('warning')->with('There are no rules to re-arrange');
|
||||
|
||||
$this->handler->manageRules($this->io, $this->shortUrl, []);
|
||||
|
||||
@@ -13,7 +13,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class CliTestUtils
|
||||
{
|
||||
public static function createCommandStub(string $name): Stub & Command
|
||||
public static function createCommandStub(string $name): Stub&Command
|
||||
{
|
||||
static $generator = null;
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
class PhpProcessRunnerTest extends TestCase
|
||||
{
|
||||
private MockObject & ProcessRunnerInterface $wrapped;
|
||||
private MockObject & PhpExecutableFinder $executableFinder;
|
||||
private MockObject&ProcessRunnerInterface $wrapped;
|
||||
private MockObject&PhpExecutableFinder $executableFinder;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
|
||||
@@ -17,10 +17,10 @@ use Symfony\Component\Process\Process;
|
||||
class ProcessRunnerTest extends TestCase
|
||||
{
|
||||
private ProcessRunner $runner;
|
||||
private MockObject & ProcessHelper $helper;
|
||||
private MockObject & DebugFormatterHelper $formatter;
|
||||
private MockObject & Process $process;
|
||||
private MockObject & OutputInterface $output;
|
||||
private MockObject&ProcessHelper $helper;
|
||||
private MockObject&DebugFormatterHelper $formatter;
|
||||
private MockObject&Process $process;
|
||||
private MockObject&OutputInterface $output;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -76,10 +76,13 @@ class ProcessRunnerTest extends TestCase
|
||||
$this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturnSelf();
|
||||
$this->process->expects($this->never())->method('isSuccessful');
|
||||
$this->process->expects($this->never())->method('getCommandLine');
|
||||
$this->helper->expects($this->once())->method('wrapCallback')->withAnyParameters()->willReturn(
|
||||
function (): void {
|
||||
},
|
||||
);
|
||||
$this->helper
|
||||
->expects($this->once())
|
||||
->method('wrapCallback')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
static function (): void {},
|
||||
);
|
||||
$this->formatter->expects($this->never())->method('start');
|
||||
$this->formatter->expects($this->never())->method('stop');
|
||||
|
||||
|
||||
@@ -23,9 +23,12 @@ class ShlinkTableTest extends TestCase
|
||||
$footerTitle = 'Footer';
|
||||
|
||||
$baseTable = $this->createMock(Table::class);
|
||||
$baseTable->expects($this->once())->method('setStyle')->with(
|
||||
$this->isInstanceOf(TableStyle::class),
|
||||
)->willReturnSelf();
|
||||
$baseTable->expects($this->once())
|
||||
->method('setStyle')
|
||||
->with(
|
||||
$this->isInstanceOf(TableStyle::class),
|
||||
)
|
||||
->willReturnSelf();
|
||||
$baseTable->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
|
||||
$baseTable->expects($this->once())->method('setRows')->with($rows)->willReturnSelf();
|
||||
$baseTable->expects($this->once())->method('setFooterTitle')->with($footerTitle)->willReturnSelf();
|
||||
|
||||
@@ -20,9 +20,7 @@ use Symfony\Component\Lock;
|
||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
|
||||
'delegators' => [
|
||||
MapperBuilder::class => [
|
||||
ObjectMapper\MapperBuilderWithCacheDelegatorFactory::class,
|
||||
@@ -270,5 +268,4 @@ return [
|
||||
|
||||
Crawling\CrawlingHelper::class => [ShortUrl\Repository\CrawlableShortCodesQuery::class],
|
||||
],
|
||||
|
||||
];
|
||||
Loaded 100 of 453 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user