create([ 'email' => 'admin@pm.com', 'password' => Hash::make('password'), 'status' => 'active', ]); $response = $this->postJson('/api/login', [ 'email' => ' admin@pm.com ', 'password' => 'password', ]); $response ->assertOk() ->assertJsonPath('success', true) ->assertJsonPath('data.user.id', $user->id) ->assertJsonStructure([ 'data' => ['user', 'token', 'permissions'], ]); } public function test_login_rejects_invalid_credentials(): void { User::factory()->create([ 'email' => 'admin@pm.com', 'password' => Hash::make('password'), 'status' => 'active', ]); $this->postJson('/api/login', [ 'email' => 'admin@pm.com', 'password' => 'wrong-password', ]) ->assertUnauthorized() ->assertJsonPath('success', false); } public function test_inactive_user_cannot_login(): void { User::factory()->create([ 'email' => 'inactive@pm.com', 'password' => Hash::make('password'), 'status' => 'inactive', ]); $this->postJson('/api/login', [ 'email' => 'inactive@pm.com', 'password' => 'password', ]) ->assertForbidden() ->assertJsonPath('success', false); } public function test_authenticated_user_can_be_loaded_and_logged_out(): void { $user = User::factory()->create([ 'password' => Hash::make('password'), 'status' => 'active', ]); $token = $user->createToken('api-token')->plainTextToken; $this->withToken($token) ->getJson('/api/user') ->assertOk() ->assertJsonPath('data.id', $user->id); $this->withToken($token) ->postJson('/api/logout') ->assertOk() ->assertJsonPath('success', true); Auth::forgetGuards(); $this->withToken($token) ->getJson('/api/user') ->assertUnauthorized(); } public function test_password_change_revokes_all_access_tokens(): void { $user = User::factory()->create([ 'password' => Hash::make('old-password'), 'status' => 'active', ]); $currentToken = $user->createToken('current')->plainTextToken; $user->createToken('other'); $this->withToken($currentToken) ->putJson('/api/user/password', [ 'current_password' => 'old-password', 'new_password' => 'new-secure-password', 'new_password_confirmation' => 'new-secure-password', ]) ->assertOk() ->assertJsonPath('reauthentication_required', true); $this->assertSame(0, $user->tokens()->count()); $this->assertTrue(Hash::check('new-secure-password', $user->fresh()->password)); } public function test_forgot_password_sends_reset_notification_without_account_disclosure(): void { Notification::fake(); $user = User::factory()->create(['email' => 'reset@example.com', 'status' => 'active']); $this->postJson('/api/forgot-password', ['email' => $user->email]) ->assertOk() ->assertJsonPath('success', true); Notification::assertSentTo($user, ResetPassword::class); $this->postJson('/api/forgot-password', ['email' => 'missing@example.com']) ->assertOk() ->assertJsonPath('success', true); } }