UseResponseAttributesTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. namespace Knuckles\Scribe\Tests\Strategies\Responses;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Foundation\Application;
  5. use Illuminate\Routing\Route;
  6. use Illuminate\Support\Facades\Schema;
  7. use Knuckles\Camel\Extraction\ExtractedEndpointData;
  8. use Knuckles\Scribe\Attributes\Response;
  9. use Knuckles\Scribe\Attributes\ResponseFromApiResource;
  10. use Knuckles\Scribe\Attributes\ResponseFromFile;
  11. use Knuckles\Scribe\Attributes\ResponseFromTransformer;
  12. use Knuckles\Scribe\Extracting\Strategies\Responses\UseResponseAttributes;
  13. use Knuckles\Scribe\ScribeServiceProvider;
  14. use Knuckles\Scribe\Tests\BaseLaravelTest;
  15. use Knuckles\Scribe\Tests\Fixtures\TestModel;
  16. use Knuckles\Scribe\Tests\Fixtures\TestPet;
  17. use Knuckles\Scribe\Tests\Fixtures\TestTransformer;
  18. use Knuckles\Scribe\Tests\Fixtures\TestUser;
  19. use Knuckles\Scribe\Tests\Fixtures\TestUserApiResource;
  20. use Knuckles\Scribe\Tools\DocumentationConfig;
  21. use Knuckles\Scribe\Tools\Utils;
  22. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  23. use ReflectionClass;
  24. class UseResponseAttributesTest extends BaseLaravelTest
  25. {
  26. protected function getPackageProviders($app)
  27. {
  28. $providers = parent::getPackageProviders($app);
  29. if (class_exists(\Illuminate\Database\Eloquent\LegacyFactoryServiceProvider::class)) {
  30. $providers[] = \Illuminate\Database\Eloquent\LegacyFactoryServiceProvider ::class;
  31. }
  32. return $providers;
  33. }
  34. public function setUp(): void
  35. {
  36. parent::setUp();
  37. $this->setConfig(['database_connections_to_transact' => []]);
  38. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  39. $factory->define(TestUser::class, function () {
  40. return [
  41. 'id' => 4,
  42. 'first_name' => 'Tested',
  43. 'last_name' => 'Again',
  44. 'email' => 'a@b.com',
  45. ];
  46. });
  47. $factory->state(TestUser::class, 'state1', ["state1" => true]);
  48. $factory->state(TestUser::class, 'random-state', ["random-state" => true]);
  49. $factory->define(TestPet::class, function () {
  50. return [
  51. 'id' => 1,
  52. 'name' => 'Mephistopheles',
  53. 'species' => 'dog',
  54. ];
  55. });
  56. }
  57. /** @test */
  58. public function can_parse_plain_response_attributes()
  59. {
  60. $results = $this->fetch($this->endpoint("plainResponseAttributes"));
  61. $this->assertArraySubset([
  62. [
  63. 'status' => 200,
  64. 'content' => json_encode(["all" => "good"]),
  65. "description" => "Success"
  66. ],
  67. [
  68. 'status' => 201,
  69. 'content' => json_encode(["all" => "good"]),
  70. ],
  71. [
  72. 'status' => 404,
  73. 'content' => null,
  74. ]
  75. ], $results);
  76. }
  77. /** @test */
  78. public function can_parse_responsefile_attributes()
  79. {
  80. $results = $this->fetch($this->endpoint("responseFileAttributes"));
  81. $this->assertArraySubset([
  82. [
  83. 'status' => 401,
  84. 'content' => json_encode(["message" => "Unauthorized", "merge" => "this"]),
  85. ],
  86. ], $results);
  87. }
  88. /** @test */
  89. public function can_parse_apiresource_attributes()
  90. {
  91. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  92. $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
  93. if ($user->id === 4) {
  94. $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
  95. $user->setRelation('children', collect([$child]));
  96. }
  97. });
  98. $results = $this->fetch($this->endpoint("apiResourceAttributes"));
  99. $this->assertArraySubset([
  100. [
  101. 'status' => 200,
  102. 'content' => json_encode([
  103. 'data' => [
  104. [
  105. 'id' => 4,
  106. 'name' => 'Tested Again',
  107. 'email' => 'a@b.com',
  108. 'children' => [
  109. [
  110. 'id' => 5,
  111. 'name' => 'Tested Again',
  112. 'email' => 'a@b.com',
  113. ],
  114. ],
  115. 'state1' => true,
  116. 'random-state' => true,
  117. ],
  118. ],
  119. 'links' => [
  120. "first" => '/?page=1',
  121. "last" => null,
  122. "prev" => null,
  123. "next" => '/?page=2',
  124. ],
  125. "meta" => [
  126. "current_page" => 1,
  127. "from" => 1,
  128. "path" => '/',
  129. "per_page" => 1,
  130. "to" => 1,
  131. ],
  132. "a" => "b",
  133. ]),
  134. ],
  135. ], $results);
  136. }
  137. /** @test */
  138. public function can_parse_apiresource_attributes_with_no_model_specified()
  139. {
  140. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  141. $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
  142. if ($user->id === 4) {
  143. $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
  144. $user->setRelation('children', collect([$child]));
  145. }
  146. });
  147. $results = $this->fetch($this->endpoint("apiResourceAttributesWithNoModel"));
  148. $this->assertArraySubset([
  149. [
  150. 'status' => 200,
  151. 'content' => json_encode([
  152. 'data' => [
  153. [
  154. 'id' => 4,
  155. 'name' => 'Tested Again',
  156. 'email' => 'a@b.com',
  157. 'children' => [
  158. [
  159. 'id' => 5,
  160. 'name' => 'Tested Again',
  161. 'email' => 'a@b.com',
  162. ],
  163. ],
  164. 'state1' => true,
  165. 'random-state' => true,
  166. ],
  167. ],
  168. 'links' => [
  169. "first" => '/?page=1',
  170. "last" => null,
  171. "prev" => null,
  172. "next" => '/?page=2',
  173. ],
  174. "meta" => [
  175. "current_page" => 1,
  176. "from" => 1,
  177. "path" => '/',
  178. "per_page" => 1,
  179. "to" => 1,
  180. ],
  181. "a" => "b",
  182. ]),
  183. ],
  184. ], $results);
  185. }
  186. /** @test */
  187. public function can_parse_transformer_attributes()
  188. {
  189. $results = $this->fetch($this->endpoint("transformerAttributes"));
  190. $this->assertArraySubset([
  191. [
  192. 'status' => 200,
  193. 'content' => json_encode([
  194. "data" => [
  195. [
  196. "id" => 1,
  197. "description" => "Welcome on this test versions",
  198. "name" => "TestName",
  199. ],
  200. ],
  201. 'meta' => [
  202. "pagination" => [
  203. "total" => 2,
  204. "count" => 1,
  205. "per_page" => 1,
  206. "current_page" => 1,
  207. "total_pages" => 2,
  208. "links" => ["next" => "/?page=2"],
  209. ],
  210. ],
  211. ]),
  212. ],
  213. ], $results);
  214. }
  215. /** @test */
  216. public function can_parse_apiresource_attributes_with_cursor_pagination()
  217. {
  218. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  219. $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
  220. if ($user->id === 4) {
  221. $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
  222. $user->setRelation('children', collect([$child]));
  223. }
  224. });
  225. $results = $this->fetch($this->endpoint("apiResourceAttributesWithCursorPaginate"));
  226. $nextCursor = base64_encode(json_encode(['_pointsToNextItems' => true]));
  227. $this->assertArraySubset([
  228. [
  229. 'status' => 200,
  230. 'content' => json_encode([
  231. 'data' => [
  232. [
  233. 'id' => 4,
  234. 'name' => 'Tested Again',
  235. 'email' => 'a@b.com',
  236. 'children' => [
  237. [
  238. 'id' => 5,
  239. 'name' => 'Tested Again',
  240. 'email' => 'a@b.com',
  241. ],
  242. ],
  243. ],
  244. ],
  245. 'links' => [
  246. "first" => null,
  247. "last" => null,
  248. "prev" => null,
  249. "next" => "/?cursor={$nextCursor}",
  250. ],
  251. "meta" => match (version_compare(Application::VERSION, '9.0', '>=')) {
  252. false => [
  253. "path" => '/',
  254. 'per_page' => 1,
  255. ],
  256. true => [
  257. "path" => '/',
  258. 'per_page' => 1,
  259. 'next_cursor' => $nextCursor,
  260. 'prev_cursor' => null,
  261. ]
  262. },
  263. ]),
  264. ],
  265. ], $results);
  266. }
  267. /** @test */
  268. public function can_parse_apiresource_attributes_and_load_children_using_factory_create()
  269. {
  270. Schema::create('test_users', function (Blueprint $table) {
  271. $table->id();
  272. $table->string('first_name');
  273. $table->string('last_name');
  274. $table->string('email');
  275. $table->integer('parent_id')->nullable();
  276. });
  277. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  278. $factory->afterCreating(TestUser::class, function (TestUser $user, $faker) {
  279. if ($user->id === 4) {
  280. Utils::getModelFactory(TestUser::class)->create(['id' => 5, 'parent_id' => 4]);
  281. }
  282. });
  283. $documentationConfig = ['examples' => ['models_source' => ['factoryCreate']]];
  284. $results = $this->fetch($this->endpoint("apiResourceAttributesIncludeChildren"), $documentationConfig);
  285. $this->assertArraySubset([
  286. [
  287. 'status' => 200,
  288. 'content' => json_encode([
  289. "data" => [
  290. "id" => 4,
  291. "name" => "Tested Again",
  292. "email" => "a@b.com",
  293. "children" => [
  294. [
  295. "id" => 5,
  296. "name" => "Tested Again",
  297. "email" => "a@b.com",
  298. ]
  299. ],
  300. ],
  301. ]),
  302. ],
  303. ], $results);
  304. }
  305. /** @test */
  306. public function can_parse_apiresource_attributes_and_load_children_and_children_count_using_factory_create()
  307. {
  308. if (version_compare(Application::VERSION, '9', '<')) {
  309. $this->markTestSkipped('The whenCounted method in JsonResource requires Laravel 9 or higher.');
  310. }
  311. Schema::create('test_users', function (Blueprint $table) {
  312. $table->id();
  313. $table->string('first_name');
  314. $table->string('last_name');
  315. $table->string('email');
  316. $table->integer('parent_id')->nullable();
  317. });
  318. $factory = app(\Illuminate\Database\Eloquent\Factory::class);
  319. $factory->afterCreating(TestUser::class, function (TestUser $user, $faker) {
  320. if ($user->id === 4) {
  321. Utils::getModelFactory(TestUser::class)->create(['id' => 5, 'parent_id' => 4]);
  322. }
  323. });
  324. $documentationConfig = ['examples' => ['models_source' => ['factoryCreate']]];
  325. $results = $this->fetch($this->endpoint("apiResourceAttributesIncludeChildrenAndChildrenCount"), $documentationConfig);
  326. $this->assertArraySubset([
  327. [
  328. 'status' => 200,
  329. 'content' => json_encode([
  330. "data" => [
  331. "id" => 4,
  332. "name" => "Tested Again",
  333. "email" => "a@b.com",
  334. "children" => [
  335. [
  336. "id" => 5,
  337. "name" => "Tested Again",
  338. "email" => "a@b.com",
  339. ]
  340. ],
  341. 'children_count' => 1,
  342. ],
  343. ]),
  344. ],
  345. ], $results);
  346. }
  347. protected function fetch($endpoint, array $documentationConfig = []): array
  348. {
  349. $strategy = new UseResponseAttributes(new DocumentationConfig([]));
  350. return $strategy($endpoint, []);
  351. }
  352. protected function endpoint(string $method): ExtractedEndpointData
  353. {
  354. $endpoint = new class extends ExtractedEndpointData {
  355. public function __construct(array $parameters = []) {}
  356. };
  357. $endpoint->controller = new ReflectionClass(ResponseAttributesTestController::class);
  358. $endpoint->method = $endpoint->controller->getMethod($method);
  359. $endpoint->route = new Route(['POST'], "/somethingRandom", ['uses' => [ResponseAttributesTestController::class, $method]]);
  360. return $endpoint;
  361. }
  362. }
  363. class ResponseAttributesTestController
  364. {
  365. #[Response(["all" => "good"], 200, "Success")]
  366. #[Response('{"all":"good"}', 201)]
  367. #[Response(status: 404)]
  368. public function plainResponseAttributes()
  369. {
  370. }
  371. #[ResponseFromFile("tests/Fixtures/response_error_test.json", 401, ["merge" => "this"])]
  372. public function responseFileAttributes()
  373. {
  374. }
  375. #[ResponseFromApiResource(TestUserApiResource::class, TestUser::class, collection: true,
  376. factoryStates: ["state1", "random-state"], simplePaginate: 1, additional: ["a" => "b"])]
  377. public function apiResourceAttributes()
  378. {
  379. }
  380. #[ResponseFromApiResource(TestUserApiResource::class, collection: true,
  381. factoryStates: ["state1", "random-state"], simplePaginate: 1, additional: ["a" => "b"])]
  382. public function apiResourceAttributesWithNoModel()
  383. {
  384. }
  385. #[ResponseFromTransformer(TestTransformer::class, TestModel::class, collection: true,
  386. paginate: [IlluminatePaginatorAdapter::class, 1])]
  387. public function transformerAttributes()
  388. {
  389. }
  390. #[ResponseFromApiResource(TestUserApiResource::class, collection: true, cursorPaginate: 1)]
  391. public function apiResourceAttributesWithCursorPaginate()
  392. {
  393. }
  394. #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'], withCount: ['children'])]
  395. public function apiResourceAttributesIncludeChildrenAndChildrenCount()
  396. {
  397. }
  398. #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'])]
  399. public function apiResourceAttributesIncludeChildren()
  400. {
  401. }
  402. }