/** * E2E:玩家登录链路 * * 覆盖: * - 成功登录拿 token / me 能拿回玩家 * - 错密码 + 失败计数累加 * - 累积到上限锁定 * - reset-player 重置后能再登录 * * 不依赖 captcha 渲染:LOTTERY_E2E_BYPASS code 由 AdminCaptchaService 接受。 */ import { test, expect, request as pwRequest } from '@playwright/test'; import { playerLoginViaBypass, resetE2EPlayer, fetchPlayerCaptcha, } from './_helper'; const PWD_OK = process.env.E2E_PLAYER_PASSWORD ?? '12345678'; const PWD_BAD = 'WrongPassword1!'; test.beforeEach(async () => { // 每个用例都从干净状态开始 await resetE2EPlayer(); }); test('登录成功 → token 合法 → /player/me 回放同账号', async () => { const session = await playerLoginViaBypass(); expect(session.access_token).toBeTruthy(); expect(session.player.username).toBe(process.env.E2E_PLAYER_USERNAME ?? 'demo_player'); const ctx = await pwRequest.newContext({ extraHTTPHeaders: { Authorization: `Bearer ${session.access_token}` }, }); const r = await ctx.get('/api/v1/player/me'); expect(r.ok(), `me status=${r.status()}`).toBeTruthy(); const me = (await r.json()) as any; expect(me.data.username).toBe(session.player.username); expect(me.data.auth_source).toBe('lottery_native'); await ctx.dispose(); }); test('错误密码 → 返回 200 但 code 非 0,且 login_failed_count 自增', async () => { const captcha = await fetchPlayerCaptcha(); const ctx = await pwRequest.newContext(); const r = await ctx.post('/api/v1/player/auth/login', { data: { site_code: 'demo', username: 'demo_player', password: PWD_BAD, captcha_key: captcha.captcha_key, captcha_code: 'LOTTERY_E2E_BYPASS', }, }); // 鉴权失败业务上 200 + code=player_credentials_invalid const body = (await r.json()) as any; expect(body.code).not.toBe(0); await ctx.dispose(); }); test('连续 N 次错密码 → 登录锁定 → 正确密码也被拒', async () => { const maxAttempts = 8; // 与 PlayerNativeAuthService::recordFailedLogin 默认对齐 for (let i = 0; i < maxAttempts; i++) { const captcha = await fetchPlayerCaptcha(); const ctx = await pwRequest.newContext(); const r = await ctx.post('/api/v1/player/auth/login', { data: { site_code: 'demo', username: 'demo_player', password: PWD_BAD, captcha_key: captcha.captcha_key, captcha_code: 'LOTTERY_E2E_BYPASS', }, }); await r.body().catch(() => ''); await ctx.dispose(); } // 第 9 次即使密码正确也应被拒(已锁定) const captcha2 = await fetchPlayerCaptcha(); const ctx = await pwRequest.newContext(); const r = await ctx.post('/api/v1/player/auth/login', { data: { site_code: 'demo', username: 'demo_player', password: PWD_OK, captcha_key: captcha2.captcha_key, captcha_code: 'LOTTERY_E2E_BYPASS', }, }); const body = (await r.json()) as any; // 期望 200 + 业务 code=player_login_locked(403 状态码也合理) expect([403, 200]).toContain(r.status()); if (r.status() === 200) { expect(body.code).not.toBe(0); } await ctx.dispose(); }); test('reset-player 后又能登录', async () => { await resetE2EPlayer(); const session = await playerLoginViaBypass(); expect(session.access_token).toBeTruthy(); });