From 9755f62bcb614b56508135109cc8be0b7b00d00f Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Wed, 20 Aug 2025 18:45:29 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20include=20in=20include?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rules/Search/SearchInclude.php | 3 - ...chIncludingRelationshipsOperationsTest.php | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Rules/Search/SearchInclude.php b/src/Rules/Search/SearchInclude.php index 42ffce8..a11bb66 100644 --- a/src/Rules/Search/SearchInclude.php +++ b/src/Rules/Search/SearchInclude.php @@ -25,9 +25,6 @@ public function buildValidationRules(string $attribute, mixed $value): array 'required', (new ResourceRelationOrNested())->setResource($this->resource), ], - $attribute.'.includes' => [ - 'prohibited', - ], $attribute.'.text' => [ 'prohibited', ], diff --git a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php index 6a01789..65ea9f7 100644 --- a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php +++ b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php @@ -290,6 +290,62 @@ public function test_getting_a_list_of_resources_including_belongs_to_has_many_r ); } + public function test_getting_a_list_of_resources_including_belongs_to_has_many_relation_using_nested_include(): void + { + $belongsTo = BelongsToRelationFactory::new()->create(); + $matchingModel = ModelFactory::new() + ->for($belongsTo) + ->create()->fresh(); + + $matchingModel2 = ModelFactory::new()->create()->fresh(); + + Gate::policy(Model::class, GreenPolicy::class); + Gate::policy(BelongsToRelation::class, GreenPolicy::class); + + $response = $this->post( + '/api/models/search', + [ + 'search' => [ + 'includes' => [ + [ + 'relation' => 'belongsToRelation', + 'includes' => [ + ['relation' => 'models'] + ] + ], + ], + ], + ], + ['Accept' => 'application/json'] + ); + + $matchingModelBelongsToRelation = $matchingModel->belongsToRelation; + + $this->assertResourcePaginated( + $response, + [$matchingModel, $matchingModel2], + new ModelResource(), + [ + [ + 'belongs_to_relation' => array_merge( + $matchingModelBelongsToRelation + ->only((new BelongsToResource())->getFields(app()->make(RestRequest::class))), + [ + 'models' => $matchingModelBelongsToRelation->models + ->map(function ($model) { + return $model->only((new ModelResource())->getFields(app()->make(RestRequest::class))); + }) + ->toArray(), + ] + ), + ], + [ + 'belongs_to_relation' => null, + ], + ] + ); + } + public function test_getting_a_list_of_resources_including_distant_relation_with_intermediary_search_query_condition(): void { $matchingModel = ModelFactory::new()->create( From aed897ba9de2fc1b945208716154f19f32e4d4c6 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 20 Aug 2025 16:45:39 +0000 Subject: [PATCH 2/3] Apply fixes from StyleCI --- .../SearchIncludingRelationshipsOperationsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php index 65ea9f7..30a2129 100644 --- a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php +++ b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php @@ -310,8 +310,8 @@ public function test_getting_a_list_of_resources_including_belongs_to_has_many_r [ 'relation' => 'belongsToRelation', 'includes' => [ - ['relation' => 'models'] - ] + ['relation' => 'models'], + ], ], ], ], From b7b5f65b8a7bcbb9c20f5bee14bcad9018be8b93 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Wed, 20 Aug 2025 18:54:03 +0200 Subject: [PATCH 3/3] ai feedback --- ...chIncludingRelationshipsOperationsTest.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php index 30a2129..890aadc 100644 --- a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php +++ b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php @@ -331,7 +331,9 @@ public function test_getting_a_list_of_resources_including_belongs_to_has_many_r $matchingModelBelongsToRelation ->only((new BelongsToResource())->getFields(app()->make(RestRequest::class))), [ - 'models' => $matchingModelBelongsToRelation->models + 'models' => $matchingModelBelongsToRelation->models() + ->orderBy('id') + ->get() ->map(function ($model) { return $model->only((new ModelResource())->getFields(app()->make(RestRequest::class))); }) @@ -346,6 +348,32 @@ public function test_getting_a_list_of_resources_including_belongs_to_has_many_r ); } + public function test_including_unauthorized_nested_relation_returns_validation_error(): void + { + Gate::policy(Model::class, GreenPolicy::class); + Gate::policy(BelongsToRelation::class, GreenPolicy::class); + + $response = $this->post( + '/api/models/search', + [ + 'search' => [ + 'includes' => [ + [ + 'relation' => 'belongsToRelation', + 'includes' => [ + ['relation' => 'unauthorized'], + ], + ], + ], + ], + ], + ['Accept' => 'application/json'] + ); + + $response->assertStatus(422); + $response->assertExactJsonStructure(['message', 'errors' => ['search.includes.0.includes.0.relation']]); + } + public function test_getting_a_list_of_resources_including_distant_relation_with_intermediary_search_query_condition(): void { $matchingModel = ModelFactory::new()->create(