ExtractorPluginSystemTest.php 10 KB

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