diff --git a/.gitattributes b/.gitattributes index 80102b6f..53b0a935 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,8 +5,6 @@ /module/CLI/test-resources export-ignore /module/Core/test export-ignore /module/Core/test-db export-ignore -/module/PreviewGenerator/test export-ignore -/module/PreviewGenerator/test-db export-ignore /module/Rest/test export-ignore /module/Rest/test-api export-ignore .gitattributes export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index e89d44c6..23fc1f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#429](https://github.com/shlinkio/shlink/issues/429) Dropped support for PHP 7.2 and 7.3 +* [#229](https://github.com/shlinkio/shlink/issues/229) Remove everything which was deprecated, including: + + * Preview generation feature completely removed. + * Authentication against REST API using JWT is no longer supported. + + See [UPGRADING doc](UPGRADING.md) in order to get details on how to migrate to this version. + #### Fixed * *Nothing* diff --git a/README.md b/README.md index 9d0911c6..d5201276 100644 --- a/README.md +++ b/README.md @@ -215,12 +215,6 @@ Those tasks can be performed using shlink's CLI tool, so it should be easy to sc > You don't need this if you use Shlink v1.17.0 or newer, since now it downloads/updates the geolocation database automatically just before trying to use it. -* Generate website previews: `/path/to/shlink/bin/cli short-url:process-previews` - - Running this will improve the performance of the `doma.in/abc123/preview` URLs, which return a preview of the site. - - > **Important!** Generating previews is considered deprecated and the feature will be removed in Shlink v2. - *Any of these commands accept the `-q` flag, which makes it not display any output. This is recommended when configuring the commands as cron jobs.* ## Update to new version @@ -274,33 +268,28 @@ Options: -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: - help Displays help for a command - list Lists commands + help Displays help for a command + list Lists commands api-key - api-key:disable Disables an API key. - api-key:generate Generates a new valid API key. - api-key:list Lists all the available API keys. - config - config:generate-charset [DEPRECATED] Generates a character set sample just by shuffling the default one, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". Then it can be set in the SHORTCODE_CHARS environment variable - config:generate-secret [DEPRECATED] Generates a random secret string that can be used for JWT token encryption + api-key:disable Disables an API key. + api-key:generate Generates a new valid API key. + api-key:list Lists all the available API keys. db - db:create Creates the database needed for shlink to work. It will do nothing if the database already exists - db:migrate Runs database migrations, which will ensure the shlink database is up to date. + db:create Creates the database needed for shlink to work. It will do nothing if the database already exists + db:migrate Runs database migrations, which will ensure the shlink database is up to date. short-url - short-url:delete [short-code:delete] Deletes a short URL - short-url:generate [shortcode:generate|short-code:generate] Generates a short URL for provided long URL and returns it - short-url:list [shortcode:list|short-code:list] List all short URLs - short-url:parse [shortcode:parse|short-code:parse] Returns the long URL behind a short code - short-url:process-previews [shortcode:process-previews|short-code:process-previews] [DEPRECATED] Processes and generates the previews for every URL, improving performance for later web requests. - short-url:visits [shortcode:visits|short-code:visits] Returns the detailed visits information for provided short code + short-url:delete Deletes a short URL + short-url:generate Generates a short URL for provided long URL and returns it + short-url:list List all short URLs + short-url:parse Returns the long URL behind a short code + short-url:visits Returns the detailed visits information for provided short code tag - tag:create Creates one or more tags. - tag:delete Deletes one or more tags. - tag:list Lists existing tags. - tag:rename Renames one existing tag. + tag:create Creates one or more tags. + tag:delete Deletes one or more tags. + tag:list Lists existing tags. + tag:rename Renames one existing tag. visit - visit:locate [visit:process] Resolves visits origin locations. - visit:update-db [DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses + visit:locate Resolves visits origin locations. ``` > This product includes GeoLite2 data created by MaxMind, available from [https://www.maxmind.com](https://www.maxmind.com) diff --git a/bin/wkhtmltoimage b/bin/wkhtmltoimage deleted file mode 100755 index 1459957b..00000000 Binary files a/bin/wkhtmltoimage and /dev/null differ diff --git a/composer.json b/composer.json index 2f33ce98..0b2c1d93 100644 --- a/composer.json +++ b/composer.json @@ -71,8 +71,7 @@ "psr-4": { "Shlinkio\\Shlink\\CLI\\": "module/CLI/src", "Shlinkio\\Shlink\\Rest\\": "module/Rest/src", - "Shlinkio\\Shlink\\Core\\": "module/Core/src", - "Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/" + "Shlinkio\\Shlink\\Core\\": "module/Core/src" }, "files": [ "module/Core/functions/functions.php" @@ -86,8 +85,7 @@ "ShlinkioTest\\Shlink\\Core\\": [ "module/Core/test", "module/Core/test-db" - ], - "ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test" + ] } }, "scripts": { diff --git a/config/autoload/app_options.global.php b/config/autoload/app_options.global.php index e10f1b20..f64f9cff 100644 --- a/config/autoload/app_options.global.php +++ b/config/autoload/app_options.global.php @@ -2,14 +2,11 @@ declare(strict_types=1); -use function Shlinkio\Shlink\Common\env; - return [ 'app_options' => [ 'name' => 'Shlink', 'version' => '%SHLINK_VERSION%', - 'secret_key' => env('SECRET_KEY', ''), 'disable_track_param' => null, ], diff --git a/config/autoload/error-handler.global.php b/config/autoload/error-handler.global.php index 7f5c91c9..69a54f03 100644 --- a/config/autoload/error-handler.global.php +++ b/config/autoload/error-handler.global.php @@ -15,10 +15,6 @@ return [ ], ], - 'backwards_compatible_problem_details' => [ - 'json_flags' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION, - ], - 'error_handler' => [ 'listeners' => [Logger\ErrorLogger::class], ], diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php index 35a9a16b..0f24a7c9 100644 --- a/config/autoload/middleware-pipeline.global.php +++ b/config/autoload/middleware-pipeline.global.php @@ -21,7 +21,6 @@ return [ 'path' => '/rest', 'middleware' => [ Rest\Middleware\CrossDomainMiddleware::class, - Rest\Middleware\BackwardsCompatibleProblemDetailsMiddleware::class, ProblemDetails\ProblemDetailsMiddleware::class, ], ], @@ -31,13 +30,6 @@ return [ Common\Middleware\CloseDbConnectionMiddleware::class, ], ], - 'pre-routing-rest' => [ - 'path' => '/rest', - 'middleware' => [ - Rest\Middleware\PathVersionMiddleware::class, - Rest\Middleware\ShortUrl\ShortCodePathMiddleware::class, - ], - ], 'routing' => [ 'middleware' => [ diff --git a/config/autoload/preview-generation.global.php b/config/autoload/preview-generation.global.php deleted file mode 100644 index 93696000..00000000 --- a/config/autoload/preview-generation.global.php +++ /dev/null @@ -1,12 +0,0 @@ - [ - 'files_location' => 'data/cache', - ], - -]; diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php index 58bc3faa..5cf4f86f 100644 --- a/config/autoload/url-shortener.global.php +++ b/config/autoload/url-shortener.global.php @@ -9,7 +9,7 @@ return [ 'schema' => 'https', 'hostname' => '', ], - 'validate_url' => true, + 'validate_url' => false, 'visits_webhooks' => [], ], diff --git a/config/config.php b/config/config.php index ad18fd20..855f5d41 100644 --- a/config/config.php +++ b/config/config.php @@ -23,7 +23,6 @@ return (new ConfigAggregator\ConfigAggregator([ CLI\ConfigProvider::class, Rest\ConfigProvider::class, EventDispatcher\ConfigProvider::class, - PreviewGenerator\ConfigProvider::class, new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'), env('APP_ENV') === 'test' ? new ConfigAggregator\PhpFileProvider('config/test/*.global.php') diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index 0aeaea6d..a3b297b7 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -61,6 +61,7 @@ return [ 'schema' => 'http', 'hostname' => 'doma.in', ], + 'validate_url' => true, ], 'zend-expressive-swoole' => [ diff --git a/docker/README.md b/docker/README.md index b600c268..423ba8f8 100644 --- a/docker/README.md +++ b/docker/README.md @@ -103,7 +103,7 @@ This is the complete list of supported env vars: * **postgres** -> `5432` * `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided. * `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`. -* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x (after following redirects) is returned when trying to shorten a URL. Defaults to `true`. +* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x is returned (after following redirects) when trying to shorten a URL. Defaults to `false`. * `INVALID_SHORT_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. * `REGULAR_404_REDIRECT_TO`: If a URL is provided here, when a user tries to access a URL not matching any one supported by the router, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. * `BASE_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access Shlink's base URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. @@ -119,7 +119,6 @@ This is the complete list of supported env vars: In the future, these redis servers could be used for other caching operations performed by shlink. -* `NOT_FOUND_REDIRECT_TO`: **Deprecated since v1.20 in favor of `INVALID_SHORT_URL_REDIRECT_TO`** If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. * `SHORTCODE_CHARS`: **Ignored when using Shlink 1.20 or newer**. A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)). An example using all env vars could look like this: @@ -138,7 +137,7 @@ docker run \ -e DB_PORT=3306 \ -e DISABLE_TRACK_PARAM="no-track" \ -e DELETE_SHORT_URL_THRESHOLD=30 \ - -e VALIDATE_URLS=false \ + -e VALIDATE_URLS=true \ -e "INVALID_SHORT_URL_REDIRECT_TO=https://my-landing-page.com" \ -e "REGULAR_404_REDIRECT_TO=https://my-landing-page.com" \ -e "BASE_URL_REDIRECT_TO=https://my-landing-page.com" \ @@ -164,7 +163,7 @@ The whole configuration should have this format, but it can be split into multip "delete_short_url_threshold": 30, "short_domain_schema": "https", "short_domain_host": "doma.in", - "validate_url": false, + "validate_url": true, "invalid_short_url_redirect_to": "https://my-landing-page.com", "regular_404_redirect_to": "https://my-landing-page.com", "base_url_redirect_to": "https://my-landing-page.com", @@ -186,15 +185,12 @@ The whole configuration should have this format, but it can be split into multip "password": "123abc", "host": "something.rds.amazonaws.com", "port": "3306" - }, - "not_found_redirect_to": "https://my-landing-page.com" + } } ``` > This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes). -> The `not_found_redirect_to` option has been deprecated in v1.20. Use `invalid_short_url_redirect_to` instead (however, it will still work for backwards compatibility). - Once created just run shlink with the volume: ```bash diff --git a/docker/config/shlink_in_docker.local.php b/docker/config/shlink_in_docker.local.php index 6d3367ac..5b9d5547 100644 --- a/docker/config/shlink_in_docker.local.php +++ b/docker/config/shlink_in_docker.local.php @@ -8,19 +8,10 @@ use Monolog\Handler\StreamHandler; use Monolog\Logger; use function explode; -use function file_exists; -use function file_get_contents; -use function file_put_contents; use function Functional\contains; -use function implode; use function Shlinkio\Shlink\Common\env; -use function sprintf; -use function str_shuffle; -use function substr; -use function sys_get_temp_dir; $helper = new class { - private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; private const DB_DRIVERS_MAP = [ 'mysql' => 'pdo_mysql', 'maria' => 'pdo_mysql', @@ -32,40 +23,6 @@ $helper = new class { 'postgres' => '5432', ]; - /** @var string */ - private $secretKey; - - public function __construct() - { - [, $this->secretKey] = $this->initShlinkSecretKey(); - } - - private function initShlinkSecretKey(): array - { - $keysFile = sprintf('%s/shlink.keys', sys_get_temp_dir()); - if (file_exists($keysFile)) { - return explode(',', file_get_contents($keysFile)); - } - - $keys = [ - '', // This was the SHORTCODE_CHARS. Kept as empty string for BC - env('SECRET_KEY', $this->generateSecretKey()), // Deprecated - ]; - - file_put_contents($keysFile, implode(',', $keys)); - return $keys; - } - - private function generateSecretKey(): string - { - return substr(str_shuffle(self::BASE62), 0, 32); - } - - public function getSecretKey(): string - { - return $this->secretKey; - } - public function getDbConfig(): array { $driver = env('DB_DRIVER'); @@ -94,7 +51,7 @@ $helper = new class { public function getNotFoundRedirectsConfig(): array { return [ - 'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO', env('NOT_FOUND_REDIRECT_TO')), + 'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'), 'regular_404' => env('REGULAR_404_REDIRECT_TO'), 'base_url' => env('BASE_URL_REDIRECT_TO'), ]; @@ -112,7 +69,6 @@ return [ 'config_cache_enabled' => false, 'app_options' => [ - 'secret_key' => $helper->getSecretKey(), 'disable_track_param' => env('DISABLE_TRACK_PARAM'), ], @@ -130,7 +86,7 @@ return [ 'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'), 'hostname' => env('SHORT_DOMAIN_HOST', ''), ], - 'validate_url' => (bool) env('VALIDATE_URLS', true), + 'validate_url' => (bool) env('VALIDATE_URLS', false), 'visits_webhooks' => $helper->getVisitsWebhooks(), ], diff --git a/docs/swagger/definitions/Error.json b/docs/swagger/definitions/Error.json index 006fa47a..217f32c5 100644 --- a/docs/swagger/definitions/Error.json +++ b/docs/swagger/definitions/Error.json @@ -17,16 +17,6 @@ "status": { "type": "number", "description": "HTTP response status code" - }, - "code": { - "type": "string", - "description": "**[Deprecated] Use type instead. Not returned for v2 of the REST API** A machine unique code", - "deprecated": true - }, - "message": { - "type": "string", - "description": "**[Deprecated] Use detail instead. Not returned for v2 of the REST API** A human-friendly error message", - "deprecated": true } } } diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json index 9b259249..8111bd6e 100644 --- a/docs/swagger/definitions/ShortUrl.json +++ b/docs/swagger/definitions/ShortUrl.json @@ -31,11 +31,6 @@ }, "meta": { "$ref": "./ShortUrlMeta.json" - }, - "originalUrl": { - "deprecated": true, - "type": "string", - "description": "The original long URL. [DEPRECATED. Use longUrl instead]" } } } diff --git a/docs/swagger/definitions/Visit.json b/docs/swagger/definitions/Visit.json index 131bc84e..9e1eb5b5 100644 --- a/docs/swagger/definitions/Visit.json +++ b/docs/swagger/definitions/Visit.json @@ -16,11 +16,6 @@ }, "visitLocation": { "$ref": "./VisitLocation.json" - }, - "remoteAddr": { - "type": "string", - "description": "This value is deprecated and will always be null", - "deprecated": true } } } diff --git a/docs/swagger/definitions/VisitLocation.json b/docs/swagger/definitions/VisitLocation.json index e4c9fc90..7e2fa18c 100644 --- a/docs/swagger/definitions/VisitLocation.json +++ b/docs/swagger/definitions/VisitLocation.json @@ -11,10 +11,10 @@ "type": "string" }, "latitude": { - "type": "string" + "type": "number" }, "longitude": { - "type": "string" + "type": "number" }, "regionName": { "type": "string" diff --git a/docs/swagger/paths/v1_authenticate.json b/docs/swagger/paths/v1_authenticate.json deleted file mode 100644 index cffa9e05..00000000 --- a/docs/swagger/paths/v1_authenticate.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "post": { - "deprecated": true, - "operationId": "authenticate", - "tags": [ - "Authentication" - ], - "summary": "[Deprecated] Perform authentication", - "description": "**This endpoint is deprecated, since the authentication can be performed via API key now**. Performs an authentication.", - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "apiKey" - ], - "properties": { - "apiKey": { - "description": "The API key to authenticate with", - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "The authentication worked.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "token": { - "type": "string", - "description": "The authentication token that needs to be sent in the Authorization header" - } - } - } - } - }, - "examples": { - "application/json": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" - } - } - }, - "400": { - "description": "An API key was not provided.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "401": { - "description": "The API key is incorrect, is disabled or has expired.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "500": { - "description": "Unexpected error.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - } -} diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 46708e22..0c6d0484 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "List short URLs", - "description": "Returns the list of short URLs.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Returns the list of short URLs.", "parameters": [ { "$ref": "../parameters/version.json" @@ -77,9 +77,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -187,13 +184,10 @@ "Short URLs" ], "summary": "Create short URL", - "description": "Creates a new short URL.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.

