From 8d1d612ffdb38c4bdaf8feac98a819070106a91b Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 13:14:27 -0700 Subject: [PATCH 1/8] feat: add the ability to create API routes with multiple route parameters --- src/Concerns/HandlesStandardOperations.php | 4 ++- src/Contracts/KeyResolver.php | 14 ++++++++++ src/Drivers/Standard/KeyResolver.php | 23 +++++++++++++++ src/Http/Controllers/BaseController.php | 26 +++++++++++++++++ src/OrionServiceProvider.php | 3 +- tests/Feature/StandardShowOperationsTest.php | 28 +++++++++++++++++++ tests/Feature/TestCase.php | 14 +++++++++- .../Drivers/TwoRouteParameterKeyResolver.php | 24 ++++++++++++++++ .../app/Providers/OrionServiceProvider.php | 2 ++ tests/Fixtures/routes/api.php | 4 +++ .../Http/Controllers/BaseControllerTest.php | 7 ++++- .../Controllers/RelationControllerTest.php | 6 ++++ 12 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 src/Contracts/KeyResolver.php create mode 100644 src/Drivers/Standard/KeyResolver.php create mode 100644 tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php diff --git a/src/Concerns/HandlesStandardOperations.php b/src/Concerns/HandlesStandardOperations.php index 5a36b042..c8fee955 100644 --- a/src/Concerns/HandlesStandardOperations.php +++ b/src/Concerns/HandlesStandardOperations.php @@ -334,12 +334,14 @@ protected function afterStore(Request $request, Model $entity) * @throws AuthorizationException * @throws BindingResolutionException */ - public function show(Request $request, $key) + public function show(Request $request, ...$args) { $requestedRelations = $this->relationsResolver->requestedRelations($request); $query = $this->buildShowFetchQuery($request, $requestedRelations); + $key = $this->keyResolver->resolveStandardOperationKey($request, $args); + $beforeHookResult = $this->beforeShow($request, $key); if ($this->hookResponds($beforeHookResult)) { return $beforeHookResult; diff --git a/src/Contracts/KeyResolver.php b/src/Contracts/KeyResolver.php new file mode 100644 index 00000000..870636c1 --- /dev/null +++ b/src/Contracts/KeyResolver.php @@ -0,0 +1,14 @@ + $this instanceof RelationController, ] ); + $this->keyResolver = App::make(KeyResolver::class); $this->resolveComponents(); $this->bindComponents(); @@ -484,6 +491,25 @@ public function setRelationsResolver(RelationsResolver $relationsResolver): self return $this; } + /** + * @return KeyResolver + */ + public function getKeyResolver(): KeyResolver + { + return $this->keyResolver; + } + + /** + * @param KeyResolver $keyResolver + * @return $this + */ + public function setKeyResolver(KeyResolver $keyResolver): self + { + $this->keyResolver = $keyResolver; + + return $this; + } + /** * @return Paginator */ diff --git a/src/OrionServiceProvider.php b/src/OrionServiceProvider.php index d97fb202..06ff705f 100644 --- a/src/OrionServiceProvider.php +++ b/src/OrionServiceProvider.php @@ -5,6 +5,7 @@ use Illuminate\Support\ServiceProvider; use Orion\Commands\BuildSpecsCommand; use Orion\Contracts\ComponentsResolver; +use Orion\Contracts\KeyResolver; use Orion\Contracts\Paginator; use Orion\Contracts\ParamsValidator; use Orion\Contracts\QueryBuilder; @@ -29,6 +30,7 @@ public function register() $this->app->bind(Paginator::class, Drivers\Standard\Paginator::class); $this->app->bind(SearchBuilder::class, Drivers\Standard\SearchBuilder::class); $this->app->bind(ComponentsResolver::class, Drivers\Standard\ComponentsResolver::class); + $this->app->bind(KeyResolver::class, Drivers\Standard\KeyResolver::class); $this->app->singleton(ResourcesCacheStore::class); } @@ -42,7 +44,6 @@ public function boot() { app('router')->pushMiddlewareToGroup('api', EnforceExpectsJson::class); - $this->publishes( [ __DIR__ . '/../config/orion.php' => config_path('orion.php'), diff --git a/tests/Feature/StandardShowOperationsTest.php b/tests/Feature/StandardShowOperationsTest.php index 46c8fc11..51bbda90 100644 --- a/tests/Feature/StandardShowOperationsTest.php +++ b/tests/Feature/StandardShowOperationsTest.php @@ -5,6 +5,8 @@ use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; +use Orion\Tests\Fixtures\App\Http\Resources\SampleCollectionResource; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; use Orion\Tests\Fixtures\App\Models\Comment; @@ -166,4 +168,30 @@ public function getting_a_single_resource_with_included_relation_whitelisted_by_ $this->assertResourceShown($response, $team->fresh('company')->toArray()); } + + /** @test */ + public function getting_a_single_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $team = factory(Team::class)->create(); + + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->get("/api/v1/teams/$team->id"); + + $this->assertResourceShown($response, $team->fresh()->toArray()); + } + + /** @test */ + public function getting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $team = factory(Team::class)->create(); + + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->get("/api/v1/teams/$team->id"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/TestCase.php b/tests/Feature/TestCase.php index a2a8cbcc..204cc818 100644 --- a/tests/Feature/TestCase.php +++ b/tests/Feature/TestCase.php @@ -4,15 +4,17 @@ use Mockery; use Orion\Contracts\ComponentsResolver; +use Orion\Contracts\KeyResolver; use Orion\Testing\InteractsWithAuthorization; use Orion\Testing\InteractsWithJsonFields; use Orion\Testing\InteractsWithResources; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Models\User; use Orion\Tests\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - use InteractsWithResources, InteractsWithJsonFields, InteractsWithAuthorization; + use InteractsWithAuthorization, InteractsWithJsonFields, InteractsWithResources; protected function setUp(): void { @@ -86,4 +88,14 @@ function () use ($collectionResourceClass) { return $this; } + + protected function useKeyResolver(string $keyResolverClass): self + { + app()->bind( + KeyResolver::class, + TwoRouteParameterKeyResolver::class + ); + + return $this; + } } diff --git a/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php b/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php new file mode 100644 index 00000000..dae5e16c --- /dev/null +++ b/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php @@ -0,0 +1,24 @@ +app->bind(Paginator::class, \Orion\Drivers\Standard\Paginator::class); $this->app->bind(SearchBuilder::class, \Orion\Drivers\Standard\SearchBuilder::class); $this->app->bind(ComponentsResolver::class, \Orion\Drivers\Standard\ComponentsResolver::class); + $this->app->bind(KeyResolver::class, \Orion\Drivers\Standard\KeyResolver::class); $this->app->singleton(ResourcesCacheStore::class); } diff --git a/tests/Fixtures/routes/api.php b/tests/Fixtures/routes/api.php index 72dd82be..a3607cb9 100644 --- a/tests/Fixtures/routes/api.php +++ b/tests/Fixtures/routes/api.php @@ -33,4 +33,8 @@ Orion::hasManyResource('access_keys', 'scopes', AccessKeyAccessKeyScopesController::class)->withSoftDeletes(); Orion::belongsToManyResource('users', 'roles', UserRolesController::class); Orion::belongsToManyResource('users', 'notifications', UserNotificationsController::class)->withSoftDeletes(); + + Route::group(['prefix' => '{apiVersion}'], function () { + Orion::resource('teams', TeamsController::class); + }); }); diff --git a/tests/Unit/Http/Controllers/BaseControllerTest.php b/tests/Unit/Http/Controllers/BaseControllerTest.php index b023988a..71d23943 100644 --- a/tests/Unit/Http/Controllers/BaseControllerTest.php +++ b/tests/Unit/Http/Controllers/BaseControllerTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\App; use Mockery; use Orion\Drivers\Standard\ComponentsResolver; +use Orion\Drivers\Standard\KeyResolver; use Orion\Drivers\Standard\Paginator; use Orion\Drivers\Standard\ParamsValidator; use Orion\Drivers\Standard\QueryBuilder; @@ -25,7 +26,6 @@ class BaseControllerTest extends TestCase { - /** @test */ public function binding_exception_is_thrown_if_model_is_not_set() { @@ -44,6 +44,7 @@ public function dependencies_are_resolved_correctly() $fakePaginator = new Paginator(15, null); $fakeSearchBuilder = new SearchBuilder([]); $fakeQueryBuilder = new QueryBuilder(Post::class, $fakeParamsValidator, $fakeRelationsResolver, $fakeSearchBuilder); + $fakeKeyResolver = new KeyResolver(); App::shouldReceive('makeWith')->with( \Orion\Contracts\ComponentsResolver::class, @@ -97,6 +98,10 @@ public function dependencies_are_resolved_correctly() ] )->once()->andReturn($fakeQueryBuilder); + App::shouldReceive('make')->with( + \Orion\Contracts\KeyResolver::class, + )->once()->andReturn($fakeKeyResolver); + $stub = new BaseControllerStubWithWhitelistedFieldsAndRelations(); $this->assertEquals($fakeComponentsResolver, $stub->getComponentsResolver()); $this->assertEquals($fakeParamsValidator, $stub->getParamsValidator()); diff --git a/tests/Unit/Http/Controllers/RelationControllerTest.php b/tests/Unit/Http/Controllers/RelationControllerTest.php index 8cfabf15..fd7dda68 100644 --- a/tests/Unit/Http/Controllers/RelationControllerTest.php +++ b/tests/Unit/Http/Controllers/RelationControllerTest.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Facades\App; use Orion\Drivers\Standard\ComponentsResolver; +use Orion\Drivers\Standard\KeyResolver; use Orion\Drivers\Standard\Paginator; use Orion\Drivers\Standard\ParamsValidator; use Orion\Drivers\Standard\QueryBuilder; @@ -39,6 +40,7 @@ public function dependencies_are_resolved_correctly(): void $fakeSearchBuilder = new SearchBuilder([]); $fakeQueryBuilder = new QueryBuilder(Post::class, $fakeParamsValidator, $fakeRelationsResolver, $fakeSearchBuilder); $fakeRelationQueryBuilder = new QueryBuilder(User::class, $fakeParamsValidator, $fakeRelationsResolver, $fakeSearchBuilder); + $fakeKeyResolver = new KeyResolver(); App::shouldReceive('makeWith')->with( \Orion\Contracts\ComponentsResolver::class, @@ -109,6 +111,10 @@ public function dependencies_are_resolved_correctly(): void ] )->once()->andReturn($fakeRelationQueryBuilder); + App::shouldReceive('make')->with( + \Orion\Contracts\KeyResolver::class, + )->once()->andReturn($fakeKeyResolver); + $stub = new RelationControllerStub(); $this->assertEquals($fakeComponentsResolver, $stub->getComponentsResolver()); $this->assertEquals($fakeParentComponentsResolver, $stub->getParentComponentsResolver()); From fe647440ecd99854f389681cc493c96109bd1ce5 Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 13:41:18 -0700 Subject: [PATCH 2/8] Finish the rest of standard operations --- src/Concerns/HandlesStandardOperations.php | 20 +++++++----- .../Feature/StandardDeleteOperationsTest.php | 29 +++++++++++++++++ .../Feature/StandardRestoreOperationsTest.php | 27 ++++++++++++++++ tests/Feature/StandardShowOperationsTest.php | 17 +++++----- .../Feature/StandardUpdateOperationsTest.php | 31 +++++++++++++++++++ tests/Fixtures/routes/api.php | 2 +- 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/Concerns/HandlesStandardOperations.php b/src/Concerns/HandlesStandardOperations.php index c8fee955..d80525b1 100644 --- a/src/Concerns/HandlesStandardOperations.php +++ b/src/Concerns/HandlesStandardOperations.php @@ -329,7 +329,7 @@ protected function afterStore(Request $request, Model $entity) * Fetches resource. * * @param Request $request - * @param int|string $key + * @param array $args * @return Resource * @throws AuthorizationException * @throws BindingResolutionException @@ -440,11 +440,13 @@ protected function afterShow(Request $request, Model $entity) * Update a resource in a transaction-safe way. * * @param Request $request - * @param int|string $key + * @param array $args * @return Resource */ - public function update(Request $request, $key) + public function update(Request $request, ...$args) { + $key = $this->keyResolver->resolveStandardOperationKey($request, $args); + try { $this->startTransaction(); $result = $this->updateWithTransaction($request, $key); @@ -604,12 +606,14 @@ protected function afterUpdate(Request $request, Model $entity) * Deletes a resource. * * @param Request $request - * @param int|string $key + * @param array $args * @return Resource * @throws Exception */ - public function destroy(Request $request, $key) + public function destroy(Request $request, ...$args) { + $key = $this->keyResolver->resolveStandardOperationKey($request, $args); + try { $this->startTransaction(); $result = $this->destroyWithTransaction($request, $key); @@ -769,12 +773,14 @@ protected function afterDestroy(Request $request, Model $entity) * Restore previously deleted resource in a transaction-safe way. * * @param Request $request - * @param int|string $key + * @param array $args * @return Resource * @throws Exception */ - public function restore(Request $request, $key) + public function restore(Request $request, ...$args) { + $key = $this->keyResolver->resolveStandardOperationKey($request, $args); + try { $this->startTransaction(); $result = $this->restoreWithTransaction($request, $key); diff --git a/tests/Feature/StandardDeleteOperationsTest.php b/tests/Feature/StandardDeleteOperationsTest.php index 34becb94..444fdddf 100644 --- a/tests/Feature/StandardDeleteOperationsTest.php +++ b/tests/Feature/StandardDeleteOperationsTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; use Orion\Tests\Fixtures\App\Models\Post; @@ -186,4 +187,32 @@ public function deleting_a_single_resource_and_getting_included_relation(): void $this->assertResourceDeleted($response, $post, ['user' => $user->toArray()]); } + + /** @test */ + public function deleting_a_single_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->delete("/api/v1/posts/$post->id"); + + $this->assertResourceShown($response, $post); + } + + /** @test */ + public function deleting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->delete("/api/v1/posts/$post->id"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/StandardRestoreOperationsTest.php b/tests/Feature/StandardRestoreOperationsTest.php index 42003b69..cf224a32 100644 --- a/tests/Feature/StandardRestoreOperationsTest.php +++ b/tests/Feature/StandardRestoreOperationsTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; use Orion\Tests\Fixtures\App\Models\Post; @@ -109,4 +110,30 @@ public function restoring_a_single_resource_and_getting_included_relation(): voi $this->assertResourceRestored($response, $trashedPost, ['user' => $user->toArray()]); } + + /** @test */ + public function restoring_a_single_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $trashedPost = factory(Post::class)->state('trashed')->create(['user_id' => factory(User::class)->create()->id]); + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->post("/api/v1/posts/{$trashedPost->id}/restore"); + + $this->assertResourceRestored($response, $trashedPost); + } + + /** @test */ + public function restoring_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $trashedPost = factory(Post::class)->state('trashed')->create(['user_id' => factory(User::class)->create()->id]); + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->post("/api/v1/posts/{$trashedPost->id}/restore"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/StandardShowOperationsTest.php b/tests/Feature/StandardShowOperationsTest.php index 51bbda90..fa6cc770 100644 --- a/tests/Feature/StandardShowOperationsTest.php +++ b/tests/Feature/StandardShowOperationsTest.php @@ -6,7 +6,6 @@ use Mockery; use Orion\Contracts\ComponentsResolver; use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; -use Orion\Tests\Fixtures\App\Http\Resources\SampleCollectionResource; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; use Orion\Tests\Fixtures\App\Models\Comment; @@ -174,23 +173,25 @@ public function getting_a_single_resource_with_multiple_route_parameters(): void { $this->useKeyResolver(TwoRouteParameterKeyResolver::class); - $team = factory(Team::class)->create(); + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); - Gate::policy(Team::class, GreenPolicy::class); + Gate::policy(Post::class, GreenPolicy::class); - $response = $this->get("/api/v1/teams/$team->id"); + $response = $this->get("/api/v1/posts/$post->id"); - $this->assertResourceShown($response, $team->fresh()->toArray()); + $this->assertResourceShown($response, $post); } /** @test */ public function getting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { - $team = factory(Team::class)->create(); + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); - Gate::policy(Team::class, GreenPolicy::class); + Gate::policy(Post::class, GreenPolicy::class); - $response = $this->get("/api/v1/teams/$team->id"); + $response = $this->get("/api/v1/posts/$post->id"); $response->assertNotFound(); } diff --git a/tests/Feature/StandardUpdateOperationsTest.php b/tests/Feature/StandardUpdateOperationsTest.php index b8c23928..76f9cbe6 100644 --- a/tests/Feature/StandardUpdateOperationsTest.php +++ b/tests/Feature/StandardUpdateOperationsTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Requests\PostRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; @@ -158,4 +159,34 @@ public function updating_a_single_resource_and_getting_included_relation(): void ['user' => $user->fresh()->toArray()] ); } + + /** @test */ + public function updating_a_single_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + $payload = ['title' => 'test post title updated']; + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->patch("/api/v1/posts/$post->id", $payload); + + $this->assertResourceShown($response, $post); + } + + /** @test */ + public function updating_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + $payload = ['title' => 'test post title updated']; + + Gate::policy(Post::class, GreenPolicy::class); + + $response = $this->patch("/api/v1/posts/$post->id", $payload); + + $response->assertNotFound(); + } } diff --git a/tests/Fixtures/routes/api.php b/tests/Fixtures/routes/api.php index a3607cb9..c6127303 100644 --- a/tests/Fixtures/routes/api.php +++ b/tests/Fixtures/routes/api.php @@ -35,6 +35,6 @@ Orion::belongsToManyResource('users', 'notifications', UserNotificationsController::class)->withSoftDeletes(); Route::group(['prefix' => '{apiVersion}'], function () { - Orion::resource('teams', TeamsController::class); + Orion::resource('posts', PostsController::class)->withSoftDeletes(); }); }); From 8c8df4540c4d772ccbf22bd294418b20984467d5 Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 14:22:03 -0700 Subject: [PATCH 3/8] Finish belongs to tests --- .../HandlesRelationStandardOperations.php | 35 +++++++++++------ src/Drivers/Standard/KeyResolver.php | 2 +- ...ToRelationStandardDeleteOperationsTest.php | 29 ++++++++++++++ ...oRelationStandardRestoreOperationsTest.php | 31 ++++++++++++++- ...gsToRelationStandardShowOperationsTest.php | 30 ++++++++++++++- ...ToRelationStandardUpdateOperationsTest.php | 38 ++++++++++++++++++- .../Feature/StandardDeleteOperationsTest.php | 2 +- .../Feature/StandardUpdateOperationsTest.php | 7 +++- .../Drivers/TwoRouteParameterKeyResolver.php | 2 +- tests/Fixtures/routes/api.php | 3 ++ 10 files changed, 160 insertions(+), 19 deletions(-) diff --git a/src/Concerns/HandlesRelationStandardOperations.php b/src/Concerns/HandlesRelationStandardOperations.php index 5c65763b..1185ba09 100644 --- a/src/Concerns/HandlesRelationStandardOperations.php +++ b/src/Concerns/HandlesRelationStandardOperations.php @@ -27,13 +27,15 @@ trait HandlesRelationStandardOperations * Fetch the list of relation resources. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return CollectionResource */ - public function index(Request $request, $parentKey) + public function index(Request $request, ...$args) { $requestedRelations = $this->relationsResolver->requestedRelations($request); + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $parentQuery = $this->buildIndexParentFetchQuery($request, $parentKey); $parentEntity = $this->runIndexParentFetchQuery($request, $parentQuery, $parentKey); @@ -484,12 +486,14 @@ protected function afterStore(Request $request, Model $parentEntity, Model $enti * Fetch a relation resource. * * @param Request $request - * @param int|string $parentKey - * @param int|string|null $relatedKey + * @param array $args * @return Resource */ - public function show(Request $request, $parentKey, $relatedKey = null) + public function show(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + $parentQuery = $this->buildShowParentFetchQuery($request, $parentKey); $parentEntity = $this->runShowParentFetchQuery($request, $parentQuery, $parentKey); @@ -672,12 +676,15 @@ protected function afterShow(Request $request, Model $parentEntity, Model $entit * Update a relation resource in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @param int|string|null $relatedKey * @return Resource */ - public function update(Request $request, $parentKey, $relatedKey = null) + public function update(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + try { $this->startTransaction(); $result = $this->updateWithTransaction($request, $parentKey, $relatedKey); @@ -889,13 +896,16 @@ protected function afterUpdate(Request $request, Model $parentEntity, Model $ent * Delete a relation resource. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @param int|string|null $relatedKey * @return Resource * @throws Exception */ - public function destroy(Request $request, $parentKey, $relatedKey = null) + public function destroy(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + try { $this->startTransaction(); $result = $this->destroyWithTransaction($request, $parentKey, $relatedKey); @@ -1079,12 +1089,15 @@ protected function afterDestroy(Request $request, Model $parentEntity, Model $en * Restores a previously deleted relation resource in a transaction-save way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @param int|string|null $relatedKey * @return Resource */ - public function restore(Request $request, $parentKey, $relatedKey = null) + public function restore(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + try { $this->startTransaction(); $result = $this->restoreWithTransaction($request, $parentKey, $relatedKey); diff --git a/src/Drivers/Standard/KeyResolver.php b/src/Drivers/Standard/KeyResolver.php index 76c94ebc..f3f4b1dd 100644 --- a/src/Drivers/Standard/KeyResolver.php +++ b/src/Drivers/Standard/KeyResolver.php @@ -18,6 +18,6 @@ public function resolveRelationOperationParentKey(Request $request, array $args) public function resolveRelationOperationRelatedKey(Request $request, array $args) { - return $args[1]; + return $args[1] ?? null; } } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php index e1f7e421..69a5a096 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php @@ -6,6 +6,7 @@ use Mockery; use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Category; use Orion\Tests\Fixtures\App\Models\Post; @@ -158,4 +159,32 @@ public function deleting_a_single_relation_resource_and_getting_included_relatio $this->assertResourceDeleted($response, $user, ['posts' => [$post->toArray()]]); } + + /** @test */ + public function deleting_a_single_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $category = factory(Category::class)->create(); + $post = factory(Post::class)->create(['category_id' => $category->id]); + + Gate::policy(Category::class, GreenPolicy::class); + + $response = $this->delete("/api/v1/posts/{$post->id}/category"); + + $this->assertResourceTrashed($response, $category); + } + + /** @test */ + public function deleting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $category = factory(Category::class)->create(); + $post = factory(Post::class)->create(['category_id' => $category->id]); + + Gate::policy(Category::class, GreenPolicy::class); + + $response = $this->delete("/api/v1/posts/{$post->id}/category"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php index cd057860..292d173d 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php @@ -3,9 +3,8 @@ namespace Orion\Tests\Feature\Relations\BelongsTo; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Category; use Orion\Tests\Fixtures\App\Models\Post; @@ -94,4 +93,32 @@ public function restoring_a_single_relation_resource_and_getting_included_relati $this->assertResourceRestored($response, $trashedCategory, ['posts' => $trashedCategory->posts->toArray()]); } + + /** @test */ + public function restoring_a_single_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $trashedCategory = factory(Category::class)->state('trashed')->create(); + $post = factory(Post::class)->create(['category_id' => $trashedCategory->id]); + + Gate::policy(Category::class, GreenPolicy::class); + + $response = $this->post("/api/v1/posts/{$post->id}/category/{$trashedCategory->id}/restore"); + + $this->assertResourceRestored($response, $trashedCategory); + } + + /** @test */ + public function restoring_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $trashedCategory = factory(Category::class)->state('trashed')->create(); + $post = factory(Post::class)->create(['category_id' => $trashedCategory->id]); + + Gate::policy(Category::class, GreenPolicy::class); + + $response = $this->post("/api/v1/posts/{$post->id}/category/{$trashedCategory->id}/restore"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php index ea673c61..06510bdb 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Category; use Orion\Tests\Fixtures\App\Models\Post; @@ -65,7 +66,6 @@ public function getting_a_single_trashed_relation_resource_when_with_trashed_que $this->assertResourceShown($response, $trashedCategory); } - /** @test */ public function getting_a_single_transformed_relation_resource(): void { @@ -110,4 +110,32 @@ public function getting_a_single_relation_resource_with_aggregate(): void $this->assertResourceShown($response, $user->fresh()->loadCount('posts')->toArray()); } + + /** @test */ + public function getting_a_single_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + + Gate::policy(User::class, GreenPolicy::class); + + $response = $this->get("/api/v1/posts/{$post->id}/user"); + + $this->assertResourceShown($response, $user); + } + + /** @test */ + public function getting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + + Gate::policy(User::class, GreenPolicy::class); + + $response = $this->get("/api/v1/posts/{$post->id}/user"); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php index 46472018..99f59309 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Requests\UserRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Post; @@ -92,7 +93,7 @@ public function transforming_a_single_updated_relation_resource(): void $post = factory(Post::class)->create(['user_id' => $user->id]); $payload = ['name' => 'test user updated']; - $this->useResource(SampleResource::class); + $this->useResource(SampleResource::class); Gate::policy(User::class, GreenPolicy::class); @@ -126,4 +127,39 @@ public function updating_a_single_resource_and_getting_included_relation(): void ['posts' => $user->fresh('posts')->posts->toArray()] ); } + + /** @test */ + public function updating_a_single_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + $payload = ['name' => 'test user updated']; + + Gate::policy(User::class, GreenPolicy::class); + + $response = $this->patch("/api/v1/posts/{$post->id}/user", $payload); + + $this->assertResourceUpdated( + $response, + User::class, + $user->toArray(), + $payload + ); + } + + /** @test */ + public function updating_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + $user = factory(User::class)->create(); + $post = factory(Post::class)->create(['user_id' => $user->id]); + $payload = ['name' => 'test user updated']; + + Gate::policy(User::class, GreenPolicy::class); + + $response = $this->patch("/api/v1/posts/{$post->id}/user", $payload); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/StandardDeleteOperationsTest.php b/tests/Feature/StandardDeleteOperationsTest.php index 444fdddf..f15cc103 100644 --- a/tests/Feature/StandardDeleteOperationsTest.php +++ b/tests/Feature/StandardDeleteOperationsTest.php @@ -200,7 +200,7 @@ public function deleting_a_single_resource_with_multiple_route_parameters(): voi $response = $this->delete("/api/v1/posts/$post->id"); - $this->assertResourceShown($response, $post); + $this->assertResourceTrashed($response, $post); } /** @test */ diff --git a/tests/Feature/StandardUpdateOperationsTest.php b/tests/Feature/StandardUpdateOperationsTest.php index 76f9cbe6..c0605efa 100644 --- a/tests/Feature/StandardUpdateOperationsTest.php +++ b/tests/Feature/StandardUpdateOperationsTest.php @@ -173,7 +173,12 @@ public function updating_a_single_resource_with_multiple_route_parameters(): voi $response = $this->patch("/api/v1/posts/$post->id", $payload); - $this->assertResourceShown($response, $post); + $this->assertResourceUpdated( + $response, + Post::class, + $post->toArray(), + $payload + ); } /** @test */ diff --git a/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php b/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php index dae5e16c..be03ed31 100644 --- a/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php +++ b/tests/Fixtures/app/Drivers/TwoRouteParameterKeyResolver.php @@ -19,6 +19,6 @@ public function resolveRelationOperationParentKey(Request $request, array $args) public function resolveRelationOperationRelatedKey(Request $request, array $args) { - return $args[2]; + return $args[2] ?? null; } } diff --git a/tests/Fixtures/routes/api.php b/tests/Fixtures/routes/api.php index c6127303..9aaedea9 100644 --- a/tests/Fixtures/routes/api.php +++ b/tests/Fixtures/routes/api.php @@ -36,5 +36,8 @@ Route::group(['prefix' => '{apiVersion}'], function () { Orion::resource('posts', PostsController::class)->withSoftDeletes(); + + Orion::belongsToResource('posts', 'user', PostUserController::class); + Orion::belongsToResource('posts', 'category', PostCategoryController::class)->withSoftDeletes(); }); }); From e47042c71a602ba380e9379797a8a637f7976a1d Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 14:32:28 -0700 Subject: [PATCH 4/8] Add the rest of the resolvers to all operations --- .../HandlesRelationManyToManyOperations.php | 32 ++++++++++++------- .../HandlesRelationOneToManyOperations.php | 14 +++++--- ...HandlesRelationStandardBatchOperations.php | 24 +++++++++----- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/Concerns/HandlesRelationManyToManyOperations.php b/src/Concerns/HandlesRelationManyToManyOperations.php index 70680b71..a8b11346 100644 --- a/src/Concerns/HandlesRelationManyToManyOperations.php +++ b/src/Concerns/HandlesRelationManyToManyOperations.php @@ -17,11 +17,13 @@ trait HandlesRelationManyToManyOperations * Attach resource to the relation in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array$args * @return JsonResponse */ - public function attach(Request $request, $parentKey) + public function attach(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->attachWithTransaction($request, $parentKey); @@ -237,11 +239,13 @@ protected function afterAttach(Request $request, Model $parentEntity, array &$at * Detach resource to the relation in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return JsonResponse */ - public function detach(Request $request, $parentKey) + public function detach(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->detachWithTransaction($request, $parentKey); @@ -360,11 +364,13 @@ protected function afterDetach(Request $request, Model $parentEntity, array &$de * Sync relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return JsonResponse */ - public function sync(Request $request, $parentKey) + public function sync(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->syncWithTransaction($request, $parentKey); @@ -486,11 +492,13 @@ protected function afterSync(Request $request, Model $parentEntity, array &$sync * Toggle relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return JsonResponse */ - public function toggle(Request $request, $parentKey) + public function toggle(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->toggleWithTransaction($request, $parentKey); @@ -603,12 +611,14 @@ protected function afterToggle(Request $request, Model $parentEntity, array &$to * Update relation resource pivot in a transaction-safe wqy. * * @param Request $request - * @param int|string $parentKey - * @param int|string $relatedKey + * @param array $args * @return JsonResponse */ - public function updatePivot(Request $request, $parentKey, $relatedKey) + public function updatePivot(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + try { $this->startTransaction(); $result = $this->updatePivotWithTransaction($request, $parentKey, $relatedKey); diff --git a/src/Concerns/HandlesRelationOneToManyOperations.php b/src/Concerns/HandlesRelationOneToManyOperations.php index facc9fd2..29f4c5f6 100644 --- a/src/Concerns/HandlesRelationOneToManyOperations.php +++ b/src/Concerns/HandlesRelationOneToManyOperations.php @@ -14,11 +14,13 @@ trait HandlesRelationOneToManyOperations * Associates resource with another resource in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return Resource */ - public function associate(Request $request, $parentKey) + public function associate(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->associateWithTransaction($request, $parentKey); @@ -164,12 +166,14 @@ protected function afterAssociate(Request $request, Model $parentEntity, Model $ * Disassociates resource from another resource in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey - * @param int|string $relatedKey + * @param array $args * @return Resource */ - public function dissociate(Request $request, $parentKey, $relatedKey) + public function dissociate(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + $relatedKey = $this->keyResolver->resolveRelationOperationRelatedKey($request, $args); + try { $this->startTransaction(); $result = $this->dissociateWithTransaction($request, $parentKey, $relatedKey); diff --git a/src/Concerns/HandlesRelationStandardBatchOperations.php b/src/Concerns/HandlesRelationStandardBatchOperations.php index ec8b3644..6a7b5990 100644 --- a/src/Concerns/HandlesRelationStandardBatchOperations.php +++ b/src/Concerns/HandlesRelationStandardBatchOperations.php @@ -17,11 +17,13 @@ trait HandlesRelationStandardBatchOperations * Create a batch of new relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return CollectionResource */ - public function batchStore(Request $request, $parentKey) + public function batchStore(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->batchStoreWithTransaction($request, $parentKey); @@ -161,11 +163,13 @@ protected function afterBatchStore(Request $request, Model $parentEntity, Collec * Updates a batch of relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return CollectionResource */ - public function batchUpdate(Request $request, $parentKey) + public function batchUpdate(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->batchUpdateWithTransaction($request, $parentKey); @@ -355,12 +359,14 @@ protected function afterBatchUpdate(Request $request, Model $parentEntity, Colle * Deletes a batch of relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return CollectionResource * @throws Exception */ - public function batchDestroy(Request $request, $parentKey) + public function batchDestroy(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->batchDestroyWithTransaction($request, $parentKey); @@ -527,12 +533,14 @@ protected function afterBatchDestroy(Request $request, Model $parentEntity, Coll * Restores a batch of relation resources in a transaction-safe way. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return CollectionResource * @throws Exception */ - public function batchRestore(Request $request, $parentKey) + public function batchRestore(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->batchRestoreWithTransaction($request, $parentKey); From 473789a402ac1c6baf7b4853c0fe727ce03dd4d9 Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 20:32:29 -0700 Subject: [PATCH 5/8] Allow query exception if postgres because postgres will perform a type check on the binding to make sure it matches the column type --- .../BelongsToRelationStandardDeleteOperationsTest.php | 6 ++++++ .../BelongsToRelationStandardRestoreOperationsTest.php | 6 ++++++ .../BelongsToRelationStandardShowOperationsTest.php | 6 ++++++ .../BelongsToRelationStandardUpdateOperationsTest.php | 6 ++++++ tests/Feature/StandardDeleteOperationsTest.php | 6 ++++++ tests/Feature/StandardRestoreOperationsTest.php | 6 ++++++ tests/Feature/StandardShowOperationsTest.php | 6 ++++++ tests/Feature/StandardUpdateOperationsTest.php | 6 ++++++ tests/TestCase.php | 1 + 9 files changed, 49 insertions(+) diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php index 69a5a096..bdaf0701 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature\Relations\BelongsTo; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; @@ -178,6 +180,10 @@ public function deleting_a_single_relation_resource_with_multiple_route_paramete /** @test */ public function deleting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $category = factory(Category::class)->create(); $post = factory(Post::class)->create(['category_id' => $category->id]); diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php index 292d173d..9ffc71e7 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature\Relations\BelongsTo; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; @@ -112,6 +114,10 @@ public function restoring_a_single_relation_resource_with_multiple_route_paramet /** @test */ public function restoring_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $trashedCategory = factory(Category::class)->state('trashed')->create(); $post = factory(Post::class)->create(['category_id' => $trashedCategory->id]); diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php index 06510bdb..e65005e8 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature\Relations\BelongsTo; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; @@ -129,6 +131,10 @@ public function getting_a_single_relation_resource_with_multiple_route_parameter /** @test */ public function getting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $user = factory(User::class)->create(); $post = factory(Post::class)->create(['user_id' => $user->id]); diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php index 99f59309..d5f291ca 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature\Relations\BelongsTo; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; @@ -152,6 +154,10 @@ public function updating_a_single_relation_resource_with_multiple_route_paramete /** @test */ public function updating_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $user = factory(User::class)->create(); $post = factory(Post::class)->create(['user_id' => $user->id]); $payload = ['name' => 'test user updated']; diff --git a/tests/Feature/StandardDeleteOperationsTest.php b/tests/Feature/StandardDeleteOperationsTest.php index f15cc103..60bbe265 100644 --- a/tests/Feature/StandardDeleteOperationsTest.php +++ b/tests/Feature/StandardDeleteOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; @@ -206,6 +208,10 @@ public function deleting_a_single_resource_with_multiple_route_parameters(): voi /** @test */ public function deleting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $user = factory(User::class)->create(); $post = factory(Post::class)->create(['user_id' => $user->id]); diff --git a/tests/Feature/StandardRestoreOperationsTest.php b/tests/Feature/StandardRestoreOperationsTest.php index cf224a32..4cc8cdcd 100644 --- a/tests/Feature/StandardRestoreOperationsTest.php +++ b/tests/Feature/StandardRestoreOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; @@ -128,6 +130,10 @@ public function restoring_a_single_resource_with_multiple_route_parameters(): vo /** @test */ public function restoring_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $trashedPost = factory(Post::class)->state('trashed')->create(['user_id' => factory(User::class)->create()->id]); Gate::policy(Post::class, GreenPolicy::class); diff --git a/tests/Feature/StandardShowOperationsTest.php b/tests/Feature/StandardShowOperationsTest.php index fa6cc770..435055a1 100644 --- a/tests/Feature/StandardShowOperationsTest.php +++ b/tests/Feature/StandardShowOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; @@ -186,6 +188,10 @@ public function getting_a_single_resource_with_multiple_route_parameters(): void /** @test */ public function getting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $user = factory(User::class)->create(); $post = factory(Post::class)->create(['user_id' => $user->id]); diff --git a/tests/Feature/StandardUpdateOperationsTest.php b/tests/Feature/StandardUpdateOperationsTest.php index c0605efa..e299d0a2 100644 --- a/tests/Feature/StandardUpdateOperationsTest.php +++ b/tests/Feature/StandardUpdateOperationsTest.php @@ -2,6 +2,8 @@ namespace Orion\Tests\Feature; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; @@ -184,6 +186,10 @@ public function updating_a_single_resource_with_multiple_route_parameters(): voi /** @test */ public function updating_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->expectException(QueryException::class); + } + $user = factory(User::class)->create(); $post = factory(Post::class)->create(['user_id' => $user->id]); $payload = ['title' => 'test post title updated']; diff --git a/tests/TestCase.php b/tests/TestCase.php index 23a191f4..867e555f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,6 +15,7 @@ protected function setUp(): void parent::setUp(); $this->withFactories(__DIR__ . '/Fixtures/database/factories'); + $this->withoutExceptionHandling(); } /** From 57de89b3e523bb4565033eec3392da32b282337a Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 20:35:03 -0700 Subject: [PATCH 6/8] Roll back --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 867e555f..23a191f4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,7 +15,6 @@ protected function setUp(): void parent::setUp(); $this->withFactories(__DIR__ . '/Fixtures/database/factories'); - $this->withoutExceptionHandling(); } /** From 113d52fc4e2d86e8ce2ec18885ef413bcd47675b Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Sat, 31 Aug 2024 21:08:53 -0700 Subject: [PATCH 7/8] Finish test suite --- ...ToRelationStandardDeleteOperationsTest.php | 1 + ...oRelationStandardRestoreOperationsTest.php | 1 + ...gsToRelationStandardShowOperationsTest.php | 1 + ...ToRelationStandardUpdateOperationsTest.php | 1 + ...ToManyRelationManyToManyOperationsTest.php | 307 +++++++++++++++++- ...nyRelationStandardDeleteOperationsTest.php | 2 - ...anyRelationStandardIndexOperationsTest.php | 2 - ...yRelationStandardRestoreOperationsTest.php | 2 - ...ManyRelationStandardShowOperationsTest.php | 1 - ...anyRelationStandardStoreOperationsTest.php | 2 - ...HasManyRelationOneToManyOperationsTest.php | 90 ++++- ...nyRelationStandardDeleteOperationsTest.php | 2 - ...anyRelationStandardIndexOperationsTest.php | 2 - ...yRelationStandardRestoreOperationsTest.php | 2 - ...ManyRelationStandardShowOperationsTest.php | 1 - ...anyRelationStandardStoreOperationsTest.php | 2 - ...nyRelationStandardUpdateOperationsTest.php | 2 - ...neRelationStandardDeleteOperationsTest.php | 2 - ...eRelationStandardRestoreOperationsTest.php | 2 - ...sOneRelationStandardShowOperationsTest.php | 3 - ...neRelationStandardUpdateOperationsTest.php | 2 - .../Feature/StandardDeleteOperationsTest.php | 1 + .../Feature/StandardIncludeOperationsTest.php | 2 +- .../StandardIndexFilteringOperationsTest.php | 5 +- tests/Feature/StandardIndexOperationsTest.php | 1 - .../Feature/StandardRestoreOperationsTest.php | 1 + tests/Feature/StandardShowOperationsTest.php | 1 + .../Feature/StandardUpdateOperationsTest.php | 1 + tests/Fixtures/routes/api.php | 4 + 29 files changed, 409 insertions(+), 37 deletions(-) diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php index bdaf0701..3c05382c 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardDeleteOperationsTest.php @@ -181,6 +181,7 @@ public function deleting_a_single_relation_resource_with_multiple_route_paramete public function deleting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php index 9ffc71e7..99ead972 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardRestoreOperationsTest.php @@ -115,6 +115,7 @@ public function restoring_a_single_relation_resource_with_multiple_route_paramet public function restoring_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php index e65005e8..5d7dc8ce 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardShowOperationsTest.php @@ -132,6 +132,7 @@ public function getting_a_single_relation_resource_with_multiple_route_parameter public function getting_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php index d5f291ca..5dea3420 100644 --- a/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php +++ b/tests/Feature/Relations/BelongsTo/BelongsToRelationStandardUpdateOperationsTest.php @@ -155,6 +155,7 @@ public function updating_a_single_relation_resource_with_multiple_route_paramete public function updating_a_single_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationManyToManyOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationManyToManyOperationsTest.php index 7b046233..4efc2e99 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationManyToManyOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationManyToManyOperationsTest.php @@ -4,8 +4,11 @@ namespace Orion\Tests\Feature\Relations\BelongsToMany; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Models\Role; use Orion\Tests\Fixtures\App\Models\User; use Orion\Tests\Fixtures\App\Policies\GreenPolicy; @@ -46,7 +49,6 @@ public function attaching_relation_resources_when_authorized_only_on_parent(): v self::assertEquals(0, $user->roles()->count()); - $response = $this->post( "/api/users/{$user->id}/roles/attach", [ @@ -754,4 +756,307 @@ public function updating_pivot_of_relation_resource_casted_to_json_pivot_field() ['references' => ['key' => 'value']] ); } + + /** @test */ + public function attaching_relation_resources_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleA = factory(Role::class)->create(); + $roleB = factory(Role::class)->create(); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(0, $user->roles()->count()); + + $response = $this->post( + "/api/v1/users/{$user->id}/roles/attach", + [ + 'resources' => [$roleA->id, $roleB->id], + ] + ); + + $this->assertResourcesAttached( + $response, + 'roles', + $user, + collect([$roleA, $roleB]) + ); + } + + /** @test */ + public function attaching_relation_resources_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleA = factory(Role::class)->create(); + $roleB = factory(Role::class)->create(); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(0, $user->roles()->count()); + + $response = $this->post( + "/api/v1/users/{$user->id}/roles/attach", + [ + 'resources' => [$roleA->id, $roleB->id], + ] + ); + + $response->assertNotFound(); + } + + /** @test */ + public function detaching_relation_resources_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + /** @var User $user */ + $user = factory(User::class)->create(); + $roles = factory(Role::class)->times(5)->make(); + $user->roles()->saveMany($roles); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + $roles = $user->roles()->get(); + + self::assertEquals(5, $roles->count()); + + $response = $this->delete( + "/api/v1/users/{$user->id}/roles/detach", + [ + 'resources' => $roles->pluck('id')->toArray(), + ] + ); + + $this->assertResourcesDetached($response, 'roles', $user, $roles); + } + + /** @test */ + public function detaching_relation_resources_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + /** @var User $user */ + $user = factory(User::class)->create(); + $roles = factory(Role::class)->times(5)->make(); + $user->roles()->saveMany($roles); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + $roles = $user->roles()->get(); + + self::assertEquals(5, $roles->count()); + + $response = $this->delete( + "/api/v1/users/{$user->id}/roles/detach", + [ + 'resources' => $roles->pluck('id')->toArray(), + ] + ); + + $response->assertNotFound(); + } + + /** @test */ + public function syncing_relation_resources_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleToAttach = factory(Role::class)->create(); + $roleToDetach = factory(Role::class)->create(); + $roleToUpdate = factory(Role::class)->create(); + $user->roles()->attach($roleToDetach->id); + $user->roles()->attach($roleToUpdate->id, ['custom_name' => 'test original']); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(2, $user->roles()->count()); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/sync", + [ + 'resources' => [ + $roleToAttach->id, + $roleToUpdate->id => ['custom_name' => 'test updated'], + ], + ] + ); + + $this->assertResourcesSynced( + $response, + 'roles', + $user, + $this->buildSyncMap([$roleToAttach], [$roleToDetach], [$roleToUpdate]), + [$roleToUpdate->id => ['custom_name' => 'test updated']] + ); + } + + /** @test */ + public function syncing_relation_resources_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleToAttach = factory(Role::class)->create(); + $roleToDetach = factory(Role::class)->create(); + $roleToUpdate = factory(Role::class)->create(); + $user->roles()->attach($roleToDetach->id); + $user->roles()->attach($roleToUpdate->id, ['custom_name' => 'test original']); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(2, $user->roles()->count()); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/sync", + [ + 'resources' => [ + $roleToAttach->id, + $roleToUpdate->id => ['custom_name' => 'test updated'], + ], + ] + ); + + $response->assertNotFound(); + } + + /** @test */ + public function toggling_relation_resources_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleToAttach = factory(Role::class)->create(); + $roleToDetach = factory(Role::class)->create(); + $user->roles()->attach($roleToDetach->id); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(1, $user->roles()->count()); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/toggle", + [ + 'resources' => [$roleToAttach->id, $roleToDetach->id], + ] + ); + + $this->assertResourcesToggled( + $response, + 'roles', + $user, + $this->buildSyncMap([$roleToAttach], [$roleToDetach]) + ); + } + + /** @test */ + public function toggling_relation_resources_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + /** @var User $user */ + $user = factory(User::class)->create(); + $roleToAttach = factory(Role::class)->create(); + $roleToDetach = factory(Role::class)->create(); + $user->roles()->attach($roleToDetach->id); + + Gate::policy(User::class, GreenPolicy::class); + Gate::policy(Role::class, GreenPolicy::class); + + self::assertEquals(1, $user->roles()->count()); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/toggle", + [ + 'resources' => [$roleToAttach->id, $roleToDetach->id], + ] + ); + + $response->assertNotFound(); + } + + /** @test */ + public function updating_pivot_of_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + /** @var User $user */ + $user = factory(User::class)->create(); + $role = factory(Role::class)->create(); + $user->roles()->save($role); + + Gate::policy(Role::class, GreenPolicy::class); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/{$role->id}/pivot", + [ + 'pivot' => [ + 'custom_name' => 'test value', + ], + ] + ); + + $this->assertResourcePivotUpdated( + $response, + 'roles', + $user, + $role, + ['custom_name' => 'test value'] + ); + } + + /** @test */ + public function updating_pivot_of_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + /** @var User $user */ + $user = factory(User::class)->create(); + $role = factory(Role::class)->create(); + $user->roles()->save($role); + + Gate::policy(Role::class, GreenPolicy::class); + + $response = $this->patch( + "/api/v1/users/{$user->id}/roles/{$role->id}/pivot", + [ + 'pivot' => [ + 'custom_name' => 'test value', + ], + ] + ); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardDeleteOperationsTest.php index 13012507..afc2c1c4 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardDeleteOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\BelongsToMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Notification; diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardIndexOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardIndexOperationsTest.php index 1fa2a32a..b85264af 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardIndexOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardIndexOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\BelongsToMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleCollectionResource; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardRestoreOperationsTest.php index 9541bf98..d6addaf6 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardRestoreOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\BelongsToMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Notification; diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardShowOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardShowOperationsTest.php index e26956cd..4cc9838d 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardShowOperationsTest.php @@ -95,7 +95,6 @@ public function getting_a_single_trashed_relation_resource_when_with_trashed_que $this->assertResourceShown($response, $user->notifications()->withTrashed()->first()->toArray()); } - /** @test */ public function getting_a_single_transformed_relation_resource(): void { diff --git a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardStoreOperationsTest.php b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardStoreOperationsTest.php index 203b3c85..8bd8b324 100644 --- a/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardStoreOperationsTest.php +++ b/tests/Feature/Relations/BelongsToMany/BelongsToManyRelationStandardStoreOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\BelongsToMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Requests\RoleRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/Relations/HasMany/HasManyRelationOneToManyOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationOneToManyOperationsTest.php index 45fd3e59..701594a5 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationOneToManyOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationOneToManyOperationsTest.php @@ -2,10 +2,11 @@ namespace Orion\Tests\Feature\Relations\HasMany; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; +use Orion\Tests\Fixtures\App\Drivers\TwoRouteParameterKeyResolver; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Company; use Orion\Tests\Fixtures\App\Models\Team; @@ -204,4 +205,89 @@ public function transforming_a_dissociated_relation_resource_when_authorized(): ['test-field-from-resource' => 'test-value'] ); } + + /** @test */ + public function associating_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $company = factory(Company::class)->create(); + $team = factory(Team::class)->create(); + + Gate::policy(Company::class, GreenPolicy::class); + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->post( + "/api/v1/companies/{$company->id}/teams/associate", + [ + 'related_key' => $team->id, + ] + ); + + $this->assertResourceAssociated($response, $company, $team, 'company'); + } + + /** @test */ + public function associating_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + $company = factory(Company::class)->create(); + $team = factory(Team::class)->create(); + + Gate::policy(Company::class, GreenPolicy::class); + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->post( + "/api/v1/companies/{$company->id}/teams/associate", + [ + 'related_key' => $team->id, + ] + ); + + $response->assertNotFound(); + } + + /** @test */ + public function disassociating_relation_resource_with_multiple_route_parameters(): void + { + $this->useKeyResolver(TwoRouteParameterKeyResolver::class); + + $company = factory(Company::class)->create(); + $team = factory(Team::class)->create(['company_id' => $company->id]); + + Gate::policy(Company::class, GreenPolicy::class); + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->delete("/api/v1/companies/{$company->id}/teams/{$team->id}/dissociate"); + + $this->assertResourceDissociated($response, $team, 'company'); + } + + /** @test */ + public function disassociating_relation_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void + { + if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); + $this->expectException(QueryException::class); + } + + $company = factory(Company::class)->create(); + $team = factory(Team::class)->create(); + + Gate::policy(Company::class, GreenPolicy::class); + Gate::policy(Team::class, GreenPolicy::class); + + $response = $this->post( + "/api/v1/companies/{$company->id}/teams/associate", + [ + 'related_key' => $team->id, + ] + ); + + $response->assertNotFound(); + } } diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardDeleteOperationsTest.php index 22411ee9..5283e657 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardDeleteOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardIndexOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardIndexOperationsTest.php index d8297a03..3e9d7e0a 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardIndexOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardIndexOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleCollectionResource; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardRestoreOperationsTest.php index 4924f9dc..032849e1 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardRestoreOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\AccessKey; diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardShowOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardShowOperationsTest.php index b26e4ba5..492737f3 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardShowOperationsTest.php @@ -81,7 +81,6 @@ public function getting_a_single_trashed_relation_resource_when_with_trashed_que $this->assertResourceShown($response, $trashedPost); } - /** @test */ public function getting_a_single_transformed_relation_resource(): void { diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardStoreOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardStoreOperationsTest.php index 50952f0e..93fc0eb7 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardStoreOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardStoreOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Requests\TeamRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/Relations/HasMany/HasManyRelationStandardUpdateOperationsTest.php b/tests/Feature/Relations/HasMany/HasManyRelationStandardUpdateOperationsTest.php index 70e4d46e..bbec5d49 100644 --- a/tests/Feature/Relations/HasMany/HasManyRelationStandardUpdateOperationsTest.php +++ b/tests/Feature/Relations/HasMany/HasManyRelationStandardUpdateOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasMany; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Requests\TeamRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/Relations/HasOne/HasOneRelationStandardDeleteOperationsTest.php b/tests/Feature/Relations/HasOne/HasOneRelationStandardDeleteOperationsTest.php index 7aae4407..e2e6298c 100644 --- a/tests/Feature/Relations/HasOne/HasOneRelationStandardDeleteOperationsTest.php +++ b/tests/Feature/Relations/HasOne/HasOneRelationStandardDeleteOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasOne; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Post; diff --git a/tests/Feature/Relations/HasOne/HasOneRelationStandardRestoreOperationsTest.php b/tests/Feature/Relations/HasOne/HasOneRelationStandardRestoreOperationsTest.php index 20822a84..f27f1523 100644 --- a/tests/Feature/Relations/HasOne/HasOneRelationStandardRestoreOperationsTest.php +++ b/tests/Feature/Relations/HasOne/HasOneRelationStandardRestoreOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasOne; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Post; diff --git a/tests/Feature/Relations/HasOne/HasOneRelationStandardShowOperationsTest.php b/tests/Feature/Relations/HasOne/HasOneRelationStandardShowOperationsTest.php index d8fa1618..3cd9f407 100644 --- a/tests/Feature/Relations/HasOne/HasOneRelationStandardShowOperationsTest.php +++ b/tests/Feature/Relations/HasOne/HasOneRelationStandardShowOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasOne; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Post; @@ -67,7 +65,6 @@ public function getting_a_single_trashed_relation_resource_when_with_trashed_que $this->assertResourceShown($response, $trashedPostMeta); } - /** @test */ public function getting_a_single_transformed_relation_resource(): void { diff --git a/tests/Feature/Relations/HasOne/HasOneRelationStandardUpdateOperationsTest.php b/tests/Feature/Relations/HasOne/HasOneRelationStandardUpdateOperationsTest.php index 8baf5939..29be47d9 100644 --- a/tests/Feature/Relations/HasOne/HasOneRelationStandardUpdateOperationsTest.php +++ b/tests/Feature/Relations/HasOne/HasOneRelationStandardUpdateOperationsTest.php @@ -3,8 +3,6 @@ namespace Orion\Tests\Feature\Relations\HasOne; use Illuminate\Support\Facades\Gate; -use Mockery; -use Orion\Contracts\ComponentsResolver; use Orion\Tests\Feature\TestCase; use Orion\Tests\Fixtures\App\Http\Requests\PostMetaRequest; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; diff --git a/tests/Feature/StandardDeleteOperationsTest.php b/tests/Feature/StandardDeleteOperationsTest.php index 60bbe265..c6d695b2 100644 --- a/tests/Feature/StandardDeleteOperationsTest.php +++ b/tests/Feature/StandardDeleteOperationsTest.php @@ -209,6 +209,7 @@ public function deleting_a_single_resource_with_multiple_route_parameters(): voi public function deleting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/StandardIncludeOperationsTest.php b/tests/Feature/StandardIncludeOperationsTest.php index c77d8d5b..350c11d3 100644 --- a/tests/Feature/StandardIncludeOperationsTest.php +++ b/tests/Feature/StandardIncludeOperationsTest.php @@ -106,7 +106,7 @@ public function ensuring_root_level_filters_are_not_applied_on_includes(): void $this->assertResourcesPaginated( $response, - $this->makePaginator([$user->load(['posts'])->toArray(),], 'users/search'), + $this->makePaginator([$user->load(['posts'])->toArray()], 'users/search'), [], false ); diff --git a/tests/Feature/StandardIndexFilteringOperationsTest.php b/tests/Feature/StandardIndexFilteringOperationsTest.php index c2707e6b..93bf7385 100644 --- a/tests/Feature/StandardIndexFilteringOperationsTest.php +++ b/tests/Feature/StandardIndexFilteringOperationsTest.php @@ -681,7 +681,7 @@ public function getting_a_list_of_resources_filtered_by_identically_named_relati { $user = factory(User::class)->create(['name' => 'John Doe']); $matchingPost = factory(Post::class) - ->create([ 'user_id' => $user->id, ])->fresh(); + ->create(['user_id' => $user->id])->fresh(); factory(PostMeta::class)->create(['post_id' => $matchingPost->id, 'name' => 'test']); factory(Post::class)->create(['publish_at' => Carbon::now()])->fresh(); @@ -708,10 +708,9 @@ public function getting_a_list_of_resources_filtered_by_identically_named_relati public function getting_a_list_of_resources_filtered_by_identically_named_fields_on_different_nesting_level(): void { $matchingPost = factory(Post::class) - ->create(['title' => 'test' ])->fresh(); + ->create(['title' => 'test'])->fresh(); factory(PostMeta::class)->create(['post_id' => $matchingPost->id, 'title' => 'test']); - factory(Post::class)->create(['publish_at' => Carbon::now()])->fresh(); Gate::policy(Post::class, GreenPolicy::class); diff --git a/tests/Feature/StandardIndexOperationsTest.php b/tests/Feature/StandardIndexOperationsTest.php index 9c6f194e..74caf148 100644 --- a/tests/Feature/StandardIndexOperationsTest.php +++ b/tests/Feature/StandardIndexOperationsTest.php @@ -5,7 +5,6 @@ use Illuminate\Support\Facades\Gate; use Mockery; use Orion\Contracts\ComponentsResolver; -use Orion\Exceptions\MaxPaginationLimitExceededException; use Orion\Tests\Fixtures\App\Http\Resources\SampleCollectionResource; use Orion\Tests\Fixtures\App\Http\Resources\SampleResource; use Orion\Tests\Fixtures\App\Models\Post; diff --git a/tests/Feature/StandardRestoreOperationsTest.php b/tests/Feature/StandardRestoreOperationsTest.php index 4cc8cdcd..cf40a455 100644 --- a/tests/Feature/StandardRestoreOperationsTest.php +++ b/tests/Feature/StandardRestoreOperationsTest.php @@ -131,6 +131,7 @@ public function restoring_a_single_resource_with_multiple_route_parameters(): vo public function restoring_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/StandardShowOperationsTest.php b/tests/Feature/StandardShowOperationsTest.php index 435055a1..03795eaa 100644 --- a/tests/Feature/StandardShowOperationsTest.php +++ b/tests/Feature/StandardShowOperationsTest.php @@ -189,6 +189,7 @@ public function getting_a_single_resource_with_multiple_route_parameters(): void public function getting_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Feature/StandardUpdateOperationsTest.php b/tests/Feature/StandardUpdateOperationsTest.php index e299d0a2..4264ee6f 100644 --- a/tests/Feature/StandardUpdateOperationsTest.php +++ b/tests/Feature/StandardUpdateOperationsTest.php @@ -187,6 +187,7 @@ public function updating_a_single_resource_with_multiple_route_parameters(): voi public function updating_a_single_resource_with_multiple_route_parameters_fails_with_default_key_resolver(): void { if (DB::connection()->getDriverName() === 'pgsql') { + $this->withoutExceptionHandling(); $this->expectException(QueryException::class); } diff --git a/tests/Fixtures/routes/api.php b/tests/Fixtures/routes/api.php index 9aaedea9..d22604d1 100644 --- a/tests/Fixtures/routes/api.php +++ b/tests/Fixtures/routes/api.php @@ -39,5 +39,9 @@ Orion::belongsToResource('posts', 'user', PostUserController::class); Orion::belongsToResource('posts', 'category', PostCategoryController::class)->withSoftDeletes(); + + Orion::hasManyResource('companies', 'teams', CompanyTeamsController::class); + + Orion::belongsToManyResource('users', 'roles', UserRolesController::class); }); }); From 3fa175c429294252cae77332e0e9fd9c3ce5ff9d Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Fri, 6 Sep 2024 21:28:49 -0600 Subject: [PATCH 8/8] Add missed key resolver statement --- src/Concerns/HandlesRelationStandardOperations.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Concerns/HandlesRelationStandardOperations.php b/src/Concerns/HandlesRelationStandardOperations.php index 1185ba09..a01dd4db 100644 --- a/src/Concerns/HandlesRelationStandardOperations.php +++ b/src/Concerns/HandlesRelationStandardOperations.php @@ -257,11 +257,13 @@ public function search(Request $request, $parentKey) * Create new relation resource. * * @param Request $request - * @param int|string $parentKey + * @param array $args * @return Resource */ - public function store(Request $request, $parentKey) + public function store(Request $request, ...$args) { + $parentKey = $this->keyResolver->resolveRelationOperationParentKey($request, $args); + try { $this->startTransaction(); $result = $this->storeWithTransaction($request, $parentKey);