Browse Source

Merge remote-tracking branch 'origin/master'

shalvah 4 năm trước cách đây
mục cha
commit
8122820840

+ 21 - 4
config/scribe.php

@@ -116,12 +116,9 @@ INTRO
      * Generate a Postman collection in addition to HTML docs.
      * For 'static' docs, the collection will be generated to public/docs/collection.json.
      * For 'laravel' docs, it will be generated to storage/app/scribe/collection.json.
-     * Setting `laravel.autoload` to true (above) will add routes for both the HTML and the Postman collection.
+     * Setting `laravel.add_routes` to true (above) will also add a route for the collection.
      */
     'postman' => [
-        /*
-         * Specify whether the Postman collection should be generated.
-         */
         'enabled' => true,
 
         /*
@@ -140,10 +137,30 @@ INTRO
          * https://schema.getpostman.com/json/collection/v2.0.0/docs/index.html
          */
         'auth' => null,
+
+        /*
+         * Manually override some generated content in the spec. Dot notation is supported.
+         */
+        'overrides' => [
+            // 'info.version' => '2.0.0',
+        ],
     ],
 
+    /*
+     * Generate an OpenAPI spec file in addition to docs webpage.
+     * For 'static' docs, the collection will be generated to public/docs/openapi.yaml.
+     * For 'laravel' docs, it will be generated to storage/app/scribe/openapi.yaml.
+     * Setting `laravel.add_routes` to true (above) will also add a route for the spec.
+     */
     'openapi' => [
         'enabled' => true,
+
+        /*
+         * Manually override some generated content in the spec. Dot notation is supported.
+         */
+        'overrides' => [
+            // 'info.version' => '2.0.0',
+        ],
     ],
 
     /*

+ 5 - 1
docs/config.md

@@ -61,6 +61,8 @@ For `static` output, the collection will be created in `public/docs/collection.j
 
 - `enabled`: Whether or not to generate a Postman API collection. Default: `true`
 
+- `overrides`: List of fields to apply to the generated collection. Dot notation is supported. For instance, if you'd like to override the version (in the `info` object, you can set `overrides` to `['info.version' => '2.0.0']`.
+
 - `description`: The description for the generated Postman collection.
 
 - `base_url`: The base URL to be used in the Postman collection. If this is null, Scribe will use the value of [`base_url`](#base_url) set above.
@@ -68,12 +70,14 @@ For `static` output, the collection will be created in `public/docs/collection.j
 - `auth`: The "Auth" section that should appear in the postman collection. See the [Postman schema docs](https://schema.getpostman.com/json/collection/v2.0.0/docs/index.html) for more information.
 
 ### `openapi`
-Scribe can also generate an OpenAPI (Swagger) spec for your routes. This section is where you can configure or disable that.
+Scribe can also generate an OpenAPI (Swagger) spec for your routes (disabled by default). This section is where you can configure or enable that.
 
 For `static` output, the spec will be created in `public/docs/openapi.yaml`. For `laravel` output, the spec will be generated to `storage/app/scribe/openapi.yaml`. Setting `laravel.add_routes` to `true` will add a `/docs.openapi` endpoint to fetch it.
 
 - `enabled`: Whether or not to generate an OpenAPI spec. Default: `false`
 
+- `overrides`: List of fields to apply to the generated spec. Dot notation is supported. For instance, if you'd like to override the version (in the `info` object, you can set `overrides` to `['info.version' => '2.0.0']`.
+
 ## Extraction settings
 ### `router`
 The router to use when processing your routes. Can be `laravel` or `dingo`. Defaults to `laravel`.

+ 4 - 0
docs/generating-documentation.md

@@ -23,6 +23,8 @@ You can configure Postman collection generation in the `postman` section of your
 
 - To turn it off, set the `postman.enabled` config option to false.
 
+- To override some fields in the generated collection, set the `openapi.overrides` config option to your changes. You can use dot notation to update specific nested fields. For instance, `['info.version' => '2.0.0']` will override the 'version` key in the 'info` object whenever generating.
+
 - The base URL used in the Postman collection is the value of `config('app.url')` by default. To change this, set the value of the `postman.base_url` key.
 
 - The name of the Postman collection will be derived from `config('app.name')` by default. To change this, set the value of the `title` key (not in the `postman` array). This will also set the title for your docs HTML page.
@@ -34,6 +36,8 @@ Scribe can also generate an OpenAPI spec file. This is disabled by default. You
 
 - To enable it, set the `openapi.enabled` config option to `true`.
 
+- To override some fields in the generated spec, set the `openapi.overrides` config option to your changes. You can use dot notation to update specific nested fields. For instance, `['info.version' => '2.0.0']` will override the 'version` key in the 'info` object whenever generating.
+
 You can view the generated spec by visiting `public/docs/openapi.yaml` for `static` type, and `<your-app>/docs.openapi` for `laravel` type. This link will also be added to the sidebar of your docs.
 
 ## Customising the environment with `--env`

+ 10 - 11
docs/index.md

@@ -12,6 +12,16 @@ Generate API documentation for humans from your Laravel/Lumen/[Dingo](https://gi
 ```eval_rst
 .. Tip:: Looking to document your Node.js APIs? Check out `Scribe for JS <https://github.com/knuckleswtf/scribe-js>`_.
 ```
+## Features
+- Pretty HTML documentation page, with included code samples and friendly text
+- Markdown source files that can be edited to modify docs
+- Extracts body parameters information from FormRequests
+- Safely calls API endpoints to generate sample responses, with authentication and other custom configuration supported
+- Supports generating responses from Transformers or Eloquent API Resources
+- Supports Postman collection and OpenAPI (Swagger) spec generation
+- Included UI components for additional styling
+- Easily customisable with custom views
+- Easily extensible with custom strategies
 
 ## Contents
 ```eval_rst
@@ -31,17 +41,6 @@ Generate API documentation for humans from your Laravel/Lumen/[Dingo](https://gi
    contributing
 ```
 
-## Features
-- Pretty HTML documentation page, with included code samples and friendly text
-- Markdown source files that can be edited to modify docs
-- Extracts body parameters information from FormRequests
-- Safely calls API endpoints to generate sample responses, with authentication and other custom configuration supported
-- Supports generating responses from Transformers or Eloquent API Resources
-- Supports Postman collection and OpenAPI (Swagger) spec generation
-- Included UI components for additional styling
-- Easily customisable with custom views
-- Easily extensible with custom strategies
-
 ## Installation
 PHP 7.2.5 and Laravel/Lumen 5.8 or higher are required.
 

+ 1 - 1
src/Extracting/Strategies/Responses/ResponseCalls.php

@@ -122,7 +122,7 @@ class ResponseCalls extends Strategy
         $method = array_shift($routeMethods);
         $cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];
 
-        // Note that we initialise the request with the bodyPatams here
+        // Note that we initialise the request with the bodyParams here
         // and later still add them to the ParameterBag (`addBodyParameters`)
         // The first is so the body params get added to the request content
         // (where Laravel reads body from)

+ 2 - 2
src/Writing/PostmanCollectionWriter.php

@@ -43,7 +43,7 @@ class PostmanCollectionWriter
         $this->auth = config('scribe.postman.auth');
     }
 
-    public function makePostmanCollection()
+    public function generatePostmanCollection()
     {
         $collection = [
             'variables' => [],
@@ -66,7 +66,7 @@ class PostmanCollectionWriter
             $collection['auth'] = $this->auth;
         }
 
-        return json_encode($collection, JSON_PRETTY_PRINT);
+        return $collection;
     }
 
     protected function generateEndpointItem($route): array

+ 16 - 2
src/Writing/Writer.php

@@ -218,7 +218,14 @@ class Writer
             ['routeGroups' => $routes, 'baseUrl' => $this->postmanBaseUrl]
         );
 
-        return $writer->makePostmanCollection();
+        $collection = $writer->generatePostmanCollection();
+        $overrides = $this->config->get('postman.overrides');
+        if (count($overrides)) {
+            foreach ($overrides as $key => $value) {
+                data_set($collection, $key, $value);
+            }
+        }
+        return json_encode($collection, JSON_PRETTY_PRINT);
     }
 
     public function generateOpenAPISpec(Collection $groupedEndpoints)
@@ -229,7 +236,14 @@ class Writer
             ['config' => $this->config]
         );
 
-        return Yaml::dump($writer->generateSpecContent($groupedEndpoints), 10, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP);
+        $spec = $writer->generateSpecContent($groupedEndpoints);
+        $overrides = $this->config->get('openapi.overrides');
+        if (count($overrides)) {
+            foreach ($overrides as $key => $value) {
+                data_set($spec, $key, $value);
+            }
+        }
+        return Yaml::dump($spec, 10, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP);
     }
 
     protected function performFinalTasksForLaravelType(): void

+ 185 - 0
tests/Fixtures/collection-overridden.json

@@ -0,0 +1,185 @@
+{
+    "variables": [],
+    "info": {
+        "name": "Custom API",
+        "_postman_id": "",
+        "description": "",
+        "schema": "https:\/\/schema.getpostman.com\/json\/collection\/v2.0.0\/collection.json",
+        "version": "3.9.9"
+    },
+    "item": [
+        {
+            "name": "Group A",
+            "description": "",
+            "item": [
+                {
+                    "name": "Example title.",
+                    "request": {
+                        "url": {
+                            "protocol": "http",
+                            "host": "localhost",
+                            "path": "api\/withDescription",
+                            "query": [],
+                            "raw": "http:\/\/localhost\/api\/withDescription"
+                        },
+                        "method": "GET",
+                        "header": [
+                            {
+                                "key": "Content-Type",
+                                "value": "application\/json"
+                            },
+                            {
+                                "key": "Accept",
+                                "value": "application\/json"
+                            }
+                        ],
+                        "body": {
+                            "mode": "raw",
+                            "raw": "[]",
+                            "options": {
+                                "raw": {
+                                    "language": "json"
+                                }
+                            }
+                        },
+                        "description": "This will be the long description.\nIt can also be multiple lines long.",
+                        "response": []
+                    }
+                },
+                {
+                    "name": "api\/withResponseTag",
+                    "request": {
+                        "url": {
+                            "protocol": "http",
+                            "host": "localhost",
+                            "path": "api\/withResponseTag",
+                            "query": [],
+                            "raw": "http:\/\/localhost\/api\/withResponseTag"
+                        },
+                        "method": "GET",
+                        "header": [
+                            {
+                                "key": "Content-Type",
+                                "value": "application\/json"
+                            },
+                            {
+                                "key": "Accept",
+                                "value": "application\/json"
+                            }
+                        ],
+                        "body": {
+                            "mode": "raw",
+                            "raw": "[]",
+                            "options": {
+                                "raw": {
+                                    "language": "json"
+                                }
+                            }
+                        },
+                        "description": "",
+                        "response": []
+                    }
+                },
+                {
+                    "name": "Endpoint with body parameters.",
+                    "request": {
+                        "url": {
+                            "protocol": "http",
+                            "host": "localhost",
+                            "path": "api\/withBodyParameters",
+                            "query": [],
+                            "raw": "http:\/\/localhost\/api\/withBodyParameters"
+                        },
+                        "method": "POST",
+                        "header": [
+                            {
+                                "key": "Content-Type",
+                                "value": "application\/json"
+                            },
+                            {
+                                "key": "Accept",
+                                "value": "application\/json"
+                            }
+                        ],
+                        "body": {
+                            "mode": "raw",
+                            "raw": "{\n    \"user_id\": 9,\n    \"room_id\": \"consequatur\",\n    \"forever\": false,\n    \"another_one\": 11613.31890586,\n    \"yet_another_param\": {\n        \"name\": \"consequatur\"\n    },\n    \"even_more_param\": [\n        11613.31890586\n    ],\n    \"book\": {\n        \"name\": \"consequatur\",\n        \"author_id\": 17,\n        \"pages_count\": 17\n    },\n    \"ids\": [\n        17\n    ],\n    \"users\": [\n        {\n            \"first_name\": \"John\",\n            \"last_name\": \"Doe\"\n        }\n    ]\n}",
+                            "options": {
+                                "raw": {
+                                    "language": "json"
+                                }
+                            }
+                        },
+                        "description": "",
+                        "response": []
+                    }
+                },
+                {
+                    "name": "api\/withQueryParameters",
+                    "request": {
+                        "url": {
+                            "protocol": "http",
+                            "host": "localhost",
+                            "path": "api\/withQueryParameters",
+                            "query": [
+                                {
+                                    "key": "location_id",
+                                    "value": "consequatur",
+                                    "description": "The id of the location.",
+                                    "disabled": false
+                                },
+                                {
+                                    "key": "user_id",
+                                    "value": "me",
+                                    "description": "The id of the user.",
+                                    "disabled": false
+                                },
+                                {
+                                    "key": "page",
+                                    "value": "4",
+                                    "description": "The page number.",
+                                    "disabled": false
+                                },
+                                {
+                                    "key": "filters",
+                                    "value": "consequatur",
+                                    "description": "The filters.",
+                                    "disabled": false
+                                },
+                                {
+                                    "key": "url_encoded",
+                                    "value": "%2B+%5B%5D%26%3D",
+                                    "description": "Used for testing that URL parameters will be URL-encoded where needed.",
+                                    "disabled": false
+                                }
+                            ],
+                            "raw": "http:\/\/localhost\/api\/withQueryParameters?location_id=consequatur&user_id=me&page=4&filters=consequatur&url_encoded=%2B+%5B%5D%26%3D"
+                        },
+                        "method": "GET",
+                        "header": [
+                            {
+                                "key": "Content-Type",
+                                "value": "application\/json"
+                            },
+                            {
+                                "key": "Accept",
+                                "value": "application\/json"
+                            }
+                        ],
+                        "body": {
+                            "mode": "raw",
+                            "raw": "[]",
+                            "options": {
+                                "raw": {
+                                    "language": "json"
+                                }
+                            }
+                        },
+                        "description": "",
+                        "response": []
+                    }
+                }
+            ]
+        }
+    ]
+}

+ 229 - 0
tests/Fixtures/openapi-overridden.yaml

@@ -0,0 +1,229 @@
+openapi: 3.0.3
+info:
+    title: null
+    description: ''
+    version: 3.9.9
+servers:
+    -
+        url: 'http://okay.dev'
+paths:
+    /api/withDescription:
+        get:
+            summary: 'Example title.'
+            description: "This will be the long description.\nIt can also be multiple lines long."
+            parameters:
+                -
+                    in: header
+                    name: Content-Type
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+                -
+                    in: header
+                    name: Accept
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+            responses: {  }
+            tags:
+                - 'Group A'
+            security: []
+    /api/withResponseTag:
+        get:
+            summary: ''
+            description: ''
+            parameters:
+                -
+                    in: header
+                    name: Content-Type
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+                -
+                    in: header
+                    name: Accept
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+            responses:
+                200:
+                    description: '200'
+                    content:
+                        application/json:
+                            schema:
+                                type: object
+                                example:
+                                    id: 4
+                                    name: banana
+                                    color: red
+                                    weight: '1 kg'
+                                    delicious: true
+                                    responseTag: true
+                                properties:
+                                    id: { type: integer, example: 4 }
+                                    name: { type: string, example: banana }
+                                    color: { type: string, example: red }
+                                    weight: { type: string, example: '1 kg' }
+                                    delicious: { type: boolean, example: true }
+                                    responseTag: { type: boolean, example: true }
+            tags:
+                - 'Group A'
+            security: []
+    /api/withBodyParameters:
+        post:
+            summary: 'Endpoint with body parameters.'
+            description: ''
+            parameters:
+                -
+                    in: header
+                    name: Content-Type
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+                -
+                    in: header
+                    name: Accept
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+            responses: {  }
+            tags:
+                - 'Group A'
+            requestBody:
+                required: true
+                content:
+                    application/json:
+                        schema:
+                            type: object
+                            properties:
+                                user_id:
+                                    type: integer
+                                    description: 'The id of the user.'
+                                    example: 9
+                                room_id:
+                                    type: string
+                                    description: 'The id of the room.'
+                                    example: consequatur
+                                forever:
+                                    type: boolean
+                                    description: 'Whether to ban the user forever.'
+                                    example: false
+                                another_one:
+                                    type: number
+                                    description: 'Just need something here.'
+                                    example: 11613.31890586
+                                yet_another_param:
+                                    type: object
+                                    description: 'Some object params.'
+                                    example: {  }
+                                yet_another_param.name:
+                                    type: string
+                                    description: 'Subkey in the object param.'
+                                    example: consequatur
+                                even_more_param:
+                                    type: array
+                                    description: 'Some array params.'
+                                    example: []
+                                    items: { type: object }
+                                'even_more_param.*':
+                                    type: number
+                                    description: 'Subkey in the array param.'
+                                    example: 11613.31890586
+                                book.name:
+                                    type: string
+                                    description: ''
+                                    example: consequatur
+                                book.author_id:
+                                    type: integer
+                                    description: ''
+                                    example: 17
+                                'book[pages_count]':
+                                    type: integer
+                                    description: ''
+                                    example: 17
+                                'ids.*':
+                                    type: integer
+                                    description: ''
+                                    example: 17
+                                'users.*.first_name':
+                                    type: string
+                                    description: 'The first name of the user.'
+                                    example: John
+                                'users.*.last_name':
+                                    type: string
+                                    description: 'The last name of the user.'
+                                    example: Doe
+                            required:
+                                - user_id
+                                - yet_another_param
+                                - yet_another_param.name
+            security: []
+    /api/withQueryParameters:
+        get:
+            summary: ''
+            description: ''
+            parameters:
+                -
+                    in: query
+                    name: location_id
+                    description: 'The id of the location.'
+                    example: consequatur
+                    required: true
+                    schema:
+                        type: string
+                -
+                    in: query
+                    name: user_id
+                    description: 'The id of the user.'
+                    example: me
+                    required: true
+                    schema:
+                        type: string
+                -
+                    in: query
+                    name: page
+                    description: 'The page number.'
+                    example: '4'
+                    required: true
+                    schema:
+                        type: string
+                -
+                    in: query
+                    name: 'filters.*'
+                    description: 'The filters.'
+                    example: consequatur
+                    required: false
+                    schema:
+                        type: string
+                -
+                    in: query
+                    name: url_encoded
+                    description: 'Used for testing that URL parameters will be URL-encoded where needed.'
+                    example: '+ []&='
+                    required: false
+                    schema:
+                        type: string
+                -
+                    in: header
+                    name: Content-Type
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+                -
+                    in: header
+                    name: Accept
+                    description: ''
+                    example: application/json
+                    schema:
+                        type: string
+            responses: {  }
+            tags:
+                - 'Group A'
+            security: []

+ 47 - 0
tests/GenerateDocumentationTest.php

@@ -260,6 +260,30 @@ class GenerateDocumentationTest extends TestCase
         $this->assertEquals($fixtureCollection, $generatedCollection);
     }
 
+    /** @test */
+    public function can_override_fields_in_generated_postman_collection_file()
+    {
+        RouteFacade::get('/api/withDescription', [TestController::class, 'withEndpointDescription']);
+        RouteFacade::get('/api/withResponseTag', TestController::class . '@withResponseTag');
+        RouteFacade::post('/api/withBodyParameters', TestController::class . '@withBodyParameters');
+        RouteFacade::get('/api/withQueryParameters', TestController::class . '@withQueryParameters');
+
+        config(['scribe.faker_seed' => 1234]);
+        config(['scribe.postman.enabled' => true]);
+        config(['scribe.postman.overrides' => [
+            'info.version' => '3.9.9',
+            'info.name' => 'Custom API',
+        ]]);
+        config(['scribe.routes.0.match.prefixes' => ['api/*']]);
+
+        $this->artisan('scribe:generate');
+
+        $generatedCollection = json_decode(file_get_contents(__DIR__ . '/../public/docs/collection.json'), true);
+        $generatedCollection['info']['_postman_id'] = '';
+        $fixtureCollection = json_decode(file_get_contents(__DIR__ . '/Fixtures/collection-overridden.json'), true);
+        $this->assertEquals($fixtureCollection, $generatedCollection);
+    }
+
     /** @test */
     public function generated_openapi_spec_file_is_correct()
     {
@@ -293,6 +317,29 @@ class GenerateDocumentationTest extends TestCase
         $this->assertEquals($fixtureCollection, $generatedCollection);
     }
 
