Merge pull request #1259 from acelaya-forks/feature/83-msi

Feature/83 msi
This commit is contained in:
Alejandro Celaya 2021-12-10 14:17:20 +01:00 committed by GitHub
commit 00f867c6ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 161 additions and 33 deletions

View file

@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#1218](https://github.com/shlinkio/shlink/issues/1218) Updated to symfony/mercure 0.6.
* [#1223](https://github.com/shlinkio/shlink/issues/1223) Updated to phpstan 1.0.
* Added `domain` field to `DeleteShortUrlException` exception.
* [#1001](https://github.com/shlinkio/shlink/issues/1001) Increased required MSI to 83%.
### Deprecated
* *Nothing*

View file

@ -53,12 +53,12 @@
"shlinkio/shlink-importer": "dev-main#d099072 as 2.5",
"shlinkio/shlink-installer": "dev-develop#7dd00fb as 6.3",
"shlinkio/shlink-ip-geolocation": "^2.2",
"symfony/console": "^5.4",
"symfony/filesystem": "^5.4",
"symfony/lock": "^5.4",
"symfony/console": "^6.0 || ^5.4",
"symfony/filesystem": "^6.0 || ^5.4",
"symfony/lock": "^6.0 || ^5.4",
"symfony/mercure": "^0.6",
"symfony/process": "^5.4",
"symfony/string": "^5.4"
"symfony/process": "^6.0 || ^5.4",
"symfony/string": "^6.0 || ^5.4"
},
"require-dev": {
"cebe/php-openapi": "^1.5",
@ -107,11 +107,12 @@
"ci": [
"@cs",
"@stan",
"@swagger:validate",
"@test:ci",
"@infect:ci"
],
"ci:parallel": [
"@parallel cs stan test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
"@parallel test:api infect:ci:unit infect:ci:db"
],
"cs": "phpcs",
@ -139,19 +140,23 @@
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
"test:api": "bin/test/run-api-tests.sh",
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=83",
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
"infect:ci": "@parallel infect:ci:unit infect:ci:db",
"infect:test": [
"@parallel test:unit:ci test:db:sqlite:ci",
"@infect:ci"
],
"infect:test:unit": [
"@test:unit:ci",
"@infect:ci:unit"
],
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
},
"scripts-descriptions": {
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
"cs": "<fg=blue;options=bold>Checks coding styles</>",
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",

View file

@ -45,7 +45,8 @@ class ListTagsCommand extends Command
return map(
$tags,
fn (TagInfo $tagInfo) => [(string) $tagInfo->tag(), $tagInfo->shortUrlsCount(), $tagInfo->visitsCount()],
static fn (TagInfo $tagInfo) =>
[$tagInfo->tag()->__toString(), $tagInfo->shortUrlsCount(), $tagInfo->visitsCount()],
);
}
}

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Api;
use Cake\Chronos\Chronos;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand;
@ -45,19 +46,25 @@ class ListKeysCommandTest extends TestCase
public function provideKeysAndOutputs(): iterable
{
$dateInThePast = Chronos::createFromFormat('Y-m-d H:i:s', '2020-01-01 00:00:00');
yield 'all keys' => [
[$apiKey1 = ApiKey::create(), $apiKey2 = ApiKey::create(), $apiKey3 = ApiKey::create()],
[
$apiKey1 = ApiKey::create()->disable(),
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withExpirationDate($dateInThePast)),
$apiKey3 = ApiKey::create(),
],
false,
<<<OUTPUT
+--------------------------------------+------+------------+-----------------+-------+
| Key | Name | Is enabled | Expiration date | Roles |
+--------------------------------------+------+------------+-----------------+-------+
| {$apiKey1} | - | +++ | - | Admin |
+--------------------------------------+------+------------+-----------------+-------+
| {$apiKey2} | - | +++ | - | Admin |
+--------------------------------------+------+------------+-----------------+-------+
| {$apiKey3} | - | +++ | - | Admin |
+--------------------------------------+------+------------+-----------------+-------+
+--------------------------------------+------+------------+---------------------------+-------+
| Key | Name | Is enabled | Expiration date | Roles |
+--------------------------------------+------+------------+---------------------------+-------+
| {$apiKey1} | - | --- | - | Admin |
+--------------------------------------+------+------------+---------------------------+-------+
| {$apiKey2} | - | --- | 2020-01-01T00:00:00+00:00 | Admin |
+--------------------------------------+------+------------+---------------------------+-------+
| {$apiKey3} | - | +++ | - | Admin |
+--------------------------------------+------+------------+---------------------------+-------+
OUTPUT,
];

View file

@ -49,12 +49,18 @@ class ListTagsCommandTest extends TestCase
$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString('| foo', $output);
self::assertStringContainsString('| bar', $output);
self::assertStringContainsString('| 10 ', $output);
self::assertStringContainsString('| 2 ', $output);
self::assertStringContainsString('| 7 ', $output);
self::assertStringContainsString('| 32 ', $output);
self::assertEquals(
<<<OUTPUT
+------+-------------+---------------+
| Name | URLs amount | Visits amount |
+------+-------------+---------------+
| foo | 10 | 2 |
| bar | 7 | 32 |
+------+-------------+---------------+
OUTPUT,
$output,
);
$tagsInfo->shouldHaveBeenCalled();
}
}

View file

@ -103,7 +103,7 @@ class Visit extends AbstractEntity implements JsonSerializable
}
try {
return (string) IpAddress::fromString($address)->getAnonymizedCopy();
return IpAddress::fromString($address)->getAnonymizedCopy()->__toString();
} catch (InvalidArgumentException) {
return null;
}

View file

