diff --git a/composer.json b/composer.json index 39d2948d..8b2da353 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=7.3.0", "laravel/framework": ">=5.3.0", - "phpunit/phpunit": ">=7.0" + "phpunit/phpunit": ">=7.0", + "ext-json": "*" }, "require-dev": { "orchestra/testbench": "^6.25", diff --git a/composer.lock b/composer.lock index 67204cd2..94404c68 100644 --- a/composer.lock +++ b/composer.lock @@ -1860,16 +1860,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.5", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", - "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -1942,7 +1942,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -1958,7 +1959,7 @@ "type": "tidelift" } ], - "time": "2023-03-09T06:34:10+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "psr/container", @@ -3378,16 +3379,16 @@ }, { "name": "symfony/console", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9" + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c77433ddc6cdc689caf48065d9ea22ca0853fbd9", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9", + "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8", + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8", "shasum": "" }, "require": { @@ -3452,12 +3453,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.21" + "source": "https://github.com/symfony/console/tree/v5.4.22" }, "funding": [ { @@ -3473,7 +3474,7 @@ "type": "tidelift" } ], - "time": "2023-02-25T16:59:41+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "symfony/css-selector", @@ -3681,16 +3682,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f0ae1383a8285dfc6752b8d8602790953118ff5a" + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f0ae1383a8285dfc6752b8d8602790953118ff5a", - "reference": "f0ae1383a8285dfc6752b8d8602790953118ff5a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", "shasum": "" }, "require": { @@ -3746,7 +3747,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.21" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" }, "funding": [ { @@ -3762,7 +3763,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-03-17T11:31:58+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3908,16 +3909,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3bb6ee5582366c4176d5ce596b380117c8200bbf" + "reference": "05cd1acdd0e3ce8473aaba1d86c188321d85f313" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3bb6ee5582366c4176d5ce596b380117c8200bbf", - "reference": "3bb6ee5582366c4176d5ce596b380117c8200bbf", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/05cd1acdd0e3ce8473aaba1d86c188321d85f313", + "reference": "05cd1acdd0e3ce8473aaba1d86c188321d85f313", "shasum": "" }, "require": { @@ -3964,7 +3965,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.21" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.22" }, "funding": [ { @@ -3980,20 +3981,20 @@ "type": "tidelift" } ], - "time": "2023-02-17T21:35:35+00:00" + "time": "2023-03-28T07:28:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "09c19fc7e4218fbcf73fe0330eea38d66064b775" + "reference": "2d3a8be2c756353627398827c409af6f126c096d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/09c19fc7e4218fbcf73fe0330eea38d66064b775", - "reference": "09c19fc7e4218fbcf73fe0330eea38d66064b775", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2d3a8be2c756353627398827c409af6f126c096d", + "reference": "2d3a8be2c756353627398827c409af6f126c096d", "shasum": "" }, "require": { @@ -4076,7 +4077,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.21" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.22" }, "funding": [ { @@ -4092,7 +4093,7 @@ "type": "tidelift" } ], - "time": "2023-02-28T13:19:09+00:00" + "time": "2023-03-31T11:54:37+00:00" }, { "name": "symfony/mime", @@ -4997,16 +4998,16 @@ }, { "name": "symfony/process", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd" + "reference": "4b850da0cc3a2a9181c1ed407adbca4733dc839b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", + "url": "https://api.github.com/repos/symfony/process/zipball/4b850da0cc3a2a9181c1ed407adbca4733dc839b", + "reference": "4b850da0cc3a2a9181c1ed407adbca4733dc839b", "shasum": "" }, "require": { @@ -5039,7 +5040,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.21" + "source": "https://github.com/symfony/process/tree/v5.4.22" }, "funding": [ { @@ -5055,20 +5056,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-03-06T21:29:33+00:00" }, { "name": "symfony/routing", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "2ea0f3049076e8ef96eab203a809d6b2332f0361" + "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/2ea0f3049076e8ef96eab203a809d6b2332f0361", - "reference": "2ea0f3049076e8ef96eab203a809d6b2332f0361", + "url": "https://api.github.com/repos/symfony/routing/zipball/c2ac11eb34947999b7c38fb4c835a57306907e6d", + "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d", "shasum": "" }, "require": { @@ -5129,7 +5130,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.21" + "source": "https://github.com/symfony/routing/tree/v5.4.22" }, "funding": [ { @@ -5145,7 +5146,7 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2023-03-14T14:59:20+00:00" }, { "name": "symfony/service-contracts", @@ -5232,16 +5233,16 @@ }, { "name": "symfony/string", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f" + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/edac10d167b78b1d90f46a80320d632de0bd9f2f", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f", + "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", "shasum": "" }, "require": { @@ -5298,7 +5299,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.21" + "source": "https://github.com/symfony/string/tree/v5.4.22" }, "funding": [ { @@ -5314,20 +5315,20 @@ "type": "tidelift" } ], - "time": "2023-02-22T08:00:55+00:00" + "time": "2023-03-14T06:11:53+00:00" }, { "name": "symfony/translation", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "6996affeea65705086939894b77110e9a7f80874" + "reference": "9a401392f01bc385aa42760eff481d213a0cc2ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/6996affeea65705086939894b77110e9a7f80874", - "reference": "6996affeea65705086939894b77110e9a7f80874", + "url": "https://api.github.com/repos/symfony/translation/zipball/9a401392f01bc385aa42760eff481d213a0cc2ba", + "reference": "9a401392f01bc385aa42760eff481d213a0cc2ba", "shasum": "" }, "require": { @@ -5395,7 +5396,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.21" + "source": "https://github.com/symfony/translation/tree/v5.4.22" }, "funding": [ { @@ -5411,7 +5412,7 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-03-27T16:07:23+00:00" }, { "name": "symfony/translation-contracts", @@ -5493,16 +5494,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74" + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", "shasum": "" }, "require": { @@ -5562,7 +5563,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.22" }, "funding": [ { @@ -5578,7 +5579,7 @@ "type": "tidelift" } ], - "time": "2023-02-23T10:00:28+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "theseer/tokenizer", @@ -5970,22 +5971,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -6078,7 +6079,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.5.1" }, "funding": [ { @@ -6094,7 +6095,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-04-17T16:30:08+00:00" }, { "name": "guzzlehttp/promises", @@ -6421,25 +6422,25 @@ }, { "name": "orchestra/testbench", - "version": "v6.25.1", + "version": "v6.27.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "0516123d26d64117bc04f7e9cb982eae2624e750" + "reference": "b78f91f17bb86981e73dfbe786f055064c11539d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/0516123d26d64117bc04f7e9cb982eae2624e750", - "reference": "0516123d26d64117bc04f7e9cb982eae2624e750", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/b78f91f17bb86981e73dfbe786f055064c11539d", + "reference": "b78f91f17bb86981e73dfbe786f055064c11539d", "shasum": "" }, "require": { - "laravel/framework": "^8.75", + "laravel/framework": "^8.83.26", "mockery/mockery": "^1.4.4", - "orchestra/testbench-core": "^6.29.1", + "orchestra/testbench-core": "^6.31.1", "php": "^7.3 || ^8.0", "phpunit/phpunit": "^8.5.21 || ^9.5.10", - "spatie/laravel-ray": "^1.26.2" + "spatie/laravel-ray": "^1.29.7" }, "type": "library", "extra": { @@ -6470,32 +6471,22 @@ ], "support": { "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v6.25.1" + "source": "https://github.com/orchestral/testbench/tree/v6.27.1" }, - "funding": [ - { - "url": "https://paypal.me/crynobone", - "type": "custom" - }, - { - "url": "https://liberapay.com/crynobone", - "type": "liberapay" - } - ], - "time": "2022-10-11T14:01:10+00:00" + "time": "2023-04-03T01:20:40+00:00" }, { "name": "orchestra/testbench-core", - "version": "v6.29.1", + "version": "v6.31.2", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "29a7586915885f89b8d2203efe20f76afe9cf956" + "reference": "a630f963d45a4aeb6a8771093fa029bf2a8a4f15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/29a7586915885f89b8d2203efe20f76afe9cf956", - "reference": "29a7586915885f89b8d2203efe20f76afe9cf956", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a630f963d45a4aeb6a8771093fa029bf2a8a4f15", + "reference": "a630f963d45a4aeb6a8771093fa029bf2a8a4f15", "shasum": "" }, "require": { @@ -6505,7 +6496,7 @@ "vlucas/phpdotenv": "^5.1" }, "require-dev": { - "laravel/framework": "^8.75", + "laravel/framework": "^8.83.27", "laravel/laravel": "8.x-dev", "mockery/mockery": "^1.4.4", "orchestra/canvas": "^6.1", @@ -6514,7 +6505,7 @@ "symfony/process": "^5.0" }, "suggest": { - "laravel/framework": "Required for testing (^8.75).", + "laravel/framework": "Required for testing (^8.83.26).", "mockery/mockery": "Allow using Mockery for testing (^1.4.4).", "orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^6.0).", "orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^6.0).", @@ -6553,26 +6544,16 @@ "keywords": [ "BDD", "TDD", + "dev", "laravel", - "orchestra-platform", - "orchestral", + "laravel-packages", "testing" ], "support": { "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "funding": [ - { - "url": "https://paypal.me/crynobone", - "type": "custom" - }, - { - "url": "https://liberapay.com/crynobone", - "type": "liberapay" - } - ], - "time": "2022-10-11T12:12:52+00:00" + "time": "2023-04-11T07:42:07+00:00" }, { "name": "php-coveralls/php-coveralls", @@ -6712,21 +6693,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -6746,7 +6727,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -6758,9 +6739,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "psr/http-factory", diff --git a/config/auto-doc.php b/config/auto-doc.php index 434d915e..f1ad1377 100644 --- a/config/auto-doc.php +++ b/config/auto-doc.php @@ -40,7 +40,7 @@ | Documentation Template |-------------------------------------------------------------------------- | - | You can use your custom documentation view + | You can use your custom documentation view. */ 'description' => 'auto-doc::swagger-description', 'version' => '0.0.0', @@ -54,9 +54,6 @@ 'url' => '' ] ], - 'swagger' => [ - 'version' => '2.0' - ], /* |-------------------------------------------------------------------------- diff --git a/src/Exceptions/SpecValidation/DuplicateFieldException.php b/src/Exceptions/SpecValidation/DuplicateFieldException.php new file mode 100644 index 00000000..60394af3 --- /dev/null +++ b/src/Exceptions/SpecValidation/DuplicateFieldException.php @@ -0,0 +1,13 @@ +data = $this->driver->getTmpData(); - if (empty($this->data)) { + if (!empty($this->data)) { + $this->validateSpec($this->data); + } else { $this->data = $this->generateEmptyData(); $this->driver->saveTmpData($this->data); @@ -110,44 +117,35 @@ protected function generateEmptyData(): array } $data = [ - 'swagger' => Arr::get($this->config, 'swagger.version'), + 'swagger' => self::SWAGGER_VERSION, 'host' => $this->getAppUrl(), 'basePath' => $this->config['basePath'], 'schemes' => $this->config['schemes'], 'paths' => [], - 'definitions' => $this->config['definitions'] + 'definitions' => $this->config['definitions'], + 'info' => $this->prepareInfo($this->config['info']) ]; - $info = $this->prepareInfo($this->config['info']); - - if (!empty($info)) { - $data['info'] = $info; - } - $securityDefinitions = $this->generateSecurityDefinition(); if (!empty($securityDefinitions)) { $data['securityDefinitions'] = $securityDefinitions; } - if (!empty($data['info']['description'])) { - $data['info']['description'] = view($data['info']['description'])->render(); - } - return $data; } - protected function getAppUrl() + protected function getAppUrl(): string { $url = config('app.url'); return str_replace(['http://', 'https://', '/'], '', $url); } - protected function generateSecurityDefinition() + protected function generateSecurityDefinition(): ?array { if (empty($this->security)) { - return ''; + return null; } return [ @@ -691,34 +689,48 @@ public function getDocFileContent() foreach ($additionalDocs as $filePath) { $fullFilePath = base_path($filePath); - if (file_exists($fullFilePath)) { - $fileContent = json_decode(file_get_contents($fullFilePath), true); + if (!file_exists($fullFilePath)) { + continue; + } + + $fileContent = json_decode(file_get_contents($fullFilePath), true); - $paths = array_keys($fileContent['paths']); + if (empty($fileContent)) { + continue; + } + + try { + $this->validateSpec($fileContent); + } catch (InvalidSwaggerSpecException $exception) { + report($exception); - foreach ($paths as $path) { - $additionalDocPath = $fileContent['paths'][$path]; + continue; + } - if (empty($documentation['paths'][$path])) { - $documentation['paths'][$path] = $additionalDocPath; - } else { - $methods = array_keys($documentation['paths'][$path]); - $additionalDocMethods = array_keys($additionalDocPath); + $paths = array_keys($fileContent['paths']); - foreach ($additionalDocMethods as $method) { - if (!in_array($method, $methods)) { - $documentation['paths'][$path][$method] = $additionalDocPath[$method]; - } + foreach ($paths as $path) { + $additionalDocPath = $fileContent['paths'][$path]; + + if (empty($documentation['paths'][$path])) { + $documentation['paths'][$path] = $additionalDocPath; + } else { + $methods = array_keys($documentation['paths'][$path]); + $additionalDocMethods = array_keys($additionalDocPath); + + foreach ($additionalDocMethods as $method) { + if (!in_array($method, $methods)) { + $documentation['paths'][$path][$method] = $additionalDocPath[$method]; } } } + } - $definitions = array_keys($fileContent['definitions']); + $definitions = array_keys($fileContent['definitions']); - foreach ($definitions as $definition) { - if (empty($documentation['definitions'][$definition])) { - $documentation['definitions'][$definition] = $fileContent['definitions'][$definition]; - } + foreach ($definitions as $definition) { + if (empty($documentation['definitions'][$definition])) { + $documentation['definitions'][$definition] = $fileContent['definitions'][$definition]; } } } @@ -828,11 +840,7 @@ protected function getDefaultValueByType($type) return $values[$type]; } - /** - * @param $info - * @return mixed - */ - protected function prepareInfo($info) + protected function prepareInfo(array $info): array { if (empty($info)) { return $info; @@ -843,10 +851,20 @@ protected function prepareInfo($info) unset($info['license'][$key]); } } + if (empty($info['license'])) { unset($info['license']); } + if (!empty($info['description'])) { + $info['description'] = view($info['description'])->render(); + } + return $info; } + + protected function validateSpec(array $doc): void + { + app(SwaggerSpecValidator::class)->validate($doc); + } } diff --git a/src/Validators/SwaggerSpecValidator.php b/src/Validators/SwaggerSpecValidator.php new file mode 100644 index 00000000..3be8b8c9 --- /dev/null +++ b/src/Validators/SwaggerSpecValidator.php @@ -0,0 +1,477 @@ + ['type'], + 'doc' => ['swagger', 'info', 'paths'], + 'info' => ['title', 'version'], + 'item' => ['type'], + 'header' => ['type'], + 'operation' => ['responses'], + 'parameter' => ['in', 'name'], + 'response' => ['description'], + 'security_definition' => ['type'], + 'tag' => ['name'] + ]; + + public const ALLOWED_VALUES = [ + 'parameter_collection_format' => ['csv', 'ssv', 'tsv', 'pipes', 'multi'], + 'items_collection_format' => ['csv', 'ssv', 'tsv', 'pipes'], + 'header_collection_format' => ['csv', 'ssv', 'tsv', 'pipes'], + 'parameter_in' => ['body', 'formData', 'query', 'path', 'header'], + 'schemes' => ['http', 'https', 'ws', 'wss'], + 'security_definition_flow' => ['implicit', 'password', 'application', 'accessCode'], + 'security_definition_in' => ['query', 'header'], + 'security_definition_type' => ['basic', 'apiKey', 'oauth2'] + ]; + + public const PATH_PARAM_REGEXP = '#(?<={)[^/}]+(?=})#'; + public const PATH_REGEXP = '/^x-/'; + + public const MIME_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data'; + public const MIME_TYPE_APPLICATION_URLENCODED = 'application/x-www-form-urlencoded'; + + protected $doc; + + public function validate(array $doc): void + { + $this->doc = $doc; + + $this->validateVersion(); + $this->validateFieldsPresent(self::REQUIRED_FIELDS['doc']); + $this->validateInfo(); + $this->validateSchemes(); + $this->validatePaths(); + $this->validateDefinitions(); + $this->validateSecurityDefinitions(); + $this->validateTags(); + $this->validateRefs(); + } + + protected function validateVersion(): void + { + $version = Arr::get($this->doc, 'swagger', ''); + + if (version_compare($version, SwaggerService::SWAGGER_VERSION, '!=')) { + throw new InvalidSwaggerVersionException($version); + } + } + + protected function validateInfo(): void + { + $this->validateFieldsPresent(self::REQUIRED_FIELDS['info'], 'info'); + } + + protected function validateSchemes(): void + { + $this->validateFieldValue('schemes', self::ALLOWED_VALUES['schemes']); + } + + protected function validatePaths(): void + { + foreach ($this->doc['paths'] as $path => $operations) { + if (!Str::startsWith($path, '/') && !preg_match(self::PATH_REGEXP, $path)) { + throw new InvalidPathException("paths.{$path}"); + } + + foreach ($operations as $pathKey => $operation) { + $operationId = "paths.{$path}.{$pathKey}"; + + $this->validateFieldsPresent(self::REQUIRED_FIELDS['operation'], $operationId); + $this->validateFieldValue("{$operationId}.schemes", self::ALLOWED_VALUES['schemes']); + + $this->validateParameters($operation, $path, $operationId); + + foreach ($operation['responses'] as $statusCode => $response) { + $this->validateResponse($response, $statusCode, $operationId); + } + } + } + + $this->validateOperationIdsUnique(); + } + + protected function validateDefinitions(): void + { + $definitions = Arr::get($this->doc, 'definitions', []); + + foreach ($definitions as $index => $definition) { + $this->validateFieldsPresent(self::REQUIRED_FIELDS['definition'], "definitions.{$index}"); + } + } + + protected function validateSecurityDefinitions(): void + { + $securityDefinitions = Arr::get($this->doc, 'securityDefinitions', []); + + foreach ($securityDefinitions as $index => $securityDefinition) { + $parentId = "securityDefinitions.{$index}"; + + $this->validateFieldsPresent(self::REQUIRED_FIELDS['security_definition'], $parentId); + + $this->validateFieldValue("{$parentId}.'type", self::ALLOWED_VALUES['security_definition_type']); + $this->validateFieldValue("{$parentId}.'in", self::ALLOWED_VALUES['security_definition_in']); + $this->validateFieldValue("{$parentId}.'flow", self::ALLOWED_VALUES['security_definition_flow']); + } + } + + protected function validateTags(): void + { + $tags = Arr::get($this->doc, 'tags', []); + + foreach ($tags as $index => $tag) { + $this->validateFieldsPresent(self::REQUIRED_FIELDS['tag'], "tags.{$index}"); + } + + $this->validateTagsUnique(); + } + + protected function validateResponse(array $response, string $statusCode, string $operationId): void + { + $responseId = "{$operationId}.responses.{$statusCode}"; + + $this->validateFieldsPresent(self::REQUIRED_FIELDS['response'], $responseId); + + if ( + ($statusCode !== 'default') + && !$this->isValidStatusCode($statusCode) + && !preg_match(self::PATH_REGEXP, $statusCode) + ) { + throw new InvalidStatusCodeException($responseId); + } + + foreach (Arr::get($response, 'headers', []) as $headerName => $header) { + $this->validateHeader($header, "{$responseId}.headers.{$headerName}"); + } + + if (!empty($response['schema'])) { + $this->validateType( + $response['schema'], + array_merge(self::SCHEMA_TYPES, ['file']), + "{$responseId}.schema" + ); + } + + if (!empty($response['items'])) { + $this->validateItems($response['items'], "{$responseId}.items"); + } + } + + protected function validateParameters(array $operation, string $path, string $operationId): void + { + $parameters = Arr::get($operation, 'parameters', []); + + foreach ($parameters as $index => $param) { + $paramId = "{$operationId}.parameters.{$index}"; + + $this->validateFieldsPresent(self::REQUIRED_FIELDS['parameter'], $paramId); + + $this->validateFieldValue("{$paramId}.in", self::ALLOWED_VALUES['parameter_in']); + $this->validateFieldValue( + "{$paramId}.collectionFormat", + self::ALLOWED_VALUES['parameter_collection_format'] + ); + + $this->validateParameterType($param, $operation, $paramId, $operationId); + + if (!empty($param['items'])) { + $this->validateItems($param['items'], "{$paramId}.items"); + } + } + + $this->validateParamsUnique($parameters, $operationId); + $this->validatePathParameters($parameters, $path, $operationId); + $this->validateBodyParameters($parameters, $operationId); + } + + protected function validateType(array $schema, array $validTypes, string $schemaId): void + { + $schemaType = Arr::get($schema, 'type'); + + if (!empty($schemaType) && !in_array($schemaType, $validTypes)) { + throw new InvalidFieldValueException("{$schemaId}.type", $validTypes, [$schema['type']]); + } + + if (($schemaType === 'array') && empty($schema['items'])) { + throw new InvalidSwaggerSpecException("{$schemaId} is an array, so it must include an 'items' field."); + } + } + + protected function validatePathParameters(array $params, string $path, string $operationId): void + { + $pathParams = Arr::where($params, function ($param) { + return $param['in'] === 'path'; + }); + + preg_match_all(self::PATH_PARAM_REGEXP, $path, $matches); + $placeholders = Arr::first($matches); + + $placeholderDuplicates = $this->getArrayDuplicates($placeholders); + + if (!empty($placeholderDuplicates)) { + throw new DuplicatePathPlaceholderException($placeholderDuplicates, $path); + } + + $missedRequiredParams = array_filter($pathParams, function ($param) use ($placeholders) { + return Arr::get($param, 'required', false) && !in_array(Arr::get($param, 'name'), $placeholders); + }); + + if (!empty($missedRequiredParams)) { + $missedRequiredString = implode(',', Arr::pluck($missedRequiredParams, 'name')); + + throw new InvalidSwaggerSpecException( + "Path parameters cannot be optional. Set required=true for the " + . "'{$missedRequiredString}' parameters at operation '{$operationId}'." + ); + } + + $missingPlaceholders = array_diff(Arr::pluck($pathParams, 'name'), $placeholders); + + if (!empty($missingPlaceholders)) { + throw new MissingPathPlaceholderException($operationId, $missingPlaceholders); + } + + $missingPathParams = array_diff($placeholders, Arr::pluck($pathParams, 'name')); + + if (!empty($missingPathParams)) { + throw new MissingPathParamException($operationId, $missingPathParams); + } + } + + protected function validateBodyParameters(array $parameters, string $operationId): void + { + $bodyParamsCount = collect($parameters)->where('in', 'body')->count(); + $formParamsCount = collect($parameters)->where('in', 'formData')->count(); + + if ($bodyParamsCount > 1) { + throw new InvalidSwaggerSpecException( + "Operation '{$operationId}' has {$bodyParamsCount} body parameters. Only one is allowed." + ); + } + + if ($bodyParamsCount && $formParamsCount) { + throw new InvalidSwaggerSpecException( + "Operation '{$operationId}' has body and formData parameters. Only one or the other is allowed." + ); + } + } + + protected function validateParameterType(array $param, array $operation, string $paramId, string $operationId): void + { + switch ($param['in']) { + case 'body': + $requiredFields = ['schema']; + $validTypes = self::SCHEMA_TYPES; + + break; + case 'formData': + $this->validateFormDataConsumes($operation, $operationId); + + $requiredFields = ['type']; + $validTypes = array_merge(self::PRIMITIVE_TYPES, ['file']); + + break; + default: + $requiredFields = ['type']; + $validTypes = self::PRIMITIVE_TYPES; + } + + $this->validateFieldsPresent($requiredFields, $paramId); + + $schema = Arr::get($param, 'schema', $param); + $this->validateType($schema, $validTypes, $paramId); + } + + protected function validateHeader(array $header, string $headerId): void + { + $this->validateFieldsPresent(self::REQUIRED_FIELDS['header'], $headerId); + $this->validateType($header, self::PRIMITIVE_TYPES, $headerId); + $this->validateFieldValue("{$headerId}.collectionFormat", self::ALLOWED_VALUES['header_collection_format']); + + if (!empty($header['items'])) { + $this->validateItems($header['items'], $headerId); + } + } + + protected function validateItems(array $items, string $itemsId): void + { + $this->validateFieldsPresent(self::REQUIRED_FIELDS['item'], $itemsId); + $this->validateType($items, self::PRIMITIVE_TYPES, $itemsId); + $this->validateFieldValue("{$itemsId}.collectionFormat", self::ALLOWED_VALUES['items_collection_format']); + } + + protected function getMissingFields(array $requiredFields, array $doc, ?string $fieldName = null): array + { + return array_diff($requiredFields, array_keys(Arr::get($doc, $fieldName))); + } + + protected function validateFieldsPresent(array $requiredFields, ?string $fieldName = null): void + { + $missingDocFields = $this->getMissingFields($requiredFields, $this->doc, $fieldName); + + if (!empty($missingDocFields)) { + throw new MissingFieldException($missingDocFields, $fieldName); + } + } + + protected function validateFieldValue(string $fieldName, array $allowedValues): void + { + $inputValue = Arr::wrap(Arr::get($this->doc, $fieldName, [])); + $approvedValues = array_intersect($inputValue, $allowedValues); + $invalidValues = array_diff($inputValue, $approvedValues); + + if (!empty($invalidValues)) { + throw new InvalidFieldValueException($fieldName, $allowedValues, $invalidValues); + } + } + + protected function validateRefs(): void + { + array_walk_recursive($this->doc, function ($item, $key) { + if ($key === '$ref') { + $refParts = explode('#/', $item); + $refFilename = Arr::first($refParts); + + if (count($refParts) > 1) { + $path = pathinfo(Arr::last($refParts)); + $refParentKey = $path['dirname']; + $refKey = $path['filename']; + } + + if (!empty($refFilename) && !file_exists($refFilename)) { + throw new MissingRefFileException($refFilename); + } + + $missingRefs = $this->getMissingFields( + (array) $refKey, + !empty($refFilename) + ? json_decode(file_get_contents($refFilename), true) + : $this->doc, + $refParentKey + ); + + if (!empty($missingRefs)) { + if (!empty($refFilename)) { + throw new MissingExternalRefException($refKey, $refFilename); + } else { + throw new MissingLocalRefException($refKey, $refParentKey); + } + } + } + }); + } + + protected function validateParamsUnique(array $params, string $operationId): void + { + $collection = collect($params); + $duplicates = $collection->duplicates(function ($item) { + return $item['in'] . $item['name']; + }); + + if ($duplicates->count()) { + $duplicateIndex = $duplicates->keys()->first(); + + throw new DuplicateParamException($params[$duplicateIndex]['in'], $params[$duplicateIndex]['name'], $operationId); + } + } + + protected function validateTagsUnique(): void + { + $tags = Arr::get($this->doc, 'tags', []); + $tagNames = Arr::pluck($tags, 'name'); + $duplicates = $this->getArrayDuplicates($tagNames); + + if (!empty($duplicates)) { + throw new DuplicateFieldException('tags.*.name', $duplicates); + } + } + + protected function validateOperationIdsUnique(): void + { + $operationIds = Arr::pluck(Arr::get($this->doc, 'paths', []), '*.operationId'); + $operationIds = Arr::flatten($operationIds); + $duplicates = $this->getArrayDuplicates($operationIds); + + if (!empty($duplicates)) { + throw new DuplicateFieldException('paths.*.*.operationId', $duplicates); + } + } + + protected function validateFormDataConsumes(array $operation, string $operationId): void + { + $consumes = Arr::get($operation, 'consumes', []); + + $requiredConsume = Arr::first($consumes, function ($consume) { + return in_array($consume, [ + self::MIME_TYPE_APPLICATION_URLENCODED, + self::MIME_TYPE_MULTIPART_FORM_DATA, + ]); + }); + + if (empty($requiredConsume)) { + throw new InvalidSwaggerSpecException( + "Operation '{$operationId}' has body and formData parameters. Only one or the other is allowed." + ); + } + } + + protected function getArrayDuplicates(array $array): array + { + $array = array_filter($array); + $duplicates = array_filter(array_count_values($array), function ($value) { + return $value > 1; + }); + + return array_keys($duplicates); + } + + protected function isValidStatusCode(string $code): bool + { + $code = intval($code); + + return $code >= 100 && $code < 600; + } +} diff --git a/tests/AutoDocControllerTest.php b/tests/AutoDocControllerTest.php index f31bd0a6..eccb1b3c 100644 --- a/tests/AutoDocControllerTest.php +++ b/tests/AutoDocControllerTest.php @@ -50,10 +50,32 @@ public function testGetJSONDocumentationWithAdditionalPaths() $this->assertEqualsJsonFixture('tmp_data_with_additional_paths', $response->json()); } - public function testGetJSONDocumentationWithInvalidAdditionalPath() + public function getJSONDocumentationInvalidAdditionalDoc(): array + { + $basePath = 'tests/fixtures/AutoDocControllerTest'; + + return [ + [ + 'additionalDocPath' => 'invalid_path/non_existent_file.json' + ], + [ + 'additionalDocPath' => $basePath . '/documentation__non_json.txt' + ], + [ + 'additionalDocPath' => $basePath. '/documentation__invalid_format__missing_field__paths.json' + ] + ]; + } + + /** + * @dataProvider getJSONDocumentationInvalidAdditionalDoc + * + * @param string $additionalDocPath + */ + public function testGetJSONDocumentationInvalidAdditionalDoc(string $additionalDocPath) { config([ - 'auto-doc.additional_paths' => ['invalid_path/non_existent_file.json'] + 'auto-doc.additional_paths' => [$additionalDocPath] ]); $response = $this->json('get', '/auto-doc/documentation'); @@ -154,7 +176,10 @@ public function testGetElementsAssetFile() $response->assertStatus(Response::HTTP_OK); - $this->assertEquals($response->getContent(), file_get_contents(resource_path('/assets/elements/web-components.min.js'))); + $this->assertEquals( + $response->getContent(), + file_get_contents(resource_path('/assets/elements/web-components.min.js')) + ); $response->assertHeader('Content-Type', 'text/html; charset=UTF-8'); } diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index 89eca052..ad02b5b2 100755 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -6,6 +6,20 @@ use RonasIT\Support\AutoDoc\Exceptions\EmptyContactEmailException; use RonasIT\Support\AutoDoc\Exceptions\InvalidDriverClassException; use RonasIT\Support\AutoDoc\Exceptions\LegacyConfigException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\DuplicateFieldException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\DuplicateParamException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\DuplicatePathPlaceholderException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidFieldValueException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidPathException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidStatusCodeException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerSpecException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerVersionException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingExternalRefException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingLocalRefException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingFieldException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingPathParamException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingPathPlaceholderException; +use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\MissingRefFileException; use RonasIT\Support\AutoDoc\Exceptions\SwaggerDriverClassNotFoundException; use RonasIT\Support\AutoDoc\Exceptions\WrongSecurityConfigException; use RonasIT\Support\AutoDoc\Services\SwaggerService; @@ -52,6 +66,230 @@ public function testConstructorDriverClassNotImplementsInterface() app(SwaggerService::class); } + public function testResponseHeaderWithItems() + { + $this->mockDriverGetTpmData($this->getJsonFixture('documentation/array_response_header_with_items')); + + app(SwaggerService::class); + } + + public function testFormData() + { + $this->mockDriverGetTpmData($this->getJsonFixture('documentation/formdata_request')); + + app(SwaggerService::class); + } + + public function getConstructorInvalidTmpData(): array + { + return [ + [ + 'tmpDoc' => 'documentation/invalid_version', + 'exception' => InvalidSwaggerVersionException::class, + 'exceptionMessage' => "Unrecognized Swagger version '1.0'. Expected 2.0." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__array_parameter__no_items', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. paths./users.post.parameters.0 is an " + . "array, so it must include an 'items' field." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__array_response_body__no_items', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. paths./users.get.responses.200.schema is an array, " + . "so it must include an 'items' field." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__array_response_header__no_items', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. paths./users.get.responses.default.headers." + . "Last-Modified is an array, so it must include an 'items' field." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__body_and_form_params', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.post' " + . "has body and formData parameters. Only one or the other is allowed." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__duplicate_header_params', + 'exception' => DuplicateParamException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' " + . "has multiple in:header parameters with name:foo." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__duplicate_path_params', + 'exception' => DuplicateParamException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' has " + . "multiple in:path parameters with name:username" + ], + [ + 'tmpDoc' => 'documentation/invalid_format__duplicate_path_placeholders', + 'exception' => DuplicatePathPlaceholderException::class, + 'exceptionMessage' => "Validation failed. Path '/users/{username}/profile/{username}/image/{img_id}' " + . "has multiple path placeholders with name: username." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__duplicate_operation_id', + 'exception' => DuplicateFieldException::class, + 'exceptionMessage' => "Validation failed. Found multiple fields 'paths.*.*.operationId' " + . "with values: addPet." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__duplicate_tag', + 'exception' => DuplicateFieldException::class, + 'exceptionMessage' => "Validation failed. Found multiple fields 'tags.*.name' with values: user." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__file_invalid_consumes', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/profile/image.post' " + . "has body and formData parameters. Only one or the other is allowed." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__file_no_consumes', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/profile/image.post' " + . "has body and formData parameters. Only one or the other is allowed." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__multiple_body_params', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' has 2 body " + . "parameters. Only one is allowed." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__no_path_params', + 'exception' => MissingPathParamException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/{foo}.get' has " + . "no params for placeholders: username, foo." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__path_param_no_placeholder', + 'exception' => MissingPathPlaceholderException::class, + 'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.post' has no " + . "placeholders for params: foo." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__invalid_value__path', + 'exception' => InvalidPathException::class, + 'exceptionMessage' => "Validation failed. Incorrect 'paths.users'. Paths should only have path " + . "names that starts with `/`." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__invalid_value__status_code', + 'exception' => InvalidStatusCodeException::class, + 'exceptionMessage' => "Validation failed. Operation at 'paths./users.get.responses.8888' should " + . "only have three-digit status codes, `default`, and vendor extensions (`x-*`) as properties." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__invalid_value__parameter_in', + 'exception' => InvalidFieldValueException::class, + 'exceptionMessage' => "Validation failed. Field 'paths./auth/login.post.parameters.0.in' " + . "has an invalid value: invalid_in. Allowed values: body, formData, query, path, header." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__paths', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. '' should have required fields: paths." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__operation_responses', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'paths./auth/login.post' should have required " + . "fields: responses." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__parameter_in', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'paths./auth/login.post.parameters.0' should " + . "have required fields: in." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__response_description', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'paths./auth/login.post.responses.200' should " + . "have required fields: description." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__definition_type', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'definitions.authloginObject' should have " + . "required fields: type." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__info_version', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'info' should have required fields: version." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__items_type', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'paths./pet/findByStatus.get.parameters.0.items' " + . "should have required fields: type." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__header_type', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'paths./user/login.get.responses.200.headers.X-Rate-Limit' " + . "should have required fields: type." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_field__tag_name', + 'exception' => MissingFieldException::class, + 'exceptionMessage' => "Validation failed. 'tags.0' should have required fields: name." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_local_ref', + 'exception' => MissingLocalRefException::class, + 'exceptionMessage' => "Validation failed. Ref 'loginObject' is used in \$ref but not defined " + . "in 'definitions' field." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_external_ref', + 'exception' => MissingExternalRefException::class, + 'exceptionMessage' => "Validation failed. Ref 'authloginObject' is used in \$ref but not defined " + . "in 'tests/fixtures/SwaggerServiceTest/documentation/with_definitions.json' file." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_ref_file', + 'exception' => MissingRefFileException::class, + 'exceptionMessage' => "Validation failed. Filename 'invalid-filename.json' is used in \$ref but " + . "file doesn't exist." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__invalid_schema_type', + 'exception' => InvalidFieldValueException::class, + 'exceptionMessage' => "Validation failed. Field 'paths./users.get.responses.200.schema.type' " + . "has an invalid value: something. Allowed values: array, boolean, integer, number, " + . "string, object, null, undefined, file." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__missing_path_parameter', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Path parameters cannot be optional. " + . "Set required=true for the 'username' parameters at operation 'paths./users.get'." + ], + ]; + } + + /** + * @dataProvider getConstructorInvalidTmpData + * + * @param string $tmpDoc + * @param string $exception + * @param string $exceptionMessage + */ + public function testConstructorInvalidTmpData(string $tmpDoc, string $exception, string $exceptionMessage) + { + $this->mockDriverGetTpmData($this->getJsonFixture($tmpDoc)); + $this->expectException($exception); + $this->expectExceptionMessage($exceptionMessage); + + app(SwaggerService::class); + } + public function testEmptyContactEmail() { config(['auto-doc.contact.email' => null]); @@ -96,7 +334,10 @@ public function testAddDataRequestWithEmptyDataAndInfo() { config(['auto-doc.info' => []]); - $this->mockDriverGetEmptyAndSaveTpmData([], $this->getJsonFixture('tmp_data_request_with_empty_data_and_info')); + $this->mockDriverGetEmptyAndSaveTpmData( + [], + $this->getJsonFixture('tmp_data_request_with_empty_data_and_info') + ); app(SwaggerService::class); } @@ -157,7 +398,9 @@ public function testAddData(?string $contentType, string $requestFixture, string public function testAddDataRequestWithoutRuleType() { - $this->mockDriverGetEmptyAndSaveTpmData($this->getJsonFixture('tmp_data_search_roles_request_without_rule_type')); + $this->mockDriverGetEmptyAndSaveTpmData( + $this->getJsonFixture('tmp_data_search_roles_request_without_rule_type') + ); $service = app(SwaggerService::class); @@ -170,7 +413,9 @@ public function testAddDataRequestWithoutRuleType() public function testAddDataRequestWithAnnotations() { - $this->mockDriverGetEmptyAndSaveTpmData($this->getJsonFixture('tmp_data_search_roles_request_with_annotations')); + $this->mockDriverGetEmptyAndSaveTpmData( + $this->getJsonFixture('tmp_data_search_roles_request_with_annotations') + ); $service = app(SwaggerService::class); @@ -216,21 +461,6 @@ public function testAddDataWithJWTSecurity(string $security, string $requestFixt $service->addData($request, $response); } - public function testAddDataWithoutInfo() - { - config(['auto-doc.info' => []]); - - $this->mockDriverGetEmptyAndSaveTpmData($this->getJsonFixture('tmp_data_search_roles_request_without_info')); - - $service = app(SwaggerService::class); - - $request = $this->generateGetRolesRequest(); - - $response = $this->generateResponse('example_success_roles_response.json'); - - $service->addData($request, $response); - } - public function testAddDataWithEmptySecurity() { config(['auto-doc.security' => 'invalid']); @@ -327,7 +557,9 @@ public function testAddDataPostRequestWithObjectParams() { config(['auto-doc.security' => 'jwt']); - $this->mockDriverGetEmptyAndSaveTpmData($this->getJsonFixture('tmp_data_post_user_request_with_object_params')); + $this->mockDriverGetEmptyAndSaveTpmData( + $this->getJsonFixture('tmp_data_post_user_request_with_object_params') + ); $service = app(SwaggerService::class); diff --git a/tests/fixtures/AutoDocControllerTest/documentation__invalid_format__missing_field__paths.json b/tests/fixtures/AutoDocControllerTest/documentation__invalid_format__missing_field__paths.json new file mode 100644 index 00000000..45e9c178 --- /dev/null +++ b/tests/fixtures/AutoDocControllerTest/documentation__invalid_format__missing_field__paths.json @@ -0,0 +1,38 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/AutoDocControllerTest/documentation__non_json.txt b/tests/fixtures/AutoDocControllerTest/documentation__non_json.txt new file mode 100644 index 00000000..5566faea --- /dev/null +++ b/tests/fixtures/AutoDocControllerTest/documentation__non_json.txt @@ -0,0 +1,90 @@ +swagger: "2.0" +info: + description: "This is a sample server Petstore server." + version: "1.0.0" + title: "Swagger Petstore 2.0" + termsOfService: "http://swagger.io/terms/" + contact: + email: "apiteam@swagger.io" + license: + name: "Apache 2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0.html" +host: "petstore.swagger.io" +basePath: "/v2" +tags: +- name: "pet" + description: "Everything about your Pets" + externalDocs: + description: "Find out more" + url: "http://swagger.io" +- name: "store" + description: "Access to Petstore orders" +- name: "user" + description: "Operations about user" + externalDocs: + description: "Find out more about our store" + url: "http://swagger.io" +schemes: +- "https" +- "http" +paths: + /pet: + post: + tags: + - "pet" + summary: "Add a new pet to the store" + description: "" + operationId: "addPet" + + parameters: + - in: "body" + name: "body" + description: "Pet object that needs to be added to the store" + required: true + schema: + $ref: "#/definitions/Pet" + responses: + "405": + description: "Invalid input" + security: + - petstore_auth: + - "write:pets" + - "read:pets" +definitions: + Pet: + type: "object" + required: + - "name" + - "photoUrls" + properties: + id: + type: "integer" + format: "int64" + category: + $ref: "#/definitions/Category" + name: + type: "string" + example: "doggie" + photoUrls: + type: "array" + xml: + name: "photoUrl" + wrapped: true + items: + type: "string" + tags: + type: "array" + xml: + name: "tag" + wrapped: true + items: + $ref: "#/definitions/Tag" + status: + type: "string" + description: "pet status in the store" + enum: + - "available" + - "pending" + - "sold" + xml: + name: "Pet" diff --git a/tests/fixtures/SwaggerServiceTest/documentation/array_response_header_with_items.json b/tests/fixtures/SwaggerServiceTest/documentation/array_response_header_with_items.json new file mode 100644 index 00000000..5fd100d8 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/array_response_header_with_items.json @@ -0,0 +1,39 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "200": { + "description": "hello world", + "headers": { + "Content-Type": { + "type": "string" + }, + "Last-Modified": { + "type": "array", + "items": { + "date": { + "type": "string" + } + } + } + }, + "schema": { + "type": "array", + "items": { + "user": { + "type": "string" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/formdata_request.json b/tests/fixtures/SwaggerServiceTest/documentation/formdata_request.json new file mode 100644 index 00000000..7f6cfd19 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/formdata_request.json @@ -0,0 +1,34 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/profile/image": { + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "type": "string", + "required": true + }, + { + "name": "image", + "in": "formData", + "type": "file" + } + ], + "consumes": [ + "multipart/form-data" + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_parameter__no_items.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_parameter__no_items.json new file mode 100644 index 00000000..47675fb7 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_parameter__no_items.json @@ -0,0 +1,27 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "post": { + "parameters": [ + { + "name": "people", + "in": "body", + "schema": { + "type": "array" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_body__no_items.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_body__no_items.json new file mode 100644 index 00000000..6484e8ee --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_body__no_items.json @@ -0,0 +1,21 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "200": { + "description": "hello world", + "schema": { + "type": "array" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_header__no_items.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_header__no_items.json new file mode 100644 index 00000000..a871732e --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__array_response_header__no_items.json @@ -0,0 +1,26 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "default": { + "description": "hello world", + "headers": { + "Content-Type": { + "type": "string" + }, + "Last-Modified": { + "type": "array" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__body_and_form_params.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__body_and_form_params.json new file mode 100644 index 00000000..ba1ae7f1 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__body_and_form_params.json @@ -0,0 +1,61 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "username", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + }, + "post": { + "consumes": ["multipart/form-data"], + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "foo", + "in": "body", + "schema": { + "type": "number" + } + }, + { + "name": "bar", + "in": "formData", + "type": "number" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_header_params.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_header_params.json new file mode 100644 index 00000000..1ed3eaf3 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_header_params.json @@ -0,0 +1,50 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "foo", + "in": "header", + "type": "string", + "required": false + }, + { + "name": "username", + "in": "header", + "type": "string" + }, + { + "name": "username", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "name": "foo", + "in": "header", + "type": "number", + "required": true + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_operation_id.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_operation_id.json new file mode 100644 index 00000000..830673cb --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_operation_id.json @@ -0,0 +1,301 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore 2.0", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "schemes": [ + "https", + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_params.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_params.json new file mode 100644 index 00000000..95e262ed --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_params.json @@ -0,0 +1,50 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "bar", + "in": "header", + "type": "string", + "required": false + }, + { + "name": "username", + "in": "header", + "type": "string" + }, + { + "name": "username", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "name": "username", + "in": "path", + "type": "number", + "required": true + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_placeholders.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_placeholders.json new file mode 100644 index 00000000..9cc4454e --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_path_placeholders.json @@ -0,0 +1,32 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/profile/{username}/image/{img_id}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "img_id", + "in": "path", + "required": true, + "type": "number" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_tag.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_tag.json new file mode 100644 index 00000000..925536ed --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__duplicate_tag.json @@ -0,0 +1,62 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets" + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user" + }, + { + "name": "user", + "description": "Duplicated tag" + } + ], + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "bar", + "in": "header", + "type": "string", + "required": false + }, + { + "name": "username", + "in": "header", + "type": "string" + }, + { + "name": "username", + "in": "body", + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_invalid_consumes.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_invalid_consumes.json new file mode 100644 index 00000000..abe32b75 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_invalid_consumes.json @@ -0,0 +1,35 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/profile/image": { + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "type": "string", + "required": true + }, + { + "name": "image", + "in": "formData", + "type": "file" + } + ], + "consumes": [ + "application/octet-stream", + "image/png" + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_no_consumes.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_no_consumes.json new file mode 100644 index 00000000..337f47f2 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__file_no_consumes.json @@ -0,0 +1,31 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/profile/image": { + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "type": "string", + "required": true + }, + { + "name": "image", + "in": "formData", + "type": "file" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_schema_type.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_schema_type.json new file mode 100644 index 00000000..9367a558 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_schema_type.json @@ -0,0 +1,39 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "200": { + "description": "hello world", + "headers": { + "Content-Type": { + "type": "string" + }, + "Last-Modified": { + "type": "array", + "items": { + "date": { + "type": "string" + } + } + } + }, + "schema": { + "type": "something", + "items": { + "user": { + "type": "string" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__parameter_in.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__parameter_in.json new file mode 100644 index 00000000..823e860a --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__parameter_in.json @@ -0,0 +1,120 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "invalid_in", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/authloginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__path.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__path.json new file mode 100644 index 00000000..4d9d4845 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__path.json @@ -0,0 +1,18 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "users": { + "get": { + "responses": { + "204": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__status_code.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__status_code.json new file mode 100644 index 00000000..4d4b9e71 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__invalid_value__status_code.json @@ -0,0 +1,18 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "8888": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_external_ref.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_external_ref.json new file mode 100644 index 00000000..600f5a2d --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_external_ref.json @@ -0,0 +1,120 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "tests\/fixtures\/SwaggerServiceTest\/documentation\/with_definitions.json#\/definitions\/authloginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__definition_type.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__definition_type.json new file mode 100644 index 00000000..79e73308 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__definition_type.json @@ -0,0 +1,119 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/authloginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__header_type.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__header_type.json new file mode 100644 index 00000000..d3ad3085 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__header_type.json @@ -0,0 +1,260 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore 2.0" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "https", + "http" + ], + "paths": { + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + }, + "headers": { + "X-Rate-Limit": { + "format": "int32", + "description": "calls per hour allowed by the user" + }, + "X-Expires-After": { + "format": "date-time", + "description": "date in UTC when token expires" + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__info_version.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__info_version.json new file mode 100644 index 00000000..eadee203 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__info_version.json @@ -0,0 +1,16 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + }, + "info": { + "description": "This is automatically collected documentation", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__items_type.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__items_type.json new file mode 100644 index 00000000..779820c1 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__items_type.json @@ -0,0 +1,258 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore 2.0" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "https", + "http" + ], + "paths": { + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__operation_responses.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__operation_responses.json new file mode 100644 index 00000000..ea9c8614 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__operation_responses.json @@ -0,0 +1,67 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/authloginObject" + } + } + ], + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__parameter_in.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__parameter_in.json new file mode 100644 index 00000000..43c0565e --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__parameter_in.json @@ -0,0 +1,119 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/authloginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__paths.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__paths.json new file mode 100644 index 00000000..45e9c178 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__paths.json @@ -0,0 +1,38 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__response_description.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__response_description.json new file mode 100644 index 00000000..bcaf81ba --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__response_description.json @@ -0,0 +1,119 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/authloginObject" + } + } + ], + "responses": { + "200": { + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__tag_name.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__tag_name.json new file mode 100644 index 00000000..9a194afc --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_field__tag_name.json @@ -0,0 +1,36 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore 2.0" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "https", + "http" + ], + "paths": { + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_local_ref.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_local_ref.json new file mode 100644 index 00000000..5f37a1f0 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_local_ref.json @@ -0,0 +1,120 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/loginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_path_parameter.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_path_parameter.json new file mode 100644 index 00000000..0062a2cf --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_path_parameter.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "bar", + "in": "header", + "type": "string", + "required": false + }, + { + "name": "username", + "in": "header", + "type": "string" + }, + { + "name": "username", + "in": "body", + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_ref_file.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_ref_file.json new file mode 100644 index 00000000..3faa2df9 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__missing_ref_file.json @@ -0,0 +1,120 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "invalid-filename.json" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__multiple_body_params.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__multiple_body_params.json new file mode 100644 index 00000000..891c1f79 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__multiple_body_params.json @@ -0,0 +1,75 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "bar", + "in": "header", + "type": "number", + "required": true + }, + { + "name": "foo", + "in": "body", + "required": true, + "schema": { + "type": "number" + } + }, + { + "name": "foo2", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + }, + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "bar", + "in": "header", + "type": "number", + "required": true + }, + { + "name": "bar", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__no_path_params.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__no_path_params.json new file mode 100644 index 00000000..165008a8 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__no_path_params.json @@ -0,0 +1,48 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/{foo}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "header", + "required": true, + "type": "number" + }, + { + "name": "foo", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + }, + "post": { + "parameters": [ + { + "name": "username", + "in": "header", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_param_no_placeholder.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_param_no_placeholder.json new file mode 100644 index 00000000..c3cc60d9 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_param_no_placeholder.json @@ -0,0 +1,53 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "foo", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + }, + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "foo", + "in": "path", + "type": "number" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_placeholder_no_param.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_placeholder_no_param.json new file mode 100644 index 00000000..599678fb --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__path_placeholder_no_param.json @@ -0,0 +1,48 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users/{username}/{foo}": { + "get": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "foo", + "in": "body", + "schema": { + "type": "number" + } + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + }, + "post": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "default": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_version.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_version.json new file mode 100644 index 00000000..773cb8ee --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_version.json @@ -0,0 +1,18 @@ +{ + "swagger": "1.0", + "info": { + "version": "1.0.0", + "title": "Invalid API" + }, + "paths": { + "/users": { + "get": { + "responses": { + "200": { + "description": "hello world" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/SwaggerServiceTest/documentation/with_definitions.json b/tests/fixtures/SwaggerServiceTest/documentation/with_definitions.json new file mode 100644 index 00000000..657c7c93 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/with_definitions.json @@ -0,0 +1,120 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/auth\/login": { + "post": { + "tags": [ + "auth" + ], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#\/definitions\/loginObject" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "schema": { + "example": { + "token": "some_token", + "user": { + "id": 2, + "email": "user@test.com", + "deleted_at": null, + "created_at": "2017-11-16 06:08:34", + "updated_at": "2018-01-01 00:00:00", + "role_id": 2, + "state": "confirmed", + "reset_password_hash": null, + "failed_auth_attempts": 0, + "last_auth_attempt": "2018-01-01 00:00:00", + "first_name": "user", + "last_name": null, + "set_password_hash_created_at": null, + "full_name": "user", + "new_email": "new_email_test2@test.com", + "is_email_verified": true, + "role": { + "id": 2, + "name": "client", + "created_at": null, + "updated_at": null, + "settable": true + } + }, + "ttl": 60, + "refresh_ttl": 20160 + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "example": { + "error": "You have entered an incorrect credentials." + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "example": { + "error": "The limit of failed authorization attempts has been reached. You can't login in next 50 minutes." + } + } + } + }, + "security": [], + "description": "", + "summary": "login" + } + } + }, + "definitions": { + "authloginObject123": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "2" + }, + "password": { + "type": "string", + "description": "" + } + }, + "required": [ + "email", + "password" + ], + "example": { + "email": "admin@test.com", + "password": "123" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Project Title", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_request_with_empty_data_and_info.json b/tests/fixtures/SwaggerServiceTest/tmp_data_request_with_empty_data_and_info.json index 9d54ef1e..0d55c48d 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_request_with_empty_data_and_info.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_request_with_empty_data_and_info.json @@ -4,5 +4,6 @@ "basePath": "\/", "schemes": [], "paths": [], - "definitions": [] + "definitions": [], + "info": [] } \ No newline at end of file diff --git a/tests/support/Traits/SwaggerServiceMockTrait.php b/tests/support/Traits/SwaggerServiceMockTrait.php index 7a764bec..d54df43c 100644 --- a/tests/support/Traits/SwaggerServiceMockTrait.php +++ b/tests/support/Traits/SwaggerServiceMockTrait.php @@ -48,4 +48,16 @@ protected function mockDriverGetPreparedAndSaveTpmData($getTmpData, $saveTmpData $this->app->instance($driverClass, $driver); } + + protected function mockDriverGetTpmData($tmpData, $driverClass = LocalDriver::class) + { + $driver = $this->mockClass($driverClass, ['getTmpData']); + + $driver + ->expects($this->exactly(1)) + ->method('getTmpData') + ->willReturn($tmpData); + + $this->app->instance($driverClass, $driver); + } }