GeneratorTestCase.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. namespace Knuckles\Scribe\Tests\Unit;
  3. use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
  4. use Illuminate\Support\Arr;
  5. use Knuckles\Scribe\ScribeServiceProvider;
  6. use Knuckles\Scribe\Extracting\Generator;
  7. use Knuckles\Scribe\Tests\Fixtures\TestController;
  8. use Knuckles\Scribe\Tools\DocumentationConfig;
  9. use Knuckles\Scribe\Tools\Flags;
  10. use Orchestra\Testbench\TestCase;
  11. abstract class GeneratorTestCase extends TestCase
  12. {
  13. use ArraySubsetAsserts;
  14. /**
  15. * @var \Knuckles\Scribe\Extracting\Generator
  16. */
  17. protected $generator;
  18. protected $config = [
  19. 'strategies' => [
  20. 'metadata' => [
  21. \Knuckles\Scribe\Extracting\Strategies\Metadata\GetFromDocBlocks::class,
  22. ],
  23. 'urlParameters' => [
  24. \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromUrlParamTag::class,
  25. ],
  26. 'queryParameters' => [
  27. \Knuckles\Scribe\Extracting\Strategies\QueryParameters\GetFromQueryParamTag::class,
  28. ],
  29. 'headers' => [
  30. \Knuckles\Scribe\Extracting\Strategies\Headers\GetFromRouteRules::class,
  31. \Knuckles\Scribe\Extracting\Strategies\Headers\GetFromHeaderTag::class,
  32. ],
  33. 'bodyParameters' => [
  34. \Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
  35. ],
  36. 'responses' => [
  37. \Knuckles\Scribe\Extracting\Strategies\Responses\UseTransformerTags::class,
  38. \Knuckles\Scribe\Extracting\Strategies\Responses\UseResponseTag::class,
  39. \Knuckles\Scribe\Extracting\Strategies\Responses\UseResponseFileTag::class,
  40. \Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags::class,
  41. \Knuckles\Scribe\Extracting\Strategies\Responses\ResponseCalls::class,
  42. ],
  43. 'responseFields' => [
  44. \Knuckles\Scribe\Extracting\Strategies\ResponseFields\GetFromResponseFieldTag::class,
  45. ],
  46. ],
  47. 'default_group' => 'general',
  48. ];
  49. public static $globalValue = null;
  50. protected function getPackageProviders($app)
  51. {
  52. $providers = [
  53. ScribeServiceProvider::class,
  54. ];
  55. if (class_exists(\Dingo\Api\Provider\LaravelServiceProvider::class)) {
  56. $providers[] = \Dingo\Api\Provider\LaravelServiceProvider::class;
  57. }
  58. return $providers;
  59. }
  60. /**
  61. * Setup the test environment.
  62. */
  63. public function setUp(): void
  64. {
  65. parent::setUp();
  66. $this->generator = new Generator(new DocumentationConfig($this->config));
  67. }
  68. /** @test */
  69. public function clean_can_properly_parse_array_keys()
  70. {
  71. $parameters = [
  72. 'object' => [
  73. 'type' => 'object',
  74. 'value' => [],
  75. ],
  76. 'object.key1' => [
  77. 'type' => 'string',
  78. 'value' => '43',
  79. ],
  80. 'object.key2' => [
  81. 'type' => 'integer',
  82. 'value' => 77,
  83. ],
  84. 'object.key3' => [
  85. 'type' => 'object',
  86. 'value'=> [],
  87. ],
  88. 'object.key3.key1' => [
  89. 'type' => 'string',
  90. 'value' => 'hoho',
  91. ],
  92. 'list' => [
  93. 'type' => 'integer[]',
  94. 'value' => [4],
  95. ],
  96. 'list_of_objects' => [
  97. 'type' => 'object[]',
  98. 'value' => [[]],
  99. ],
  100. 'list_of_objects[].key1' => [
  101. 'type' => 'string',
  102. 'value' => 'John',
  103. ],
  104. 'list_of_objects[].key2' => [
  105. 'type' => 'boolean',
  106. 'value' => false,
  107. ],
  108. ];
  109. $cleanBodyParameters = Generator::cleanParams($parameters);
  110. $this->assertEquals([
  111. 'object' => [
  112. 'key1' => '43',
  113. 'key2' => 77,
  114. 'key3' => [
  115. 'key1' => 'hoho'
  116. ]
  117. ],
  118. 'list' => [4],
  119. 'list_of_objects' => [
  120. [
  121. 'key1' => 'John',
  122. 'key2' => false,
  123. ],
  124. ],
  125. ], $cleanBodyParameters);
  126. }
  127. /** @test */
  128. public function does_not_generate_values_for_excluded_params_and_excludes_them_from_clean_params()
  129. {
  130. $route = $this->createRoute('GET', '/api/test', 'withExcludedExamples');
  131. $parsed = $this->generator->processRoute($route);
  132. $cleanBodyParameters = $parsed['cleanBodyParameters'];
  133. $cleanQueryParameters = $parsed['cleanQueryParameters'];
  134. $bodyParameters = $parsed['bodyParameters'];
  135. $queryParameters = $parsed['queryParameters'];
  136. $this->assertArrayHasKey('included', $cleanBodyParameters);
  137. $this->assertArrayNotHasKey('excluded_body_param', $cleanBodyParameters);
  138. $this->assertEmpty($cleanQueryParameters);
  139. $this->assertArraySubset([
  140. 'included' => [
  141. 'required' => true,
  142. 'type' => 'string',
  143. 'description' => 'Exists in examples.',
  144. ],
  145. 'excluded_body_param' => [
  146. 'type' => 'integer',
  147. 'description' => 'Does not exist in examples.',
  148. ],
  149. ], $bodyParameters);
  150. $this->assertArraySubset([
  151. 'excluded_query_param' => [
  152. 'description' => 'Does not exist in examples.',
  153. ],
  154. ], $queryParameters);
  155. }
  156. /** @test */
  157. public function can_parse_route_methods()
  158. {
  159. $route = $this->createRoute('GET', '/get', 'withEndpointDescription');
  160. $parsed = $this->generator->processRoute($route);
  161. $this->assertEquals(['GET'], $parsed['methods']);
  162. $route = $this->createRoute('POST', '/post', 'withEndpointDescription');
  163. $parsed = $this->generator->processRoute($route);
  164. $this->assertEquals(['POST'], $parsed['methods']);
  165. $route = $this->createRoute('PUT', '/put', 'withEndpointDescription');
  166. $parsed = $this->generator->processRoute($route);
  167. $this->assertEquals(['PUT'], $parsed['methods']);
  168. $route = $this->createRoute('DELETE', '/delete', 'withEndpointDescription');
  169. $parsed = $this->generator->processRoute($route);
  170. $this->assertEquals(['DELETE'], $parsed['methods']);
  171. }
  172. /** @test */
  173. public function can_override_url_path_parameters_with_urlparam_annotation()
  174. {
  175. $route = $this->createRoute('POST', '/echoesUrlParameters/{param}', 'echoesUrlParameters', true);
  176. $rules = [
  177. 'response_calls' => [
  178. 'methods' => ['*'],
  179. ],
  180. ];
  181. $parsed = $this->generator->processRoute($route, $rules);
  182. $response = json_decode(Arr::first($parsed['responses'])['content'], true);
  183. $this->assertEquals(4, $response['param']);
  184. }
  185. /** @test */
  186. public function ignores_or_inserts_optional_url_path_parameters_according_to_annotations()
  187. {
  188. $route = $this->createRoute('POST', '/echoesUrlParameters/{param}/{param2?}/{param3}/{param4?}', 'echoesUrlParameters', true);
  189. $rules = [
  190. 'response_calls' => [
  191. 'methods' => ['*'],
  192. ],
  193. ];
  194. $parsed = $this->generator->processRoute($route, $rules);
  195. $response = json_decode(Arr::first($parsed['responses'])['content'], true);
  196. $this->assertEquals(4, $response['param']);
  197. $this->assertNotNull($response['param2']);
  198. $this->assertEquals(1, $response['param3']);
  199. $this->assertNull($response['param4']);
  200. }
  201. /**
  202. * @test
  203. * @dataProvider authRules
  204. */
  205. public function adds_appropriate_field_based_on_configured_auth_type($config, $expected)
  206. {
  207. $route = $this->createRoute('POST', '/withAuthenticatedTag', 'withAuthenticatedTag', true);
  208. $generator = new Generator(new DocumentationConfig($config));
  209. $parsed = $generator->processRoute($route, []);
  210. $this->assertNotNull($parsed[$expected['where']][$expected['name']]);
  211. $this->assertStringStartsWith("{$expected['where']}.{$expected['name']}.", $parsed['auth']);
  212. }
  213. /** @test */
  214. public function generates_consistent_examples_when_faker_seed_is_set()
  215. {
  216. $route = $this->createRoute('GET', '/withBodyParameters', 'withBodyParameters');
  217. $paramName = 'room_id';
  218. $results = [];
  219. $results[$this->generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  220. $results[$this->generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  221. $results[$this->generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  222. $results[$this->generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  223. $results[$this->generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  224. // Examples should have different values
  225. $this->assertNotEquals(count($results), 1);
  226. $generator = new Generator(new DocumentationConfig($this->config + ['faker_seed' => 12345]));
  227. $results = [];
  228. $results[$generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  229. $results[$generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  230. $results[$generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  231. $results[$generator->processRoute($route)['cleanBodyParameters'][$paramName]] = true;
  232. // Examples should have same values
  233. $this->assertEquals(count($results), 1);
  234. }
  235. /** @test */
  236. public function can_use_arrays_in_routes_uses()
  237. {
  238. $route = $this->createRouteUsesArray('GET', '/api/array/test', 'withEndpointDescription');
  239. $parsed = $this->generator->processRoute($route);
  240. $this->assertSame('Example title.', $parsed['metadata']['title']);
  241. $this->assertSame("This will be the long description.\nIt can also be multiple lines long.", $parsed['metadata']['description']);
  242. }
  243. /** @test */
  244. public function can_use_closure_in_routes_uses()
  245. {
  246. /**
  247. * A short title.
  248. * A longer description.
  249. * Can be multiple lines.
  250. *
  251. * @queryParam location_id required The id of the location.
  252. * @bodyParam name required Name of the location
  253. */
  254. $handler = function () {
  255. return 'hi';
  256. };
  257. $route = $this->createRouteUsesCallable('GET', '/api/closure/test', $handler);
  258. $parsed = $this->generator->processRoute($route);
  259. $this->assertSame('A short title.', $parsed['metadata']['title']);
  260. $this->assertSame("A longer description.\nCan be multiple lines.", $parsed['metadata']['description']);
  261. $this->assertCount(1, $parsed['queryParameters']);
  262. $this->assertCount(1, $parsed['bodyParameters']);
  263. $this->assertSame('The id of the location.', $parsed['queryParameters']['location_id']['description']);
  264. $this->assertSame('Name of the location', $parsed['bodyParameters']['name']['description']);
  265. }
  266. abstract public function createRoute(string $httpMethod, string $path, string $controllerMethod, $register = false, $class = TestController::class);
  267. abstract public function createRouteUsesArray(string $httpMethod, string $path, string $controllerMethod, $register = false, $class = TestController::class);
  268. abstract public function createRouteUsesCallable(string $httpMethod, string $path, callable $handler, $register = false);
  269. public function authRules()
  270. {
  271. return [
  272. [
  273. array_merge($this->config, [
  274. 'auth' => [
  275. 'enabled' => true,
  276. 'in' => 'bearer',
  277. 'name' => 'dfadb',
  278. ]
  279. ]),
  280. [
  281. 'name' => 'Authorization',
  282. 'where' => 'headers',
  283. ]
  284. ],
  285. [
  286. array_merge($this->config, [
  287. 'auth' => [
  288. 'enabled' => true,
  289. 'in' => 'basic',
  290. 'name' => 'efwr',
  291. ]
  292. ]),
  293. [
  294. 'name' => 'Authorization',
  295. 'where' => 'headers',
  296. ]
  297. ],
  298. [
  299. array_merge($this->config, [
  300. 'auth' => [
  301. 'enabled' => true,
  302. 'in' => 'header',
  303. 'name' => 'Api-Key',
  304. ]
  305. ]),
  306. [
  307. 'name' => 'Api-Key',
  308. 'where' => 'headers',
  309. ]
  310. ],
  311. [
  312. array_merge($this->config, [
  313. 'auth' => [
  314. 'enabled' => true,
  315. 'in' => 'query',
  316. 'name' => 'apiKey',
  317. ]
  318. ]),
  319. [
  320. 'name' => 'apiKey',
  321. 'where' => 'cleanQueryParameters',
  322. ]
  323. ],
  324. [
  325. array_merge($this->config, [
  326. 'auth' => [
  327. 'enabled' => true,
  328. 'in' => 'body',
  329. 'name' => 'access_token',
  330. ]
  331. ]),
  332. [
  333. 'name' => 'access_token',
  334. 'where' => 'cleanBodyParameters',
  335. ]
  336. ],
  337. ];
  338. }
  339. }