@ -17,6 +17,6 @@ class BelongsToApiKeyInlined implements Filter
public function getFilter(QueryBuilder $qb, string $dqlAlias): string
{
// Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later
return (string) $qb->expr()->eq('s.authorApiKey', '\'' . $this->apiKey->getId() . '\'');
return $qb->expr()->eq('s.authorApiKey', '\'' . $this->apiKey->getId() . '\'')->__toString();
}
}

View file

@ -16,6 +16,6 @@ class BelongsToDomainInlined implements Filter
public function getFilter(QueryBuilder $qb, string $context): string
{
// Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later
return (string) $qb->expr()->eq('s.domain', '\'' . $this->domainId . '\'');
return $qb->expr()->eq('s.domain', '\'' . $this->domainId . '\'')->__toString();
}
}

View file

@ -38,7 +38,7 @@ class ListTagsAction extends AbstractRestAction
}
$tagsInfo = $this->tagService->tagsInfo($apiKey);
$data = map($tagsInfo, fn (TagInfo $info) => (string) $info->tag());
$data = map($tagsInfo, static fn (TagInfo $info) => $info->tag()->__toString());
return new JsonResponse([
'tags' => [

View file

@ -54,7 +54,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
private function parseFromJson(Request $request): Request
{
$rawBody = (string) $request->getBody();
$rawBody = $request->getBody()->__toString();
if (empty($rawBody)) {
return $request;
}
@ -68,7 +68,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
*/
private function parseFromUrlEncoded(Request $request): Request
{
$rawBody = (string) $request->getBody();
$rawBody = $request->getBody()->__toString();
if (empty($rawBody)) {
return $request;
}

View file

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Rest\Action\Domain\Request;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Config\NotFoundRedirectConfigInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions;
use Shlinkio\Shlink\Rest\Action\Domain\Request\DomainRedirectsRequest;
class DomainRedirectsRequestTest extends TestCase
{
/**
* @test
* @dataProvider provideInvalidData
*/
public function throwsExceptionWhenCreatingWithInvalidData(array $data): void
{
$this->expectException(ValidationException::class);
DomainRedirectsRequest::fromRawData($data);
}
public function provideInvalidData(): iterable
{
yield 'missing domain' => [[]];
yield 'invalid domain' => [['domain' => 'foo:bar:baz']];
}
/**
* @test
* @dataProvider provideValidData
*/
public function isProperlyCastToNotFoundRedirects(
array $data,
?NotFoundRedirectConfigInterface $defaults,
string $expectedAuthority,
?string $expectedBaseUrlRedirect,
?string $expectedRegular404Redirect,
?string $expectedInvalidShortUrlRedirect,
): void {
$request = DomainRedirectsRequest::fromRawData($data);
$notFound = $request->toNotFoundRedirects($defaults);
self::assertEquals($expectedAuthority, $request->authority());
self::assertEquals($expectedBaseUrlRedirect, $notFound->baseUrlRedirect());
self::assertEquals($expectedRegular404Redirect, $notFound->regular404Redirect());
self::assertEquals($expectedInvalidShortUrlRedirect, $notFound->invalidShortUrlRedirect());
}
public function provideValidData(): iterable
{
yield 'no values' => [['domain' => 'foo'], null, 'foo', null, null, null];
yield 'some values' => [['domain' => 'foo', 'regular404Redirect' => 'bar'], null, 'foo', null, 'bar', null];
yield 'fallbacks' => [
['domain' => 'domain', 'baseUrlRedirect' => 'bar'],
new NotFoundRedirectOptions(['regular404' => 'fallback', 'invalidShortUrl' => 'fallback2']),
'domain',
'bar',
'fallback',
'fallback2',
];
yield 'fallback ignored' => [
['domain' => 'domain', 'regular404Redirect' => 'bar', 'invalidShortUrlRedirect' => null],
new NotFoundRedirectOptions(['regular404' => 'fallback', 'invalidShortUrl' => 'fallback2']),
'domain',
null,
'bar',
null,
];
}
}

View file

@ -45,13 +45,16 @@ class OrphanVisitsActionTest extends TestCase
$orphanVisits = $this->visitsHelper->orphanVisits(Argument::type(VisitsParams::class))->willReturn(
new Paginator(new ArrayAdapter($visits)),
);
$visitsAmount = count($visits);
$transform = $this->orphanVisitTransformer->transform(Argument::type(Visit::class))->willReturn([]);
/** @var JsonResponse $response */
$response = $this->action->handle(ServerRequestFactory::fromGlobals());
$payload = $response->getPayload();
self::assertInstanceOf(JsonResponse::class, $response);
self::assertCount($visitsAmount, $payload['visits']['data']);
self::assertEquals(200, $response->getStatusCode());
$orphanVisits->shouldHaveBeenCalledOnce();
$transform->shouldHaveBeenCalledTimes(count($visits));
$transform->shouldHaveBeenCalledTimes($visitsAmount);
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Rest\ApiKey\Model;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
use Shlinkio\Shlink\Rest\ApiKey\Role;
class RoleDefinitionTest extends TestCase
{
/** @test */
public function forAuthoredShortUrlsCreatesRoleDefinitionAsExpected(): void
{
$definition = RoleDefinition::forAuthoredShortUrls();
self::assertEquals(Role::AUTHORED_SHORT_URLS, $definition->roleName());
self::assertEquals([], $definition->meta());
}
/** @test */
public function forDomainCreatesRoleDefinitionAsExpected(): void
{
$domain = Domain::withAuthority('foo.com')->setId('123');
$definition = RoleDefinition::forDomain($domain);
self::assertEquals(Role::DOMAIN_SPECIFIC, $definition->roleName());
self::assertEquals(['domain_id' => '123', 'authority' => 'foo.com'], $definition->meta());
}
}