diff --git a/composer.json b/composer.json index 90fdb87b..3d325b03 100644 --- a/composer.json +++ b/composer.json @@ -123,9 +123,9 @@ ], "test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --coverage-html build/coverage --order-by=random", - "infect": "infection --threads=4 --min-msi=60 --log-verbosity=2 --only-covered", - "infect:ci": "infection --threads=4 --min-msi=60 --log-verbosity=2 --only-covered --coverage=build", - "infect:show": "infection --threads=4 --min-msi=60 --log-verbosity=2 --only-covered --show-mutations", + "infect": "infection --threads=4 --min-msi=65 --log-verbosity=2 --only-covered", + "infect:ci": "infection --threads=4 --min-msi=65 --log-verbosity=2 --only-covered --coverage=build", + "infect:show": "infection --threads=4 --min-msi=65 --log-verbosity=2 --only-covered --show-mutations", "infect:test": [ "@test:unit:ci", "@infect:ci" diff --git a/module/Common/src/Response/PixelResponse.php b/module/Common/src/Response/PixelResponse.php index e2554bb5..87a10dca 100644 --- a/module/Common/src/Response/PixelResponse.php +++ b/module/Common/src/Response/PixelResponse.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Response; +use Fig\Http\Message\StatusCodeInterface as StatusCode; use Psr\Http\Message\StreamInterface; use Zend\Diactoros\Response; use Zend\Diactoros\Stream; @@ -13,7 +14,7 @@ class PixelResponse extends Response private const BASE_64_IMAGE = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='; private const CONTENT_TYPE = 'image/gif'; - public function __construct(int $status = 200, array $headers = []) + public function __construct(int $status = StatusCode::STATUS_OK, array $headers = []) { $headers['content-type'] = self::CONTENT_TYPE; parent::__construct($this->createBody(), $status, $headers); diff --git a/module/Common/src/Response/QrCodeResponse.php b/module/Common/src/Response/QrCodeResponse.php index b207fa0d..230ae08f 100644 --- a/module/Common/src/Response/QrCodeResponse.php +++ b/module/Common/src/Response/QrCodeResponse.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Response; use Endroid\QrCode\QrCode; +use Fig\Http\Message\StatusCodeInterface as StatusCode; use Psr\Http\Message\StreamInterface; use Zend\Diactoros\Response; use Zend\Diactoros\Stream; @@ -12,7 +13,7 @@ class QrCodeResponse extends Response { use Response\InjectContentTypeTrait; - public function __construct(QrCode $qrCode, $status = 200, array $headers = []) + public function __construct(QrCode $qrCode, int $status = StatusCode::STATUS_OK, array $headers = []) { parent::__construct( $this->createBody($qrCode), @@ -21,13 +22,7 @@ class QrCodeResponse extends Response ); } - /** - * Create the message body. - * - * @param QrCode $qrCode - * @return StreamInterface - */ - private function createBody(QrCode $qrCode) + private function createBody(QrCode $qrCode): StreamInterface { $body = new Stream('php://temp', 'wb+'); $body->write($qrCode->get()); diff --git a/module/Common/src/Util/ResponseUtilsTrait.php b/module/Common/src/Util/ResponseUtilsTrait.php index e8ad8bf0..25a884f9 100644 --- a/module/Common/src/Util/ResponseUtilsTrait.php +++ b/module/Common/src/Util/ResponseUtilsTrait.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Util; +use Fig\Http\Message\StatusCodeInterface as StatusCode; use finfo; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response; @@ -20,7 +21,7 @@ trait ResponseUtilsTrait private function generateBinaryResponse(string $path, array $extraHeaders = []): ResponseInterface { $body = new Stream($path); - return new Response($body, 200, ArrayUtils::merge([ + return new Response($body, StatusCode::STATUS_OK, ArrayUtils::merge([ 'Content-Type' => (new finfo(FILEINFO_MIME))->file($path), 'Content-Length' => (string) $body->getSize(), ], $extraHeaders)); diff --git a/module/Common/src/Util/StringUtilsTrait.php b/module/Common/src/Util/StringUtilsTrait.php index 87b77a2a..853ba7b8 100644 --- a/module/Common/src/Util/StringUtilsTrait.php +++ b/module/Common/src/Util/StringUtilsTrait.php @@ -9,7 +9,7 @@ use function strlen; trait StringUtilsTrait { - private function generateRandomString($length = 10): string + private function generateRandomString(int $length = 10): string { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); diff --git a/module/Common/test/Response/QrCodeResponseTest.php b/module/Common/test/Response/QrCodeResponseTest.php new file mode 100644 index 00000000..36d6fada --- /dev/null +++ b/module/Common/test/Response/QrCodeResponseTest.php @@ -0,0 +1,23 @@ +assertEquals($qrCode->getContentType(), $resp->getHeaderLine('Content-Type')); + $this->assertEquals($qrCode->get(), (string) $resp->getBody()); + } +} diff --git a/module/Common/test/Util/DateRangeTest.php b/module/Common/test/Util/DateRangeTest.php index e5e48d46..ec5602a2 100644 --- a/module/Common/test/Util/DateRangeTest.php +++ b/module/Common/test/Util/DateRangeTest.php @@ -32,4 +32,24 @@ class DateRangeTest extends TestCase $this->assertSame($endDate, $range->getEndDate()); $this->assertFalse($range->isEmpty()); } + + /** + * @test + * @dataProvider provideDates + */ + public function isConsideredEmptyOnlyIfNoneOfTheDatesIsSet(?Chronos $startDate, ?Chronos $endDate, bool $isEmpty) + { + $range = new DateRange($startDate, $endDate); + $this->assertEquals($isEmpty, $range->isEmpty()); + } + + public function provideDates(): array + { + return [ + [null, null, true], + [null, Chronos::now(), false], + [Chronos::now(), null, false], + [Chronos::now(), Chronos::now(), false], + ]; + } } diff --git a/module/Common/test/Util/StringUtilsTraitTest.php b/module/Common/test/Util/StringUtilsTraitTest.php new file mode 100644 index 00000000..b64f4738 --- /dev/null +++ b/module/Common/test/Util/StringUtilsTraitTest.php @@ -0,0 +1,45 @@ +assertEquals($length, strlen($this->generateRandomString($length))); + } + + public function provideLengths(): array + { + return [ + [1], + [10], + [15], + ]; + } + + /** + * @test + */ + public function generatesUuidV4() + { + $uuidPattern = '/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/'; + + $this->assertRegExp($uuidPattern, $this->generateV4Uuid()); + $this->assertRegExp($uuidPattern, $this->generateV4Uuid()); + $this->assertRegExp($uuidPattern, $this->generateV4Uuid()); + $this->assertRegExp($uuidPattern, $this->generateV4Uuid()); + $this->assertRegExp($uuidPattern, $this->generateV4Uuid()); + } +} diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index 3fd98db0..d7c44ff7 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -118,4 +118,12 @@ class Visit extends AbstractEntity implements JsonSerializable 'remoteAddr' => null, ]; } + + /** + * @internal + */ + public function getDate(): Chronos + { + return $this->date; + } } diff --git a/module/Core/src/Exception/InvalidShortCodeException.php b/module/Core/src/Exception/InvalidShortCodeException.php index a4e68cea..59a361bb 100644 --- a/module/Core/src/Exception/InvalidShortCodeException.php +++ b/module/Core/src/Exception/InvalidShortCodeException.php @@ -8,7 +8,7 @@ use function sprintf; class InvalidShortCodeException extends RuntimeException { - public static function fromCharset($shortCode, $charSet, Exception $previous = null) + public static function fromCharset(string $shortCode, string $charSet, Exception $previous = null): self { $code = $previous !== null ? $previous->getCode() : -1; return new static( @@ -18,7 +18,7 @@ class InvalidShortCodeException extends RuntimeException ); } - public static function fromNotFoundShortCode($shortCode) + public static function fromNotFoundShortCode(string $shortCode): self { return new static(sprintf('Provided short code "%s" does not belong to a short URL', $shortCode)); } diff --git a/module/Core/src/Exception/InvalidUrlException.php b/module/Core/src/Exception/InvalidUrlException.php index 65f87e4e..6a94b548 100644 --- a/module/Core/src/Exception/InvalidUrlException.php +++ b/module/Core/src/Exception/InvalidUrlException.php @@ -10,7 +10,7 @@ class InvalidUrlException extends RuntimeException { public static function fromUrl($url, Throwable $previous = null) { - $code = isset($previous) ? $previous->getCode() : -1; + $code = $previous !== null ? $previous->getCode() : -1; return new static(sprintf('Provided URL "%s" is not an existing and valid URL', $url), $code, $previous); } } diff --git a/module/Core/test/Entity/VisitTest.php b/module/Core/test/Entity/VisitTest.php new file mode 100644 index 00000000..c5daaad6 --- /dev/null +++ b/module/Core/test/Entity/VisitTest.php @@ -0,0 +1,40 @@ +assertEquals([ + 'referer' => 'some site', + 'date' => ($date ?? $visit->getDate())->toAtomString(), + 'userAgent' => 'Chrome', + 'visitLocation' => null, + + // Deprecated + 'remoteAddr' => null, + ], $visit->jsonSerialize()); + } + + public function provideDates(): array + { + return [ + [null], + [Chronos::now()->subDays(10)], + ]; + } +} diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index 80c5ff10..f5c1ef80 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -21,6 +21,7 @@ class DeleteShortUrlExceptionTest extends TestCase ) { $e = DeleteShortUrlException::fromVisitsThreshold($threshold, $shortCode); $this->assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals(0, $e->getCode()); } public function provideMessages(): array diff --git a/module/Core/test/Exception/InvalidShortCodeExceptionTest.php b/module/Core/test/Exception/InvalidShortCodeExceptionTest.php new file mode 100644 index 00000000..4dca8e07 --- /dev/null +++ b/module/Core/test/Exception/InvalidShortCodeExceptionTest.php @@ -0,0 +1,43 @@ +assertEquals('Provided short code "abc123" does not match the char set "def456"', $e->getMessage()); + $this->assertEquals($prev !== null ? $prev->getCode() : -1, $e->getCode()); + $this->assertEquals($prev, $e->getPrevious()); + } + + public function providePrevious(): array + { + return [ + [null], + [new Exception('Previos error', 10)], + ]; + } + + /** + * @test + */ + public function properlyCreatesExceptionFromNotFoundShortCode() + { + $e = InvalidShortCodeException::fromNotFoundShortCode('abc123'); + + $this->assertEquals('Provided short code "abc123" does not belong to a short URL', $e->getMessage()); + } +} diff --git a/module/Core/test/Exception/InvalidUrlExceptionTest.php b/module/Core/test/Exception/InvalidUrlExceptionTest.php new file mode 100644 index 00000000..67a12601 --- /dev/null +++ b/module/Core/test/Exception/InvalidUrlExceptionTest.php @@ -0,0 +1,33 @@ +assertEquals('Provided URL "http://the_url.com" is not an existing and valid URL', $e->getMessage()); + $this->assertEquals($prev !== null ? $prev->getCode() : -1, $e->getCode()); + $this->assertEquals($prev, $e->getPrevious()); + } + + public function providePrevious(): array + { + return [ + [null], + [new Exception('Previos error', 10)], + ]; + } +}