diff --git a/Dockerfile b/Dockerfile
index 2616323a..34d6d7ef 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,6 @@ ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
ARG SHLINK_USER_ID='root'
ENV SHLINK_USER_ID ${SHLINK_USER_ID}
-ENV OPENSWOOLE_VERSION 22.1.2
ENV PDO_SQLSRV_VERSION 5.12.0
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
@@ -26,13 +25,8 @@ RUN \
apk del .dev-deps && \
apk add --no-cache postgresql icu libzip libpng
-# Install openswoole and sqlsrv driver for x86_64 builds
+# Install sqlsrv driver for x86_64 builds
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
- if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
- # Openswoole is deprecated. Remove in v4.0.0
- pecl install openswoole-${OPENSWOOLE_VERSION} && \
- docker-php-ext-enable openswoole ; \
- fi; \
if [ $(uname -m) == "x86_64" ]; then \
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
@@ -47,14 +41,7 @@ FROM base as builder
COPY . .
COPY --from=composer:2 /usr/bin/composer ./composer.phar
RUN apk add --no-cache git && \
- # FIXME Ignoring ext-openswoole platform req, as it makes install fail with roadrunner, even though it's a dev dependency and we are passing --no-dev
- php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole && \
- if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
- # Openswoole is deprecated. Remove in v4.0.0
- php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
- elif [ "$SHLINK_RUNTIME" == 'rr' ]; then \
- php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole ; \
- fi; \
+ php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction && \
php composer.phar clear-cache && \
rm -r docker composer.* && \
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
diff --git a/build.sh b/build.sh
index f91ab408..7b77295f 100755
--- a/build.sh
+++ b/build.sh
@@ -1,18 +1,15 @@
#!/usr/bin/env bash
set -e
-if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
+if [ "$#" -lt 1 ]; then
echo "Usage:" >&2
- echo " $0 {version} [--no-swoole]" >&2
+ echo " $0 {version}" >&2
exit 1
fi
version=$1
-noSwoole=$2
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
-# Openswoole is deprecated. Remove in v4.0.0
-[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
-distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
+distId="shlink${version}_php${phpVersion}_dist"
builtContent="./build/${distId}"
projectdir=$(pwd)
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
@@ -31,18 +28,8 @@ cd "${builtContent}"
# Install dependencies
echo "Installing dependencies with $composerBin..."
-composerFlags="--optimize-autoloader --no-progress --no-interaction"
${composerBin} self-update
-${composerBin} install --no-dev --prefer-dist $composerFlags
-
-if [[ $noSwoole ]]; then
- # If generating a dist not for openswoole, uninstall mezzio-swoole
- ${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
-else
- # Deprecated. Remove in Shlink v4.0.0
- # If generating a dist for openswoole, uninstall RoadRunner
- ${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev $composerFlags
-fi
+${composerBin} install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction
# Delete development files
echo 'Deleting dev files...'
diff --git a/config/autoload/installer.global.php b/config/autoload/installer.global.php
index 45f92153..affb0897 100644
--- a/config/autoload/installer.global.php
+++ b/config/autoload/installer.global.php
@@ -21,8 +21,6 @@ return [
Option\Database\DatabaseUnixSocketConfigOption::class,
Option\UrlShortener\ShortDomainHostConfigOption::class,
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
- Option\Visit\VisitsWebhooksConfigOption::class,
- Option\Visit\OrphanVisitsWebhooksConfigOption::class,
Option\Redirect\BaseUrlRedirectConfigOption::class,
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
Option\Redirect\Regular404RedirectConfigOption::class,
diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php
index 072937ec..99f71bce 100644
--- a/config/autoload/middleware-pipeline.global.php
+++ b/config/autoload/middleware-pipeline.global.php
@@ -47,7 +47,6 @@ return [
'rest' => [
'path' => '/rest',
'middleware' => [
- Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
Router\Middleware\ImplicitOptionsMiddleware::class,
Rest\Middleware\BodyParserMiddleware::class,
Rest\Middleware\AuthenticationMiddleware::class,
diff --git a/config/autoload/rabbit.global.php b/config/autoload/rabbit.global.php
index bf9591e5..fd8cda68 100644
--- a/config/autoload/rabbit.global.php
+++ b/config/autoload/rabbit.global.php
@@ -14,9 +14,6 @@ return [
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
-
- // Deprecated
- 'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
],
];
diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php
index 2a121bee..2816577d 100644
--- a/config/autoload/url-shortener.global.php
+++ b/config/autoload/url-shortener.global.php
@@ -14,7 +14,7 @@ return (static function (): array {
MIN_SHORT_CODES_LENGTH,
);
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv(ShortUrlMode::STRICT->value);
- $mode = ShortUrlMode::tryDeprecated($modeFromEnv) ?? ShortUrlMode::STRICT;
+ $mode = ShortUrlMode::tryFrom($modeFromEnv) ?? ShortUrlMode::STRICT;
return [
diff --git a/config/autoload/webhooks.global.php b/config/autoload/webhooks.global.php
deleted file mode 100644
index e72c4904..00000000
--- a/config/autoload/webhooks.global.php
+++ /dev/null
@@ -1,20 +0,0 @@
-loadFromEnv();
-
- return [
-
- 'visits_webhooks' => [
- 'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
- 'notify_orphan_visits_to_webhooks' =>
- (bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
- ],
-
- ];
-})();
diff --git a/config/constants.php b/config/constants.php
index f08c135c..7b263262 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -9,7 +9,7 @@ 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; // Deprecated. Default to 307 for Shlink v4
+const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const TITLE_TAG_VALUE = '/
]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
@@ -19,6 +19,5 @@ const DEFAULT_QR_CODE_MARGIN = 0;
const DEFAULT_QR_CODE_FORMAT = 'png';
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
-// Deprecated. Shlink 4.0.0 should change default value to `true`
-const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = false;
+const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
const MIN_TASK_WORKERS = 4;
diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh
index 6c95bee2..faa506a9 100644
--- a/docker/docker-entrypoint.sh
+++ b/docker/docker-entrypoint.sh
@@ -20,19 +20,6 @@ fi
php vendor/bin/shlink-installer init ${flags}
-# Periodically run visit:locate every hour, if ENABLE_PERIODIC_VISIT_LOCATE=true was provided and running as root
-# FIXME: ENABLE_PERIODIC_VISIT_LOCATE is deprecated. Remove cron support in Shlink 4.0.0
-if [ "${ENABLE_PERIODIC_VISIT_LOCATE}" = "true" ] && [ "${SHLINK_USER_ID}" = "root" ]; then
- echo "Configuring periodic visit location..."
- echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
- /usr/sbin/crond &
-fi
-
-if [ "$SHLINK_RUNTIME" = 'openswoole' ]; then
- # Openswoole is deprecated. Remove in Shlink 4.0.0
- # When restarting the container, openswoole might think it is already in execution
- # This forces the app to be started every second until the exit code is 0
- until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
-elif [ "$SHLINK_RUNTIME" = 'rr' ]; then
+if [ "$SHLINK_RUNTIME" = 'rr' ]; then
./bin/rr serve -c config/roadrunner/.rr.yml
fi
diff --git a/docs/async-api/async-api.json b/docs/async-api/async-api.json
index d45dae2b..d3177b98 100644
--- a/docs/async-api/async-api.json
+++ b/docs/async-api/async-api.json
@@ -122,11 +122,6 @@
"visitsSummary": {
"$ref": "#/components/schemas/VisitsSummary"
},
- "visitsCount": {
- "deprecated": true,
- "type": "integer",
- "description": "The number of visits that this short URL has received."
- },
"tags": {
"type": "array",
"items": {
diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json
index 98fd9c87..8a420e9b 100644
--- a/docs/swagger/definitions/ShortUrl.json
+++ b/docs/swagger/definitions/ShortUrl.json
@@ -6,7 +6,6 @@
"longUrl",
"deviceLongUrls",
"dateCreated",
- "visitsCount",
"visitsSummary",
"tags",
"meta",
@@ -36,11 +35,6 @@
"format": "date-time",
"description": "The date in which the short URL was created in ISO format."
},
- "visitsCount": {
- "deprecated": true,
- "type": "integer",
- "description": "**[DEPRECATED]** Use `visitsSummary.total` instead."
- },
"visitsSummary": {
"$ref": "./VisitsSummary.json"
},
diff --git a/docs/swagger/definitions/TagInfo.json b/docs/swagger/definitions/TagInfo.json
index 41de1068..27658c95 100644
--- a/docs/swagger/definitions/TagInfo.json
+++ b/docs/swagger/definitions/TagInfo.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "required": ["tag", "shortUrlsCount", "visitsSummary", "visitsCount"],
+ "required": ["tag", "shortUrlsCount", "visitsSummary"],
"properties": {
"tag": {
"type": "string",
@@ -12,11 +12,6 @@
},
"visitsSummary": {
"$ref": "./VisitsSummary.json"
- },
- "visitsCount": {
- "deprecated": true,
- "type": "number",
- "description": "**[DEPRECATED]** Use visitsSummary.total instead"
}
}
}
diff --git a/docs/swagger/definitions/VisitStats.json b/docs/swagger/definitions/VisitStats.json
index 2ed24375..a1d8ce19 100644
--- a/docs/swagger/definitions/VisitStats.json
+++ b/docs/swagger/definitions/VisitStats.json
@@ -1,22 +1,12 @@
{
"type": "object",
- "required": ["nonOrphanVisits", "orphanVisits", "visitsCount", "orphanVisitsCount"],
+ "required": ["nonOrphanVisits", "orphanVisits"],
"properties": {
"nonOrphanVisits": {
"$ref": "./VisitsSummary.json"
},
"orphanVisits": {
"$ref": "./VisitsSummary.json"
- },
- "visitsCount": {
- "deprecated": true,
- "type": "number",
- "description": "**[DEPRECATED]** Use nonOrphanVisits.total instead"
- },
- "orphanVisitsCount": {
- "deprecated": true,
- "type": "number",
- "description": "**[DEPRECATED]** Use orphanVisits.total instead"
}
}
}
diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
index c9497daf..297c435e 100644
--- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
@@ -218,7 +218,7 @@ class ListShortUrlsCommand extends Command
'Short URL' => $pickProp('shortUrl'),
'Long URL' => $pickProp('longUrl'),
'Date created' => $pickProp('dateCreated'),
- 'Visits count' => $pickProp('visitsCount'),
+ 'Visits count' => static fn (array $shortUrl) => $shortUrl['visitsSummary']->total,
];
if ($input->getOption('show-tags')) {
$columnsMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']);
diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php
index 591fcc79..6b6be190 100644
--- a/module/Core/config/dependencies.config.php
+++ b/module/Core/config/dependencies.config.php
@@ -31,7 +31,6 @@ return [
Options\TrackingOptions::class => [ValinorConfigFactory::class, 'config.tracking'],
Options\QrCodeOptions::class => [ValinorConfigFactory::class, 'config.qr_codes'],
Options\RabbitMqOptions::class => [ValinorConfigFactory::class, 'config.rabbitmq'],
- Options\WebhookOptions::class => ConfigAbstractFactory::class,
ShortUrl\UrlShortener::class => ConfigAbstractFactory::class,
ShortUrl\ShortUrlService::class => ConfigAbstractFactory::class,
@@ -113,8 +112,6 @@ return [
Domain\DomainService::class,
],
- Options\WebhookOptions::class => ['config.visits_webhooks'],
-
ShortUrl\UrlShortener::class => [
ShortUrl\Helper\ShortUrlTitleResolutionHelper::class,
'em',
diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php
index 1a81d8ed..012b8e12 100644
--- a/module/Core/config/event_dispatcher.config.php
+++ b/module/Core/config/event_dispatcher.config.php
@@ -37,7 +37,6 @@ return (static function (): array {
EventDispatcher\Mercure\NotifyVisitToMercure::class,
EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class,
EventDispatcher\RedisPubSub\NotifyVisitToRedis::class,
- EventDispatcher\NotifyVisitToWebHooks::class,
EventDispatcher\UpdateGeoLiteDb::class,
],
EventDispatcher\Event\ShortUrlCreated::class => [
@@ -66,7 +65,6 @@ return (static function (): array {
EventDispatcher\LocateVisit::class => ConfigAbstractFactory::class,
EventDispatcher\Matomo\SendVisitToMatomo::class => ConfigAbstractFactory::class,
EventDispatcher\LocateUnlocatedVisits::class => ConfigAbstractFactory::class,
- EventDispatcher\NotifyVisitToWebHooks::class => ConfigAbstractFactory::class,
EventDispatcher\Mercure\NotifyVisitToMercure::class => ConfigAbstractFactory::class,
EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => ConfigAbstractFactory::class,
EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => ConfigAbstractFactory::class,
@@ -104,9 +102,6 @@ return (static function (): array {
EventDispatcher\LocateUnlocatedVisits::class => [
EventDispatcher\CloseDbConnectionEventListenerDelegator::class,
],
- EventDispatcher\NotifyVisitToWebHooks::class => [
- EventDispatcher\CloseDbConnectionEventListenerDelegator::class,
- ],
],
],
@@ -119,14 +114,6 @@ return (static function (): array {
EventDispatcherInterface::class,
],
EventDispatcher\LocateUnlocatedVisits::class => [VisitLocator::class, VisitToLocationHelper::class],
- EventDispatcher\NotifyVisitToWebHooks::class => [
- 'httpClient',
- 'em',
- 'Logger_Shlink',
- Options\WebhookOptions::class,
- ShortUrl\Transformer\ShortUrlDataTransformer::class,
- Options\AppOptions::class,
- ],
EventDispatcher\Mercure\NotifyVisitToMercure::class => [
MercureHubPublishingHelper::class,
EventDispatcher\PublishingUpdatesGenerator::class,
@@ -144,7 +131,6 @@ return (static function (): array {
EventDispatcher\PublishingUpdatesGenerator::class,
'em',
'Logger_Shlink',
- Visit\Transformer\OrphanVisitDataTransformer::class,
Options\RabbitMqOptions::class,
],
EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => [
@@ -187,7 +173,6 @@ return (static function (): array {
Options\RabbitMqOptions::class,
'config.redis.pub_sub_enabled',
MercureOptions::class,
- Options\WebhookOptions::class,
GeoLite2Options::class,
MatomoOptions::class,
],
diff --git a/module/Core/src/Config/EnvVars.php b/module/Core/src/Config/EnvVars.php
index 40f311e9..4a32b7c3 100644
--- a/module/Core/src/Config/EnvVars.php
+++ b/module/Core/src/Config/EnvVars.php
@@ -36,8 +36,6 @@ enum EnvVars: string
case RABBITMQ_PASSWORD = 'RABBITMQ_PASSWORD';
case RABBITMQ_VHOST = 'RABBITMQ_VHOST';
case RABBITMQ_USE_SSL = 'RABBITMQ_USE_SSL';
- /** @deprecated */
- case RABBITMQ_LEGACY_VISITS_PUBLISHING = 'RABBITMQ_LEGACY_VISITS_PUBLISHING';
case MATOMO_ENABLED = 'MATOMO_ENABLED';
case MATOMO_BASE_URL = 'MATOMO_BASE_URL';
case MATOMO_SITE_ID = 'MATOMO_SITE_ID';
@@ -74,10 +72,6 @@ enum EnvVars: string
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
case TIMEZONE = 'TIMEZONE';
case MULTI_SEGMENT_SLUGS_ENABLED = 'MULTI_SEGMENT_SLUGS_ENABLED';
- /** @deprecated */
- case VISITS_WEBHOOKS = 'VISITS_WEBHOOKS';
- /** @deprecated */
- case NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS = 'NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS';
public function loadFromEnv(mixed $default = null): mixed
{
diff --git a/module/Core/src/EventDispatcher/Helper/EnabledListenerChecker.php b/module/Core/src/EventDispatcher/Helper/EnabledListenerChecker.php
index 269aed76..ad4c8070 100644
--- a/module/Core/src/EventDispatcher/Helper/EnabledListenerChecker.php
+++ b/module/Core/src/EventDispatcher/Helper/EnabledListenerChecker.php
@@ -8,19 +8,17 @@ use Shlinkio\Shlink\Common\Mercure\MercureOptions;
use Shlinkio\Shlink\Core\EventDispatcher;
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
use Shlinkio\Shlink\Core\Options\RabbitMqOptions;
-use Shlinkio\Shlink\Core\Options\WebhookOptions;
use Shlinkio\Shlink\EventDispatcher\Listener\EnabledListenerCheckerInterface;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2Options;
-class EnabledListenerChecker implements EnabledListenerCheckerInterface
+readonly class EnabledListenerChecker implements EnabledListenerCheckerInterface
{
public function __construct(
- private readonly RabbitMqOptions $rabbitMqOptions,
- private readonly bool $redisPubSubEnabled,
- private readonly MercureOptions $mercureOptions,
- private readonly WebhookOptions $webhookOptions,
- private readonly GeoLite2Options $geoLiteOptions,
- private readonly MatomoOptions $matomoOptions,
+ private RabbitMqOptions $rabbitMqOptions,
+ private bool $redisPubSubEnabled,
+ private MercureOptions $mercureOptions,
+ private GeoLite2Options $geoLiteOptions,
+ private MatomoOptions $matomoOptions,
) {
}
@@ -38,7 +36,6 @@ class EnabledListenerChecker implements EnabledListenerCheckerInterface
EventDispatcher\Mercure\NotifyVisitToMercure::class,
EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => $this->mercureOptions->isEnabled(),
EventDispatcher\Matomo\SendVisitToMatomo::class => $this->matomoOptions->enabled,
- EventDispatcher\NotifyVisitToWebHooks::class => $this->webhookOptions->hasWebhooks(),
EventDispatcher\UpdateGeoLiteDb::class => $this->geoLiteOptions->hasLicenseKey(),
default => false, // Any unknown async listener should not be enabled by default
};
diff --git a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php b/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php
deleted file mode 100644
index 028c3c13..00000000
--- a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php
+++ /dev/null
@@ -1,101 +0,0 @@
-webhookOptions->hasWebhooks()) {
- return;
- }
-
- $visitId = $shortUrlLocated->visitId;
-
- /** @var Visit|null $visit */
- $visit = $this->em->find(Visit::class, $visitId);
- if ($visit === null) {
- $this->logger->warning('Tried to notify webhooks for visit with id "{visitId}", but it does not exist.', [
- 'visitId' => $visitId,
- ]);
- return;
- }
-
- if ($visit->isOrphan() && ! $this->webhookOptions->notifyOrphanVisits()) {
- return;
- }
-
- $requestOptions = $this->buildRequestOptions($visit);
- $requestPromises = $this->performRequests($requestOptions, $visitId);
-
- // Wait for all the promises to finish, ignoring rejections, as in those cases we only want to log the error.
- Utils::settle($requestPromises)->wait();
- }
-
- private function buildRequestOptions(Visit $visit): array
- {
- $payload = ['visit' => $visit->jsonSerialize()];
- $shortUrl = $visit->getShortUrl();
- if ($shortUrl !== null) {
- $payload['shortUrl'] = $this->transformer->transform($shortUrl);
- }
-
- return [
- RequestOptions::TIMEOUT => 10,
- RequestOptions::JSON => $payload,
- RequestOptions::HEADERS => ['User-Agent' => $this->appOptions->__toString()],
- ];
- }
-
- /**
- * @param Promise[] $requestOptions
- */
- private function performRequests(array $requestOptions, string $visitId): array
- {
- return array_map(
- fn (string $webhook): PromiseInterface => $this->httpClient
- ->requestAsync(RequestMethodInterface::METHOD_POST, $webhook, $requestOptions)
- ->otherwise(fn (Throwable $e) => $this->logWebhookFailure($webhook, $visitId, $e)),
- $this->webhookOptions->webhooks(),
- );
- }
-
- private function logWebhookFailure(string $webhook, string $visitId, Throwable $e): void
- {
- $this->logger->warning('Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}', [
- 'visitId' => $visitId,
- 'webhook' => $webhook,
- 'e' => $e,
- ]);
- }
-}
diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php
index ed5b08e0..ddc4221c 100644
--- a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php
+++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php
@@ -6,15 +6,11 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\RabbitMq;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
-use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
-use Shlinkio\Shlink\Common\UpdatePublishing\Update;
use Shlinkio\Shlink\Core\EventDispatcher\Async\AbstractNotifyVisitListener;
use Shlinkio\Shlink\Core\EventDispatcher\Async\RemoteSystem;
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
-use Shlinkio\Shlink\Core\EventDispatcher\Topic;
use Shlinkio\Shlink\Core\Options\RabbitMqOptions;
-use Shlinkio\Shlink\Core\Visit\Entity\Visit;
class NotifyVisitToRabbitMq extends AbstractNotifyVisitListener
{
@@ -23,42 +19,11 @@ class NotifyVisitToRabbitMq extends AbstractNotifyVisitListener
PublishingUpdatesGeneratorInterface $updatesGenerator,
EntityManagerInterface $em,
LoggerInterface $logger,
- private readonly DataTransformerInterface $orphanVisitTransformer,
private readonly RabbitMqOptions $options,
) {
parent::__construct($rabbitMqHelper, $updatesGenerator, $em, $logger);
}
- /**
- * @return Update[]
- */
- protected function determineUpdatesForVisit(Visit $visit): array
- {
- // Once the two deprecated cases below have been removed, make parent method private
- if (! $this->options->legacyVisitsPublishing) {
- return parent::determineUpdatesForVisit($visit);
- }
-
- // This was defined incorrectly.
- // According to the spec, both the visit and the short URL it belongs to, should be published.
- // The shape should be ['visit' => [...], 'shortUrl' => ?[...]]
- // However, this would be a breaking change, so we need a flag that determines the shape of the payload.
- return $visit->isOrphan()
- ? [
- Update::forTopicAndPayload(
- Topic::NEW_ORPHAN_VISIT->value,
- $this->orphanVisitTransformer->transform($visit),
- ),
- ]
- : [
- Update::forTopicAndPayload(Topic::NEW_VISIT->value, $visit->jsonSerialize()),
- Update::forTopicAndPayload(
- Topic::newShortUrlVisit($visit->getShortUrl()?->getShortCode()),
- $visit->jsonSerialize(),
- ),
- ];
- }
-
protected function isEnabled(): bool
{
return $this->options->enabled;
diff --git a/module/Core/src/Options/RabbitMqOptions.php b/module/Core/src/Options/RabbitMqOptions.php
index cc25f3bf..308dff2a 100644
--- a/module/Core/src/Options/RabbitMqOptions.php
+++ b/module/Core/src/Options/RabbitMqOptions.php
@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Options;
-final class RabbitMqOptions
+final readonly class RabbitMqOptions
{
public function __construct(
- public readonly bool $enabled = false,
- /** @deprecated */
- public readonly bool $legacyVisitsPublishing = false,
+ public bool $enabled = false,
) {
}
}
diff --git a/module/Core/src/Options/WebhookOptions.php b/module/Core/src/Options/WebhookOptions.php
deleted file mode 100644
index 7196fd0c..00000000
--- a/module/Core/src/Options/WebhookOptions.php
+++ /dev/null
@@ -1,41 +0,0 @@
-webhooks;
- }
-
- public function hasWebhooks(): bool
- {
- return ! empty($this->webhooks);
- }
-
- protected function setWebhooks(array $webhooks): void
- {
- $this->webhooks = $webhooks;
- }
-
- public function notifyOrphanVisits(): bool
- {
- return $this->notifyOrphanVisitsToWebhooks;
- }
-
- protected function setNotifyOrphanVisitsToWebhooks(bool $notifyOrphanVisitsToWebhooks): void
- {
- $this->notifyOrphanVisitsToWebhooks = $notifyOrphanVisitsToWebhooks;
- }
-}
diff --git a/module/Core/src/ShortUrl/Model/ShortUrlMode.php b/module/Core/src/ShortUrl/Model/ShortUrlMode.php
index d359e8cc..19886657 100644
--- a/module/Core/src/ShortUrl/Model/ShortUrlMode.php
+++ b/module/Core/src/ShortUrl/Model/ShortUrlMode.php
@@ -6,10 +6,4 @@ enum ShortUrlMode: string
{
case STRICT = 'strict';
case LOOSE = 'loose';
-
- /** @deprecated */
- public static function tryDeprecated(string $mode): ?self
- {
- return $mode === 'loosely' ? self::LOOSE : self::tryFrom($mode);
- }
}
diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php
index a6641998..d0661504 100644
--- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php
+++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php
@@ -39,9 +39,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface
$shortUrl->getVisitsCount(),
$shortUrl->nonBotVisitsCount(),
),
-
- // Deprecated
- 'visitsCount' => $shortUrl->getVisitsCount(),
];
}
diff --git a/module/Core/src/Tag/Model/OrderableField.php b/module/Core/src/Tag/Model/OrderableField.php
index b7a9509f..39092e4d 100644
--- a/module/Core/src/Tag/Model/OrderableField.php
+++ b/module/Core/src/Tag/Model/OrderableField.php
@@ -10,15 +10,13 @@ enum OrderableField: string
case SHORT_URLS_COUNT = 'shortUrlsCount';
case VISITS = 'visits';
case NON_BOT_VISITS = 'nonBotVisits';
- /** @deprecated Use VISITS instead */
- case VISITS_COUNT = 'visitsCount';
- public static function toSnakeCaseValidField(?string $field): self
+ public static function toValidField(?string $field): self
{
- $parsed = $field !== null ? self::tryFrom($field) : self::TAG;
- return match ($parsed) {
- self::VISITS_COUNT, null => self::VISITS,
- default => $parsed,
- };
+ if ($field === null) {
+ return self::TAG;
+ }
+
+ return self::tryFrom($field) ?? self::TAG;
}
}
diff --git a/module/Core/src/Tag/Model/TagInfo.php b/module/Core/src/Tag/Model/TagInfo.php
index 4c0018b2..504181ec 100644
--- a/module/Core/src/Tag/Model/TagInfo.php
+++ b/module/Core/src/Tag/Model/TagInfo.php
@@ -7,13 +7,13 @@ namespace Shlinkio\Shlink\Core\Tag\Model;
use JsonSerializable;
use Shlinkio\Shlink\Core\Visit\Model\VisitsSummary;
-final class TagInfo implements JsonSerializable
+final readonly class TagInfo implements JsonSerializable
{
- public readonly VisitsSummary $visitsSummary;
+ public VisitsSummary $visitsSummary;
public function __construct(
- public readonly string $tag,
- public readonly int $shortUrlsCount,
+ public string $tag,
+ public int $shortUrlsCount,
int $visitsCount,
?int $nonBotVisitsCount = null,
) {
@@ -36,9 +36,6 @@ final class TagInfo implements JsonSerializable
'tag' => $this->tag,
'shortUrlsCount' => $this->shortUrlsCount,
'visitsSummary' => $this->visitsSummary,
-
- // Deprecated
- 'visitsCount' => $this->visitsSummary->total,
];
}
}
diff --git a/module/Core/src/Tag/Model/TagsParams.php b/module/Core/src/Tag/Model/TagsParams.php
index 3b1d84b2..422f9da1 100644
--- a/module/Core/src/Tag/Model/TagsParams.php
+++ b/module/Core/src/Tag/Model/TagsParams.php
@@ -14,8 +14,6 @@ final class TagsParams extends AbstractInfinitePaginableListParams
private function __construct(
public readonly ?string $searchTerm,
public readonly Ordering $orderBy,
- /** @deprecated */
- public readonly bool $withStats,
?int $page,
?int $itemsPerPage,
) {
@@ -27,7 +25,6 @@ final class TagsParams extends AbstractInfinitePaginableListParams
return new self(
$query['searchTerm'] ?? null,
Ordering::fromTuple(isset($query['orderBy']) ? parseOrderBy($query['orderBy']) : [null, null]),
- ($query['withStats'] ?? null) === 'true',
isset($query['page']) ? (int) $query['page'] : null,
isset($query['itemsPerPage']) ? (int) $query['itemsPerPage'] : null,
);
diff --git a/module/Core/src/Tag/Repository/TagRepository.php b/module/Core/src/Tag/Repository/TagRepository.php
index ce8b1f76..7f07e867 100644
--- a/module/Core/src/Tag/Repository/TagRepository.php
+++ b/module/Core/src/Tag/Repository/TagRepository.php
@@ -43,7 +43,7 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
*/
public function findTagsWithInfo(?TagsListFiltering $filtering = null): array
{
- $orderField = OrderableField::toSnakeCaseValidField($filtering?->orderBy?->field);
+ $orderField = OrderableField::toValidField($filtering?->orderBy?->field);
$orderDir = $filtering?->orderBy?->direction ?? 'ASC';
$apiKey = $filtering?->apiKey;
$conn = $this->getEntityManager()->getConnection();
diff --git a/module/Core/src/Visit/Model/VisitsStats.php b/module/Core/src/Visit/Model/VisitsStats.php
index adac34eb..22f05bd4 100644
--- a/module/Core/src/Visit/Model/VisitsStats.php
+++ b/module/Core/src/Visit/Model/VisitsStats.php
@@ -6,10 +6,10 @@ namespace Shlinkio\Shlink\Core\Visit\Model;
use JsonSerializable;
-final class VisitsStats implements JsonSerializable
+final readonly class VisitsStats implements JsonSerializable
{
- private readonly VisitsSummary $nonOrphanVisitsSummary;
- private readonly VisitsSummary $orphanVisitsSummary;
+ private VisitsSummary $nonOrphanVisitsSummary;
+ private VisitsSummary $orphanVisitsSummary;
public function __construct(
int $nonOrphanVisitsTotal,
@@ -32,10 +32,6 @@ final class VisitsStats implements JsonSerializable
return [
'nonOrphanVisits' => $this->nonOrphanVisitsSummary,
'orphanVisits' => $this->orphanVisitsSummary,
-
- // Deprecated
- 'visitsCount' => $this->nonOrphanVisitsSummary->total,
- 'orphanVisitsCount' => $this->orphanVisitsSummary->total,
];
}
}
diff --git a/module/Core/test-api/Action/QrCodeTest.php b/module/Core/test-api/Action/QrCodeTest.php
index 955e6c7e..21fd5147 100644
--- a/module/Core/test-api/Action/QrCodeTest.php
+++ b/module/Core/test-api/Action/QrCodeTest.php
@@ -10,7 +10,7 @@ use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
class QrCodeTest extends ApiTestCase
{
#[Test]
- public function returnsNotFoundWhenShortUrlIsNotEnabled(): void
+ public function returnsQrCodeEvenIfShortUrlIsNotEnabled(): void
{
// The QR code successfully resolves at first
$response = $this->callShortUrl('custom/qr-code');
@@ -20,8 +20,8 @@ class QrCodeTest extends ApiTestCase
$this->callShortUrl('custom');
$this->callShortUrl('custom');
- // After 2 visits, the QR code should return a 404
- $response = $this->callShortUrl('custom/qr-code');
- self::assertEquals(404, $response->getStatusCode());
+ // After 2 visits, the short URL returns a 404, but the QR code should still work
+ self::assertEquals(404, $this->callShortUrl('custom')->getStatusCode());
+ self::assertEquals(200, $this->callShortUrl('custom/qr-code')->getStatusCode());
}
}
diff --git a/module/Core/test-db/Tag/Repository/TagRepositoryTest.php b/module/Core/test-db/Tag/Repository/TagRepositoryTest.php
index 6cccf199..77077142 100644
--- a/module/Core/test-db/Tag/Repository/TagRepositoryTest.php
+++ b/module/Core/test-db/Tag/Repository/TagRepositoryTest.php
@@ -197,13 +197,6 @@ class TagRepositoryTest extends DatabaseTestCase
['another', 0, 0, 0],
],
];
- yield 'visits count DESC ordering and limit' => [
- new TagsListFiltering(2, null, null, Ordering::fromTuple([OrderableField::VISITS_COUNT->value, 'DESC'])),
- [
- ['foo', 2, 4, 3],
- ['bar', 3, 3, 2],
- ],
- ];
yield 'api key' => [new TagsListFiltering(null, null, null, null, ApiKey::fromMeta(
ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls()),
)), [
diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php
index 9a89ff47..98e1e375 100644
--- a/module/Core/test/Action/QrCodeActionTest.php
+++ b/module/Core/test/Action/QrCodeActionTest.php
@@ -265,7 +265,7 @@ class QrCodeActionTest extends TestCase
$this->urlResolver,
new ShortUrlStringifier(['domain' => 's.test']),
new NullLogger(),
- $options ?? new QrCodeOptions(),
+ $options ?? new QrCodeOptions(enabledForDisabledShortUrls: false),
);
}
}
diff --git a/module/Core/test/EventDispatcher/Helper/EnabledListenerCheckerTest.php b/module/Core/test/EventDispatcher/Helper/EnabledListenerCheckerTest.php
index 00f78fe4..cebde437 100644
--- a/module/Core/test/EventDispatcher/Helper/EnabledListenerCheckerTest.php
+++ b/module/Core/test/EventDispatcher/Helper/EnabledListenerCheckerTest.php
@@ -12,7 +12,6 @@ use Shlinkio\Shlink\Core\EventDispatcher\Helper\EnabledListenerChecker;
use Shlinkio\Shlink\Core\EventDispatcher\Matomo\SendVisitToMatomo;
use Shlinkio\Shlink\Core\EventDispatcher\Mercure\NotifyNewShortUrlToMercure;
use Shlinkio\Shlink\Core\EventDispatcher\Mercure\NotifyVisitToMercure;
-use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToWebHooks;
use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq;
use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyVisitToRabbitMq;
use Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub\NotifyNewShortUrlToRedis;
@@ -20,7 +19,6 @@ use Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub\NotifyVisitToRedis;
use Shlinkio\Shlink\Core\EventDispatcher\UpdateGeoLiteDb;
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
use Shlinkio\Shlink\Core\Options\RabbitMqOptions;
-use Shlinkio\Shlink\Core\Options\WebhookOptions;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2Options;
class EnabledListenerCheckerTest extends TestCase
@@ -41,7 +39,6 @@ class EnabledListenerCheckerTest extends TestCase
[NotifyVisitToMercure::class],
[NotifyNewShortUrlToMercure::class],
[SendVisitToMatomo::class],
- [NotifyVisitToWebHooks::class],
[UpdateGeoLiteDb::class],
];
}
@@ -68,7 +65,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyNewShortUrlToRedis::class => false,
NotifyVisitToMercure::class => false,
NotifyNewShortUrlToMercure::class => false,
- NotifyVisitToWebHooks::class => false,
UpdateGeoLiteDb::class => false,
'unknown' => false,
]];
@@ -79,7 +75,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyNewShortUrlToRedis::class => true,
NotifyVisitToMercure::class => false,
NotifyNewShortUrlToMercure::class => false,
- NotifyVisitToWebHooks::class => false,
UpdateGeoLiteDb::class => false,
'unknown' => false,
]];
@@ -90,18 +85,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyNewShortUrlToRedis::class => false,
NotifyVisitToMercure::class => true,
NotifyNewShortUrlToMercure::class => true,
- NotifyVisitToWebHooks::class => false,
- UpdateGeoLiteDb::class => false,
- 'unknown' => false,
- ]];
- yield 'Webhooks' => [self::checker(webhooksEnabled: true), [
- NotifyVisitToRabbitMq::class => false,
- NotifyNewShortUrlToRabbitMq::class => false,
- NotifyVisitToRedis::class => false,
- NotifyNewShortUrlToRedis::class => false,
- NotifyVisitToMercure::class => false,
- NotifyNewShortUrlToMercure::class => false,
- NotifyVisitToWebHooks::class => true,
UpdateGeoLiteDb::class => false,
'unknown' => false,
]];
@@ -112,7 +95,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyNewShortUrlToRedis::class => false,
NotifyVisitToMercure::class => false,
NotifyNewShortUrlToMercure::class => false,
- NotifyVisitToWebHooks::class => false,
UpdateGeoLiteDb::class => true,
'unknown' => false,
]];
@@ -124,7 +106,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyVisitToMercure::class => false,
NotifyNewShortUrlToMercure::class => false,
SendVisitToMatomo::class => true,
- NotifyVisitToWebHooks::class => false,
UpdateGeoLiteDb::class => false,
'unknown' => false,
]];
@@ -135,7 +116,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyNewShortUrlToRedis::class => false,
NotifyVisitToMercure::class => false,
NotifyNewShortUrlToMercure::class => false,
- NotifyVisitToWebHooks::class => false,
UpdateGeoLiteDb::class => false,
'unknown' => false,
]];
@@ -143,7 +123,6 @@ class EnabledListenerCheckerTest extends TestCase
rabbitMqEnabled: true,
redisPubSubEnabled: true,
mercureEnabled: true,
- webhooksEnabled: true,
geoLiteEnabled: true,
matomoEnabled: true,
), [
@@ -154,7 +133,6 @@ class EnabledListenerCheckerTest extends TestCase
NotifyVisitToMercure::class => true,
NotifyNewShortUrlToMercure::class => true,
SendVisitToMatomo::class => true,
- NotifyVisitToWebHooks::class => true,
UpdateGeoLiteDb::class => true,
'unknown' => false,
]];
@@ -164,7 +142,6 @@ class EnabledListenerCheckerTest extends TestCase
bool $rabbitMqEnabled = false,
bool $redisPubSubEnabled = false,
bool $mercureEnabled = false,
- bool $webhooksEnabled = false,
bool $geoLiteEnabled = false,
bool $matomoEnabled = false,
): EnabledListenerChecker {
@@ -172,7 +149,6 @@ class EnabledListenerCheckerTest extends TestCase
new RabbitMqOptions(enabled: $rabbitMqEnabled),
$redisPubSubEnabled,
new MercureOptions(publicHubUrl: $mercureEnabled ? 'the-url' : null),
- new WebhookOptions(['webhooks' => $webhooksEnabled ? ['foo', 'bar'] : []]),
new GeoLite2Options(licenseKey: $geoLiteEnabled ? 'the-key' : null),
new MatomoOptions(enabled: $matomoEnabled),
);
diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php
deleted file mode 100644
index 8b9c10ac..00000000
--- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php
+++ /dev/null
@@ -1,144 +0,0 @@
-httpClient = $this->createMock(ClientInterface::class);
- $this->em = $this->createMock(EntityManagerInterface::class);
- $this->logger = $this->createMock(LoggerInterface::class);
- }
-
- #[Test]
- public function emptyWebhooksMakeNoFurtherActions(): void
- {
- $this->em->expects($this->never())->method('find');
-
- $this->createListener([])(new VisitLocated('1'));
- }
-
- #[Test]
- public function invalidVisitDoesNotPerformAnyRequest(): void
- {
- $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn(null);
- $this->httpClient->expects($this->never())->method('requestAsync');
- $this->logger->expects($this->once())->method('warning')->with(
- 'Tried to notify webhooks for visit with id "{visitId}", but it does not exist.',
- ['visitId' => '1'],
- );
-
- $this->createListener(['foo', 'bar'])(new VisitLocated('1'));
- }
-
- #[Test]
- public function orphanVisitDoesNotPerformAnyRequestWhenDisabled(): void
- {
- $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn(
- Visit::forBasePath(Visitor::emptyInstance()),
- );
- $this->httpClient->expects($this->never())->method('requestAsync');
- $this->logger->expects($this->never())->method('warning');
-
- $this->createListener(['foo', 'bar'], false)(new VisitLocated('1'));
- }
-
- #[Test, DataProvider('provideVisits')]
- public function expectedRequestsArePerformedToWebhooks(Visit $visit, array $expectedResponseKeys): void
- {
- $webhooks = ['foo', 'invalid', 'bar', 'baz'];
- $invalidWebhooks = ['invalid', 'baz'];
-
- $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn($visit);
- $this->httpClient->expects($this->exactly(count($webhooks)))->method('requestAsync')->with(
- RequestMethodInterface::METHOD_POST,
- $this->istype('string'),
- $this->callback(function (array $requestOptions) use ($expectedResponseKeys) {
- Assert::assertArrayHasKey(RequestOptions::HEADERS, $requestOptions);
- Assert::assertArrayHasKey(RequestOptions::JSON, $requestOptions);
- Assert::assertArrayHasKey(RequestOptions::TIMEOUT, $requestOptions);
- Assert::assertEquals(10, $requestOptions[RequestOptions::TIMEOUT]);
- Assert::assertEquals(['User-Agent' => 'Shlink:v1.2.3'], $requestOptions[RequestOptions::HEADERS]);
-
- $json = $requestOptions[RequestOptions::JSON];
- Assert::assertCount(count($expectedResponseKeys), $json);
- foreach ($expectedResponseKeys as $key) {
- Assert::assertArrayHasKey($key, $json);
- }
-
- return true;
- }),
- )->willReturnCallback(function ($_, $webhook) use ($invalidWebhooks) {
- $shouldReject = contains($webhook, $invalidWebhooks);
- return $shouldReject ? new RejectedPromise(new Exception('')) : new FulfilledPromise('');
- });
- $this->logger->expects($this->exactly(count($invalidWebhooks)))->method('warning')->with(
- 'Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}',
- $this->callback(function (array $extra): bool {
- Assert::assertArrayHasKey('webhook', $extra);
- Assert::assertArrayHasKey('visitId', $extra);
- Assert::assertArrayHasKey('e', $extra);
-
- return true;
- }),
- );
-
- $this->createListener($webhooks)(new VisitLocated('1'));
- }
-
- public static function provideVisits(): iterable
- {
- yield 'regular visit' => [
- Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
- ['shortUrl', 'visit'],
- ];
- yield 'orphan visit' => [Visit::forBasePath(Visitor::emptyInstance()), ['visit']];
- }
-
- private function createListener(array $webhooks, bool $notifyOrphanVisits = true): NotifyVisitToWebHooks
- {
- return new NotifyVisitToWebHooks(
- $this->httpClient,
- $this->em,
- $this->logger,
- new WebhookOptions(
- ['webhooks' => $webhooks, 'notify_orphan_visits_to_webhooks' => $notifyOrphanVisits],
- ),
- new ShortUrlDataTransformer(new ShortUrlStringifier([])),
- new AppOptions('Shlink', '1.2.3'),
- );
- }
-}
diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php
index 9d28f2cd..c5ebb1a8 100644
--- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php
+++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php
@@ -53,7 +53,6 @@ class PublishingUpdatesGeneratorTest extends TestCase
'longUrl' => 'https://longUrl',
'deviceLongUrls' => $shortUrl->deviceLongUrls(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
- 'visitsCount' => 0,
'tags' => [],
'meta' => [
'validSince' => null,
@@ -128,7 +127,6 @@ class PublishingUpdatesGeneratorTest extends TestCase
'longUrl' => 'https://longUrl',
'deviceLongUrls' => $shortUrl->deviceLongUrls(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
- 'visitsCount' => 0,
'tags' => [],
'meta' => [
'validSince' => null,
diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php
index e722bf25..7386169f 100644
--- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php
+++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php
@@ -7,7 +7,6 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\RabbitMq;
use Doctrine\ORM\EntityManagerInterface;
use DomainException;
use Exception;
-use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
@@ -24,7 +23,6 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
-use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer;
use Throwable;
use function array_walk;
@@ -132,9 +130,8 @@ class NotifyVisitToRabbitMqTest extends TestCase
yield [new DomainException('DomainException Error')];
}
- #[Test, DataProvider('provideLegacyPayloads')]
+ #[Test, DataProvider('providePayloads')]
public function expectedPayloadIsPublishedDependingOnConfig(
- bool $legacy,
Visit $visit,
callable $setup,
callable $expect,
@@ -144,44 +141,12 @@ class NotifyVisitToRabbitMqTest extends TestCase
$setup($this->updatesGenerator);
$expect($this->helper, $this->updatesGenerator);
- ($this->listener(new RabbitMqOptions(true, $legacy)))(new VisitLocated($visitId));
+ ($this->listener())(new VisitLocated($visitId));
}
- public static function provideLegacyPayloads(): iterable
+ public static function providePayloads(): iterable
{
- yield 'legacy non-orphan visit' => [
- true,
- $visit = Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
- static fn () => null,
- function (MockObject & PublishingHelperInterface $helper) use ($visit): void {
- $helper->method('publishUpdate')->with(self::callback(function (Update $update) use ($visit): bool {
- $payload = $update->payload;
- Assert::assertEquals($payload, $visit->jsonSerialize());
- Assert::assertArrayNotHasKey('visitedUrl', $payload);
- Assert::assertArrayNotHasKey('type', $payload);
- Assert::assertArrayNotHasKey('visit', $payload);
- Assert::assertArrayNotHasKey('shortUrl', $payload);
-
- return true;
- }));
- },
- ];
- yield 'legacy orphan visit' => [
- true,
- Visit::forBasePath(Visitor::emptyInstance()),
- static fn () => null,
- function (MockObject & PublishingHelperInterface $helper): void {
- $helper->method('publishUpdate')->with(self::callback(function (Update $update): bool {
- $payload = $update->payload;
- Assert::assertArrayHasKey('visitedUrl', $payload);
- Assert::assertArrayHasKey('type', $payload);
-
- return true;
- }));
- },
- ];
- yield 'non-legacy non-orphan visit' => [
- false,
+ yield 'non-orphan visit' => [
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void {
$update = Update::forTopicAndPayload('', []);
@@ -195,8 +160,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
$helper->expects(self::exactly(2))->method('publishUpdate')->with(self::isInstanceOf(Update::class));
},
];
- yield 'non-legacy orphan visit' => [
- false,
+ yield 'orphan visit' => [
Visit::forBasePath(Visitor::emptyInstance()),
function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void {
$update = Update::forTopicAndPayload('', []);
@@ -217,8 +181,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
$this->updatesGenerator,
$this->em,
$this->logger,
- new OrphanVisitDataTransformer(),
- $options ?? new RabbitMqOptions(enabled: true, legacyVisitsPublishing: false),
+ $options ?? new RabbitMqOptions(enabled: true),
);
}
}
diff --git a/module/Core/test/ShortUrl/Model/ShortUrlModeTest.php b/module/Core/test/ShortUrl/Model/ShortUrlModeTest.php
deleted file mode 100644
index f2ca7cce..00000000
--- a/module/Core/test/ShortUrl/Model/ShortUrlModeTest.php
+++ /dev/null
@@ -1,28 +0,0 @@
- ['invalid', null];
- yield 'foo' => ['foo', null];
- yield 'loose' => ['loose', ShortUrlMode::LOOSE];
- yield 'loosely' => ['loosely', ShortUrlMode::LOOSE];
- yield 'strict' => ['strict', ShortUrlMode::STRICT];
- }
-}
diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php
index acca571d..67343e27 100644
--- a/module/Rest/config/dependencies.config.php
+++ b/module/Rest/config/dependencies.config.php
@@ -55,7 +55,6 @@ return [
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => ConfigAbstractFactory::class,
Middleware\ShortUrl\OverrideDomainMiddleware::class => ConfigAbstractFactory::class,
Middleware\Mercure\NotConfiguredMercureErrorHandler::class => ConfigAbstractFactory::class,
- Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class => InvokableFactory::class,
],
],
diff --git a/module/Rest/src/Action/Tag/ListTagsAction.php b/module/Rest/src/Action/Tag/ListTagsAction.php
index 9674d5bc..13898584 100644
--- a/module/Rest/src/Action/Tag/ListTagsAction.php
+++ b/module/Rest/src/Action/Tag/ListTagsAction.php
@@ -8,14 +8,11 @@ use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
-use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Tag\Model\TagsParams;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
-use function array_map;
-
class ListTagsAction extends AbstractRestAction
{
use PagerfantaUtilsTrait;
@@ -32,17 +29,8 @@ class ListTagsAction extends AbstractRestAction
$params = TagsParams::fromRawData($request->getQueryParams());
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
- if (! $params->withStats) {
- return new JsonResponse([
- 'tags' => $this->serializePaginator($this->tagService->listTags($params, $apiKey)),
- ]);
- }
-
- // This part is deprecated. To get tags with stats, the /tags/stats endpoint should be used instead
- $tagsInfo = $this->tagService->tagsInfo($params, $apiKey);
- $rawTags = $this->serializePaginator($tagsInfo, dataProp: 'stats');
- $rawTags['data'] = array_map(static fn (TagInfo $info) => $info->tag, [...$tagsInfo]);
-
- return new JsonResponse(['tags' => $rawTags]);
+ return new JsonResponse([
+ 'tags' => $this->serializePaginator($this->tagService->listTags($params, $apiKey)),
+ ]);
}
}
diff --git a/module/Rest/src/ApiKey/Role.php b/module/Rest/src/ApiKey/Role.php
index dd2d8ae7..4f3685db 100644
--- a/module/Rest/src/ApiKey/Role.php
+++ b/module/Rest/src/ApiKey/Role.php
@@ -40,8 +40,8 @@ enum Role: string
public static function toSpec(ApiKeyRole $role, ?string $context = null): Specification
{
- return match ($role->role()) {
- self::AUTHORED_SHORT_URLS => new BelongsToApiKey($role->apiKey(), $context),
+ return match ($role->role) {
+ self::AUTHORED_SHORT_URLS => new BelongsToApiKey($role->apiKey, $context),
self::DOMAIN_SPECIFIC => new BelongsToDomain(self::domainIdFromMeta($role->meta()), $context),
default => Spec::andX(),
};
@@ -49,8 +49,8 @@ enum Role: string
public static function toInlinedSpec(ApiKeyRole $role): Specification
{
- return match ($role->role()) {
- self::AUTHORED_SHORT_URLS => Spec::andX(new BelongsToApiKeyInlined($role->apiKey())),
+ return match ($role->role) {
+ self::AUTHORED_SHORT_URLS => Spec::andX(new BelongsToApiKeyInlined($role->apiKey)),
self::DOMAIN_SPECIFIC => Spec::andX(new BelongsToDomainInlined(self::domainIdFromMeta($role->meta()))),
default => Spec::andX(),
};
diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php
index dae30de0..9ad3fcf4 100644
--- a/module/Rest/src/Entity/ApiKey.php
+++ b/module/Rest/src/Entity/ApiKey.php
@@ -156,7 +156,7 @@ class ApiKey extends AbstractEntity
*/
public function mapRoles(callable $fun): array
{
- return $this->roles->map(fn (ApiKeyRole $role) => $fun($role->role(), $role->meta()))->getValues();
+ return $this->roles->map(fn (ApiKeyRole $role) => $fun($role->role, $role->meta()))->getValues();
}
public function registerRole(RoleDefinition $roleDefinition): void
diff --git a/module/Rest/src/Entity/ApiKeyRole.php b/module/Rest/src/Entity/ApiKeyRole.php
index 6fadb839..5053b74d 100644
--- a/module/Rest/src/Entity/ApiKeyRole.php
+++ b/module/Rest/src/Entity/ApiKeyRole.php
@@ -13,22 +13,6 @@ class ApiKeyRole extends AbstractEntity
{
}
- /**
- * @deprecated Use property access directly
- */
- public function role(): Role
- {
- return $this->role;
- }
-
- /**
- * @deprecated Use property access directly
- */
- public function apiKey(): ApiKey
- {
- return $this->apiKey;
- }
-
public function meta(): array
{
return $this->meta;
diff --git a/module/Rest/src/Exception/BackwardsCompatibleProblemDetailsException.php b/module/Rest/src/Exception/BackwardsCompatibleProblemDetailsException.php
deleted file mode 100644
index 8cfb918c..00000000
--- a/module/Rest/src/Exception/BackwardsCompatibleProblemDetailsException.php
+++ /dev/null
@@ -1,99 +0,0 @@
-getMessage(), $e->getCode(), $e);
- }
-
- public static function fromProblemDetails(ProblemDetailsExceptionInterface $e): self
- {
- return new self($e);
- }
-
- public function getStatus(): int
- {
- return $this->e->getStatus();
- }
-
- public function getType(): string
- {
- return $this->remapType($this->e->getType());
- }
-
- public function getTitle(): string
- {
- return $this->e->getTitle();
- }
-
- public function getDetail(): string
- {
- return $this->e->getDetail();
- }
-
- public function getAdditionalData(): array
- {
- return $this->e->getAdditionalData();
- }
-
- public function toArray(): array
- {
- return $this->remapTypeInArray($this->e->toArray());
- }
-
- public function jsonSerialize(): array
- {
- return $this->remapTypeInArray($this->e->jsonSerialize());
- }
-
- private function remapTypeInArray(array $wrappedArray): array
- {
- if (! isset($wrappedArray['type'])) {
- return $wrappedArray;
- }
-
- return [...$wrappedArray, 'type' => $this->remapType($wrappedArray['type'])];
- }
-
- private function remapType(string $wrappedType): string
- {
- $segments = explode('/', $wrappedType);
- $lastSegment = end($segments);
-
- return match ($lastSegment) {
- ValidationException::ERROR_CODE => 'INVALID_ARGUMENT',
- DeleteShortUrlException::ERROR_CODE => 'INVALID_SHORT_URL_DELETION',
- DomainNotFoundException::ERROR_CODE => 'DOMAIN_NOT_FOUND',
- ForbiddenTagOperationException::ERROR_CODE => 'FORBIDDEN_OPERATION',
- InvalidUrlException::ERROR_CODE => 'INVALID_URL',
- NonUniqueSlugException::ERROR_CODE => 'INVALID_SLUG',
- ShortUrlNotFoundException::ERROR_CODE => 'INVALID_SHORTCODE',
- TagConflictException::ERROR_CODE => 'TAG_CONFLICT',
- TagNotFoundException::ERROR_CODE => 'TAG_NOT_FOUND',
- MercureException::ERROR_CODE => 'MERCURE_NOT_CONFIGURED',
- MissingAuthenticationException::ERROR_CODE => 'INVALID_AUTHORIZATION',
- VerifyAuthenticationException::ERROR_CODE => 'INVALID_API_KEY',
- default => $wrappedType,
- };
- }
-}
diff --git a/module/Rest/src/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandler.php b/module/Rest/src/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandler.php
deleted file mode 100644
index c099ad70..00000000
--- a/module/Rest/src/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandler.php
+++ /dev/null
@@ -1,30 +0,0 @@
-handle($request);
- } catch (ProblemDetailsExceptionInterface $e) {
- $version = $request->getAttribute('version') ?? '2';
- throw version_compare($version, '3', '>=')
- ? $e
- : BackwardsCompatibleProblemDetailsException::fromProblemDetails($e);
- }
- }
-}
diff --git a/module/Rest/test-api/Action/CreateShortUrlTest.php b/module/Rest/test-api/Action/CreateShortUrlTest.php
index 01592129..efd70666 100644
--- a/module/Rest/test-api/Action/CreateShortUrlTest.php
+++ b/module/Rest/test-api/Action/CreateShortUrlTest.php
@@ -19,7 +19,7 @@ class CreateShortUrlTest extends ApiTestCase
#[Test]
public function createsNewShortUrlWhenOnlyLongUrlIsProvided(): void
{
- $expectedKeys = ['shortCode', 'shortUrl', 'longUrl', 'dateCreated', 'visitsCount', 'tags'];
+ $expectedKeys = ['shortCode', 'shortUrl', 'longUrl', 'dateCreated', 'tags'];
[$statusCode, $payload] = $this->createShortUrl();
self::assertEquals(self::STATUS_OK, $statusCode);
@@ -48,7 +48,7 @@ class CreateShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
self::assertEquals($detail, $payload['detail']);
- self::assertEquals('INVALID_SLUG', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/non-unique-slug', $payload['type']);
self::assertEquals('Invalid custom slug', $payload['title']);
self::assertEquals($slug, $payload['customSlug']);
@@ -70,8 +70,8 @@ class CreateShortUrlTest extends ApiTestCase
public static function provideDuplicatedSlugApiVersions(): iterable
{
- yield ['1', 'INVALID_SLUG'];
- yield ['2', 'INVALID_SLUG'];
+ yield ['1', 'https://shlink.io/api/error/non-unique-slug'];
+ yield ['2', 'https://shlink.io/api/error/non-unique-slug'];
yield ['3', 'https://shlink.io/api/error/non-unique-slug'];
}
@@ -241,7 +241,7 @@ class CreateShortUrlTest extends ApiTestCase
public static function provideInvalidUrls(): iterable
{
- yield 'API version 2' => ['https://this-has-to-be-invalid.com', '2', 'INVALID_URL'];
+ yield 'API version 2' => ['https://this-has-to-be-invalid.com', '2', 'https://shlink.io/api/error/invalid-url'];
yield 'API version 3' => ['https://this-has-to-be-invalid.com', '3', 'https://shlink.io/api/error/invalid-url'];
}
@@ -264,18 +264,18 @@ class CreateShortUrlTest extends ApiTestCase
public static function provideInvalidArgumentApiVersions(): iterable
{
- yield 'missing long url v2' => [[], '2', 'INVALID_ARGUMENT'];
+ yield 'missing long url v2' => [[], '2', 'https://shlink.io/api/error/invalid-data'];
yield 'missing long url v3' => [[], '3', 'https://shlink.io/api/error/invalid-data'];
- yield 'empty long url v2' => [['longUrl' => null], '2', 'INVALID_ARGUMENT'];
+ yield 'empty long url v2' => [['longUrl' => null], '2', 'https://shlink.io/api/error/invalid-data'];
yield 'empty long url v3' => [['longUrl' => ' '], '3', 'https://shlink.io/api/error/invalid-data'];
- yield 'missing url schema v2' => [['longUrl' => 'foo.com'], '2', 'INVALID_ARGUMENT'];
+ yield 'missing url schema v2' => [['longUrl' => 'foo.com'], '2', 'https://shlink.io/api/error/invalid-data'];
yield 'missing url schema v3' => [['longUrl' => 'foo.com'], '3', 'https://shlink.io/api/error/invalid-data'];
yield 'empty device long url v2' => [[
'longUrl' => 'foo',
'deviceLongUrls' => [
'android' => null,
],
- ], '2', 'INVALID_ARGUMENT'];
+ ], '2', 'https://shlink.io/api/error/invalid-data'];
yield 'empty device long url v3' => [[
'longUrl' => 'foo',
'deviceLongUrls' => [
diff --git a/module/Rest/test-api/Action/DeleteShortUrlTest.php b/module/Rest/test-api/Action/DeleteShortUrlTest.php
index 7bd3dfea..06848c48 100644
--- a/module/Rest/test-api/Action/DeleteShortUrlTest.php
+++ b/module/Rest/test-api/Action/DeleteShortUrlTest.php
@@ -31,7 +31,7 @@ class DeleteShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('INVALID_SHORTCODE', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/short-url-not-found', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Short URL not found', $payload['title']);
self::assertEquals($shortCode, $payload['shortCode']);
@@ -52,8 +52,8 @@ class DeleteShortUrlTest extends ApiTestCase
public static function provideApiVersions(): iterable
{
- yield ['1', 'INVALID_SHORTCODE'];
- yield ['2', 'INVALID_SHORTCODE'];
+ yield ['1', 'https://shlink.io/api/error/short-url-not-found'];
+ yield ['2', 'https://shlink.io/api/error/short-url-not-found'];
yield ['3', 'https://shlink.io/api/error/short-url-not-found'];
}
diff --git a/module/Rest/test-api/Action/DeleteTagsTest.php b/module/Rest/test-api/Action/DeleteTagsTest.php
index b04fbaf5..a269d2db 100644
--- a/module/Rest/test-api/Action/DeleteTagsTest.php
+++ b/module/Rest/test-api/Action/DeleteTagsTest.php
@@ -30,8 +30,8 @@ class DeleteTagsTest extends ApiTestCase
public static function provideNonAdminApiKeys(): iterable
{
- yield 'author' => ['author_api_key', '2', 'FORBIDDEN_OPERATION'];
- yield 'domain' => ['domain_api_key', '2', 'FORBIDDEN_OPERATION'];
+ yield 'author' => ['author_api_key', '2', 'https://shlink.io/api/error/forbidden-tag-operation'];
+ yield 'domain' => ['domain_api_key', '2', 'https://shlink.io/api/error/forbidden-tag-operation'];
yield 'version 3' => ['domain_api_key', '3', 'https://shlink.io/api/error/forbidden-tag-operation'];
}
}
diff --git a/module/Rest/test-api/Action/DomainRedirectsTest.php b/module/Rest/test-api/Action/DomainRedirectsTest.php
index bc78d035..d97092d6 100644
--- a/module/Rest/test-api/Action/DomainRedirectsTest.php
+++ b/module/Rest/test-api/Action/DomainRedirectsTest.php
@@ -21,7 +21,7 @@ class DomainRedirectsTest extends ApiTestCase
self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
- self::assertEquals('INVALID_ARGUMENT', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/invalid-data', $payload['type']);
self::assertEquals('Provided data is not valid', $payload['detail']);
self::assertEquals('Invalid data', $payload['title']);
}
diff --git a/module/Rest/test-api/Action/DomainVisitsTest.php b/module/Rest/test-api/Action/DomainVisitsTest.php
index 2c1d1d2e..3a06257b 100644
--- a/module/Rest/test-api/Action/DomainVisitsTest.php
+++ b/module/Rest/test-api/Action/DomainVisitsTest.php
@@ -49,7 +49,7 @@ class DomainVisitsTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('DOMAIN_NOT_FOUND', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/domain-not-found', $payload['type']);
self::assertEquals(sprintf('Domain with authority "%s" could not be found', $domain), $payload['detail']);
self::assertEquals('Domain not found', $payload['title']);
self::assertEquals($domain, $payload['authority']);
@@ -73,8 +73,8 @@ class DomainVisitsTest extends ApiTestCase
public static function provideApiVersions(): iterable
{
- yield ['1', 'DOMAIN_NOT_FOUND'];
- yield ['2', 'DOMAIN_NOT_FOUND'];
+ yield ['1', 'https://shlink.io/api/error/domain-not-found'];
+ yield ['2', 'https://shlink.io/api/error/domain-not-found'];
yield ['3', 'https://shlink.io/api/error/domain-not-found'];
}
}
diff --git a/module/Rest/test-api/Action/EditShortUrlTest.php b/module/Rest/test-api/Action/EditShortUrlTest.php
index a55fb066..89055adb 100644
--- a/module/Rest/test-api/Action/EditShortUrlTest.php
+++ b/module/Rest/test-api/Action/EditShortUrlTest.php
@@ -96,7 +96,7 @@ class EditShortUrlTest extends ApiTestCase
public static function provideLongUrls(): iterable
{
yield 'valid URL' => ['https://shlink.io', self::STATUS_OK, null];
- yield 'invalid URL' => ['http://foo', self::STATUS_BAD_REQUEST, 'INVALID_URL'];
+ yield 'invalid URL' => ['http://foo', self::STATUS_BAD_REQUEST, 'https://shlink.io/api/error/invalid-url'];
}
#[Test, DataProviderExternal(ApiTestDataProviders::class, 'invalidUrlsProvider')]
@@ -112,7 +112,7 @@ class EditShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('INVALID_SHORTCODE', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/short-url-not-found', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Short URL not found', $payload['title']);
self::assertEquals($shortCode, $payload['shortCode']);
@@ -131,7 +131,7 @@ class EditShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
- self::assertEquals('INVALID_ARGUMENT', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/invalid-data', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Invalid data', $payload['title']);
}
diff --git a/module/Rest/test-api/Action/GlobalVisitsTest.php b/module/Rest/test-api/Action/GlobalVisitsTest.php
index 657f16a6..30c880d5 100644
--- a/module/Rest/test-api/Action/GlobalVisitsTest.php
+++ b/module/Rest/test-api/Action/GlobalVisitsTest.php
@@ -17,10 +17,8 @@ class GlobalVisitsTest extends ApiTestCase
$payload = $this->getJsonResponsePayload($resp);
self::assertArrayHasKey('visits', $payload);
- self::assertArrayHasKey('visitsCount', $payload['visits']);
- self::assertArrayHasKey('orphanVisitsCount', $payload['visits']);
- self::assertEquals($expectedVisits, $payload['visits']['visitsCount']);
- self::assertEquals($expectedOrphanVisits, $payload['visits']['orphanVisitsCount']);
+ self::assertEquals($expectedVisits, $payload['visits']['nonOrphanVisits']['total']);
+ self::assertEquals($expectedOrphanVisits, $payload['visits']['orphanVisits']['total']);
}
public static function provideApiKeys(): iterable
diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php
index bb6296f7..3591ea60 100644
--- a/module/Rest/test-api/Action/ListShortUrlsTest.php
+++ b/module/Rest/test-api/Action/ListShortUrlsTest.php
@@ -20,7 +20,6 @@ class ListShortUrlsTest extends ApiTestCase
'shortUrl' => 'http://s.test/abc123',
'longUrl' => 'https://shlink.io',
'dateCreated' => '2018-05-01T00:00:00+00:00',
- 'visitsCount' => 3,
'visitsSummary' => [
'total' => 3,
'nonBots' => 3,
@@ -42,7 +41,6 @@ class ListShortUrlsTest extends ApiTestCase
'shortUrl' => 'http://s.test/ghi789',
'longUrl' => 'https://shlink.io/documentation/',
'dateCreated' => '2018-05-01T00:00:00+00:00',
- 'visitsCount' => 2,
'visitsSummary' => [
'total' => 2,
'nonBots' => 2,
@@ -64,7 +62,6 @@ class ListShortUrlsTest extends ApiTestCase
'shortUrl' => 'http://some-domain.com/custom-with-domain',
'longUrl' => 'https://google.com',
'dateCreated' => '2018-10-20T00:00:00+00:00',
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
@@ -88,7 +85,6 @@ class ListShortUrlsTest extends ApiTestCase
'https://blog.alejandrocelaya.com/2017/12/09'
. '/acmailer-7-0-the-most-important-release-in-a-long-time/',
'dateCreated' => '2019-01-01T00:00:10+00:00',
- 'visitsCount' => 2,
'visitsSummary' => [
'total' => 2,
'nonBots' => 1,
@@ -110,7 +106,6 @@ class ListShortUrlsTest extends ApiTestCase
'shortUrl' => 'http://s.test/custom',
'longUrl' => 'https://shlink.io',
'dateCreated' => '2019-01-01T00:00:20+00:00',
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
@@ -134,7 +129,6 @@ class ListShortUrlsTest extends ApiTestCase
'https://blog.alejandrocelaya.com/2019/04/27'
. '/considerations-to-properly-use-open-source-software-projects/',
'dateCreated' => '2019-01-01T00:00:30+00:00',
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
@@ -310,7 +304,7 @@ class ListShortUrlsTest extends ApiTestCase
self::assertEquals([
'invalidElements' => $expectedInvalidElements,
'title' => 'Invalid data',
- 'type' => 'INVALID_ARGUMENT',
+ 'type' => 'https://shlink.io/api/error/invalid-data',
'status' => 400,
'detail' => 'Provided data is not valid',
], $respPayload);
diff --git a/module/Rest/test-api/Action/RenameTagTest.php b/module/Rest/test-api/Action/RenameTagTest.php
index e401da1d..35a3e1b2 100644
--- a/module/Rest/test-api/Action/RenameTagTest.php
+++ b/module/Rest/test-api/Action/RenameTagTest.php
@@ -24,7 +24,7 @@ class RenameTagTest extends ApiTestCase
self::assertEquals(self::STATUS_FORBIDDEN, $resp->getStatusCode());
self::assertEquals(self::STATUS_FORBIDDEN, $payload['status']);
- self::assertEquals('FORBIDDEN_OPERATION', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/forbidden-tag-operation', $payload['type']);
self::assertEquals('You are not allowed to rename tags', $payload['detail']);
self::assertEquals('Forbidden tag operation', $payload['title']);
}
diff --git a/module/Rest/test-api/Action/ResolveShortUrlTest.php b/module/Rest/test-api/Action/ResolveShortUrlTest.php
index c10abc74..0c0ce5ec 100644
--- a/module/Rest/test-api/Action/ResolveShortUrlTest.php
+++ b/module/Rest/test-api/Action/ResolveShortUrlTest.php
@@ -58,7 +58,7 @@ class ResolveShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('INVALID_SHORTCODE', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/short-url-not-found', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Short URL not found', $payload['title']);
self::assertEquals($shortCode, $payload['shortCode']);
diff --git a/module/Rest/test-api/Action/ShortUrlVisitsTest.php b/module/Rest/test-api/Action/ShortUrlVisitsTest.php
index 6a7e6a7e..8db002c4 100644
--- a/module/Rest/test-api/Action/ShortUrlVisitsTest.php
+++ b/module/Rest/test-api/Action/ShortUrlVisitsTest.php
@@ -34,7 +34,7 @@ class ShortUrlVisitsTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('INVALID_SHORTCODE', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/short-url-not-found', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Short URL not found', $payload['title']);
self::assertEquals($shortCode, $payload['shortCode']);
diff --git a/module/Rest/test-api/Action/SingleStepCreateShortUrlTest.php b/module/Rest/test-api/Action/SingleStepCreateShortUrlTest.php
index faed281d..038e3f38 100644
--- a/module/Rest/test-api/Action/SingleStepCreateShortUrlTest.php
+++ b/module/Rest/test-api/Action/SingleStepCreateShortUrlTest.php
@@ -38,7 +38,7 @@ class SingleStepCreateShortUrlTest extends ApiTestCase
self::assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
self::assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
- self::assertEquals('INVALID_AUTHORIZATION', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/missing-authentication', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Invalid authorization', $payload['title']);
}
diff --git a/module/Rest/test-api/Action/TagVisitsTest.php b/module/Rest/test-api/Action/TagVisitsTest.php
index fc54c111..c51f02fb 100644
--- a/module/Rest/test-api/Action/TagVisitsTest.php
+++ b/module/Rest/test-api/Action/TagVisitsTest.php
@@ -53,7 +53,7 @@ class TagVisitsTest extends ApiTestCase
self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
- self::assertEquals('TAG_NOT_FOUND', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/tag-not-found', $payload['type']);
self::assertEquals(sprintf('Tag with name "%s" could not be found', $tag), $payload['detail']);
self::assertEquals('Tag not found', $payload['title']);
}
diff --git a/module/Rest/test-api/Action/TagsStatsTest.php b/module/Rest/test-api/Action/TagsStatsTest.php
index 4ee94d42..9bf01474 100644
--- a/module/Rest/test-api/Action/TagsStatsTest.php
+++ b/module/Rest/test-api/Action/TagsStatsTest.php
@@ -25,29 +25,12 @@ class TagsStatsTest extends ApiTestCase
self::assertEquals($expectedPagination, $tags['pagination']);
}
- #[Test, DataProvider('provideQueries')]
- public function expectedListOfTagsIsReturnedForDeprecatedApproach(
- string $apiKey,
- array $query,
- array $expectedStats,
- array $expectedPagination,
- ): void {
- $query['withStats'] = 'true';
- $resp = $this->callApiWithKey(self::METHOD_GET, '/tags', [RequestOptions::QUERY => $query], $apiKey);
- ['tags' => $tags] = $this->getJsonResponsePayload($resp);
-
- self::assertEquals($expectedStats, $tags['stats']);
- self::assertEquals($expectedPagination, $tags['pagination']);
- self::assertArrayHasKey('data', $tags);
- }
-
public static function provideQueries(): iterable
{
yield 'admin API key' => ['valid_api_key', [], [
[
'tag' => 'bar',
'shortUrlsCount' => 1,
- 'visitsCount' => 2,
'visitsSummary' => [
'total' => 2,
'nonBots' => 1,
@@ -57,7 +40,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'baz',
'shortUrlsCount' => 0,
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
@@ -67,7 +49,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'foo',
'shortUrlsCount' => 3,
- 'visitsCount' => 5,
'visitsSummary' => [
'total' => 5,
'nonBots' => 4,
@@ -85,7 +66,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'bar',
'shortUrlsCount' => 1,
- 'visitsCount' => 2,
'visitsSummary' => [
'total' => 2,
'nonBots' => 1,
@@ -95,7 +75,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'baz',
'shortUrlsCount' => 0,
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
@@ -113,7 +92,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'bar',
'shortUrlsCount' => 1,
- 'visitsCount' => 2,
'visitsSummary' => [
'total' => 2,
'nonBots' => 1,
@@ -123,7 +101,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'foo',
'shortUrlsCount' => 2,
- 'visitsCount' => 5,
'visitsSummary' => [
'total' => 5,
'nonBots' => 4,
@@ -141,7 +118,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'foo',
'shortUrlsCount' => 2,
- 'visitsCount' => 5,
'visitsSummary' => [
'total' => 5,
'nonBots' => 4,
@@ -159,7 +135,6 @@ class TagsStatsTest extends ApiTestCase
[
'tag' => 'foo',
'shortUrlsCount' => 1,
- 'visitsCount' => 0,
'visitsSummary' => [
'total' => 0,
'nonBots' => 0,
diff --git a/module/Rest/test-api/Action/UpdateTagTest.php b/module/Rest/test-api/Action/UpdateTagTest.php
index 96b8ed62..3bced135 100644
--- a/module/Rest/test-api/Action/UpdateTagTest.php
+++ b/module/Rest/test-api/Action/UpdateTagTest.php
@@ -23,7 +23,7 @@ class UpdateTagTest extends ApiTestCase
self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
- self::assertEquals('INVALID_ARGUMENT', $payload['type']);
+ self::assertEquals('https://shlink.io/api/error/invalid-data', $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Invalid data', $payload['title']);
}
@@ -55,8 +55,8 @@ class UpdateTagTest extends ApiTestCase
public static function provideTagNotFoundApiVersions(): iterable
{
- yield 'version 1' => ['1', 'TAG_NOT_FOUND'];
- yield 'version 2' => ['2', 'TAG_NOT_FOUND'];
+ yield 'version 1' => ['1', 'https://shlink.io/api/error/tag-not-found'];
+ yield 'version 2' => ['2', 'https://shlink.io/api/error/tag-not-found'];
yield 'version 3' => ['3', 'https://shlink.io/api/error/tag-not-found'];
}
@@ -80,8 +80,8 @@ class UpdateTagTest extends ApiTestCase
public static function provideTagConflictsApiVersions(): iterable
{
- yield 'version 1' => ['1', 'TAG_CONFLICT'];
- yield 'version 2' => ['2', 'TAG_CONFLICT'];
+ yield 'version 1' => ['1', 'https://shlink.io/api/error/tag-conflict'];
+ yield 'version 2' => ['2', 'https://shlink.io/api/error/tag-conflict'];
yield 'version 3' => ['3', 'https://shlink.io/api/error/tag-conflict'];
}
diff --git a/module/Rest/test-api/Action/VisitStatsTest.php b/module/Rest/test-api/Action/VisitStatsTest.php
index 10a4de0c..2adf5a6a 100644
--- a/module/Rest/test-api/Action/VisitStatsTest.php
+++ b/module/Rest/test-api/Action/VisitStatsTest.php
@@ -32,8 +32,6 @@ class VisitStatsTest extends ApiTestCase
'nonBots' => 2,
'bots' => 1,
],
- 'visitsCount' => 7,
- 'orphanVisitsCount' => 3,
]];
yield 'domain-only API key' => ['domain_api_key', [
'nonOrphanVisits' => [
@@ -46,8 +44,6 @@ class VisitStatsTest extends ApiTestCase
'nonBots' => 2,
'bots' => 1,
],
- 'visitsCount' => 0,
- 'orphanVisitsCount' => 3,
]];
yield 'author API key' => ['author_api_key', [
'nonOrphanVisits' => [
@@ -60,8 +56,6 @@ class VisitStatsTest extends ApiTestCase
'nonBots' => 2,
'bots' => 1,
],
- 'visitsCount' => 5,
- 'orphanVisitsCount' => 3,
]];
}
}
diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php
index d086f6a6..1c164c85 100644
--- a/module/Rest/test-api/Middleware/AuthenticationTest.php
+++ b/module/Rest/test-api/Middleware/AuthenticationTest.php
@@ -29,8 +29,8 @@ class AuthenticationTest extends ApiTestCase
public static function provideApiVersions(): iterable
{
- yield 'version 1' => ['1', 'INVALID_AUTHORIZATION'];
- yield 'version 2' => ['2', 'INVALID_AUTHORIZATION'];
+ yield 'version 1' => ['1', 'https://shlink.io/api/error/missing-authentication'];
+ yield 'version 2' => ['2', 'https://shlink.io/api/error/missing-authentication'];
yield 'version 3' => ['3', 'https://shlink.io/api/error/missing-authentication'];
}
@@ -58,9 +58,9 @@ class AuthenticationTest extends ApiTestCase
public static function provideInvalidApiKeys(): iterable
{
- yield 'key which does not exist' => ['invalid', '2', 'INVALID_API_KEY'];
- yield 'key which is expired' => ['expired_api_key', '2', 'INVALID_API_KEY'];
- yield 'key which is disabled' => ['disabled_api_key', '2', 'INVALID_API_KEY'];
+ yield 'key which does not exist' => ['invalid', '2', 'https://shlink.io/api/error/invalid-api-key'];
+ yield 'key which is expired' => ['expired_api_key', '2', 'https://shlink.io/api/error/invalid-api-key'];
+ yield 'key which is disabled' => ['disabled_api_key', '2', 'https://shlink.io/api/error/invalid-api-key'];
yield 'version 3' => ['disabled_api_key', '3', 'https://shlink.io/api/error/invalid-api-key'];
}
}
diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php
index 447e8331..a63041dd 100644
--- a/module/Rest/test/Action/Tag/ListTagsActionTest.php
+++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php
@@ -7,14 +7,12 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequestFactory;
use Pagerfanta\Adapter\ArrayAdapter;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\Tag\Entity\Tag;
-use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\Tag\ListTagsAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
@@ -32,8 +30,8 @@ class ListTagsActionTest extends TestCase
$this->action = new ListTagsAction($this->tagService);
}
- #[Test, DataProvider('provideNoStatsQueries')]
- public function returnsBaseDataWhenStatsAreNotRequested(array $query): void
+ #[Test]
+ public function returnsBaseDataWhenStatsAreNotRequested(): void
{
$tags = [new Tag('foo'), new Tag('bar')];
$tagsCount = count($tags);
@@ -43,7 +41,7 @@ class ListTagsActionTest extends TestCase
)->willReturn(new Paginator(new ArrayAdapter($tags)));
/** @var JsonResponse $resp */
- $resp = $this->action->handle($this->requestWithApiKey()->withQueryParams($query));
+ $resp = $this->action->handle($this->requestWithApiKey());
$payload = $resp->getPayload();
self::assertEquals([
@@ -60,46 +58,6 @@ class ListTagsActionTest extends TestCase
], $payload);
}
- public static function provideNoStatsQueries(): iterable
- {
- yield 'no query' => [[]];
- yield 'withStats is false' => [['withStats' => 'withStats']];
- yield 'withStats is something else' => [['withStats' => 'foo']];
- }
-
- #[Test]
- public function returnsStatsWhenRequested(): void
- {
- $stats = [
- new TagInfo('foo', 1, 1),
- new TagInfo('bar', 3, 10),
- ];
- $itemsCount = count($stats);
- $this->tagService->expects($this->once())->method('tagsInfo')->with(
- $this->anything(),
- $this->isInstanceOf(ApiKey::class),
- )->willReturn(new Paginator(new ArrayAdapter($stats)));
- $req = $this->requestWithApiKey()->withQueryParams(['withStats' => 'true']);
-
- /** @var JsonResponse $resp */
- $resp = $this->action->handle($req);
- $payload = $resp->getPayload();
-
- self::assertEquals([
- 'tags' => [
- 'data' => ['foo', 'bar'],
- 'stats' => $stats,
- 'pagination' => [
- 'currentPage' => 1,
- 'pagesCount' => 1,
- 'itemsPerPage' => 10,
- 'itemsInCurrentPage' => $itemsCount,
- 'totalItems' => $itemsCount,
- ],
- ],
- ], $payload);
- }
-
private function requestWithApiKey(): ServerRequestInterface
{
return ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, ApiKey::create());
diff --git a/module/Rest/test/Exception/BackwardsCompatibleProblemDetailsExceptionTest.php b/module/Rest/test/Exception/BackwardsCompatibleProblemDetailsExceptionTest.php
deleted file mode 100644
index e51a5ac1..00000000
--- a/module/Rest/test/Exception/BackwardsCompatibleProblemDetailsExceptionTest.php
+++ /dev/null
@@ -1,113 +0,0 @@
-type;
- }
-
- public function getTitle(): string
- {
- return 'title';
- }
-
- public function getDetail(): string
- {
- return 'detail';
- }
-
- public function getAdditionalData(): array
- {
- return [];
- }
-
- public function toArray(): array
- {
- return ['type' => $this->type];
- }
-
- public function jsonSerialize(): array
- {
- return ['type' => $this->type];
- }
- };
- $e = BackwardsCompatibleProblemDetailsException::fromProblemDetails($original);
-
- self::assertEquals($e->getType(), $expectedType);
- self::assertEquals($e->toArray(), ['type' => $expectedType]);
- self::assertEquals($e->jsonSerialize(), ['type' => $expectedType]);
-
- self::assertEquals($original->getTitle(), $e->getTitle());
- self::assertEquals($original->getDetail(), $e->getDetail());
- self::assertEquals($original->getAdditionalData(), $e->getAdditionalData());
-
- if ($expectSameType) {
- self::assertEquals($original->getType(), $e->getType());
- self::assertEquals($original->toArray(), $e->toArray());
- self::assertEquals($original->jsonSerialize(), $e->jsonSerialize());
- } else {
- self::assertNotEquals($original->getType(), $e->getType());
- self::assertNotEquals($original->toArray(), $e->toArray());
- self::assertNotEquals($original->jsonSerialize(), $e->jsonSerialize());
- }
- }
-
- public static function provideTypes(): iterable
- {
- yield ['foo', 'foo', true];
- yield ['bar', 'bar', true];
- yield [ValidationException::ERROR_CODE, 'INVALID_ARGUMENT'];
- yield [DeleteShortUrlException::ERROR_CODE, 'INVALID_SHORT_URL_DELETION'];
- yield [DomainNotFoundException::ERROR_CODE, 'DOMAIN_NOT_FOUND'];
- yield [ForbiddenTagOperationException::ERROR_CODE, 'FORBIDDEN_OPERATION'];
- yield [InvalidUrlException::ERROR_CODE, 'INVALID_URL'];
- yield [NonUniqueSlugException::ERROR_CODE, 'INVALID_SLUG'];
- yield [ShortUrlNotFoundException::ERROR_CODE, 'INVALID_SHORTCODE'];
- yield [TagConflictException::ERROR_CODE, 'TAG_CONFLICT'];
- yield [TagNotFoundException::ERROR_CODE, 'TAG_NOT_FOUND'];
- yield [MercureException::ERROR_CODE, 'MERCURE_NOT_CONFIGURED'];
- yield [MissingAuthenticationException::ERROR_CODE, 'INVALID_AUTHORIZATION'];
- yield [VerifyAuthenticationException::ERROR_CODE, 'INVALID_API_KEY'];
- }
-}
diff --git a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php b/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php
deleted file mode 100644
index 78862980..00000000
--- a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
-handler = new BackwardsCompatibleProblemDetailsHandler();
- }
-
- /**
- * @param class-string $expectedException
- */
- #[Test, DataProvider('provideExceptions')]
- public function expectedExceptionIsThrownBasedOnTheRequestVersion(
- ServerRequestInterface $request,
- Throwable $thrownException,
- string $expectedException,
- ): void {
- $handler = $this->createMock(RequestHandlerInterface::class);
- $handler->expects($this->once())->method('handle')->with($request)->willThrowException($thrownException);
-
- $this->expectException($expectedException);
-
- $this->handler->process($request, $handler);
- }
-
- public static function provideExceptions(): iterable
- {
- $baseRequest = ServerRequestFactory::fromGlobals();
-
- yield 'no version' => [
- $baseRequest,
- ValidationException::fromArray([]),
- BackwardsCompatibleProblemDetailsException::class,
- ];
- yield 'version 1' => [
- $baseRequest->withAttribute('version', '1'),
- ValidationException::fromArray([]),
- BackwardsCompatibleProblemDetailsException::class,
- ];
- yield 'version 2' => [
- $baseRequest->withAttribute('version', '2'),
- ValidationException::fromArray([]),
- BackwardsCompatibleProblemDetailsException::class,
- ];
- yield 'version 3' => [
- $baseRequest->withAttribute('version', '3'),
- ValidationException::fromArray([]),
- ValidationException::class,
- ];
- yield 'version 4' => [
- $baseRequest->withAttribute('version', '3'),
- ValidationException::fromArray([]),
- ValidationException::class,
- ];
- }
-}