+    /** @test */
+    public function can_override_fields_in_generated_openapi_spec_file()
+    {
+        RouteFacade::get('/api/withDescription', [TestController::class, 'withEndpointDescription']);
+        RouteFacade::get('/api/withResponseTag', TestController::class . '@withResponseTag');
+        RouteFacade::post('/api/withBodyParameters', TestController::class . '@withBodyParameters');
+        RouteFacade::get('/api/withQueryParameters', TestController::class . '@withQueryParameters');
+
+        config(['scribe.faker_seed' => 1234]);
+        config(['scribe.openapi.enabled' => true]);
+        config(['scribe.openapi.overrides' => [
+            'info.version' => '3.9.9',
+            'servers.0.url' => 'http://okay.dev',
+        ]]);
+        config(['scribe.routes.0.match.prefixes' => ['api/*']]);
+
+        $this->artisan('scribe:generate');
+
+        $generatedCollection = json_decode(file_get_contents(__DIR__ . '/../public/docs/openapi.yaml'), true);
+        $fixtureCollection = json_decode(file_get_contents(__DIR__ . '/Fixtures/openapi-overridden.yaml'), true);
+        $this->assertEquals($fixtureCollection, $generatedCollection);
+    }
+
     /** @test */
     public function generated_postman_collection_domain_is_correct()
     {

+ 37 - 46
tests/Unit/PostmanCollectionWriterTest.php

@@ -9,14 +9,18 @@ use Orchestra\Testbench\TestCase;
 
 class PostmanCollectionWriterTest extends TestCase
 {
-    public function testNameIsPresentInCollection()
+    public function testCorrectStructureIsFollowed()
     {
         \Config::set('scribe.title', 'Test API');
+        \Config::set('scribe.postman', [
+            'description' => 'A fake description',
+        ]);
 
         $writer = new PostmanCollectionWriter(new Collection(), '');
-        $collection = $writer->makePostmanCollection();
+        $collection = $writer->generatePostmanCollection();
 
-        $this->assertSame('Test API', json_decode($collection)->info->name);
+        $this->assertSame('Test API', $collection['info']['name']);
+        $this->assertSame('A fake description', $collection['info']['description']);
     }
 
     public function testFallbackCollectionNameIsUsed()
@@ -24,29 +28,17 @@ class PostmanCollectionWriterTest extends TestCase
         \Config::set('app.name', 'Fake App');
 
         $writer = new PostmanCollectionWriter(new Collection(), '');
-        $collection = $writer->makePostmanCollection();
-
-        $this->assertSame('Fake App API', json_decode($collection)->info->name);
-    }
-
-    public function testDescriptionIsPresentInCollection()
-    {
-        \Config::set('scribe.postman', [
-            'description' => 'A fake description',
-        ]);
-
-        $writer = new PostmanCollectionWriter(new Collection(), '');
-        $collection = $writer->makePostmanCollection();
+        $collection = $writer->generatePostmanCollection();
 
-        $this->assertSame('A fake description', json_decode($collection)->info->description);
+        $this->assertSame('Fake App API', $collection['info']['name']);
     }
 
     public function testAuthIsNotIncludedWhenNull()
     {
         $writer = new PostmanCollectionWriter(new Collection(), '');
-        $collection = $writer->makePostmanCollection();
+        $collection = $writer->generatePostmanCollection();
 
-        $this->assertArrayNotHasKey('auth', json_decode($collection, true));
+        $this->assertArrayNotHasKey('auth', $collection);
     }
 
     public function testAuthIsIncludedVerbatim()
@@ -60,9 +52,9 @@ class PostmanCollectionWriterTest extends TestCase
         ]);
 
         $writer = new PostmanCollectionWriter(new Collection(), '');
