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