Some checks failed
lotterLaravel CI / test (push) Has been cancelled
添加LOTTERY_E2E环境变量来控制E2E测试相关功能, 包括绕过验证码、登录限制和钱包API URL验证, 同时更新composer.json以包含E2E专用的提供者和服务。
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
/**
|
||
* 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();
|
||
});
|