diff --git a/composer.json b/composer.json index fb786eec..06c0be62 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ }, "require-dev": { "devster/ubench": "^2.0", + "dms/phpunit-arraysubset-asserts": "^0.1.0", "eaglewu/swoole-ide-helper": "dev-master", "infection/infection": "^0.15.0", "phpstan/phpstan": "^0.12.3", diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 883e228f..f0c487b7 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -13,12 +13,12 @@ use function array_key_exists; final class ShortUrlMeta { - private bool $hasValidSince = false; + private bool $validSincePropWasProvided = false; private ?Chronos $validSince = null; - private bool $hasValidUntil = false; + private bool $validUntilPropWasProvided = false; private ?Chronos $validUntil = null; private ?string $customSlug = null; - private bool $hasMaxVisits = false; + private bool $maxVisitsPropWasProvided = false; private ?int $maxVisits = null; private ?bool $findIfExists = null; private ?string $domain = null; @@ -56,11 +56,13 @@ final class ShortUrlMeta } $this->validSince = $this->parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE)); - $this->hasValidSince = array_key_exists(ShortUrlMetaInputFilter::VALID_SINCE, $data); + $this->validSincePropWasProvided = array_key_exists(ShortUrlMetaInputFilter::VALID_SINCE, $data); $this->validUntil = $this->parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL)); + $this->validUntilPropWasProvided = array_key_exists(ShortUrlMetaInputFilter::VALID_UNTIL, $data); $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); $maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); $this->maxVisits = $maxVisits !== null ? (int) $maxVisits : null; + $this->maxVisitsPropWasProvided = array_key_exists(ShortUrlMetaInputFilter::MAX_VISITS, $data); $this->findIfExists = $inputFilter->getValue(ShortUrlMetaInputFilter::FIND_IF_EXISTS); $this->domain = $inputFilter->getValue(ShortUrlMetaInputFilter::DOMAIN); } @@ -88,7 +90,7 @@ final class ShortUrlMeta public function hasValidSince(): bool { - return $this->validSince !== null; + return $this->validSincePropWasProvided; } public function getValidUntil(): ?Chronos @@ -98,7 +100,7 @@ final class ShortUrlMeta public function hasValidUntil(): bool { - return $this->validUntil !== null; + return $this->validUntilPropWasProvided; } public function getCustomSlug(): ?string @@ -118,7 +120,7 @@ final class ShortUrlMeta public function hasMaxVisits(): bool { - return $this->maxVisits !== null; + return $this->maxVisitsPropWasProvided; } public function findIfExists(): bool diff --git a/module/Rest/test-api/Action/EditShortUrlActionTest.php b/module/Rest/test-api/Action/EditShortUrlActionTest.php index 024ffb79..62b297d9 100644 --- a/module/Rest/test-api/Action/EditShortUrlActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlActionTest.php @@ -4,11 +4,73 @@ declare(strict_types=1); namespace ShlinkioApiTest\Shlink\Rest\Action; +use Cake\Chronos\Chronos; +use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use GuzzleHttp\RequestOptions; use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase; +use function Functional\first; +use function sprintf; + class EditShortUrlActionTest extends ApiTestCase { + use ArraySubsetAsserts; + + /** + * @test + * @dataProvider provideDisablingMeta + */ + public function metadataCanBeReset(array $meta): void + { + $shortCode = 'abc123'; + $url = sprintf('/short-urls/%s', $shortCode); + $resetMeta = [ + 'validSince' => null, + 'validUntil' => null, + 'maxVisits' => null, + ]; + + // Setting meta that disables the URL should not let it be visited + $editWithProvidedMeta = $this->callApiWithKey(self::METHOD_PATCH, $url, [RequestOptions::JSON => $meta]); + $metaAfterEditing = $this->findShortUrlMetaByShortCode($shortCode); + + // Resetting all meta should allow the URL to be visitable again + $editWithResetMeta = $this->callApiWithKey(self::METHOD_PATCH, $url, [ + RequestOptions::JSON => $resetMeta, + ]); + $metaAfterResetting = $this->findShortUrlMetaByShortCode($shortCode); + + $this->assertEquals(self::STATUS_NO_CONTENT, $editWithProvidedMeta->getStatusCode()); + $this->assertEquals(self::STATUS_NO_CONTENT, $editWithResetMeta->getStatusCode()); + $this->assertEquals($resetMeta, $metaAfterResetting); + self::assertArraySubset($meta, $metaAfterEditing); + } + + public function provideDisablingMeta(): iterable + { + $now = Chronos::now(); + + yield [['validSince' => $now->addMonth()->toAtomString()]]; + yield [['validUntil' => $now->subMonth()->toAtomString()]]; + yield [['maxVisits' => 20]]; + yield [['validUntil' => $now->addYear()->toAtomString(), 'maxVisits' => 100]]; + yield [[ + 'validSince' => $now->subYear()->toAtomString(), + 'validUntil' => $now->addYear()->toAtomString(), + 'maxVisits' => 100, + ]]; + } + + private function findShortUrlMetaByShortCode(string $shortCode): ?array + { + // FIXME Call GET /short-urls/{shortCode} once issue https://github.com/shlinkio/shlink/issues/628 is fixed + $allShortUrls = $this->getJsonResponsePayload($this->callApiWithKey(self::METHOD_GET, '/short-urls')); + $list = $allShortUrls['shortUrls']['data'] ?? []; + $matchingShortUrl = first($list, fn (array $shortUrl) => $shortUrl['shortCode'] ?? '' === $shortCode); + + return $matchingShortUrl['meta'] ?? null; + } + /** @test */ public function tryingToEditInvalidUrlReturnsNotFoundError(): void {