ExtractorStrategiesInvocationTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <?php
  2. namespace Knuckles\Scribe\Tests\Unit;
  3. use Illuminate\Routing\Route;
  4. use Knuckles\Camel\Extraction\ExtractedEndpointData;
  5. use Knuckles\Scribe\Extracting\Extractor;
  6. use Knuckles\Scribe\Extracting\Strategies\Strategy;
  7. use Knuckles\Scribe\ScribeServiceProvider;
  8. use Knuckles\Scribe\Tests\BaseUnitTest;
  9. use Knuckles\Scribe\Tests\Fixtures\TestController;
  10. use Knuckles\Scribe\Tools\DocumentationConfig;
  11. class ExtractorStrategiesInvocationTest extends BaseUnitTest
  12. {
  13. protected ?Extractor $generator;
  14. protected function tearDown(): void
  15. {
  16. EmptyStrategy1::$called = false;
  17. EmptyStrategy2::$called = false;
  18. NotDummyMetadataStrategy::$called = false;
  19. parent::tearDown();
  20. }
  21. /** @test */
  22. public function only_specified_strategies_are_loaded()
  23. {
  24. $config = [
  25. 'strategies' => [
  26. 'metadata' => [NotDummyMetadataStrategy::class],
  27. 'bodyParameters' => [
  28. EmptyStrategy1::class,
  29. ],
  30. 'responses' => [], // Making this empty so the Laravel-dependent strategies are not called
  31. ],
  32. ];
  33. $this->processRoute($config);
  34. $this->assertTrue(EmptyStrategy1::$called);
  35. $this->assertTrue(NotDummyMetadataStrategy::$called);
  36. $this->assertFalse(EmptyStrategy2::$called);
  37. }
  38. /** @test */
  39. public function supports_overrides_tuples()
  40. {
  41. $config = [
  42. 'strategies' => [
  43. 'headers' => [
  44. DummyHeaderStrategy::class,
  45. [
  46. 'overrides',
  47. ['Content-Type' => 'application/xml'],
  48. ]
  49. ],
  50. 'bodyParameters' => [],
  51. 'responses' => [], // Making this empty so the Laravel-dependent strategies are not called
  52. ],
  53. ];
  54. $endpointData = $this->processRoute($config);
  55. $this->assertEquals([
  56. 'Accept' => 'application/form-data',
  57. 'Content-Type' => 'application/xml',
  58. ], $endpointData->headers);
  59. }
  60. /** @test */
  61. public function supports_strategy_settings_tuples()
  62. {
  63. $config = [
  64. 'strategies' => [
  65. 'headers' => [
  66. [
  67. DummyHeaderStrategy::class,
  68. ['use_this_content_type' => 'text/plain'],
  69. ]
  70. ],
  71. 'bodyParameters' => [],
  72. 'responses' => [], // Making this empty so the Laravel-dependent strategies are not called
  73. ],
  74. ];
  75. $endpointData = $this->processRoute($config);
  76. $this->assertEquals([
  77. 'Accept' => 'application/form-data',
  78. 'Content-Type' => 'text/plain',
  79. ], $endpointData->headers);
  80. }
  81. /** @test */
  82. public function respects_strategy_s_only_setting()
  83. {
  84. $config = [
  85. 'strategies' => [
  86. 'bodyParameters' => [
  87. [EmptyStrategy1::class, ['only' => 'GET /test']]
  88. ],
  89. 'responses' => [],
  90. ],
  91. ];
  92. $this->processRoute($config);
  93. $this->assertFalse(EmptyStrategy1::$called);
  94. $config['strategies']['bodyParameters'][0] =
  95. [EmptyStrategy1::class, ['only' => ['GET api/*']]];
  96. $this->processRoute($config);
  97. $this->assertTrue(EmptyStrategy1::$called);
  98. }
  99. /** @test */
  100. public function respects_strategy_s_except_setting()
  101. {
  102. $config = [
  103. 'strategies' => [
  104. 'bodyParameters' => [
  105. [EmptyStrategy1::class, ['except' => 'GET /api*']]
  106. ],
  107. 'responses' => [],
  108. ],
  109. ];
  110. $this->processRoute($config);
  111. $this->assertFalse(EmptyStrategy1::$called);
  112. $config['strategies']['bodyParameters'][0] =
  113. [EmptyStrategy1::class, ['except' => ['*']]];
  114. $this->processRoute($config);
  115. $this->assertFalse(EmptyStrategy1::$called);
  116. $config['strategies']['bodyParameters'][0] =
  117. [EmptyStrategy1::class, ['except' => []]];
  118. $this->processRoute($config);
  119. $this->assertTrue(EmptyStrategy1::$called);
  120. }
  121. /** @test */
  122. public function responses_from_different_strategies_get_added()
  123. {
  124. $config = [
  125. 'strategies' => [
  126. 'bodyParameters' => [],
  127. 'responses' => [DummyResponseStrategy200::class, DummyResponseStrategy400::class],
  128. ],
  129. ];
  130. $parsed = $this->processRoute($config);
  131. $this->assertCount(2, $parsed->responses->toArray());
  132. $responses = $parsed->responses->toArray();
  133. $first = array_shift($responses);
  134. $this->assertTrue(is_array($first));
  135. $this->assertEquals(200, $first['status']);
  136. $this->assertEquals('dummy', $first['content']);
  137. $second = array_shift($responses);
  138. $this->assertTrue(is_array($second));
  139. $this->assertEquals(400, $second['status']);
  140. $this->assertEquals('dummy2', $second['content']);
  141. }
  142. /**
  143. * @test
  144. * This is a generalized test, as opposed to the one above for responses only
  145. */
  146. public function combines_results_from_different_strategies_in_same_stage()
  147. {
  148. $config = [
  149. 'strategies' => [
  150. 'metadata' => [PartialDummyMetadataStrategy1::class, PartialDummyMetadataStrategy2::class],
  151. 'bodyParameters' => [],
  152. 'responses' => [],
  153. ],
  154. ];
  155. $parsed = $this->processRoute($config);
  156. $expectedMetadata = [
  157. 'groupName' => 'dummy',
  158. 'groupDescription' => 'dummy',
  159. 'title' => 'dummy',
  160. 'description' => 'dummy',
  161. 'authenticated' => false,
  162. ];
  163. $this->assertArraySubset($expectedMetadata, $parsed->metadata->toArray());
  164. }
  165. /** @test */
  166. public function missing_metadata_is_filled_in()
  167. {
  168. $config = [
  169. 'strategies' => [
  170. 'metadata' => [PartialDummyMetadataStrategy2::class],
  171. 'bodyParameters' => [],
  172. 'responses' => [],
  173. ],
  174. ];
  175. $parsed = $this->processRoute($config);
  176. $expectedMetadata = [
  177. 'groupName' => '',
  178. 'groupDescription' => 'dummy',
  179. 'title' => '',
  180. 'description' => 'dummy',
  181. 'authenticated' => false,
  182. ];
  183. $this->assertArraySubset($expectedMetadata, $parsed->metadata->toArray());
  184. }
  185. public function responsesToSort(): array
  186. {
  187. return [
  188. '400, 200, 201' => [[DummyResponseStrategy400::class, DummyResponseStrategy200::class, DummyResponseStrategy201::class]],
  189. '201, 400, 200' => [[DummyResponseStrategy201::class, DummyResponseStrategy400::class, DummyResponseStrategy200::class]],
  190. '400, 201, 200' => [[DummyResponseStrategy400::class, DummyResponseStrategy201::class, DummyResponseStrategy200::class]],
  191. ];
  192. }
  193. /**
  194. * @test
  195. * @dataProvider responsesToSort
  196. */
  197. public function sort_responses_by_status_code(array $responses)
  198. {
  199. $config = [
  200. 'strategies' => [
  201. 'bodyParameters' => [],
  202. 'responses' => $responses,
  203. ],
  204. ];
  205. $parsed = $this->processRoute($config);
  206. [$first, $second, $third] = $parsed->responses;
  207. self::assertEquals(200, $first->status);
  208. self::assertEquals(201, $second->status);
  209. self::assertEquals(400, $third->status);
  210. }
  211. /** @test */
  212. public function overwrites_metadata_from_previous_strategies_in_same_stage()
  213. {
  214. $config = [
  215. 'strategies' => [
  216. 'metadata' => [NotDummyMetadataStrategy::class, PartialDummyMetadataStrategy1::class],
  217. 'bodyParameters' => [],
  218. 'responses' => [],
  219. ],
  220. ];
  221. $parsed = $this->processRoute($config);
  222. $expectedMetadata = [
  223. 'groupName' => 'dummy',
  224. 'groupDescription' => 'notdummy',
  225. 'title' => 'dummy',
  226. 'description' => 'dummy',
  227. 'authenticated' => false,
  228. ];
  229. $this->assertArraySubset($expectedMetadata, $parsed->metadata->toArray());
  230. }
  231. protected function processRoute(
  232. array $config, $routeMethod = "GET", $routePath = "/api/test", $routeName = "dummy"
  233. ): ExtractedEndpointData
  234. {
  235. $route = $this->createRoute($routeMethod, $routePath, $routeName);
  236. $extractor = new Extractor(new DocumentationConfig($config));
  237. return $extractor->processRoute($route);
  238. }
  239. public function createRoute(string $httpMethod, string $path, string $controllerMethod, $class = TestController::class)
  240. {
  241. return new Route([$httpMethod], $path, ['uses' => [$class, $controllerMethod]]);
  242. }
  243. }
  244. class EmptyStrategy1 extends Strategy
  245. {
  246. public static $called = false;
  247. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  248. {
  249. static::$called = true;
  250. return [];
  251. }
  252. }
  253. class EmptyStrategy2 extends Strategy
  254. {
  255. public static $called = false;
  256. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  257. {
  258. static::$called = true;
  259. return [];
  260. }
  261. }
  262. class DummyHeaderStrategy extends Strategy
  263. {
  264. public function __invoke(ExtractedEndpointData $endpointData, array $settings = []): ?array
  265. {
  266. return [
  267. 'Accept' => 'application/form-data',
  268. 'Content-Type' => $settings['use_this_content_type'] ?? 'application/form-data',
  269. ];
  270. }
  271. }
  272. class NotDummyMetadataStrategy extends Strategy
  273. {
  274. public static $called = false;
  275. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  276. {
  277. static::$called = true;
  278. return [
  279. 'groupName' => 'notdummy',
  280. 'groupDescription' => 'notdummy',
  281. 'title' => 'notdummy',
  282. 'description' => 'notdummy',
  283. 'authenticated' => true,
  284. ];
  285. }
  286. }
  287. class PartialDummyMetadataStrategy1 extends Strategy
  288. {
  289. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  290. {
  291. return [
  292. 'groupName' => 'dummy',
  293. 'title' => 'dummy',
  294. 'description' => 'dummy',
  295. 'authenticated' => false,
  296. ];
  297. }
  298. }
  299. class PartialDummyMetadataStrategy2 extends Strategy
  300. {
  301. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  302. {
  303. return [
  304. 'description' => 'dummy',
  305. 'groupDescription' => 'dummy',
  306. ];
  307. }
  308. }
  309. class DummyResponseStrategy200 extends Strategy
  310. {
  311. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  312. {
  313. return [['status' => 200, 'content' => 'dummy']];
  314. }
  315. }
  316. class DummyResponseStrategy201 extends Strategy
  317. {
  318. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  319. {
  320. return [['status' => 201, 'content' => 'dummy2']];
  321. }
  322. }
  323. class DummyResponseStrategy400 extends Strategy
  324. {
  325. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
  326. {
  327. return [['status' => 400, 'content' => 'dummy2']];
  328. }
  329. }