From 76bbd1afeff769b56c45b119b9ae1c5abcfc541b Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sun, 16 Aug 2026 22:16:14 +0900 Subject: [PATCH 1/2] feat: add removeFavorite to FavoriteController and route --- .../Controller/FavoriteController.php | 34 +++++++++++++++++++ src/routes/api.php | 1 + 2 files changed, 35 insertions(+) diff --git a/src/app/Packages/Features/Controller/FavoriteController.php b/src/app/Packages/Features/Controller/FavoriteController.php index 10004a59..7a7ce9ed 100644 --- a/src/app/Packages/Features/Controller/FavoriteController.php +++ b/src/app/Packages/Features/Controller/FavoriteController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Packages\Features\CommandUseCases\UseCase\Favorite\AddFavoriteUseCase; +use App\Packages\Features\CommandUseCases\UseCase\Favorite\RemoveFavoriteUseCase; use App\Packages\Features\QueryUseCases\UseCase\Favorite\GetFavoriteHeritagesUseCase; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -68,4 +69,37 @@ public function addFavorite( ], 500); } } + + public function removeFavorite( + Request $request, + RemoveFavoriteUseCase $useCase, + ): JsonResponse { + try { + $useCase->handle( + $request->user()->id, + (int) $request->route('world_heritage_id'), + ); + + return response()->json([ + 'status' => 'success', + ], 200); + } catch (Throwable $throwable) { + if ($throwable->getMessage() === 'User not found.') { + return response()->json([ + 'status' => 'error', + 'message' => 'User not found.', + ], 404); + } + + Log::error('Failed to remove favorite', [ + 'message' => $throwable->getMessage(), + 'trace' => $throwable->getTraceAsString(), + ]); + + return response()->json([ + 'status' => 'error', + 'message' => 'Internal Server Error', + ], 500); + } + } } diff --git a/src/routes/api.php b/src/routes/api.php index 4c58d279..83bd0a17 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -31,5 +31,6 @@ Route::controller(FavoriteController::class)->prefix('favorites')->middleware('auth:sanctum')->group(function (): void { Route::get('/', 'getFavorites'); Route::post('/', 'addFavorite'); + Route::delete('/{world_heritage_id}', 'removeFavorite'); }); }); \ No newline at end of file From 4527c4ee472475b45d4a1c67eca233cc3754fdef Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sun, 16 Aug 2026 22:16:14 +0900 Subject: [PATCH 2/2] test: cover removeFavorite in FavoriteControllerTest --- .../Features/Tests/FavoriteControllerTest.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/app/Packages/Features/Tests/FavoriteControllerTest.php b/src/app/Packages/Features/Tests/FavoriteControllerTest.php index b9f6a768..1f4229d9 100644 --- a/src/app/Packages/Features/Tests/FavoriteControllerTest.php +++ b/src/app/Packages/Features/Tests/FavoriteControllerTest.php @@ -8,6 +8,7 @@ use App\Packages\Domains\WorldHeritage\Ports\Dto\HeritageSearchResult; use App\Packages\Domains\WorldHeritage\Ports\WorldHeritageSearchPort; use App\Packages\Features\CommandUseCases\UseCase\Favorite\AddFavoriteUseCase; +use App\Packages\Features\CommandUseCases\UseCase\Favorite\RemoveFavoriteUseCase; use App\Packages\Features\QueryUseCases\QueryServiceInterface\WorldHeritageQueryServiceInterface; use App\Packages\Features\QueryUseCases\UseCase\Favorite\GetFavoriteHeritagesUseCase; use Illuminate\Support\Facades\DB; @@ -124,6 +125,68 @@ public function test_addFavorite_returns_500_on_unexpected_error(): void ]); } + public function test_removeFavorite_returns_200_and_removes_favorite_when_authenticated(): void + { + $user = $this->seedUser(); + $worldHeritage = $this->seedWorldHeritage(1); + $token = $user->createToken('auth-token')->plainTextToken; + $user->favorites()->attach($worldHeritage->id); + + $response = $this->withToken($token) + ->deleteJson("/api/v1/favorites/{$worldHeritage->id}"); + + $response->assertStatus(200) + ->assertJsonFragment(['status' => 'success']); + + $this->assertDatabaseMissing('user_favorite', [ + 'user_id' => $user->id, + 'world_heritage_site_id' => $worldHeritage->id, + ]); + } + + public function test_removeFavorite_returns_200_when_not_favorited(): void + { + $user = $this->seedUser(); + $worldHeritage = $this->seedWorldHeritage(1); + $token = $user->createToken('auth-token')->plainTextToken; + + $response = $this->withToken($token) + ->deleteJson("/api/v1/favorites/{$worldHeritage->id}"); + + $response->assertStatus(200) + ->assertJsonFragment(['status' => 'success']); + } + + public function test_removeFavorite_returns_401_when_unauthenticated(): void + { + $worldHeritage = $this->seedWorldHeritage(1); + + $response = $this->deleteJson("/api/v1/favorites/{$worldHeritage->id}"); + + $response->assertStatus(401); + } + + public function test_removeFavorite_returns_500_on_unexpected_error(): void + { + $user = $this->seedUser(); + $token = $user->createToken('auth-token')->plainTextToken; + + $repository = Mockery::mock(FavoriteRepositoryInterface::class); + $repository->shouldReceive('removeFavorite') + ->andThrow(new RuntimeException('Unexpected error')); + + $this->app->instance(RemoveFavoriteUseCase::class, new RemoveFavoriteUseCase($repository)); + + $response = $this->withToken($token) + ->deleteJson('/api/v1/favorites/1'); + + $response->assertStatus(500) + ->assertJsonFragment([ + 'status' => 'error', + 'message' => 'Internal Server Error', + ]); + } + public function test_getFavorites_returns_200_with_favorite_heritages_when_authenticated(): void { $user = $this->seedUser();