-        $collection = $writer->makePostmanCollection();
+        $collection = $writer->generatePostmanCollection();
 
-        $this->assertSame($auth, json_decode($collection, true)['auth']);
+        $this->assertSame($auth, $collection['auth']);
     }
 
     public function testEndpointIsParsed()
@@ -75,7 +67,7 @@ class PostmanCollectionWriterTest extends TestCase
         $collection = $this->createMockRouteGroup([$route], 'Group');
 
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $this->assertSame('Group', data_get($collection, 'item.0.name'), 'Group name exists');
 
@@ -96,7 +88,7 @@ class PostmanCollectionWriterTest extends TestCase
     {
         $collection = $this->createMockRouteGroup([$this->createMockRouteData('fake')]);
         $writer = new PostmanCollectionWriter($collection, 'https://fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $this->assertSame('https', data_get($collection, 'item.0.item.0.request.url.protocol'));
     }
@@ -109,7 +101,7 @@ class PostmanCollectionWriterTest extends TestCase
 
         $collection = $this->createMockRouteGroup([$route], 'Group');
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $this->assertContains([
             'key' => 'X-Fake',
@@ -117,33 +109,25 @@ class PostmanCollectionWriterTest extends TestCase
         ], data_get($collection, 'item.0.item.0.request.header'));
     }
 
