Files
lotteryLaravel/e2e/tests/api/07.admin-player.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

248 lines
7.9 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超管玩家管理
*
* 链路:
* 1. 创建玩家site_code=demo, username/password
* 2. 用新建玩家登录 → 拿 token
* 3. /admin/players/{id}/freeze → status=1
* 4. 玩家用 frozen 账号登录 → 403 PlayerAccountSuspended
* 5. /admin/players/{id}/unfreeze → status=0
* 6. 玩家又能登录
* 7. /admin/players 列表能找到新建玩家
* 8. /admin/players/{id}/wallets 返回钱包列表
*
* 不测 destroy避免把 e2e 自带的 demo_player 误删player id 不固定)。
*/
import { test, expect, request as pwRequest } from '@playwright/test';
import { adminLoginViaBypass, fetchPlayerCaptcha, e2e } from './_helper';
const SITE_CODE = 'demo';
const CURRENCY = (process.env.LOTTERY_DEFAULT_CURRENCY ?? 'NPR').toUpperCase();
const PWD = '12345678';
function uniqueUsername(): string {
// 玩家 username 规则 native 6-32 位字母数字下划线(参考 nativePlayerUsernameRules
return 'e2e_p_' + Math.random().toString(36).slice(2, 10);
}
async function adminCtxOf(token: string) {
return pwRequest.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${token}` },
});
}
async function playerLoginCtx(siteCode: string, username: string, password: string, captchaKey: string) {
const ctx = await pwRequest.newContext();
const r = await ctx.post('/api/v1/player/auth/login', {
data: {
site_code: siteCode,
username,
password,
captcha_key: captchaKey,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
return { ctx, status: r.status(), body: (await r.json().catch(() => ({}))) as any };
}
test('超管创建玩家 + 玩家登录成功', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
const r = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-' + username,
username,
password: PWD,
default_currency: CURRENCY,
status: 0,
},
});
expect(r.ok(), `create player status=${r.status()}`).toBeTruthy();
const created = (await r.json()) as any;
expect(created.code).toBe(0);
const playerId = created.data.id;
expect(typeof playerId).toBe('number');
await admin.dispose();
// 玩家登录
const cap = await fetchPlayerCaptcha();
const { ctx, status, body } = await playerLoginCtx(SITE_CODE, username, PWD, cap.captcha_key);
expect(status, `player login status=${status}`).toBe(200);
expect(body.code).toBe(0);
expect(body.data.player.username).toBe(username);
await ctx.dispose();
});
test('超管 freeze → 玩家登录 403unfreeze → 又能登录', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
// 创建
const r = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-' + username,
username,
password: PWD,
default_currency: CURRENCY,
status: 0,
},
});
const created = (await r.json()) as any;
const playerId = created.data.id;
// freeze
const fz = await admin.post(`/api/v1/admin/players/${playerId}/freeze`);
expect(fz.ok(), `freeze status=${fz.status()}`).toBeTruthy();
const fzBody = (await fz.json()) as any;
expect(fzBody.data.status).toBe(1);
await admin.dispose();
// frozen 玩家登录应失败player_account_suspended
const cap1 = await fetchPlayerCaptcha();
const { ctx: ctx1, status: st1, body: b1 } = await playerLoginCtx(SITE_CODE, username, PWD, cap1.captcha_key);
expect([200, 403]).toContain(st1);
if (st1 === 200) {
expect(b1.code).not.toBe(0);
}
await ctx1.dispose();
// unfreeze
const admin2 = await adminCtxOf(session.accessToken);
const uf = await admin2.post(`/api/v1/admin/players/${playerId}/unfreeze`);
expect(uf.ok(), `unfreeze status=${uf.status()}`).toBeTruthy();
const ufBody = (await uf.json()) as any;
expect(ufBody.data.status).toBe(0);
await admin2.dispose();
// 玩家又能登录
const cap2 = await fetchPlayerCaptcha();
const { ctx: ctx2, status: st2, body: b2 } = await playerLoginCtx(SITE_CODE, username, PWD, cap2.captcha_key);
expect(st2, `player login after unfreeze status=${st2}`).toBe(200);
expect(b2.code).toBe(0);
await ctx2.dispose();
});
test('/admin/players 列表能找到新建玩家(按 username 搜索)', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
const r = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-' + username,
username,
password: PWD,
default_currency: CURRENCY,
status: 0,
},
});
const created = (await r.json()) as any;
const playerId = created.data.id;
// 列表搜索
const list = await admin.get(`/api/v1/admin/players?keyword=${encodeURIComponent(username)}&size=20`);
expect(list.ok(), `list status=${list.status()}`).toBeTruthy();
const body = (await list.json()) as any;
const items: any[] = body.data.items;
expect(items.length).toBeGreaterThanOrEqual(1);
const found = items.find((it) => it.id === playerId);
expect(found).toBeTruthy();
expect(found.username).toBe(username);
await admin.dispose();
});
test('/admin/players/{id}/wallets 信用盘玩家返回空钱包列表', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
const r = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-' + username,
username,
password: PWD,
default_currency: CURRENCY,
status: 0,
},
});
const created = (await r.json()) as any;
const playerId = created.data.id;
expect(created.data.funding_mode).toBe('credit');
// 信用盘玩家登录不会开立 player_wallets
const cap = await fetchPlayerCaptcha();
const { ctx, status, body } = await playerLoginCtx(SITE_CODE, username, PWD, cap.captcha_key);
expect(status).toBe(200);
expect(body.code).toBe(0);
await ctx.dispose();
// 查 wallet
const w = await admin.get(`/api/v1/admin/players/${playerId}/wallets`);
expect(w.ok(), `wallets status=${w.status()}`).toBeTruthy();
const wb = (await w.json()) as any;
const wallets: any[] = wb.data.wallets ?? [];
expect(wallets.length).toBe(0);
await admin.dispose();
});
test('创建玩家缺 site_player_id → 422', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
const r = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
username,
default_currency: CURRENCY,
},
});
expect([422, 400]).toContain(r.status());
await admin.dispose();
});
test('创建玩家重复 username → 409/422 业务失败', async () => {
const session = await adminLoginViaBypass();
const admin = await adminCtxOf(session.accessToken);
const username = uniqueUsername();
// 第一次
const r1 = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-dup-' + username,
username,
password: PWD,
default_currency: CURRENCY,
},
});
expect(r1.ok()).toBeTruthy();
// 第二次同 username不同 site_player_id
const r2 = await admin.post('/api/v1/admin/players', {
data: {
site_code: SITE_CODE,
site_player_id: 'e2e-dup2-' + username,
username,
password: PWD,
default_currency: CURRENCY,
},
});
// 业务失败HTTP 200 + code 非 0 是常见;或直接 4xx
const b2 = (await r2.json().catch(() => ({}))) as any;
if (r2.status() === 200) {
expect(b2.code).not.toBe(0);
} else {
expect([409, 422, 400, 500]).toContain(r2.status());
}
await admin.dispose();
});