Browse Source

Merge branch 'master' of https://github.com/mpociot/laravel-apidoc-generator

Icaro Jerry 6 years ago
parent
commit
920a308485

+ 4 - 0
CHANGELOG.md

@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Removed
 
+## [3.10.0] - Sunday, 23 June 2019
+### Added
+- `--verbose` flag to show exception encountered when making response calls (https://github.com/mpociot/laravel-apidoc-generator/commit/dc987f296e5a3d073f56c67911b2cb61ae47e9dc)
+
 ## [3.9.0] - Saturday, 8 June 2019
 ### Modified
 - Postman collections and URLs in example requests now use the `apidoc.base_url` config variable (https://github.com/mpociot/laravel-apidoc-generator/pull/523)

+ 1 - 4
TODO.md

@@ -1,6 +1,3 @@
-- Add tests for bindings and bindings prefixes
-- Add tests for config overrides
-- Add tests on output (deterministic)
+Major
 - Bring `bindings` outside of `response_calls`
 - Should `routes.*.apply.response_calls.headers` be replaced by `routes.*.apply.headers`?
-- Implement debug flag

+ 3 - 1
composer.json

@@ -22,7 +22,9 @@
         "illuminate/console": "5.5.* || 5.6.* || 5.7.* || 5.8.*",
         "mpociot/documentarian": "^0.2.0",
         "mpociot/reflection-docblock": "^1.0.1",
-        "ramsey/uuid": "^3.8"
+        "ramsey/uuid": "^3.8",
+        "nunomaduro/collision": "^3.0",
+        "league/flysystem": "^1.0"
     },
     "require-dev": {
         "orchestra/testbench": "3.5.* || 3.6.* || 3.7.*",

+ 1 - 0
resources/views/partials/route.blade.php

@@ -18,6 +18,7 @@
 @include("vendor.apidoc.partials.example-requests.$language")
 @endif
 
+
 @endforeach
 
 @if(in_array('GET',$route['methods']) || (isset($route['showresponse']) && $route['showresponse']))

+ 10 - 6
src/Commands/GenerateDocumentation.php

@@ -6,6 +6,7 @@ use ReflectionClass;
 use ReflectionException;
 use Illuminate\Routing\Route;
 use Illuminate\Console\Command;
+use Mpociot\ApiDoc\Tools\Flags;
 use Mpociot\ApiDoc\Tools\Utils;
 use Mpociot\Reflection\DocBlock;
 use Illuminate\Support\Collection;
@@ -54,21 +55,24 @@ class GenerateDocumentation extends Command
      */
     public function handle()
     {
+        // Using a global static variable here, so fuck off if you don't like it
+        // Also, the --verbose option is included with all Artisan commands
+        Flags::$shouldBeVerbose = $this->option('verbose');
+
         $this->docConfig = new DocumentationConfig(config('apidoc'));
 
         try {
             URL::forceRootUrl($this->docConfig->get('base_url'));
-        } catch (\Exception $e) {
+        } catch (\Error $e) {
             echo "Warning: Couldn't force base url as your version of Lumen doesn't have the forceRootUrl method.\n";
             echo "You should probably double check URLs in your generated documentation.\n";
         }
+
         $usingDingoRouter = strtolower($this->docConfig->get('router')) == 'dingo';
         $routes = $this->docConfig->get('routes');
-        if ($usingDingoRouter) {
-            $routes = $this->routeMatcher->getDingoRoutesToBeDocumented($routes);
-        } else {
-            $routes = $this->routeMatcher->getLaravelRoutesToBeDocumented($routes);
-        }
+        $routes = $usingDingoRouter
+            ? $this->routeMatcher->getDingoRoutesToBeDocumented($routes)
+            : $this->routeMatcher->getLaravelRoutesToBeDocumented($routes);
 
         $generator = new Generator($this->docConfig);
         $parsedRoutes = $this->processRoutes($generator, $routes);

+ 1 - 1
src/Postman/CollectionWriter.php

@@ -33,7 +33,7 @@ class CollectionWriter
     {
         try {
             URL::forceRootUrl($this->baseUrl);
-        } catch (\Exception $e) {
+        } catch (\Error $e) {
             echo "Warning: Couldn't force base url as your version of Lumen doesn't have the forceRootUrl method.\n";
             echo "You should probably double check URLs in your generated Postman collection.\n";
         }

+ 8 - 0
src/Tools/Flags.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tools;
+
+class Flags
+{
+    public static $shouldBeVerbose = false;
+}

+ 12 - 3
src/Tools/ResponseStrategies/ResponseCallStrategy.php

@@ -6,7 +6,10 @@ use Dingo\Api\Dispatcher;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 use Illuminate\Routing\Route;
+use Mpociot\ApiDoc\Tools\Flags;
 use Mpociot\ApiDoc\Tools\Utils;
+use Whoops\Exception\Inspector;
+use NunoMaduro\Collision\Handler;
 use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
 
 /**
@@ -36,9 +39,15 @@ class ResponseCallStrategy
         try {
             $response = [$this->makeApiCall($request)];
         } catch (\Exception $e) {
-            echo 'Response call failed for ['.implode(',', $route->methods)."] {$route->uri}";
-            // TODO
-            // echo "Run this again with the --debug flag for details
+            echo 'Exception thrown during response call for ['.implode(',', $route->methods)."] {$route->uri}.\n";
+            if (Flags::$shouldBeVerbose) {
+                $handler = new Handler;
+                $handler->setInspector(new Inspector($e));
+                $handler->setException($e);
+                $handler->handle();
+            } else {
+                echo "Run this again with the --verbose flag to see the exception.\n";
+            }
             $response = null;
         } finally {
             $this->finish();

+ 8 - 0
src/Tools/ResponseStrategies/TransformerTagsStrategy.php

@@ -6,6 +6,7 @@ use ReflectionClass;
 use ReflectionMethod;
 use League\Fractal\Manager;
 use Illuminate\Routing\Route;
+use Mpociot\ApiDoc\Tools\Flags;
 use League\Fractal\Resource\Item;
 use Mpociot\Reflection\DocBlock\Tag;
 use League\Fractal\Resource\Collection;
@@ -108,6 +109,10 @@ class TransformerTagsStrategy
             // try Eloquent model factory
             return factory($type)->make();
         } catch (\Exception $e) {
+            if (Flags::$shouldBeVerbose) {
+                echo "Eloquent model factory failed to instantiate {$type}; trying to fetch from database";
+            }
+
             $instance = new $type;
             if ($instance instanceof \Illuminate\Database\Eloquent\Model) {
                 try {
@@ -118,6 +123,9 @@ class TransformerTagsStrategy
                     }
                 } catch (\Exception $e) {
                     // okay, we'll stick with `new`
+                    if (Flags::$shouldBeVerbose) {
+                        echo "Failed to fetch first {$type} from database; using `new` to instantiate";
+                    }
                 }
             }
         }

+ 10 - 1
src/Tools/Utils.php

@@ -4,6 +4,8 @@ namespace Mpociot\ApiDoc\Tools;
 
 use Illuminate\Support\Str;
 use Illuminate\Routing\Route;
+use League\Flysystem\Filesystem;
+use League\Flysystem\Adapter\Local;
 
 class Utils
 {
@@ -45,7 +47,7 @@ class Utils
      *
      * @return mixed
      */
-    protected static function replaceUrlParameterBindings(string $uri, array $bindings)
+    public static function replaceUrlParameterBindings(string $uri, array $bindings)
     {
         foreach ($bindings as $path => $binding) {
             // So we can support partial bindings like
@@ -63,4 +65,11 @@ class Utils
 
         return $uri;
     }
+
+    public static function deleteDirectoryAndContents($dir)
+    {
+        $adapter = new Local(realpath(__DIR__.'/../../'));
+        $fs = new Filesystem($adapter);
+        $fs->deleteDir($dir);
+    }
 }

+ 14 - 0
tests/Fixtures/TestController.php

@@ -112,6 +112,20 @@ class TestController extends Controller
         ];
     }
 
+    public function echoesConfig()
+    {
+        return [
+            'app.env' => config('app.env'),
+        ];
+    }
+
+    public function echoesUrlPathParameters($param)
+    {
+        return [
+            'param' => $param,
+        ];
+    }
+
     public function shouldFetchRouteResponseWithEchoedSettings($id)
     {
         return [

+ 90 - 80
tests/Fixtures/index.md

@@ -20,10 +20,10 @@ Welcome to the generated API reference.
 
 <!-- END_INFO -->
 
-#general
+#Group A
 <!-- START_264ee15c728df32e7ca6eedce5e42dcb -->
 ## Example title.
- 
+
 This will be the long description.
 It can also be multiple lines long.
 
@@ -31,15 +31,16 @@ It can also be multiple lines long.
 
 ```bash
 curl -X GET -G "http://localhost/api/withDescription" \
-    -H "Accept: application/json" \
     -H "Authorization: customAuthToken" \
-        -H "Custom-Header: NotSoCustom" 
+    -H "Custom-Header: NotSoCustom"
 ```
 
 ```javascript
-const url = new URL("http://localhost/api/users");
+const url = new URL("http://localhost/api/withDescription");
 
 let headers = {
+    "Authorization": "customAuthToken",
+    "Custom-Header": "NotSoCustom",
     "Accept": "application/json",
     "Content-Type": "application/json",
 }
@@ -52,7 +53,8 @@ fetch(url, {
     .then(json => console.log(json));
 ```
 
-> Example response:
+
+> Example response (200):
 
 ```json
 null
@@ -66,35 +68,34 @@ null
 
 <!-- START_9cedd363be06f5512f9e844b100fcc9d -->
 ## api/withResponseTag
- 
 > Example request:
 
 ```bash
 curl -X GET -G "http://localhost/api/withResponseTag" \
-    -H "Accept: application/json" \
     -H "Authorization: customAuthToken" \
-        -H "Custom-Header: NotSoCustom" 
+    -H "Custom-Header: NotSoCustom"
 ```
 
 ```javascript
-var settings = {
-    "async": true,
-    "crossDomain": true,
-    "url": "http://localhost/api/withResponseTag",
-    "method": "GET",
-    "headers": {
-        "accept": "application/json",
-        "Authorization": "customAuthToken",
-        "Custom-Header": "NotSoCustom",
-    }
+const url = new URL("http://localhost/api/withResponseTag");
+
+let headers = {
+    "Authorization": "customAuthToken",
+    "Custom-Header": "NotSoCustom",
+    "Accept": "application/json",
+    "Content-Type": "application/json",
 }
 
-$.ajax(settings).done(function (response) {
-    console.log(response);
-});
+fetch(url, {
+    method: "GET",
+    headers: headers,
+})
+    .then(response => response.json())
+    .then(json => console.log(json));
 ```
 
-> Example response:
+
+> Example response (200):
 
 ```json
 {
@@ -114,49 +115,61 @@ $.ajax(settings).done(function (response) {
 
 <!-- START_a25cb3b490fa579d7d77b386bbb7ec03 -->
 ## api/withBodyParameters
- 
 > Example request:
 
 ```bash
 curl -X GET -G "http://localhost/api/withBodyParameters" \
-    -H "Accept: application/json" \
     -H "Authorization: customAuthToken" \
-        -H "Custom-Header: NotSoCustom"  \
-    -d "user_id"=20 \
-        -d "room_id"=6DZyNcBgezdjdAIs \
-        -d "forever"= \
-        -d "another_one"=2153.4 \
-        -d "yet_another_param"={} \
-        -d "even_more_param"=[] 
+    -H "Custom-Header: NotSoCustom" \
+    -H "Content-Type: application/json" \
+    -d '{"user_id":9,"room_id":"consequatur","forever":false,"another_one":11613.31890586,"yet_another_param":{},"even_more_param":[],"book":{"name":"consequatur","author_id":17,"pages_count":17},"ids":[17],"users":[{"first_name":"John","last_name":"Doe"}]}'
+
 ```
 
 ```javascript
-var settings = {
-    "async": true,
-    "crossDomain": true,
-    "url": "http://localhost/api/withBodyParameters",
-    "method": "GET",
-    "data": {
-        "user_id": 20,
-        "room_id": "6DZyNcBgezdjdAIs",
-        "forever": false,
-        "another_one": 2153.4,
-        "yet_another_param": "{}",
-        "even_more_param": "[]"
+const url = new URL("http://localhost/api/withBodyParameters");
+
+let headers = {
+    "Authorization": "customAuthToken",
+    "Custom-Header": "NotSoCustom",
+    "Content-Type": "application/json",
+    "Accept": "application/json",
+}
+
+let body = {
+    "user_id": 9,
+    "room_id": "consequatur",
+    "forever": false,
+    "another_one": 11613.31890586,
+    "yet_another_param": {},
+    "even_more_param": [],
+    "book": {
+        "name": "consequatur",
+        "author_id": 17,
+        "pages_count": 17
     },
-    "headers": {
-        "accept": "application/json",
-        "Authorization": "customAuthToken",
-        "Custom-Header": "NotSoCustom",
-    }
+    "ids": [
+        17
+    ],
+    "users": [
+        {
+            "first_name": "John",
+            "last_name": "Doe"
+        }
+    ]
 }
 
-$.ajax(settings).done(function (response) {
-    console.log(response);
-});
+fetch(url, {
+    method: "GET",
+    headers: headers,
+    body: body
+})
+    .then(response => response.json())
+    .then(json => console.log(json));
 ```
 
-> Example response:
+
+> Example response (200):
 
 ```json
 null
@@ -165,7 +178,7 @@ null
 ### HTTP Request
 `GET api/withBodyParameters`
 
-#### Parameters
+#### Body Parameters
 
 Parameter | Type | Status | Description
 --------- | ------- | ------- | ------- | -----------
@@ -175,49 +188,46 @@ Parameter | Type | Status | Description
     another_one | number |  optional  | Just need something here.
     yet_another_param | object |  required  | 
     even_more_param | array |  optional  | 
+    book.name | string |  optional  | 
+    book.author_id | integer |  optional  | 
+    book[pages_count] | integer |  optional  | 
+    ids.* | integer |  optional  | 
+    users.*.first_name | string |  optional  | The first name of the user.
+    users.*.last_name | string |  optional  | The last name of the user.
 
 <!-- END_a25cb3b490fa579d7d77b386bbb7ec03 -->
 
 <!-- START_5c08cc4d72b6e5830f6814c64086e197 -->
 ## api/withAuthTag
- <small style="
-  padding: 1px 9px 2px;
-  font-weight: bold;
-  white-space: nowrap;
-  color: #ffffff;
-  -webkit-border-radius: 9px;
-  -moz-border-radius: 9px;
-  border-radius: 9px;
-  background-color: #3a87ad;">Requires authentication</small>
-
+<br><small style="padding: 1px 9px 2px;font-weight: bold;white-space: nowrap;color: #ffffff;-webkit-border-radius: 9px;-moz-border-radius: 9px;border-radius: 9px;background-color: #3a87ad;">Requires authentication</small>
 > Example request:
 
 ```bash
 curl -X GET -G "http://localhost/api/withAuthTag" \
-    -H "Accept: application/json" \
     -H "Authorization: customAuthToken" \
-        -H "Custom-Header: NotSoCustom" 
+    -H "Custom-Header: NotSoCustom"
 ```
 
 ```javascript
-var settings = {
-    "async": true,
-    "crossDomain": true,
-    "url": "http://localhost/api/withAuthTag",
-    "method": "GET",
-    "headers": {
-        "accept": "application/json",
-        "Authorization": "customAuthToken",
-        "Custom-Header": "NotSoCustom",
-    }
+const url = new URL("http://localhost/api/withAuthTag");
+
+let headers = {
+    "Authorization": "customAuthToken",
+    "Custom-Header": "NotSoCustom",
+    "Accept": "application/json",
+    "Content-Type": "application/json",
 }
 
-$.ajax(settings).done(function (response) {
-    console.log(response);
-});
+fetch(url, {
+    method: "GET",
+    headers: headers,
+})
+    .then(response => response.json())
+    .then(json => console.log(json));
 ```
 
-> Example response:
+
+> Example response (200):
 
 ```json
 null

+ 6 - 63
tests/GenerateDocumentationTest.php

@@ -4,12 +4,10 @@ namespace Mpociot\ApiDoc\Tests;
 
 use ReflectionException;
 use Illuminate\Support\Str;
-use RecursiveIteratorIterator;
-use RecursiveDirectoryIterator;
+use Mpociot\ApiDoc\Tools\Utils;
 use Orchestra\Testbench\TestCase;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Facades\Config;
-use Illuminate\Contracts\Console\Kernel;
 use Mpociot\ApiDoc\Tests\Fixtures\TestController;
 use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider;
 use Illuminate\Support\Facades\Route as RouteFacade;
@@ -19,6 +17,8 @@ use Mpociot\ApiDoc\Tests\Fixtures\TestPartialResourceController;
 
 class GenerateDocumentationTest extends TestCase
 {
+    use TestHelpers;
+
     /**
      * Setup the test environment.
      */
@@ -29,20 +29,7 @@ class GenerateDocumentationTest extends TestCase
 
     public function tearDown()
     {
-        // delete the generated docs - compatible cross-platform
-        $dir = __DIR__.'/../public/docs';
-        if (is_dir($dir)) {
-            $files = new RecursiveIteratorIterator(
-                new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
-                RecursiveIteratorIterator::CHILD_FIRST
-            );
-
-            foreach ($files as $fileinfo) {
-                $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
-                $todo($fileinfo->getRealPath());
-            }
-            rmdir($dir);
-        }
+        Utils::deleteDirectoryAndContents('/public/docs');
     }
 
     /**
@@ -206,6 +193,8 @@ class GenerateDocumentationTest extends TestCase
         RouteFacade::get('/api/withBodyParameters', TestController::class.'@withBodyParameters');
         RouteFacade::get('/api/withAuthTag', TestController::class.'@withAuthenticatedTag');
 
+        // We want to have the same values for params each time
+        config(['apidoc.faker_seed' => 1234]);
         config(['apidoc.routes.0.match.prefixes' => ['api/*']]);
         config([
             'apidoc.routes.0.apply.headers' => [
@@ -219,7 +208,6 @@ class GenerateDocumentationTest extends TestCase
         $compareMarkdown = __DIR__.'/../public/docs/source/.compare.md';
         $fixtureMarkdown = __DIR__.'/Fixtures/index.md';
 
-        $this->markTestSkipped('Test is non-deterministic since example values for body parameters are random.');
         $this->assertFilesHaveSameContent($fixtureMarkdown, $generatedMarkdown);
         $this->assertFilesHaveSameContent($fixtureMarkdown, $compareMarkdown);
     }
@@ -365,49 +353,4 @@ class GenerateDocumentationTest extends TestCase
         $this->assertContains('Group A', $generatedMarkdown);
         $this->assertContains('Group B', $generatedMarkdown);
     }
-
-    /**
-     * @param string $command
-     * @param array $parameters
-     *
-     * @return mixed
-     */
-    public function artisan($command, $parameters = [])
-    {
-        $this->app[Kernel::class]->call($command, $parameters);
-
-        return $this->app[Kernel::class]->output();
-    }
-
-    private function assertFilesHaveSameContent($pathToExpected, $pathToActual)
-    {
-        $actual = $this->getFileContents($pathToActual);
-        $expected = $this->getFileContents($pathToExpected);
-        $this->assertSame($expected, $actual);
-    }
-
-    /**
-     * Get the contents of a file in a cross-platform-compatible way.
-     *
-     * @param $path
-     *
-     * @return string
-     */
-    private function getFileContents($path)
-    {
-        return str_replace("\r\n", "\n", file_get_contents($path));
-    }
-
-    /**
-     * Assert that a string contains another string, ignoring all whitespace.
-     *
-     * @param $needle
-     * @param $haystack
-     */
-    private function assertContainsIgnoringWhitespace($needle, $haystack)
-    {
-        $haystack = preg_replace('/\s/', '', $haystack);
-        $needle = preg_replace('/\s/', '', $needle);
-        $this->assertContains($needle, $haystack);
-    }
 }

+ 53 - 0
tests/TestHelpers.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tests;
+
+use Illuminate\Contracts\Console\Kernel;
+
+trait TestHelpers
+{
+    /**
+     * @param string $command
+     * @param array $parameters
+     *
+     * @return mixed
+     */
+    public function artisan($command, $parameters = [])
+    {
+        $this->app[Kernel::class]->call($command, $parameters);
+
+        return $this->app[Kernel::class]->output();
+    }
+
+    private function assertFilesHaveSameContent($pathToExpected, $pathToActual)
+    {
+        $actual = $this->getFileContents($pathToActual);
+        $expected = $this->getFileContents($pathToExpected);
+        $this->assertSame($expected, $actual);
+    }
+
+    /**
+     * Get the contents of a file in a cross-platform-compatible way.
+     *
+     * @param $path
+     *
+     * @return string
+     */
+    private function getFileContents($path)
+    {
+        return str_replace("\r\n", "\n", file_get_contents($path));
+    }
+
+    /**
+     * Assert that a string contains another string, ignoring all whitespace.
+     *
+     * @param $needle
+     * @param $haystack
+     */
+    private function assertContainsIgnoringWhitespace($needle, $haystack)
+    {
+        $haystack = preg_replace('/\s/', '', $haystack);
+        $needle = preg_replace('/\s/', '', $needle);
+        $this->assertContains($needle, $haystack);
+    }
+}

+ 112 - 18
tests/Unit/GeneratorTestCase.php

@@ -348,20 +348,6 @@ abstract class GeneratorTestCase extends TestCase
         );
     }
 
-    public function dataResources()
-    {
-        return [
-            [
-                null,
-                '{"data":{"id":1,"description":"Welcome on this test versions","name":"TestName"}}',
-            ],
-            [
-                'League\Fractal\Serializer\JsonApiSerializer',
-                '{"data":{"type":null,"id":"1","attributes":{"description":"Welcome on this test versions","name":"TestName"}}}',
-            ],
-        ];
-    }
-
     /** @test */
     public function can_parse_transformer_tag_with_model()
     {
@@ -449,6 +435,104 @@ abstract class GeneratorTestCase extends TestCase
         ], json_decode($response['content'], true));
     }
 
+    /** @test */
+    public function can_override_config_during_response_call()
+    {
+        $route = $this->createRoute('POST', '/echoesConfig', 'echoesConfig', true);
+
+        $rules = [
+            'response_calls' => [
+                'methods' => ['*'],
+            ],
+        ];
+        $parsed = $this->generator->processRoute($route, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $originalValue = $response['app.env'];
+
+        $now = time();
+        $rules = [
+            'response_calls' => [
+                'methods' => ['*'],
+                'config' => [
+                    'app.env' => $now,
+                ],
+            ],
+        ];
+        $parsed = $this->generator->processRoute($route, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $newValue = $response['app.env'];
+        $this->assertEquals($now, $newValue);
+        $this->assertNotEquals($originalValue, $newValue);
+    }
+
+    /** @test */
+    public function can_override_url_path_parameters_with_bindings()
+    {
+        $route = $this->createRoute('POST', '/echoesUrlPathParameters/{param}', 'echoesUrlPathParameters', true);
+
+        $rand = rand();
+        $rules = [
+            'response_calls' => [
+                'methods' => ['*'],
+                'bindings' => [
+                    '{param}' => $rand,
+                ],
+            ],
+        ];
+        $parsed = $this->generator->processRoute($route, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $param = $response['param'];
+        $this->assertEquals($rand, $param);
+    }
+
+    /** @test */
+    public function replaces_optional_url_path_parameters_with_bindings()
+    {
+        $route = $this->createRoute('POST', '/echoesUrlPathParameters/{param?}', 'echoesUrlPathParameters', true);
+
+        $rand = rand();
+        $rules = [
+            'response_calls' => [
+                'methods' => ['*'],
+                'bindings' => [
+                    '{param?}' => $rand,
+                ],
+            ],
+        ];
+        $parsed = $this->generator->processRoute($route, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $param = $response['param'];
+        $this->assertEquals($rand, $param);
+    }
+
+    /** @test */
+    public function uses_correct_bindings_by_prefix()
+    {
+        $route1 = $this->createRoute('POST', '/echoesUrlPathParameters/first/{param}', 'echoesUrlPathParameters', true);
+        $route2 = $this->createRoute('POST', '/echoesUrlPathParameters/second/{param}', 'echoesUrlPathParameters', true);
+
+        $rand1 = rand();
+        $rand2 = rand();
+        $rules = [
+            'response_calls' => [
+                'methods' => ['*'],
+                'bindings' => [
+                    'first/{param}' => $rand1,
+                    'second/{param}' => $rand2,
+                ],
+            ],
+        ];
+        $parsed = $this->generator->processRoute($route1, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $param = $response['param'];
+        $this->assertEquals($rand1, $param);
+
+        $parsed = $this->generator->processRoute($route2, $rules);
+        $response = json_decode(array_first($parsed['response'])['content'], true);
+        $param = $response['param'];
+        $this->assertEquals($rand2, $param);
+    }
+
     /** @test */
     public function can_parse_response_file_tag()
     {
@@ -574,9 +658,6 @@ abstract class GeneratorTestCase extends TestCase
                 'bindings' => [
                     '{id}' => 3,
                 ],
-                'env' => [
-                    'APP_ENV' => 'documentation',
-                ],
                 'query' => [
                     'queryParam' => 'queryValue',
                 ],
@@ -595,7 +676,6 @@ abstract class GeneratorTestCase extends TestCase
         $this->assertEquals(200, $response['status']);
         $responseContent = json_decode($response['content'], true);
         $this->assertEquals(3, $responseContent['{id}']);
-        $this->assertEquals('documentation', $responseContent['APP_ENV']);
         $this->assertEquals('queryValue', $responseContent['queryParam']);
         $this->assertEquals('bodyValue', $responseContent['bodyParam']);
         $this->assertEquals('value', $responseContent['header']);
@@ -615,4 +695,18 @@ abstract class GeneratorTestCase extends TestCase
     abstract public function createRoute(string $httpMethod, string $path, string $controllerMethod, $register = false);
 
     abstract public function createRouteUsesArray(string $httpMethod, string $path, string $controllerMethod, $register = false);
+
+    public function dataResources()
+    {
+        return [
+            [
+                null,
+                '{"data":{"id":1,"description":"Welcome on this test versions","name":"TestName"}}',
+            ],
+            [
+                'League\Fractal\Serializer\JsonApiSerializer',
+                '{"data":{"type":null,"id":"1","attributes":{"description":"Welcome on this test versions","name":"TestName"}}}',
+            ],
+        ];
+    }
 }