// 夹具自检:真包必须覆盖全部阶段,且保留座位差异 const fs = require('fs'); const path = require('path'); const { ROOT } = require('./_load'); const t = require('./_assert')(); const fx = JSON.parse(fs.readFileSync(path.join(ROOT, 'client/tests/fixtures/packets.json'), 'utf8')); t.eq('三个座位', fx.seats.length, 3); const rpcsOf = seat => fx.seats[seat].map(p => p.rpc); const allRpcs = [].concat(rpcsOf(0), rpcsOf(1), rpcsOf(2)); ['fapai','jiaofen','shangzhuang','xuanzhu','maipai','chupai1','chupai2','chupai3','jiesuan'] .forEach(rpc => t.eq('夹具覆盖 ' + rpc, allRpcs.indexOf(rpc) >= 0, true)); // 差异化下发确实存在:bottomcards 恰好只发给庄家一人(不是"少于三家"这种可被空集合蒙混过去的弱断言) const withBottom = [0,1,2].filter(s => fx.seats[s].some(p => p.rpc === 'shangzhuang' && p.data.hasOwnProperty('bottomcards'))); t.eq('底牌恰好发给1个座位(庄家)', withBottom.length, 1); // deskinfo 快照必须覆盖全部 5 个阶段(下游任务要用 step3 埋牌快照做回归断言) const steps = fx.deskinfo.map(d => d.step).filter((v, i, a) => a.indexOf(v) === i).sort((a,b) => a - b); t.eq('deskinfo 覆盖全部阶段 1/2/3/5/6', steps, [1, 2, 3, 5, 6]); // roomtype 必须存在且非空,Task 12 要用它还原房间选项 t.eq('roomtype 存在且非空', typeof fx.roomtype === 'string' && fx.roomtype.length > 0, true); // packetIndex 必须落在 (0, 该座位包总数] 区间内,且与 info.step 自洽 fx.deskinfo.forEach(d => { const inRange = d.packetIndex > 0 && d.packetIndex <= fx.seats[d.seat].length; t.eq('packetIndex 落在有效区间 (step' + d.step + ' seat' + d.seat + ')', inRange, true); t.eq('deskinfo.step 与 info.step 一致 (seat' + d.seat + ' pi' + d.packetIndex + ')', d.step, d.info.step); }); // C-1 回归锁:同一座位的两条 step5 快照,playproc(当前轮桌面牌/currseat/round)不得相同—— // 否则说明 deskinfo 快照存的是活引用,被后续出牌篡改成了终局状态(曾经的真实缺陷) const step5BySeat = {}; fx.deskinfo.filter(d => d.step === 5).forEach(d => { (step5BySeat[d.seat] = step5BySeat[d.seat] || []).push(d); }); Object.keys(step5BySeat).forEach(seat => { const list = step5BySeat[seat]; if (list.length < 2) { return; } for (let i = 1; i < list.length; i++) { const same = JSON.stringify(list[0].info.PushCards.playproc) === JSON.stringify(list[i].info.PushCards.playproc); t.eq('seat' + seat + ' 两条 step5 快照 playproc 不相同(未被活引用污染)', same, false); } }); // 每个包都有 success(协议 §0.1:每个下发包的 data 必带 success) const noSuccess = []; [0,1,2].forEach(s => fx.seats[s].forEach(p => { if (!p.data || !p.data.hasOwnProperty('success')) { noSuccess.push(s + ':' + p.rpc); } })); t.eq('每个下发包都带 success', noSuccess, []); process.exit(t.done('fixture') ? 0 : 1);