'superlevel'], ['body' => '100'] ); } /** * 测试普通登录成功后会轮换 session id,阻断会话固定风险。 */ public function test_login_regenerates_session_id(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(false); $user = User::factory()->create([ 'username' => 'session-user', 'password' => Hash::make('password123'), ]); $this->startSession(); $oldSessionId = session()->getId(); $response = $this->postJson('/login', [ 'username' => 'session-user', 'password' => 'password123', 'captcha' => '1234', ]); $response->assertOk() ->assertJsonPath('status', 'success'); $this->assertAuthenticatedAs($user); $this->assertNotSame($oldSessionId, session()->getId()); } /** * 验证首次登录的用户会被自动注册并完成登录。 */ public function test_can_register_new_user(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(false); $response = $this->postJson('/login', [ 'username' => 'newuser', 'password' => 'secret123', 'captcha' => '1234', 'bSex' => '1', ]); $response->assertStatus(200) ->assertJsonPath('status', 'success'); $this->assertDatabaseHas('users', [ 'username' => 'newuser', 'user_level' => 1, 'sex' => 1, ]); $this->assertAuthenticated(); } /** * 验证命中黑名单的用户名会被拒绝注册。 */ public function test_cannot_register_with_blacklisted_username(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(false); UsernameBlacklist::create([ 'username' => 'admin', 'type' => 'permanent', ]); $response = $this->postJson('/login', [ 'username' => 'admin', 'password' => 'secret123', 'captcha' => '1234', ]); $response->assertStatus(422) ->assertJsonPath('status', 'error'); $this->assertDatabaseMissing('users', [ 'username' => 'admin', ]); $this->assertGuest(); } /** * 验证已有用户可以通过正确密码登录。 */ public function test_can_login_existing_user(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(false); $user = User::factory()->create([ 'username' => 'testuser', 'password' => Hash::make('password123'), ]); $response = $this->postJson('/login', [ 'username' => 'testuser', 'password' => 'password123', 'captcha' => '1234', ]); $response->assertStatus(200) ->assertJsonPath('status', 'success'); $this->assertAuthenticatedAs($user); } /** * 验证旧版 MD5 密码会在成功登录后升级为 bcrypt。 */ public function test_login_md5_user_upgrades_to_bcrypt(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(false); $password = 'oldsecret'; $user = User::factory()->create([ 'username' => 'olduser', 'password' => 'temp', ]); \Illuminate\Support\Facades\DB::table('users') ->where('id', $user->id) ->update(['password' => md5($password)]); $response = $this->postJson('/login', [ 'username' => 'olduser', 'password' => $password, 'captcha' => '1234', ]); if ($response->status() !== 200) { dd($response->json()); } $response->assertStatus(200) ->assertJsonPath('status', 'success'); $user->refresh(); $this->assertTrue(Hash::check($password, $user->password)); $this->assertAuthenticatedAs($user); } /** * 验证被封禁账号无法登录。 */ public function test_banned_user_cannot_login(): void { $user = User::factory()->create([ 'username' => 'banneduser', 'password' => Hash::make('secret123'), 'user_level' => -1, // banned ]); $response = $this->postJson('/login', [ 'username' => 'banneduser', 'password' => 'secret123', 'captcha' => '1234', ]); $response->assertStatus(403) ->assertJsonPath('status', 'error'); $this->assertGuest(); } /** * 验证被封禁 IP 无法登录普通账号。 */ public function test_banned_ip_cannot_login(): void { Redis::shouldReceive('sismember')->with('banned_ips', '127.0.0.1')->andReturn(true); $user = User::factory()->create([ 'username' => 'normaluser', 'password' => Hash::make('secret'), ]); $response = $this->postJson('/login', [ 'username' => 'normaluser', 'password' => 'secret', 'captcha' => '1234', ]); $response->assertStatus(403); $this->assertGuest(); } /** * 验证已登录用户可以正常退出。 */ public function test_can_logout(): void { /** @var \App\Models\User $user */ $user = User::factory()->create(); $response = $this->actingAs($user)->post('/logout'); $response->assertRedirect('/'); $this->assertGuest(); } /** * 测试前台登录接口在连续失败后会触发服务端限流。 */ public function test_login_route_is_rate_limited_after_repeated_failures(): void { RateLimiter::clear('chat-login|rate-user|127.0.0.1'); Redis::shouldReceive('sismember')->zeroOrMoreTimes()->andReturn(false); User::factory()->create([ 'username' => 'rate-user', 'password' => Hash::make('correct-password'), ]); for ($attempt = 1; $attempt <= 5; $attempt++) { $response = $this->withServerVariables(['REMOTE_ADDR' => '127.0.0.1']) ->postJson('/login', [ 'username' => 'rate-user', 'password' => 'wrong-password', 'captcha' => '1234', ]); $response->assertStatus(422); } $rateLimitedResponse = $this->withServerVariables(['REMOTE_ADDR' => '127.0.0.1']) ->postJson('/login', [ 'username' => 'rate-user', 'password' => 'wrong-password', 'captcha' => '1234', ]); $rateLimitedResponse->assertStatus(429) ->assertJsonPath('status', 'error'); } }