From 1aaeb108c0e0ec17a81ae4d7c073f0fc765ded11 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 29 Oct 2024 17:42:31 -0500 Subject: [PATCH 01/15] Add Parameter to test against the URL and QueryString --- phpunit.xml.dist | 27 ++--- src/Base/Body.php | 2 +- src/Base/Parameter.php | 11 ++ src/Base/Schema.php | 37 ++++-- src/OpenApi/OpenApiSchema.php | 9 +- src/Swagger/SwaggerSchema.php | 9 +- tests/AbstractRequesterTest.php | 30 ++++- tests/OpenApiRequestBodyTest.php | 195 +++++++++++++++++------------- tests/OpenApiResponseBodyTest.php | 6 +- tests/SwaggerRequestBodyTest.php | 2 +- tests/SwaggerResponseBodyTest.php | 6 +- 11 files changed, 204 insertions(+), 130 deletions(-) create mode 100644 src/Base/Parameter.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8441f55..1d413ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,33 +4,32 @@ To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. --> - - + stopOnFailure="false" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> - - - + + + - - - + + + ./src - - - + + + ./tests/ - diff --git a/src/Base/Body.php b/src/Base/Body.php index 180a5fe..450ffaa 100644 --- a/src/Base/Body.php +++ b/src/Base/Body.php @@ -209,7 +209,7 @@ protected function matchTypes(string $name, mixed $schemaArray, mixed $body): ?b } $type = $schemaArray['type']; - $nullable = isset($schemaArray['nullable']) ? (bool)$schemaArray['nullable'] : $this->schema->isAllowNullValues(); + $nullable = isset($schemaArray['nullable']) ? (bool)$schemaArray['nullable'] : ($this->allowNullValues || $this->schema->isAllowNullValues()); $validators = [ function () use ($name, $body, $type, $nullable) diff --git a/src/Base/Parameter.php b/src/Base/Parameter.php new file mode 100644 index 0000000..9b00451 --- /dev/null +++ b/src/Base/Parameter.php @@ -0,0 +1,11 @@ +matchSchema($this->name, $this->structure, $body) ?? false; + } +} \ No newline at end of file diff --git a/src/Base/Schema.php b/src/Base/Schema.php index 992496c..37d83a2 100644 --- a/src/Base/Schema.php +++ b/src/Base/Schema.php @@ -81,6 +81,10 @@ public function getPathDefinition(string $path, string $method): mixed // Try direct match if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) { if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) { + + parse_str($uri->getQuery(), $matches); + $this->prepareToValidateArguments($uri->getPath(), $method, 'query', $matches); + return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method]; } throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'"); @@ -104,18 +108,9 @@ public function getPathDefinition(string $path, string $method): mixed throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'"); } - $parametersPathMethod = []; - $parametersPath = []; - - if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) { - $parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS]; - } - - if (isset($pathDef[self::SWAGGER_PARAMETERS])) { - $parametersPath = $pathDef[self::SWAGGER_PARAMETERS]; - } - - $this->validateArguments('path', array_merge($parametersPathMethod, $parametersPath), $matches); + $this->prepareToValidateArguments($pathItem, $method, 'path', $matches); + parse_str($uri->getQuery(), $queryParsed); + $this->prepareToValidateArguments($pathItem, $method, 'query', $queryParsed); return $pathDef[$method]; } @@ -124,6 +119,24 @@ public function getPathDefinition(string $path, string $method): mixed throw new PathNotFoundException('Path "' . $path . '" not found'); } + protected function prepareToValidateArguments(string $path, string $method, string $parameterIn, $matches): void + { + $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$path]; + + $parametersPathMethod = []; + $parametersPath = []; + + if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) { + $parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS]; + } + + if (isset($pathDef[self::SWAGGER_PARAMETERS])) { + $parametersPath = $pathDef[self::SWAGGER_PARAMETERS]; + } + + $this->validateArguments($parameterIn, array_merge($parametersPathMethod, $parametersPath), $matches); + } + /** * @param string $path * @param string $method diff --git a/src/OpenApi/OpenApiSchema.php b/src/OpenApi/OpenApiSchema.php index 0880609..da8be2b 100644 --- a/src/OpenApi/OpenApiSchema.php +++ b/src/OpenApi/OpenApiSchema.php @@ -3,11 +3,11 @@ namespace ByJG\ApiTools\OpenApi; use ByJG\ApiTools\Base\Body; +use ByJG\ApiTools\Base\Parameter; use ByJG\ApiTools\Base\Schema; use ByJG\ApiTools\Exception\DefinitionNotFoundException; use ByJG\ApiTools\Exception\InvalidDefinitionException; use ByJG\ApiTools\Exception\InvalidRequestException; -use ByJG\ApiTools\Exception\NotMatchedException; use ByJG\Util\Uri; class OpenApiSchema extends Schema @@ -77,10 +77,9 @@ protected function validateArguments(string $parameterIn, array $parameters, arr } $parameter = $this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]]; } - if ($parameter['in'] === $parameterIn && - $parameter['schema']['type'] === "integer" - && filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) { - throw new NotMatchedException('Path expected an integer value'); + if ($parameter['in'] === $parameterIn) { + $parameterMatch = new Parameter($this, $parameter['name'], $parameter["schema"] ?? [], true); + $parameterMatch->match($arguments[$parameter['name']] ?? null); } } } diff --git a/src/Swagger/SwaggerSchema.php b/src/Swagger/SwaggerSchema.php index af432ef..d8003dd 100644 --- a/src/Swagger/SwaggerSchema.php +++ b/src/Swagger/SwaggerSchema.php @@ -3,11 +3,11 @@ namespace ByJG\ApiTools\Swagger; use ByJG\ApiTools\Base\Body; +use ByJG\ApiTools\Base\Parameter; use ByJG\ApiTools\Base\Schema; use ByJG\ApiTools\Exception\DefinitionNotFoundException; use ByJG\ApiTools\Exception\InvalidDefinitionException; use ByJG\ApiTools\Exception\InvalidRequestException; -use ByJG\ApiTools\Exception\NotMatchedException; class SwaggerSchema extends Schema { @@ -59,10 +59,9 @@ public function getServerUrl(): string protected function validateArguments(string $parameterIn, array $parameters, array $arguments): void { foreach ($parameters as $parameter) { - if ($parameter['in'] === $parameterIn - && $parameter['type'] === "integer" - && filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) { - throw new NotMatchedException('Path expected an integer value'); + if ($parameter['in'] === $parameterIn) { + $parameterMatch = new Parameter($this, $parameter['name'], $parameter ?? []); + $parameterMatch->match($arguments[$parameter['name']] ?? null); } } } diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index 0c4a1db..8568459 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -77,7 +77,7 @@ public function testExpectOK() */ public function testExpectError() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Required property 'name'"); $expectedResponse = Response::getInstance(200) @@ -94,6 +94,26 @@ public function testExpectError() $this->assertRequest($request); } + public function testExpectParamError() + { + $expectedResponse = Response::getInstance(200) + ->withBody(new MemoryStream(json_encode([ + "id" => 1, + "name" => "Spike", + "photoUrls" => [] + ]))); + + // Basic Request + $request = new MockRequester($expectedResponse); + $request + ->withMethod('GET') + ->withPath("/pet/ABC"); + + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Expected 'petId' to be numeric, but found 'ABC'."); + $this->assertRequest($request); + } + /** * @throws DefinitionNotFoundException * @throws GenericSwaggerException @@ -166,7 +186,7 @@ public function testValidateAssertResponse404() */ public function testValidateAssertResponse404WithContent() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Expected empty body for GET 404 /v2/pet/1"); $expectedResponse = Response::getInstance(404) @@ -258,7 +278,7 @@ public function testValidateAssertHeaderContains() */ public function testValidateAssertHeaderContainsWrongValue() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Does not exists header 'X-Test' with value 'Different'"); $expectedResponse = Response::getInstance(200) @@ -294,7 +314,7 @@ public function testValidateAssertHeaderContainsWrongValue() */ public function testValidateAssertHeaderContainsNonExistent() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Does not exists header 'X-Test' with value 'Different'"); $expectedResponse = Response::getInstance(200) @@ -361,7 +381,7 @@ public function testValidateAssertBodyContains() */ public function testValidateAssertBodyNotContains() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Body does not contain 'Doris'"); $expectedResponse = Response::getInstance(200) diff --git a/tests/OpenApiRequestBodyTest.php b/tests/OpenApiRequestBodyTest.php index c16b8ee..e24ae05 100644 --- a/tests/OpenApiRequestBodyTest.php +++ b/tests/OpenApiRequestBodyTest.php @@ -3,23 +3,25 @@ namespace Tests; use ByJG\ApiTools\Exception\DefinitionNotFoundException; +use ByJG\ApiTools\Exception\GenericSwaggerException; use ByJG\ApiTools\Exception\HttpMethodNotFoundException; use ByJG\ApiTools\Exception\InvalidDefinitionException; use ByJG\ApiTools\Exception\InvalidRequestException; use ByJG\ApiTools\Exception\NotMatchedException; use ByJG\ApiTools\Exception\PathNotFoundException; +use ByJG\ApiTools\Exception\RequiredArgumentNotFound; class OpenApiRequestBodyTest extends OpenApiBodyTestCase { /** - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBody() { @@ -38,18 +40,18 @@ public function testMatchRequestBody() /** * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequiredRequestBodyEmpty() { - $this->expectException(\ByJG\ApiTools\Exception\RequiredArgumentNotFound::class); + $this->expectException(RequiredArgumentNotFound::class); $this->expectExceptionMessage("The body is required"); $requestParameter = self::openApiSchema()->getRequestParameters('/v2/store/order', 'post'); @@ -58,18 +60,18 @@ public function testMatchRequiredRequestBodyEmpty() /** * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchInexistantBodyDefinition() { - $this->expectException(\ByJG\ApiTools\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage("Body is passed but there is no request body definition"); $body = [ @@ -96,8 +98,8 @@ public function testMatchInexistantBodyDefinition() */ public function testMatchDataType() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); - $this->expectExceptionMessage("Path expected an integer value"); + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Expected 'petId' to be numeric, but found 'STRING'"); self::openApiSchema()->getRequestParameters('/v2/pet/STRING', 'get'); $this->assertTrue(true); @@ -119,6 +121,14 @@ public function testMatchParameterInQuery() $this->assertTrue(true); } + public function testMatchParameterInQueryNotValid() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Value 'ABC' in 'status' not matched in ENUM"); + + self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus?status=ABC', 'get'); + } + /** * @throws DefinitionNotFoundException * @throws HttpMethodNotFoundException @@ -144,28 +154,51 @@ public function testMatchParameterInQuery2() */ public function testMatchParameterInQuery3() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); - $this->expectExceptionMessage("Path expected an integer value"); + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Expected 'test_id' to be numeric, but found 'STRING'"); self::openApiSchema3()->getRequestParameters('/tests/STRING?count=20&offset=2', 'get'); $this->assertTrue(true); } + public function testMatchParameterInQuery4() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Expected 'count' to be numeric, but found 'ABC'"); + + self::openApiSchema3()->getRequestParameters('/tests/12345?count=ABC&offset=2', 'get'); + $this->assertTrue(true); + } + + public function testMatchParameterInQuery5() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Expected 'offset' to be numeric, but found 'ABC'"); + + self::openApiSchema3()->getRequestParameters('/tests/12345?count=20&offset=ABC', 'get'); + $this->assertTrue(true); + } + + public function testMatchParameterInQuery6() + { + self::openApiSchema3()->getRequestParameters('/tests/12345', 'get'); + $this->assertTrue(true); + } /** * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequired1() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Required property"); $body = [ @@ -181,18 +214,18 @@ public function testMatchRequestBodyRequired1() * It is not OK when allowNullValues is false (as by default) { name: null } * https://stackoverflow.com/questions/45575493/what-does-required-in-openapi-really-mean * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequiredNullsNotAllowed() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Value of property 'name' is null, but should be of type 'string'"); $body = [ @@ -207,14 +240,14 @@ public function testMatchRequestBodyRequiredNullsNotAllowed() } /** - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequiredNullsAllowed() { @@ -234,14 +267,14 @@ public function testMatchRequestBodyRequiredNullsAllowed() * It is OK: { name: ""} * https://stackoverflow.com/questions/45575493/what-does-required-in-openapi-really-mean * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequired3() { @@ -259,14 +292,14 @@ public function testMatchRequestBodyRequired3() /** * issue #21 * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequired_Issue21() { @@ -283,18 +316,18 @@ public function testMatchRequestBodyRequired_Issue21() /** * Issue #21 * - * @throws \ByJG\ApiTools\Exception\DefinitionNotFoundException - * @throws \ByJG\ApiTools\Exception\GenericSwaggerException - * @throws \ByJG\ApiTools\Exception\HttpMethodNotFoundException - * @throws \ByJG\ApiTools\Exception\InvalidDefinitionException - * @throws \ByJG\ApiTools\Exception\InvalidRequestException - * @throws \ByJG\ApiTools\Exception\NotMatchedException - * @throws \ByJG\ApiTools\Exception\PathNotFoundException - * @throws \ByJG\ApiTools\Exception\RequiredArgumentNotFound + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound */ public function testMatchRequestBodyRequired_Issue21_Required() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); + $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Required property 'user_uuid'"); // Missing Request diff --git a/tests/OpenApiResponseBodyTest.php b/tests/OpenApiResponseBodyTest.php index 8bcce37..b505db6 100644 --- a/tests/OpenApiResponseBodyTest.php +++ b/tests/OpenApiResponseBodyTest.php @@ -432,7 +432,7 @@ public function testIssue9() ], ]; - $responseParameter = $this->openApiSchema2()->getResponseParameters('/v2/languages', 'get', 200); + $responseParameter = $this->openApiSchema2()->getResponseParameters('/v2/languages?site=test', 'get', 200); $this->assertTrue($responseParameter->match($body)); } @@ -466,7 +466,7 @@ public function testIssue9Error() ] ]; - $responseParameter = $this->openApiSchema2()->getResponseParameters('/v2/languages', 'get', 200); + $responseParameter = $this->openApiSchema2()->getResponseParameters('/v2/languages?site=test', 'get', 200); $this->assertTrue($responseParameter->match($body)); } @@ -547,6 +547,6 @@ public function testResponseWithNoDefault() $this->expectExceptionMessage("Could not found status code '503'"); $body = []; - $responseParameter = $this->openApiSchema()->getResponseParameters('/v2/user/login', 'get', 503); + $responseParameter = $this->openApiSchema()->getResponseParameters('/v2/user/login?username=foo&password=bar', 'get', 503); } } diff --git a/tests/SwaggerRequestBodyTest.php b/tests/SwaggerRequestBodyTest.php index 0acd411..32b1cf9 100644 --- a/tests/SwaggerRequestBodyTest.php +++ b/tests/SwaggerRequestBodyTest.php @@ -94,7 +94,7 @@ public function testMatchInexistantBodyDefinition() public function testMatchDataType() { $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); - $this->expectExceptionMessage("Path expected an integer value"); + $this->expectExceptionMessage("Expected 'petId' to be numeric, but found 'STRING'"); self::swaggerSchema()->getRequestParameters('/v2/pet/STRING', 'get'); $this->assertTrue(true); diff --git a/tests/SwaggerResponseBodyTest.php b/tests/SwaggerResponseBodyTest.php index f1d4bda..61aa6a7 100644 --- a/tests/SwaggerResponseBodyTest.php +++ b/tests/SwaggerResponseBodyTest.php @@ -479,7 +479,7 @@ public function testIssue9() ] ], ]; - $responseParameter = $this->swaggerSchema2()->getResponseParameters('/v2/languages', 'get', 200); + $responseParameter = $this->swaggerSchema2()->getResponseParameters('/v2/languages?site=test', 'get', 200); $this->assertTrue($responseParameter->match($body)); } @@ -513,7 +513,7 @@ public function testIssue9Error() "isDefault" => false ] ]; - $responseParameter = $this->swaggerSchema2()->getResponseParameters('/v2/languages', 'get', 200); + $responseParameter = $this->swaggerSchema2()->getResponseParameters('/v2/languages?site=test', 'get', 200); $this->assertTrue($responseParameter->match($body)); } @@ -574,6 +574,6 @@ public function testResponseWithNoDefault() $this->expectExceptionMessage("Could not found status code '503'"); $body = []; - $responseParameter = $this->swaggerSchema()->getResponseParameters('/v2/user/login', 'get', 503); + $responseParameter = $this->swaggerSchema()->getResponseParameters('/v2/user/login?username=foo&password=bar', 'get', 503); } } From 8e550dd1af633caeb2e7eb25ed31ba62f918dafc Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 1 Nov 2024 13:38:58 -0500 Subject: [PATCH 02/15] Minor change in the error message --- src/Base/Body.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Base/Body.php b/src/Base/Body.php index 450ffaa..5476670 100644 --- a/src/Base/Body.php +++ b/src/Base/Body.php @@ -89,7 +89,7 @@ protected function matchString(string $name, array $schemaArray, mixed $body, mi } if (!is_string($body)) { - throw new NotMatchedException("Value '" . var_export($body, true) . "' in '$name' is not string. ", $this->structure); + throw new NotMatchedException("Value '" . str_replace("\n", "", var_export($body, true)) . "' in '$name' is not string. ", $this->structure); } return true; From d842d0bf5f9879b61f5bfda068df06e77103bfe3 Mon Sep 17 00:00:00 2001 From: cdenadai Date: Mon, 11 Nov 2024 18:54:59 -0300 Subject: [PATCH 03/15] feat: adjust on openapi body response to accept text/html --- src/OpenApi/OpenApiResponseBody.php | 13 ++++++++++-- tests/OpenApiResponseBodyTest.php | 17 +++++++++++++++ tests/example/openapi.json | 32 +++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/OpenApi/OpenApiResponseBody.php b/src/OpenApi/OpenApiResponseBody.php index 18fe74b..fbf98e7 100644 --- a/src/OpenApi/OpenApiResponseBody.php +++ b/src/OpenApi/OpenApiResponseBody.php @@ -23,7 +23,16 @@ public function match(mixed $body): bool $definition = $this->schema->getDefinition($this->structure['$ref']); return $this->matchSchema($this->name, $definition, $body) ?? false; } - - return $this->matchSchema($this->name, $this->structure['content'][key($this->structure['content'])]['schema'], $body) ?? false; + + foreach ($this->structure['content'] as $contentType => $schema) { + if ($contentType === 'application/json') { + if (!isset($schema['schema'])) { + throw new NotMatchedException("Content type " . $contentType . " does not have schema"); + } + return $this->matchSchema($this->name, $schema['schema'], $body) ?? false; + } + } + + return true; } } diff --git a/tests/OpenApiResponseBodyTest.php b/tests/OpenApiResponseBodyTest.php index b505db6..43c9c03 100644 --- a/tests/OpenApiResponseBodyTest.php +++ b/tests/OpenApiResponseBodyTest.php @@ -65,6 +65,23 @@ public function testMatchResponseBody() $this->assertTrue($responseParameter->match($body)); } + public function testMatchResponseBodyWithHtmlResponse() + { + $openApiSchema = self::openApiSchema(); + + $body = [ + "id" => 10, + "petId" => 50, + "quantity" => 1, + "shipDate" => '2010-10-20', + "status" => 'placed', + "complete" => true + ]; + + $responseParameter = $openApiSchema->getResponseParameters('/v2/store/orderhtml', 'post', 200); + $this->assertTrue($responseParameter->match($body)); + } + /** * @throws DefinitionNotFoundException * @throws GenericSwaggerException diff --git a/tests/example/openapi.json b/tests/example/openapi.json index 458a8fc..06ec6ec 100644 --- a/tests/example/openapi.json +++ b/tests/example/openapi.json @@ -564,6 +564,38 @@ } } }, + "/store/orderhtml": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "order placed for purchasing the pet", + "required": true + }, + "responses": { + "200": { + "description": "The object to be created", + "content": { + "text/html": {} + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, "/store/order/{orderId}": { "get": { "tags": [ From c15357351193045cd8084f47936c3f027d660981 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 22 Nov 2024 16:53:56 -0600 Subject: [PATCH 04/15] Added BaseApiTestCase::assertRequestException() --- src/ApiTestCase.php | 15 ++++++++ tests/AbstractRequesterTest.php | 64 ++++----------------------------- tests/TestingTestCase.php | 23 +++--------- 3 files changed, 26 insertions(+), 76 deletions(-) diff --git a/src/ApiTestCase.php b/src/ApiTestCase.php index 88e890c..51197fb 100644 --- a/src/ApiTestCase.php +++ b/src/ApiTestCase.php @@ -136,6 +136,21 @@ public function assertRequest(AbstractRequester $request): ResponseInterface return $body; } + public function assertRequestException(AbstractRequester $request, string $exceptionClass, string $exceptionMessage = null): void + { + try { + $this->assertRequest($request); + $this->fail("Expected exception " . $exceptionClass); + } catch (\Exception $ex) { + $this->assertInstanceOf($exceptionClass, $ex); + + if (!empty($exceptionMessage)) { + $this->assertStringContainsString($exceptionMessage, $ex->getMessage()); + } + } + } + + /** * @throws GenericSwaggerException */ diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index 8568459..d631838 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -13,12 +13,12 @@ use ByJG\ApiTools\Exception\RequiredArgumentNotFound; use ByJG\ApiTools\Exception\StatusCodeNotMatchedException; use ByJG\ApiTools\MockRequester; +use ByJG\Util\Uri; use ByJG\WebRequest\Exception\MessageException; use ByJG\WebRequest\Exception\RequestException; +use ByJG\WebRequest\Psr7\MemoryStream; use ByJG\WebRequest\Psr7\Request; use ByJG\WebRequest\Psr7\Response; -use ByJG\Util\Uri; -use ByJG\WebRequest\Psr7\MemoryStream; abstract class AbstractRequesterTest extends ApiTestCase { @@ -109,9 +109,7 @@ public function testExpectParamError() ->withMethod('GET') ->withPath("/pet/ABC"); - $this->expectException(NotMatchedException::class); - $this->expectExceptionMessage("Expected 'petId' to be numeric, but found 'ABC'."); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Expected 'petId' to be numeric, but found 'ABC'."); } /** @@ -172,23 +170,11 @@ public function testValidateAssertResponse404() } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException * @throws MessageException - * @throws NotMatchedException - * @throws PathNotFoundException * @throws RequestException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testValidateAssertResponse404WithContent() { - $this->expectException(NotMatchedException::class); - $this->expectExceptionMessage("Expected empty body for GET 404 /v2/pet/1"); - $expectedResponse = Response::getInstance(404) ->withBody(new MemoryStream('{"error":"not found"}')); @@ -198,27 +184,15 @@ public function testValidateAssertResponse404WithContent() ->withPath("/pet/1") ->assertResponseCode(404); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Expected empty body for GET 404 /v2/pet/1"); } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException * @throws MessageException - * @throws NotMatchedException - * @throws PathNotFoundException * @throws RequestException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testValidateAssertResponseNotExpected() { - $this->expectException(\ByJG\ApiTools\Exception\StatusCodeNotMatchedException::class); - $this->expectExceptionMessage("Status code not matched: Expected 404, got 522"); - $expectedResponse = Response::getInstance(522); $request = new MockRequester($expectedResponse); @@ -227,7 +201,7 @@ public function testValidateAssertResponseNotExpected() ->withPath("/pet/1") ->assertResponseCode(404); - $this->assertRequest($request); + $this->assertRequestException($request, StatusCodeNotMatchedException::class, "Status code not matched: Expected 404, got 522"); } /** @@ -264,23 +238,11 @@ public function testValidateAssertHeaderContains() } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException * @throws MessageException - * @throws NotMatchedException - * @throws PathNotFoundException * @throws RequestException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testValidateAssertHeaderContainsWrongValue() { - $this->expectException(NotMatchedException::class); - $this->expectExceptionMessage("Does not exists header 'X-Test' with value 'Different'"); - $expectedResponse = Response::getInstance(200) ->withBody(new MemoryStream(json_encode([ "id" => 1, @@ -296,27 +258,15 @@ public function testValidateAssertHeaderContainsWrongValue() ->assertResponseCode(200) ->assertHeaderContains("X-Test", "Different"); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Does not exists header 'X-Test' with value 'Different'"); } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException * @throws MessageException - * @throws NotMatchedException - * @throws PathNotFoundException * @throws RequestException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testValidateAssertHeaderContainsNonExistent() { - $this->expectException(NotMatchedException::class); - $this->expectExceptionMessage("Does not exists header 'X-Test' with value 'Different'"); - $expectedResponse = Response::getInstance(200) ->withBody(new MemoryStream(json_encode([ "id" => 1, @@ -331,7 +281,7 @@ public function testValidateAssertHeaderContainsNonExistent() ->assertResponseCode(200) ->assertHeaderContains("X-Test", "Different"); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Does not exists header 'X-Test' with value 'Different'"); } /** diff --git a/tests/TestingTestCase.php b/tests/TestingTestCase.php index ddfa8b6..e643d5a 100644 --- a/tests/TestingTestCase.php +++ b/tests/TestingTestCase.php @@ -14,14 +14,14 @@ use ByJG\ApiTools\Exception\RequiredArgumentNotFound; use ByJG\ApiTools\Exception\StatusCodeNotMatchedException; use ByJG\ApiTools\MockRequester; +use ByJG\Util\Uri; use ByJG\WebRequest\Exception\MessageException; use ByJG\WebRequest\Exception\RequestException; use ByJG\WebRequest\Helper\RequestMultiPart; use ByJG\WebRequest\MultiPartItem; +use ByJG\WebRequest\Psr7\MemoryStream; use ByJG\WebRequest\Psr7\Request; use ByJG\WebRequest\Psr7\Response; -use ByJG\Util\Uri; -use ByJG\WebRequest\Psr7\MemoryStream; /** * Class TestingTestCase @@ -115,9 +115,6 @@ public function testPost() */ public function testAddError() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); - $this->expectExceptionMessage("Required property 'name'"); - $request = new ApiRequester(); $request ->withMethod('POST') @@ -130,25 +127,13 @@ public function testAddError() 'status' => 'available' ]); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Required property 'name'"); } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException - * @throws NotMatchedException - * @throws PathNotFoundException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testPostError() { - $this->expectException(\ByJG\ApiTools\Exception\NotMatchedException::class); - $this->expectExceptionMessage("Expected empty body"); - $request = new ApiRequester(); $request ->withMethod('POST') @@ -162,7 +147,7 @@ public function testPostError() 'status' => 'available' ]); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Expected empty body"); } /** From 96d5124df3ba7dff76f1baf26924548d8033a682 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 22 Nov 2024 17:01:16 -0600 Subject: [PATCH 05/15] Add AbstractRequester::getPsr7Request() --- src/AbstractRequester.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index 18ad4ad..671a55d 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -12,11 +12,11 @@ use ByJG\ApiTools\Exception\PathNotFoundException; use ByJG\ApiTools\Exception\RequiredArgumentNotFound; use ByJG\ApiTools\Exception\StatusCodeNotMatchedException; +use ByJG\Util\Uri; use ByJG\WebRequest\Exception\MessageException; use ByJG\WebRequest\Exception\RequestException; -use ByJG\WebRequest\Psr7\Request; -use ByJG\Util\Uri; use ByJG\WebRequest\Psr7\MemoryStream; +use ByJG\WebRequest\Psr7\Request; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -171,6 +171,11 @@ public function withPsr7Request(RequestInterface $requestInterface): self return $this; } + public function getPsr7Request(): RequestInterface + { + return $this->psr7Request; + } + public function assertResponseCode(int $code): self { $this->statusExpected = $code; From 862b47a15dd2a892bae4e7c73e75642780da5790 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 22 Nov 2024 21:00:00 -0600 Subject: [PATCH 06/15] Minor Changes --- src/ApiTestCase.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ApiTestCase.php b/src/ApiTestCase.php index 51197fb..3076578 100644 --- a/src/ApiTestCase.php +++ b/src/ApiTestCase.php @@ -15,6 +15,7 @@ use ByJG\WebRequest\Psr7\Response; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; +use Throwable; abstract class ApiTestCase extends TestCase { @@ -136,17 +137,19 @@ public function assertRequest(AbstractRequester $request): ResponseInterface return $body; } - public function assertRequestException(AbstractRequester $request, string $exceptionClass, string $exceptionMessage = null): void + public function assertRequestException(AbstractRequester $request, string $exceptionClass, string $exceptionMessage = null): Throwable { try { $this->assertRequest($request); $this->fail("Expected exception " . $exceptionClass); - } catch (\Exception $ex) { + } catch (Throwable $ex) { $this->assertInstanceOf($exceptionClass, $ex); if (!empty($exceptionMessage)) { $this->assertStringContainsString($exceptionMessage, $ex->getMessage()); } + + return $ex; } } From 9a9fe4df32133b21a70364b28cf83df334d49893 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 27 Nov 2024 14:30:03 -0600 Subject: [PATCH 07/15] Minor Changes --- src/ApiTestCase.php | 2 +- tests/AbstractRequesterTest.php | 32 +++++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/ApiTestCase.php b/src/ApiTestCase.php index 3076578..4d878f6 100644 --- a/src/ApiTestCase.php +++ b/src/ApiTestCase.php @@ -141,7 +141,6 @@ public function assertRequestException(AbstractRequester $request, string $excep { try { $this->assertRequest($request); - $this->fail("Expected exception " . $exceptionClass); } catch (Throwable $ex) { $this->assertInstanceOf($exceptionClass, $ex); @@ -151,6 +150,7 @@ public function assertRequestException(AbstractRequester $request, string $excep return $ex; } + $this->fail("Expected exception '{$exceptionClass}' but no exception was thrown"); } diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index d631838..bb69019 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -63,23 +63,11 @@ public function testExpectOK() } /** - * @throws DefinitionNotFoundException - * @throws GenericSwaggerException - * @throws HttpMethodNotFoundException - * @throws InvalidDefinitionException - * @throws InvalidRequestException * @throws MessageException - * @throws NotMatchedException - * @throws PathNotFoundException * @throws RequestException - * @throws RequiredArgumentNotFound - * @throws StatusCodeNotMatchedException */ public function testExpectError() { - $this->expectException(NotMatchedException::class); - $this->expectExceptionMessage("Required property 'name'"); - $expectedResponse = Response::getInstance(200) ->withBody(new MemoryStream(json_encode([ "id" => 1, @@ -91,7 +79,7 @@ public function testExpectError() ->withMethod('GET') ->withPath("/pet/1"); - $this->assertRequest($request); + $this->assertRequestException($request, NotMatchedException::class, "Required property 'name'"); } public function testExpectParamError() @@ -112,6 +100,24 @@ public function testExpectParamError() $this->assertRequestException($request, NotMatchedException::class, "Expected 'petId' to be numeric, but found 'ABC'."); } + public function testExpectParamErrorRequired() + { + $expectedResponse = Response::getInstance(200) + ->withBody(new MemoryStream(json_encode([ + "id" => 1, + "name" => "Spike", + "photoUrls" => [] + ]))); + + // Basic Request + $request = new MockRequester($expectedResponse); + $request + ->withMethod('GET') + ->withPath("/pet/"); + + $this->assertRequestException($request, NotMatchedException::class, "Expected 'petId' to be numeric, but found 'ABC'."); + } + /** * @throws DefinitionNotFoundException * @throws GenericSwaggerException From 830969996299af0fbd51a92171a41ab0cd7c9661 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 27 Nov 2024 14:34:11 -0600 Subject: [PATCH 08/15] Minor Changes --- tests/AbstractRequesterTest.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index bb69019..b5ceaa1 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -100,24 +100,6 @@ public function testExpectParamError() $this->assertRequestException($request, NotMatchedException::class, "Expected 'petId' to be numeric, but found 'ABC'."); } - public function testExpectParamErrorRequired() - { - $expectedResponse = Response::getInstance(200) - ->withBody(new MemoryStream(json_encode([ - "id" => 1, - "name" => "Spike", - "photoUrls" => [] - ]))); - - // Basic Request - $request = new MockRequester($expectedResponse); - $request - ->withMethod('GET') - ->withPath("/pet/"); - - $this->assertRequestException($request, NotMatchedException::class, "Expected 'petId' to be numeric, but found 'ABC'."); - } - /** * @throws DefinitionNotFoundException * @throws GenericSwaggerException From fb52d3d623efb2955bf185da5bf680e81984c7c0 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 27 Nov 2024 16:38:14 -0600 Subject: [PATCH 09/15] Fix Parameter Required --- src/Base/Schema.php | 7 +++++-- src/OpenApi/OpenApiSchema.php | 2 +- src/Swagger/SwaggerSchema.php | 2 +- tests/OpenApiRequestBodyTest.php | 24 ++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Base/Schema.php b/src/Base/Schema.php index 37d83a2..42c8154 100644 --- a/src/Base/Schema.php +++ b/src/Base/Schema.php @@ -119,6 +119,11 @@ public function getPathDefinition(string $path, string $method): mixed throw new PathNotFoundException('Path "' . $path . '" not found'); } + /** + * @throws DefinitionNotFoundException + * @throws NotMatchedException + * @throws InvalidDefinitionException + */ protected function prepareToValidateArguments(string $path, string $method, string $parameterIn, $matches): void { $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$path]; @@ -142,11 +147,9 @@ protected function prepareToValidateArguments(string $path, string $method, stri * @param string $method * @param int $status * @return Body - * @throws DefinitionNotFoundException * @throws HttpMethodNotFoundException * @throws InvalidDefinitionException * @throws InvalidRequestException - * @throws NotMatchedException * @throws PathNotFoundException */ public function getResponseParameters(string $path, string $method, int $status): Body diff --git a/src/OpenApi/OpenApiSchema.php b/src/OpenApi/OpenApiSchema.php index da8be2b..3873455 100644 --- a/src/OpenApi/OpenApiSchema.php +++ b/src/OpenApi/OpenApiSchema.php @@ -78,7 +78,7 @@ protected function validateArguments(string $parameterIn, array $parameters, arr $parameter = $this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]]; } if ($parameter['in'] === $parameterIn) { - $parameterMatch = new Parameter($this, $parameter['name'], $parameter["schema"] ?? [], true); + $parameterMatch = new Parameter($this, $parameter['name'], $parameter["schema"] ?? [], !($parameter["required"] ?? false)); $parameterMatch->match($arguments[$parameter['name']] ?? null); } } diff --git a/src/Swagger/SwaggerSchema.php b/src/Swagger/SwaggerSchema.php index d8003dd..8d2eceb 100644 --- a/src/Swagger/SwaggerSchema.php +++ b/src/Swagger/SwaggerSchema.php @@ -60,7 +60,7 @@ protected function validateArguments(string $parameterIn, array $parameters, arr { foreach ($parameters as $parameter) { if ($parameter['in'] === $parameterIn) { - $parameterMatch = new Parameter($this, $parameter['name'], $parameter ?? []); + $parameterMatch = new Parameter($this, $parameter['name'], $parameter ?? [], !($parameter["required"] ?? false)); $parameterMatch->match($arguments[$parameter['name']] ?? null); } } diff --git a/tests/OpenApiRequestBodyTest.php b/tests/OpenApiRequestBodyTest.php index e24ae05..1b4f885 100644 --- a/tests/OpenApiRequestBodyTest.php +++ b/tests/OpenApiRequestBodyTest.php @@ -121,6 +121,30 @@ public function testMatchParameterInQuery() $this->assertTrue(true); } + public function testMatchParameterInQueryRequired() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Value of property 'status' is null, but should be of type 'array'"); + + self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus', 'get'); + } + + public function testMatchParameterInQueryRequired2() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Value of property 'username' is null, but should be of type 'string"); + + self::openApiSchema()->getRequestParameters('/user/login', 'get'); + } + + public function testMatchParameterInQueryRequired3() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("Value of property 'password' is null, but should be of type 'string"); + + self::openApiSchema()->getRequestParameters('/user/login?username=test', 'get'); + } + public function testMatchParameterInQueryNotValid() { $this->expectException(NotMatchedException::class); From e204db9856e1549cfc1007e0946cd537fa35c1b2 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 27 Nov 2024 17:14:26 -0600 Subject: [PATCH 10/15] Fix Parameter Required --- src/Base/Schema.php | 20 +++++++++++++++----- src/OpenApi/OpenApiSchema.php | 2 +- src/Swagger/SwaggerSchema.php | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Base/Schema.php b/src/Base/Schema.php index 42c8154..d79bbd0 100644 --- a/src/Base/Schema.php +++ b/src/Base/Schema.php @@ -70,7 +70,7 @@ public static function getInstance(array|string $data, bool $extraArgs = false): * @throws NotMatchedException * @throws PathNotFoundException */ - public function getPathDefinition(string $path, string $method): mixed + protected function parsePathRequest(string $path, string $method, bool $validateQuery): mixed { $method = strtolower($method); @@ -82,8 +82,10 @@ public function getPathDefinition(string $path, string $method): mixed if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) { if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) { - parse_str($uri->getQuery(), $matches); - $this->prepareToValidateArguments($uri->getPath(), $method, 'query', $matches); + if ($validateQuery) { + parse_str($uri->getQuery(), $matches); + $this->prepareToValidateArguments($uri->getPath(), $method, 'query', $matches); + } return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method]; } @@ -109,8 +111,11 @@ public function getPathDefinition(string $path, string $method): mixed } $this->prepareToValidateArguments($pathItem, $method, 'path', $matches); - parse_str($uri->getQuery(), $queryParsed); - $this->prepareToValidateArguments($pathItem, $method, 'query', $queryParsed); + + if ($validateQuery) { + parse_str($uri->getQuery(), $queryParsed); + $this->prepareToValidateArguments($pathItem, $method, 'query', $queryParsed); + } return $pathDef[$method]; } @@ -119,6 +124,11 @@ public function getPathDefinition(string $path, string $method): mixed throw new PathNotFoundException('Path "' . $path . '" not found'); } + public function getPathDefinition(string $path, string $method): mixed + { + return $this->parsePathRequest($path, $method, false); + } + /** * @throws DefinitionNotFoundException * @throws NotMatchedException diff --git a/src/OpenApi/OpenApiSchema.php b/src/OpenApi/OpenApiSchema.php index 3873455..e9fc42b 100644 --- a/src/OpenApi/OpenApiSchema.php +++ b/src/OpenApi/OpenApiSchema.php @@ -111,7 +111,7 @@ public function getDefinition($name): mixed */ public function getRequestParameters(string $path, string $method): Body { - $structure = $this->getPathDefinition($path, $method); + $structure = $this->parsePathRequest($path, $method, true); if (!isset($structure['requestBody'])) { return new OpenApiRequestBody($this, "$method $path", []); diff --git a/src/Swagger/SwaggerSchema.php b/src/Swagger/SwaggerSchema.php index 8d2eceb..a3cfdb8 100644 --- a/src/Swagger/SwaggerSchema.php +++ b/src/Swagger/SwaggerSchema.php @@ -93,7 +93,7 @@ public function getDefinition($name): mixed */ public function getRequestParameters(string $path, string $method): Body { - $structure = $this->getPathDefinition($path, $method); + $structure = $this->parsePathRequest($path, $method, true); if (!isset($structure[self::SWAGGER_PARAMETERS])) { return new SwaggerRequestBody($this, "$method $path", []); From 36210fa61fa4fed8a1114b332be788a66eaa0dc4 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Sat, 30 Nov 2024 15:15:10 -0600 Subject: [PATCH 11/15] Better Validation for Query parameters getRequestParameters now accepts the parameter querystring which will be tested --- src/AbstractRequester.php | 2 +- src/Base/Schema.php | 14 +++++----- src/OpenApi/OpenApiSchema.php | 15 ++++++++-- src/Swagger/SwaggerSchema.php | 4 +-- tests/AbstractRequesterTest.php | 33 ++++++++++++++++++++++ tests/Classes/Handler.php | 5 ++++ tests/OpenApiRequestBodyTest.php | 26 ++++++++++++------ tests/rest/openapi.json | 47 ++++++++++++++++++++++++++++++++ tests/rest/swagger.json | 43 +++++++++++++++++++++++++++++ 9 files changed, 168 insertions(+), 21 deletions(-) diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index 671a55d..bfb95ac 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -248,7 +248,7 @@ public function send(): ResponseInterface } // Check if the body is the expected before request - $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); + $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod(), $this->psr7Request->getUri()->getQuery()); $bodyRequestDef->match($requestBody); // Handle Request diff --git a/src/Base/Schema.php b/src/Base/Schema.php index d79bbd0..8cc952b 100644 --- a/src/Base/Schema.php +++ b/src/Base/Schema.php @@ -70,7 +70,7 @@ public static function getInstance(array|string $data, bool $extraArgs = false): * @throws NotMatchedException * @throws PathNotFoundException */ - protected function parsePathRequest(string $path, string $method, bool $validateQuery): mixed + protected function parsePathRequest(string $path, string $method, ?string $queryString = null): mixed { $method = strtolower($method); @@ -82,8 +82,8 @@ protected function parsePathRequest(string $path, string $method, bool $validate if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) { if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) { - if ($validateQuery) { - parse_str($uri->getQuery(), $matches); + if (!is_null($queryString)) { + parse_str($queryString, $matches); $this->prepareToValidateArguments($uri->getPath(), $method, 'query', $matches); } @@ -112,8 +112,8 @@ protected function parsePathRequest(string $path, string $method, bool $validate $this->prepareToValidateArguments($pathItem, $method, 'path', $matches); - if ($validateQuery) { - parse_str($uri->getQuery(), $queryParsed); + if (!is_null($queryString)) { + parse_str($queryString, $queryParsed); $this->prepareToValidateArguments($pathItem, $method, 'query', $queryParsed); } @@ -126,7 +126,7 @@ protected function parsePathRequest(string $path, string $method, bool $validate public function getPathDefinition(string $path, string $method): mixed { - return $this->parsePathRequest($path, $method, false); + return $this->parsePathRequest($path, $method); } /** @@ -229,7 +229,7 @@ abstract public function getDefinition($name): mixed; * @throws NotMatchedException * @throws PathNotFoundException */ - abstract public function getRequestParameters(string $path, string $method): Body; + abstract public function getRequestParameters(string $path, string $method, ?string $queryString = null): Body; /** * @param Schema $schema diff --git a/src/OpenApi/OpenApiSchema.php b/src/OpenApi/OpenApiSchema.php index e9fc42b..0b23233 100644 --- a/src/OpenApi/OpenApiSchema.php +++ b/src/OpenApi/OpenApiSchema.php @@ -8,6 +8,7 @@ use ByJG\ApiTools\Exception\DefinitionNotFoundException; use ByJG\ApiTools\Exception\InvalidDefinitionException; use ByJG\ApiTools\Exception\InvalidRequestException; +use ByJG\ApiTools\Exception\NotMatchedException; use ByJG\Util\Uri; class OpenApiSchema extends Schema @@ -62,6 +63,10 @@ public function getBasePath(): string */ protected function validateArguments(string $parameterIn, array $parameters, array $arguments): void { + $checked = array_filter($arguments, function ($key) { + return !is_numeric($key); + }, ARRAY_FILTER_USE_KEY); + foreach ($parameters as $parameter) { if (isset($parameter['$ref'])) { $paramParts = explode("/", $parameter['$ref']); @@ -81,6 +86,12 @@ protected function validateArguments(string $parameterIn, array $parameters, arr $parameterMatch = new Parameter($this, $parameter['name'], $parameter["schema"] ?? [], !($parameter["required"] ?? false)); $parameterMatch->match($arguments[$parameter['name']] ?? null); } + + unset($checked[$parameter['name']]); + } + + if (!empty($checked)) { + throw new NotMatchedException("There are parameters that are not defined in the schema: " . implode(", ", array_keys($checked))); } } @@ -109,9 +120,9 @@ public function getDefinition($name): mixed * @inheritDoc * @throws InvalidRequestException */ - public function getRequestParameters(string $path, string $method): Body + public function getRequestParameters(string $path, string $method, ?string $queryString = null): Body { - $structure = $this->parsePathRequest($path, $method, true); + $structure = $this->parsePathRequest($path, $method, $queryString); if (!isset($structure['requestBody'])) { return new OpenApiRequestBody($this, "$method $path", []); diff --git a/src/Swagger/SwaggerSchema.php b/src/Swagger/SwaggerSchema.php index a3cfdb8..7b6ffc7 100644 --- a/src/Swagger/SwaggerSchema.php +++ b/src/Swagger/SwaggerSchema.php @@ -91,9 +91,9 @@ public function getDefinition($name): mixed * @inheritDoc * @throws InvalidRequestException */ - public function getRequestParameters(string $path, string $method): Body + public function getRequestParameters(string $path, string $method, ?string $queryString = null): Body { - $structure = $this->parsePathRequest($path, $method, true); + $structure = $this->parsePathRequest($path, $method, $queryString); if (!isset($structure[self::SWAGGER_PARAMETERS])) { return new SwaggerRequestBody($this, "$method $path", []); diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index b5ceaa1..42403e0 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -338,4 +338,37 @@ public function testValidateAssertBodyNotContains() $this->assertRequest($request); } + + public function testMatchParameterInQueryAssert() + { + $expectedResponse = Response::getInstance(200) + ->withBody(new MemoryStream(json_encode([ + "status" => "a", + ]))); + + // Basic Request + $request = new MockRequester($expectedResponse); + $request + ->withMethod('GET') + ->withPath("/check") + ->withQuery(["status" => "1"]); + + $this->assertRequest($request); + } + + public function testMissingParameterInQueryAssert() + { + $expectedResponse = Response::getInstance(200) + ->withBody(new MemoryStream(json_encode([ + "status" => "a", + ]))); + + // Basic Request + $request = new MockRequester($expectedResponse); + $request + ->withMethod('GET') + ->withPath("/check"); + + $this->assertRequestException($request, NotMatchedException::class, "Value of property 'status' is null, but should be of type 'integer'"); + } } diff --git a/tests/Classes/Handler.php b/tests/Classes/Handler.php index a18cae0..90aa85f 100644 --- a/tests/Classes/Handler.php +++ b/tests/Classes/Handler.php @@ -58,4 +58,9 @@ public function processUpload($response, $request) ); $response->write($pet); } + + public function check($response, $request) + { + $response->write(["status" => $request->get("status")]); + } } \ No newline at end of file diff --git a/tests/OpenApiRequestBodyTest.php b/tests/OpenApiRequestBodyTest.php index 1b4f885..36da828 100644 --- a/tests/OpenApiRequestBodyTest.php +++ b/tests/OpenApiRequestBodyTest.php @@ -126,7 +126,7 @@ public function testMatchParameterInQueryRequired() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Value of property 'status' is null, but should be of type 'array'"); - self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus', 'get'); + self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus', 'get', ""); } public function testMatchParameterInQueryRequired2() @@ -134,7 +134,7 @@ public function testMatchParameterInQueryRequired2() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Value of property 'username' is null, but should be of type 'string"); - self::openApiSchema()->getRequestParameters('/user/login', 'get'); + self::openApiSchema()->getRequestParameters('/user/login', 'get', ""); } public function testMatchParameterInQueryRequired3() @@ -142,7 +142,15 @@ public function testMatchParameterInQueryRequired3() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Value of property 'password' is null, but should be of type 'string"); - self::openApiSchema()->getRequestParameters('/user/login?username=test', 'get'); + self::openApiSchema()->getRequestParameters('/user/login', 'get', "username=test"); + } + + public function testMatchParameterInQueryNotDefined() + { + $this->expectException(NotMatchedException::class); + $this->expectExceptionMessage("There are parameters that are not defined in the schema: notdefined"); + + self::openApiSchema()->getRequestParameters('/user/login', 'get', "username=test&password=test¬defined=error"); } public function testMatchParameterInQueryNotValid() @@ -150,7 +158,7 @@ public function testMatchParameterInQueryNotValid() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Value 'ABC' in 'status' not matched in ENUM"); - self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus?status=ABC', 'get'); + self::openApiSchema()->getRequestParameters('/v2/pet/findByStatus', 'get', "status=ABC"); } /** @@ -163,7 +171,7 @@ public function testMatchParameterInQueryNotValid() */ public function testMatchParameterInQuery2() { - self::openApiSchema3()->getRequestParameters('/tests/12345?count=20&offset=2', 'get'); + self::openApiSchema3()->getRequestParameters('/tests/12345', 'get', "count=20&offset=2"); $this->assertTrue(true); } @@ -180,8 +188,8 @@ public function testMatchParameterInQuery3() { $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Expected 'test_id' to be numeric, but found 'STRING'"); - - self::openApiSchema3()->getRequestParameters('/tests/STRING?count=20&offset=2', 'get'); + + self::openApiSchema3()->getRequestParameters('/tests/STRING', 'get', "count=20&offset=2"); $this->assertTrue(true); } @@ -190,7 +198,7 @@ public function testMatchParameterInQuery4() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Expected 'count' to be numeric, but found 'ABC'"); - self::openApiSchema3()->getRequestParameters('/tests/12345?count=ABC&offset=2', 'get'); + self::openApiSchema3()->getRequestParameters('/tests/12345', 'get', "count=ABC&offset=2"); $this->assertTrue(true); } @@ -199,7 +207,7 @@ public function testMatchParameterInQuery5() $this->expectException(NotMatchedException::class); $this->expectExceptionMessage("Expected 'offset' to be numeric, but found 'ABC'"); - self::openApiSchema3()->getRequestParameters('/tests/12345?count=20&offset=ABC', 'get'); + self::openApiSchema3()->getRequestParameters('/tests/12345', 'get', "count=20&offset=ABC"); $this->assertTrue(true); } diff --git a/tests/rest/openapi.json b/tests/rest/openapi.json index 9104871..b5884e8 100644 --- a/tests/rest/openapi.json +++ b/tests/rest/openapi.json @@ -101,6 +101,53 @@ } } }, + "/check": { + "get": { + "tags": [ + "pet" + ], + "summary": "Check the parameters", + "description": "Check the parameters", + "operationId": "Tests\\Classes\\Handler::check", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "The status to be checked", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid ID supplied", + "content": {} + }, + "404": { + "description": "Pet not found", + "content": {} + } + } + } + }, "/inventory" : { "post": { "tags": [ diff --git a/tests/rest/swagger.json b/tests/rest/swagger.json index dc982e7..7441195 100644 --- a/tests/rest/swagger.json +++ b/tests/rest/swagger.json @@ -128,6 +128,49 @@ } } } + }, + "/check": { + "get": { + "tags": [ + "pet" + ], + "summary": "Check the parameters", + "description": "Check the parameters", + "operationId": "Tests\\Classes\\Handler::check", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "The status to be checked", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + } + } } }, "definitions": { From a21e9a084f093138eb82f84cc292121d2c1e93ee Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Sat, 30 Nov 2024 15:41:19 -0600 Subject: [PATCH 12/15] Better Validation for Query parameters Add parameter matchQueryParam to allow match or not. --- src/AbstractRequester.php | 4 ++-- src/ApiTestCase.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index bfb95ac..7b61e88 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -209,7 +209,7 @@ public function assertBodyContains(string $contains): self * @throws RequiredArgumentNotFound * @throws StatusCodeNotMatchedException */ - public function send(): ResponseInterface + public function send(bool $matchQueryParams = true): ResponseInterface { // Process URI based on the OpenAPI schema $uriSchema = new Uri($this->schema->getServerUrl()); @@ -248,7 +248,7 @@ public function send(): ResponseInterface } // Check if the body is the expected before request - $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod(), $this->psr7Request->getUri()->getQuery()); + $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod(), $matchQueryParams ? $this->psr7Request->getUri()->getQuery() : null); $bodyRequestDef->match($requestBody); // Handle Request diff --git a/src/ApiTestCase.php b/src/ApiTestCase.php index 4d878f6..63b475c 100644 --- a/src/ApiTestCase.php +++ b/src/ApiTestCase.php @@ -117,7 +117,7 @@ protected function makeRequest( * @throws RequiredArgumentNotFound * @throws StatusCodeNotMatchedException */ - public function assertRequest(AbstractRequester $request): ResponseInterface + public function assertRequest(AbstractRequester $request, bool $matchQueryParams = true): ResponseInterface { // Add own schema if nothing is passed. if (!$request->hasSchema()) { @@ -126,7 +126,7 @@ public function assertRequest(AbstractRequester $request): ResponseInterface } // Request based on the Swagger Request definitios - $body = $request->send(); + $body = $request->send($matchQueryParams); // Note: // This code is only reached if to send is successful and @@ -137,10 +137,10 @@ public function assertRequest(AbstractRequester $request): ResponseInterface return $body; } - public function assertRequestException(AbstractRequester $request, string $exceptionClass, string $exceptionMessage = null): Throwable + public function assertRequestException(AbstractRequester $request, string $exceptionClass, string $exceptionMessage = null, bool $matchQueryParams = true): Throwable { try { - $this->assertRequest($request); + $this->assertRequest($request, $matchQueryParams); } catch (Throwable $ex) { $this->assertInstanceOf($exceptionClass, $ex); From 61a1f014ac005b930997fcb108986e556e15fc90 Mon Sep 17 00:00:00 2001 From: leomunsa Date: Wed, 2 Jul 2025 16:01:04 -0300 Subject: [PATCH 13/15] Insert xml logic with body requests --- composer.json | 3 ++- src/AbstractRequester.php | 35 ++++++++++++++++++----------- tests/AbstractRequesterTest.php | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 064e2d3..4a80a07 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,8 @@ "require": { "php": ">=8.1 <8.4", "ext-json": "*", - "byjg/webrequest": "^5.0" + "byjg/webrequest": "^5.0", + "byjg/xmlutil": "^5.0" }, "require-dev": { "phpunit/phpunit": "^9.6", diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index 18ad4ad..422c19a 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -17,6 +17,8 @@ use ByJG\WebRequest\Psr7\Request; use ByJG\Util\Uri; use ByJG\WebRequest\Psr7\MemoryStream; +use ByJG\XmlUtil\XmlDocument; +use ByJG\XmlUtil\XmlNode; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -230,21 +232,28 @@ public function send(): ResponseInterface $this->psr7Request = $this->psr7Request->withUri($uri); // Prepare Body to Match Against Specification - $requestBody = $this->psr7Request->getBody()->getContents(); - if (!empty($requestBody)) { - $contentType = $this->psr7Request->getHeaderLine("content-type"); - if (empty($contentType) || str_contains($contentType, "application/json")) { - $requestBody = json_decode($requestBody, true); - } elseif (str_contains($contentType, "multipart/")) { - $requestBody = $this->parseMultiPartForm($contentType, $requestBody); - } else { - throw new InvalidRequestException("Cannot handle Content Type '$contentType'"); + $rawBody = $this->psr7Request->getBody()->getContents(); + $contentType = $this->psr7Request->getHeaderLine("content-type"); + + if (str_contains($contentType, 'application/xml') || str_contains($contentType, 'text/xml')) { + if (!empty($rawBody)) { + new XmlDocument($rawBody); + } + } else { + $requestBody = null; + if (!empty($rawBody)) { + if (empty($contentType) || str_contains($contentType, "application/json")) { + $requestBody = json_decode($rawBody, true); + } elseif (str_contains($contentType, "multipart/")) { + $requestBody = $this->parseMultiPartForm($contentType, $rawBody); + } else { + throw new InvalidRequestException("Cannot handle Content Type '$contentType'"); + } } - } - // Check if the body is the expected before request - $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); - $bodyRequestDef->match($requestBody); + $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); + $bodyRequestDef->match($requestBody); + } // Handle Request $response = $this->handleRequest($this->psr7Request); diff --git a/tests/AbstractRequesterTest.php b/tests/AbstractRequesterTest.php index 0c4a1db..35203a2 100644 --- a/tests/AbstractRequesterTest.php +++ b/tests/AbstractRequesterTest.php @@ -380,4 +380,43 @@ public function testValidateAssertBodyNotContains() $this->assertRequest($request); } + + /** + * @throws DefinitionNotFoundException + * @throws GenericSwaggerException + * @throws HttpMethodNotFoundException + * @throws InvalidDefinitionException + * @throws InvalidRequestException + * @throws NotMatchedException + * @throws PathNotFoundException + * @throws RequiredArgumentNotFound + * @throws StatusCodeNotMatchedException + * @throws MessageException + * @throws RequestException + */ + public function testPostPetWithXmlBody() + { + $xmlBody = + '' . + 'Garfield XML' . + 'available' . + '' . + 'http://example.com/garfield.png' . + '' . + '' . + 'Cats' . + '' . + ''; + + $expectedResponse = Response::getInstance(200); + + $request = new MockRequester($expectedResponse); + $request + ->withMethod('POST') + ->withPath('/pet') + ->withRequestHeader(['Content-Type' => 'application/xml']) + ->withRequestBody($xmlBody); + + $this->assertRequest($request); + } } From 5173d91a42195589f2741c91a467e126a213dbc2 Mon Sep 17 00:00:00 2001 From: leomunsa Date: Wed, 2 Jul 2025 16:27:13 -0300 Subject: [PATCH 14/15] Fix --- src/AbstractRequester.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index 422c19a..0495200 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -233,26 +233,24 @@ public function send(): ResponseInterface // Prepare Body to Match Against Specification $rawBody = $this->psr7Request->getBody()->getContents(); + $requestBody = null; $contentType = $this->psr7Request->getHeaderLine("content-type"); - - if (str_contains($contentType, 'application/xml') || str_contains($contentType, 'text/xml')) { - if (!empty($rawBody)) { + if (!empty($rawBody)) { + if (str_contains($contentType, 'application/xml') || str_contains($contentType, 'text/xml')) { new XmlDocument($rawBody); - } - } else { - $requestBody = null; - if (!empty($rawBody)) { - if (empty($contentType) || str_contains($contentType, "application/json")) { - $requestBody = json_decode($rawBody, true); - } elseif (str_contains($contentType, "multipart/")) { - $requestBody = $this->parseMultiPartForm($contentType, $rawBody); - } else { - throw new InvalidRequestException("Cannot handle Content Type '$contentType'"); - } + } elseif (empty($contentType) || str_contains($contentType, "application/json")) { + $requestBody = json_decode($rawBody, true); + } elseif (str_contains($contentType, "multipart/")) { + $requestBody = $this->parseMultiPartForm($contentType, $rawBody); + } else { + throw new InvalidRequestException("Cannot handle Content Type '$contentType'"); } - $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); - $bodyRequestDef->match($requestBody); + // Check if the body is the expected before request + if (!is_null($requestBody)) { + $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); + $bodyRequestDef->match($requestBody); + } } // Handle Request From 64231f07f0b6808ce733140e615ff82eef947753 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 2 Jul 2025 16:44:30 -0500 Subject: [PATCH 15/15] Refactor body content handling to fix XML and JSON validation logic. --- src/AbstractRequester.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/AbstractRequester.php b/src/AbstractRequester.php index 0495200..c166871 100644 --- a/src/AbstractRequester.php +++ b/src/AbstractRequester.php @@ -12,13 +12,12 @@ use ByJG\ApiTools\Exception\PathNotFoundException; use ByJG\ApiTools\Exception\RequiredArgumentNotFound; use ByJG\ApiTools\Exception\StatusCodeNotMatchedException; +use ByJG\Util\Uri; use ByJG\WebRequest\Exception\MessageException; use ByJG\WebRequest\Exception\RequestException; -use ByJG\WebRequest\Psr7\Request; -use ByJG\Util\Uri; use ByJG\WebRequest\Psr7\MemoryStream; +use ByJG\WebRequest\Psr7\Request; use ByJG\XmlUtil\XmlDocument; -use ByJG\XmlUtil\XmlNode; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -233,11 +232,12 @@ public function send(): ResponseInterface // Prepare Body to Match Against Specification $rawBody = $this->psr7Request->getBody()->getContents(); + $isXmlBody = false; $requestBody = null; $contentType = $this->psr7Request->getHeaderLine("content-type"); if (!empty($rawBody)) { if (str_contains($contentType, 'application/xml') || str_contains($contentType, 'text/xml')) { - new XmlDocument($rawBody); + $isXmlBody = new XmlDocument($rawBody); } elseif (empty($contentType) || str_contains($contentType, "application/json")) { $requestBody = json_decode($rawBody, true); } elseif (str_contains($contentType, "multipart/")) { @@ -246,11 +246,12 @@ public function send(): ResponseInterface throw new InvalidRequestException("Cannot handle Content Type '$contentType'"); } - // Check if the body is the expected before request - if (!is_null($requestBody)) { - $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); - $bodyRequestDef->match($requestBody); - } + } + + // Check if the body is the expected before request + if ($isXmlBody === false) { + $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod()); + $bodyRequestDef->match($requestBody); } // Handle Request