From bdd741fc19086b78406c2225066892272261deaf Mon Sep 17 00:00:00 2001 From: Thibault Taillandier Date: Thu, 1 Feb 2024 08:07:35 +0100 Subject: [PATCH] Add 403UserCentricPreparator to handle random parameters that generates a 403 error because the user is not allowed to browse them --- .../Loader/OpenApiDefinitionLoader.php | 11 +++ src/Definition/Operation.php | 20 ++++++ .../Error403UserCentricPreparator.php | 71 +++++++++++++++++++ src/Preparator/Error404Preparator.php | 11 ++- 4 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/Preparator/Error403UserCentricPreparator.php diff --git a/src/Definition/Loader/OpenApiDefinitionLoader.php b/src/Definition/Loader/OpenApiDefinitionLoader.php index 82ceaa2..e84b146 100644 --- a/src/Definition/Loader/OpenApiDefinitionLoader.php +++ b/src/Definition/Loader/OpenApiDefinitionLoader.php @@ -36,6 +36,7 @@ use cebe\openapi\spec\MediaType; use cebe\openapi\spec\OAuthFlow; use cebe\openapi\spec\OpenApi; +use cebe\openapi\spec\Operation as OpenApiOperation; use cebe\openapi\spec\PathItem; use cebe\openapi\spec\RequestBody; use cebe\openapi\spec\Schema; @@ -115,6 +116,7 @@ private function getOperations(array $paths, array $securitySchemes): Operations ->setTags($this->getTags($operation->tags)) ->setSecurities($this->getSecurities($securitySchemes, $requirements)) ->setExamples($this->getExamples($operation, $parameters)) + ->setCustomParameters($this->getCustomParameters($operation)) ); } } @@ -122,6 +124,15 @@ private function getOperations(array $paths, array $securitySchemes): Operations return $operations; } + private function getCustomParameters(OpenApiOperation $operation): array + { + return array_filter( + (array) $operation->getSerializableData(), + fn ($v, $k) => str_starts_with($k, 'x-'), + ARRAY_FILTER_USE_BOTH + ); + } + /** * @param \cebe\openapi\spec\Server[] $servers */ diff --git a/src/Definition/Operation.php b/src/Definition/Operation.php index 5916ec4..8edf1dd 100644 --- a/src/Definition/Operation.php +++ b/src/Definition/Operation.php @@ -43,6 +43,8 @@ final class Operation implements Filterable private OperationExamples $examples; + private array $customParameters; + public function __construct( private readonly string $id, private readonly string $path, @@ -56,6 +58,7 @@ public function __construct( $this->tags = new Tags(); $this->securities = new Securities(); $this->examples = new OperationExamples(); + $this->customParameters = []; } public function addHeader(Parameter $header): self @@ -277,6 +280,23 @@ public function setExamples(OperationExamples $examples): self return $this; } + public function getCustomParameters(): array + { + return $this->customParameters; + } + + public function setCustomParameters(array $customParameters): self + { + $this->customParameters = $customParameters; + + return $this; + } + + public function isUserCentric(): bool + { + return isset($this->customParameters['x-apitester-user-centric-parameter']) && $this->customParameters['x-apitester-user-centric-parameter'] === true; + } + /** * @param array $params * diff --git a/src/Preparator/Error403UserCentricPreparator.php b/src/Preparator/Error403UserCentricPreparator.php new file mode 100644 index 0000000..49ae23f --- /dev/null +++ b/src/Preparator/Error403UserCentricPreparator.php @@ -0,0 +1,71 @@ + */ + return $operations + ->filter(fn ($operation) => $operation->isUserCentric()) + ->values() + ->map(function ($operation) { + return $this->prepareTestCase($operation); + }) + ->flatten() + ; + } + + /** + * @return array + */ + private function prepareTestCase(Operation $operation): array + { + $testcases = []; + + if ($operation->getRequestBodies()->count() === 0) { + $testcases[] = $this->buildTestCase( + OperationExample::create('RandomPath', $operation) + ->setForceRandom() + ->setResponse( + ResponseExample::create() + ->setStatusCode($this->config->response->getStatusCode() ?? '403') + ->setHeaders($this->config->response->headers ?? []) + ->setContent($this->config->response->body ?? null) + ) + ); + } + + foreach ($operation->getRequestBodies() as $ignored) { + $testcases[] = $this->buildTestCase( + OperationExample::create('RandomPath', $operation) + ->setForceRandom() + ->setResponse( + ResponseExample::create() + ->setStatusCode($this->config->response->getStatusCode() ?? '403') + ->setHeaders($this->config->response->headers ?? []) + ->setContent($this->config->response->body ?? null) + ) + ); + } + + return $testcases; + } +} diff --git a/src/Preparator/Error404Preparator.php b/src/Preparator/Error404Preparator.php index 08302be..0af8ac4 100644 --- a/src/Preparator/Error404Preparator.php +++ b/src/Preparator/Error404Preparator.php @@ -12,6 +12,10 @@ final class Error404Preparator extends TestCasesPreparator { + protected function getStatusCode(): string + { + return '404'; + } /** * @inheritDoc */ @@ -21,7 +25,8 @@ protected function prepare(Operations $operations): iterable return $operations ->select('responses.*') ->flatten() - ->where('statusCode', 404) + ->where('statusCode', $this->getStatusCode()) + ->filter(fn ($response) => !$response->getParent()->isUserCentric()) ->values() ->map(function ($response) { /** @var DefinitionResponse $response */ @@ -46,7 +51,7 @@ private function prepareTestCase(DefinitionResponse $response): array ->setForceRandom() ->setResponse( ResponseExample::create() - ->setStatusCode($this->config->response->getStatusCode() ?? '404') + ->setStatusCode($this->config->response->getStatusCode() ?? $this->getStatusCode()) ->setHeaders($this->config->response->headers ?? []) ->setContent($this->config->response->body ?? $response->getDescription()) ) @@ -59,7 +64,7 @@ private function prepareTestCase(DefinitionResponse $response): array ->setForceRandom() ->setResponse( ResponseExample::create() - ->setStatusCode($this->config->response->getStatusCode() ?? '404') + ->setStatusCode($this->config->response->getStatusCode() ?? $this->getStatusCode()) ->setHeaders($this->config->response->headers ?? []) ->setContent($this->config->response->body ?? $response->getDescription()) )