Files
lotteryLaravel/e2e/tests/api/03.wallet-ticket.spec.ts
kang 3f29229499
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled
fix: 部分收付累计释额并扩展结算 E2E 与 CI
账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 09:48:01 +08:00

119 lines
4.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钱包 + 下注 + 注单查看
*
* 链路:
* 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, fetchOpenDrawNo } 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 drawNo = await fetchOpenDrawNo();
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: drawNo,
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: drawNo,
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 drawNo = await fetchOpenDrawNo();
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: drawNo,
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();
});