diff --git a/README.md b/README.md index a85593e7..4dbfcf88 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ public function show($id) #### @transformer, @transformerCollection, and @transformerModel You can define the transformer that is used for the result of the route using the `@transformer` tag (or `@transformerCollection` if the route returns a list). The package will attempt to generate an instance of the model to be transformed using the following steps, stopping at the first successful one: -1. Check if there is a `@transformerModel` tag to define the model being transformed. If there is none, use the class of the first parameter to the method. +1. Check if there is a `@transformerModel` tag to define the model being transformed. If there is none, use the class of the first parameter to the transformer's `transform()` method. 2. Get an instance of the model from the Eloquent model factory 2. If the parameter is an Eloquent model, load the first from the database. 3. Create an instance using `new`. diff --git a/config/apidoc.php b/config/apidoc.php index e5232548..ba95788a 100644 --- a/config/apidoc.php +++ b/config/apidoc.php @@ -84,6 +84,62 @@ // 'Authorization' => 'Bearer: {token}', // 'Api-Version' => 'v2', ], + + /* + * If no @response or @transformer declaratons are found for the route, + * we'll try to get a sample response by attempting an API call. + * Configure the settings for the API call here, + */ + 'response_calls' => [ + /* + * What HTTP methods (GET, POST, etc) should API calls be made for. List the methods here + * or use '*' to mean all methods. Set to false to disable API calls. + */ + 'methods' => ['*'], + + /* + * For URLs which have parameters (/users/{user}, /orders/{id?}), + * specify what values the parameters should be replaced with. + * Note that you must specify the full parameter, including curly brackets and question marks if any. + */ + 'bindings' => [ + // '{user}' => 1 + ], + + /* + * Environment variables which should be set for the API call. + */ + 'env' => [ + 'APP_ENV' => 'documentation', + 'APP_DEBUG' => false, + // 'env_var' => 'value', + ], + + /* + * Headers which should be sent with the API call. + */ + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + // 'key' => 'value', + ], + + + /* + * Query parameters which should be sent with the API call. + */ + 'query' => [ + // 'key' => 'value', + ], + + + /* + * Body parameters which should be sent with the API call. + */ + 'body' => [ + // 'key' => 'value', + ], + ], ], ], ], diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index 0642714c..1923171a 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -187,7 +187,7 @@ private function processRoutes(AbstractGenerator $generator, array $routes) $route = $routeItem['route']; /** @var Route $route */ if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) { - $parsedRoutes[] = $generator->processRoute($route) + $routeItem['apply']; + $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply']); $this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); } else { $this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); diff --git a/src/Generators/AbstractGenerator.php b/src/Generators/AbstractGenerator.php index 19bd64e7..b5e2b801 100644 --- a/src/Generators/AbstractGenerator.php +++ b/src/Generators/AbstractGenerator.php @@ -4,13 +4,11 @@ use Faker\Factory; use ReflectionClass; -use Illuminate\Support\Str; -use League\Fractal\Manager; +use ReflectionMethod; use Illuminate\Routing\Route; use Mpociot\Reflection\DocBlock; -use League\Fractal\Resource\Item; use Mpociot\Reflection\DocBlock\Tag; -use League\Fractal\Resource\Collection; +use Mpociot\ApiDoc\Tools\ResponseResolver; abstract class AbstractGenerator { @@ -50,14 +48,18 @@ public function getMethods(Route $route) * * @return array */ - public function processRoute($route) + public function processRoute(Route $route, array $rulesToApply = []) { $routeAction = $route->getAction(); - $routeGroup = $this->getRouteGroup($routeAction['uses']); - $docBlock = $this->parseDocBlock($routeAction['uses']); - $content = $this->getResponse($docBlock['tags']); + list($class, $method) = explode('@', $routeAction['uses']); + $controller = new ReflectionClass($class); + $method = $controller->getMethod($method); - return [ + $routeGroup = $this->getRouteGroup($controller, $method); + $docBlock = $this->parseDocBlock($method); + $content = ResponseResolver::getResponse($route, $docBlock['tags'], $rulesToApply); + + $parsedRoute = [ 'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))), 'group' => $routeGroup, 'title' => $docBlock['short'], @@ -69,6 +71,9 @@ public function processRoute($route) 'response' => $content, 'showresponse' => ! empty($content), ]; + $parsedRoute['headers'] = $rulesToApply['headers'] ?? []; + + return $parsedRoute; } /** @@ -80,26 +85,6 @@ public function processRoute($route) */ abstract public function prepareMiddleware($enable = false); - /** - * Get the response from the docblock if available. - * - * @param array $tags - * - * @return mixed - */ - protected function getDocblockResponse($tags) - { - $responseTags = array_filter($tags, function ($tag) { - return $tag instanceof Tag && \strtolower($tag->getName()) == 'response'; - }); - if (empty($responseTags)) { - return; - } - $responseTag = \array_first($responseTags); - - return \response(json_encode($responseTag->getContent()), 200, ['Content-Type' => 'application/json']); - } - /** * @param array $tags * @@ -153,62 +138,13 @@ protected function getAuthStatusFromDocBlock(array $tags) } /** - * @param $route - * @param $bindings - * @param $headers - * - * @return \Illuminate\Http\Response - */ - protected function getRouteResponse($route, $bindings, $headers = []) - { - $uri = $this->addRouteModelBindings($route, $bindings); - - $methods = $this->getMethods($route); - - // Split headers into key - value pairs - $headers = collect($headers)->map(function ($value) { - $split = explode(':', $value); // explode to get key + values - $key = array_shift($split); // extract the key and keep the values in the array - $value = implode(':', $split); // implode values into string again - - return [trim($key) => trim($value)]; - })->collapse()->toArray(); - - //Changes url with parameters like /users/{user} to /users/1 - $uri = preg_replace('/{(.*?)}/', 1, $uri); // 1 is the default value for route parameters - - return $this->callRoute(array_shift($methods), $uri, [], [], [], $headers); - } - - /** - * @param $route - * @param array $bindings - * - * @return mixed - */ - protected function addRouteModelBindings($route, $bindings) - { - $uri = $this->getUri($route); - foreach ($bindings as $model => $id) { - $uri = str_replace('{'.$model.'}', $id, $uri); - $uri = str_replace('{'.$model.'?}', $id, $uri); - } - - return $uri; - } - - /** - * @param \Illuminate\Routing\Route $routeAction + * @param ReflectionMethod $method * * @return array */ - protected function parseDocBlock(string $routeAction) + protected function parseDocBlock(ReflectionMethod $method) { - list($class, $method) = explode('@', $routeAction); - $reflection = new ReflectionClass($class); - $reflectionMethod = $reflection->getMethod($method); - - $comment = $reflectionMethod->getDocComment(); + $comment = $method->getDocComment(); $phpdoc = new DocBlock($comment); return [ @@ -219,17 +155,15 @@ protected function parseDocBlock(string $routeAction) } /** - * @param string $routeAction + * @param ReflectionClass $controller + * @param ReflectionMethod $method * * @return string + * */ - protected function getRouteGroup(string $routeAction) + protected function getRouteGroup(ReflectionClass $controller, ReflectionMethod $method) { - list($class, $method) = explode('@', $routeAction); - $controller = new ReflectionClass($class); - // @group tag on the method overrides that on the controller - $method = $controller->getMethod($method); $docBlockComment = $method->getDocComment(); if ($docBlockComment) { $phpdoc = new DocBlock($docBlockComment); @@ -253,174 +187,6 @@ protected function getRouteGroup(string $routeAction) return 'general'; } - /** - * Call the given URI and return the Response. - * - * @param string $method - * @param string $uri - * @param array $parameters - * @param array $cookies - * @param array $files - * @param array $server - * @param string $content - * - * @return \Illuminate\Http\Response - */ - abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null); - - /** - * Transform headers array to array of $_SERVER vars with HTTP_* format. - * - * @param array $headers - * - * @return array - */ - protected function transformHeadersToServerVars(array $headers) - { - $server = []; - $prefix = 'HTTP_'; - - foreach ($headers as $name => $value) { - $name = strtr(strtoupper($name), '-', '_'); - - if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') { - $name = $prefix.$name; - } - - $server[$name] = $value; - } - - return $server; - } - - /** - * @param $response - * - * @return mixed - */ - private function getResponseContent($response) - { - if (empty($response)) { - return ''; - } - if ($response->headers->get('Content-Type') === 'application/json') { - $content = json_decode($response->getContent(), JSON_PRETTY_PRINT); - } else { - $content = $response->getContent(); - } - - return $content; - } - - /** - * Get a response from the transformer tags. - * - * @param array $tags - * - * @return mixed - */ - protected function getTransformerResponse($tags) - { - try { - $transFormerTags = array_filter($tags, function ($tag) { - if (! ($tag instanceof Tag)) { - return false; - } - - return \in_array(\strtolower($tag->getName()), ['transformer', 'transformercollection']); - }); - if (empty($transFormerTags)) { - // we didn't have any of the tags so goodbye - return false; - } - - $modelTag = array_first(array_filter($tags, function ($tag) { - if (! ($tag instanceof Tag)) { - return false; - } - - return \in_array(\strtolower($tag->getName()), ['transformermodel']); - })); - $tag = \array_first($transFormerTags); - $transformer = $tag->getContent(); - if (! \class_exists($transformer)) { - // if we can't find the transformer we can't generate a response - return; - } - $demoData = []; - - $reflection = new ReflectionClass($transformer); - $method = $reflection->getMethod('transform'); - $parameter = \array_first($method->getParameters()); - $type = null; - if ($modelTag) { - $type = $modelTag->getContent(); - } - if (\is_null($type)) { - if ($parameter->hasType() && - ! $parameter->getType()->isBuiltin() && - \class_exists((string) $parameter->getType())) { - //we have a type - $type = (string) $parameter->getType(); - } - } - if ($type) { - // we have a class so we try to create an instance - $demoData = new $type; - try { - // try a factory - $demoData = \factory($type)->make(); - } catch (\Exception $e) { - if ($demoData instanceof \Illuminate\Database\Eloquent\Model) { - // we can't use a factory but can try to get one from the database - try { - // check if we can find one - $newDemoData = $type::first(); - if ($newDemoData) { - $demoData = $newDemoData; - } - } catch (\Exception $e) { - // do nothing - } - } - } - } - - $fractal = new Manager(); - $resource = []; - if ($tag->getName() == 'transformer') { - // just one - $resource = new Item($demoData, new $transformer); - } - if ($tag->getName() == 'transformercollection') { - // a collection - $resource = new Collection([$demoData, $demoData], new $transformer); - } - - return \response($fractal->createData($resource)->toJson()); - } catch (\Exception $e) { - // it isn't possible to parse the transformer - return; - } - } - - private function getResponse(array $annotationTags) - { - $response = null; - if ($docblockResponse = $this->getDocblockResponse($annotationTags)) { - // we have a response from the docblock ( @response ) - $response = $docblockResponse; - } - if (! $response && ($transformerResponse = $this->getTransformerResponse($annotationTags))) { - // we have a transformer response from the docblock ( @transformer || @transformercollection ) - $response = $transformerResponse; - } - - $content = $response ? $this->getResponseContent($response) : null; - - return $content; - } - private function normalizeParameterType($type) { $typeMap = [ diff --git a/src/Tools/ResponseResolver.php b/src/Tools/ResponseResolver.php new file mode 100644 index 00000000..f515e77e --- /dev/null +++ b/src/Tools/ResponseResolver.php @@ -0,0 +1,54 @@ +route = $route; + } + + private function resolve(array $tags, array $rulesToApply) + { + $response = null; + foreach (static::$strategies as $strategy) { + $strategy = new $strategy(); + $response = $strategy($this->route, $tags, $rulesToApply); + if (!is_null($response)) { + return $this->getResponseContent($response); + } + } + } + + public static function getResponse($route, $tags, $rulesToApply) + { + return (new static($route))->resolve($tags, $rulesToApply); + } + + /** + * @param $response + * + * @return mixed + */ + private function getResponseContent($response) + { + return $response ? $response->getContent() : ''; + } +} diff --git a/src/Tools/ResponseStrategies/ResponseCallStrategy.php b/src/Tools/ResponseStrategies/ResponseCallStrategy.php new file mode 100644 index 00000000..3980d1de --- /dev/null +++ b/src/Tools/ResponseStrategies/ResponseCallStrategy.php @@ -0,0 +1,245 @@ +shouldMakeApiCall($route, $rulesToApply)) { + return; + } + + $this->configureEnvironment($rulesToApply); + $request = $this->prepareRequest($route, $rulesToApply); + try { + $response = $this->makeApiCall($request); + } catch (\Exception $e) { + $response = null; + } finally { + $this->finish(); + } + + return $response; + } + + private function configureEnvironment(array $rulesToApply) + { + $this->enableDbTransactions(); + $this->setEnvironmentVariables($rulesToApply['env'] ?? []); + } + + private function prepareRequest(Route $route, array $rulesToApply) + { + $uri = $this->replaceUrlParameterBindings($route, $rulesToApply['bindings'] ?? []); + $routeMethods = $this->getMethods($route); + $method = array_shift($routeMethods); + $request = Request::create($uri, $method, [], [], [], $this->transformHeadersToServerVars($rulesToApply['headers'] ?? [])); + $request = $this->addHeaders($request, $route, $rulesToApply['headers'] ?? []); + $request = $this->addQueryParameters($request, $rulesToApply['query'] ?? []); + $request = $this->addBodyParameters($request, $rulesToApply['body'] ?? []); + + return $request; + } + + /** + * Transform parameters in URLs into real values (/users/{user} -> /users/2). + * Uses bindings specified by caller, otherwise just uses '1' + * + * @param Route $route + * @param array $bindings + * + * @return mixed + */ + protected function replaceUrlParameterBindings(Route $route, $bindings) + { + $uri = $route->uri(); + foreach ($bindings as $parameter => $binding) { + $uri = str_replace($parameter, $binding, $uri); + } + // Replace any unbound parameters with '1' + $uri = preg_replace('/{(.*?)}/', 1, $uri); + + return $uri; + } + + private function setEnvironmentVariables(array $env) + { + foreach ($env as $name => $value) { + putenv("$name=$value"); + + $_ENV[$name] = $value; + $_SERVER[$name] = $value; + } + } + + private function enableDbTransactions() + { + try { + app('db')->beginTransaction(); + } catch (\Exception $e) { + + } + } + + private function disableDbTransactions() + { + try { + app('db')->rollBack(); + } catch (\Exception $e) { + + } + } + + private function finish() + { + $this->disableDbTransactions(); + } + + /** + * {@inheritdoc} + */ + public function callDingoRoute(Request $request) + { + /** @var Dispatcher $dispatcher */ + $dispatcher = app(\Dingo\Api\Dispatcher::class); + + foreach ($request->headers as $header => $value) { + $dispatcher->header($header, $value); + } + + // set domain and body parameters + $dispatcher->on($request->header('SERVER_NAME')) + ->with($request->request->all()); + + // set URL and query parameters + $uri = $request->getRequestUri(); + $query = $request->getQueryString(); + if (!empty($query)) { + $uri .= "?$query"; + } + $response = call_user_func_array([$dispatcher, strtolower($request->method())], [$uri]); + + // the response from the Dingo dispatcher is the 'raw' response from the controller, + // so we have to ensure it's JSON first + if (! $response instanceof Response) { + $response = response()->json($response); + } + + return $response; + } + + public function getMethods(Route $route) + { + return array_diff($route->methods(), ['HEAD']); + } + + private function addHeaders(Request $request, Route $route, $headers) + { + // set the proper domain + if ($route->getDomain()) { + $request->server->add([ + 'HTTP_HOST' => $route->getDomain(), + 'SERVER_NAME' => $route->getDomain(), + ]); + } + + $headers = collect($headers); + + if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') { + $request->setRequestFormat('json'); + } + + return $request; + } + + private function addQueryParameters(Request $request, array $query) + { + $request->query->add($query); + $request->server->add(['QUERY_STRING' => http_build_query($query)]); + + return $request; + } + + private function addBodyParameters(Request $request, array $body) + { + $request->request->add($body); + + return $request; + } + + private function makeApiCall(Request $request) + { + if (config('apidoc.router') == 'dingo') { + $response = $this->callDingoRoute($request); + } else { + $response = $this->callLaravelRoute($request); + } + + return $response; + } + + /** + * @param $request + * + * @return \Symfony\Component\HttpFoundation\Response + */ + private function callLaravelRoute($request): \Symfony\Component\HttpFoundation\Response + { + $kernel = app(\Illuminate\Contracts\Http\Kernel::class); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + return $response; + } + + private function shouldMakeApiCall(Route $route, array $rulesToApply): bool + { + $allowedMethods = $rulesToApply['methods'] ?? []; + if (empty($allowedMethods)) { + return false; + } + + if (array_search('*', $allowedMethods) !== false) { + return true; + } + + $routeMethods = $this->getMethods($route); + if (in_array(array_shift($routeMethods), $allowedMethods)) { + return true; + } + + return false; + } + + /** + * Transform headers array to array of $_SERVER vars with HTTP_* format. + * + * @param array $headers + * + * @return array + */ + protected function transformHeadersToServerVars(array $headers) + { + $server = []; + $prefix = 'HTTP_'; + foreach ($headers as $name => $value) { + $name = strtr(strtoupper($name), '-', '_'); + if (! starts_with($name, $prefix) && $name !== 'CONTENT_TYPE') { + $name = $prefix.$name; + } + $server[$name] = $value; + } + + return $server; + } +} diff --git a/src/Tools/ResponseStrategies/ResponseTagStrategy.php b/src/Tools/ResponseStrategies/ResponseTagStrategy.php new file mode 100644 index 00000000..0635c566 --- /dev/null +++ b/src/Tools/ResponseStrategies/ResponseTagStrategy.php @@ -0,0 +1,38 @@ +getDocBlockResponse($tags); + } + + /** + * Get the response from the docblock if available. + * + * @param array $tags + * + * @return mixed + */ + protected function getDocBlockResponse(array $tags) + { + $responseTags = array_filter($tags, function ($tag) { + return $tag instanceof Tag && strtolower($tag->getName()) == 'response'; + }); + if (empty($responseTags)) { + return; + } + $responseTag = array_first($responseTags); + + return response()->json(json_decode($responseTag->getContent(), true)); + } + +} diff --git a/src/Tools/ResponseStrategies/TransformerTagsStrategy.php b/src/Tools/ResponseStrategies/TransformerTagsStrategy.php new file mode 100644 index 00000000..9fbfcd46 --- /dev/null +++ b/src/Tools/ResponseStrategies/TransformerTagsStrategy.php @@ -0,0 +1,130 @@ +getTransformerResponse($tags); + } + + /** + * Get a response from the transformer tags. + * + * @param array $tags + * + * @return mixed + */ + protected function getTransformerResponse(array $tags) + { + try { + if(empty($transformerTag = $this->getTransformerTag($tags))) { + return; + } + + $transformer = $this->getTransformerClass($transformerTag); + $model = $this->getClassToBeTransformed($tags, (new ReflectionClass($transformer))->getMethod('transform')); + $modelInstance = $this->instantiateTransformerModel($model); + + $fractal = new Manager(); + $resource = (strtolower($transformerTag->getName()) == 'transformercollection') + ? new Collection([$modelInstance, $modelInstance], new $transformer) + : new Item($modelInstance, new $transformer); + + return response($fractal->createData($resource)->toJson()); + } catch (\Exception $e) { + return; + } + } + + /** + * @param Tag $tag + * + * @return string|null + */ + private function getTransformerClass($tag) + { + return $tag->getContent(); + } + + /** + * @param array $tags + * @param ReflectionMethod $transformerMethod + * + * @return null|string + */ + private function getClassToBeTransformed(array $tags, ReflectionMethod $transformerMethod) + { + $modelTag = array_first(array_filter($tags, function ($tag) { + return ($tag instanceof Tag) && strtolower($tag->getName()) == 'transformermodel'; + })); + + $type = null; + if ($modelTag) { + $type = $modelTag->getContent(); + } else { + $parameter = array_first($transformerMethod->getParameters()); + if ($parameter->hasType() && ! $parameter->getType()->isBuiltin() && class_exists((string) $parameter->getType())) { + // ladies and gentlemen, we have a type! + $type = (string) $parameter->getType(); + } + } + + return $type; + + } + + /** + * @param string $type + * + * @return mixed + */ + protected function instantiateTransformerModel(string $type) + { + try { + // try Eloquent model factory + return factory($type)->make(); + } catch (\Exception $e) { + $instance = new $type; + if ($instance instanceof \Illuminate\Database\Eloquent\Model) { + try { + // we can't use a factory but can try to get one from the database + $firstInstance = $type::first(); + if ($firstInstance) { + return $firstInstance; + } + } catch (\Exception $e) { + // okay, we'll stick with `new` + } + } + } + + return $instance; + } + + /** + * @param array $tags + * + * @return Tag|null + */ + private function getTransformerTag(array $tags) + { + $transFormerTags = array_filter($tags, function ($tag) { + return ($tag instanceof Tag) && in_array(strtolower($tag->getName()), ['transformer', 'transformercollection']); + }); + + return array_first($transFormerTags); + } +} diff --git a/tests/Fixtures/TestController.php b/tests/Fixtures/TestController.php index 95d0925d..8192d69f 100644 --- a/tests/Fixtures/TestController.php +++ b/tests/Fixtures/TestController.php @@ -61,19 +61,30 @@ public function checkCustomHeaders(Request $request) public function shouldFetchRouteResponse() { - $fixture = new \stdClass(); - $fixture->id = 1; - $fixture->name = 'banana'; - $fixture->color = 'red'; - $fixture->weight = 300; - $fixture->delicious = 1; + $fruit = new \stdClass(); + $fruit->id = 4; + $fruit->name = ' banana '; + $fruit->color = 'RED'; + $fruit->weight = 1; + $fruit->delicious = true; return [ - 'id' => (int) $fixture->id, - 'name' => ucfirst($fixture->name), - 'color' => ucfirst($fixture->color), - 'weight' => $fixture->weight.' grams', - 'delicious' => (bool) $fixture->delicious, + 'id' => (int) $fruit->id, + 'name' => trim($fruit->name), + 'color' => strtolower($fruit->color), + 'weight' => $fruit->weight.' kg', + 'delicious' => $fruit->delicious, + ]; + } + + public function shouldFetchRouteResponseWithEchoedSettings($id) + { + return [ + '{id}' => $id, + 'APP_ENV' => getenv('APP_ENV'), + 'header' => request()->header('header'), + 'queryParam' => request()->query('queryParam'), + 'bodyParam' => request()->get('bodyParam'), ]; } diff --git a/tests/Fixtures/TestResourceController.php b/tests/Fixtures/TestResourceController.php index 7ec2bb55..234aa34a 100644 --- a/tests/Fixtures/TestResourceController.php +++ b/tests/Fixtures/TestResourceController.php @@ -48,9 +48,7 @@ public function create() */ public function store(Request $request) { - return [ - 'store_resource' => true, - ]; + return; } /** @@ -99,9 +97,6 @@ public function edit($id) */ public function update(Request $request, $id) { - return [ - 'update_resource' => $id, - ]; } /** @@ -113,8 +108,5 @@ public function update(Request $request, $id) */ public function destroy($id) { - return [ - 'destroy_resource' => $id, - ]; } } diff --git a/tests/Unit/DingoGeneratorTest.php b/tests/Unit/DingoGeneratorTest.php index e7e29500..68b1d0d0 100644 --- a/tests/Unit/DingoGeneratorTest.php +++ b/tests/Unit/DingoGeneratorTest.php @@ -12,8 +12,8 @@ class DingoGeneratorTest extends GeneratorTestCase protected function getPackageProviders($app) { return [ - \Dingo\Api\Provider\LaravelServiceProvider::class, ApiDocGeneratorServiceProvider::class, + \Dingo\Api\Provider\LaravelServiceProvider::class, ]; } @@ -22,9 +22,11 @@ public function setUp() parent::setUp(); $this->generator = new DingoGenerator(); + config(['apidoc.router' => 'dingo']); + } - public function createRoute(string $httpMethod, string $path, string $controllerMethod) + public function createRoute(string $httpMethod, string $path, string $controllerMethod, $register = false) { $route = null; /** @var Router $api */ diff --git a/tests/Unit/GeneratorTestCase.php b/tests/Unit/GeneratorTestCase.php index ff7b13f3..d3551dd6 100644 --- a/tests/Unit/GeneratorTestCase.php +++ b/tests/Unit/GeneratorTestCase.php @@ -26,8 +26,6 @@ protected function getPackageProviders($app) public function setUp() { parent::setUp(); - - $this->generator = new LaravelGenerator(); } /** @test */ @@ -140,13 +138,13 @@ public function can_parse_response_tag() $this->assertTrue(is_array($parsed)); $this->assertArrayHasKey('showresponse', $parsed); $this->assertTrue($parsed['showresponse']); - $this->assertJsonStringEqualsJsonString(json_encode([ + $this->assertArraySubset([ 'id' => 4, 'name' => 'banana', 'color' => 'red', 'weight' => '1 kg', 'delicious' => true, - ]), $parsed['response']); + ], json_decode($parsed['response'], true)); } /** @test */ @@ -207,5 +205,73 @@ public function can_parse_transformer_collection_tag_with_model() ); } - abstract public function createRoute(string $httpMethod, string $path, string $controllerMethod); + /** @test */ + public function can_call_route_and_generate_response() + { + $route = $this->createRoute('PUT', '/shouldFetchRouteResponse', 'shouldFetchRouteResponse', true); + + $rules = [ + 'response_calls' => [ + 'methods' => ['*'], + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ], + ], + ]; + $parsed = $this->generator->processRoute($route, $rules); + + $this->assertTrue(is_array($parsed)); + $this->assertArrayHasKey('showresponse', $parsed); + $this->assertTrue($parsed['showresponse']); + $this->assertArraySubset([ + 'id' => 4, + 'name' => 'banana', + 'color' => 'red', + 'weight' => '1 kg', + 'delicious' => true, + ], json_decode($parsed['response'], true)); + } + + /** @test */ + public function uses_configured_settings_when_calling_route() + { + $route = $this->createRoute('PUT', '/echo/{id}', 'shouldFetchRouteResponseWithEchoedSettings', true); + + $rules = [ + 'response_calls' => [ + 'methods' => ['*'], + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'header' => 'value', + ], + 'bindings' => [ + '{id}' => 3, + ], + 'env' => [ + 'APP_ENV' => 'documentation', + ], + 'query' => [ + 'queryParam' => 'queryValue', + ], + 'body' => [ + 'bodyParam' => 'bodyValue', + ] + ], + ]; + $parsed = $this->generator->processRoute($route, $rules); + + $this->assertTrue(is_array($parsed)); + $this->assertArrayHasKey('showresponse', $parsed); + $this->assertTrue($parsed['showresponse']); + $response = json_decode($parsed['response'], true); + $this->assertEquals(3, $response['{id}']); + $this->assertEquals('documentation', $response['APP_ENV']); + $this->assertEquals('queryValue', $response['queryParam']); + $this->assertEquals('bodyValue', $response['bodyParam']); + $this->assertEquals('value', $response['header']); + } + + abstract public function createRoute(string $httpMethod, string $path, string $controllerMethod, $register = false); } diff --git a/tests/Unit/LaravelGeneratorTest.php b/tests/Unit/LaravelGeneratorTest.php index 640ca772..0b52099d 100644 --- a/tests/Unit/LaravelGeneratorTest.php +++ b/tests/Unit/LaravelGeneratorTest.php @@ -3,6 +3,7 @@ namespace Mpociot\ApiDoc\Tests\Unit; use Illuminate\Routing\Route; +use Illuminate\Support\Facades\Route as RouteFacade; use Mpociot\ApiDoc\Generators\LaravelGenerator; use Mpociot\ApiDoc\Tests\Fixtures\TestController; use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider; @@ -23,8 +24,12 @@ public function setUp() $this->generator = new LaravelGenerator(); } - public function createRoute(string $httpMethod, string $path, string $controllerMethod) + public function createRoute(string $httpMethod, string $path, string $controllerMethod, $register = false) { - return new Route([$httpMethod], $path, ['uses' => TestController::class."@$controllerMethod"]); + if ($register) { + return RouteFacade::{$httpMethod}($path, TestController::class . "@$controllerMethod"); + } else { + return new Route([$httpMethod], $path, ['uses' => TestController::class . "@$controllerMethod"]); + } } }