-    public function testUrlParametersAreConverted()
-    {
-        $collection = $this->createMockRouteGroup([$this->createMockRouteData('fake/{param}')]);
-        $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
-
-        $item = data_get($collection, 'item.0.item.0');
-        $this->assertSame('fake/{param}', $item['name'], 'Name defaults to path');
-        $this->assertSame('fake/:param', data_get($item, 'request.url.path'), 'Path is converted');
-    }
-
-    public function testUrlParamsResolveTheirDocumentation()
+    /** @test */
+    public function url_parameters_are_represented_properly()
     {
         $fakeRoute = $this->createMockRouteData('fake/{param}');
-
         $fakeRoute['urlParameters'] = ['param' => [
             'description' => 'A test description for the test param',
             'required' => true,
             'value' => 'foobar',
         ]];
-
         $collection = $this->createMockRouteGroup([$fakeRoute]);
+
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
-        $variableData = data_get($collection, 'item.0.item.0.request.url.variable');
+        $item = data_get($collection, 'item.0.item.0');
+        $this->assertSame('fake/{param}', $item['name'], 'Name defaults to URL path');
+        $this->assertSame('fake/:param', data_get($item, 'request.url.path'), 'Path is converted');
 
+        $variableData = data_get($collection, 'item.0.item.0.request.url.variable');
         $this->assertCount(1, $variableData);
         $this->assertEquals([
             'id' => 'param',
@@ -153,6 +137,7 @@ class PostmanCollectionWriterTest extends TestCase
         ], $variableData[0]);
     }
 
