Skip to content

Commit

Permalink
Merge pull request #179 from shlinkio/develop
Browse files Browse the repository at this point in the history
Release 8.3.0
  • Loading branch information
acelaya authored Jan 28, 2023
2 parents c3b0d1e + 7f6fce7 commit 1ead941
Show file tree
Hide file tree
Showing 75 changed files with 688 additions and 848 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).

## [8.3.0] - 2023-01-28
### Added
* [#174](https://github.com/shlinkio/shlink-installer/issues/174) Added support for redirect status codes 308 and 307.
* Added support for short URL mode option.

### Changed
* Migrated infection config to json5.
* Migrated test doubles from prophecy to PHPUnit mocks.
* Replaced references to `doma.in` by `s.test`.

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* *Nothing*


## [8.2.0] - 2022-09-18
### Added
* Added config option to enable/disable trailing slashes support.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A PHP command line tool used to install [shlink](https://shlink.io/).

[![Build Status](https://img.shields.io/github/workflow/status/shlinkio/shlink-installer/Continuous%20integration?logo=github&style=flat-square)](https://github.com/shlinkio/shlink-installer/actions?query=workflow%3A%22Continuous+integration%22)
[![Build Status](https://img.shields.io/github/actions/workflow/status/shlinkio/shlink-installer/ci.yml?branch=develop&logo=github&style=flat-square)](https://github.com/shlinkio/shlink-installer/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22)
[![Code Coverage](https://img.shields.io/codecov/c/gh/shlinkio/shlink-installer/develop?style=flat-square)](https://app.codecov.io/gh/shlinkio/shlink-installer)
[![Infection MSI](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.jparrowsec.cn%2Fshlinkio%2Fshlink-installer%2Fdevelop)](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink-installer/develop)
[![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink-installer.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink-installer)
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
},
"require-dev": {
"devster/ubench": "^2.0",
"infection/infection": "^0.26",
"phpspec/prophecy-phpunit": "^2.0",
"infection/infection": "^0.26.15",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.2",
"phpunit/phpunit": "^9.5",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.3.0",
Expand All @@ -52,7 +52,7 @@
],
"cs": "phpcs",
"cs:fix": "phpcbf",
"stan": "phpstan analyse src --level=8",
"stan": "phpstan analyse src test test-resources config --level=8",
"test": "phpdbg -qrr vendor/bin/phpunit --order-by=random --testdox --colors=always",
"test:ci": "@test --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/junit.xml",
"test:pretty": "@test --coverage-html build/coverage-html",
Expand Down
2 changes: 2 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
'URL shortener > Multi-segment slugs'
=> Config\Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
'URL shortener > Trailing slashes' => Config\Option\UrlShortener\EnableTrailingSlashConfigOption::class,
'URL shortener > Mode' => Config\Option\UrlShortener\ShortUrlModeConfigOption::class,
'Webhooks > List' => Config\Option\Visit\VisitsWebhooksConfigOption::class,
'Webhooks > Orphan visits' => Config\Option\Visit\OrphanVisitsWebhooksConfigOption::class,
'GeoLite2 license key' => Config\Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
Expand Down Expand Up @@ -130,6 +131,7 @@
Config\Option\UrlShortener\AppendExtraPathConfigOption::class => InvokableFactory::class,
Config\Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class => InvokableFactory::class,
Config\Option\UrlShortener\EnableTrailingSlashConfigOption::class => InvokableFactory::class,
Config\Option\UrlShortener\ShortUrlModeConfigOption::class => InvokableFactory::class,
Config\Option\Redis\RedisServersConfigOption::class => InvokableFactory::class,
Config\Option\Redis\RedisSentinelServiceConfigOption::class => InvokableFactory::class,
Config\Option\Redis\RedisPubSubConfigOption::class => InvokableFactory::class,
Expand Down
26 changes: 0 additions & 26 deletions infection.json

This file was deleted.

26 changes: 26 additions & 0 deletions infection.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
source: {
directories: [
'src'
]
},
timeout: 5,
logs: {
text: 'build/infection/infection-log.txt',
html: 'build/infection/infection-log.html',
summary: 'build/infection/summary-log.txt',
debug: 'build/infection/debug-log.txt',
stryker: {
report: 'develop'
}
},
tmpDir: 'build/infection/temp',
phpUnit: {
configDir: '.'
},
mutators: {
'@default': true,
IdenticalEqual: false,
NotIdenticalNotEqual: false
}
}
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
parameters:
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function getEnvVar(): string
public function ask(StyleInterface $io, array $currentOptions): bool
{
return $io->confirm(
'Do you want to support trailing slashes in short URLs? (https://doma.in/foo and https://doma.in/foo/ '
'Do you want to support trailing slashes in short URLs? (https://s.test/foo and https://s.test/foo/ '
. 'will be considered the same)',
false,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait;
use Symfony\Component\Console\Style\StyleInterface;

use function Functional\contains;

class RedirectCacheLifeTimeConfigOption extends BaseConfigOption implements DependentConfigOptionInterface
{
use ConfigOptionsValidatorsTrait;
Expand All @@ -21,7 +23,12 @@ public function getEnvVar(): string
public function shouldBeAsked(array $currentOptions): bool
{
$redirectStatus = $currentOptions[RedirectStatusCodeConfigOption::ENV_VAR] ?? null;
return $redirectStatus === 301 && parent::shouldBeAsked($currentOptions);
return $this->isPermanentRedirectStatus($redirectStatus) && parent::shouldBeAsked($currentOptions);
}

private function isPermanentRedirectStatus(int $redirectStatus): bool
{
return contains([301, 308], $redirectStatus);
}

public function ask(StyleInterface $io, array $currentOptions): int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ class RedirectStatusCodeConfigOption extends BaseConfigOption
{
public const ENV_VAR = 'REDIRECT_STATUS_CODE';
private const REDIRECT_STATUSES = [
'All visits will always be tracked. Not that good for SEO.' => 302,
'Best option for SEO. Redirect will be cached for a short period of time, making some visits not to be tracked.' => 301, // phpcs:ignore
302 => 'All visits will always be tracked. Not that good for SEO. Only GET requests will be redirected.',
301 => 'Best option for SEO. Redirect will be cached for a short period of time, making some visits not to be '
. 'tracked. Only GET requests will be redirected.',
307 => 'Same as 302, but Shlink will also redirect on non-GET requests.',
308 => 'Same as 301, but Shlink will also redirect on non-GET requests.',
];

public function getEnvVar(): string
Expand All @@ -24,8 +27,9 @@ public function getEnvVar(): string

public function ask(StyleInterface $io, array $currentOptions): int
{
$options = array_flip(self::REDIRECT_STATUSES);
$answer = $io->choice('What kind of redirect do you want your short URLs to have?', $options, $options[302]);
return self::REDIRECT_STATUSES[$answer];
$options = self::REDIRECT_STATUSES;
$answer = $io->choice('What kind of redirect do you want your short URLs to have?', $options, 302);

return array_flip($options)[$answer];
}
}
39 changes: 39 additions & 0 deletions src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Installer\Config\Option\UrlShortener;

use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption;
use Symfony\Component\Console\Style\StyleInterface;

use const PHP_EOL;

class ShortUrlModeConfigOption extends BaseConfigOption
{
private const MODES = [
'strict' => 'Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). '
. 'Generated short codes will include lowercase letters, uppercase letters and numbers.',
'loosely' => 'Short codes and custom slugs will be matched in a case-insensitive way ("foo" === "FOO"). '
. 'Generated short codes will include only lowercase letters and numbers.',
];

public function getEnvVar(): string
{
return 'SHORT_URL_MODE';
}

public function ask(StyleInterface $io, array $currentOptions): string
{
$options = self::MODES;
return $io->choice(
'How do you want short URLs to be matched?'
. PHP_EOL
. '<options=bold;fg=yellow> Warning!</> <comment>This feature is experimental. It only applies to public '
. 'routes (short URLs and QR codes). REST API routes always use strict match.</comment>'
. PHP_EOL,
$options,
'strict',
);
}
}
62 changes: 25 additions & 37 deletions test/Command/InstallCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,43 @@

use Laminas\Config\Writer\WriterInterface;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Installer\Command\InstallCommand;
use Shlinkio\Shlink\Installer\Config\ConfigGeneratorInterface;
use Shlinkio\Shlink\Installer\Model\ImportedConfig;
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface;
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Process\PhpExecutableFinder;

use function count;
use function Functional\map;

class InstallCommandTest extends TestCase
{
use ProphecyTrait;

private CommandTester $commandTester;
private ObjectProphecy $configWriter;
private ObjectProphecy $assetsHandler;
private ObjectProphecy $commandsRunner;
private MockObject $configWriter;
private MockObject $assetsHandler;
private MockObject $commandsRunner;

public function setUp(): void
{
$this->assetsHandler = $this->prophesize(ShlinkAssetsHandlerInterface::class);
$this->assetsHandler->dropCachedConfigIfAny(Argument::any())->shouldBeCalledOnce();

$this->configWriter = $this->prophesize(WriterInterface::class);
$this->assetsHandler = $this->createMock(ShlinkAssetsHandlerInterface::class);
$this->assetsHandler->expects($this->once())->method('dropCachedConfigIfAny');

$this->commandsRunner = $this->prophesize(InstallationCommandsRunnerInterface::class);
$this->commandsRunner->execPhpCommand(Argument::cetera())->willReturn(true);
$this->configWriter = $this->createMock(WriterInterface::class);
$this->commandsRunner = $this->createMock(InstallationCommandsRunnerInterface::class);

$configGenerator = $this->prophesize(ConfigGeneratorInterface::class);
$configGenerator->generateConfigInteractively(Argument::cetera())->willReturn([]);

$finder = $this->prophesize(PhpExecutableFinder::class);
$finder->find(false)->willReturn('php');
$configGenerator = $this->createMock(ConfigGeneratorInterface::class);
$configGenerator->method('generateConfigInteractively')->willReturn([]);

$app = new Application();
$command = new InstallCommand(
$this->configWriter->reveal(),
$this->assetsHandler->reveal(),
$configGenerator->reveal(),
$this->commandsRunner->reveal(),
$this->configWriter,
$this->assetsHandler,
$configGenerator,
$this->commandsRunner,
);
$app->add($command);

Expand All @@ -63,28 +52,27 @@ public function setUp(): void
/** @test */
public function commandIsExecutedAsExpected(): void
{
$execPhpCommand = $this->commandsRunner->execPhpCommand(
Argument::that(function (string $commandName) {
$this->commandsRunner->expects(
$this->exactly(count(InstallationCommand::POST_INSTALL_COMMANDS)),
)->method('execPhpCommand')->with(
$this->callback(function (string $commandName) {
Assert::assertContains($commandName, map(
InstallationCommand::POST_INSTALL_COMMANDS,
fn (InstallationCommand $command) => $command->value,
));
return true;
}),
Argument::cetera(),
$this->anything(),
)->willReturn(true);
$resolvePreviousCommand = $this->assetsHandler->resolvePreviousConfig(Argument::cetera())->willReturn(
ImportedConfig::notImported(),
$this->assetsHandler->expects($this->never())->method('resolvePreviousConfig');
$this->assetsHandler->expects($this->never())->method('importShlinkAssetsFromPath');
$this->configWriter->expects($this->once())->method('toFile')->with(
$this->anything(),
$this->isType('array'),
false,
);
$importAssets = $this->assetsHandler->importShlinkAssetsFromPath(Argument::cetera());
$persistConfig = $this->configWriter->toFile(Argument::any(), Argument::type('array'), false);

$this->commandTester->setInputs(['no']);
$this->commandTester->execute([]);

$execPhpCommand->shouldHaveBeenCalledTimes(count(InstallationCommand::POST_INSTALL_COMMANDS));
$resolvePreviousCommand->shouldNotHaveBeenCalled();
$importAssets->shouldNotHaveBeenCalled();
$persistConfig->shouldHaveBeenCalledOnce();
}
}
Loading

0 comments on commit 1ead941

Please sign in to comment.