Files
lotteryLaravel/e2e/tests/api/02.player-auth.spec.ts
kang 6ec9634704
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
feat: 添加E2E测试环境配置支持
添加LOTTERY_E2E环境变量来控制E2E测试相关功能,
包括绕过验证码、登录限制和钱包API URL验证,
同时更新composer.json以包含E2E专用的提供者和服务。
2026-06-18 14:42:22 +08:00

106 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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_locked403 状态码也合理)
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();
});