/** * API 层集成测试:handleTrade 各端点(含 settings 依赖的 candidates 端点) * 独立数据目录,mock dataSource + 真实 storage/settings */ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { DataStore } from '../src/storage/DataStore.js'; import { handleTrade, TRADE_METHODS } from '../src/api/trades.js'; const dir = mkdtempSync(join(tmpdir(), 'odl-api-test-')); process.env.ODL_TEST_DATA_DIR = dir; let pass = 0, fail = 0; function assert(cond, msg) { if (cond) { pass++; console.log(' ✅ ' + msg); } else { fail++; console.log(' ❌ ' + msg); } } const storage = new DataStore({ dataDir: dir }); await storage._ensure(); // mock settings(含策略名) const settings = { get: () => ({ strategies: [ { id: 'grid-supermarket', name: '网格超市' }, { id: 'manual-t', name: '手动做T' }, ]}), }; // mock dataSource const dataSource = { async getOrders() { return []; }, async getTrades() { return []; }, async getTradingDates() { return { market: '', dates: [] }; }, }; const runtime = { dataSource, storage, settings }; // 端点注册齐全(R-018:candidates/set-attribution 退役 → targets/set/segments) assert(TRADE_METHODS.has('orders/attribution-targets'), '端点 orders/attribution-targets 已注册'); assert(TRADE_METHODS.has('orders/attribution-set'), '端点 orders/attribution-set 已注册'); assert(TRADE_METHODS.has('orders/attribution-segments'), '端点 orders/attribution-segments 已注册'); assert(TRADE_METHODS.has('trades/history'), '端点 trades/history 已注册'); assert(!TRADE_METHODS.has('orders/attribution-candidates'), '端点 orders/attribution-candidates 已退役'); assert(!TRADE_METHODS.has('orders/set-attribution'), '端点 orders/set-attribution 已退役'); // attribution-targets 端点(策略级 + 策略名 + activityShares) storage.openHolding('manual-t', '300057.SZ', 1000); const tgt = await handleTrade('orders/attribution-targets', { code: '300057.SZ' }, runtime); assert(tgt.length === 2, 'targets 返回全部策略'); const tRow = tgt.find((x) => x.strategyId === 'manual-t'); assert(tRow && tRow.strategyName === '手动做T' && tRow.activityShares === 1000, 'targets 含策略名 + activityShares(手动做T 1000)'); // attribution-set 端点(R-018 账本写操作) storage.upsertTradeOrders([{ orderId: 'X-1', tradeDate: '20260901', code: '300057.SZ', insertDate: '20260901', insertTime: '093000', tradedVolume: 1000 }]); const { AttributionService } = await import('../src/trades/AttributionService.js'); const svc = new AttributionService({ store: storage.sqlite }); const rt = { ...runtime, attribution: svc }; const setRes = await handleTrade('orders/attribution-set', { orderId: 'X-1', segments: [{ strategyId: 'manual-t', volume: 1000 }] }, rt); assert(setRes.ok === true && setRes.segments.length === 1, 'attribution-set 返回 ok + segments'); const row = storage.sqlite.db.prepare('SELECT strategy_id, holding_id FROM trade_orders WHERE order_id=?').get('X-1'); assert(row.strategy_id === 'manual-t' && row.holding_id != null, '归属已持久化(冗余列镜像)'); // attribution-segments 端点 const segs = await handleTrade('orders/attribution-segments', { orderId: 'X-1' }, rt); assert(segs.length === 1 && segs[0].strategyName === '手动做T', 'attribution-segments 读段含策略名'); // trades/history 端点(策略过滤经段表) const hist = await handleTrade('trades/history', { start: '20260901', end: '20260901', strategyId: 'manual-t' }, runtime); assert(hist.orders.length === 1 && hist.orders[0].orderId === 'X-1', 'history 端点按策略过滤命中'); assert(Array.isArray(hist.orders[0].segments) && hist.orders[0].segments.length === 1, 'history orders 附加 segments'); storage.close(); rmSync(dir, { recursive: true, force: true }); console.log('\n结果: ' + pass + ' 通过, ' + fail + ' 失败'); process.exit(fail > 0 ? 1 : 0);