Files
chatroom/tests/Feature/PasswordResetRateLimitTest.php
T

192 lines
5.7 KiB
PHP
Raw Normal View History

<?php
/**
* 文件功能:找回密码账号检测与防扫描轰炸限流 Feature 测试
*
* 覆盖密码重置流程中的多维限流阀门,确保 IP 防扫与用户邮箱防轰炸策略稳健起效。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace Tests\Feature;
use App\Models\Sysparam;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\RateLimiter;
use Tests\TestCase;
class PasswordResetRateLimitTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
RateLimiter::clear('pw-check:ip:127.0.0.1');
RateLimiter::clear('pw-email:ip:127.0.0.1');
}
/**
* 测试账号检测:不存在账号返回 404/not_found
*/
public function test_check_account_returns_not_found_when_user_does_not_exist(): void
{
$response = $this->postJson(route('password.check_account'), [
'username' => 'non_existing_user_abc',
]);
$response->assertStatus(200);
$response->assertJson([
'status' => 'not_found',
'message' => '抱歉,没有找到该昵称对应的账号。请确认后再试。',
]);
}
/**
* 测试账号检测分流状态与脱敏邮箱输出
*/
public function test_check_account_shows_correct_channels(): void
{
// 1. 双绑定用户
$bothBoundUser = User::factory()->create([
'username' => 'both_user',
'email' => 'both.test@example.com',
'wxid' => 'wxid_both',
]);
$response1 = $this->postJson(route('password.check_account'), [
'username' => 'both_user',
]);
$response1->assertJson([
'status' => 'success',
'has_email' => true,
'has_wechat' => true,
'masked_email' => 'b*******t@example.com',
]);
// 2. 仅绑定微信用户
$wechatUser = User::factory()->create([
'username' => 'wx_user',
'email' => null,
'wxid' => 'wxid_single',
]);
$response2 = $this->postJson(route('password.check_account'), [
'username' => 'wx_user',
]);
$response2->assertJson([
'status' => 'success',
'has_email' => false,
'has_wechat' => true,
]);
// 3. 均未绑定用户
$noneUser = User::factory()->create([
'username' => 'none_user',
'email' => null,
'wxid' => null,
]);
$response3 = $this->postJson(route('password.check_account'), [
'username' => 'none_user',
]);
$response3->assertJson([
'status' => 'success',
'has_email' => false,
'has_wechat' => false,
]);
}
/**
* 测试 IP 防扫描限流限制 (每个 IP 1分钟限制 5 次检测)
*/
public function test_check_account_rate_limiting(): void
{
$ip = '127.0.0.1';
// 连续请求 5 次都应该正常响应
for ($i = 0; $i < 5; $i++) {
$response = $this->postJson(route('password.check_account'), [
'username' => 'non_existing_user',
]);
$response->assertStatus(200);
}
// 第 6 次应该被 RateLimiter 节流拦截返回 429
$response = $this->postJson(route('password.check_account'), [
'username' => 'non_existing_user',
]);
$response->assertStatus(429);
$response->assertJson([
'status' => 'error',
]);
$this->assertStringContainsString('请求过于频繁', $response->json('message'));
}
/**
* 测试当用户输入的邮箱地址与绑定的真实邮箱不匹配时,二次核验拦截报错
*/
public function test_store_link_fails_when_masked_email_mismatch(): void
{
Sysparam::updateOrCreate(['alias' => 'smtp_enabled'], ['body' => '1']);
User::factory()->create([
'username' => 'mismatch_user',
'email' => 'real.mail@example.com',
]);
$response = $this->postJson(route('password.email'), [
'username' => 'mismatch_user',
'email' => 'wrong.mail@example.com', // 错误的手输邮箱
]);
$response->assertStatus(422);
$response->assertJson([
'status' => 'error',
'message' => '输入的完整邮箱地址与该账号绑定的邮箱不一致,二次确认失败。',
]);
}
/**
* 测试发送链接的双层安全控频防御 (防止发信轰炸他人)
*/
public function test_store_link_prevents_email_bombing(): void
{
Sysparam::updateOrCreate(['alias' => 'smtp_enabled'], ['body' => '1']);
$user = User::factory()->create([
'username' => 'bomb_user',
'email' => 'bomb.target@example.com',
]);
$email = $user->email;
$targetKey = 'pw-email:target:'.md5($email);
RateLimiter::clear($targetKey);
// 模拟第一次发送
$response1 = $this->postJson(route('password.email'), [
'username' => 'bomb_user',
'email' => 'bomb.target@example.com',
]);
// 第二次在 3 分钟内再次连续发送,触发 429 节流
$response2 = $this->postJson(route('password.email'), [
'username' => 'bomb_user',
'email' => 'bomb.target@example.com',
]);
$response2->assertStatus(429);
$response2->assertJson([
'status' => 'error',
]);
$this->assertStringContainsString('重置链接过于频繁', $response2->json('message'));
}
}