+    /** @test */
     public function testQueryParametersAreDocumented()
     {
         $fakeRoute = $this->createMockRouteData('fake/path');
@@ -173,7 +158,7 @@ class PostmanCollectionWriterTest extends TestCase
 
         $collection = $this->createMockRouteGroup([$fakeRoute]);
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $variableData = data_get($collection, 'item.0.item.0.request.url.query');
 
@@ -204,7 +189,7 @@ class PostmanCollectionWriterTest extends TestCase
 
         $collection = $this->createMockRouteGroup([$fakeRoute]);
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $variableData = data_get($collection, 'item.0.item.0.request.url.query');
 
@@ -230,7 +215,7 @@ class PostmanCollectionWriterTest extends TestCase
 
         $collection = $this->createMockRouteGroup([$fakeRoute]);
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $variableData = data_get($collection, 'item.0.item.0.request.url.query');
 
@@ -262,7 +247,7 @@ class PostmanCollectionWriterTest extends TestCase
         $route['headers'] = $expectedRemovedHeaders;
         $collection = $this->createMockRouteGroup([$route], 'Group');
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         foreach ($expectedRemovedHeaders as $key => $value) {
             $this->assertNotContains(compact('key', 'value'), data_get($collection, 'item.0.item.0.request.header'));
@@ -296,7 +281,7 @@ class PostmanCollectionWriterTest extends TestCase
         $route['headers'] = ['X-Authorization' => 'Test'];
         $collection = $this->createMockRouteGroup([$route], 'Group');
         $writer = new PostmanCollectionWriter($collection, 'fake.localhost');
-        $collection = json_decode($writer->makePostmanCollection(), true);
+        $collection = $writer->generatePostmanCollection();
 
         $this->assertContains([
             'key' => 'X-Authorization',
@@ -314,9 +299,15 @@ class PostmanCollectionWriterTest extends TestCase
                 'groupDescription' => '',
                 'title' => $title,
             ],
-            'queryParameters' => [],
             'urlParameters' => [],
+            'cleanUrlParameters' => [],
+            'queryParameters' => [],
+            'cleanQueryParameters' => [],
+            'bodyParameters' => [],
             'cleanBodyParameters' => [],
+            'fileParameters' => [],
+            'responses' => [],
+            'responseFields' => [],
         ];
     }