From d6cdb82982d72bbd782b8645cc6b79e5068f274e Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 18 Sep 2025 17:14:42 +0200 Subject: [PATCH 1/7] Support custom gate denial messages in authorization Refactored authorization logic to use Gate::inspect and return Response objects, allowing custom denial messages from policies. Updated Response handling to extract messages or allowed status for gates, and added a test and policy to verify custom messages are returned for denied actions. --- src/Concerns/Authorizable.php | 4 +- src/Http/Response.php | 23 +++-- .../Controllers/AutomaticGatingTest.php | 39 +++++++++ .../Support/Policies/RedPolicyWithMessage.php | 87 +++++++++++++++++++ 4 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 tests/Support/Policies/RedPolicyWithMessage.php diff --git a/src/Concerns/Authorizable.php b/src/Concerns/Authorizable.php index 07bad2b..6dac45d 100644 --- a/src/Concerns/Authorizable.php +++ b/src/Concerns/Authorizable.php @@ -58,13 +58,13 @@ public function authorizeTo($ability, $model) * @param string $ability * @param Model|string $model * - * @return bool + * @return Response */ public function authorizedTo($ability, $model) { if ($this->isAuthorizingEnabled()) { $resolver = function () use ($ability, $model) { - return Gate::check($ability, $model); + return Gate::inspect($ability, $model); }; if ($this->isAuthorizationCacheEnabled()) { diff --git a/src/Http/Response.php b/src/Http/Response.php index 83428fd..187e9cc 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -32,12 +32,17 @@ public function resource(Resource $resource) protected function buildGatesForModel(Model $model, Resource $resource, array $gates) { + $authorizedToView = $resource->authorizedTo('view', $model); + $authorizedToUpdate = $resource->authorizedTo('update', $model); + $authorizedToDelete = $resource->authorizedTo('delete', $model); + $authorizedToRestore = $resource->authorizedTo('restore', $model); + $authorizedToForceDelete = $resource->authorizedTo('forceDelete', $model); return array_merge( - in_array('view', $gates) ? [config('rest.gates.names.authorized_to_view') => $resource->authorizedTo('view', $model)] : [], - in_array('update', $gates) ? [config('rest.gates.names.authorized_to_update') => $resource->authorizedTo('update', $model)] : [], - in_array('delete', $gates) ? [config('rest.gates.names.authorized_to_delete') => $resource->authorizedTo('delete', $model)] : [], - in_array('restore', $gates) ? [config('rest.gates.names.authorized_to_restore') => $resource->authorizedTo('restore', $model)] : [], - in_array('forceDelete', $gates) ? [config('rest.gates.names.authorized_to_force_delete') => $resource->authorizedTo('forceDelete', $model)] : [], + in_array('view', $gates) ? [config('rest.gates.names.authorized_to_view') => $authorizedToView->message() ?? $authorizedToView->allowed()] : [], + in_array('update', $gates) ? [config('rest.gates.names.authorized_to_update') => $authorizedToUpdate->message() ?? $authorizedToUpdate->allowed()] : [], + in_array('delete', $gates) ? [config('rest.gates.names.authorized_to_delete') => $authorizedToDelete->message() ?? $authorizedToDelete->allowed()] : [], + in_array('restore', $gates) ? [config('rest.gates.names.authorized_to_restore') => $authorizedToRestore->message() ?? $authorizedToRestore->allowed()] : [], + in_array('forceDelete', $gates) ? [config('rest.gates.names.authorized_to_force_delete') => $authorizedToForceDelete->message() ?? $authorizedToForceDelete->allowed()] : [], ); } @@ -126,15 +131,19 @@ public function modelToResponse(Model $model, Resource $resource, array $request public function toResponse($request) { if ($this->responsable instanceof LengthAwarePaginator) { + if ($this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', []))) { + $authorizedToCreate = $this->resource->authorizedTo('create', $this->resource::newModel()::class); + } + $restLengthAwarePaginator = new \Lomkit\Rest\Pagination\LengthAwarePaginator( $this->responsable->items(), $this->responsable->total(), $this->responsable->perPage(), $this->responsable->currentPage(), $this->responsable->getOptions(), - $this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', [])) ? [ + isset($authorizedToCreate) ? [ config('rest.gates.key') => [ - config('rest.gates.names.authorized_to_create') => $this->resource->authorizedTo('create', $this->resource::newModel()::class), + config('rest.gates.names.authorized_to_create') => $authorizedToCreate->message() ?? $authorizedToCreate->allowed(), ], ] : [] ); diff --git a/tests/Feature/Controllers/AutomaticGatingTest.php b/tests/Feature/Controllers/AutomaticGatingTest.php index 994598a..900d360 100644 --- a/tests/Feature/Controllers/AutomaticGatingTest.php +++ b/tests/Feature/Controllers/AutomaticGatingTest.php @@ -15,6 +15,7 @@ use Lomkit\Rest\Tests\Support\Policies\DeletePolicy; use Lomkit\Rest\Tests\Support\Policies\ForceDeletePolicy; use Lomkit\Rest\Tests\Support\Policies\GreenPolicy; +use Lomkit\Rest\Tests\Support\Policies\RedPolicyWithMessage; use Lomkit\Rest\Tests\Support\Policies\RestorePolicy; use Lomkit\Rest\Tests\Support\Policies\UpdatePolicy; use Lomkit\Rest\Tests\Support\Policies\ViewPolicy; @@ -62,6 +63,44 @@ public function test_searching_automatic_gated_resource(): void ); } + public function test_searching_automatic_gated_resource_and_custom_message(): void + { + $model = ModelFactory::new() + ->create(); + + Gate::policy(Model::class, RedPolicyWithMessage::class); + + $response = $this->post( + '/api/automatic-gating/search', + [ + 'search' => [ + 'gates' => ['create', 'view', 'update', 'delete', 'forceDelete', 'restore'], + ], + ], + ['Accept' => 'application/json'] + ); + + $this->assertResourcePaginated( + $response, + [$model], + new AutomaticGatingResource(), + [ + [ + 'gates' => [ + 'authorized_to_view' => 'You don\'t have permission to view user', + 'authorized_to_update' => 'You don\'t have permission to update user', + 'authorized_to_delete' => 'You don\'t have permission to delete user', + 'authorized_to_restore' => 'You don\'t have permission to restore user', + 'authorized_to_force_delete' => 'You don\'t have permission to force delete user', + ], + ], + ] + ); + $response->assertJson( + ['meta' => ['gates' => ['authorized_to_create' => true]]] + ); + } + public function test_searching_automatic_gated_resource_with_global_config_disabled(): void { $model = ModelFactory::new() diff --git a/tests/Support/Policies/RedPolicyWithMessage.php b/tests/Support/Policies/RedPolicyWithMessage.php new file mode 100644 index 0000000..d48a59b --- /dev/null +++ b/tests/Support/Policies/RedPolicyWithMessage.php @@ -0,0 +1,87 @@ + Date: Thu, 18 Sep 2025 15:14:54 +0000 Subject: [PATCH 2/7] Apply fixes from StyleCI --- src/Http/Response.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Response.php b/src/Http/Response.php index 187e9cc..2f23b2e 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -37,6 +37,7 @@ protected function buildGatesForModel(Model $model, Resource $resource, array $g $authorizedToDelete = $resource->authorizedTo('delete', $model); $authorizedToRestore = $resource->authorizedTo('restore', $model); $authorizedToForceDelete = $resource->authorizedTo('forceDelete', $model); + return array_merge( in_array('view', $gates) ? [config('rest.gates.names.authorized_to_view') => $authorizedToView->message() ?? $authorizedToView->allowed()] : [], in_array('update', $gates) ? [config('rest.gates.names.authorized_to_update') => $authorizedToUpdate->message() ?? $authorizedToUpdate->allowed()] : [], From a43852dcc45c306129d875997d4db85e6168f259 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 18 Sep 2025 17:35:54 +0200 Subject: [PATCH 3/7] code rabbit --- src/Concerns/Authorizable.php | 6 ++-- src/Http/Response.php | 28 +++++++++++-------- .../Controllers/AutomaticGatingTest.php | 5 ++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Concerns/Authorizable.php b/src/Concerns/Authorizable.php index 6dac45d..ffa3e9f 100644 --- a/src/Concerns/Authorizable.php +++ b/src/Concerns/Authorizable.php @@ -86,15 +86,15 @@ public function authorizedTo($ability, $model) return $resolver(); } - return true; + return Response::allow(); } /** * Determine if the current user has a given ability. * * @param string $ability - * * @param Model $model - * * @param string $toActionModel + * @param Model $model + * @param string $toActionModel * * @throws \Illuminate\Auth\Access\AuthorizationException * diff --git a/src/Http/Response.php b/src/Http/Response.php index 2f23b2e..b3f5a7b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -32,19 +32,23 @@ public function resource(Resource $resource) protected function buildGatesForModel(Model $model, Resource $resource, array $gates) { - $authorizedToView = $resource->authorizedTo('view', $model); - $authorizedToUpdate = $resource->authorizedTo('update', $model); - $authorizedToDelete = $resource->authorizedTo('delete', $model); - $authorizedToRestore = $resource->authorizedTo('restore', $model); - $authorizedToForceDelete = $resource->authorizedTo('forceDelete', $model); + $nameMap = [ + 'view' => config('rest.gates.names.authorized_to_view'), + 'update' => config('rest.gates.names.authorized_to_update'), + 'delete' => config('rest.gates.names.authorized_to_delete'), + 'restore' => config('rest.gates.names.authorized_to_restore'), + 'forceDelete' => config('rest.gates.names.authorized_to_force_delete'), + ]; - return array_merge( - in_array('view', $gates) ? [config('rest.gates.names.authorized_to_view') => $authorizedToView->message() ?? $authorizedToView->allowed()] : [], - in_array('update', $gates) ? [config('rest.gates.names.authorized_to_update') => $authorizedToUpdate->message() ?? $authorizedToUpdate->allowed()] : [], - in_array('delete', $gates) ? [config('rest.gates.names.authorized_to_delete') => $authorizedToDelete->message() ?? $authorizedToDelete->allowed()] : [], - in_array('restore', $gates) ? [config('rest.gates.names.authorized_to_restore') => $authorizedToRestore->message() ?? $authorizedToRestore->allowed()] : [], - in_array('forceDelete', $gates) ? [config('rest.gates.names.authorized_to_force_delete') => $authorizedToForceDelete->message() ?? $authorizedToForceDelete->allowed()] : [], - ); + $result = []; + foreach ($gates as $gate) { + if (isset($nameMap[$gate])) { + $auth = $resource->authorizedTo($gate, $model); + $result[$nameMap[$gate]] = $auth->message() ?? $auth->allowed(); + } + } + + return $result; } /** diff --git a/tests/Feature/Controllers/AutomaticGatingTest.php b/tests/Feature/Controllers/AutomaticGatingTest.php index 900d360..7b4fa6f 100644 --- a/tests/Feature/Controllers/AutomaticGatingTest.php +++ b/tests/Feature/Controllers/AutomaticGatingTest.php @@ -96,8 +96,9 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo ], ] ); - $response->assertJson( - ['meta' => ['gates' => ['authorized_to_create' => true]]] + $response->assertJsonPath( + 'meta.gates.authorized_to_create', + 'You don\'t have permission to create user' ); } From 6df35fc0eb6e302c10436df11a80b0cd02bf225e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 18 Sep 2025 15:36:04 +0000 Subject: [PATCH 4/7] Apply fixes from StyleCI --- src/Concerns/Authorizable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/Authorizable.php b/src/Concerns/Authorizable.php index ffa3e9f..76e0015 100644 --- a/src/Concerns/Authorizable.php +++ b/src/Concerns/Authorizable.php @@ -93,7 +93,7 @@ public function authorizedTo($ability, $model) * Determine if the current user has a given ability. * * @param string $ability - * @param Model $model + * @param Model $model * @param string $toActionModel * * @throws \Illuminate\Auth\Access\AuthorizationException From 1706a61d49ae2ea1fcd00f43b47302bb2325a3ab Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 18 Sep 2025 22:52:34 +0200 Subject: [PATCH 5/7] set this as a config --- config/rest.php | 3 ++ src/Http/Response.php | 41 +++++++++++-------- .../Controllers/AutomaticGatingTest.php | 32 ++++++++++++--- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/config/rest.php b/config/rest.php index db0ce45..aca625b 100644 --- a/config/rest.php +++ b/config/rest.php @@ -14,6 +14,9 @@ 'gates' => [ 'enabled' => true, 'key' => 'gates', + 'message' => [ + 'enabled' => false + ], // Here you can customize the keys for each gate 'names' => [ 'authorized_to_view' => 'authorized_to_view', diff --git a/src/Http/Response.php b/src/Http/Response.php index b3f5a7b..b0d46bc 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -30,9 +30,10 @@ public function resource(Resource $resource) }); } - protected function buildGatesForModel(Model $model, Resource $resource, array $gates) + protected function buildGatesForModel(Model|string $model, Resource $resource, array $gates) { $nameMap = [ + 'create' => config('rest.gates.names.authorized_to_create'), 'view' => config('rest.gates.names.authorized_to_view'), 'update' => config('rest.gates.names.authorized_to_update'), 'delete' => config('rest.gates.names.authorized_to_delete'), @@ -41,10 +42,23 @@ protected function buildGatesForModel(Model $model, Resource $resource, array $g ]; $result = []; - foreach ($gates as $gate) { - if (isset($nameMap[$gate])) { - $auth = $resource->authorizedTo($gate, $model); - $result[$nameMap[$gate]] = $auth->message() ?? $auth->allowed(); + + if (config('rest.gates.message.enabled', false)) { + foreach ($gates as $gate) { + if (isset($nameMap[$gate])) { + $authorizedTo = $resource->authorizedTo($gate, $model); + $result[$nameMap[$gate]]['allowed'] = $authorizedTo->allowed(); + $result[$nameMap[$gate]]['message'] = $authorizedTo->message(); + } + } + } else { + // TODO: put the good anchor to link to the new method (link in the string) + trigger_deprecation('lomkit/laravel-rest-api', '2.17.0', 'In Laravel Rest Api 3 it won\'t be possible to use the old gate schema, please upgrade as quickly as possible. See: https://laravel-rest-api.lomkit.com/digging-deeper/gates#policy-message-in-gates'); + foreach ($gates as $gate) { + if (isset($nameMap[$gate])) { + $authorizedTo = $resource->authorizedTo($gate, $model); + $result[$nameMap[$gate]] = $authorizedTo->allowed(); + } } } @@ -87,9 +101,10 @@ public function modelToResponse(Model $model, Resource $resource, array $request ) ) ->when($resource->isGatingEnabled() && isset($currentRequestArray['gates']), function ($attributes) use ($currentRequestArray, $resource, $model) { + $currentRequestArrayWithoutCreate = collect($currentRequestArray['gates'])->reject(fn ($value) => $value === 'create')->toArray(); return $attributes->put( config('rest.gates.key'), - $this->buildGatesForModel($model, $resource, $currentRequestArray['gates']) + $this->buildGatesForModel($model, $resource, $currentRequestArrayWithoutCreate) ); }) ->toArray(), @@ -136,20 +151,14 @@ public function modelToResponse(Model $model, Resource $resource, array $request public function toResponse($request) { if ($this->responsable instanceof LengthAwarePaginator) { - if ($this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', []))) { - $authorizedToCreate = $this->resource->authorizedTo('create', $this->resource::newModel()::class); - } - $restLengthAwarePaginator = new \Lomkit\Rest\Pagination\LengthAwarePaginator( $this->responsable->items(), $this->responsable->total(), $this->responsable->perPage(), $this->responsable->currentPage(), $this->responsable->getOptions(), - isset($authorizedToCreate) ? [ - config('rest.gates.key') => [ - config('rest.gates.names.authorized_to_create') => $authorizedToCreate->message() ?? $authorizedToCreate->allowed(), - ], + $this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', [])) ? [ + config('rest.gates.key') => $this->buildGatesForModel($this->resource::newModel()::class, $this->resource, ['create']), ] : [] ); @@ -168,9 +177,7 @@ public function toResponse($request) 'data' => $data ?? $this->map($this->responsable, $this->modelToResponse($this->responsable, $this->resource, $request->input('search', []))), 'meta' => array_merge( $this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', [])) ? [ - config('rest.gates.key') => [ - config('rest.gates.names.authorized_to_create') => $this->resource->authorizedTo('create', $this->resource::newModel()::class), - ], + config('rest.gates.key') => $this->buildGatesForModel($this->resource::newModel()::class, $this->resource, ['create']), ] : [] ), ]; diff --git a/tests/Feature/Controllers/AutomaticGatingTest.php b/tests/Feature/Controllers/AutomaticGatingTest.php index 7b4fa6f..5a7d4e7 100644 --- a/tests/Feature/Controllers/AutomaticGatingTest.php +++ b/tests/Feature/Controllers/AutomaticGatingTest.php @@ -70,6 +70,8 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo Gate::policy(Model::class, RedPolicyWithMessage::class); + config(['rest.gates.message.enabled' => true]); + $response = $this->post( '/api/automatic-gating/search', [ @@ -87,18 +89,36 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo [ [ 'gates' => [ - 'authorized_to_view' => 'You don\'t have permission to view user', - 'authorized_to_update' => 'You don\'t have permission to update user', - 'authorized_to_delete' => 'You don\'t have permission to delete user', - 'authorized_to_restore' => 'You don\'t have permission to restore user', - 'authorized_to_force_delete' => 'You don\'t have permission to force delete user', + 'authorized_to_view' => [ + 'allowed' => false, + 'message' => 'You don\'t have permission to view user' + ], + 'authorized_to_update' => [ + 'allowed' => false, + 'message' => 'You don\'t have permission to update user' + ], + 'authorized_to_delete' => [ + 'allowed' => false, + 'message' => 'You don\'t have permission to delete user' + ], + 'authorized_to_restore' => [ + 'allowed' => false, + 'message' => 'You don\'t have permission to restore user' + ], + 'authorized_to_force_delete' => [ + 'allowed' => false, + 'message' => 'You don\'t have permission to force delete user' + ], ], ], ] ); $response->assertJsonPath( 'meta.gates.authorized_to_create', - 'You don\'t have permission to create user' + [ + 'allowed' => false, + 'message' => 'You don\'t have permission to create user' + ] ); } From cf86fba45c4d74e3045291894b181aa3990419aa Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 18 Sep 2025 20:53:02 +0000 Subject: [PATCH 6/7] Apply fixes from StyleCI --- config/rest.php | 2 +- src/Http/Response.php | 1 + tests/Feature/Controllers/AutomaticGatingTest.php | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/config/rest.php b/config/rest.php index aca625b..f58b5de 100644 --- a/config/rest.php +++ b/config/rest.php @@ -15,7 +15,7 @@ 'enabled' => true, 'key' => 'gates', 'message' => [ - 'enabled' => false + 'enabled' => false, ], // Here you can customize the keys for each gate 'names' => [ diff --git a/src/Http/Response.php b/src/Http/Response.php index b0d46bc..177e59b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -102,6 +102,7 @@ public function modelToResponse(Model $model, Resource $resource, array $request ) ->when($resource->isGatingEnabled() && isset($currentRequestArray['gates']), function ($attributes) use ($currentRequestArray, $resource, $model) { $currentRequestArrayWithoutCreate = collect($currentRequestArray['gates'])->reject(fn ($value) => $value === 'create')->toArray(); + return $attributes->put( config('rest.gates.key'), $this->buildGatesForModel($model, $resource, $currentRequestArrayWithoutCreate) diff --git a/tests/Feature/Controllers/AutomaticGatingTest.php b/tests/Feature/Controllers/AutomaticGatingTest.php index 5a7d4e7..992cb30 100644 --- a/tests/Feature/Controllers/AutomaticGatingTest.php +++ b/tests/Feature/Controllers/AutomaticGatingTest.php @@ -91,23 +91,23 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo 'gates' => [ 'authorized_to_view' => [ 'allowed' => false, - 'message' => 'You don\'t have permission to view user' + 'message' => 'You don\'t have permission to view user', ], 'authorized_to_update' => [ 'allowed' => false, - 'message' => 'You don\'t have permission to update user' + 'message' => 'You don\'t have permission to update user', ], 'authorized_to_delete' => [ 'allowed' => false, - 'message' => 'You don\'t have permission to delete user' + 'message' => 'You don\'t have permission to delete user', ], 'authorized_to_restore' => [ 'allowed' => false, - 'message' => 'You don\'t have permission to restore user' + 'message' => 'You don\'t have permission to restore user', ], 'authorized_to_force_delete' => [ 'allowed' => false, - 'message' => 'You don\'t have permission to force delete user' + 'message' => 'You don\'t have permission to force delete user', ], ], ], @@ -117,7 +117,7 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo 'meta.gates.authorized_to_create', [ 'allowed' => false, - 'message' => 'You don\'t have permission to create user' + 'message' => 'You don\'t have permission to create user', ] ); } From 585c98e237515e9dcc5a803286235dfeefc93f08 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 18 Sep 2025 23:12:11 +0200 Subject: [PATCH 7/7] code rabbit --- src/Http/Response.php | 1 - .../Controllers/AutomaticGatingTest.php | 59 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index b0d46bc..c7975d9 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -52,7 +52,6 @@ protected function buildGatesForModel(Model|string $model, Resource $resource, a } } } else { - // TODO: put the good anchor to link to the new method (link in the string) trigger_deprecation('lomkit/laravel-rest-api', '2.17.0', 'In Laravel Rest Api 3 it won\'t be possible to use the old gate schema, please upgrade as quickly as possible. See: https://laravel-rest-api.lomkit.com/digging-deeper/gates#policy-message-in-gates'); foreach ($gates as $gate) { if (isset($nameMap[$gate])) { diff --git a/tests/Feature/Controllers/AutomaticGatingTest.php b/tests/Feature/Controllers/AutomaticGatingTest.php index 5a7d4e7..23bf22b 100644 --- a/tests/Feature/Controllers/AutomaticGatingTest.php +++ b/tests/Feature/Controllers/AutomaticGatingTest.php @@ -122,6 +122,65 @@ public function test_searching_automatic_gated_resource_and_custom_message(): vo ); } + public function test_searching_automatic_gated_resource_with_allowed_gates_and_custom_message(): void + { + $model = ModelFactory::new() + ->create(); + + Gate::policy(Model::class, GreenPolicy::class); + + config(['rest.gates.message.enabled' => true]); + + $response = $this->post( + '/api/automatic-gating/search', + [ + 'search' => [ + 'gates' => ['create', 'view', 'update', 'delete', 'forceDelete', 'restore'], + ], + ], + ['Accept' => 'application/json'] + ); + + $this->assertResourcePaginated( + $response, + [$model], + new AutomaticGatingResource(), + [ + [ + 'gates' => [ + 'authorized_to_view' => [ + 'allowed' => true, + 'message' => null, + ], + 'authorized_to_update' => [ + 'allowed' => true, + 'message' => null, + ], + 'authorized_to_delete' => [ + 'allowed' => true, + 'message' => null, + ], + 'authorized_to_restore' => [ + 'allowed' => true, + 'message' => null, + ], + 'authorized_to_force_delete' => [ + 'allowed' => true, + 'message' => null, + ], + ], + ], + ] + ); + $response->assertJsonPath( + 'meta.gates.authorized_to_create', + [ + 'allowed' => true, + 'message' => null, + ] + ); + } + public function test_searching_automatic_gated_resource_with_global_config_disabled(): void { $model = ModelFactory::new()