Files
lotteryLaravel/e2e/tests/api/03.wallet-ticket.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

121 lines
4.5 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. 玩家登录 → 拿钱包余额
* 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();
});