Skip to content

Commit

Permalink
fix(symfony): error in provider without uri variables (#6005)
Browse files Browse the repository at this point in the history
fixes #6002
  • Loading branch information
soyuka authored Nov 29, 2023
1 parent e7bc2ab commit c2be409
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 7 deletions.
21 changes: 14 additions & 7 deletions src/Symfony/Controller/MainController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\Exception\InvalidIdentifierException;
use ApiPlatform\Exception\InvalidUriVariableException;
use ApiPlatform\Metadata\Error;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProcessorInterface;
Expand Down Expand Up @@ -47,8 +48,11 @@ public function __construct(
public function __invoke(Request $request): Response
{
$operation = $this->initializeOperation($request);
$uriVariables = [];
if (!$operation) {
throw new RuntimeException('Not an API operation.');
}

$uriVariables = [];
if (!$operation instanceof Error) {
try {
$uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $operation->getClass());
Expand Down Expand Up @@ -80,12 +84,15 @@ public function __invoke(Request $request): Response
// The provider can change the Operation, extract it again from the Request attributes
if ($request->attributes->get('_api_operation') !== $operation) {
$operation = $this->initializeOperation($request);
try {
$uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $operation->getClass());
} catch (InvalidIdentifierException|InvalidUriVariableException $e) {
// if this occurs with our base operation we throw above so log instead of throw here
if ($this->logger) {
$this->logger->error($e->getMessage(), ['operation' => $operation]);

if (!$operation instanceof Error) {
try {
$uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $operation->getClass());
} catch (InvalidIdentifierException|InvalidUriVariableException $e) {
// if this occurs with our base operation we throw above so log instead of throw here
if ($this->logger) {
$this->logger->error($e->getMessage(), ['operation' => $operation]);
}
}
}
}
Expand Down
157 changes: 157 additions & 0 deletions tests/Symfony/Controller/MainControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Symfony\Controller;

use ApiPlatform\Metadata\Error;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\Symfony\Controller\MainController;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class MainControllerTest extends TestCase
{
public function testControllerNotSupported(): void
{
$this->expectException(RuntimeException::class);
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor);
$controller->__invoke(new Request());
}

public function testController(): void
{
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor);

$body = new \stdClass();
$response = new Response();
$request = new Request();
$request->attributes->set('_api_operation', new Get());

$provider->expects($this->once())
->method('provide')
->willReturn($body);

$processor->expects($this->once())
->method('process')
->willReturn($response);

$this->assertEquals($response, $controller->__invoke($request));
}

public function testControllerWithNonExistentUriVariables(): void
{
$this->expectException(NotFoundHttpException::class);
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor);

$body = new \stdClass();
$response = new Response();
$request = new Request();
$request->attributes->set('_api_operation', new Get(uriVariables: ['id' => new Link()]));

$controller->__invoke($request);
}

public function testControllerWithUriVariables(): void
{
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor);

$body = new \stdClass();
$response = new Response();
$request = new Request();
$request->attributes->set('_api_operation', new Get(uriVariables: ['id' => new Link()]));
$request->attributes->set('id', 0);

$provider->expects($this->once())
->method('provide')
->willReturn($body);

$processor->expects($this->once())
->method('process')
->willReturn($response);

$this->assertEquals($response, $controller->__invoke($request));
}

public function testControllerErrorWithUriVariables(): void
{
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor);

$body = new \stdClass();
$response = new Response();
$request = new Request();
$request->attributes->set('_api_operation', new Error(uriVariables: ['id' => new Link()]));

$provider->expects($this->once())
->method('provide')
->willReturn($body);

$processor->expects($this->once())
->method('process')
->willReturn($response);

$this->assertEquals($response, $controller->__invoke($request));
}

public function testControllerErrorWithUriVariablesDuringProvider(): void
{
$resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
$provider = $this->createMock(ProviderInterface::class);
$logger = $this->createMock(LoggerInterface::class);
$processor = $this->createMock(ProcessorInterface::class);
$controller = new MainController($resourceMetadataFactory, $provider, $processor, logger: $logger);

$response = new Response();
$request = new Request();
$request->attributes->set('_api_operation', new Get(uriVariables: ['id' => new Link()]));
$request->attributes->set('id', '1');

$provider->expects($this->once())
->method('provide')
->willReturnCallback(function () use ($request) {
$request->attributes->set('_api_operation', new Error(uriVariables: ['status' => new Link()]));
$request->attributes->remove('id');

return new \stdClass();
});

$logger->expects($this->never())->method('error');
$processor->expects($this->once())
->method('process')
->willReturn($response);

$this->assertEquals($response, $controller->__invoke($request));
}
}

0 comments on commit c2be409

Please sign in to comment.