From 105d14f116c649be675708ec51e7b8613f57714a Mon Sep 17 00:00:00 2001 From: fokosun Date: Mon, 21 Aug 2023 22:08:23 -0400 Subject: [PATCH] more coverage --- app/Http/Controllers/RecipeController.php | 47 +--- app/Models/Role.php | 12 + app/Models/User.php | 12 +- ...3_08_22_004422_create_user_roles_table.php | 29 ++ .../2023_08_22_004528_create_roles_table.php | 28 ++ database/seeders/StaticContentsSeeder.php | 11 +- tests/Feature/RecipeTest.php | 253 ++++++++++++++++++ 7 files changed, 353 insertions(+), 39 deletions(-) create mode 100644 app/Models/Role.php create mode 100644 database/migrations/2023_08_22_004422_create_user_roles_table.php create mode 100644 database/migrations/2023_08_22_004528_create_roles_table.php diff --git a/app/Http/Controllers/RecipeController.php b/app/Http/Controllers/RecipeController.php index 27b768de..703e2a15 100755 --- a/app/Http/Controllers/RecipeController.php +++ b/app/Http/Controllers/RecipeController.php @@ -4,18 +4,13 @@ namespace App\Http\Controllers; -use App\Exceptions\CookbookModelNotFoundException; use App\Http\Requests\RecipeStoreRequest; use App\Models\Recipe; use App\Services\RecipeService; -use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; -use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth; -use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\JWT; /** @@ -57,12 +52,6 @@ public function show($recipeId) return $this->service->show($recipeId); } - /** - * @param Request $request - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response - * @throws \App\Exceptions\CookbookModelNotFoundException - * @throws \Illuminate\Validation\ValidationException - */ public function addClap(Request $request) { $this->validate( @@ -85,11 +74,6 @@ public function myRecipes(Request $request, JWT $jwtAuth): \Illuminate\Http\Json ], 401); } - /** - * @param RecipeStoreRequest $request - * @param JWT $jwtAuth - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\Response - */ public function store(RecipeStoreRequest $request, JWT $jwtAuth) { try { @@ -115,21 +99,16 @@ public function store(RecipeStoreRequest $request, JWT $jwtAuth) } } - /** - * @param Request $request - * @param $recipeId - * @param JWT $jwtAuth - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\Response - * @throws \App\Exceptions\CookbookModelNotFoundException - * @throws \Tymon\JWTAuth\Exceptions\JWTException - */ public function update(Request $request, $recipeId, JWT $jwtAuth) { if ( - $request->user()->ownRecipe($recipeId) && - $jwtAuth->parseToken()->check() + $request->user()->ownRecipe($recipeId) ) { - return $this->service->update($request, $recipeId); + if ( + $jwtAuth->parseToken()->check() + ) { + return $this->service->update($request, $recipeId); + } } return response()->json([ @@ -137,14 +116,6 @@ public function update(Request $request, $recipeId, JWT $jwtAuth) ], 401); } - /** - * @param Request $request - * @param $recipeId - * @param JWT $jwtAuth - * @return Application|ResponseFactory|JsonResponse|Response - * @throws CookbookModelNotFoundException - * @throws JWTException - */ public function destroy(Request $request, $recipeId, JWT $jwtAuth) { if ( @@ -159,7 +130,7 @@ public function destroy(Request $request, $recipeId, JWT $jwtAuth) ], Response::HTTP_UNAUTHORIZED); } - public function report(Request $request, JWT $jwtAuth) + public function report(Request $request, JWT $jwtAuth): JsonResponse { if ($jwtAuth->parseToken()->check()) { $recipe = Recipe::find($request->get('recipe_id')); @@ -181,5 +152,9 @@ public function report(Request $request, JWT $jwtAuth) 'message' => 'There was an error processing this request. Please try again later.' ]); } + + return response()->json([ + 'error' => 'You are not authorized to perform this action.' + ], Response::HTTP_UNAUTHORIZED); } } diff --git a/app/Models/Role.php b/app/Models/Role.php new file mode 100644 index 00000000..4947109f --- /dev/null +++ b/app/Models/Role.php @@ -0,0 +1,12 @@ +getKey(), $followings); } - private function hasRole(string $role) + private function hasRole(string $role_id) { - return false; + $role_id = DB::table('roles')->where(['role_id' => $role_id])->first()?->id; + $roles = $this->roles()->pluck('role_id')->toArray(); + + return in_array($role_id, $roles); + } + + public function roles(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(Role::class, 'user_id'); } public function getTikTokUser() diff --git a/database/migrations/2023_08_22_004422_create_user_roles_table.php b/database/migrations/2023_08_22_004422_create_user_roles_table.php new file mode 100644 index 00000000..e6a2c2c0 --- /dev/null +++ b/database/migrations/2023_08_22_004422_create_user_roles_table.php @@ -0,0 +1,29 @@ +id(); + $table->integer('user_id'); + $table->integer('role_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_roles'); + } +}; diff --git a/database/migrations/2023_08_22_004528_create_roles_table.php b/database/migrations/2023_08_22_004528_create_roles_table.php new file mode 100644 index 00000000..a254f5da --- /dev/null +++ b/database/migrations/2023_08_22_004528_create_roles_table.php @@ -0,0 +1,28 @@ +id(); + $table->text('role_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('roles'); + } +}; diff --git a/database/seeders/StaticContentsSeeder.php b/database/seeders/StaticContentsSeeder.php index 5c23c4ee..1d6e7a71 100755 --- a/database/seeders/StaticContentsSeeder.php +++ b/database/seeders/StaticContentsSeeder.php @@ -5,6 +5,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class StaticContentsSeeder extends Seeder { @@ -17,7 +18,7 @@ class StaticContentsSeeder extends Seeder */ public function run() { - \Illuminate\Support\Facades\DB::table('static_contents')->insert([ + DB::table('static_contents')->insert([ [ 'title' => 'cookie-policy', 'content' => file_get_contents(__DIR__.'/policies/cookie-policy.php'), @@ -32,5 +33,13 @@ public function run() 'content' => file_get_contents(__DIR__.'/policies/terms-and-conditions.php'), ], ]); + + DB::table('roles')->insert([ + [ + 'role_id' => 'super', + ], [ + 'role_id' => 'contributor', + ] + ]); } } diff --git a/tests/Feature/RecipeTest.php b/tests/Feature/RecipeTest.php index fd54fd8f..bdfb3269 100755 --- a/tests/Feature/RecipeTest.php +++ b/tests/Feature/RecipeTest.php @@ -6,10 +6,13 @@ use App\Models\Cookbook; use App\Models\Recipe; +use App\Models\Role; use App\Models\User; +use Faker\Factory; use Illuminate\Hashing\BcryptHasher; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; class RecipeTest extends \TestCase @@ -317,4 +320,254 @@ public function it_allows_only_authorized_user_to_report_a_recipe() 'error' => 'Your session has expired. Please login and try again.' ]); } + + /** + * @test + */ + public function only_supers_can_destroy_a_recipe() + { + $this->createRoles(); + + $user = User::factory()->make([ + 'email' => 'evan.reid@123.com', + 'password' => (new BcryptHasher)->make('pass123'), + ]); + $user->save(); + + $this->createUserRole($user->refresh()->getKey(), 'super'); + + $token = Auth::attempt([ + 'email' => 'evan.reid@123.com', + 'password' => 'pass123' + ]); + + $cookbook = Cookbook::factory()->make([ + 'user_id' => $user->refresh()->getKey() + ]); + + $cookbook->save(); + + $recipe = Recipe::factory()->make([ + 'cookbook_id' => $cookbook->refresh()->getKey(), + 'user_id' => $user->getKey() + ]); + + $recipe->save(); + + $this->assertDatabaseHas('recipes', [ + 'id' => $recipe->refresh()->getKey() + ]); + + $this->json( + 'POST', + '/api/v1/recipes/' . $recipe->refresh()->getKey() . '/destroy', + [ + 'recipe_id' => $recipe->refresh()->getKey() + ], + [ + 'Authorization' => 'Bearer ' . $token + ] + )->assertStatus(Response::HTTP_ACCEPTED) + ->assertExactJson([ + "deleted" => true + ]); + } + + /** + * @test + */ + public function if_you_are_not_a_super_you_cannot_destroy_a_recipe() + { + $user = User::factory()->make([ + 'email' => 'evan.reid@123.com', + 'password' => (new BcryptHasher)->make('pass123'), + ]); + $user->save(); + + $token = Auth::attempt([ + 'email' => 'evan.reid@123.com', + 'password' => 'pass123' + ]); + + $cookbook = Cookbook::factory()->make([ + 'user_id' => $user->refresh()->getKey() + ]); + + $cookbook->save(); + + $recipe = Recipe::factory()->make([ + 'cookbook_id' => $cookbook->refresh()->getKey(), + 'user_id' => $user->getKey() + ]); + + $recipe->save(); + + $this->assertDatabaseHas('recipes', [ + 'id' => $recipe->refresh()->getKey() + ]); + + $this->json( + 'POST', + '/api/v1/recipes/' . $recipe->refresh()->getKey() . '/destroy', + [ + 'recipe_id' => $recipe->refresh()->getKey() + ], + [ + 'Authorization' => 'Bearer ' . $token + ] + )->assertStatus(Response::HTTP_UNAUTHORIZED) + ->assertExactJson([ + "error" => 'You are not authorized to perform this action.' + ]); + } + + /** + * @test + */ + public function it_can_update_an_existing_recipe() + { + $faker = Factory::create(); + + $user = User::factory()->make([ + 'email' => 'evan.reid@123.com', + 'password' => (new BcryptHasher)->make('pass123'), + ]); + $user->save(); + + $token = Auth::attempt([ + 'email' => 'evan.reid@123.com', + 'password' => 'pass123' + ]); + + $cookbook = Cookbook::factory()->make([ + 'user_id' => $user->refresh()->getKey() + ]); + + $cookbook->save(); + + $newCookbook = Cookbook::factory()->make([ + 'user_id' => $user->refresh()->getKey() + ]); + + $newCookbook->save(); + + $recipe = Recipe::factory()->make([ + 'cookbook_id' => $cookbook->refresh()->getKey(), + 'user_id' => $user->getKey() + ]); + + $recipe->save(); + + $oldValues = [ + 'cookbook_id' => $recipe->refresh()->cookbook_id, + 'description' => $recipe->refresh()->description, + 'summary' => $recipe->refresh()->summary, + 'imgUrl' => $recipe->refresh()->imgUrl, + ]; + + $this->assertDatabaseHas('recipes', [ + 'id' => $recipe->refresh()->getKey() + ]); + + $this->json( + 'POST', + '/api/v1/recipes/' . $recipe->refresh()->getKey() . '/edit', + [ + 'cookbook_id' => $newCookbook->refresh()->getKey(), + 'description' => implode(" ", $faker->words(150)), + 'summary' => implode(" ", $faker->words(55)), + 'imgUrl' => $faker->imageUrl(), + 'ingredients' => [ + [ + 'name' => $faker->jobTitle, + 'unit' => '2', + 'thumbnail' => $faker->imageUrl(), + ] + ], + 'tags' => [] + ], + [ + 'Authorization' => 'Bearer ' . $token + ] + )->assertStatus(Response::HTTP_OK) + ->assertExactJson([ + "updated" => true + ]); + + $updatedRecipe = $recipe->refresh(); + $this->assertNotSame($oldValues['cookbook_id'], $updatedRecipe->cookbook_id); + $this->assertNotSame($oldValues['description'], $updatedRecipe->description); + $this->assertNotSame($oldValues['summary'], $updatedRecipe->summary); + $this->assertNotSame($oldValues['imgUrl'], $updatedRecipe->imgUrl); + } + + /** + * @test + */ + public function you_cannot_update_a_recipe_you_do_not_own() + { + $faker = Factory::create(); + + $me = User::factory()->make([ + 'email' => 'evan.reid@123.com', + 'password' => (new BcryptHasher)->make('pass123'), + ]); + $me->save(); + + $theOtherUser = User::factory()->make([ + 'email' => 'evan.reid2@123.com', + 'password' => (new BcryptHasher)->make('pass123'), + ]); + $theOtherUser->save(); + + $myToken = Auth::attempt([ + 'email' => 'evan.reid@123.com', + 'password' => 'pass123' + ]); + + $cookbook = Cookbook::factory()->make([ + 'user_id' => $me->refresh()->getKey() + ]); + + $cookbook->save(); + + $salisRecipe = Recipe::factory()->make([ + 'cookbook_id' => $cookbook->refresh()->getKey(), + 'user_id' => $theOtherUser->refresh()->getKey() + ]); + + $salisRecipe->save(); + + $this->json( + 'POST', + '/api/v1/recipes/' . $salisRecipe->refresh()->getKey() . '/edit', + [ + 'description' => implode(" ", $faker->words(150)), + ], + [ + 'Authorization' => 'Bearer ' . $myToken + ] + )->assertStatus(Response::HTTP_UNAUTHORIZED); + } + + private function createRoles() + { + DB::table('roles')->insert([ + [ + 'role_id' => 'super', + ], [ + 'role_id' => 'contributor', + ] + ]); + } + + private function createUserRole($user_id, $role_id) + { + $role_id = DB::table('roles')->where(['role_id' => $role_id])->first()->id; + + $role = new Role(); + $role->user_id = $user_id; + $role->role_id = $role_id; + $role->save(); + } }