'JOIN2026', 'role' => 'user', 'max_uses' => 1, 'used_count' => 0, 'is_active' => true, ]); $register = $this->postJson('/api/auth/register', [ 'name' => '学生甲', 'email' => 'student@example.com', 'password' => 'secret123', 'password_confirmation' => 'secret123', 'invite_code' => $invite->code, ])->assertOk() ->assertJsonPath('data.user.email', 'student@example.com') ->assertJsonPath('data.user.role', 'user'); $this->assertSame(1, $invite->fresh()->used_count); $token = $register->json('data.token'); $this->withToken($token) ->getJson('/api/auth/me') ->assertOk() ->assertJsonPath('data.email', 'student@example.com'); $login = $this->postJson('/api/auth/login', [ 'email' => 'student@example.com', 'password' => 'secret123', ])->assertOk() ->assertJsonPath('data.user.email', 'student@example.com'); $refreshed = $this->withToken($login->json('data.token')) ->postJson('/api/auth/refresh') ->assertOk() ->json('data.token'); $this->withToken($refreshed) ->postJson('/api/auth/logout') ->assertOk() ->assertJsonPath('message', '已退出'); } public function test_login_failures_require_captcha_after_limit(): void { $user = User::factory()->create([ 'email' => 'limited@example.com', 'password' => Hash::make('correct-password'), ]); for ($i = 0; $i < 5; $i++) { $this->postJson('/api/auth/login', [ 'email' => $user->email, 'password' => 'wrong-password', ])->assertStatus(422); } $this->assertSame(5, $user->fresh()->failed_login_count); $this->postJson('/api/auth/login', [ 'email' => $user->email, 'password' => 'correct-password', ])->assertStatus(429) ->assertJsonPath('data.captcha_required', true); } }