diff --git a/composer.json b/composer.json index abc6dc79..baa1b95c 100644 --- a/composer.json +++ b/composer.json @@ -147,6 +147,10 @@ "@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" diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index 61d4e6e0..9eebe36f 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -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()], ); } } diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index a124993f..68c1e844 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -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, <<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( + <<shouldHaveBeenCalled(); } } diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index 8174e8be..c509bcc3 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -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; } diff --git a/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php b/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php index 6b103058..809d19b7 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php @@ -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(); } } diff --git a/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php b/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php index 4ce130b7..46fba689 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php @@ -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(); } } diff --git a/module/Rest/src/Action/Tag/ListTagsAction.php b/module/Rest/src/Action/Tag/ListTagsAction.php index 89371b71..3d34bd19 100644 --- a/module/Rest/src/Action/Tag/ListTagsAction.php +++ b/module/Rest/src/Action/Tag/ListTagsAction.php @@ -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' => [ diff --git a/module/Rest/src/Middleware/BodyParserMiddleware.php b/module/Rest/src/Middleware/BodyParserMiddleware.php index c7e99121..2711d900 100644 --- a/module/Rest/src/Middleware/BodyParserMiddleware.php +++ b/module/Rest/src/Middleware/BodyParserMiddleware.php @@ -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; } diff --git a/module/Rest/test/Action/Domain/Request/DomainRedirectsRequestTest.php b/module/Rest/test/Action/Domain/Request/DomainRedirectsRequestTest.php new file mode 100644 index 00000000..55828368 --- /dev/null +++ b/module/Rest/test/Action/Domain/Request/DomainRedirectsRequestTest.php @@ -0,0 +1,73 @@ +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, + ]; + } +}