添加LOTTERY_E2E环境变量来控制E2E测试相关功能, 包括绕过验证码、登录限制和钱包API URL验证, 同时更新composer.json以包含E2E专用的提供者和服务。
This commit is contained in:
105
e2e/tests/api/02.player-auth.spec.ts
Normal file
105
e2e/tests/api/02.player-auth.spec.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
Reference in New Issue
Block a user