/** * E2E:主站超时 pending_reconcile → 后台 reconcile-jobs 扫描检出。 */ import { test, expect } from '@playwright/test'; import { adminLoginViaBypass, adminCtxOf, configureWalletMock, playerCtxOf, playerLoginViaBypass, resetE2EPlayer, resetWalletMock, setMockWalletMode, } from './_helper'; const CURRENCY = (process.env.LOTTERY_DEFAULT_CURRENCY ?? 'NPR').toUpperCase(); const MOCK_BASE = `http://127.0.0.1:${process.env.E2E_MOCK_WALLET_PORT ?? '5555'}`; function isoDateOffset(days: number): string { const d = new Date(); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); } test.beforeEach(async () => { await resetWalletMock(); await setMockWalletMode('success'); }); test('transfer-out 504 pending_reconcile → reconcile-jobs 扫描到差异项', async () => { test.setTimeout(120_000); await resetE2EPlayer(); await configureWalletMock(MOCK_BASE); await setMockWalletMode('504'); const session = await playerLoginViaBypass(); const playerId = session.player.id; const idemKey = `e2e-reconcile-${Date.now()}`; const player = await playerCtxOf(session.access_token); const tout = await player.post('/api/v1/wallet/transfer-out', { data: { amount: 10_000, currency: CURRENCY, idempotent_key: idemKey, }, }); const toutBody = (await tout.json()) as any; expect(Number(toutBody.code)).toBe(1002); await player.dispose(); const adminSession = await adminLoginViaBypass(); const admin = await adminCtxOf(adminSession.accessToken); const scan = await admin.post('/api/v1/admin/reconcile-jobs', { data: { reconcile_type: 'wallet_transfer', date_from: isoDateOffset(-1), date_to: isoDateOffset(0), player_id: playerId, }, }); expect(scan.ok(), `reconcile-jobs create status=${scan.status()}`).toBeTruthy(); const scanBody = (await scan.json()) as any; expect(scanBody.code).toBe(0); expect(Number(scanBody.data.item_count)).toBeGreaterThanOrEqual(1); const jobId = scanBody.data.id; const items = await admin.get(`/api/v1/admin/reconcile-jobs/${jobId}/items?size=20`); expect(items.ok()).toBeTruthy(); const itemsBody = (await items.json()) as any; expect(itemsBody.code).toBe(0); expect(itemsBody.data.items.length).toBeGreaterThanOrEqual(1); const hit = itemsBody.data.items.find( (it: any) => String(it.side_a_ref ?? '').startsWith('TO_') || String(it.side_b_ref ?? '').startsWith('TO_'), ); expect(hit, '扫描结果应包含 pending_reconcile 转账单引用').toBeTruthy(); await admin.dispose(); });