From c2a6884d0a4129eb17b74c52601a1b8a14b8f985 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Wed, 15 Jul 2026 21:15:55 +0900 Subject: [PATCH 1/2] feat: add logout action to AuthController and register POST /v1/user/logout route --- .../Features/Controller/AuthController.php | 22 +++++++++++++++++++ src/routes/api.php | 6 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/app/Packages/Features/Controller/AuthController.php b/src/app/Packages/Features/Controller/AuthController.php index b7c18d1..40ef398 100644 --- a/src/app/Packages/Features/Controller/AuthController.php +++ b/src/app/Packages/Features/Controller/AuthController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Packages\Features\CommandUseCases\UseCase\User\LoginUseCase; +use App\Packages\Features\CommandUseCases\UseCase\User\LogoutUseCase; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -46,4 +47,25 @@ public function login( ], 500); } } + + public function logout(Request $request, LogoutUseCase $useCase): JsonResponse + { + try { + $useCase->handle($request->bearerToken() ?? ''); + + return response()->json([ + 'status' => 'success', + ], 200); + } catch (Throwable $throw) { + Log::error('Failed to logout', [ + 'message' => $throw->getMessage(), + 'trace' => $throw->getTraceAsString(), + ]); + + return response()->json([ + 'status' => 'error', + 'message' => 'Internal Server Error', + ], 500); + } + } } diff --git a/src/routes/api.php b/src/routes/api.php index d499e71..3b734cc 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -14,7 +14,11 @@ }); Route::post('/user/create', [UserController::class, 'createUser']); - Route::post('/user/login', [AuthController::class, 'login']); + + Route::controller(AuthController::class)->prefix('user')->group(function (): void { + Route::post('/login', 'login'); + Route::post('/logout', 'logout')->middleware('auth:sanctum'); + }); Route::controller(UserController::class)->prefix('users')->group(function (): void { Route::get('/{id}', 'getUserById'); From cd841fba27d96eb8ace185bd6c94f9b6e4d5f17b Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Wed, 15 Jul 2026 21:16:43 +0900 Subject: [PATCH 2/2] test: add integration tests for POST /api/v1/user/logout --- .../Packages/Features/Tests/LogoutTest.php | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/app/Packages/Features/Tests/LogoutTest.php diff --git a/src/app/Packages/Features/Tests/LogoutTest.php b/src/app/Packages/Features/Tests/LogoutTest.php new file mode 100644 index 0000000..ac09939 --- /dev/null +++ b/src/app/Packages/Features/Tests/LogoutTest.php @@ -0,0 +1,89 @@ +truncate(); + } + + protected function tearDown(): void + { + $this->truncate(); + parent::tearDown(); + } + + private function truncate(): void + { + if (env('APP_ENV') === 'testing') { + DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=0;'); + DB::connection('mysql')->table('personal_access_tokens')->truncate(); + User::truncate(); + DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=1;'); + } + } + + private function seedUser(): User + { + return User::create([ + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email' => 'john@example.com', + 'password' => bcrypt('secret123'), + 'age_range' => '20s', + 'subscription_tier' => 'free', + 'subscription_expires_at' => null, + ]); + } + + public function test_logout_returns_200_when_authenticated(): void + { + $user = $this->seedUser(); + $token = $user->createToken('auth-token')->plainTextToken; + + $response = $this->withToken($token) + ->postJson('/api/v1/user/logout'); + + $response->assertStatus(200) + ->assertJsonFragment(['status' => 'success']); + + $this->assertDatabaseMissing('personal_access_tokens', [ + 'tokenable_id' => $user->id, + ]); + } + + public function test_logout_returns_401_when_unauthenticated(): void + { + $response = $this->postJson('/api/v1/user/logout'); + + $response->assertStatus(401); + } + + public function test_logout_returns_500_on_unexpected_error(): void + { + $user = $this->seedUser(); + $token = $user->createToken('auth-token')->plainTextToken; + + $this->mock(LogoutUseCase::class) + ->shouldReceive('handle') + ->andThrow(new RuntimeException('Unexpected error')); + + $response = $this->withToken($token) + ->postJson('/api/v1/user/logout'); + + $response->assertStatus(500) + ->assertJsonFragment([ + 'status' => 'error', + 'message' => 'Internal Server Error', + ]); + } +}