prophesize(RouterInterface::class); $router->generateUri(Argument::cetera())->willReturn('/foo/bar'); $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); $this->options = new QrCodeOptions(); $this->action = new QrCodeAction( $this->urlResolver->reveal(), new ShortUrlStringifier(['domain' => 'doma.in']), new NullLogger(), $this->options, ); } /** @test */ public function aNotFoundShortCodeWillDelegateIntoNextMiddleware(): void { $shortCode = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, '')) ->willThrow(ShortUrlNotFoundException::class) ->shouldBeCalledOnce(); $delegate = $this->prophesize(RequestHandlerInterface::class); $process = $delegate->handle(Argument::any())->willReturn(new Response()); $this->action->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate->reveal()); $process->shouldHaveBeenCalledOnce(); } /** @test */ public function aCorrectRequestReturnsTheQrCodeResponse(): void { $shortCode = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, '')) ->willReturn(ShortUrl::createEmpty()) ->shouldBeCalledOnce(); $delegate = $this->prophesize(RequestHandlerInterface::class); $resp = $this->action->process( (new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate->reveal(), ); self::assertInstanceOf(QrCodeResponse::class, $resp); self::assertEquals(200, $resp->getStatusCode()); $delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0); } /** * @test * @dataProvider provideQueries */ public function imageIsReturnedWithExpectedContentTypeBasedOnProvidedFormat( string $defaultFormat, array $query, string $expectedContentType, ): void { $this->options->setFromArray(['format' => $defaultFormat]); $code = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( ShortUrl::createEmpty(), ); $delegate = $this->prophesize(RequestHandlerInterface::class); $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); $resp = $this->action->process($req, $delegate->reveal()); self::assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); } public function provideQueries(): iterable { yield 'no format, png default' => ['png', [], 'image/png']; yield 'no format, svg default' => ['svg', [], 'image/svg+xml']; yield 'png format, png default' => ['png', ['format' => 'png'], 'image/png']; yield 'png format, svg default' => ['svg', ['format' => 'png'], 'image/png']; yield 'svg format, png default' => ['png', ['format' => 'svg'], 'image/svg+xml']; yield 'svg format, svg default' => ['svg', ['format' => 'svg'], 'image/svg+xml']; yield 'unsupported format, png default' => ['png', ['format' => 'jpg'], 'image/png']; yield 'unsupported format, svg default' => ['svg', ['format' => 'jpg'], 'image/svg+xml']; } /** * @test * @dataProvider provideRequestsWithSize */ public function imageIsReturnedWithExpectedSize( array $defaults, ServerRequestInterface $req, int $expectedSize, ): void { $this->options->setFromArray($defaults); $code = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( ShortUrl::createEmpty(), ); $delegate = $this->prophesize(RequestHandlerInterface::class); $resp = $this->action->process($req->withAttribute('shortCode', $code), $delegate->reveal()); [$size] = getimagesizefromstring($resp->getBody()->__toString()); self::assertEquals($expectedSize, $size); } public function provideRequestsWithSize(): iterable { yield 'different margin and size defaults' => [ ['size' => 660, 'margin' => 40], ServerRequestFactory::fromGlobals(), 740, ]; yield 'no size' => [[], ServerRequestFactory::fromGlobals(), 300]; yield 'no size, different default' => [['size' => 500], ServerRequestFactory::fromGlobals(), 500]; yield 'size in query' => [[], ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']), 123]; yield 'size in query, default margin' => [ ['margin' => 25], ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']), 173, ]; yield 'margin' => [[], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '35']), 370]; yield 'margin and different default' => [ ['size' => 400], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '35']), 470, ]; yield 'margin and size' => [ [], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '100', 'size' => '200']), 400, ]; yield 'negative margin' => [[], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-50']), 300]; yield 'negative margin, default margin' => [ ['margin' => 10], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-50']), 300, ]; yield 'non-numeric margin' => [ [], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => 'foo']), 300, ]; yield 'negative margin and size' => [ [], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-1', 'size' => '150']), 150, ]; yield 'negative margin and size, default margin' => [ ['margin' => 5], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '-1', 'size' => '150']), 150, ]; yield 'non-numeric margin and size' => [ [], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => 'foo', 'size' => '538']), 538, ]; } /** * @test * @dataProvider provideRoundBlockSize */ public function imageCanRemoveExtraMarginWhenBlockRoundIsDisabled( array $defaults, ?string $roundBlockSize, int $expectedColor, ): void { $this->options->setFromArray($defaults); $code = 'abc123'; $req = ServerRequestFactory::fromGlobals() ->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize]) ->withAttribute('shortCode', $code); $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( ShortUrl::withLongUrl('https://shlink.io'), ); $delegate = $this->prophesize(RequestHandlerInterface::class); $resp = $this->action->process($req, $delegate->reveal()); $image = imagecreatefromstring($resp->getBody()->__toString()); $color = imagecolorat($image, 1, 1); self::assertEquals($color, $expectedColor); } public function provideRoundBlockSize(): iterable { yield 'no round block param' => [[], null, self::WHITE]; yield 'no round block param, but disabled by default' => [['round_block_size' => false], null, self::BLACK]; yield 'round block: "true"' => [[], 'true', self::WHITE]; yield 'round block: "true", but disabled by default' => [['round_block_size' => false], 'true', self::WHITE]; yield 'round block: "false"' => [[], 'false', self::BLACK]; yield 'round block: "false", but enabled by default' => [['round_block_size' => true], 'false', self::BLACK]; } }