/** * 开奖 → 结算 → 派彩 确定性流水线(poll + tick,避免 test.skip)。 */ import { expect, type APIRequestContext } from '@playwright/test'; import { buildAllResultItems, finishDrawCooldown, forceCloseDraw, resolveDrawId, tickDraws, waitForDrawStatus, } from '../_helper'; const CURRENCY = (process.env.LOTTERY_DEFAULT_CURRENCY ?? 'NPR').toUpperCase(); export interface DrawSettlementResult { drawNo: string; drawId: number; batchId: number; balanceBefore?: number; balanceAfter?: number; } export async function runWalletDrawSettlement(params: { adminToken: string; playerToken: string; drawNo: string; betNumber?: string; /** 公布头奖号码;默认与 betNumber 相同(必中)。传不同值可测玩家输单。 */ publishWinNumber?: string; betAmount?: number; balanceBefore?: number; expectWalletIncrease?: boolean; }): Promise { const drawNo = params.drawNo; const betNumber = params.betNumber ?? '1234'; const publishWinNumber = params.publishWinNumber ?? betNumber; const betAmount = params.betAmount ?? 10_000; const player = await pwPlayerCtx(params.playerToken); let balanceBefore: number | undefined; if (params.expectWalletIncrease !== false) { balanceBefore = params.balanceBefore ?? Number((await (await player.get('/api/v1/wallet/balance')).json()).data.balance); } const place = await player.post('/api/v1/ticket/place', { data: { draw_id: drawNo, currency_code: CURRENCY, client_trace_id: `e2e-settle-${Date.now()}`, lines: [{ number: betNumber, play_code: 'straight', amount: betAmount }], }, }); expect(place.ok(), `place status=${place.status()}`).toBeTruthy(); const placeBody = (await place.json()) as any; expect(placeBody.code).toBe(0); await player.dispose(); await forceCloseDraw(drawNo); await waitForDrawStatus(drawNo, ['closed', 'review', 'cooldown', 'settling', 'settled'], 15); const admin = await pwAdminCtx(params.adminToken); const drawId = await resolveDrawId(admin, drawNo); const store = await admin.post(`/api/v1/admin/draws/${drawId}/result-batches`, { data: { items: buildAllResultItems(publishWinNumber) }, }); expect(store.ok(), `store batch status=${store.status()}`).toBeTruthy(); const storeBody = (await store.json()) as any; expect(storeBody.code).toBe(0); const batchId: number = storeBody.data.batch.id; const pub = await admin.post(`/api/v1/admin/draws/${drawId}/result-batches/${batchId}/publish`); expect(pub.ok(), `publish status=${pub.status()}`).toBeTruthy(); const pubBody = (await pub.json()) as any; expect(pubBody.code).toBe(0); await finishDrawCooldown(drawNo); await waitForDrawStatus(drawNo, ['settling', 'settled'], 25); const insp = await waitForDrawStatus(drawNo, ['settled'], 25); if (String(insp.data?.status ?? insp.status) !== 'settled') { const settle = await admin.post(`/api/v1/admin/draws/${drawId}/settlement/run`); const settleBody = (await settle.json()) as any; if (settleBody.code !== 0) { await tickDraws(); } await waitForDrawStatus(drawNo, ['settled'], 25); } const list = await admin.get(`/api/v1/admin/settlement-batches?size=20`); const lb = (await list.json()) as any; const ourBatch = lb.data.items.find((b: any) => b.draw_id === drawId); if (ourBatch && ourBatch.status === 'pending_review') { await admin.post(`/api/v1/admin/settlement-batches/${ourBatch.id}/approve`, { data: { remark: 'e2e approve' }, }); await admin.post(`/api/v1/admin/settlement-batches/${ourBatch.id}/payout`); } else { await tickDraws(); await waitForDrawStatus(drawNo, ['settled'], 15); } await admin.dispose(); let balanceAfter: number | undefined; if (params.expectWalletIncrease !== false) { const player2 = await pwPlayerCtx(params.playerToken); const bal1 = (await (await player2.get('/api/v1/wallet/balance')).json()) as any; balanceAfter = Number(bal1.data.balance); await player2.dispose(); } return { drawNo, drawId, batchId, balanceBefore, balanceAfter }; } async function pwAdminCtx(token: string): Promise { const { request: pwRequest } = await import('@playwright/test'); return pwRequest.newContext({ extraHTTPHeaders: { Authorization: `Bearer ${token}` }, }); } async function pwPlayerCtx(token: string): Promise { const { request: pwRequest } = await import('@playwright/test'); return pwRequest.newContext({ extraHTTPHeaders: { Authorization: `Bearer ${token}` }, }); }