diff --git a/module/Core/src/Config/NotFoundRedirects.php b/module/Core/src/Config/NotFoundRedirects.php index 9f277be5..492a00bc 100644 --- a/module/Core/src/Config/NotFoundRedirects.php +++ b/module/Core/src/Config/NotFoundRedirects.php @@ -16,7 +16,7 @@ final class NotFoundRedirects implements JsonSerializable } public static function withRedirects( - ?string $baseUrlRedirect = null, + ?string $baseUrlRedirect, ?string $regular404Redirect = null, ?string $invalidShortUrlRedirect = null, ): self { diff --git a/module/Core/src/Domain/Validation/DomainRedirectsInputFilter.php b/module/Core/src/Domain/Validation/DomainRedirectsInputFilter.php index 94c15217..de627c1c 100644 --- a/module/Core/src/Domain/Validation/DomainRedirectsInputFilter.php +++ b/module/Core/src/Domain/Validation/DomainRedirectsInputFilter.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain\Validation; use Laminas\InputFilter\InputFilter; -use Laminas\Validator; use Shlinkio\Shlink\Common\Validation; class DomainRedirectsInputFilter extends InputFilter @@ -34,13 +33,7 @@ class DomainRedirectsInputFilter extends InputFilter private function initializeInputs(): void { $domain = $this->createInput(self::DOMAIN); - $domain->getValidatorChain()->attach(new Validator\NotEmpty([ - Validator\NotEmpty::OBJECT, - Validator\NotEmpty::SPACE, - Validator\NotEmpty::NULL, - Validator\NotEmpty::EMPTY_ARRAY, - Validator\NotEmpty::BOOLEAN, - ])); + $domain->getValidatorChain()->attach(new Validation\HostAndPortValidator()); $this->add($domain); $this->add($this->createInput(self::BASE_URL_REDIRECT, false)); diff --git a/module/Core/src/Validation/ShortUrlInputFilter.php b/module/Core/src/Validation/ShortUrlInputFilter.php index c7cdaa43..b969d95e 100644 --- a/module/Core/src/Validation/ShortUrlInputFilter.php +++ b/module/Core/src/Validation/ShortUrlInputFilter.php @@ -75,7 +75,7 @@ class ShortUrlInputFilter extends InputFilter $customSlug = $this->createInput(self::CUSTOM_SLUG, false)->setContinueIfEmpty(true); $customSlug->getFilterChain()->attach(new Validation\SluggerFilter(new CocurSymfonySluggerBridge(new Slugify([ 'regexp' => CUSTOM_SLUGS_REGEXP, - 'lowercase' => false, // We want to keep it case sensitive + 'lowercase' => false, // We want to keep it case-sensitive 'rulesets' => ['default'], ])))); $customSlug->getValidatorChain()->attach(new Validator\NotEmpty([ diff --git a/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php new file mode 100644 index 00000000..5d09f3f7 --- /dev/null +++ b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php @@ -0,0 +1,160 @@ +domainService = $this->prophesize(DomainServiceInterface::class); + $this->action = new DomainRedirectsAction($this->domainService->reveal()); + } + + /** + * @test + * @dataProvider provideInvalidBodies + */ + public function invalidDataThrowsException(array $body): void + { + $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); + + $this->expectException(ValidationException::class); + $this->domainService->getOrCreate(Argument::cetera())->shouldNotBeCalled(); + $this->domainService->configureNotFoundRedirects(Argument::cetera())->shouldNotBeCalled(); + + $this->action->handle($request); + } + + public function provideInvalidBodies(): iterable + { + yield 'no domain' => [[]]; + yield 'empty domain' => [['domain' => '']]; + yield 'invalid domain' => [['domain' => '192.168.1.20']]; + } + + /** + * @test + * @dataProvider provideDomainsAndRedirects + */ + public function domainIsFetchedAndUsedToGetItConfigured( + Domain $domain, + array $redirects, + array $expectedResult, + ): void { + $authority = 'doma.in'; + $redirects['domain'] = $authority; + $apiKey = ApiKey::create(); + $request = ServerRequestFactory::fromGlobals()->withParsedBody($redirects) + ->withAttribute(ApiKey::class, $apiKey); + + $getOrCreate = $this->domainService->getOrCreate($authority)->willReturn($domain); + $configureNotFoundRedirects = $this->domainService->configureNotFoundRedirects( + $authority, + NotFoundRedirects::withRedirects( + array_key_exists(DomainRedirectsInputFilter::BASE_URL_REDIRECT, $redirects) + ? $redirects[DomainRedirectsInputFilter::BASE_URL_REDIRECT] + : $domain?->baseUrlRedirect(), + array_key_exists(DomainRedirectsInputFilter::REGULAR_404_REDIRECT, $redirects) + ? $redirects[DomainRedirectsInputFilter::REGULAR_404_REDIRECT] + : $domain?->regular404Redirect(), + array_key_exists(DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT, $redirects) + ? $redirects[DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT] + : $domain?->invalidShortUrlRedirect(), + ), + $apiKey, + ); + + /** @var JsonResponse $response */ + $response = $this->action->handle($request); + /** @var NotFoundRedirects $payload */ + $payload = $response->getPayload(); + + self::assertEquals($expectedResult, $payload->jsonSerialize()); + $getOrCreate->shouldHaveBeenCalledOnce(); + $configureNotFoundRedirects->shouldHaveBeenCalledOnce(); + } + + public function provideDomainsAndRedirects(): iterable + { + yield 'full overwrite' => [Domain::withAuthority(''), [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => 'foo', + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => 'bar', + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => 'baz', + ], [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => 'foo', + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => 'bar', + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => 'baz', + ]]; + yield 'partial overwrite' => [Domain::withAuthority(''), [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => 'foo', + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => 'baz', + ], [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => 'foo', + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => null, + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => 'baz', + ]]; + yield 'no override' => [ + (static function (): Domain { + $domain = Domain::withAuthority(''); + $domain->configureNotFoundRedirects(NotFoundRedirects::withRedirects( + 'baz', + 'bar', + 'foo', + )); + + return $domain; + })(), + [], + [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => 'baz', + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => 'bar', + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => 'foo', + ], + ]; + yield 'reset' => [ + (static function (): Domain { + $domain = Domain::withAuthority(''); + $domain->configureNotFoundRedirects(NotFoundRedirects::withRedirects( + 'foo', + 'bar', + 'baz', + )); + + return $domain; + })(), + [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => null, + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => null, + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => null, + ], + [ + DomainRedirectsInputFilter::BASE_URL_REDIRECT => null, + DomainRedirectsInputFilter::REGULAR_404_REDIRECT => null, + DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT => null, + ], + ]; + } +}