From 8c2833fa2de32caada3eb1bf1481e2f53d121be3 Mon Sep 17 00:00:00 2001 From: Fernando Piovezan Date: Sat, 8 Jun 2024 15:57:14 -0300 Subject: [PATCH] Create tests and adjust code --- config/request-docs.php | 7 + src/Doc.php | 7 +- src/LaravelRequestDocs.php | 36 ++-- .../LaravelRequestDocsControllerTest.php | 94 ++++++++++ tests/Factories/UserFactory.php | 29 +++ tests/Models/User.php | 2 + .../Stubs/TestControllers/UserController.php | 16 ++ tests/Stubs/TestRequests/UserStoreRequest.php | 47 +++++ tests/mocks/lrd-response.json | 174 +++++++++--------- 9 files changed, 307 insertions(+), 105 deletions(-) create mode 100644 tests/Factories/UserFactory.php create mode 100644 tests/Stubs/TestRequests/UserStoreRequest.php diff --git a/config/request-docs.php b/config/request-docs.php index 7fc148a..a5151c4 100644 --- a/config/request-docs.php +++ b/config/request-docs.php @@ -180,8 +180,15 @@ 'exclude_fields' => [ ], + // Use factory to create data examples 'use_factory' => false, + // Regex to extract the model name from the controller name + 'pattern_model_from_controller_name' => '/APIController$/', + + // Path where the project's factories are located + 'factory_path' => 'Database\Factories', + 'rules_order' => [ 'required', 'required_if', diff --git a/src/Doc.php b/src/Doc.php index b85e71f..fcf9d38 100644 --- a/src/Doc.php +++ b/src/Doc.php @@ -439,7 +439,12 @@ public function getFieldInfo(): array */ public function setFieldInfo(array $fieldInfo): void { - $this->fieldInfo = $fieldInfo; + foreach ($fieldInfo as $key => $item) { + $this->fieldInfo[$key] = [ + 'description' => $item, + 'example' => $this->examples[$key] ?? '' + ]; + } } /** diff --git a/src/LaravelRequestDocs.php b/src/LaravelRequestDocs.php index b1cd095..17aff9b 100644 --- a/src/LaravelRequestDocs.php +++ b/src/LaravelRequestDocs.php @@ -288,10 +288,6 @@ public function appendRequestRules(Collection $docs): Collection $requestObject = $reflectionClass->newInstanceWithoutConstructor(); } - if (method_exists($requestObject, 'fieldDescriptions')) { - $doc->setFieldInfo($requestObject->fieldDescriptions()); - } - foreach (config('request-docs.rules_methods') as $requestMethod) { if (!method_exists($requestObject, $requestMethod)) { continue; @@ -315,6 +311,10 @@ public function appendRequestRules(Collection $docs): Collection if (config('request-docs.use_factory')) { $this->appendExample($doc); } + + if (method_exists($requestObject, 'fieldDescriptions')) { + $doc->setFieldInfo($requestObject->fieldDescriptions()); + } } } catch (Throwable $e) { // Do nothing. @@ -362,24 +362,26 @@ public function lrdDocComment(string $docComment): string public function appendExample(Doc $doc): void { try { - $controllerName = class_basename($doc->getController()); - $modelName = Str::replace('APIController', '', $controllerName); - $fullModelName = "App\\Models\\" . $modelName; - - if (!class_exists($fullModelName)) { - return; - } - - /** @var \Illuminate\Database\Eloquent\Model $model */ - $model = app($fullModelName); + $modelName = Str::replaceMatches( + config('request-docs.pattern_model_from_controller_name'), + '', + class_basename($doc->getController()) + ); + $fullFactoryName = config('request-docs.factory_path') . "\\{$modelName}Factory"; - if (!method_exists($model, 'factory')) { + if (!class_exists($fullFactoryName)) { return; } + /** @var \Illuminate\Database\Eloquent\Factories\Factory $factory */ + $factory = app($fullFactoryName); $excludeFields = config('request-docs.exclude_fields') ?? []; - $example = $model->factory()->make()->toArray(); - $example = array_filter($example, fn($key) => !in_array($key, $excludeFields), ARRAY_FILTER_USE_KEY); + $example = $factory::new()->make()->toArray(); + $example = array_filter( + $example, + fn($key) => !in_array($key, $excludeFields), + ARRAY_FILTER_USE_KEY + ); $doc->mergeExamples($example); } catch (Throwable $e) { // Do nothing. diff --git a/tests/Controllers/LaravelRequestDocsControllerTest.php b/tests/Controllers/LaravelRequestDocsControllerTest.php index 4d67eef..cdbc30d 100644 --- a/tests/Controllers/LaravelRequestDocsControllerTest.php +++ b/tests/Controllers/LaravelRequestDocsControllerTest.php @@ -510,4 +510,98 @@ public function testPathWithGlobalPattern() $this->assertSame($expected, $pathParameter); } + + + public function testGenerateExampleToFactory() + { + config(['request-docs.factory_path' => 'Rakutentech\LaravelRequestDocs\Tests\Factories']); + config(['request-docs.use_factory' => true]); + config(['request-docs.pattern_model_from_controller_name' => '/Controller$/']); + Route::post('api/v1/users/store', [UserController::class, 'store']); + + $response = $this->get(route('request-docs.api')) + ->assertStatus(Response::HTTP_OK); + $docs = collect($response->json()); + $userRoute = $docs + ->filter(fn(array $item) => Str::startsWith($item['uri'], ['api'])); + $examples = $userRoute->pluck('examples')->toArray(); + $expected = [\Rakutentech\LaravelRequestDocs\Tests\Factories\UserFactory::new()->make()->toArray()]; + + $this->assertSame($expected, $examples); + } + + public function testGenerateFieldDescriptionAndExamples() + { + config(['request-docs.factory_path' => 'Rakutentech\LaravelRequestDocs\Tests\Factories']); + config(['request-docs.use_factory' => true]); + config(['request-docs.pattern_model_from_controller_name' => '/Controller$/']); + Route::post('api/v1/users/store', [UserController::class, 'store']); + + $response = $this->get(route('request-docs.api')) + ->assertStatus(Response::HTTP_OK); + $docs = collect($response->json()); + $userRoute = $docs + ->filter(fn(array $item) => Str::startsWith($item['uri'], ['api'])); + $fieldInfo = $userRoute->pluck('field_info')->get(0); + $expected = [ + 'name' => [ + 'description' => 'User Name', + 'example' => 'John Doe' + ], + 'email' => [ + 'description' => 'User email', + 'example' => 'johndoe@email.com' + ] + ]; + + $this->assertSame($expected, $fieldInfo); + } + + + public function testSummaryAndDescriptionMethod() + { + Route::post('api/v1/users/store', [UserController::class, 'store']); + + $response = $this->get(route('request-docs.api')) + ->assertStatus(Response::HTTP_OK); + $docs = collect($response->json()); + $userRoute = $docs + ->filter(fn(array $item) => Str::startsWith($item['uri'], ['api'])); + $summary = $userRoute->pluck('summary')->get(0); + $description = $userRoute->pluck('description')->get(0); + + $expectedSummary = 'Store a newly created resource in storage.'; + $expectedDescription = 'This method creates a user when validations are met.'; + + $this->assertSame($expectedSummary, $summary); + $this->assertSame($description, $expectedDescription); + } + + public function testRuleOrder() + { + Route::post('api/v1/users/store', [UserController::class, 'store']); + + $response = $this->get(route('request-docs.api')) + ->assertStatus(Response::HTTP_OK); + $docs = collect($response->json()); + $userRoute = $docs + ->filter(fn(array $item) => Str::startsWith($item['uri'], ['api'])); + $rulesOrder = $userRoute->pluck('rules_order')->get(0); + + $this->assertSame(config('request-docs.rules_order'), $rulesOrder); + } + + public function testTag() + { + Route::post('api/v1/users/store', [UserController::class, 'store']); + + $response = $this->get(route('request-docs.api')) + ->assertStatus(Response::HTTP_OK); + $docs = collect($response->json()); + $userRoute = $docs + ->filter(fn(array $item) => Str::startsWith($item['uri'], ['api'])); + $tag = $userRoute->pluck('tag')->get(0); + + $this->assertSame('User', $tag); + } } diff --git a/tests/Factories/UserFactory.php b/tests/Factories/UserFactory.php new file mode 100644 index 0000000..151778d --- /dev/null +++ b/tests/Factories/UserFactory.php @@ -0,0 +1,29 @@ + + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => 'John Doe', + 'email' => 'johndoe@email.com', + ]; + } +} diff --git a/tests/Models/User.php b/tests/Models/User.php index ec8051b..ef5434b 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -2,8 +2,10 @@ namespace Rakutentech\LaravelRequestDocs\Tests\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class User extends Model { + use HasFactory; } diff --git a/tests/Stubs/TestControllers/UserController.php b/tests/Stubs/TestControllers/UserController.php index 19ace6b..ad86ccc 100644 --- a/tests/Stubs/TestControllers/UserController.php +++ b/tests/Stubs/TestControllers/UserController.php @@ -3,11 +3,27 @@ namespace Rakutentech\LaravelRequestDocs\Tests\Stubs\TestControllers; use Illuminate\Http\Response; +use Rakutentech\LaravelRequestDocs\Tests\Stubs\TestRequests\UserStoreRequest; +/** + * class UserController + * @LRDtag User + */ class UserController { public function __invoke(): Response { return response('true'); } + + /** + * Store a newly created resource in storage. + * This method creates a user when validations are met. + * @param UserStoreRequest $request + * @return Response + */ + public function store(UserStoreRequest $request): Response + { + return response('true'); + } } diff --git a/tests/Stubs/TestRequests/UserStoreRequest.php b/tests/Stubs/TestRequests/UserStoreRequest.php new file mode 100644 index 0000000..d6d651d --- /dev/null +++ b/tests/Stubs/TestRequests/UserStoreRequest.php @@ -0,0 +1,47 @@ + 'required|string', + 'email' => 'required|email', + ]; + } + + /** + * Provides a detailed description of the expected parameters + * in the body of an HTTP request. + */ + public function fieldDescriptions(): array + { + return [ + 'name' => 'User Name', + 'email' => 'User email', + ]; + } +} diff --git a/tests/mocks/lrd-response.json b/tests/mocks/lrd-response.json index 0e7ac5b..0c33f09 100644 --- a/tests/mocks/lrd-response.json +++ b/tests/mocks/lrd-response.json @@ -43,8 +43,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -59,7 +59,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "/", @@ -105,8 +105,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -121,7 +121,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome", @@ -167,8 +167,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -183,7 +183,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome", @@ -229,8 +229,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -245,7 +245,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome/{id}", @@ -274,8 +274,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -290,7 +290,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome/{id}", @@ -319,8 +319,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -335,7 +335,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome", @@ -372,8 +372,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -388,7 +388,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome", @@ -424,8 +424,8 @@ "401" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -440,7 +440,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome/patch", @@ -476,8 +476,8 @@ "401" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -492,7 +492,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "welcome", @@ -523,8 +523,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -539,7 +539,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "health", @@ -566,8 +566,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -582,7 +582,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "health", @@ -609,8 +609,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -625,7 +625,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "single", @@ -652,8 +652,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -668,7 +668,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "single", @@ -695,8 +695,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -711,7 +711,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "welcome/no-rules", @@ -738,8 +738,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -754,7 +754,7 @@ "max", "nullable" ], - "tags": "Welcome" + "tag": "Welcome" }, { "uri": "comments-on-request-rules-method", @@ -799,8 +799,8 @@ "244" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -815,7 +815,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "closure", @@ -831,8 +831,8 @@ "doc_block": "", "responses": [], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -847,7 +847,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "closure", @@ -863,8 +863,8 @@ "doc_block": "", "responses": [], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -879,7 +879,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts", @@ -906,8 +906,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -922,7 +922,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts", @@ -949,8 +949,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -965,7 +965,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts", @@ -992,8 +992,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1008,7 +1008,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts/{account}", @@ -1037,8 +1037,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1053,7 +1053,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts/{account}", @@ -1082,8 +1082,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1098,7 +1098,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts/{account}", @@ -1127,8 +1127,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1143,7 +1143,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts/{account}", @@ -1172,8 +1172,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1188,7 +1188,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "accounts/{account}", @@ -1217,8 +1217,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1233,7 +1233,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "match", @@ -1260,8 +1260,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1276,7 +1276,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "match", @@ -1303,8 +1303,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1319,7 +1319,7 @@ "max", "nullable" ], - "tags": "" + "tag": "" }, { "uri": "match", @@ -1346,8 +1346,8 @@ "503" ], "examples": [], - "fieldInfo": [], - "rulesOrder": [ + "field_info": [], + "rules_order": [ "required", "required_if", "file", @@ -1362,6 +1362,6 @@ "max", "nullable" ], - "tags": "" + "tag": "" } ]