Files
liars-bar-game/scripts/test_socket_e2e.js
T

109 lines
4.6 KiB
JavaScript
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.
// 多人端對端整合測試:連真實伺服器,驗證「多張出牌 → 下家挑戰 → 手牌更新」流程。
// 使用:先啟動 servernode server/index.js),再跑 node scripts/test_socket_e2e.js
const { io } = require('../client/node_modules/socket.io-client');
const URL = 'http://localhost:3000';
const clients = {};
function connect(name) {
return new Promise((resolve, reject) => {
const s = io(URL, { transports: ['websocket'], reconnection: false, timeout: 8000 });
s._hand = null;
s._name = name;
s._lastError = null;
s.on('connect', () => { clients[s.id] = s; resolve(s); });
s.on('connect_error', (e) => reject(new Error(name + ' connect_error: ' + e.message)));
s.on('yourHand', (h) => { s._hand = h || []; });
s.on('error', (d) => { s._lastError = d && d.message; });
});
}
function once(socket, ev, timeout = 8000) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error(`${socket._name} wait ${ev} timeout`)), timeout);
socket.once(ev, (d) => { clearTimeout(t); resolve(d); });
});
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let failures = 0;
function ok(cond, msg) {
if (cond) console.log(' PASS ' + msg);
else { failures++; console.log(' FAIL ' + msg); }
}
(async () => {
console.log('[1] 三玩家連線+房間');
const a = await connect('A');
const b = await connect('B');
const c = await connect('C');
const roomCreated = once(a, 'roomCreated');
a.emit('createRoom');
const room = await roomCreated;
const roomId = room.id;
ok(roomId, '建立房間 roomId=' + roomId);
b.emit('joinRoom', { roomId });
await once(a, 'playerJoined');
c.emit('joinRoom', { roomId });
await once(a, 'playerJoined');
ok(true, 'B、C 加入');
console.log('[2] 開始遊戲 + 私密手牌');
const started = [once(a, 'gameStarted'), once(b, 'gameStarted'), once(c, 'gameStarted')];
a.emit('startGame', { roomId });
const [gsA] = await Promise.all(started);
ok(gsA.currentPlayerId && ['Q','K','A'].includes(gsA.targetCard), 'gameStarted 狀態正確');
ok(gsA.players.length === 3, '3 位玩家');
await sleep(300);
ok(a._hand.length === 5 && b._hand.length === 5 && c._hand.length === 5,
'每位玩家收到 5 張私密手牌(yourHand');
console.log('[3] 目前玩家多張出牌');
const cur = clients[gsA.currentPlayerId];
ok(!!cur, '定位目前玩家');
const playedEvt = once(a, 'cardPlayed');
cur.emit('playCard', { roomId, cardIndices: [0, 1] });
const played = await playedEvt;
// 資訊邊界:cardPlayed 只公開張數,不洩漏實際牌值
ok(played.playedGroup && played.playedGroup.cardCount === 2, '出牌 2 張並廣播(只含張數)');
ok(!('cards' in (played.playedGroup || {})), 'cardPlayed 不洩漏實際牌值');
const currentNow = played.gameState.currentPlayerId;
ok(currentNow !== cur.id, '換下一位');
await sleep(300);
ok(cur._hand.length === 3, `出牌者剩 3 張 (yourHand 同步,實際=${cur._hand.length})`);
console.log('[4] 非我的回合出牌 → 被拒絕');
const notTurn = [a, b, c].find((s) => s.id !== cur.id && s.id !== currentNow);
ok(!!notTurn, '找到非回合玩家');
notTurn._lastError = null;
notTurn.emit('playCard', { roomId, cardIndices: [0] });
await sleep(300);
ok(!!notTurn._lastError, '非回合者被拒絕(' + (notTurn._lastError || 'none') + '');
console.log('[5] 目前玩家(下家)質疑 → 收到結果與新狀態');
const currentSocket = clients[currentNow];
const challengedEvt = once(a, 'challenged');
currentSocket.emit('challenge', { roomId });
const ch = await challengedEvt;
ok(ch.result && typeof ch.result.isBluff === 'boolean', '質疑結果帶 isBluff=' + ch.result.isBluff);
ok(ch.result.shooter, '有開槍者(shooter=' + ch.result.shooter + '');
ok(ch.gameState.roundNumber >= 2, '質疑後進入下一局(round=' + ch.gameState.roundNumber + '');
await sleep(300);
ok(ch.gameState.players.every((p) => {
const sh = clients[p.id];
return !sh || (sh._hand.length === p.handSize);
}), 'yourHand 與公開狀態一致(' + ch.gameState.players.map((p) => p.handSize).join(',') + '');
const aliveCount = ch.gameState.players.filter((p) => p.alive).length;
console.log(`[6] 結果快照:第 ${ch.gameState.roundNumber} 局,存活 ${aliveCount}/3isBluff=${ch.result.isBluff}`);
[a, b, c].forEach((s) => s.close());
await sleep(300);
console.log(failures === 0 ? '\n整合測試全部通過' : `\n有 ${failures} 項失敗`);
process.exit(failures ? 1 : 0);
})().catch((e) => {
console.error('[FATAL]', e.message);
process.exit(1);
});