**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.", + "description": "Creates a new short URL.

**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.", "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ diff --git a/docs/swagger/paths/v1_short-urls_shorten.json b/docs/swagger/paths/v1_short-urls_shorten.json index 49233c49..c5ad9352 100644 --- a/docs/swagger/paths/v1_short-urls_shorten.json +++ b/docs/swagger/paths/v1_short-urls_shorten.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Create a short URL", - "description": "Creates a short URL in a single API call. Useful for third party integrations.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Creates a short URL in a single API call. Useful for third party integrations.", "parameters": [ { "$ref": "../parameters/version.json" diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index 95532868..d4537a83 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Parse short code", - "description": "Get the long URL behind a short URL's short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Get the long URL behind a short URL's short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -32,9 +32,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -94,7 +91,7 @@ "Short URLs" ], "summary": "Edit short URL", - "description": "Update certain meta arguments from an existing short URL.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Update certain meta arguments from an existing short URL.", "parameters": [ { "$ref": "../parameters/version.json" @@ -137,9 +134,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -201,105 +195,13 @@ } }, - "put": { - "deprecated": true, - "operationId": "editShortUrlPut", - "tags": [ - "Short URLs" - ], - "summary": "[DEPRECATED] Edit short URL", - "description": "**[DEPRECATED]** Use [editShortUrl](#/Short_URLs/getShortUrl) instead", - "parameters": [ - { - "$ref": "../parameters/version.json" - }, - { - "name": "shortCode", - "in": "path", - "description": "The short code to edit.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "validSince": { - "description": "The date (in ISO-8601 format) from which this short code will be valid", - "type": "string" - }, - "validUntil": { - "description": "The date (in ISO-8601 format) until which this short code will be valid", - "type": "string" - }, - "maxVisits": { - "description": "The maximum number of allowed visits for this short code", - "type": "number" - } - } - } - } - } - }, - "security": [ - { - "ApiKey": [] - }, - { - "Bearer": [] - } - ], - "responses": { - "204": { - "description": "The short code has been properly updated." - }, - "400": { - "description": "Provided meta arguments are invalid.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "404": { - "description": "No short URL was found for provided short code.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "500": { - "description": "Unexpected error.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - }, - "delete": { "operationId": "deleteShortUrl", "tags": [ "Short URLs" ], "summary": "Delete short URL", - "description": "Deletes the short URL for provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Deletes the short URL for provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -317,9 +219,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json b/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json index 45142e62..4065f718 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Edit tags on short URL", - "description": "Edit the tags on URL identified by provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Edit the tags on URL identified by provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -46,9 +46,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json index 508449de..2369ba13 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json @@ -5,7 +5,7 @@ "Visits" ], "summary": "List visits for short URL", - "description": "Get the list of visits on the short URL behind provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Get the list of visits on the short URL behind provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -59,9 +59,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -108,8 +105,8 @@ "cityName": "Cupertino", "countryCode": "US", "countryName": "United States", - "latitude": "37.3042", - "longitude": "-122.0946", + "latitude": 37.3042, + "longitude": -122.0946, "regionName": "California", "timezone": "America/Los_Angeles" } diff --git a/docs/swagger/paths/v1_tags.json b/docs/swagger/paths/v1_tags.json index a0fcc512..5e7fd71c 100644 --- a/docs/swagger/paths/v1_tags.json +++ b/docs/swagger/paths/v1_tags.json @@ -9,9 +9,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -78,9 +75,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -170,9 +164,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -279,9 +270,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/{shortCode}_preview.json b/docs/swagger/paths/{shortCode}_preview.json deleted file mode 100644 index 98df559c..00000000 --- a/docs/swagger/paths/{shortCode}_preview.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "get": { - "deprecated": true, - "operationId": "shortUrlPreview", - "tags": [ - "URL Shortener" - ], - "summary": "Short URL preview image", - "description": "Returns the preview of the page behind a short URL", - "parameters": [ - { - "name": "shortCode", - "in": "path", - "description": "The short code to resolve.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Image in PNG format", - "content": { - "image/png": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - } - } - } -} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index d924b8a0..ddf2b3ad 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -33,12 +33,6 @@ "type": "apiKey", "in": "header", "name": "X-Api-Key" - }, - "Bearer": { - "description": "**[DEPRECATED]** The JWT identifying a previously authenticated API key", - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" } } }, @@ -63,10 +57,6 @@ { "name": "URL Shortener", "description": "Non-rest endpoints, used to be publicly exposed" - }, - { - "name": "Authentication", - "description": "**[DEPRECATED]** Authentication-related endpoints" } ], @@ -104,13 +94,6 @@ }, "/{shortCode}/qr-code": { "$ref": "paths/{shortCode}_qr-code.json" - }, - "/{shortCode}/preview": { - "$ref": "paths/{shortCode}_preview.json" - }, - - "/rest/v1/authenticate": { - "$ref": "paths/v1_authenticate.json" } } } diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index 0d5d5d5b..fa9efc69 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -12,14 +12,9 @@ return [ Command\ShortUrl\ResolveUrlCommand::NAME => Command\ShortUrl\ResolveUrlCommand::class, Command\ShortUrl\ListShortUrlsCommand::NAME => Command\ShortUrl\ListShortUrlsCommand::class, Command\ShortUrl\GetVisitsCommand::NAME => Command\ShortUrl\GetVisitsCommand::class, - Command\ShortUrl\GeneratePreviewCommand::NAME => Command\ShortUrl\GeneratePreviewCommand::class, Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::class, Command\Visit\LocateVisitsCommand::NAME => Command\Visit\LocateVisitsCommand::class, - Command\Visit\UpdateDbCommand::NAME => Command\Visit\UpdateDbCommand::class, - - Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class, - Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class, Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class, Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class, diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index c89a0ad7..c7eea7b2 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -12,7 +12,6 @@ use Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Installer\Factory\ProcessHelperFactory; use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; -use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator; use Shlinkio\Shlink\Rest\Service\ApiKeyService; use Symfony\Component\Console as SymfonyCli; use Symfony\Component\Lock\LockFactory; @@ -34,14 +33,9 @@ return [ Command\ShortUrl\ResolveUrlCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\ListShortUrlsCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\GetVisitsCommand::class => ConfigAbstractFactory::class, - Command\ShortUrl\GeneratePreviewCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\DeleteShortUrlCommand::class => ConfigAbstractFactory::class, Command\Visit\LocateVisitsCommand::class => ConfigAbstractFactory::class, - Command\Visit\UpdateDbCommand::class => ConfigAbstractFactory::class, - - Command\Config\GenerateCharsetCommand::class => InvokableFactory::class, - Command\Config\GenerateSecretCommand::class => InvokableFactory::class, Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class, Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class, @@ -64,7 +58,6 @@ return [ Command\ShortUrl\ResolveUrlCommand::class => [Service\UrlShortener::class], Command\ShortUrl\ListShortUrlsCommand::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'], Command\ShortUrl\GetVisitsCommand::class => [Service\VisitsTracker::class], - Command\ShortUrl\GeneratePreviewCommand::class => [Service\ShortUrlService::class, PreviewGenerator::class], Command\ShortUrl\DeleteShortUrlCommand::class => [Service\ShortUrl\DeleteShortUrlService::class], Command\Visit\LocateVisitsCommand::class => [ @@ -73,7 +66,6 @@ return [ LockFactory::class, GeolocationDbUpdater::class, ], - Command\Visit\UpdateDbCommand::class => [DbUpdater::class], Command\Api\GenerateKeyCommand::class => [ApiKeyService::class], Command\Api\DisableKeyCommand::class => [ApiKeyService::class], diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php deleted file mode 100644 index a285c7f0..00000000 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ /dev/null @@ -1,40 +0,0 @@ -setName(self::NAME) - ->setDescription(sprintf( - '[DEPRECATED] Generates a character set sample just by shuffling the default one, "%s". ' - . 'Then it can be set in the SHORTCODE_CHARS environment variable', - self::DEFAULT_CHARS - )) - ->setHelp('This command is deprecated. Better leave shlink generate the charset.'); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $charSet = str_shuffle(self::DEFAULT_CHARS); - (new SymfonyStyle($input, $output))->success(sprintf('Character set: "%s"', $charSet)); - return ExitCodes::EXIT_SUCCESS; - } -} diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php deleted file mode 100644 index c0d60eea..00000000 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ /dev/null @@ -1,39 +0,0 @@ -setName(self::NAME) - ->setDescription('[DEPRECATED] Generates a random secret string that can be used for JWT token encryption') - ->setHelp( - 'This command is deprecated. Better leave shlink generate the secret key.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $secret = $this->generateRandomString(32); - (new SymfonyStyle($input, $output))->success(sprintf('Secret key: "%s"', $secret)); - return ExitCodes::EXIT_SUCCESS; - } -} diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php index 6626791b..91c37663 100644 --- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php @@ -19,7 +19,6 @@ use function sprintf; class DeleteShortUrlCommand extends Command { public const NAME = 'short-url:delete'; - private const ALIASES = ['short-code:delete']; private DeleteShortUrlServiceInterface $deleteShortUrlService; @@ -33,7 +32,6 @@ class DeleteShortUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Deletes a short URL') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code for the short URL to be deleted') ->addOption( diff --git a/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php b/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php deleted file mode 100644 index 081fa94f..00000000 --- a/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php +++ /dev/null @@ -1,76 +0,0 @@ -shortUrlService = $shortUrlService; - $this->previewGenerator = $previewGenerator; - } - - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setAliases(self::ALIASES) - ->setDescription( - '[DEPRECATED] Processes and generates the previews for every URL, improving performance for later web ' - . 'requests.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $page = 1; - do { - $shortUrls = $this->shortUrlService->listShortUrls($page); - $page += 1; - - foreach ($shortUrls as $shortUrl) { - $this->processUrl($shortUrl->getLongUrl(), $output); - } - } while ($page <= $shortUrls->count()); - - (new SymfonyStyle($input, $output))->success('Finished processing all URLs'); - return ExitCodes::EXIT_SUCCESS; - } - - private function processUrl($url, OutputInterface $output): void - { - try { - $output->write(sprintf('Processing URL %s...', $url)); - $this->previewGenerator->generatePreview($url); - $output->writeln(' Success!'); - } catch (PreviewGenerationException $e) { - $output->writeln(' Error'); - if ($output->isVerbose()) { - $this->getApplication()->renderThrowable($e, $output); - } - } - } -} diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 94748795..3e66658d 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -26,7 +26,6 @@ use function sprintf; class GenerateShortUrlCommand extends Command { public const NAME = 'short-url:generate'; - private const ALIASES = ['shortcode:generate', 'short-code:generate']; private UrlShortenerInterface $urlShortener; private array $domainConfig; @@ -42,7 +41,6 @@ class GenerateShortUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Generates a short URL for provided long URL and returns it') ->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to parse') ->addOption( diff --git a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php index 94462a57..363ff3aa 100644 --- a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php @@ -22,7 +22,6 @@ use function Functional\select_keys; class GetVisitsCommand extends AbstractWithDateRangeCommand { public const NAME = 'short-url:visits'; - private const ALIASES = ['shortcode:visits', 'short-code:visits']; private VisitsTrackerInterface $visitsTracker; @@ -36,7 +35,6 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Returns the detailed visits information for provided short code') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get'); } diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 9eb0cc13..62d44445 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -32,7 +32,6 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand use PaginatorUtilsTrait; public const NAME = 'short-url:list'; - private const ALIASES = ['shortcode:list', 'short-code:list']; private const COLUMNS_WHITELIST = [ 'shortCode', 'shortUrl', @@ -56,7 +55,6 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('List all short URLs') ->addOption( 'page', diff --git a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php index c75ac9d6..e4c75410 100644 --- a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php @@ -19,7 +19,6 @@ use function sprintf; class ResolveUrlCommand extends Command { public const NAME = 'short-url:parse'; - private const ALIASES = ['shortcode:parse', 'short-code:parse']; private UrlShortenerInterface $urlShortener; @@ -33,7 +32,6 @@ class ResolveUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Returns the long URL behind a short code') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code to parse') ->addOption('domain', 'd', InputOption::VALUE_REQUIRED, 'The domain to which the short URL is attached.'); diff --git a/module/CLI/src/Command/Visit/LocateVisitsCommand.php b/module/CLI/src/Command/Visit/LocateVisitsCommand.php index 0bbd0c90..432ac1bd 100644 --- a/module/CLI/src/Command/Visit/LocateVisitsCommand.php +++ b/module/CLI/src/Command/Visit/LocateVisitsCommand.php @@ -30,7 +30,6 @@ use function sprintf; class LocateVisitsCommand extends AbstractLockedCommand { public const NAME = 'visit:locate'; - public const ALIASES = ['visit:process']; private VisitServiceInterface $visitService; private IpLocationResolverInterface $ipLocationResolver; @@ -55,7 +54,6 @@ class LocateVisitsCommand extends AbstractLockedCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Resolves visits origin locations.'); } diff --git a/module/CLI/src/Command/Visit/UpdateDbCommand.php b/module/CLI/src/Command/Visit/UpdateDbCommand.php deleted file mode 100644 index 367423fd..00000000 --- a/module/CLI/src/Command/Visit/UpdateDbCommand.php +++ /dev/null @@ -1,91 +0,0 @@ -geoLiteDbUpdater = $geoLiteDbUpdater; - } - - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('[DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses') - ->setHelp( - 'The GeoLite2 database is updated first Tuesday every month, so this command should be ideally run ' - . 'every first Wednesday' - ) - ->addOption( - 'ignoreErrors', - 'i', - InputOption::VALUE_NONE, - 'Makes the command success even iof the update fails.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $io = new SymfonyStyle($input, $output); - $progressBar = new ProgressBar($output); - $progressBar->start(); - - try { - $this->geoLiteDbUpdater->downloadFreshCopy(function (int $total, int $downloaded) use ($progressBar) { - $progressBar->setMaxSteps($total); - $progressBar->setProgress($downloaded); - }); - - $progressBar->finish(); - $io->newLine(); - - $io->success('GeoLite2 database properly updated'); - return ExitCodes::EXIT_SUCCESS; - } catch (RuntimeException $e) { - $progressBar->finish(); - $io->newLine(); - - return $this->handleError($e, $io, $input); - } - } - - private function handleError(RuntimeException $e, SymfonyStyle $io, InputInterface $input): int - { - $ignoreErrors = $input->getOption('ignoreErrors'); - $baseErrorMsg = 'An error occurred while updating GeoLite2 database'; - - if ($ignoreErrors) { - $io->warning(sprintf('%s, but it was ignored', $baseErrorMsg)); - return ExitCodes::EXIT_SUCCESS; - } - - $io->error($baseErrorMsg); - if ($io->isVerbose()) { - $this->getApplication()->renderThrowable($e, $io); - } - return ExitCodes::EXIT_FAILURE; - } -} diff --git a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php b/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php deleted file mode 100644 index e5430e4a..00000000 --- a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php +++ /dev/null @@ -1,47 +0,0 @@ -add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function charactersAreGeneratedFromDefault() - { - $prefix = 'Character set: '; - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - - // Both default character set and the new one should have the same length - $this->assertStringContainsString($prefix, $output); - } - - protected function orderStringLetters($string) - { - $letters = str_split($string); - sort($letters); - return implode('', $letters); - } -} diff --git a/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php b/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php deleted file mode 100644 index e4029231..00000000 --- a/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php +++ /dev/null @@ -1,92 +0,0 @@ -previewGenerator = $this->prophesize(PreviewGenerator::class); - $this->shortUrlService = $this->prophesize(ShortUrlService::class); - - $command = new GeneratePreviewCommand($this->shortUrlService->reveal(), $this->previewGenerator->reveal()); - $app = new Application(); - $app->add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function previewsForEveryUrlAreGenerated() - { - $paginator = $this->createPaginator([ - new ShortUrl('http://foo.com'), - new ShortUrl('https://bar.com'), - new ShortUrl('http://baz.com/something'), - ]); - $this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledOnce(); - - $generatePreview1 = $this->previewGenerator->generatePreview('http://foo.com')->willReturn(''); - $generatePreview2 = $this->previewGenerator->generatePreview('https://bar.com')->willReturn(''); - $generatePreview3 = $this->previewGenerator->generatePreview('http://baz.com/something')->willReturn(''); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - - $this->assertStringContainsString('Processing URL http://foo.com', $output); - $this->assertStringContainsString('Processing URL https://bar.com', $output); - $this->assertStringContainsString('Processing URL http://baz.com/something', $output); - $this->assertStringContainsString('Finished processing all URLs', $output); - $generatePreview1->shouldHaveBeenCalledOnce(); - $generatePreview2->shouldHaveBeenCalledOnce(); - $generatePreview3->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function exceptionWillOutputError() - { - $items = [ - new ShortUrl('http://foo.com'), - new ShortUrl('https://bar.com'), - new ShortUrl('http://baz.com/something'), - ]; - $paginator = $this->createPaginator($items); - $this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledOnce(); - $this->previewGenerator->generatePreview(Argument::any())->willThrow(PreviewGenerationException::class) - ->shouldBeCalledTimes(count($items)); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $this->assertEquals(count($items), substr_count($output, 'Error')); - } - - protected function createPaginator(array $items) - { - $paginator = new Paginator(new ArrayAdapter($items)); - $paginator->setItemCountPerPage(count($items)); - - return $paginator; - } -} diff --git a/module/CLI/test/Command/Visit/UpdateDbCommandTest.php b/module/CLI/test/Command/Visit/UpdateDbCommandTest.php deleted file mode 100644 index 2d6fc478..00000000 --- a/module/CLI/test/Command/Visit/UpdateDbCommandTest.php +++ /dev/null @@ -1,75 +0,0 @@ -dbUpdater = $this->prophesize(DbUpdaterInterface::class); - - $command = new UpdateDbCommand($this->dbUpdater->reveal()); - $app = new Application(); - $app->add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function successMessageIsPrintedIfEverythingWorks(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->will(function () { - }); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('GeoLite2 database properly updated', $output); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function errorMessageIsPrintedIfAnExceptionIsThrown(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('An error occurred while updating GeoLite2 database', $output); - $this->assertEquals(ExitCodes::EXIT_FAILURE, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function warningMessageIsPrintedIfAnExceptionIsThrownAndErrorsAreIgnored(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class); - - $this->commandTester->execute(['--ignoreErrors' => true]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('ignored', $output); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Core/config/app_options.config.php b/module/Core/config/app_options.config.php deleted file mode 100644 index ccab5752..00000000 --- a/module/Core/config/app_options.config.php +++ /dev/null @@ -1,9 +0,0 @@ - [], - -]; diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 0ebdae7d..34759868 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -8,7 +8,6 @@ use Doctrine\Common\Cache\Cache; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\ErrorHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; -use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator; use Zend\Expressive\Router\RouterInterface; use Zend\Expressive\Template\TemplateRendererInterface; use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; @@ -37,7 +36,6 @@ return [ Action\RedirectAction::class => ConfigAbstractFactory::class, Action\PixelAction::class => ConfigAbstractFactory::class, Action\QrCodeAction::class => ConfigAbstractFactory::class, - Action\PreviewAction::class => ConfigAbstractFactory::class, Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], @@ -74,7 +72,6 @@ return [ 'Logger_Shlink', ], Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'], - Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'], Middleware\QrCodeCacheMiddleware::class => [Cache::class], ], diff --git a/module/Core/config/routes.config.php b/module/Core/config/routes.config.php index b38222df..d636ed23 100644 --- a/module/Core/config/routes.config.php +++ b/module/Core/config/routes.config.php @@ -37,23 +37,6 @@ return [ ], 'allowed_methods' => [RequestMethod::METHOD_GET], ], - - // Deprecated routes - [ - 'name' => 'short-url-preview', - 'path' => '/{shortCode}/preview', - 'middleware' => Action\PreviewAction::class, - 'allowed_methods' => [RequestMethod::METHOD_GET], - ], - [ - 'name' => 'short-url-qr-code-old', - 'path' => '/qr/{shortCode}[/{size:[0-9]+}]', - 'middleware' => [ - Middleware\QrCodeCacheMiddleware::class, - Action\QrCodeAction::class, - ], - 'allowed_methods' => [RequestMethod::METHOD_GET], - ], ], ]; diff --git a/module/Core/src/Action/PreviewAction.php b/module/Core/src/Action/PreviewAction.php deleted file mode 100644 index d243f12c..00000000 --- a/module/Core/src/Action/PreviewAction.php +++ /dev/null @@ -1,63 +0,0 @@ -previewGenerator = $previewGenerator; - $this->urlShortener = $urlShortener; - $this->logger = $logger ?: new NullLogger(); - } - - /** - * Process an incoming server request and return a response, optionally delegating - * to the next middleware component to create the response. - * - * @param Request $request - * @param RequestHandlerInterface $handler - * - * @return Response - */ - public function process(Request $request, RequestHandlerInterface $handler): Response - { - $shortCode = $request->getAttribute('shortCode'); - - try { - $url = $this->urlShortener->shortCodeToUrl($shortCode); - $imagePath = $this->previewGenerator->generatePreview($url->getLongUrl()); - return $this->generateImageResponse($imagePath); - } catch (ShortUrlNotFoundException | PreviewGenerationException $e) { - $this->logger->warning('An error occurred while generating preview image. {e}', ['e' => $e]); - return $handler->handle($request); - } - } -} diff --git a/module/Core/src/Config/SimplifiedConfigParser.php b/module/Core/src/Config/SimplifiedConfigParser.php index e9c2ae7b..19fe4fb4 100644 --- a/module/Core/src/Config/SimplifiedConfigParser.php +++ b/module/Core/src/Config/SimplifiedConfigParser.php @@ -22,7 +22,6 @@ class SimplifiedConfigParser 'short_domain_schema' => ['url_shortener', 'domain', 'schema'], 'short_domain_host' => ['url_shortener', 'domain', 'hostname'], 'validate_url' => ['url_shortener', 'validate_url'], - 'not_found_redirect_to' => ['not_found_redirects', 'invalid_short_url'], // Deprecated 'invalid_short_url_redirect_to' => ['not_found_redirects', 'invalid_short_url'], 'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'], 'base_url_redirect_to' => ['not_found_redirects', 'base_path'], diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index c4ee97a2..38c17565 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -90,9 +90,6 @@ class Visit extends AbstractEntity implements JsonSerializable 'date' => $this->date->toAtomString(), 'userAgent' => $this->userAgent, 'visitLocation' => $this->visitLocation, - - // Deprecated - 'remoteAddr' => null, ]; } diff --git a/module/Core/src/Entity/VisitLocation.php b/module/Core/src/Entity/VisitLocation.php index ebc8829a..641b078e 100644 --- a/module/Core/src/Entity/VisitLocation.php +++ b/module/Core/src/Entity/VisitLocation.php @@ -14,8 +14,8 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface private string $countryName; private string $regionName; private string $cityName; - private string $latitude; // FIXME Should be float - private string $longitude; // FIXME Should be float + private float $latitude; + private float $longitude; private string $timezone; public function __construct(Location $location) @@ -25,22 +25,22 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface public function getCountryName(): string { - return $this->countryName ?? ''; + return $this->countryName; } - public function getLatitude(): string + public function getLatitude(): float { - return $this->latitude ?? ''; + return $this->latitude; } - public function getLongitude(): string + public function getLongitude(): float { - return $this->longitude ?? ''; + return $this->longitude; } public function getCityName(): string { - return $this->cityName ?? ''; + return $this->cityName; } private function exchangeLocationInfo(Location $info): void @@ -49,8 +49,8 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface $this->countryName = $info->countryName(); $this->regionName = $info->regionName(); $this->cityName = $info->city(); - $this->latitude = (string) $info->latitude(); - $this->longitude = (string) $info->longitude(); + $this->latitude = $info->latitude(); + $this->longitude = $info->longitude(); $this->timezone = $info->timeZone(); } @@ -74,8 +74,8 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface $this->countryName === '' && $this->regionName === '' && $this->cityName === '' && - ((float) $this->latitude) === 0.0 && - ((float) $this->longitude) === 0.0 && + $this->latitude === 0.0 && + $this->longitude === 0.0 && $this->timezone === ''; } } diff --git a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php b/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php index 7f901d33..646f1757 100644 --- a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php +++ b/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php @@ -78,7 +78,7 @@ class NotifyVisitToWebHooks 'User-Agent' => (string) $this->appOptions, ], RequestOptions::JSON => [ - 'shortUrl' => $this->transformer->transform($visit->getShortUrl(), false), + 'shortUrl' => $this->transformer->transform($visit->getShortUrl()), 'visit' => $visit->jsonSerialize(), ], ]; diff --git a/module/Core/src/Options/AppOptions.php b/module/Core/src/Options/AppOptions.php index 36b6d58b..06810a68 100644 --- a/module/Core/src/Options/AppOptions.php +++ b/module/Core/src/Options/AppOptions.php @@ -15,8 +15,6 @@ class AppOptions extends AbstractOptions private string $name = ''; private string $version = '1.0'; - /** @deprecated */ - private string $secretKey = ''; private ?string $disableTrackParam = null; public function getName(): string @@ -41,23 +39,6 @@ class AppOptions extends AbstractOptions return $this; } - /** - * @deprecated - */ - public function getSecretKey(): string - { - return $this->secretKey; - } - - /** - * @deprecated - */ - protected function setSecretKey(string $secretKey): self - { - $this->secretKey = $secretKey; - return $this; - } - /** * @return string|null */ diff --git a/module/Core/src/Transformer/ShortUrlDataTransformer.php b/module/Core/src/Transformer/ShortUrlDataTransformer.php index f734cf01..bb19f997 100644 --- a/module/Core/src/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/Transformer/ShortUrlDataTransformer.php @@ -22,11 +22,11 @@ class ShortUrlDataTransformer implements DataTransformerInterface /** * @param ShortUrl $shortUrl */ - public function transform($shortUrl, bool $includeDeprecated = true): array + public function transform($shortUrl): array { $longUrl = $shortUrl->getLongUrl(); - $rawData = [ + return [ 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => $shortUrl->toString($this->domainConfig), 'longUrl' => $longUrl, @@ -35,12 +35,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'tags' => invoke($shortUrl->getTags(), '__toString'), 'meta' => $this->buildMeta($shortUrl), ]; - - if ($includeDeprecated) { - $rawData['originalUrl'] = $longUrl; - } - - return $rawData; } private function buildMeta(ShortUrl $shortUrl): array diff --git a/module/Core/src/Visit/Model/UnknownVisitLocation.php b/module/Core/src/Visit/Model/UnknownVisitLocation.php index baf056b4..b8926bd5 100644 --- a/module/Core/src/Visit/Model/UnknownVisitLocation.php +++ b/module/Core/src/Visit/Model/UnknownVisitLocation.php @@ -11,14 +11,14 @@ final class UnknownVisitLocation implements VisitLocationInterface return 'Unknown'; } - public function getLatitude(): string + public function getLatitude(): float { - return '0.0'; + return 0.0; } - public function getLongitude(): string + public function getLongitude(): float { - return '0.0'; + return 0.0; } public function getCityName(): string @@ -33,8 +33,8 @@ final class UnknownVisitLocation implements VisitLocationInterface 'countryName' => 'Unknown', 'regionName' => 'Unknown', 'cityName' => 'Unknown', - 'latitude' => '0.0', - 'longitude' => '0.0', + 'latitude' => 0.0, + 'longitude' => 0.0, 'timezone' => 'Unknown', ]; } diff --git a/module/Core/src/Visit/Model/VisitLocationInterface.php b/module/Core/src/Visit/Model/VisitLocationInterface.php index 992e138f..9a296a28 100644 --- a/module/Core/src/Visit/Model/VisitLocationInterface.php +++ b/module/Core/src/Visit/Model/VisitLocationInterface.php @@ -10,9 +10,9 @@ interface VisitLocationInterface extends JsonSerializable { public function getCountryName(): string; - public function getLatitude(): string; + public function getLatitude(): float; - public function getLongitude(): string; + public function getLongitude(): float; public function getCityName(): string; } diff --git a/module/Core/test/Action/PreviewActionTest.php b/module/Core/test/Action/PreviewActionTest.php deleted file mode 100644 index 0725c957..00000000 --- a/module/Core/test/Action/PreviewActionTest.php +++ /dev/null @@ -1,76 +0,0 @@ -previewGenerator = $this->prophesize(PreviewGenerator::class); - $this->urlShortener = $this->prophesize(UrlShortener::class); - $this->action = new PreviewAction($this->previewGenerator->reveal(), $this->urlShortener->reveal()); - } - - /** @test */ - public function correctShortCodeReturnsImageResponse(): void - { - $shortCode = 'abc123'; - $url = 'foobar.com'; - $shortUrl = new ShortUrl($url); - $path = __FILE__; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)->shouldBeCalledOnce(); - $this->previewGenerator->generatePreview($url)->willReturn($path)->shouldBeCalledOnce(); - - $resp = $this->action->process( - (new ServerRequest())->withAttribute('shortCode', $shortCode), - $this->prophesize(RequestHandlerInterface::class)->reveal() - ); - - $this->assertEquals(filesize($path), $resp->getHeaderLine('Content-length')); - $this->assertEquals((new finfo(FILEINFO_MIME))->file($path), $resp->getHeaderLine('Content-type')); - } - - /** @test */ - public function invalidShortCodeExceptionFallsBackToNextMiddleware(): void - { - $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(ShortUrlNotFoundException::class) - ->shouldBeCalledOnce(); - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle(Argument::any())->willReturn(new Response()); - - $this->action->process( - (new ServerRequest())->withAttribute('shortCode', $shortCode), - $delegate->reveal() - ); - - $process->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php index 76ba9b1b..c608a44f 100644 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ b/module/Core/test/Config/SimplifiedConfigParserTest.php @@ -11,7 +11,7 @@ use function array_merge; class SimplifiedConfigParserTest extends TestCase { - private $postProcessor; + private SimplifiedConfigParser $postProcessor; public function setUp(): void { @@ -38,9 +38,9 @@ class SimplifiedConfigParserTest extends TestCase 'disable_track_param' => 'bar', 'short_domain_schema' => 'https', 'short_domain_host' => 'doma.in', - 'validate_url' => false, + 'validate_url' => true, 'delete_short_url_threshold' => 50, - 'not_found_redirect_to' => 'foobar.com', + 'invalid_short_url_redirect_to' => 'foobar.com', 'redis_servers' => [ 'tcp://1.1.1.1:1111', 'tcp://1.2.2.2:2222', @@ -79,7 +79,7 @@ class SimplifiedConfigParserTest extends TestCase 'schema' => 'https', 'hostname' => 'doma.in', ], - 'validate_url' => false, + 'validate_url' => true, 'visits_webhooks' => [ 'http://my-api.com/api/v2.3/notify', 'https://third-party.io/foo', @@ -125,28 +125,4 @@ class SimplifiedConfigParserTest extends TestCase $this->assertEquals(array_merge($expected, $simplified), $result); } - - /** - * @test - * @dataProvider provideConfigWithDeprecates - */ - public function properlyMapsDeprecatedConfigs(array $config, string $expected): void - { - $result = ($this->postProcessor)($config); - $this->assertEquals($expected, $result['not_found_redirects']['invalid_short_url']); - } - - public function provideConfigWithDeprecates(): iterable - { - yield 'only deprecated config' => [['not_found_redirect_to' => 'old_value'], 'old_value']; - yield 'only new config' => [['invalid_short_url_redirect_to' => 'new_value'], 'new_value']; - yield 'both configs, new first' => [ - ['invalid_short_url_redirect_to' => 'new_value', 'not_found_redirect_to' => 'old_value'], - 'new_value', - ]; - yield 'both configs, deprecated first' => [ - ['not_found_redirect_to' => 'old_value', 'invalid_short_url_redirect_to' => 'new_value'], - 'new_value', - ]; - } } diff --git a/module/Core/test/Entity/VisitLocationTest.php b/module/Core/test/Entity/VisitLocationTest.php index 2ea4befa..f662645d 100644 --- a/module/Core/test/Entity/VisitLocationTest.php +++ b/module/Core/test/Entity/VisitLocationTest.php @@ -10,16 +10,6 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitLocationTest extends TestCase { - /** @test */ - public function valuesFoundWhenExchangingArrayAreCastToString(): void - { - $payload = new Location('', '', '', '', 1000.7, -2000.4, ''); - $location = new VisitLocation($payload); - - $this->assertSame('1000.7', $location->getLatitude()); - $this->assertSame('-2000.4', $location->getLongitude()); - } - /** * @test * @dataProvider provideArgs diff --git a/module/Core/test/Entity/VisitTest.php b/module/Core/test/Entity/VisitTest.php index ff2c4cec..9af71a09 100644 --- a/module/Core/test/Entity/VisitTest.php +++ b/module/Core/test/Entity/VisitTest.php @@ -25,9 +25,6 @@ class VisitTest extends TestCase 'date' => ($date ?? $visit->getDate())->toAtomString(), 'userAgent' => 'Chrome', 'visitLocation' => null, - - // Deprecated - 'remoteAddr' => null, ], $visit->jsonSerialize()); } diff --git a/module/PreviewGenerator/config/dependencies.config.php b/module/PreviewGenerator/config/dependencies.config.php deleted file mode 100644 index dc7ceb96..00000000 --- a/module/PreviewGenerator/config/dependencies.config.php +++ /dev/null @@ -1,27 +0,0 @@ - [ - 'factories' => [ - Image\ImageBuilder::class => Image\ImageBuilderFactory::class, - Service\PreviewGenerator::class => ConfigAbstractFactory::class, - ], - ], - - ConfigAbstractFactory::class => [ - Service\PreviewGenerator::class => [ - Image\ImageBuilder::class, - Filesystem::class, - 'config.preview_generation.files_location', - ], - ], - -]; diff --git a/module/PreviewGenerator/src/ConfigProvider.php b/module/PreviewGenerator/src/ConfigProvider.php deleted file mode 100644 index 1bc2f7c5..00000000 --- a/module/PreviewGenerator/src/ConfigProvider.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - Image::class => ImageFactory::class, - ]]); - } -} diff --git a/module/PreviewGenerator/src/Image/ImageBuilderInterface.php b/module/PreviewGenerator/src/Image/ImageBuilderInterface.php deleted file mode 100644 index d11daab0..00000000 --- a/module/PreviewGenerator/src/Image/ImageBuilderInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -get('config')['wkhtmltopdf']; - $image = new Image($config['images'] ?? null); - - if ($options['url'] ?? null) { - $image->setPage($options['url']); - } - - return $image; - } -} diff --git a/module/PreviewGenerator/src/Service/PreviewGenerator.php b/module/PreviewGenerator/src/Service/PreviewGenerator.php deleted file mode 100644 index 54e967aa..00000000 --- a/module/PreviewGenerator/src/Service/PreviewGenerator.php +++ /dev/null @@ -1,58 +0,0 @@ -location = $location; - $this->imageBuilder = $imageBuilder; - $this->filesystem = $filesystem; - } - - /** - * Generates and stores preview for provided website and returns the path to the image file - * - * @throws PreviewGenerationException - */ - public function generatePreview(string $url): string - { - $image = $this->imageBuilder->build(Image::class, ['url' => $url]); - - // If the file already exists, return its path - $cacheId = sprintf('preview_%s.%s', urlencode($url), $image->type); - $path = $this->location . '/' . $cacheId; - if ($this->filesystem->exists($path)) { - return $path; - } - - // Save and check if an error occurred - $image->saveAs($path); - $error = $image->getError(); - if (! empty($error)) { - throw PreviewGenerationException::fromImageError($error); - } - - // Cache the path and return it - return $path; - } -} diff --git a/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php b/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php deleted file mode 100644 index 26aefda0..00000000 --- a/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -configProvider = new ConfigProvider(); - } - - /** @test */ - public function configIsReturned(): void - { - $config = ($this->configProvider)(); - - $this->assertArrayHasKey('dependencies', $config); - } -} diff --git a/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php b/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php deleted file mode 100644 index 0319dafc..00000000 --- a/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php +++ /dev/null @@ -1,28 +0,0 @@ -factory = new ImageBuilderFactory(); - } - - /** @test */ - public function serviceIsCreated() - { - $instance = $this->factory->__invoke(new ServiceManager(), ''); - $this->assertInstanceOf(ImageBuilder::class, $instance); - } -} diff --git a/module/PreviewGenerator/test/Image/ImageFactoryTest.php b/module/PreviewGenerator/test/Image/ImageFactoryTest.php deleted file mode 100644 index e9b9fc8e..00000000 --- a/module/PreviewGenerator/test/Image/ImageFactoryTest.php +++ /dev/null @@ -1,54 +0,0 @@ -factory = new ImageFactory(); - } - - /** @test */ - public function noPageIsSetWhenOptionsAreNotProvided() - { - /** @var Image $image */ - $image = $this->factory->__invoke(new ServiceManager(['services' => [ - 'config' => ['wkhtmltopdf' => []], - ]]), ''); - $this->assertInstanceOf(Image::class, $image); - - $ref = new ReflectionObject($image); - $page = $ref->getProperty('_page'); - $page->setAccessible(true); - $this->assertNull($page->getValue($image)); - } - - /** @test */ - public function aPageIsSetWhenOptionsIncludeTheUrl() - { - $expectedPage = 'foo/bar.html'; - - /** @var Image $image */ - $image = $this->factory->__invoke(new ServiceManager(['services' => [ - 'config' => ['wkhtmltopdf' => []], - ]]), '', ['url' => $expectedPage]); - $this->assertInstanceOf(Image::class, $image); - - $ref = new ReflectionObject($image); - $page = $ref->getProperty('_page'); - $page->setAccessible(true); - $this->assertEquals($expectedPage, $page->getValue($image)); - } -} diff --git a/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php b/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php deleted file mode 100644 index 035a976b..00000000 --- a/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php +++ /dev/null @@ -1,85 +0,0 @@ -image = $this->prophesize(Image::class); - $this->filesystem = $this->prophesize(Filesystem::class); - - $this->generator = new PreviewGenerator(new ImageBuilder(new ServiceManager(), [ - 'factories' => [ - Image::class => function () { - return $this->image->reveal(); - }, - ], - ]), $this->filesystem->reveal(), 'dir'); - } - - /** @test */ - public function alreadyProcessedElementsAreNotProcessed() - { - $url = 'http://foo.com'; - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(true) - ->shouldBeCalledOnce(); - $this->image->saveAs(Argument::cetera())->shouldBeCalledTimes(0); - $this->assertEquals(sprintf('dir/preview_%s.png', urlencode($url)), $this->generator->generatePreview($url)); - } - - /** @test */ - public function nonProcessedElementsAreProcessed() - { - $url = 'http://foo.com'; - $cacheId = sprintf('preview_%s.png', urlencode($url)); - $expectedPath = 'dir/' . $cacheId; - - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false) - ->shouldBeCalledOnce(); - - $this->image->saveAs($expectedPath)->shouldBeCalledOnce(); - $this->image->getError()->willReturn('')->shouldBeCalledOnce(); - $this->assertEquals($expectedPath, $this->generator->generatePreview($url)); - } - - /** @test */ - public function errorWhileGeneratingPreviewThrowsException() - { - $url = 'http://foo.com'; - $cacheId = sprintf('preview_%s.png', urlencode($url)); - $expectedPath = 'dir/' . $cacheId; - - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false) - ->shouldBeCalledOnce(); - - $this->image->saveAs($expectedPath)->shouldBeCalledOnce(); - $this->image->getError()->willReturn('Error!!')->shouldBeCalledOnce(); - - $this->expectException(PreviewGenerationException::class); - - $this->generator->generatePreview($url); - } -} diff --git a/module/Rest/config/auth.config.php b/module/Rest/config/auth.config.php index 9d0987e0..f04337bf 100644 --- a/module/Rest/config/auth.config.php +++ b/module/Rest/config/auth.config.php @@ -10,7 +10,6 @@ return [ 'auth' => [ 'routes_whitelist' => [ - Action\AuthenticateAction::class, Action\HealthAction::class, Action\ShortUrl\SingleStepCreateShortUrlAction::class, ], @@ -18,13 +17,10 @@ return [ 'plugins' => [ 'factories' => [ Authentication\Plugin\ApiKeyHeaderPlugin::class => ConfigAbstractFactory::class, - Authentication\Plugin\AuthorizationHeaderPlugin::class => ConfigAbstractFactory::class, ], 'aliases' => [ Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME => Authentication\Plugin\ApiKeyHeaderPlugin::class, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME => - Authentication\Plugin\AuthorizationHeaderPlugin::class, ], ], ], @@ -40,7 +36,6 @@ return [ ], ConfigAbstractFactory::class => [ - Authentication\Plugin\AuthorizationHeaderPlugin::class => [Authentication\JWTService::class], Authentication\Plugin\ApiKeyHeaderPlugin::class => [Service\ApiKeyService::class], Authentication\RequestToHttpAuthPlugin::class => [Authentication\AuthenticationPluginManager::class], diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index ace2fda7..953d212a 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -17,10 +17,8 @@ return [ 'dependencies' => [ 'factories' => [ - Authentication\JWTService::class => ConfigAbstractFactory::class, ApiKeyService::class => ConfigAbstractFactory::class, - Action\AuthenticateAction::class => ConfigAbstractFactory::class, Action\HealthAction::class => ConfigAbstractFactory::class, Action\ShortUrl\CreateShortUrlAction::class => ConfigAbstractFactory::class, Action\ShortUrl\SingleStepCreateShortUrlAction::class => ConfigAbstractFactory::class, @@ -38,18 +36,13 @@ return [ ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class, Middleware\BodyParserMiddleware::class => InvokableFactory::class, Middleware\CrossDomainMiddleware::class => InvokableFactory::class, - Middleware\PathVersionMiddleware::class => InvokableFactory::class, - Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => ConfigAbstractFactory::class, Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class => InvokableFactory::class, - Middleware\ShortUrl\ShortCodePathMiddleware::class => InvokableFactory::class, ], ], ConfigAbstractFactory::class => [ - Authentication\JWTService::class => [AppOptions::class], ApiKeyService::class => ['em'], - Action\AuthenticateAction::class => [ApiKeyService::class, Authentication\JWTService::class, 'Logger_Shlink'], Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'], Action\ShortUrl\CreateShortUrlAction::class => [ Service\UrlShortener::class, @@ -76,10 +69,6 @@ return [ Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, LoggerInterface::class], - - Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => [ - 'config.backwards_compatible_problem_details.json_flags', - ], ], ]; diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 3583a091..d210f13b 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest; return [ 'routes' => [ - Action\AuthenticateAction::getRouteDef(), Action\HealthAction::getRouteDef(), // Short codes diff --git a/module/Rest/src/Action/AuthenticateAction.php b/module/Rest/src/Action/AuthenticateAction.php deleted file mode 100644 index b0919aae..00000000 --- a/module/Rest/src/Action/AuthenticateAction.php +++ /dev/null @@ -1,64 +0,0 @@ -apiKeyService = $apiKeyService; - $this->jwtService = $jwtService; - } - - /** - * @param Request $request - * @return Response - * @throws \InvalidArgumentException - */ - public function handle(Request $request): Response - { - $authData = $request->getParsedBody(); - if (! isset($authData['apiKey'])) { - return new JsonResponse([ - 'error' => 'INVALID_ARGUMENT', - 'message' => 'You have to provide a valid API key under the "apiKey" param name.', - ], self::STATUS_BAD_REQUEST); - } - - // Authenticate using provided API key - $apiKey = $this->apiKeyService->getByKey($authData['apiKey']); - if ($apiKey === null || ! $apiKey->isValid()) { - return new JsonResponse([ - 'error' => 'INVALID_API_KEY', - 'message' => 'Provided API key does not exist or is invalid.', - ], self::STATUS_UNAUTHORIZED); - } - - // Generate a JSON Web Token that will be used for authorization in next requests - $token = $this->jwtService->create($apiKey); - return new JsonResponse(['token' => $token]); - } -} diff --git a/module/Rest/src/Authentication/JWTService.php b/module/Rest/src/Authentication/JWTService.php deleted file mode 100644 index 77ec6728..00000000 --- a/module/Rest/src/Authentication/JWTService.php +++ /dev/null @@ -1,111 +0,0 @@ -appOptions = $appOptions; - } - - /** - * Creates a new JSON web token por provided API key - * - * @param ApiKey $apiKey - * @param int $lifetime - * @return string - */ - public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string - { - $currentTimestamp = time(); - - return $this->encode([ - 'iss' => (string) $this->appOptions, - 'iat' => $currentTimestamp, - 'exp' => $currentTimestamp + $lifetime, - 'sub' => 'auth', - 'key' => $apiKey->getId(), // The ID is opaque. Returning the key would be insecure - ]); - } - - /** - * Refreshes a token and returns it with the new expiration - * - * @param string $jwt - * @param int $lifetime - * @return string - * @throws AuthenticationException If the token has expired - */ - public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string - { - $payload = $this->getPayload($jwt); - $payload['exp'] = time() + $lifetime; - return $this->encode($payload); - } - - /** - * Verifies that certain JWT is valid - * - * @param string $jwt - * @return bool - */ - public function verify(string $jwt): bool - { - try { - // If no exception is thrown while decoding the token, it is considered valid - $this->decode($jwt); - return true; - } catch (UnexpectedValueException $e) { - return false; - } - } - - /** - * Decodes certain token and returns the payload - * - * @param string $jwt - * @return array - * @throws AuthenticationException If the token has expired - */ - public function getPayload(string $jwt): array - { - try { - return $this->decode($jwt); - } catch (UnexpectedValueException $e) { - throw AuthenticationException::expiredJWT($e); - } - } - - /** - * @param array $data - * @return string - */ - private function encode(array $data): string - { - return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG); - } - - /** - * @param string $jwt - * @return array - */ - private function decode(string $jwt): array - { - return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]); - } -} diff --git a/module/Rest/src/Authentication/JWTServiceInterface.php b/module/Rest/src/Authentication/JWTServiceInterface.php deleted file mode 100644 index 0ce840f1..00000000 --- a/module/Rest/src/Authentication/JWTServiceInterface.php +++ /dev/null @@ -1,50 +0,0 @@ -jwtService = $jwtService; - } - - /** - * @throws VerifyAuthenticationException - */ - public function verify(ServerRequestInterface $request): void - { - // Get token making sure the an authorization type is provided - $authToken = $request->getHeaderLine(self::HEADER_NAME); - $authTokenParts = explode(' ', $authToken); - if (count($authTokenParts) === 1) { - throw VerifyAuthenticationException::forMissingAuthType(); - } - - // Make sure the authorization type is Bearer - [$authType, $jwt] = $authTokenParts; - if (strtolower($authType) !== 'bearer') { - throw VerifyAuthenticationException::forInvalidAuthType($authType); - } - - try { - if (! $this->jwtService->verify($jwt)) { - throw $this->createInvalidTokenError(); - } - } catch (Throwable $e) { - throw $this->createInvalidTokenError(); - } - } - - private function createInvalidTokenError(): VerifyAuthenticationException - { - return VerifyAuthenticationException::forInvalidAuthToken(); - } - - public function update(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface - { - $authToken = $request->getHeaderLine(self::HEADER_NAME); - [, $jwt] = explode(' ', $authToken); - $jwt = $this->jwtService->refresh($jwt); - - return $response->withHeader(self::HEADER_NAME, sprintf('Bearer %s', $jwt)); - } -} diff --git a/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php b/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php index bb6454b2..91461704 100644 --- a/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php +++ b/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php @@ -17,7 +17,6 @@ class RequestToHttpAuthPlugin implements RequestToHttpAuthPluginInterface // When more than one is matched, the first one to be found will take precedence. public const SUPPORTED_AUTH_HEADERS = [ Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Plugin\AuthorizationHeaderPlugin::HEADER_NAME, ]; private AuthenticationPluginManagerInterface $authPluginManager; diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index 8624ad66..6352e3c7 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -12,6 +12,7 @@ use function sprintf; class ConfigProvider { private const ROUTES_PREFIX = '/rest/v{version:1|2}'; + private const UNVERSIONED_ROUTES_PREFIX = '/rest'; private Closure $loadConfig; @@ -33,7 +34,11 @@ class ConfigProvider // Prepend the routes prefix to every path foreach ($routes as $key => $route) { ['path' => $path] = $route; - $routes[$key]['path'] = sprintf('%s%s', self::ROUTES_PREFIX, $path); + $routes[$key]['path'] = sprintf( + '%s%s', + $path === '/health' ? self::UNVERSIONED_ROUTES_PREFIX : self::ROUTES_PREFIX, + $path, + ); } return $config; diff --git a/module/Rest/src/Exception/AuthenticationException.php b/module/Rest/src/Exception/AuthenticationException.php deleted file mode 100644 index 3e60edb6..00000000 --- a/module/Rest/src/Exception/AuthenticationException.php +++ /dev/null @@ -1,16 +0,0 @@ -detail = $e->getMessage(); - $e->title = 'Invalid auth token'; - $e->type = 'INVALID_AUTH_TOKEN'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } - - /** @deprecated */ - public static function forMissingAuthType(): self - { - $e = new self('You need to provide the Bearer type in the Authorization header.'); - - $e->detail = $e->getMessage(); - $e->title = 'Invalid authorization'; - $e->type = 'INVALID_AUTHORIZATION'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } - - /** @deprecated */ - public static function forInvalidAuthType(string $providedType): self - { - $e = new self(sprintf('Provided authorization type %s is not supported. Use Bearer instead.', $providedType)); - - $e->detail = $e->getMessage(); - $e->title = 'Invalid authorization'; - $e->type = 'INVALID_AUTHORIZATION'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } } diff --git a/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php b/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php deleted file mode 100644 index 11fce5c9..00000000 --- a/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php +++ /dev/null @@ -1,63 +0,0 @@ - 'type', - 'message' => 'detail', - ]; - - private int $jsonFlags; - - public function __construct(int $jsonFlags) - { - $this->jsonFlags = $jsonFlags; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $resp = $handler->handle($request); - if ($resp->getHeaderLine('Content-type') !== 'application/problem+json' || ! $this->isVersionOne($request)) { - return $resp; - } - - try { - $body = (string) $resp->getBody(); - $payload = $this->makePayloadBackwardsCompatible(json_decode($body)); - } catch (Throwable $e) { - return $resp; - } - - return new JsonResponse($payload, $resp->getStatusCode(), $resp->getHeaders(), $this->jsonFlags); - } - - private function isVersionOne(ServerRequestInterface $request): bool - { - $path = $request->getUri()->getPath(); - return strpos($path, '/v') === false || strpos($path, '/v1') === 0; - } - - private function makePayloadBackwardsCompatible(array $payload): array - { - return reduce_left(self::BACKWARDS_COMPATIBLE_FIELDS, function (string $newKey, string $oldKey, $c, $acc) { - $acc[$oldKey] = $acc[$newKey]; - return $acc; - }, $payload); - } -} diff --git a/module/Rest/src/Middleware/CrossDomainMiddleware.php b/module/Rest/src/Middleware/CrossDomainMiddleware.php index aacda9fc..ef98eb92 100644 --- a/module/Rest/src/Middleware/CrossDomainMiddleware.php +++ b/module/Rest/src/Middleware/CrossDomainMiddleware.php @@ -27,7 +27,6 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa $response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin')) ->withHeader('Access-Control-Expose-Headers', implode(', ', [ Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, ])); if ($request->getMethod() !== self::METHOD_OPTIONS) { return $response; diff --git a/module/Rest/src/Middleware/PathVersionMiddleware.php b/module/Rest/src/Middleware/PathVersionMiddleware.php deleted file mode 100644 index 84921710..00000000 --- a/module/Rest/src/Middleware/PathVersionMiddleware.php +++ /dev/null @@ -1,30 +0,0 @@ -getUri(); - $path = $uri->getPath(); - - // If the path does not begin with the version number, prepend v1 by default for BC purposes - if (strpos($path, '/v') !== 0) { - $request = $request->withUri($uri->withPath('/v1' . $uri->getPath())); - } - - return $handler->handle($request); - } -} diff --git a/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php b/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php deleted file mode 100644 index 683c3af5..00000000 --- a/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php +++ /dev/null @@ -1,34 +0,0 @@ -getUri(); - $path = $uri->getPath(); - - // If the path starts with the old prefix, replace it by the new one - return $handler->handle( - $request->withUri($uri->withPath(str_replace(self::OLD_PATH_PREFIX, self::NEW_PATH_PREFIX, $path))) - ); - } -} diff --git a/module/Rest/test-api/Action/CreateShortUrlActionTest.php b/module/Rest/test-api/Action/CreateShortUrlActionTest.php index efff5f33..9ec8e8e2 100644 --- a/module/Rest/test-api/Action/CreateShortUrlActionTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlActionTest.php @@ -49,9 +49,7 @@ class CreateShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals($detail, $payload['detail']); - $this->assertEquals($detail, $payload['message']); // Deprecated $this->assertEquals('INVALID_SLUG', $payload['type']); - $this->assertEquals('INVALID_SLUG', $payload['error']); // Deprecated $this->assertEquals('Invalid custom slug', $payload['title']); $this->assertEquals($slug, $payload['customSlug']); @@ -215,7 +213,7 @@ class CreateShortUrlActionTest extends ApiTestCase } /** @test */ - public function failsToCreateShortUrlWithInvalidOriginalUrl(): void + public function failsToCreateShortUrlWithInvalidLongUrl(): void { $url = 'https://this-has-to-be-invalid.com'; $expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url); @@ -225,9 +223,7 @@ class CreateShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_URL', $payload['type']); - $this->assertEquals('INVALID_URL', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid URL', $payload['title']); $this->assertEquals($url, $payload['url']); } diff --git a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php index 4680a6f6..7bf01c51 100644 --- a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php +++ b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php @@ -19,9 +19,7 @@ class DeleteShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } @@ -41,9 +39,7 @@ class DeleteShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']); $this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Cannot delete short URL', $payload['title']); } } diff --git a/module/Rest/test-api/Action/EditShortUrlActionTest.php b/module/Rest/test-api/Action/EditShortUrlActionTest.php index bbcb043a..024ffb79 100644 --- a/module/Rest/test-api/Action/EditShortUrlActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlActionTest.php @@ -20,9 +20,7 @@ class EditShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } @@ -40,9 +38,7 @@ class EditShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } } diff --git a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php index e2922092..d48ffc5f 100644 --- a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php @@ -20,9 +20,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } @@ -39,9 +37,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/GetVisitsActionTest.php b/module/Rest/test-api/Action/GetVisitsActionTest.php index 0db06848..f6167f78 100644 --- a/module/Rest/test-api/Action/GetVisitsActionTest.php +++ b/module/Rest/test-api/Action/GetVisitsActionTest.php @@ -19,9 +19,7 @@ class GetVisitsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 0952a627..f8d8c542 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -24,7 +24,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => 'https://shlink.io', ]; private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [ 'shortCode' => 'custom-with-domain', @@ -38,7 +37,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => 'https://google.com', ]; private const SHORT_URL_META = [ 'shortCode' => 'def456', @@ -54,9 +52,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => - 'https://blog.alejandrocelaya.com/2017/12/09' - . '/acmailer-7-0-the-most-important-release-in-a-long-time/', ]; private const SHORT_URL_CUSTOM_SLUG = [ 'shortCode' => 'custom', @@ -70,7 +65,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => 2, ], - 'originalUrl' => 'https://shlink.io', ]; private const SHORT_URL_CUSTOM_DOMAIN = [ 'shortCode' => 'ghi789', @@ -86,9 +80,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => - 'https://blog.alejandrocelaya.com/2019/04/27' - . '/considerations-to-properly-use-open-source-software-projects/', ]; /** diff --git a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php index e4d45f4a..09f48113 100644 --- a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php +++ b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php @@ -19,9 +19,7 @@ class ResolveShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/UpdateTagActionTest.php b/module/Rest/test-api/Action/UpdateTagActionTest.php index fa07fcd1..eb70685a 100644 --- a/module/Rest/test-api/Action/UpdateTagActionTest.php +++ b/module/Rest/test-api/Action/UpdateTagActionTest.php @@ -23,9 +23,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } @@ -50,9 +48,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('TAG_NOT_FOUND', $payload['type']); - $this->assertEquals('TAG_NOT_FOUND', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Tag not found', $payload['title']); } @@ -70,9 +66,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode()); $this->assertEquals(self::STATUS_CONFLICT, $payload['status']); $this->assertEquals('TAG_CONFLICT', $payload['type']); - $this->assertEquals('TAG_CONFLICT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Tag conflict', $payload['title']); } diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php index d92f4a44..58ab396d 100644 --- a/module/Rest/test-api/Middleware/AuthenticationTest.php +++ b/module/Rest/test-api/Middleware/AuthenticationTest.php @@ -21,15 +21,13 @@ class AuthenticationTest extends ApiTestCase implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) ); - $resp = $this->callApi(self::METHOD_GET, '/short-codes'); + $resp = $this->callApi(self::METHOD_GET, '/short-urls'); $payload = $this->getJsonResponsePayload($resp); $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); $this->assertEquals('INVALID_AUTHORIZATION', $payload['type']); - $this->assertEquals('INVALID_AUTHORIZATION', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid authorization', $payload['title']); } @@ -41,7 +39,7 @@ class AuthenticationTest extends ApiTestCase { $expectedDetail = 'Provided API key does not exist or is invalid.'; - $resp = $this->callApi(self::METHOD_GET, '/short-codes', [ + $resp = $this->callApi(self::METHOD_GET, '/short-urls', [ 'headers' => [ Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey, ], @@ -51,9 +49,7 @@ class AuthenticationTest extends ApiTestCase $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); $this->assertEquals('INVALID_API_KEY', $payload['type']); - $this->assertEquals('INVALID_API_KEY', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid API key', $payload['title']); } @@ -63,53 +59,4 @@ class AuthenticationTest extends ApiTestCase yield 'key which is expired' => ['expired_api_key']; yield 'key which is disabled' => ['disabled_api_key']; } - - /** - * @test - * @dataProvider provideInvalidAuthorizations - */ - public function authorizationErrorIsReturnedIfInvalidDataIsProvided( - string $authValue, - string $expectedDetail, - string $expectedType, - string $expectedTitle - ): void { - $resp = $this->callApi(self::METHOD_GET, '/short-codes', [ - 'headers' => [ - Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue, - ], - ]); - $payload = $this->getJsonResponsePayload($resp); - - $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); - $this->assertEquals($expectedType, $payload['type']); - $this->assertEquals($expectedType, $payload['error']); // Deprecated - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated - $this->assertEquals($expectedTitle, $payload['title']); - } - - public function provideInvalidAuthorizations(): iterable - { - yield 'no type' => [ - 'invalid', - 'You need to provide the Bearer type in the Authorization header.', - 'INVALID_AUTHORIZATION', - 'Invalid authorization', - ]; - yield 'invalid type' => [ - 'Basic invalid', - 'Provided authorization type Basic is not supported. Use Bearer instead.', - 'INVALID_AUTHORIZATION', - 'Invalid authorization', - ]; - yield 'invalid JWT' => [ - 'Bearer invalid', - 'Missing or invalid auth token provided. Perform a new authentication request and send provided ' - . 'token on every new request on the Authorization header', - 'INVALID_AUTH_TOKEN', - 'Invalid auth token', - ]; - } } diff --git a/module/Rest/test/Action/AuthenticateActionTest.php b/module/Rest/test/Action/AuthenticateActionTest.php deleted file mode 100644 index 84aa8fbe..00000000 --- a/module/Rest/test/Action/AuthenticateActionTest.php +++ /dev/null @@ -1,72 +0,0 @@ -apiKeyService = $this->prophesize(ApiKeyService::class); - $this->jwtService = $this->prophesize(JWTService::class); - $this->jwtService->create(Argument::cetera())->willReturn(''); - - $this->action = new AuthenticateAction($this->apiKeyService->reveal(), $this->jwtService->reveal()); - } - - /** @test */ - public function notProvidingAuthDataReturnsError() - { - $resp = $this->action->handle(new ServerRequest()); - $this->assertEquals(400, $resp->getStatusCode()); - } - - /** @test */ - public function properApiKeyReturnsTokenInResponse() - { - $this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->setId('5')) - ->shouldBeCalledOnce(); - - $request = (new ServerRequest())->withParsedBody([ - 'apiKey' => 'foo', - ]); - $response = $this->action->handle($request); - $this->assertEquals(200, $response->getStatusCode()); - - $response->getBody()->rewind(); - $this->assertTrue(strpos($response->getBody()->getContents(), '"token"') > 0); - } - - /** @test */ - public function invalidApiKeyReturnsErrorResponse() - { - $this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->disable()) - ->shouldBeCalledOnce(); - - $request = (new ServerRequest())->withParsedBody([ - 'apiKey' => 'foo', - ]); - $response = $this->action->handle($request); - $this->assertEquals(401, $response->getStatusCode()); - } -} diff --git a/module/Rest/test/Authentication/JWTServiceTest.php b/module/Rest/test/Authentication/JWTServiceTest.php deleted file mode 100644 index 76375fdc..00000000 --- a/module/Rest/test/Authentication/JWTServiceTest.php +++ /dev/null @@ -1,86 +0,0 @@ -service = new JWTService(new AppOptions([ - 'name' => 'ShlinkTest', - 'version' => '10000.3.1', - 'secret_key' => 'foo', - ])); - } - - /** @test */ - public function tokenIsProperlyCreated() - { - $id = '34'; - $token = $this->service->create((new ApiKey())->setId($id)); - $payload = (array) JWT::decode($token, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]); - $this->assertGreaterThanOrEqual($payload['iat'], time()); - $this->assertGreaterThan(time(), $payload['exp']); - $this->assertEquals($id, $payload['key']); - $this->assertEquals('auth', $payload['sub']); - $this->assertEquals('ShlinkTest:v10000.3.1', $payload['iss']); - } - - /** @test */ - public function refreshIncreasesExpiration() - { - $originalLifetime = 10; - $newLifetime = 30; - $originalPayload = ['exp' => time() + $originalLifetime]; - $token = JWT::encode($originalPayload, 'foo'); - $newToken = $this->service->refresh($token, $newLifetime); - $newPayload = (array) JWT::decode($newToken, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]); - - $this->assertGreaterThan($originalPayload['exp'], $newPayload['exp']); - } - - /** @test */ - public function verifyReturnsTrueWhenTheTokenIsCorrect() - { - $this->assertTrue($this->service->verify(JWT::encode([], 'foo'))); - } - - /** @test */ - public function verifyReturnsFalseWhenTheTokenIsCorrect() - { - $this->assertFalse($this->service->verify('invalidToken')); - } - - /** @test */ - public function getPayloadWorksWithCorrectTokens() - { - $originalPayload = [ - 'exp' => time() + 10, - 'sub' => 'testing', - ]; - $token = JWT::encode($originalPayload, 'foo'); - $this->assertEquals($originalPayload, $this->service->getPayload($token)); - } - - /** @test */ - public function getPayloadThrowsExceptionWithIncorrectTokens() - { - $this->expectException(AuthenticationException::class); - $this->service->getPayload('invalidToken'); - } -} diff --git a/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php deleted file mode 100644 index 71d6eb20..00000000 --- a/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php +++ /dev/null @@ -1,119 +0,0 @@ -jwtService = $this->prophesize(JWTServiceInterface::class); - $this->plugin = new AuthorizationHeaderPlugin($this->jwtService->reveal()); - } - - /** @test */ - public function verifyAnAuthorizationWithoutBearerTypeThrowsException() - { - $authToken = 'ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage(sprintf( - 'You need to provide the Bearer type in the %s header.', - AuthorizationHeaderPlugin::HEADER_NAME - )); - - $this->plugin->verify($request); - } - - /** @test */ - public function verifyAnAuthorizationWithWrongTypeThrowsException() - { - $authToken = 'Basic ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage( - 'Provided authorization type Basic is not supported. Use Bearer instead.' - ); - - $this->plugin->verify($request); - } - - /** @test */ - public function verifyAnExpiredTokenThrowsException() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(false); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage(sprintf( - 'Missing or invalid auth token provided. Perform a new authentication request and send provided ' - . 'token on every new request on the %s header', - AuthorizationHeaderPlugin::HEADER_NAME - )); - - $this->plugin->verify($request); - - $jwtVerify->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function verifyValidTokenDoesNotThrowException() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(true); - - $this->plugin->verify($request); - - $jwtVerify->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function updateReturnsAnUpdatedResponseWithNewJwt() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtRefresh = $this->jwtService->refresh('ABC-abc')->willReturn('DEF-def'); - - $response = $this->plugin->update($request, new Response()); - - $this->assertTrue($response->hasHeader(AuthorizationHeaderPlugin::HEADER_NAME)); - $this->assertEquals('Bearer DEF-def', $response->getHeaderLine(AuthorizationHeaderPlugin::HEADER_NAME)); - $jwtRefresh->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Rest/test/Authentication/RequestToAuthPluginTest.php b/module/Rest/test/Authentication/RequestToAuthPluginTest.php index d4de01a8..8b52f408 100644 --- a/module/Rest/test/Authentication/RequestToAuthPluginTest.php +++ b/module/Rest/test/Authentication/RequestToAuthPluginTest.php @@ -9,7 +9,6 @@ use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerInterface; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; -use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthorizationHeaderPlugin; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin; use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException; use Zend\Diactoros\ServerRequest; @@ -63,14 +62,7 @@ class RequestToAuthPluginTest extends TestCase public function provideHeaders(): iterable { - yield 'API key header only' => [[ - ApiKeyHeaderPlugin::HEADER_NAME => 'foobar', - ], ApiKeyHeaderPlugin::HEADER_NAME]; - yield 'Authorization header only' => [[ - AuthorizationHeaderPlugin::HEADER_NAME => 'foobar', - ], AuthorizationHeaderPlugin::HEADER_NAME]; - yield 'Both headers' => [[ - AuthorizationHeaderPlugin::HEADER_NAME => 'foobar', + yield 'API key header' => [[ ApiKeyHeaderPlugin::HEADER_NAME => 'foobar', ], ApiKeyHeaderPlugin::HEADER_NAME]; } diff --git a/module/Rest/test/ConfigProviderTest.php b/module/Rest/test/ConfigProviderTest.php index b8cebf93..80919c30 100644 --- a/module/Rest/test/ConfigProviderTest.php +++ b/module/Rest/test/ConfigProviderTest.php @@ -33,6 +33,7 @@ class ConfigProviderTest extends TestCase ['path' => '/foo'], ['path' => '/bar'], ['path' => '/baz/foo'], + ['path' => '/health'], ], ]); @@ -42,6 +43,7 @@ class ConfigProviderTest extends TestCase ['path' => '/rest/v{version:1|2}/foo'], ['path' => '/rest/v{version:1|2}/bar'], ['path' => '/rest/v{version:1|2}/baz/foo'], + ['path' => '/rest/health'], ], $config['routes']); } } diff --git a/module/Rest/test/Exception/AuthenticationExceptionTest.php b/module/Rest/test/Exception/AuthenticationExceptionTest.php deleted file mode 100644 index da389acb..00000000 --- a/module/Rest/test/Exception/AuthenticationExceptionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -assertEquals($prev, $e->getPrevious()); - $this->assertEquals(-1, $e->getCode()); - $this->assertEquals('The token has expired.', $e->getMessage()); - } - - public function providePrev(): iterable - { - yield 'with previous exception' => [new Exception('Prev')]; - yield 'without previous exception' => [null]; - } -} diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index 8bef98a1..1d306fdc 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -13,7 +13,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; -use Shlinkio\Shlink\Rest\Action\AuthenticateAction; +use Shlinkio\Shlink\Rest\Action\HealthAction; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; @@ -37,7 +37,7 @@ class AuthenticationMiddlewareTest extends TestCase $this->middleware = new AuthenticationMiddleware( $this->requestToPlugin->reveal(), - [AuthenticateAction::class], + [HealthAction::class], $this->logger->reveal() ); } @@ -72,7 +72,7 @@ class AuthenticationMiddlewareTest extends TestCase yield 'with whitelisted route' => [(new ServerRequest())->withAttribute( RouteResult::class, RouteResult::fromRoute( - new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, AuthenticateAction::class) + new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, HealthAction::class) ) )]; yield 'with OPTIONS method' => [(new ServerRequest())->withAttribute( diff --git a/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php b/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php deleted file mode 100644 index 20a5b04d..00000000 --- a/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php +++ /dev/null @@ -1,94 +0,0 @@ -handler = $this->prophesize(RequestHandlerInterface::class); - $this->middleware = new BackwardsCompatibleProblemDetailsMiddleware(0); - } - - /** - * @test - * @dataProvider provideNonProcessableResponses - */ - public function nonProblemDetailsOrInvalidResponsesAreReturnedAsTheyAre( - Response $response, - ?ServerRequest $request = null - ): void { - $request = $request ?? ServerRequestFactory::fromGlobals(); - $handle = $this->handler->handle($request)->willReturn($response); - - $result = $this->middleware->process($request, $this->handler->reveal()); - - $this->assertSame($response, $result); - $handle->shouldHaveBeenCalledOnce(); - } - - public function provideNonProcessableResponses(): iterable - { - yield 'no problem details' => [new Response()]; - yield 'invalid JSON' => [(new Response('data://text/plain,{invalid-json'))->withHeader( - 'Content-Type', - 'application/problem+json' - )]; - yield 'version 2' => [ - (new Response())->withHeader('Content-type', 'application/problem+json'), - ServerRequestFactory::fromGlobals()->withUri(new Uri('/v2/something')), - ]; - } - - /** - * @test - * @dataProvider provideRequestPath - */ - public function mapsDeprecatedPropertiesWhenRequestIsPerformedToVersionOne(string $requestPath): void - { - $request = ServerRequestFactory::fromGlobals()->withUri(new Uri($requestPath)); - $response = $this->jsonResponse([ - 'type' => 'the_type', - 'detail' => 'the_detail', - ]); - $handle = $this->handler->handle($request)->willReturn($response); - - /** @var Response\JsonResponse $result */ - $result = $this->middleware->process($request, $this->handler->reveal()); - $payload = $result->getPayload(); - - $this->assertEquals([ - 'type' => 'the_type', - 'detail' => 'the_detail', - 'error' => 'the_type', - 'message' => 'the_detail', - ], $payload); - $handle->shouldHaveBeenCalledOnce(); - } - - public function provideRequestPath(): iterable - { - yield 'no version' => ['/foo']; - yield 'version one' => ['/v1/foo']; - } - - private function jsonResponse(array $payload, int $status = 200): Response\JsonResponse - { - $headers = ['Content-Type' => 'application/problem+json']; - return new Response\JsonResponse($payload, $status, $headers); - } -} diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 735338ca..28a85010 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -15,7 +15,6 @@ use Zend\Diactoros\ServerRequest; use Zend\Expressive\Router\Route; use Zend\Expressive\Router\RouteResult; -use function implode; use function Zend\Stratigility\middleware; class CrossDomainMiddlewareTest extends TestCase @@ -62,10 +61,10 @@ class CrossDomainMiddlewareTest extends TestCase $headers = $response->getHeaders(); $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals(implode(', ', [ + $this->assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, - ]), $response->getHeaderLine('Access-Control-Expose-Headers')); + $response->getHeaderLine('Access-Control-Expose-Headers') + ); $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers); $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); @@ -87,10 +86,10 @@ class CrossDomainMiddlewareTest extends TestCase $headers = $response->getHeaders(); $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals(implode(', ', [ + $this->assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, - ]), $response->getHeaderLine('Access-Control-Expose-Headers')); + $response->getHeaderLine('Access-Control-Expose-Headers') + ); $this->assertArrayHasKey('Access-Control-Allow-Methods', $headers); $this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); $this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); diff --git a/module/Rest/test/Middleware/PathVersionMiddlewareTest.php b/module/Rest/test/Middleware/PathVersionMiddlewareTest.php deleted file mode 100644 index 98e6698d..00000000 --- a/module/Rest/test/Middleware/PathVersionMiddlewareTest.php +++ /dev/null @@ -1,58 +0,0 @@ -middleware = new PathVersionMiddleware(); - } - - /** @test */ - public function whenVersionIsProvidedRequestRemainsUnchanged(): void - { - $request = (new ServerRequest())->withUri(new Uri('/v2/foo')); - - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle($request)->willReturn(new Response()); - - $this->middleware->process($request, $delegate->reveal()); - - $process->shouldHaveBeenCalled(); - } - - /** @test */ - public function versionOneIsPrependedWhenNoVersionIsDefined(): void - { - $request = (new ServerRequest())->withUri(new Uri('/bar/baz')); - - $delegate = $this->prophesize(RequestHandlerInterface::class); - $delegate->handle(Argument::type(Request::class))->will(function (array $args) use ($request) { - $req = array_shift($args); - - Assert::assertNotSame($request, $req); - Assert::assertEquals('/v1/bar/baz', $req->getUri()->getPath()); - return new Response(); - }); - - - $this->middleware->process($request, $delegate->reveal()); - } -} diff --git a/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php deleted file mode 100644 index fb1ae215..00000000 --- a/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php +++ /dev/null @@ -1,49 +0,0 @@ -middleware = new ShortCodePathMiddleware(); - $this->requestHandler = $this->prophesize(RequestHandlerInterface::class); - $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(new Response()); - } - - /** @test */ - public function properlyReplacesTheOldPathByTheNewOne() - { - $uri = new Uri('/short-codes/foo'); - - $request = $this->prophesize(ServerRequestInterface::class); - $request->getUri()->willReturn($uri); - $withUri = $request->withUri(Argument::that(function (UriInterface $uri) { - $path = $uri->getPath(); - - Assert::assertStringContainsString('/short-urls', $path); - Assert::assertStringNotContainsString('/short-codes', $path); - - return $uri; - }))->willReturn($request->reveal()); - - $this->middleware->process($request->reveal(), $this->requestHandler->reveal()); - - $withUri->shouldHaveBeenCalledOnce(); - } -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 67d08507..03f73521 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,9 +15,6 @@ ./module/CLI/test - - ./module/PreviewGenerator/test -