mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Fix merge conflicts
This commit is contained in:
@@ -24,6 +24,7 @@ return [
|
||||
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
||||
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||
Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class,
|
||||
Command\Api\InitialApiKeyCommand::NAME => Command\Api\InitialApiKeyCommand::class,
|
||||
|
||||
Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class,
|
||||
Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class,
|
||||
|
||||
@@ -53,6 +53,7 @@ return [
|
||||
Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Api\InitialApiKeyCommand::class => ConfigAbstractFactory::class,
|
||||
|
||||
Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class,
|
||||
@@ -105,6 +106,7 @@ return [
|
||||
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, ApiKey\RoleResolver::class],
|
||||
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
|
||||
Command\Api\ListKeysCommand::class => [ApiKeyService::class],
|
||||
Command\Api\InitialApiKeyCommand::class => [ApiKeyService::class],
|
||||
|
||||
Command\Tag\ListTagsCommand::class => [TagService::class],
|
||||
Command\Tag\RenameTagCommand::class => [TagService::class],
|
||||
|
||||
@@ -14,28 +14,27 @@ use function is_string;
|
||||
|
||||
class RoleResolver implements RoleResolverInterface
|
||||
{
|
||||
public function __construct(private DomainServiceInterface $domainService, private string $defaultDomain)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DomainServiceInterface $domainService,
|
||||
private readonly string $defaultDomain,
|
||||
) {
|
||||
}
|
||||
|
||||
public function determineRoles(InputInterface $input): array
|
||||
public function determineRoles(InputInterface $input): iterable
|
||||
{
|
||||
$domainAuthority = $input->getOption(Role::DOMAIN_SPECIFIC->paramName());
|
||||
$author = $input->getOption(Role::AUTHORED_SHORT_URLS->paramName());
|
||||
$noOrphanVisits = $input->getOption(Role::NO_ORPHAN_VISITS->paramName());
|
||||
|
||||
$roleDefinitions = [];
|
||||
if ($author) {
|
||||
$roleDefinitions[] = RoleDefinition::forAuthoredShortUrls();
|
||||
yield RoleDefinition::forAuthoredShortUrls();
|
||||
}
|
||||
if (is_string($domainAuthority)) {
|
||||
$roleDefinitions[] = $this->resolveRoleForAuthority($domainAuthority);
|
||||
yield $this->resolveRoleForAuthority($domainAuthority);
|
||||
}
|
||||
if ($noOrphanVisits) {
|
||||
$roleDefinitions[] = RoleDefinition::forNoOrphanVisits();
|
||||
yield RoleDefinition::forNoOrphanVisits();
|
||||
}
|
||||
|
||||
return $roleDefinitions;
|
||||
}
|
||||
|
||||
private function resolveRoleForAuthority(string $domainAuthority): RoleDefinition
|
||||
|
||||
@@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
interface RoleResolverInterface
|
||||
{
|
||||
/**
|
||||
* @return RoleDefinition[]
|
||||
* @return iterable<RoleDefinition>
|
||||
*/
|
||||
public function determineRoles(InputInterface $input): array;
|
||||
public function determineRoles(InputInterface $input): iterable;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use Cake\Chronos\Chronos;
|
||||
use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCode;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
|
||||
use Shlinkio\Shlink\Rest\ApiKey\Role;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
@@ -60,7 +61,7 @@ class GenerateKeyCommand extends Command
|
||||
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Generates a new valid API key.')
|
||||
->setDescription('Generate a new valid API key.')
|
||||
->addOption(
|
||||
'name',
|
||||
'm',
|
||||
@@ -100,11 +101,12 @@ class GenerateKeyCommand extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
||||
{
|
||||
$expirationDate = $input->getOption('expiration-date');
|
||||
$apiKey = $this->apiKeyService->create(
|
||||
isset($expirationDate) ? Chronos::parse($expirationDate) : null,
|
||||
$input->getOption('name'),
|
||||
...$this->roleResolver->determineRoles($input),
|
||||
);
|
||||
|
||||
$apiKey = $this->apiKeyService->create(ApiKeyMeta::fromParams(
|
||||
name: $input->getOption('name'),
|
||||
expirationDate: isset($expirationDate) ? Chronos::parse($expirationDate) : null,
|
||||
roleDefinitions: $this->roleResolver->determineRoles($input),
|
||||
));
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->success(sprintf('Generated API key: "%s"', $apiKey->toString()));
|
||||
|
||||
43
module/CLI/src/Command/Api/InitialApiKeyCommand.php
Normal file
43
module/CLI/src/Command/Api/InitialApiKeyCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCode;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class InitialApiKeyCommand extends Command
|
||||
{
|
||||
public const NAME = 'api-key:initial';
|
||||
|
||||
public function __construct(private readonly ApiKeyServiceInterface $apiKeyService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setHidden()
|
||||
->setName(self::NAME)
|
||||
->setDescription('Tries to create initial API key')
|
||||
->addArgument('apiKey', InputArgument::REQUIRED, 'The initial API to create');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
||||
{
|
||||
$key = $input->getArgument('apiKey');
|
||||
$result = $this->apiKeyService->createInitial($key);
|
||||
|
||||
if ($result === null && $output->isVerbose()) {
|
||||
$output->writeln('<comment>Other API keys already exist. Initial API key creation skipped.</comment>');
|
||||
}
|
||||
|
||||
return ExitCode::EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
26
module/CLI/test-cli/Command/InitialApiKeyTest.php
Normal file
26
module/CLI/test-cli/Command/InitialApiKeyTest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioCliTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\InitialApiKeyCommand;
|
||||
use Shlinkio\Shlink\TestUtils\CliTest\CliTestCase;
|
||||
|
||||
class InitialApiKeyTest extends CliTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function createsNoKeyWhenOtherApiKeysAlreadyExist(): void
|
||||
{
|
||||
[$output] = $this->exec([InitialApiKeyCommand::NAME, 'new_api_key', '-v']);
|
||||
|
||||
self::assertEquals(
|
||||
<<<OUT
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
|
||||
OUT,
|
||||
$output,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class RoleResolverTest extends TestCase
|
||||
'example.com',
|
||||
)->willReturn(self::domainWithId(Domain::withAuthority('example.com')));
|
||||
|
||||
$result = $this->resolver->determineRoles($input);
|
||||
$result = [...$this->resolver->determineRoles($input)];
|
||||
|
||||
self::assertEquals($expectedRoles, $result);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class RoleResolverTest extends TestCase
|
||||
|
||||
$this->expectException(InvalidRoleConfigException::class);
|
||||
|
||||
$this->resolver->determineRoles($input);
|
||||
[...$this->resolver->determineRoles($input)];
|
||||
}
|
||||
|
||||
private static function domainWithId(Domain $domain): Domain
|
||||
|
||||
@@ -10,6 +10,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand;
|
||||
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||
@@ -35,8 +36,7 @@ class GenerateKeyCommandTest extends TestCase
|
||||
public function noExpirationDateIsDefinedIfNotProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->isNull(),
|
||||
$this->isNull(),
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->name === null && $meta->expirationDate === null),
|
||||
)->willReturn(ApiKey::create());
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
@@ -49,8 +49,7 @@ class GenerateKeyCommandTest extends TestCase
|
||||
public function expirationDateIsDefinedIfProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->isInstanceOf(Chronos::class),
|
||||
$this->isNull(),
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->expirationDate instanceof Chronos),
|
||||
)->willReturn(ApiKey::create());
|
||||
|
||||
$this->commandTester->execute([
|
||||
@@ -62,8 +61,7 @@ class GenerateKeyCommandTest extends TestCase
|
||||
public function nameIsDefinedIfProvided(): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('create')->with(
|
||||
$this->isNull(),
|
||||
$this->isType('string'),
|
||||
$this->callback(fn (ApiKeyMeta $meta) => $meta->name === 'Alice'),
|
||||
)->willReturn(ApiKey::create());
|
||||
|
||||
$this->commandTester->execute([
|
||||
|
||||
54
module/CLI/test/Command/Api/InitialApiKeyCommandTest.php
Normal file
54
module/CLI/test/Command/Api/InitialApiKeyCommandTest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\InitialApiKeyCommand;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class InitialApiKeyCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & ApiKeyServiceInterface $apiKeyService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->apiKeyService = $this->createMock(ApiKeyServiceInterface::class);
|
||||
$this->commandTester = CliTestUtils::testerForCommand(new InitialApiKeyCommand($this->apiKeyService));
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideParams')]
|
||||
public function initialKeyIsCreatedWithProvidedValue(?ApiKey $result, bool $verbose, string $expectedOutput): void
|
||||
{
|
||||
$this->apiKeyService->expects($this->once())->method('createInitial')->with('the_key')->willReturn($result);
|
||||
|
||||
$this->commandTester->execute(
|
||||
['apiKey' => 'the_key'],
|
||||
['verbosity' => $verbose ? OutputInterface::VERBOSITY_VERBOSE : OutputInterface::VERBOSITY_NORMAL],
|
||||
);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
self::assertEquals($expectedOutput, $output);
|
||||
}
|
||||
|
||||
public static function provideParams(): iterable
|
||||
{
|
||||
yield 'api key created, no verbose' => [ApiKey::create(), false, ''];
|
||||
yield 'api key created, verbose' => [ApiKey::create(), true, ''];
|
||||
yield 'no api key created, no verbose' => [null, false, ''];
|
||||
yield 'no api key created, verbose' => [null, true, <<<OUT
|
||||
Other API keys already exist. Initial API key creation skipped.
|
||||
|
||||
OUT,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class ListKeysCommandTest extends TestCase
|
||||
yield 'all keys' => [
|
||||
[
|
||||
$apiKey1 = ApiKey::create()->disable(),
|
||||
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withExpirationDate($dateInThePast)),
|
||||
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::fromParams(expirationDate: $dateInThePast)),
|
||||
$apiKey3 = ApiKey::create(),
|
||||
],
|
||||
false,
|
||||
@@ -115,9 +115,9 @@ class ListKeysCommandTest extends TestCase
|
||||
];
|
||||
yield 'with names' => [
|
||||
[
|
||||
$apiKey1 = ApiKey::fromMeta(ApiKeyMeta::withName('Alice')),
|
||||
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withName('Alice and Bob')),
|
||||
$apiKey3 = ApiKey::fromMeta(ApiKeyMeta::withName('')),
|
||||
$apiKey1 = ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'Alice')),
|
||||
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'Alice and Bob')),
|
||||
$apiKey3 = ApiKey::fromMeta(ApiKeyMeta::fromParams(name: '')),
|
||||
$apiKey4 = ApiKey::create(),
|
||||
],
|
||||
true,
|
||||
|
||||
@@ -136,7 +136,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
|
||||
public static function provideOptionalFlags(): iterable
|
||||
{
|
||||
$apiKey = ApiKey::fromMeta(ApiKeyMeta::withName('my api key'));
|
||||
$apiKey = ApiKey::fromMeta(ApiKeyMeta::fromParams(name: 'my api key'));
|
||||
$key = $apiKey->toString();
|
||||
|
||||
yield 'tags only' => [
|
||||
|
||||
Reference in New Issue
Block a user