From 0b485a5c6172251530f26beec7eaf6ea171fbc27 Mon Sep 17 00:00:00 2001 From: Koen Hoeijmakers Date: Mon, 24 Feb 2020 14:57:09 +0100 Subject: [PATCH] chore: remove resources and implement own response factory --- .docs/api/openapi.yml | 392 ++++++++++++++++++ .../Http/Responses/ResponseFactory.php | 66 +++ .../Api/Profile/Password/Update.php | 13 +- app/Http/Controllers/Api/Profile/Show.php | 18 +- app/Http/Controllers/Api/Profile/Update.php | 15 +- app/Http/Controllers/Api/User/Index.php | 13 +- app/Http/Controllers/Api/User/Show.php | 15 +- app/Http/Controllers/Api/User/Store.php | 18 +- app/Http/Controllers/Api/User/Update.php | 15 +- app/Http/Resources/Api/UserResource.php | 30 -- app/Http/Responses/ResponseFactory.php | 78 ++++ app/Providers/RouteServiceProvider.php | 10 + composer.json | 3 +- composer.lock | 57 +-- phpunit.xml | 3 + routes/api.php | 2 +- tests/Feature/Http/Api/Auth/DispenseTest.php | 12 +- tests/Feature/Http/Api/Auth/LoginTest.php | 8 +- tests/Feature/Http/Api/Auth/LogoutTest.php | 2 +- .../Http/Api/Invitation/AcceptTest.php | 6 +- .../Http/Api/Password/ForgottenTest.php | 6 +- tests/Feature/Http/Api/Password/ResetTest.php | 6 +- .../Http/Api/Profile/Email/UpdateTest.php | 6 +- .../Http/Api/Profile/Email/VerifyTest.php | 8 +- .../Http/Api/Profile/Password/UpdateTest.php | 6 +- tests/Feature/Http/Api/Profile/ShowTest.php | 2 +- tests/Feature/Http/Api/Profile/UpdateTest.php | 4 +- tests/Feature/Http/Api/User/DestroyTest.php | 2 +- tests/Feature/Http/Api/User/IndexTest.php | 24 +- tests/Feature/Http/Api/User/ShowTest.php | 2 +- tests/Feature/Http/Api/User/StoreTest.php | 4 +- tests/Feature/Http/Api/User/UpdateTest.php | 4 +- 32 files changed, 717 insertions(+), 133 deletions(-) create mode 100644 .docs/api/openapi.yml create mode 100644 app/Contracts/Http/Responses/ResponseFactory.php delete mode 100644 app/Http/Resources/Api/UserResource.php create mode 100644 app/Http/Responses/ResponseFactory.php diff --git a/.docs/api/openapi.yml b/.docs/api/openapi.yml new file mode 100644 index 0000000..2ec3ab1 --- /dev/null +++ b/.docs/api/openapi.yml @@ -0,0 +1,392 @@ +openapi: "3.0.2" + +info: + title: Laravel API Starter Documentation + description: Laravel API Starter Documentation + version: "2.1" + termsOfService: https://github.com/kingscode/laravel-api-starter + contact: + name: Kings Code + url: https://kingscode.nl + +paths: + /auth/dispense: + post: + description: Dispense a new token + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + - token + properties: + redirect_uri: + type: string + email: + type: string + token: + type: string + responses: + 302: + description: All is good and the user will be redirected back to the website. + 422: + description: The resource had validation errors. + /auth/login: + post: + description: Request a login. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + - password + properties: + email: + type: string + password: + type: string + responses: + 200: + description: The resource was stored. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + type: object + properties: + token: + type: string + 422: + description: The request had validation errors. + /auth/logout: + post: + security: + - BearerAuth: [] + description: Log out the given user (invalidate the used token). + responses: + 200: + description: The token was invalidated. + /invitation/accept: + post: + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + - token + - password + - password_confirmation + properties: + email: + type: string + token: + type: string + password: + type: string + password_confirmation: + type: string + responses: + 200: + description: Password has been reset. + 400: + description: Something went wrong. + /password/forgotten: + post: + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + properties: + email: + type: string + responses: + 200: + description: Password forgotten email has been sent if the user exists. + /password/reset: + post: + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + - token + - password + - password_confirmation + properties: + email: + type: string + token: + type: string + password: + type: string + password_confirmation: + type: string + responses: + 200: + description: Password has been reset. + 400: + description: Something went wrong. + /profile: + get: + security: + - BearerAuth: [] + description: Get the profile of the logged in user. + responses: + 200: + description: All is ok. + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + email: + type: string + is_admin: + type: boolean + is_business: + type: boolean + can_order_on_invoice: + type: boolean + put: + security: + - BearerAuth: [] + description: Update the profile of the logged in user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - name + - email + properties: + name: + type: string + email: + type: string + responses: + 200: + description: Profile updated. + 422: + description: Validation errors. + /profile/email: + put: + security: + - BearerAuth: [] + description: Update the email of the logged in user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + properties: + email: + type: string + responses: + 200: + description: Password updated. + 422: + description: Validation errors. + /profile/email/verify: + post: + security: + - BearerAuth: [] + description: Update the email of the logged in user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - email + - token + properties: + email: + type: string + token: + type: string + responses: + 200: + description: Password updated. + 422: + description: Validation errors. + /profile/password: + put: + security: + - BearerAuth: [] + description: Update the password of the logged in user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - password + - password_confirmation + - current_password + properties: + password: + type: string + maxLength: 191 + minLength: 10 + password_confirmation: + type: string + current_password: + type: string + responses: + 200: + description: Password updated. + 422: + description: Validation errors. + /user: + get: + security: + - BearerAuth: [] + description: Get paginated Users. + parameters: + - name: perPage + in: query + required: false + schema: + type: integer + - name: sortBy + in: query + required: false + schema: + type: string + enum: [name, email] + default: name + - name: desc + in: query + required: false + schema: + type: boolean + default: false + responses: + 200: + description: A paginated index of users. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + properties: + id: + type: integer + name: + type: string + email: + type: string + post: + security: + - BearerAuth: [] + description: Store a new user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - name + properties: + name: + type: string + email: + type: string + responses: + 200: + description: The resource was stored. + 422: + description: The resource had validation errors. + /user/{user}: + parameters: + - name: user + in: path + required: true + schema: + type: integer + get: + security: + - BearerAuth: [] + description: Retrieve the given User. + responses: + 200: + description: The user. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + properties: + id: + type: integer + name: + type: string + email: + type: string + put: + security: + - BearerAuth: [] + description: Update the user. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - name + - email + properties: + name: + type: string + email: + type: string + responses: + 200: + description: The resource was updated. + 404: + description: Not found. + 422: + description: The resource had validation errors. + + delete: + security: + - BearerAuth: [] + description: Delete the resource. + responses: + 200: + description: The resource was deleted. + 409: + description: The resource was not deleted due to a conflict. +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer diff --git a/app/Contracts/Http/Responses/ResponseFactory.php b/app/Contracts/Http/Responses/ResponseFactory.php new file mode 100644 index 0000000..a9e58ee --- /dev/null +++ b/app/Contracts/Http/Responses/ResponseFactory.php @@ -0,0 +1,66 @@ +hasher = $hasher; + $this->responseFactory = $responseFactory; } - public function __invoke(Guard $guard, UpdateRequest $request): JsonResponse + public function __invoke(Guard $guard, UpdateRequest $request): Response { /** @var \App\Models\User $user */ $user = $guard->user(); @@ -28,6 +31,6 @@ public function __invoke(Guard $guard, UpdateRequest $request): JsonResponse 'password' => $this->hasher->make($request->input('password')), ]); - return (new UserResource($user))->toResponse($request); + return $this->responseFactory->noContent(Response::HTTP_OK); } } diff --git a/app/Http/Controllers/Api/Profile/Show.php b/app/Http/Controllers/Api/Profile/Show.php index 78d7cdd..ccbc2cd 100644 --- a/app/Http/Controllers/Api/Profile/Show.php +++ b/app/Http/Controllers/Api/Profile/Show.php @@ -4,15 +4,29 @@ namespace App\Http\Controllers\Api\Profile; -use App\Http\Resources\Api\UserResource; +use App\Contracts\Http\Responses\ResponseFactory; use Illuminate\Contracts\Auth\Guard; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; final class Show { + private ResponseFactory $responseFactory; + + public function __construct(ResponseFactory $responseFactory) + { + $this->responseFactory = $responseFactory; + } + public function __invoke(Guard $guard, Request $request): JsonResponse { - return (new UserResource($guard->user()))->toResponse($request); + /** @var \App\Models\User $user */ + $user = $guard->user(); + + return $this->responseFactory->json([ + 'id' => $user->getKey(), + 'name' => $user->getAttribute('name'), + 'email' => $user->getAttribute('email'), + ]); } } diff --git a/app/Http/Controllers/Api/Profile/Update.php b/app/Http/Controllers/Api/Profile/Update.php index 498cfe2..c8a276d 100644 --- a/app/Http/Controllers/Api/Profile/Update.php +++ b/app/Http/Controllers/Api/Profile/Update.php @@ -4,14 +4,21 @@ namespace App\Http\Controllers\Api\Profile; +use App\Contracts\Http\Responses\ResponseFactory; use App\Http\Requests\Api\Profile\UpdateRequest; -use App\Http\Resources\Api\UserResource; use Illuminate\Contracts\Auth\Guard; -use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; final class Update { - public function __invoke(Guard $guard, UpdateRequest $request): JsonResponse + private ResponseFactory $responseFactory; + + public function __construct(ResponseFactory $responseFactory) + { + $this->responseFactory = $responseFactory; + } + + public function __invoke(Guard $guard, UpdateRequest $request): Response { /** @var \App\Models\User $user */ $user = $guard->user(); @@ -20,6 +27,6 @@ public function __invoke(Guard $guard, UpdateRequest $request): JsonResponse $request->validated() ); - return (new UserResource($user))->toResponse($request); + return $this->responseFactory->noContent(Response::HTTP_OK); } } diff --git a/app/Http/Controllers/Api/User/Index.php b/app/Http/Controllers/Api/User/Index.php index 174538f..e2e9fc2 100644 --- a/app/Http/Controllers/Api/User/Index.php +++ b/app/Http/Controllers/Api/User/Index.php @@ -4,8 +4,8 @@ namespace App\Http\Controllers\Api\User; +use App\Contracts\Http\Responses\ResponseFactory; use App\Http\Requests\Api\User\IndexRequest; -use App\Http\Resources\Api\UserResource; use App\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\JsonResponse; @@ -13,16 +13,19 @@ final class Index { + private ResponseFactory $responseFactory; + private Filtering $filtering; - public function __construct(Filtering $filtering) + public function __construct(ResponseFactory $responseFactory, Filtering $filtering) { + $this->responseFactory = $responseFactory; $this->filtering = $filtering; } public function __invoke(IndexRequest $request): JsonResponse { - $builder = User::query(); + $builder = User::query()->select(['id', 'name', 'email']); $this->filtering->builder($builder) ->filterFor('name', function (Builder $builder, string $value) { @@ -33,12 +36,14 @@ public function __invoke(IndexRequest $request): JsonResponse }) ->sortFor('name') ->sortFor('email') + ->defaultSorting('name') ->filter(); $paginator = $builder->paginate( $request->input('perPage') ); - return UserResource::collection($paginator)->toResponse($request); + return $this->responseFactory->paginator($paginator); } } + diff --git a/app/Http/Controllers/Api/User/Show.php b/app/Http/Controllers/Api/User/Show.php index 0ee5e2f..35c5af9 100644 --- a/app/Http/Controllers/Api/User/Show.php +++ b/app/Http/Controllers/Api/User/Show.php @@ -4,15 +4,26 @@ namespace App\Http\Controllers\Api\User; -use App\Http\Resources\Api\UserResource; +use App\Contracts\Http\Responses\ResponseFactory; use App\Models\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; final class Show { + private ResponseFactory $responseFactory; + + public function __construct(ResponseFactory $responseFactory) + { + $this->responseFactory = $responseFactory; + } + public function __invoke(Request $request, User $user): JsonResponse { - return (new UserResource($user))->toResponse($request); + return $this->responseFactory->json([ + 'id' => $user->getKey(), + 'name' => $user->getAttribute('name'), + 'email' => $user->getAttribute('email'), + ]); } } diff --git a/app/Http/Controllers/Api/User/Store.php b/app/Http/Controllers/Api/User/Store.php index 9fc5d1c..1a063d6 100644 --- a/app/Http/Controllers/Api/User/Store.php +++ b/app/Http/Controllers/Api/User/Store.php @@ -4,14 +4,14 @@ namespace App\Http\Controllers\Api\User; +use App\Contracts\Http\Responses\ResponseFactory; use App\Http\Requests\Api\User\StoreRequest; -use App\Http\Resources\Api\UserResource; use App\Models\User; use App\Notifications\User\Invitation; use Illuminate\Auth\Passwords\PasswordBrokerManager; use Illuminate\Contracts\Auth\PasswordBroker; use Illuminate\Contracts\Notifications\Dispatcher; -use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -21,13 +21,19 @@ final class Store private PasswordBrokerManager $passwordBrokerManager; - public function __construct(Dispatcher $notificationDispatcher, PasswordBrokerManager $passwordBrokerManager) - { + private ResponseFactory $responseFactory; + + public function __construct( + Dispatcher $notificationDispatcher, + PasswordBrokerManager $passwordBrokerManager, + ResponseFactory $responseFactory + ) { $this->notificationDispatcher = $notificationDispatcher; $this->passwordBrokerManager = $passwordBrokerManager; + $this->responseFactory = $responseFactory; } - public function __invoke(StoreRequest $request): JsonResponse + public function __invoke(StoreRequest $request): Response { $attributes = $request->validated(); @@ -41,7 +47,7 @@ public function __invoke(StoreRequest $request): JsonResponse new Invitation($this->getPasswordBroker()->createToken($user)) ); - return (new UserResource($user))->toResponse($request); + return $this->responseFactory->noContent(Response::HTTP_CREATED); } private function getPasswordBroker(): PasswordBroker diff --git a/app/Http/Controllers/Api/User/Update.php b/app/Http/Controllers/Api/User/Update.php index 2331512..3d1ea9f 100644 --- a/app/Http/Controllers/Api/User/Update.php +++ b/app/Http/Controllers/Api/User/Update.php @@ -4,19 +4,26 @@ namespace App\Http\Controllers\Api\User; +use App\Contracts\Http\Responses\ResponseFactory; use App\Http\Requests\Api\User\UpdateRequest; -use App\Http\Resources\Api\UserResource; use App\Models\User; -use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; final class Update { - public function __invoke(UpdateRequest $request, User $user): JsonResponse + private ResponseFactory $responseFactory; + + public function __construct(ResponseFactory $responseFactory) + { + $this->responseFactory = $responseFactory; + } + + public function __invoke(UpdateRequest $request, User $user): Response { $user->update( $request->validated() ); - return (new UserResource($user))->toResponse($request); + return $this->responseFactory->noContent(Response::HTTP_OK); } } diff --git a/app/Http/Resources/Api/UserResource.php b/app/Http/Resources/Api/UserResource.php deleted file mode 100644 index 11d5068..0000000 --- a/app/Http/Resources/Api/UserResource.php +++ /dev/null @@ -1,30 +0,0 @@ - $this->resource->getKey(), - 'name' => $this->resource->getAttribute('name'), - 'email' => $this->resource->getAttribute('email'), - ]; - } -} diff --git a/app/Http/Responses/ResponseFactory.php b/app/Http/Responses/ResponseFactory.php new file mode 100644 index 0000000..fe3fa7d --- /dev/null +++ b/app/Http/Responses/ResponseFactory.php @@ -0,0 +1,78 @@ +make('', $status, $headers); + } + + /** + * @inheritDoc + */ + public function json($data = [], $status = Response::HTTP_OK, array $headers = [], $options = 0): JsonResponse + { + if (! array_key_exists('data', $data)) { + $data = ['data' => $data]; + } + + return new JsonResponse($data, $status, $headers, $options); + } + + /** + * @inheritDoc + */ + public function paginator( + LengthAwarePaginator $paginator, + int $status = Response::HTTP_OK, + array $headers = [], + int $options = 0 + ): JsonResponse { + return $this->json([ + 'data' => $paginator->items(), + 'meta' => [ + 'current_page' => $paginator->currentPage(), + 'last_page' => $paginator->lastPage(), + 'from' => $paginator->firstItem(), + 'to' => $paginator->lastItem(), + 'total' => $paginator->total(), + 'per_page' => $paginator->perPage(), + ], + ]); + } + + /** + * @inheritDoc + */ + public function mappedPaginator( + LengthAwarePaginator $paginator, + callable $map, + int $status = Response::HTTP_OK, + array $headers = [], + int $options = 0 + ): JsonResponse { + $paginator->setCollection($paginator->getCollection()->map($map)); + + return $this->paginator($paginator, $status, $headers, $options); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index c81f3e3..c49b7aa 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -4,6 +4,9 @@ namespace App\Providers; +use App\Contracts\Http\Responses\ResponseFactory as ResponseFactoryContract; +use App\Http\Responses\ResponseFactory; +use Illuminate\Contracts\Container\Container; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Routing\Router; @@ -33,6 +36,13 @@ public function map(): void $this->mapWebRoutes($router); } + public function register() + { + $this->app->singleton(ResponseFactoryContract::class, function (Container $container) { + return $container->make(ResponseFactory::class); + }); + } + /** * Define the "web" routes for the application. * diff --git a/composer.json b/composer.json index 3dca448..3a77506 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "nunomaduro/collision": "^3.0", "nunomaduro/phpinsights": "^1.11", "phpunit/phpunit": "^8.5", - "roave/security-advisories": "dev-master" + "roave/security-advisories": "dev-master", + "ext-json": "*" }, "config": { "optimize-autoloader": true, diff --git a/composer.lock b/composer.lock index e99ef64..e604d3c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f6dcecc40d40110a18a7ae20d34e6dd4", + "content-hash": "6d7d318c4c25f35566b34668ae1fe277", "packages": [ { "name": "clue/stream-filter", @@ -334,24 +334,24 @@ }, { "name": "fideloper/proxy", - "version": "4.2.2", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "790194d5d3da89a713478875d2e2d05855a90a81" + "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/790194d5d3da89a713478875d2e2d05855a90a81", - "reference": "790194d5d3da89a713478875d2e2d05855a90a81", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", + "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", "shasum": "" }, "require": { - "illuminate/contracts": "^5.0|^6.0|^7.0", + "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "^5.0|^6.0|^7.0", + "illuminate/http": "^5.0|^6.0|^7.0|^8.0", "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.0" }, @@ -384,7 +384,7 @@ "proxy", "trusted proxy" ], - "time": "2019-12-20T13:11:11+00:00" + "time": "2020-02-22T01:51:47+00:00" }, { "name": "guzzlehttp/guzzle", @@ -850,16 +850,16 @@ }, { "name": "laravel/framework", - "version": "v6.15.1", + "version": "v6.16.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b7c152e3327c03428fb68d5abb63ca8b3eca8422" + "reference": "b47217e41868d3049ec545cbb713aa94c6f39e55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b7c152e3327c03428fb68d5abb63ca8b3eca8422", - "reference": "b7c152e3327c03428fb68d5abb63ca8b3eca8422", + "url": "https://api.github.com/repos/laravel/framework/zipball/b47217e41868d3049ec545cbb713aa94c6f39e55", + "reference": "b47217e41868d3049ec545cbb713aa94c6f39e55", "shasum": "" }, "require": { @@ -927,7 +927,7 @@ "aws/aws-sdk-php": "^3.0", "doctrine/dbal": "^2.6", "filp/whoops": "^2.4", - "guzzlehttp/guzzle": "^6.3", + "guzzlehttp/guzzle": "^6.3|^7.0", "league/flysystem-cached-adapter": "^1.0", "mockery/mockery": "^1.3.1", "moontoast/math": "^1.1", @@ -947,7 +947,7 @@ "ext-redis": "Required to use the Redis cache and queue drivers.", "filp/whoops": "Required for friendly error pages in development (^2.4).", "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", - "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0).", + "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0|^7.0).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", @@ -992,7 +992,7 @@ "framework", "laravel" ], - "time": "2020-02-12T21:56:14+00:00" + "time": "2020-02-18T15:17:52+00:00" }, { "name": "laravel/tinker", @@ -2465,16 +2465,16 @@ }, { "name": "ramsey/uuid", - "version": "3.9.2", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "7779489a47d443f845271badbdcedfe4df8e06fb" + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/7779489a47d443f845271badbdcedfe4df8e06fb", - "reference": "7779489a47d443f845271badbdcedfe4df8e06fb", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", "shasum": "" }, "require": { @@ -2548,7 +2548,7 @@ "identifier", "uuid" ], - "time": "2019-12-17T08:18:51+00:00" + "time": "2020-02-21T04:36:14+00:00" }, { "name": "sentry/sdk", @@ -6671,12 +6671,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0365bf26eddd4a8be9980d7dabf05ceb2aba2f02" + "reference": "1df6b9d09d2b074fd3f0f10a7696d9f797d4772c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0365bf26eddd4a8be9980d7dabf05ceb2aba2f02", - "reference": "0365bf26eddd4a8be9980d7dabf05ceb2aba2f02", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/1df6b9d09d2b074fd3f0f10a7696d9f797d4772c", + "reference": "1df6b9d09d2b074fd3f0f10a7696d9f797d4772c", "shasum": "" }, "conflict": { @@ -6695,6 +6695,7 @@ "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", + "centreon/centreon": "<18.10.8|>=19,<19.4.5", "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "codeigniter/framework": "<=3.0.6", "composer/composer": "<=1-alpha.11", @@ -6761,7 +6762,7 @@ "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", - "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", "openid/php-openid": "<2.3", "oro/crm": ">=1.7,<1.7.4", "oro/platform": ">=1.7,<1.7.4", @@ -6797,7 +6798,7 @@ "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.4.4", + "silverstripe/framework": "<4.4.5|>=4.5,<4.5.2", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", @@ -6921,7 +6922,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2020-02-10T16:13:40+00:00" + "time": "2020-02-19T06:23:50+00:00" }, { "name": "scrivo/highlight.php", @@ -8305,5 +8306,7 @@ "platform": { "php": ">=7.4" }, - "platform-dev": [] + "platform-dev": { + "ext-json": "*" + } } diff --git a/phpunit.xml b/phpunit.xml index 6f51682..2b4105f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,6 +21,9 @@ ./app + + ./app/Http/Responses/ResponseFactory.php + diff --git a/routes/api.php b/routes/api.php index f5fb326..6cf0519 100644 --- a/routes/api.php +++ b/routes/api.php @@ -6,7 +6,7 @@ // @formatter:off -$router->namespace('Api')->prefix('api')->group(function (Router $router) { +$router->namespace('Api')->group(function (Router $router) { $router->namespace('Auth')->prefix('auth')->middleware('throttle:5,15,spa_login_lock')->group(function (Router $router) { $router->post('login', 'Login'); $router->post('dispense', 'Dispense'); diff --git a/tests/Feature/Http/Api/Auth/DispenseTest.php b/tests/Feature/Http/Api/Auth/DispenseTest.php index ed89dd7..6f31bbe 100644 --- a/tests/Feature/Http/Api/Auth/DispenseTest.php +++ b/tests/Feature/Http/Api/Auth/DispenseTest.php @@ -34,7 +34,7 @@ public function test() $token = $this->dispensary->dispense($user); - $response = $this->json('post', 'api/auth/dispense', [ + $response = $this->json('post', 'auth/dispense', [ 'email' => $user->email, 'token' => $token, ]); @@ -56,7 +56,7 @@ public function testWithRedirectUri() $token = $this->dispensary->dispense($user); - $response = $this->json('post', 'api/auth/dispense', [ + $response = $this->json('post', 'auth/dispense', [ 'email' => $user->email, 'token' => $token, 'redirect_uri' => 'to-here', @@ -73,7 +73,7 @@ public function testWithRedirectUri() public function testRedirectsBackWhenNoDataIsSentInRequest() { - $response = $this->json('post', 'api/auth/dispense'); + $response = $this->json('post', 'auth/dispense'); $redirectUri = $response->headers->get('Location'); @@ -93,7 +93,7 @@ public function testRedirectsBackWhenTokenExpired() $cache->clear(); - $response = $this->json('post', 'api/auth/dispense', [ + $response = $this->json('post', 'auth/dispense', [ 'email' => $user->email, 'token' => $token, ]); @@ -111,7 +111,7 @@ public function testRedirectsBackWhenTokenWrong() $token = $this->dispensary->dispense($user); - $response = $this->json('post', 'api/auth/dispense', [ + $response = $this->json('post', 'auth/dispense', [ 'email' => $user->email, 'token' => 'shizzlepizza', ]); @@ -128,7 +128,7 @@ public function testRedirectsBackWhenEmailDoesntExists() 'password' => bcrypt('kingscodedotnl'), ]); - $response = $this->json('post', 'api/auth/dispense', [ + $response = $this->json('post', 'auth/dispense', [ 'email' => 'info@kingscode.nl', 'token' => 'shizzlepizza', ]); diff --git a/tests/Feature/Http/Api/Auth/LoginTest.php b/tests/Feature/Http/Api/Auth/LoginTest.php index 5d2a5a0..25ddee7 100644 --- a/tests/Feature/Http/Api/Auth/LoginTest.php +++ b/tests/Feature/Http/Api/Auth/LoginTest.php @@ -16,7 +16,7 @@ public function test() 'password' => bcrypt('kingscodedotnl'), ]); - $response = $this->json('post', 'api/auth/login', [ + $response = $this->json('post', 'auth/login', [ 'email' => $user->email, 'password' => 'kingscodedotnl', ]); @@ -32,7 +32,7 @@ public function test() public function testNonExistentEmail() { - $response = $this->json('post', 'api/auth/login', [ + $response = $this->json('post', 'auth/login', [ 'email' => 'info@kingscode.nl', 'password' => 'kingscodedotnl', ]); @@ -48,7 +48,7 @@ public function testWrongPassword() 'password' => bcrypt('kingscodedotnl'), ]); - $response = $this->json('post', 'api/auth/login', [ + $response = $this->json('post', 'auth/login', [ 'email' => $user->email, 'password' => 'pooper', ]); @@ -60,7 +60,7 @@ public function testWrongPassword() public function testValidationErrors() { - $response = $this->json('post', 'api/auth/login'); + $response = $this->json('post', 'auth/login'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'email', 'password', diff --git a/tests/Feature/Http/Api/Auth/LogoutTest.php b/tests/Feature/Http/Api/Auth/LogoutTest.php index 7630248..ea74c8d 100644 --- a/tests/Feature/Http/Api/Auth/LogoutTest.php +++ b/tests/Feature/Http/Api/Auth/LogoutTest.php @@ -18,7 +18,7 @@ public function test() ]); $user->tokens()->create(['token' => 'yayeet']); - $response = $this->json('post', 'api/auth/logout', [], [ + $response = $this->json('post', 'auth/logout', [], [ Header::AUTHORIZATION => 'Bearer yayeet', ]); diff --git a/tests/Feature/Http/Api/Invitation/AcceptTest.php b/tests/Feature/Http/Api/Invitation/AcceptTest.php index 6a5944e..dcda44c 100644 --- a/tests/Feature/Http/Api/Invitation/AcceptTest.php +++ b/tests/Feature/Http/Api/Invitation/AcceptTest.php @@ -16,7 +16,7 @@ public function test() $token = $this->app->make(PasswordBrokerManager::class)->broker('user-invitations')->createToken($user); - $response = $this->json('post', 'api/invitation/accept', [ + $response = $this->json('post', 'invitation/accept', [ 'email' => $user->email, 'token' => $token, 'password' => 'kingscodedotnl', @@ -30,7 +30,7 @@ public function testWithNonExistentToken() { $user = factory(User::class)->create(); - $response = $this->json('post', 'api/invitation/accept', [ + $response = $this->json('post', 'invitation/accept', [ 'email' => $user->email, 'token' => 'does-not-exist', 'password' => 'kingscodedotnl', @@ -42,7 +42,7 @@ public function testWithNonExistentToken() public function testValidationErrors() { - $response = $this->json('post', 'api/invitation/accept'); + $response = $this->json('post', 'invitation/accept'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'token', 'email', 'password', diff --git a/tests/Feature/Http/Api/Password/ForgottenTest.php b/tests/Feature/Http/Api/Password/ForgottenTest.php index a9179d5..e1e2a50 100644 --- a/tests/Feature/Http/Api/Password/ForgottenTest.php +++ b/tests/Feature/Http/Api/Password/ForgottenTest.php @@ -19,7 +19,7 @@ public function testMailGetsSentForNonExistentUser() 'email' => 'info@kingscode.nl', ]); - $response = $this->json('post', 'api/password/forgotten', [ + $response = $this->json('post', 'password/forgotten', [ 'email' => 'info@kingscode.nl', ]); @@ -30,7 +30,7 @@ public function testMailGetsSentForNonExistentUser() public function testStatusIsOkEvenWhenUserDoesntExist() { - $response = $this->json('post', 'api/password/forgotten', [ + $response = $this->json('post', 'password/forgotten', [ 'email' => 'info@kingscode.nl', ]); @@ -39,7 +39,7 @@ public function testStatusIsOkEvenWhenUserDoesntExist() public function testValidationErrors() { - $response = $this->json('post', 'api/password/forgotten'); + $response = $this->json('post', 'password/forgotten'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'email', diff --git a/tests/Feature/Http/Api/Password/ResetTest.php b/tests/Feature/Http/Api/Password/ResetTest.php index 1b7e1a4..fa4bdc6 100644 --- a/tests/Feature/Http/Api/Password/ResetTest.php +++ b/tests/Feature/Http/Api/Password/ResetTest.php @@ -16,7 +16,7 @@ public function test() $token = $this->app->make(PasswordBrokerManager::class)->broker('users')->createToken($user); - $response = $this->json('post', 'api/password/reset', [ + $response = $this->json('post', 'password/reset', [ 'email' => $user->email, 'token' => $token, 'password' => 'kingscodedotnl', @@ -30,7 +30,7 @@ public function testWithNonExistentToken() { $user = factory(User::class)->create(); - $response = $this->json('post', 'api/password/reset', [ + $response = $this->json('post', 'password/reset', [ 'email' => $user->email, 'token' => 'does-not-exist', 'password' => 'kingscodedotnl', @@ -42,7 +42,7 @@ public function testWithNonExistentToken() public function testValidationErrors() { - $response = $this->json('post', 'api/password/reset'); + $response = $this->json('post', 'password/reset'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'token', 'email', 'password', diff --git a/tests/Feature/Http/Api/Profile/Email/UpdateTest.php b/tests/Feature/Http/Api/Profile/Email/UpdateTest.php index 1acc43b..28b9853 100644 --- a/tests/Feature/Http/Api/Profile/Email/UpdateTest.php +++ b/tests/Feature/Http/Api/Profile/Email/UpdateTest.php @@ -23,7 +23,7 @@ public function testVerifyEmailIsSent() 'email' => $email, ]); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/email', [ + $response = $this->actingAs($user, 'api')->json('put', 'profile/email', [ 'email' => $email, ]); @@ -46,7 +46,7 @@ public function testCantUpdateEmailIsSent() 'email' => 'yoink@dadoink.nl', ]); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/email', [ + $response = $this->actingAs($user, 'api')->json('put', 'profile/email', [ 'email' => $email, ]); @@ -59,7 +59,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/email'); + $response = $this->actingAs($user, 'api')->json('put', 'profile/email'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'email', diff --git a/tests/Feature/Http/Api/Profile/Email/VerifyTest.php b/tests/Feature/Http/Api/Profile/Email/VerifyTest.php index 1e9fa67..cc50f90 100644 --- a/tests/Feature/Http/Api/Profile/Email/VerifyTest.php +++ b/tests/Feature/Http/Api/Profile/Email/VerifyTest.php @@ -29,7 +29,7 @@ public function testVerification() $token = $this->dispensary->dispense($user, $email); - $response = $this->actingAs($user, 'api')->json('post', 'api/profile/email/verify', [ + $response = $this->actingAs($user, 'api')->json('post', 'profile/email/verify', [ 'email' => $email, 'token' => $token, ]); @@ -45,7 +45,7 @@ public function testBadRequestWhenWrongTokenPassed() $this->dispensary->dispense($user, $email); - $response = $this->actingAs($user, 'api')->json('post', 'api/profile/email/verify', [ + $response = $this->actingAs($user, 'api')->json('post', 'profile/email/verify', [ 'email' => $email, 'token' => 'zigzagzog', ]); @@ -66,7 +66,7 @@ public function testBadRequestTokenExpired() $cache->clear(); - $response = $this->actingAs($user, 'api')->json('post', 'api/profile/email/verify', [ + $response = $this->actingAs($user, 'api')->json('post', 'profile/email/verify', [ 'email' => $email, 'token' => $token, ]); @@ -78,7 +78,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('post', 'api/profile/email/verify'); + $response = $this->actingAs($user, 'api')->json('post', 'profile/email/verify'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'email', 'token', diff --git a/tests/Feature/Http/Api/Profile/Password/UpdateTest.php b/tests/Feature/Http/Api/Profile/Password/UpdateTest.php index b104c16..33e2d75 100644 --- a/tests/Feature/Http/Api/Profile/Password/UpdateTest.php +++ b/tests/Feature/Http/Api/Profile/Password/UpdateTest.php @@ -13,7 +13,7 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/password', [ + $response = $this->actingAs($user, 'api')->json('put', 'profile/password', [ 'password' => 'kingscodedotnl', 'password_confirmation' => 'kingscodedotnl', 'current_password' => 'secret', @@ -26,7 +26,7 @@ public function testCurrentPasswordIncorrect() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/password', [ + $response = $this->actingAs($user, 'api')->json('put', 'profile/password', [ 'password' => 'kingscodedotnl', 'password_confirmation' => 'kingscodedotnl', 'current_password' => 'secretiswrong', @@ -41,7 +41,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile/password'); + $response = $this->actingAs($user, 'api')->json('put', 'profile/password'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'password', 'current_password', diff --git a/tests/Feature/Http/Api/Profile/ShowTest.php b/tests/Feature/Http/Api/Profile/ShowTest.php index 1f87f10..f0d1d4e 100644 --- a/tests/Feature/Http/Api/Profile/ShowTest.php +++ b/tests/Feature/Http/Api/Profile/ShowTest.php @@ -12,7 +12,7 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('get', 'api/profile'); + $response = $this->actingAs($user, 'api')->json('get', 'profile'); $response->assertStatus(Response::HTTP_OK)->assertJson([ 'data' => [ diff --git a/tests/Feature/Http/Api/Profile/UpdateTest.php b/tests/Feature/Http/Api/Profile/UpdateTest.php index 4f14dd2..f76ffe6 100644 --- a/tests/Feature/Http/Api/Profile/UpdateTest.php +++ b/tests/Feature/Http/Api/Profile/UpdateTest.php @@ -15,7 +15,7 @@ public function test() 'email' => 'info@kingscode.nl', ]); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile', [ + $response = $this->actingAs($user, 'api')->json('put', 'profile', [ 'name' => 'King', ]); @@ -30,7 +30,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/profile'); + $response = $this->actingAs($user, 'api')->json('put', 'profile'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'name', diff --git a/tests/Feature/Http/Api/User/DestroyTest.php b/tests/Feature/Http/Api/User/DestroyTest.php index 3c360ea..86d8d39 100644 --- a/tests/Feature/Http/Api/User/DestroyTest.php +++ b/tests/Feature/Http/Api/User/DestroyTest.php @@ -14,7 +14,7 @@ public function test() $user1 = factory(User::class)->create(); $user2 = factory(User::class)->create(); - $response = $this->actingAs($user1, 'api')->json('delete', 'api/user/' . $user2->getKey()); + $response = $this->actingAs($user1, 'api')->json('delete', 'user/' . $user2->getKey()); $response->assertStatus(Response::HTTP_OK); diff --git a/tests/Feature/Http/Api/User/IndexTest.php b/tests/Feature/Http/Api/User/IndexTest.php index dab6ef2..e838b0d 100644 --- a/tests/Feature/Http/Api/User/IndexTest.php +++ b/tests/Feature/Http/Api/User/IndexTest.php @@ -13,9 +13,9 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('get', 'api/user'); + $response = $this->actingAs($user, 'api')->json('get', 'user'); - $response->assertStatus(Response::HTTP_OK)->assertJson([ + $response->assertStatus(Response::HTTP_OK)->assertExactJson([ 'data' => [ [ 'id' => $user->getKey(), @@ -23,6 +23,14 @@ public function test() 'email' => $user->email, ], ], + 'meta' => [ + 'current_page' => 1, + 'last_page' => 1, + 'from' => 1, + 'to' => 1, + 'total' => 1, + 'per_page' => 15, + ] ]); } @@ -35,7 +43,7 @@ public function testSearchingForName() 'name' => 'bbb', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?name=bbb'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?name=bbb'); $response->assertStatus(Response::HTTP_OK); @@ -51,7 +59,7 @@ public function testSearchingForEmail() 'email' => 'support@kingscode.nl', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?email=support'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?email=support'); $response->assertStatus(Response::HTTP_OK); @@ -67,7 +75,7 @@ public function testSortingByNameAsc() 'name' => 'aaa', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?sortBy=name&desc=0'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?sortBy=name&desc=0'); $response->assertStatus(Response::HTTP_OK); @@ -83,7 +91,7 @@ public function testSortingByNameDesc() 'name' => 'aaa', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?sortBy=name&desc=1'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?sortBy=name&desc=1'); $response->assertStatus(Response::HTTP_OK); @@ -99,7 +107,7 @@ public function testSortingByEmailAsc() 'email' => 'b@kingscode.nl', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?sortBy=email&desc=0'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?sortBy=email&desc=0'); $response->assertStatus(Response::HTTP_OK); @@ -115,7 +123,7 @@ public function testSortingByEmailDesc() 'email' => 'b@kingscode.nl', ]); - $response = $this->actingAs($user1, 'api')->json('get', 'api/user?sortBy=email&desc=1'); + $response = $this->actingAs($user1, 'api')->json('get', 'user?sortBy=email&desc=1'); $response->assertStatus(Response::HTTP_OK); diff --git a/tests/Feature/Http/Api/User/ShowTest.php b/tests/Feature/Http/Api/User/ShowTest.php index 443f83f..13e4ce0 100644 --- a/tests/Feature/Http/Api/User/ShowTest.php +++ b/tests/Feature/Http/Api/User/ShowTest.php @@ -13,7 +13,7 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('get', 'api/user/' . $user->getKey()); + $response = $this->actingAs($user, 'api')->json('get', 'user/' . $user->getKey()); $response->assertStatus(Response::HTTP_OK)->assertJson([ 'data' => [ diff --git a/tests/Feature/Http/Api/User/StoreTest.php b/tests/Feature/Http/Api/User/StoreTest.php index 76916a2..4c3651a 100644 --- a/tests/Feature/Http/Api/User/StoreTest.php +++ b/tests/Feature/Http/Api/User/StoreTest.php @@ -13,7 +13,7 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('post', 'api/user', [ + $response = $this->actingAs($user, 'api')->json('post', 'user', [ 'name' => 'Kings Code', 'email' => 'info@kingscode.nl', ]); @@ -30,7 +30,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('post', 'api/user'); + $response = $this->actingAs($user, 'api')->json('post', 'user'); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'name', 'email', diff --git a/tests/Feature/Http/Api/User/UpdateTest.php b/tests/Feature/Http/Api/User/UpdateTest.php index 6de46c6..68e7b60 100644 --- a/tests/Feature/Http/Api/User/UpdateTest.php +++ b/tests/Feature/Http/Api/User/UpdateTest.php @@ -13,7 +13,7 @@ public function test() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/user/' . $user->getKey(), [ + $response = $this->actingAs($user, 'api')->json('put', 'user/' . $user->getKey(), [ 'name' => 'Kings Code', 'email' => 'info@kingscode.nl', ]); @@ -31,7 +31,7 @@ public function testValidationErrors() { $user = factory(User::class)->create(); - $response = $this->actingAs($user, 'api')->json('put', 'api/user/' . $user->getKey()); + $response = $this->actingAs($user, 'api')->json('put', 'user/' . $user->getKey()); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)->assertJsonValidationErrors([ 'name', 'email',