forked from loafle/openapi-generator-original
[Slim4] Add Data Mocker middleware (#4978)
* [Slim4] Store response schemas * [Slim4] Add Data Mocker middleware * [Slim4] Enhance Slim router * [Slim4] Enhance config * [Slim4] Fix data format key in object mocking * [Slim4] Add tests for Data Mocker middleware * [Slim4] Add Mock feature documentation * [Slim4] Refresh samples
This commit is contained in:
@@ -113,6 +113,9 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen {
|
||||
additionalProperties.put("interfacesSrcPath", "./" + toSrcPath(interfacesPackage, srcBasePath));
|
||||
additionalProperties.put("interfacesTestPath", "./" + toSrcPath(interfacesPackage, testBasePath));
|
||||
|
||||
// external docs folder
|
||||
additionalProperties.put("docsBasePath", "./" + docsBasePath);
|
||||
|
||||
if (additionalProperties.containsKey(PSR7_IMPLEMENTATION)) {
|
||||
this.setPsr7Implementation((String) additionalProperties.get(PSR7_IMPLEMENTATION));
|
||||
}
|
||||
@@ -150,6 +153,9 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen {
|
||||
supportingFiles.add(new SupportingFile("openapi_data_mocker_interface.mustache", toSrcPath(mockPackage, srcBasePath), toInterfaceName("OpenApiDataMocker") + ".php"));
|
||||
supportingFiles.add(new SupportingFile("openapi_data_mocker.mustache", toSrcPath(mockPackage, srcBasePath), "OpenApiDataMocker.php"));
|
||||
supportingFiles.add(new SupportingFile("openapi_data_mocker_test.mustache", toSrcPath(mockPackage, testBasePath), "OpenApiDataMockerTest.php"));
|
||||
supportingFiles.add(new SupportingFile("openapi_data_mocker_middleware.mustache", toSrcPath(mockPackage, srcBasePath), "OpenApiDataMockerMiddleware.php"));
|
||||
supportingFiles.add(new SupportingFile("openapi_data_mocker_middleware_test.mustache", toSrcPath(mockPackage, testBasePath), "OpenApiDataMockerMiddlewareTest.php"));
|
||||
supportingFiles.add(new SupportingFile("mock_server.mustache", docsBasePath, "MockServer.md"));
|
||||
|
||||
// traits of ported utils
|
||||
supportingFiles.add(new SupportingFile("string_utils_trait.mustache", toSrcPath(utilsPackage, srcBasePath), toTraitName("StringUtils") + ".php"));
|
||||
|
||||
@@ -57,6 +57,8 @@ Command | Target
|
||||
`$ composer test` | All tests
|
||||
`$ composer test-apis` | Apis tests
|
||||
`$ composer test-models` | Models tests
|
||||
`$ composer test-mock` | Mock feature tests
|
||||
`$ composer test-utils` | Utils tests
|
||||
|
||||
#### Config
|
||||
|
||||
@@ -110,6 +112,8 @@ Switch on option in `./index.php`:
|
||||
+++ $app->addErrorMiddleware(true, true, true);
|
||||
```
|
||||
|
||||
## [Mock Server Documentation]({{docsBasePath}}/MockServer.md)
|
||||
|
||||
{{#generateApiDocs}}
|
||||
## API Endpoints
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ use Dyorg\TokenAuthentication;
|
||||
use Dyorg\TokenAuthentication\TokenSearch;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use {{invokerPackage}}\Middleware\JsonBodyParserMiddleware;
|
||||
use {{mockPackage}}\OpenApiDataMocker;
|
||||
use {{mockPackage}}\OpenApiDataMockerMiddleware;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
@@ -69,6 +71,15 @@ class SlimRouter
|
||||
'classname' => '{{classname}}',
|
||||
'userClassname' => '{{userClassname}}',
|
||||
'operationId' => '{{operationId}}',
|
||||
'responses' => [
|
||||
{{#responses}}
|
||||
'{{#isDefault}}default{{/isDefault}}{{^isDefault}}{{code}}{{/isDefault}}' => [
|
||||
'code' => {{code}},
|
||||
'message' => '{{message}}',
|
||||
'jsonSchema' => '{{{jsonSchema}}}',
|
||||
],
|
||||
{{/responses}}
|
||||
],
|
||||
'authMethods' => [
|
||||
{{#hasAuthMethods}}
|
||||
{{#authMethods}}
|
||||
@@ -161,12 +172,13 @@ class SlimRouter
|
||||
};
|
||||
{{/hasAuthMethods}}
|
||||
|
||||
$userOptions = null;
|
||||
if ($settings instanceof ContainerInterface && $settings->has('tokenAuthenticationOptions')) {
|
||||
$userOptions = $settings->get('tokenAuthenticationOptions');
|
||||
} elseif (is_array($settings) && isset($settings['tokenAuthenticationOptions'])) {
|
||||
$userOptions = $settings['tokenAuthenticationOptions'];
|
||||
}
|
||||
$userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null);
|
||||
|
||||
// mocker options
|
||||
$mockerOptions = $this->getSetting($settings, 'mockerOptions', null);
|
||||
$dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker();
|
||||
$getMockResponseCallback = $mockerOptions['getMockResponseCallback'] ?? null;
|
||||
$mockAfterCallback = $mockerOptions['afterCallback'] ?? null;
|
||||
|
||||
foreach ($this->operations as $operation) {
|
||||
$callback = function ($request, $response, $arguments) use ($operation) {
|
||||
@@ -235,6 +247,10 @@ class SlimRouter
|
||||
}
|
||||
{{/hasAuthMethods}}
|
||||
|
||||
if (is_callable($getMockResponseCallback)) {
|
||||
$middlewares[] = new OpenApiDataMockerMiddleware($dataMocker, $operation['responses'], $getMockResponseCallback, $mockAfterCallback);
|
||||
}
|
||||
|
||||
$this->addRoute(
|
||||
[$operation['httpMethod']],
|
||||
"{$operation['basePathWithoutHost']}{$operation['path']}",
|
||||
@@ -261,6 +277,26 @@ class SlimRouter
|
||||
return array_merge($userOptions, $staticOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns app setting by name.
|
||||
*
|
||||
* @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings
|
||||
* @param string $settingName Setting name
|
||||
* @param mixed $default Default setting value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getSetting($settings, $settingName, $default = null)
|
||||
{
|
||||
if ($settings instanceof ContainerInterface && $settings->has($settingName)) {
|
||||
return $settings->get($settingName);
|
||||
} elseif (is_array($settings) && array_key_exists($settingName, $settings)) {
|
||||
return $settings[$settingName];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add route with multiple methods
|
||||
*
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use {{invokerPackage}}\SlimRouter;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use {{mockPackage}}\OpenApiDataMocker;
|
||||
{{/apiInfo}}
|
||||
|
||||
$config = [];
|
||||
@@ -51,6 +54,35 @@ $config['tokenAuthenticationOptions'] = [
|
||||
// 'error' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Mocker Middleware options.
|
||||
*/
|
||||
$config['mockerOptions'] = [
|
||||
// 'dataMocker' => new OpenApiDataMocker(),
|
||||
|
||||
// 'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
|
||||
// // check if client clearly asks for mocked response
|
||||
// if (
|
||||
// $request->hasHeader('X-{{invokerPackage}}-Mock')
|
||||
// && $request->getHeader('X-{{invokerPackage}}-Mock')[0] === 'ping'
|
||||
// ) {
|
||||
// if (array_key_exists('default', $responses)) {
|
||||
// return $responses['default'];
|
||||
// }
|
||||
|
||||
// // return first response
|
||||
// return $responses[array_key_first($responses)];
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// },
|
||||
|
||||
// 'afterCallback' => function ($request, $response) {
|
||||
// // mark mocked response to distinguish real and fake responses
|
||||
// return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
|
||||
// },
|
||||
];
|
||||
|
||||
$router = new SlimRouter($config);
|
||||
$app = $router->getSlimApp();
|
||||
|
||||
|
||||
135
modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache
vendored
Normal file
135
modules/openapi-generator/src/main/resources/php-slim4-server/mock_server.mustache
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
# {{packageName}} - PHP Slim 4 Server library for {{appName}}
|
||||
|
||||
## Mock Server Documentation
|
||||
|
||||
### Mocker Options
|
||||
To enable mock server uncomment these lines in `index.php` config file:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Mocker Middleware options.
|
||||
*/
|
||||
$config['mockerOptions'] = [
|
||||
'dataMocker' => new OpenApiDataMocker(),
|
||||
|
||||
'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
|
||||
// check if client clearly asks for mocked response
|
||||
if (
|
||||
$request->hasHeader('X-{{invokerPackage}}-Mock')
|
||||
&& $request->getHeader('X-{{invokerPackage}}-Mock')[0] === 'ping'
|
||||
) {
|
||||
if (array_key_exists('default', $responses)) {
|
||||
return $responses['default'];
|
||||
}
|
||||
|
||||
// return first response
|
||||
return $responses[array_key_first($responses)];
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
'afterCallback' => function ($request, $response) {
|
||||
// mark mocked response to distinguish real and fake responses
|
||||
return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
* `dataMocker` is mocker class instance. To create custom data mocker extend `{{mockPackage}}\{{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}}`.
|
||||
* `getMockResponseCallback` is callback before mock data generation. Above example shows how to enable mock feature for only requests with `{{X-{{invokerPackage}}}}-mock: ping` HTTP header. Adjust requests filtering to fit your project requirements. This function must return single response schema from `$responses` array parameter. **Mock feature is disabled when callback returns anything beside array.**
|
||||
* `afterCallback` is callback executed after mock data generation. Most obvious use case is append specific HTTP headers to distinguish real and fake responses. **This function must always return response instance.**
|
||||
|
||||
### Supported features
|
||||
|
||||
All data types supported except specific string formats: `email`, `uuid`, `password` which are poorly implemented.
|
||||
|
||||
#### Data Types Support
|
||||
|
||||
| Data Type | Data Format | Supported |
|
||||
|:---------:|:-----------:|:------------------:|
|
||||
| `integer` | `int32` | :white_check_mark: |
|
||||
| `integer` | `int64` | :white_check_mark: |
|
||||
| `number` | `float` | :white_check_mark: |
|
||||
| `number` | `double` | |
|
||||
| `string` | `byte` | :white_check_mark: |
|
||||
| `string` | `binary` | :white_check_mark: |
|
||||
| `boolean` | | :white_check_mark: |
|
||||
| `string` | `date` | :white_check_mark: |
|
||||
| `string` | `date-time` | :white_check_mark: |
|
||||
| `string` | `password` | :white_check_mark: |
|
||||
| `string` | `email` | :white_check_mark: |
|
||||
| `string` | `uuid` | :white_check_mark: |
|
||||
|
||||
#### Data Options Support
|
||||
|
||||
| Data Type | Option | Supported |
|
||||
|:-----------:|:----------------------:|:------------------:|
|
||||
| `string` | `minLength` | :white_check_mark: |
|
||||
| `string` | `maxLength` | :white_check_mark: |
|
||||
| `string` | `enum` | :white_check_mark: |
|
||||
| `string` | `pattern` | |
|
||||
| `integer` | `minimum` | :white_check_mark: |
|
||||
| `integer` | `maximum` | :white_check_mark: |
|
||||
| `integer` | `exclusiveMinimum` | :white_check_mark: |
|
||||
| `integer` | `exclusiveMaximum` | :white_check_mark: |
|
||||
| `number` | `minimum` | :white_check_mark: |
|
||||
| `number` | `maximum` | :white_check_mark: |
|
||||
| `number` | `exclusiveMinimum` | :white_check_mark: |
|
||||
| `number` | `exclusiveMaximum` | :white_check_mark: |
|
||||
| `array` | `items` | :white_check_mark: |
|
||||
| `array` | `additionalItems` | |
|
||||
| `array` | `minItems` | :white_check_mark: |
|
||||
| `array` | `maxItems` | :white_check_mark: |
|
||||
| `array` | `uniqueItems` | |
|
||||
| `object` | `properties` | :white_check_mark: |
|
||||
| `object` | `maxProperties` | |
|
||||
| `object` | `minProperties` | |
|
||||
| `object` | `patternProperties` | |
|
||||
| `object` | `additionalProperties` | |
|
||||
| `object` | `required` | |
|
||||
| `*` | `$ref` | :white_check_mark: |
|
||||
| `*` | `allOf` | |
|
||||
| `*` | `anyOf` | |
|
||||
| `*` | `oneOf` | |
|
||||
| `*` | `not` | |
|
||||
|
||||
### Known Limitations
|
||||
|
||||
Avoid circular refs in your schema. Schema below can cause infinite loop and `Out of Memory` PHP error:
|
||||
```yml
|
||||
# ModelA has reference to ModelB while ModelB has reference to ModelA.
|
||||
# Mock server will produce huge nested JSON example and ended with `Out of Memory` error.
|
||||
definitions:
|
||||
ModelA:
|
||||
type: object
|
||||
properties:
|
||||
model_b:
|
||||
$ref: '#/definitions/ModelB'
|
||||
ModelB:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ModelA'
|
||||
```
|
||||
|
||||
Don't ref scalar types, because generator will not produce models which mock server can find. So schema below will cause error:
|
||||
```yml
|
||||
# generated build contains only `OuterComposite` model class which referenced to not existed `OuterNumber`, `OuterString`, `OuterBoolean` classes
|
||||
# mock server cannot mock `OuterComposite` model and throws exception
|
||||
definitions:
|
||||
OuterComposite:
|
||||
type: object
|
||||
properties:
|
||||
my_number:
|
||||
$ref: '#/definitions/OuterNumber'
|
||||
my_string:
|
||||
$ref: '#/definitions/OuterString'
|
||||
my_boolean:
|
||||
$ref: '#/definitions/OuterBoolean'
|
||||
OuterNumber:
|
||||
type: number
|
||||
OuterString:
|
||||
type: string
|
||||
OuterBoolean:
|
||||
type: boolean
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddleware
|
||||
*
|
||||
* PHP version 7.1
|
||||
*
|
||||
* @package {{invokerPackage}}
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
|
||||
/**{{#apiInfo}}{{#appName}}
|
||||
* {{{appName}}}
|
||||
*
|
||||
{{/appName}}
|
||||
{{#appDescription}}
|
||||
* {{{appDescription}}}
|
||||
{{/appDescription}}
|
||||
{{#version}}
|
||||
* The version of the OpenAPI document: {{{version}}}
|
||||
{{/version}}
|
||||
{{#infoEmail}}
|
||||
* Contact: {{{infoEmail}}}
|
||||
{{/infoEmail}}
|
||||
* Generated by: https://github.com/openapitools/openapi-generator.git
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This class is auto generated by the openapi generator program.
|
||||
* https://github.com/openapitools/openapi-generator
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
namespace {{mockPackage}};
|
||||
|
||||
use Slim\Factory\AppFactory;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use {{mockPackage}}\{{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}};
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddleware Class Doc Comment
|
||||
*
|
||||
* @package {{mockPackage}}
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
final class OpenApiDataMockerMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} DataMocker.
|
||||
*/
|
||||
private $mocker;
|
||||
|
||||
/**
|
||||
* @var array Array of responses schemas.
|
||||
*/
|
||||
private $responses;
|
||||
|
||||
/**
|
||||
* @var callable|null Custom callback to select mocked response.
|
||||
*/
|
||||
private $getMockResponseCallback;
|
||||
|
||||
/**
|
||||
* @var callable|null Custom after callback.
|
||||
*/
|
||||
private $afterCallback;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} $mocker DataMocker.
|
||||
* @param array $responses Array of responses schemas.
|
||||
* @param callable|null $getMockResponseCallback Custom callback to select mocked response.
|
||||
* Mock feature is disabled when this argument is null.
|
||||
* @example $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) {
|
||||
* // check if client clearly asks for mocked response
|
||||
* if (
|
||||
* $request->hasHeader('X-{{invokerPackage}}-Mock')
|
||||
* && $request->header('X-{{invokerPackage}}-Mock')[0] === 'ping'
|
||||
* ) {
|
||||
* return $responses[array_key_first($responses)];
|
||||
* }
|
||||
* return false;
|
||||
* };
|
||||
* @param callable|null $afterCallback After callback.
|
||||
* Function must return response instance.
|
||||
* @example $afterCallback = function (ServerRequestInterface $request, ResponseInterface $response) {
|
||||
* // mark mocked response to distinguish real and fake responses
|
||||
* return $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
|
||||
* };
|
||||
*/
|
||||
public function __construct(
|
||||
{{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}} $mocker,
|
||||
array $responses,
|
||||
$getMockResponseCallback = null,
|
||||
$afterCallback = null
|
||||
) {
|
||||
$this->mocker = $mocker;
|
||||
$this->responses = $responses;
|
||||
if (is_callable($getMockResponseCallback)) {
|
||||
$this->getMockResponseCallback = $getMockResponseCallback;
|
||||
} elseif ($getMockResponseCallback !== null) {
|
||||
// wrong argument type
|
||||
throw new InvalidArgumentException('\$getMockResponseCallback must be closure or null');
|
||||
}
|
||||
|
||||
if (is_callable($afterCallback)) {
|
||||
$this->afterCallback = $afterCallback;
|
||||
} elseif ($afterCallback !== null) {
|
||||
// wrong argument type
|
||||
throw new InvalidArgumentException('\$afterCallback must be closure or null');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse incoming JSON input into a native PHP format
|
||||
*
|
||||
* @param ServerRequestInterface $request HTTP request
|
||||
* @param RequestHandlerInterface $handler Request handler
|
||||
*
|
||||
* @return ResponseInterface HTTP response
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$customCallback = $this->getMockResponseCallback;
|
||||
$customAfterCallback = $this->afterCallback;
|
||||
$mockedResponse = (is_callable($customCallback)) ? $customCallback($request, $this->responses) : null;
|
||||
if (
|
||||
is_array($mockedResponse)
|
||||
&& array_key_exists('code', $mockedResponse)
|
||||
&& array_key_exists('jsonSchema', $mockedResponse)
|
||||
) {
|
||||
// response schema succesfully selected, we can mock it now
|
||||
$statusCode = ($mockedResponse['code'] === 0) ? 200 : $mockedResponse['code'];
|
||||
$contentType = '*/*';
|
||||
$response = AppFactory::determineResponseFactory()->createResponse($statusCode);
|
||||
$responseSchema = json_decode($mockedResponse['jsonSchema'], true);
|
||||
|
||||
if (is_array($responseSchema) && array_key_exists('headers', $responseSchema)) {
|
||||
// response schema contains headers definitions, apply them one by one
|
||||
foreach ($responseSchema['headers'] as $headerName => $headerDefinition) {
|
||||
$response = $response->withHeader($headerName, $this->mocker->mockFromSchema($headerDefinition['schema']));
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
is_array($responseSchema)
|
||||
&& array_key_exists('content', $responseSchema)
|
||||
&& !empty($responseSchema['content'])
|
||||
) {
|
||||
// response schema contains body definition
|
||||
$responseContentSchema = null;
|
||||
foreach ($responseSchema['content'] as $schemaContentType => $schemaDefinition) {
|
||||
// we can respond in JSON format when any(*/*) content-type allowed
|
||||
// or JSON(application/json) content-type specifically defined
|
||||
if (
|
||||
$schemaContentType === '*/*'
|
||||
|| strtolower(substr($schemaContentType, 0, 16)) === 'application/json'
|
||||
) {
|
||||
$contentType = 'application/json';
|
||||
$responseContentSchema = $schemaDefinition['schema'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($contentType === 'application/json') {
|
||||
$responseBody = $this->mocker->mockFromSchema($responseContentSchema);
|
||||
$response->getBody()->write(json_encode($responseBody));
|
||||
} else {
|
||||
// notify developer that only application/json response supported so far
|
||||
$response->getBody()->write('Mock feature supports only "application/json" content-type!');
|
||||
}
|
||||
}
|
||||
|
||||
// after callback applied only when mocked response schema has been selected
|
||||
if (is_callable($customAfterCallback)) {
|
||||
$response = $customAfterCallback($request, $response);
|
||||
}
|
||||
|
||||
// no reason to execute following middlewares (auth, validation etc.)
|
||||
// return mocked response and end connection
|
||||
return $response
|
||||
->withHeader('Content-Type', $contentType);
|
||||
}
|
||||
|
||||
// no response selected, mock feature disabled
|
||||
// execute following middlewares
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
{{/apiInfo}}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddlewareTest
|
||||
*
|
||||
* PHP version 7.1
|
||||
*
|
||||
* @package {{invokerPackage}}
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
|
||||
/**{{#apiInfo}}{{#invokerPackage}}
|
||||
* {{{invokerPackage}}}
|
||||
*
|
||||
{{/invokerPackage}}
|
||||
{{#appDescription}}
|
||||
* {{{appDescription}}}
|
||||
{{/appDescription}}
|
||||
{{#version}}
|
||||
* The version of the OpenAPI document: {{{version}}}
|
||||
{{/version}}
|
||||
{{#infoEmail}}
|
||||
* Contact: {{{infoEmail}}}
|
||||
{{/infoEmail}}
|
||||
* Generated by: https://github.com/openapitools/openapi-generator.git
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This class is auto generated by the openapi generator program.
|
||||
* https://github.com/openapitools/openapi-generator
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
namespace {{mockPackage}};
|
||||
|
||||
use {{mockPackage}}\OpenApiDataMockerMiddleware;
|
||||
use {{mockPackage}}\OpenApiDataMocker;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Factory\ServerRequestCreatorFactory;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddlewareTest Class Doc Comment
|
||||
*
|
||||
* @package {{mockPackage}}
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
* @coversDefaultClass \{{mockPackage}}\OpenApiDataMockerMiddleware
|
||||
*/
|
||||
class OpenApiDataMockerMiddlewareTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @dataProvider provideConstructCorrectArguments
|
||||
*/
|
||||
public function testConstructor(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
) {
|
||||
$middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
|
||||
$this->assertInstanceOf(OpenApiDataMockerMiddleware::class, $middleware);
|
||||
$this->assertNotNull($middleware);
|
||||
}
|
||||
|
||||
public function provideConstructCorrectArguments()
|
||||
{
|
||||
$getMockResponseCallback = function () {
|
||||
return false;
|
||||
};
|
||||
$afterCallback = function () {
|
||||
return false;
|
||||
};
|
||||
return [
|
||||
[new OpenApiDataMocker(), [], null, null],
|
||||
[new OpenApiDataMocker(), [], $getMockResponseCallback, $afterCallback],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @dataProvider provideConstructInvalidArguments
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedException \TypeError
|
||||
*/
|
||||
public function testConstructorWithInvalidArguments(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
) {
|
||||
$middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
|
||||
}
|
||||
|
||||
public function provideConstructInvalidArguments()
|
||||
{
|
||||
return [
|
||||
'getMockResponseCallback not callable' => [
|
||||
new OpenApiDataMocker(), [], 'foobar', null,
|
||||
],
|
||||
'afterCallback not callable' => [
|
||||
new OpenApiDataMocker(), [], null, 'foobar',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::process
|
||||
* @dataProvider provideProcessArguments
|
||||
*/
|
||||
public function testProcess(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$request,
|
||||
$expectedStatusCode,
|
||||
$expectedHeaders,
|
||||
$notExpectedHeaders,
|
||||
$expectedBody
|
||||
) {
|
||||
|
||||
// Create a stub for the RequestHandlerInterface interface.
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$handler->method('handle')
|
||||
->willReturn(AppFactory::determineResponseFactory()->createResponse());
|
||||
|
||||
$middleware = new OpenApiDataMockerMiddleware(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
);
|
||||
$response = $middleware->process($request, $handler);
|
||||
|
||||
// check status code
|
||||
$this->assertSame($expectedStatusCode, $response->getStatusCode());
|
||||
|
||||
// check http headers in request
|
||||
foreach ($expectedHeaders as $expectedHeader => $expectedHeaderValue) {
|
||||
$this->assertTrue($response->hasHeader($expectedHeader));
|
||||
if ($expectedHeaderValue !== '*') {
|
||||
$this->assertSame($expectedHeaderValue, $response->getHeader($expectedHeader)[0]);
|
||||
}
|
||||
}
|
||||
foreach ($notExpectedHeaders as $notExpectedHeader) {
|
||||
$this->assertFalse($response->hasHeader($notExpectedHeader));
|
||||
}
|
||||
|
||||
// check body
|
||||
if (is_array($expectedBody)) {
|
||||
// random values, check keys only
|
||||
foreach ($expectedBody as $attribute => $value) {
|
||||
$this->assertObjectHasAttribute($attribute, json_decode((string) $response->getBody(), false));
|
||||
}
|
||||
} else {
|
||||
$this->assertEquals($expectedBody, (string) $response->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideProcessArguments()
|
||||
{
|
||||
$mocker = new OpenApiDataMocker();
|
||||
$isMockResponseRequired = function (ServerRequestInterface $request) {
|
||||
$mockHttpHeader = 'X-{{invokerPackage}}-Mock';
|
||||
return $request->hasHeader($mockHttpHeader)
|
||||
&& $request->getHeader($mockHttpHeader)[0] === 'ping';
|
||||
};
|
||||
|
||||
$getMockResponseCallback = function (ServerRequestInterface $request, array $responses) use ($isMockResponseRequired) {
|
||||
if ($isMockResponseRequired($request)) {
|
||||
if (array_key_exists('default', $responses)) {
|
||||
return $responses['default'];
|
||||
}
|
||||
|
||||
// return first response
|
||||
return $responses[array_key_first($responses)];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$afterCallback = function ($request, $response) use ($isMockResponseRequired) {
|
||||
if ($isMockResponseRequired($request)) {
|
||||
$response = $response->withHeader('X-{{invokerPackage}}-Mock', 'pong');
|
||||
}
|
||||
|
||||
return $response;
|
||||
};
|
||||
|
||||
$responses = [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Bad Request Response',
|
||||
'content' => new StdClass(),
|
||||
]),
|
||||
],
|
||||
'default' => [
|
||||
'code' => 201,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Success Response',
|
||||
'headers' => [
|
||||
'X-Location' => ['schema' => ['type' => 'string']],
|
||||
'X-Created-Id' => ['schema' => ['type' => 'integer']],
|
||||
],
|
||||
'content' => [
|
||||
'application/json;encoding=utf-8' => ['schema' => ['type' => 'object', 'properties' => ['id' => ['type' => 'integer'], 'className' => ['type' => 'string'], 'declawed' => ['type' => 'boolean']]]],
|
||||
],
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
$responsesXmlOnly = [
|
||||
'default' => [
|
||||
'code' => 201,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Success Response',
|
||||
'content' => [
|
||||
'application/xml' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
$requestFactory = ServerRequestCreatorFactory::create();
|
||||
|
||||
return [
|
||||
'callbacks null' => [
|
||||
$mocker,
|
||||
$responses,
|
||||
null,
|
||||
null,
|
||||
$requestFactory->createServerRequestFromGlobals(),
|
||||
200,
|
||||
[],
|
||||
['X-{{invokerPackage}}-Mock', 'x-location', 'x-created-id'],
|
||||
'',
|
||||
],
|
||||
'xml not supported' => [
|
||||
$mocker,
|
||||
$responsesXmlOnly,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$requestFactory
|
||||
->createServerRequestFromGlobals()
|
||||
->withHeader('X-{{invokerPackage}}-Mock', 'ping'),
|
||||
201,
|
||||
['X-{{invokerPackage}}-Mock' => 'pong', 'content-type' => '*/*'],
|
||||
['x-location', 'x-created-id'],
|
||||
'Mock feature supports only "application/json" content-type!',
|
||||
],
|
||||
'mock response default schema' => [
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$requestFactory
|
||||
->createServerRequestFromGlobals()
|
||||
->withHeader('X-{{invokerPackage}}-Mock', 'ping'),
|
||||
201,
|
||||
['X-{{invokerPackage}}-Mock' => 'pong', 'content-type' => 'application/json', 'x-location' => '*', 'x-created-id' => '*'],
|
||||
[],
|
||||
[
|
||||
'id' => 1,
|
||||
'className' => 'cat',
|
||||
'declawed' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
{{/apiInfo}}
|
||||
@@ -46,6 +46,8 @@ Command | Target
|
||||
`$ composer test` | All tests
|
||||
`$ composer test-apis` | Apis tests
|
||||
`$ composer test-models` | Models tests
|
||||
`$ composer test-mock` | Mock feature tests
|
||||
`$ composer test-utils` | Utils tests
|
||||
|
||||
#### Config
|
||||
|
||||
@@ -99,6 +101,8 @@ Switch on option in `./index.php`:
|
||||
+++ $app->addErrorMiddleware(true, true, true);
|
||||
```
|
||||
|
||||
## [Mock Server Documentation](./docs/MockServer.md)
|
||||
|
||||
## API Endpoints
|
||||
|
||||
All URIs are relative to *http://petstore.swagger.io:80/v2*
|
||||
|
||||
135
samples/server/petstore/php-slim4/docs/MockServer.md
Normal file
135
samples/server/petstore/php-slim4/docs/MockServer.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# php-base - PHP Slim 4 Server library for OpenAPI Petstore
|
||||
|
||||
## Mock Server Documentation
|
||||
|
||||
### Mocker Options
|
||||
To enable mock server uncomment these lines in `index.php` config file:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Mocker Middleware options.
|
||||
*/
|
||||
$config['mockerOptions'] = [
|
||||
'dataMocker' => new OpenApiDataMocker(),
|
||||
|
||||
'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
|
||||
// check if client clearly asks for mocked response
|
||||
if (
|
||||
$request->hasHeader('X-OpenAPIServer-Mock')
|
||||
&& $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
|
||||
) {
|
||||
if (array_key_exists('default', $responses)) {
|
||||
return $responses['default'];
|
||||
}
|
||||
|
||||
// return first response
|
||||
return $responses[array_key_first($responses)];
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
'afterCallback' => function ($request, $response) {
|
||||
// mark mocked response to distinguish real and fake responses
|
||||
return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
* `dataMocker` is mocker class instance. To create custom data mocker extend `OpenAPIServer\Mock\OpenApiDataMockerInterface`.
|
||||
* `getMockResponseCallback` is callback before mock data generation. Above example shows how to enable mock feature for only requests with `{{X-OpenAPIServer}}-mock: ping` HTTP header. Adjust requests filtering to fit your project requirements. This function must return single response schema from `$responses` array parameter. **Mock feature is disabled when callback returns anything beside array.**
|
||||
* `afterCallback` is callback executed after mock data generation. Most obvious use case is append specific HTTP headers to distinguish real and fake responses. **This function must always return response instance.**
|
||||
|
||||
### Supported features
|
||||
|
||||
All data types supported except specific string formats: `email`, `uuid`, `password` which are poorly implemented.
|
||||
|
||||
#### Data Types Support
|
||||
|
||||
| Data Type | Data Format | Supported |
|
||||
|:---------:|:-----------:|:------------------:|
|
||||
| `integer` | `int32` | :white_check_mark: |
|
||||
| `integer` | `int64` | :white_check_mark: |
|
||||
| `number` | `float` | :white_check_mark: |
|
||||
| `number` | `double` | |
|
||||
| `string` | `byte` | :white_check_mark: |
|
||||
| `string` | `binary` | :white_check_mark: |
|
||||
| `boolean` | | :white_check_mark: |
|
||||
| `string` | `date` | :white_check_mark: |
|
||||
| `string` | `date-time` | :white_check_mark: |
|
||||
| `string` | `password` | :white_check_mark: |
|
||||
| `string` | `email` | :white_check_mark: |
|
||||
| `string` | `uuid` | :white_check_mark: |
|
||||
|
||||
#### Data Options Support
|
||||
|
||||
| Data Type | Option | Supported |
|
||||
|:-----------:|:----------------------:|:------------------:|
|
||||
| `string` | `minLength` | :white_check_mark: |
|
||||
| `string` | `maxLength` | :white_check_mark: |
|
||||
| `string` | `enum` | :white_check_mark: |
|
||||
| `string` | `pattern` | |
|
||||
| `integer` | `minimum` | :white_check_mark: |
|
||||
| `integer` | `maximum` | :white_check_mark: |
|
||||
| `integer` | `exclusiveMinimum` | :white_check_mark: |
|
||||
| `integer` | `exclusiveMaximum` | :white_check_mark: |
|
||||
| `number` | `minimum` | :white_check_mark: |
|
||||
| `number` | `maximum` | :white_check_mark: |
|
||||
| `number` | `exclusiveMinimum` | :white_check_mark: |
|
||||
| `number` | `exclusiveMaximum` | :white_check_mark: |
|
||||
| `array` | `items` | :white_check_mark: |
|
||||
| `array` | `additionalItems` | |
|
||||
| `array` | `minItems` | :white_check_mark: |
|
||||
| `array` | `maxItems` | :white_check_mark: |
|
||||
| `array` | `uniqueItems` | |
|
||||
| `object` | `properties` | :white_check_mark: |
|
||||
| `object` | `maxProperties` | |
|
||||
| `object` | `minProperties` | |
|
||||
| `object` | `patternProperties` | |
|
||||
| `object` | `additionalProperties` | |
|
||||
| `object` | `required` | |
|
||||
| `*` | `$ref` | :white_check_mark: |
|
||||
| `*` | `allOf` | |
|
||||
| `*` | `anyOf` | |
|
||||
| `*` | `oneOf` | |
|
||||
| `*` | `not` | |
|
||||
|
||||
### Known Limitations
|
||||
|
||||
Avoid circular refs in your schema. Schema below can cause infinite loop and `Out of Memory` PHP error:
|
||||
```yml
|
||||
# ModelA has reference to ModelB while ModelB has reference to ModelA.
|
||||
# Mock server will produce huge nested JSON example and ended with `Out of Memory` error.
|
||||
definitions:
|
||||
ModelA:
|
||||
type: object
|
||||
properties:
|
||||
model_b:
|
||||
$ref: '#/definitions/ModelB'
|
||||
ModelB:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ModelA'
|
||||
```
|
||||
|
||||
Don't ref scalar types, because generator will not produce models which mock server can find. So schema below will cause error:
|
||||
```yml
|
||||
# generated build contains only `OuterComposite` model class which referenced to not existed `OuterNumber`, `OuterString`, `OuterBoolean` classes
|
||||
# mock server cannot mock `OuterComposite` model and throws exception
|
||||
definitions:
|
||||
OuterComposite:
|
||||
type: object
|
||||
properties:
|
||||
my_number:
|
||||
$ref: '#/definitions/OuterNumber'
|
||||
my_string:
|
||||
$ref: '#/definitions/OuterString'
|
||||
my_boolean:
|
||||
$ref: '#/definitions/OuterBoolean'
|
||||
OuterNumber:
|
||||
type: number
|
||||
OuterString:
|
||||
type: string
|
||||
OuterBoolean:
|
||||
type: boolean
|
||||
```
|
||||
@@ -14,6 +14,9 @@
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use OpenAPIServer\SlimRouter;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use OpenAPIServer\Mock\OpenApiDataMocker;
|
||||
|
||||
$config = [];
|
||||
|
||||
@@ -50,6 +53,35 @@ $config['tokenAuthenticationOptions'] = [
|
||||
// 'error' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Mocker Middleware options.
|
||||
*/
|
||||
$config['mockerOptions'] = [
|
||||
// 'dataMocker' => new OpenApiDataMocker(),
|
||||
|
||||
// 'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
|
||||
// // check if client clearly asks for mocked response
|
||||
// if (
|
||||
// $request->hasHeader('X-OpenAPIServer-Mock')
|
||||
// && $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
|
||||
// ) {
|
||||
// if (array_key_exists('default', $responses)) {
|
||||
// return $responses['default'];
|
||||
// }
|
||||
|
||||
// // return first response
|
||||
// return $responses[array_key_first($responses)];
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// },
|
||||
|
||||
// 'afterCallback' => function ($request, $response) {
|
||||
// // mark mocked response to distinguish real and fake responses
|
||||
// return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
|
||||
// },
|
||||
];
|
||||
|
||||
$router = new SlimRouter($config);
|
||||
$app = $router->getSlimApp();
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddleware
|
||||
*
|
||||
* PHP version 7.1
|
||||
*
|
||||
* @package OpenAPIServer
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI Petstore
|
||||
*
|
||||
* This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
* Generated by: https://github.com/openapitools/openapi-generator.git
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This class is auto generated by the openapi generator program.
|
||||
* https://github.com/openapitools/openapi-generator
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
namespace OpenAPIServer\Mock;
|
||||
|
||||
use Slim\Factory\AppFactory;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use OpenAPIServer\Mock\OpenApiDataMockerInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddleware Class Doc Comment
|
||||
*
|
||||
* @package OpenAPIServer\Mock
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
final class OpenApiDataMockerMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var OpenApiDataMockerInterface DataMocker.
|
||||
*/
|
||||
private $mocker;
|
||||
|
||||
/**
|
||||
* @var array Array of responses schemas.
|
||||
*/
|
||||
private $responses;
|
||||
|
||||
/**
|
||||
* @var callable|null Custom callback to select mocked response.
|
||||
*/
|
||||
private $getMockResponseCallback;
|
||||
|
||||
/**
|
||||
* @var callable|null Custom after callback.
|
||||
*/
|
||||
private $afterCallback;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param OpenApiDataMockerInterface $mocker DataMocker.
|
||||
* @param array $responses Array of responses schemas.
|
||||
* @param callable|null $getMockResponseCallback Custom callback to select mocked response.
|
||||
* Mock feature is disabled when this argument is null.
|
||||
* @example $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) {
|
||||
* // check if client clearly asks for mocked response
|
||||
* if (
|
||||
* $request->hasHeader('X-OpenAPIServer-Mock')
|
||||
* && $request->header('X-OpenAPIServer-Mock')[0] === 'ping'
|
||||
* ) {
|
||||
* return $responses[array_key_first($responses)];
|
||||
* }
|
||||
* return false;
|
||||
* };
|
||||
* @param callable|null $afterCallback After callback.
|
||||
* Function must return response instance.
|
||||
* @example $afterCallback = function (ServerRequestInterface $request, ResponseInterface $response) {
|
||||
* // mark mocked response to distinguish real and fake responses
|
||||
* return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
|
||||
* };
|
||||
*/
|
||||
public function __construct(
|
||||
OpenApiDataMockerInterface $mocker,
|
||||
array $responses,
|
||||
$getMockResponseCallback = null,
|
||||
$afterCallback = null
|
||||
) {
|
||||
$this->mocker = $mocker;
|
||||
$this->responses = $responses;
|
||||
if (is_callable($getMockResponseCallback)) {
|
||||
$this->getMockResponseCallback = $getMockResponseCallback;
|
||||
} elseif ($getMockResponseCallback !== null) {
|
||||
// wrong argument type
|
||||
throw new InvalidArgumentException('\$getMockResponseCallback must be closure or null');
|
||||
}
|
||||
|
||||
if (is_callable($afterCallback)) {
|
||||
$this->afterCallback = $afterCallback;
|
||||
} elseif ($afterCallback !== null) {
|
||||
// wrong argument type
|
||||
throw new InvalidArgumentException('\$afterCallback must be closure or null');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse incoming JSON input into a native PHP format
|
||||
*
|
||||
* @param ServerRequestInterface $request HTTP request
|
||||
* @param RequestHandlerInterface $handler Request handler
|
||||
*
|
||||
* @return ResponseInterface HTTP response
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$customCallback = $this->getMockResponseCallback;
|
||||
$customAfterCallback = $this->afterCallback;
|
||||
$mockedResponse = (is_callable($customCallback)) ? $customCallback($request, $this->responses) : null;
|
||||
if (
|
||||
is_array($mockedResponse)
|
||||
&& array_key_exists('code', $mockedResponse)
|
||||
&& array_key_exists('jsonSchema', $mockedResponse)
|
||||
) {
|
||||
// response schema succesfully selected, we can mock it now
|
||||
$statusCode = ($mockedResponse['code'] === 0) ? 200 : $mockedResponse['code'];
|
||||
$contentType = '*/*';
|
||||
$response = AppFactory::determineResponseFactory()->createResponse($statusCode);
|
||||
$responseSchema = json_decode($mockedResponse['jsonSchema'], true);
|
||||
|
||||
if (is_array($responseSchema) && array_key_exists('headers', $responseSchema)) {
|
||||
// response schema contains headers definitions, apply them one by one
|
||||
foreach ($responseSchema['headers'] as $headerName => $headerDefinition) {
|
||||
$response = $response->withHeader($headerName, $this->mocker->mockFromSchema($headerDefinition['schema']));
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
is_array($responseSchema)
|
||||
&& array_key_exists('content', $responseSchema)
|
||||
&& !empty($responseSchema['content'])
|
||||
) {
|
||||
// response schema contains body definition
|
||||
$responseContentSchema = null;
|
||||
foreach ($responseSchema['content'] as $schemaContentType => $schemaDefinition) {
|
||||
// we can respond in JSON format when any(*/*) content-type allowed
|
||||
// or JSON(application/json) content-type specifically defined
|
||||
if (
|
||||
$schemaContentType === '*/*'
|
||||
|| strtolower(substr($schemaContentType, 0, 16)) === 'application/json'
|
||||
) {
|
||||
$contentType = 'application/json';
|
||||
$responseContentSchema = $schemaDefinition['schema'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($contentType === 'application/json') {
|
||||
$responseBody = $this->mocker->mockFromSchema($responseContentSchema);
|
||||
$response->getBody()->write(json_encode($responseBody));
|
||||
} else {
|
||||
// notify developer that only application/json response supported so far
|
||||
$response->getBody()->write('Mock feature supports only "application/json" content-type!');
|
||||
}
|
||||
}
|
||||
|
||||
// after callback applied only when mocked response schema has been selected
|
||||
if (is_callable($customAfterCallback)) {
|
||||
$response = $customAfterCallback($request, $response);
|
||||
}
|
||||
|
||||
// no reason to execute following middlewares (auth, validation etc.)
|
||||
// return mocked response and end connection
|
||||
return $response
|
||||
->withHeader('Content-Type', $contentType);
|
||||
}
|
||||
|
||||
// no response selected, mock feature disabled
|
||||
// execute following middlewares
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ use Dyorg\TokenAuthentication;
|
||||
use Dyorg\TokenAuthentication\TokenSearch;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use OpenAPIServer\Middleware\JsonBodyParserMiddleware;
|
||||
use OpenAPIServer\Mock\OpenApiDataMocker;
|
||||
use OpenAPIServer\Mock\OpenApiDataMockerMiddleware;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
@@ -58,6 +60,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractAnotherFakeApi',
|
||||
'userClassname' => 'AnotherFakeApi',
|
||||
'operationId' => 'call123TestSpecialTags',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Client"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -69,6 +87,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'createXmlItem',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -80,6 +108,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'fakeOuterBooleanSerialize',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Output boolean',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Output boolean",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/OuterBoolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -91,6 +135,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'fakeOuterCompositeSerialize',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Output composite',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Output composite",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/OuterComposite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -102,6 +162,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'fakeOuterNumberSerialize',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Output number',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Output number",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/OuterNumber"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -113,6 +189,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'fakeOuterStringSerialize',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Output string',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Output string",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/OuterString"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -124,6 +216,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testBodyWithFileSchema',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Success',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Success",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -135,6 +237,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testBodyWithQueryParams',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Success',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Success",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -146,6 +258,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testClientModel',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Client"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -157,6 +285,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testEndpointParameters',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid username supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid username supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'User not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "User not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// http security schema named 'http_basic_test'
|
||||
[
|
||||
@@ -176,6 +322,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testEnumParameters',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid request',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid request",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'Not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -187,6 +351,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testGroupParameters',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Someting wrong',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Someting wrong",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -198,6 +372,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testInlineAdditionalProperties',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -209,6 +393,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testJsonFormData',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -220,6 +414,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeApi',
|
||||
'userClassname' => 'FakeApi',
|
||||
'operationId' => 'testQueryParameterCollectionFormat',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'Success',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Success",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -231,6 +435,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractFakeClassnameTags123Api',
|
||||
'userClassname' => 'FakeClassnameTags123Api',
|
||||
'operationId' => 'testClassname',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Client"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// apiKey security schema named 'api_key_query'
|
||||
[
|
||||
@@ -254,6 +474,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'addPet',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'405' => [
|
||||
'code' => 405,
|
||||
'message' => 'Invalid input',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid input",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -277,6 +515,41 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'findPetsByStatus',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid status value',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid status value",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -300,6 +573,41 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'findPetsByTags',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid tag value',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid tag value",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -323,6 +631,40 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'updatePet',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid ID supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid ID supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'Pet not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Pet not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'405' => [
|
||||
'code' => 405,
|
||||
'message' => 'Validation exception',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Validation exception",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -346,6 +688,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'deletePet',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid pet value',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid pet value",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -369,6 +729,43 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'getPetById',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid ID supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid ID supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'Pet not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Pet not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// apiKey security schema named 'api_key'
|
||||
[
|
||||
@@ -392,6 +789,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'updatePetWithForm',
|
||||
'responses' => [
|
||||
'405' => [
|
||||
'code' => 405,
|
||||
'message' => 'Invalid input',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid input",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -415,6 +822,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'uploadFile',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -438,6 +861,22 @@ class SlimRouter
|
||||
'classname' => 'AbstractPetApi',
|
||||
'userClassname' => 'PetApi',
|
||||
'operationId' => 'uploadFileWithRequiredFile',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// oauth2 security schema named 'petstore_auth'
|
||||
[
|
||||
@@ -461,6 +900,26 @@ class SlimRouter
|
||||
'classname' => 'AbstractStoreApi',
|
||||
'userClassname' => 'StoreApi',
|
||||
'operationId' => 'getInventory',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"type" : "integer",
|
||||
"format" : "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
// apiKey security schema named 'api_key'
|
||||
[
|
||||
@@ -484,6 +943,35 @@ class SlimRouter
|
||||
'classname' => 'AbstractStoreApi',
|
||||
'userClassname' => 'StoreApi',
|
||||
'operationId' => 'placeOrder',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Order"
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid Order',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid Order",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -495,6 +983,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractStoreApi',
|
||||
'userClassname' => 'StoreApi',
|
||||
'operationId' => 'deleteOrder',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid ID supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid ID supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'Order not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Order not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -506,6 +1012,43 @@ class SlimRouter
|
||||
'classname' => 'AbstractStoreApi',
|
||||
'userClassname' => 'StoreApi',
|
||||
'operationId' => 'getOrderById',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Order"
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid ID supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid ID supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'Order not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Order not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -517,6 +1060,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'createUser',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 0,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -528,6 +1081,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'createUsersWithArrayInput',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 0,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -539,6 +1102,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'createUsersWithListInput',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 0,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -550,6 +1123,51 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'loginUser',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"headers" : {
|
||||
"X-Rate-Limit" : {
|
||||
"description" : "calls per hour allowed by the user",
|
||||
"schema" : {
|
||||
"type" : "integer",
|
||||
"format" : "int32"
|
||||
}
|
||||
},
|
||||
"X-Expires-After" : {
|
||||
"description" : "date in UTC when token expires",
|
||||
"schema" : {
|
||||
"type" : "string",
|
||||
"format" : "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid username/password supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid username/password supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -561,6 +1179,16 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'logoutUser',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 0,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -572,6 +1200,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'deleteUser',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid username supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid username supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'User not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "User not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -583,6 +1229,43 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'getUserByName',
|
||||
'responses' => [
|
||||
'default' => [
|
||||
'code' => 200,
|
||||
'message' => 'successful operation',
|
||||
'jsonSchema' => '{
|
||||
"description" : "successful operation",
|
||||
"content" : {
|
||||
"application/xml" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/User"
|
||||
}
|
||||
},
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid username supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid username supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'User not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "User not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -594,6 +1277,24 @@ class SlimRouter
|
||||
'classname' => 'AbstractUserApi',
|
||||
'userClassname' => 'UserApi',
|
||||
'operationId' => 'updateUser',
|
||||
'responses' => [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'message' => 'Invalid user supplied',
|
||||
'jsonSchema' => '{
|
||||
"description" : "Invalid user supplied",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
'404' => [
|
||||
'code' => 404,
|
||||
'message' => 'User not found',
|
||||
'jsonSchema' => '{
|
||||
"description" : "User not found",
|
||||
"content" : { }
|
||||
}',
|
||||
],
|
||||
],
|
||||
'authMethods' => [
|
||||
],
|
||||
],
|
||||
@@ -631,12 +1332,13 @@ class SlimRouter
|
||||
throw new Exception($message);
|
||||
};
|
||||
|
||||
$userOptions = null;
|
||||
if ($settings instanceof ContainerInterface && $settings->has('tokenAuthenticationOptions')) {
|
||||
$userOptions = $settings->get('tokenAuthenticationOptions');
|
||||
} elseif (is_array($settings) && isset($settings['tokenAuthenticationOptions'])) {
|
||||
$userOptions = $settings['tokenAuthenticationOptions'];
|
||||
}
|
||||
$userOptions = $this->getSetting($settings, 'tokenAuthenticationOptions', null);
|
||||
|
||||
// mocker options
|
||||
$mockerOptions = $this->getSetting($settings, 'mockerOptions', null);
|
||||
$dataMocker = $mockerOptions['dataMocker'] ?? new OpenApiDataMocker();
|
||||
$getMockResponseCallback = $mockerOptions['getMockResponseCallback'] ?? null;
|
||||
$mockAfterCallback = $mockerOptions['afterCallback'] ?? null;
|
||||
|
||||
foreach ($this->operations as $operation) {
|
||||
$callback = function ($request, $response, $arguments) use ($operation) {
|
||||
@@ -703,6 +1405,10 @@ class SlimRouter
|
||||
}
|
||||
}
|
||||
|
||||
if (is_callable($getMockResponseCallback)) {
|
||||
$middlewares[] = new OpenApiDataMockerMiddleware($dataMocker, $operation['responses'], $getMockResponseCallback, $mockAfterCallback);
|
||||
}
|
||||
|
||||
$this->addRoute(
|
||||
[$operation['httpMethod']],
|
||||
"{$operation['basePathWithoutHost']}{$operation['path']}",
|
||||
@@ -729,6 +1435,26 @@ class SlimRouter
|
||||
return array_merge($userOptions, $staticOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns app setting by name.
|
||||
*
|
||||
* @param ContainerInterface|array $settings Either a ContainerInterface or an associative array of app settings
|
||||
* @param string $settingName Setting name
|
||||
* @param mixed $default Default setting value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getSetting($settings, $settingName, $default = null)
|
||||
{
|
||||
if ($settings instanceof ContainerInterface && $settings->has($settingName)) {
|
||||
return $settings->get($settingName);
|
||||
} elseif (is_array($settings) && array_key_exists($settingName, $settings)) {
|
||||
return $settings[$settingName];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add route with multiple methods
|
||||
*
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddlewareTest
|
||||
*
|
||||
* PHP version 7.1
|
||||
*
|
||||
* @package OpenAPIServer
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPIServer
|
||||
*
|
||||
* This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
* Generated by: https://github.com/openapitools/openapi-generator.git
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This class is auto generated by the openapi generator program.
|
||||
* https://github.com/openapitools/openapi-generator
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
namespace OpenAPIServer\Mock;
|
||||
|
||||
use OpenAPIServer\Mock\OpenApiDataMockerMiddleware;
|
||||
use OpenAPIServer\Mock\OpenApiDataMocker;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Factory\ServerRequestCreatorFactory;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* OpenApiDataMockerMiddlewareTest Class Doc Comment
|
||||
*
|
||||
* @package OpenAPIServer\Mock
|
||||
* @author OpenAPI Generator team
|
||||
* @link https://github.com/openapitools/openapi-generator
|
||||
* @coversDefaultClass \OpenAPIServer\Mock\OpenApiDataMockerMiddleware
|
||||
*/
|
||||
class OpenApiDataMockerMiddlewareTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @dataProvider provideConstructCorrectArguments
|
||||
*/
|
||||
public function testConstructor(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
) {
|
||||
$middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
|
||||
$this->assertInstanceOf(OpenApiDataMockerMiddleware::class, $middleware);
|
||||
$this->assertNotNull($middleware);
|
||||
}
|
||||
|
||||
public function provideConstructCorrectArguments()
|
||||
{
|
||||
$getMockResponseCallback = function () {
|
||||
return false;
|
||||
};
|
||||
$afterCallback = function () {
|
||||
return false;
|
||||
};
|
||||
return [
|
||||
[new OpenApiDataMocker(), [], null, null],
|
||||
[new OpenApiDataMocker(), [], $getMockResponseCallback, $afterCallback],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @dataProvider provideConstructInvalidArguments
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedException \TypeError
|
||||
*/
|
||||
public function testConstructorWithInvalidArguments(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
) {
|
||||
$middleware = new OpenApiDataMockerMiddleware($mocker, $responses, $getMockResponseCallback, $afterCallback);
|
||||
}
|
||||
|
||||
public function provideConstructInvalidArguments()
|
||||
{
|
||||
return [
|
||||
'getMockResponseCallback not callable' => [
|
||||
new OpenApiDataMocker(), [], 'foobar', null,
|
||||
],
|
||||
'afterCallback not callable' => [
|
||||
new OpenApiDataMocker(), [], null, 'foobar',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::process
|
||||
* @dataProvider provideProcessArguments
|
||||
*/
|
||||
public function testProcess(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$request,
|
||||
$expectedStatusCode,
|
||||
$expectedHeaders,
|
||||
$notExpectedHeaders,
|
||||
$expectedBody
|
||||
) {
|
||||
|
||||
// Create a stub for the RequestHandlerInterface interface.
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$handler->method('handle')
|
||||
->willReturn(AppFactory::determineResponseFactory()->createResponse());
|
||||
|
||||
$middleware = new OpenApiDataMockerMiddleware(
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback
|
||||
);
|
||||
$response = $middleware->process($request, $handler);
|
||||
|
||||
// check status code
|
||||
$this->assertSame($expectedStatusCode, $response->getStatusCode());
|
||||
|
||||
// check http headers in request
|
||||
foreach ($expectedHeaders as $expectedHeader => $expectedHeaderValue) {
|
||||
$this->assertTrue($response->hasHeader($expectedHeader));
|
||||
if ($expectedHeaderValue !== '*') {
|
||||
$this->assertSame($expectedHeaderValue, $response->getHeader($expectedHeader)[0]);
|
||||
}
|
||||
}
|
||||
foreach ($notExpectedHeaders as $notExpectedHeader) {
|
||||
$this->assertFalse($response->hasHeader($notExpectedHeader));
|
||||
}
|
||||
|
||||
// check body
|
||||
if (is_array($expectedBody)) {
|
||||
// random values, check keys only
|
||||
foreach ($expectedBody as $attribute => $value) {
|
||||
$this->assertObjectHasAttribute($attribute, json_decode((string) $response->getBody(), false));
|
||||
}
|
||||
} else {
|
||||
$this->assertEquals($expectedBody, (string) $response->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideProcessArguments()
|
||||
{
|
||||
$mocker = new OpenApiDataMocker();
|
||||
$isMockResponseRequired = function (ServerRequestInterface $request) {
|
||||
$mockHttpHeader = 'X-OpenAPIServer-Mock';
|
||||
return $request->hasHeader($mockHttpHeader)
|
||||
&& $request->getHeader($mockHttpHeader)[0] === 'ping';
|
||||
};
|
||||
|
||||
$getMockResponseCallback = function (ServerRequestInterface $request, array $responses) use ($isMockResponseRequired) {
|
||||
if ($isMockResponseRequired($request)) {
|
||||
if (array_key_exists('default', $responses)) {
|
||||
return $responses['default'];
|
||||
}
|
||||
|
||||
// return first response
|
||||
return $responses[array_key_first($responses)];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$afterCallback = function ($request, $response) use ($isMockResponseRequired) {
|
||||
if ($isMockResponseRequired($request)) {
|
||||
$response = $response->withHeader('X-OpenAPIServer-Mock', 'pong');
|
||||
}
|
||||
|
||||
return $response;
|
||||
};
|
||||
|
||||
$responses = [
|
||||
'400' => [
|
||||
'code' => 400,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Bad Request Response',
|
||||
'content' => new StdClass(),
|
||||
]),
|
||||
],
|
||||
'default' => [
|
||||
'code' => 201,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Success Response',
|
||||
'headers' => [
|
||||
'X-Location' => ['schema' => ['type' => 'string']],
|
||||
'X-Created-Id' => ['schema' => ['type' => 'integer']],
|
||||
],
|
||||
'content' => [
|
||||
'application/json;encoding=utf-8' => ['schema' => ['type' => 'object', 'properties' => ['id' => ['type' => 'integer'], 'className' => ['type' => 'string'], 'declawed' => ['type' => 'boolean']]]],
|
||||
],
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
$responsesXmlOnly = [
|
||||
'default' => [
|
||||
'code' => 201,
|
||||
'jsonSchema' => json_encode([
|
||||
'description' => 'Success Response',
|
||||
'content' => [
|
||||
'application/xml' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
$requestFactory = ServerRequestCreatorFactory::create();
|
||||
|
||||
return [
|
||||
'callbacks null' => [
|
||||
$mocker,
|
||||
$responses,
|
||||
null,
|
||||
null,
|
||||
$requestFactory->createServerRequestFromGlobals(),
|
||||
200,
|
||||
[],
|
||||
['X-OpenAPIServer-Mock', 'x-location', 'x-created-id'],
|
||||
'',
|
||||
],
|
||||
'xml not supported' => [
|
||||
$mocker,
|
||||
$responsesXmlOnly,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$requestFactory
|
||||
->createServerRequestFromGlobals()
|
||||
->withHeader('X-OpenAPIServer-Mock', 'ping'),
|
||||
201,
|
||||
['X-OpenAPIServer-Mock' => 'pong', 'content-type' => '*/*'],
|
||||
['x-location', 'x-created-id'],
|
||||
'Mock feature supports only "application/json" content-type!',
|
||||
],
|
||||
'mock response default schema' => [
|
||||
$mocker,
|
||||
$responses,
|
||||
$getMockResponseCallback,
|
||||
$afterCallback,
|
||||
$requestFactory
|
||||
->createServerRequestFromGlobals()
|
||||
->withHeader('X-OpenAPIServer-Mock', 'ping'),
|
||||
201,
|
||||
['X-OpenAPIServer-Mock' => 'pong', 'content-type' => 'application/json', 'x-location' => '*', 'x-created-id' => '*'],
|
||||
[],
|
||||
[
|
||||
'id' => 1,
|
||||
'className' => 'cat',
|
||||
'declawed' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user