GenerateDocumentationTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. namespace Mpociot\ApiDoc\Tests;
  3. use RecursiveIteratorIterator;
  4. use RecursiveDirectoryIterator;
  5. use Orchestra\Testbench\TestCase;
  6. use Illuminate\Contracts\Console\Kernel;
  7. use Dingo\Api\Provider\LaravelServiceProvider;
  8. use Mpociot\ApiDoc\Generators\LaravelGenerator;
  9. use Mpociot\ApiDoc\Tests\Fixtures\TestController;
  10. use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider;
  11. use Illuminate\Support\Facades\Route as RouteFacade;
  12. use Mpociot\ApiDoc\Tests\Fixtures\DingoTestController;
  13. use Mpociot\ApiDoc\Tests\Fixtures\TestResourceController;
  14. class GenerateDocumentationTest extends TestCase
  15. {
  16. /**
  17. * @var \Mpociot\ApiDoc\AbstractGenerator
  18. */
  19. protected $generator;
  20. /**
  21. * Setup the test environment.
  22. */
  23. public function setUp()
  24. {
  25. parent::setUp();
  26. $this->generator = new LaravelGenerator();
  27. }
  28. public function tearDown()
  29. {
  30. // delete the generated docs - compatible cross-platform
  31. $dir = __DIR__.'/../public/docs';/*
  32. if (is_dir($dir)) {
  33. $files = new RecursiveIteratorIterator(
  34. new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
  35. RecursiveIteratorIterator::CHILD_FIRST
  36. );
  37. foreach ($files as $fileinfo) {
  38. $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
  39. $todo($fileinfo->getRealPath());
  40. }
  41. rmdir($dir);
  42. }*/
  43. }
  44. /**
  45. * @param \Illuminate\Foundation\Application $app
  46. *
  47. * @return array
  48. */
  49. protected function getPackageProviders($app)
  50. {
  51. return [
  52. LaravelServiceProvider::class,
  53. ApiDocGeneratorServiceProvider::class,
  54. ];
  55. }
  56. public function testConsoleCommandNeedsPrefixesOrDomainsOrRoutes()
  57. {
  58. $output = $this->artisan('apidoc:generate');
  59. $this->assertEquals('You must provide either a route prefix, a route domain, a route or a middleware to generate the documentation.'.PHP_EOL, $output);
  60. }
  61. public function testConsoleCommandDoesNotWorkWithClosure()
  62. {
  63. RouteFacade::get('/api/closure', function () {
  64. return 'foo';
  65. });
  66. RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription');
  67. $output = $this->artisan('apidoc:generate', [
  68. '--routePrefix' => 'api/*',
  69. ]);
  70. $this->assertContains('Skipping route: [GET] api/closure', $output);
  71. $this->assertContains('Processed route: [GET] api/test', $output);
  72. }
  73. public function testConsoleCommandDoesNotWorkWithClosureUsingDingo()
  74. {
  75. $api = app('Dingo\Api\Routing\Router');
  76. $api->version('v1', function ($api) {
  77. $api->get('v1/closure', function () {
  78. return 'foo';
  79. });
  80. $api->get('v1/test', DingoTestController::class.'@parseMethodDescription');
  81. $output = $this->artisan('apidoc:generate', [
  82. '--router' => 'dingo',
  83. '--routePrefix' => 'v1/*',
  84. ]);
  85. $this->assertContains('Skipping route: [GET] closure', $output);
  86. $this->assertContains('Processed route: [GET] test', $output);
  87. });
  88. }
  89. public function testCanSkipSingleRoutesCommandDoesNotWorkWithClosure()
  90. {
  91. RouteFacade::get('/api/skip', TestController::class.'@skip');
  92. RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription');
  93. $output = $this->artisan('apidoc:generate', [
  94. '--routePrefix' => 'api/*',
  95. ]);
  96. $this->assertContains('Skipping route: [GET] api/skip', $output);
  97. $this->assertContains('Processed route: [GET] api/test', $output);
  98. }
  99. public function testCanParseResourceRoutes()
  100. {
  101. RouteFacade::resource('/api/user', TestResourceController::class);
  102. $output = $this->artisan('apidoc:generate', [
  103. '--routePrefix' => 'api/*',
  104. ]);
  105. $fixtureMarkdown = __DIR__.'/Fixtures/resource_index.md';
  106. $generatedMarkdown = __DIR__.'/../public/docs/source/index.md';
  107. $this->assertFilesHaveSameContent($fixtureMarkdown, $generatedMarkdown);
  108. }
  109. public function testCanParsePartialResourceRoutes()
  110. {
  111. RouteFacade::resource('/api/user', TestResourceController::class, [
  112. 'only' => [
  113. 'index', 'create',
  114. ],
  115. ]);
  116. $output = $this->artisan('apidoc:generate', [
  117. '--routePrefix' => 'api/*',
  118. ]);
  119. $fixtureMarkdown = __DIR__.'/Fixtures/partial_resource_index.md';
  120. $generatedMarkdown = __DIR__.'/../public/docs/source/index.md';
  121. $this->assertFilesHaveSameContent($fixtureMarkdown, $generatedMarkdown);
  122. RouteFacade::apiResource('/api/user', TestResourceController::class, [
  123. 'only' => [
  124. 'index', 'create',
  125. ],
  126. ]);
  127. $output = $this->artisan('apidoc:generate', [
  128. '--routePrefix' => 'api/*',
  129. ]);
  130. $fixtureMarkdown = __DIR__.'/Fixtures/partial_resource_index.md';
  131. $generatedMarkdown = __DIR__.'/../public/docs/source/index.md';
  132. $this->assertFilesHaveSameContent($fixtureMarkdown, $generatedMarkdown);
  133. }
  134. public function testGeneratedMarkdownFileIsCorrect()
  135. {
  136. RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription');
  137. RouteFacade::get('/api/fetch', TestController::class.'@fetchRouteResponse');
  138. $output = $this->artisan('apidoc:generate', [
  139. '--routePrefix' => 'api/*',
  140. ]);
  141. $generatedMarkdown = __DIR__.'/../public/docs/source/index.md';
  142. $compareMarkdown = __DIR__.'/../public/docs/source/.compare.md';
  143. $fixtureMarkdown = __DIR__.'/Fixtures/index.md';
  144. $this->assertFilesHaveSameContent($fixtureMarkdown, $generatedMarkdown);
  145. $this->assertFilesHaveSameContent($fixtureMarkdown, $compareMarkdown);
  146. }
  147. public function testCanPrependAndAppendDataToGeneratedMarkdown()
  148. {
  149. RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription');
  150. RouteFacade::get('/api/fetch', TestController::class.'@fetchRouteResponse');
  151. $this->artisan('apidoc:generate', [
  152. '--routePrefix' => 'api/*',
  153. ]);
  154. $prependMarkdown = __DIR__.'/Fixtures/prepend.md';
  155. $appendMarkdown = __DIR__.'/Fixtures/append.md';
  156. copy($prependMarkdown, __DIR__.'/../public/docs/source/prepend.md');
  157. copy($appendMarkdown, __DIR__.'/../public/docs/source/append.md');
  158. $this->artisan('apidoc:generate', [
  159. '--routePrefix' => 'api/*',
  160. ]);
  161. $generatedMarkdown = __DIR__.'/../public/docs/source/index.md';
  162. $this->assertContainsRaw($this->getFileContents($prependMarkdown), $this->getFileContents($generatedMarkdown));
  163. $this->assertContainsRaw($this->getFileContents($appendMarkdown), $this->getFileContents($generatedMarkdown));
  164. }
  165. public function testAddsBindingsToGetRouteRules()
  166. {
  167. RouteFacade::get('/api/test/{foo}', TestController::class.'@addRouteBindingsToRequestClass');
  168. $this->artisan('apidoc:generate', [
  169. '--routePrefix' => 'api/*',
  170. '--bindings' => 'foo,bar',
  171. ]);
  172. $generatedMarkdown = file_get_contents(__DIR__.'/../public/docs/source/index.md');
  173. $this->assertContains('Not in: `bar`', $generatedMarkdown);
  174. }
  175. public function testGeneratedPostmanCollectionFileIsCorrect()
  176. {
  177. RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription');
  178. RouteFacade::post('/api/fetch', TestController::class.'@fetchRouteResponse');
  179. $output = $this->artisan('apidoc:generate', [
  180. '--routePrefix' => 'api/*',
  181. ]);
  182. $generatedCollection = json_decode(file_get_contents(__DIR__.'/../public/docs/collection.json'));
  183. $generatedCollection->info->_postman_id = '';
  184. $fixtureCollection = json_decode(file_get_contents(__DIR__.'/Fixtures/collection.json'));
  185. $this->assertEquals($generatedCollection, $fixtureCollection);
  186. }
  187. public function testCanAppendCustomHttpHeaders()
  188. {
  189. RouteFacade::get('/api/headers', TestController::class.'@checkCustomHeaders');
  190. $output = $this->artisan('apidoc:generate', [
  191. '--routePrefix' => 'api/*',
  192. '--header' => [
  193. 'Authorization: customAuthToken',
  194. 'X-Custom-Header: foobar',
  195. ],
  196. ]);
  197. $generatedMarkdown = $this->getFileContents(__DIR__.'/../public/docs/source/index.md');
  198. $this->assertContainsRaw('"authorization": [
  199. "customAuthToken"
  200. ],
  201. "x-custom-header": [
  202. "foobar"
  203. ]', $generatedMarkdown);
  204. }
  205. public function testGeneratesUTF8Responses()
  206. {
  207. RouteFacade::get('/api/utf8', TestController::class.'@utf8');
  208. $output = $this->artisan('apidoc:generate', [
  209. '--routePrefix' => 'api/*',
  210. ]);
  211. $generatedMarkdown = file_get_contents(__DIR__.'/../public/docs/source/index.md');
  212. $this->assertContains('Лорем ипсум долор сит амет', $generatedMarkdown);
  213. }
  214. /**
  215. * @param string $command
  216. * @param array $parameters
  217. *
  218. * @return mixed
  219. */
  220. public function artisan($command, $parameters = [])
  221. {
  222. $this->app[Kernel::class]->call($command, $parameters);
  223. return $this->app[Kernel::class]->output();
  224. }
  225. private function assertFilesHaveSameContent($pathToExpected, $pathToActual)
  226. {
  227. $actual = $this->getFileContents($pathToActual);
  228. $expected = $this->getFileContents($pathToExpected);
  229. $this->assertSame($expected, $actual);
  230. }
  231. /**
  232. * Get the contents of a file in a cross-platform-compatible way.
  233. *
  234. * @param $path
  235. *
  236. * @return string
  237. */
  238. private function getFileContents($path)
  239. {
  240. return str_replace("\r\n", "\n", file_get_contents($path));
  241. }
  242. /**
  243. * Assert that a string contains another string, ignoring all whitespace.
  244. *
  245. * @param $needle
  246. * @param $haystack
  247. */
  248. private function assertContainsRaw($needle, $haystack)
  249. {
  250. $haystack = preg_replace('/\s/', '', $haystack);
  251. $needle = preg_replace('/\s/', '', $needle);
  252. $this->assertContains($needle, $haystack);
  253. }
  254. }