feat: 添加E2E测试环境配置支持
Some checks failed
lotterLaravel CI / test (push) Has been cancelled

添加LOTTERY_E2E环境变量来控制E2E测试相关功能,
包括绕过验证码、登录限制和钱包API URL验证,
同时更新composer.json以包含E2E专用的提供者和服务。
This commit is contained in:
2026-06-18 14:42:22 +08:00
parent 6b2ea39ea1
commit 6ec9634704
45 changed files with 3702 additions and 6 deletions

View File

@@ -0,0 +1,120 @@
/**
* E2E钱包 + 下注 + 注单查看
*
* 链路:
* 1. 玩家登录 → 拿钱包余额
* 2. 取当前 draw_no公开 draw.current
* 3. POST /ticket/preview不落库只算钱
* 4. POST /ticket/place落库 + 扣 frozen
* 5. GET /ticket/items/{ticket_no} 验真
* 6. 钱包余额减少frozen 增加)
* 7. 同 client_trace_id 第二次 place → 幂等回放
*
* 不做结算(结算链路走 settlement service + commande2e 单独测)。
*/
import { test, expect, request as pwRequest } from '@playwright/test';
import { playerLoginViaBypass, resetE2EPlayer, fetchCurrentDrawNo } from './_helper';
const CURRENCY = (process.env.LOTTERY_DEFAULT_CURRENCY ?? 'NPR').toUpperCase();
const BET_AMOUNT_MINOR = 1000;
test.beforeEach(async () => {
await resetE2EPlayer();
});
test('玩家登录 → /wallet/balance 返回币种 + 余额', async () => {
const session = await playerLoginViaBypass();
const ctx = await pwRequest.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${session.access_token}` },
});
const r = await ctx.get('/api/v1/wallet/balance');
expect(r.ok(), `balance status=${r.status()}`).toBeTruthy();
const body = (await r.json()) as any;
expect(body.data.currency_code).toBe(CURRENCY);
expect(typeof body.data.balance).toBe('number');
expect(typeof body.data.available_balance).toBe('number');
expect(body.data.credit_line_mode).toBe(false);
await ctx.dispose();
});
test('preview → place → 拿 ticket_no + 余额变动', async () => {
const draw = await fetchCurrentDrawNo();
test.skip(draw === null, '当前无开放期号draw.current 返回 null跳过下注链路');
const session = await playerLoginViaBypass();
const ctx = await pwRequest.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${session.access_token}` },
});
const bal0 = (await (await ctx.get('/api/v1/wallet/balance')).json()) as any;
const before = Number(bal0.data.balance);
const preview = await ctx.post('/api/v1/ticket/preview', {
data: {
draw_id: draw!.draw_no,
currency_code: CURRENCY,
client_trace_id: `e2e-preview-${Date.now()}`,
lines: [{ number: '1234', play_code: 'straight', amount: BET_AMOUNT_MINOR }],
},
});
expect(preview.ok(), `preview status=${preview.status()}`).toBeTruthy();
const previewBody = (await preview.json()) as any;
expect(previewBody.code).toBe(0);
const place = await ctx.post('/api/v1/ticket/place', {
data: {
draw_id: draw!.draw_no,
currency_code: CURRENCY,
client_trace_id: `e2e-place-${Date.now()}`,
lines: [{ number: '1234', play_code: 'straight', amount: BET_AMOUNT_MINOR }],
},
});
expect(place.ok(), `place status=${place.status()}`).toBeTruthy();
const placeBody = (await place.json()) as any;
expect(placeBody.code).toBe(0);
const ticketNo = placeBody.data.items?.[0]?.ticket_no ?? placeBody.data.ticket_no;
expect(ticketNo).toMatch(/^TK[0-9]+$/);
const show = await ctx.get(`/api/v1/ticket/items/${ticketNo}`);
expect(show.ok(), `items show status=${show.status()}`).toBeTruthy();
const showBody = (await show.json()) as any;
expect(showBody.data.ticket_no).toBe(ticketNo);
const bal1 = (await (await ctx.get('/api/v1/wallet/balance')).json()) as any;
const afterAvailable = Number(bal1.data.available_balance);
expect(afterAvailable).toBeLessThan(before);
await ctx.dispose();
});
test('同 client_trace_id 第二次 place → 幂等回放,不重复扣款', async () => {
const draw = await fetchCurrentDrawNo();
test.skip(draw === null, '当前无开放期号,跳过幂等用例');
const session = await playerLoginViaBypass();
const ctx = await pwRequest.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${session.access_token}` },
});
const trace = `e2e-idem-${Date.now()}`;
const payload = {
draw_id: draw!.draw_no,
currency_code: CURRENCY,
client_trace_id: trace,
lines: [{ number: '5678', play_code: 'straight', amount: 500 }],
};
const r1 = await ctx.post('/api/v1/ticket/place', { data: payload });
expect(r1.ok(), `place1 status=${r1.status()}`).toBeTruthy();
const body1 = (await r1.json()) as any;
const ticket1 = body1.data.items?.[0]?.ticket_no ?? body1.data.ticket_no;
const r2 = await ctx.post('/api/v1/ticket/place', { data: payload });
expect(r2.ok(), `place2 status=${r2.status()}`).toBeTruthy();
const body2 = (await r2.json()) as any;
const ticket2 = body2.data.items?.[0]?.ticket_no ?? body2.data.ticket_no;
expect(ticket1).toBe(ticket2);
await ctx.dispose();
});