set('services.mobile.password_reset_url', 'bowly://reset-password'); }); it('sends a password reset email with a mobile app url', function () { Notification::fake(); $user = User::factory()->create([ 'email' => 'leon@example.com', ]); $this ->postJson('/api/auth/forgot-password', [ 'email' => 'LEON@example.com', ]) ->assertOk() ->assertJsonPath('message', __('api.auth.password_reset_link_sent')); Notification::assertSentTo( $user, ResetPassword::class, function (ResetPassword $notification) use ($user): bool { $mail = $notification->toMail($user); $url = $mail->actionUrl; expect($url)->toStartWith('bowly://reset-password?'); parse_str((string) parse_url($url, PHP_URL_QUERY), $query); expect($query['email'])->toBe($user->email) ->and($query['token'])->not->toBeEmpty(); return true; } ); }); it('does not reveal whether a reset email can be sent', function () { Notification::fake(); $this ->postJson('/api/auth/forgot-password', [ 'email' => 'missing@example.com', ]) ->assertOk() ->assertJsonPath('message', __('api.auth.password_reset_link_sent')); Notification::assertNothingSent(); }); it('resets the password and invalidates existing api tokens', function () { $user = User::factory()->create([ 'email' => 'leon@example.com', 'password' => 'old-password', ]); $token = Password::broker()->createToken($user); $user->createToken('api'); $this ->postJson('/api/auth/reset-password', [ 'email' => 'LEON@example.com', 'password' => 'new-password', 'password_confirmation' => 'new-password', 'token' => $token, ]) ->assertOk() ->assertJsonPath('message', __('api.auth.password_reset')); expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue(); $this->assertDatabaseMissing('personal_access_tokens', [ 'tokenable_id' => $user->getKey(), ]); }); it('rejects an invalid password reset token', function () { $user = User::factory()->create([ 'email' => 'leon@example.com', ]); $this ->postJson('/api/auth/reset-password', [ 'email' => $user->email, 'password' => 'new-password', 'password_confirmation' => 'new-password', 'token' => 'invalid-token', ]) ->assertUnprocessable() ->assertJsonPath('message', __('passwords.token')); expect(Hash::check('new-password', $user->fresh()->password))->toBeFalse(); });