import assert from 'node:assert/strict' import { io } from 'socket.io-client' const SOCKET_SERVER_PORT = process.env.SOCKET_SERVER_PORT || '31873' const SOCKET_SERVER_URL = `http://127.0.0.1:${SOCKET_SERVER_PORT}` /** * 테스트에 사용할 유효한 50장 덱 URL을 만든다. * * @returns {string} 덱 등록용 결과 페이지 URL */ function createValidDeckUrl() { const cards = Array.from({ length: 13 }, (_, cardIndex) => ({ imgSrc: `/ua01/TEST-${String(cardIndex + 1).padStart(3, '0')}.png`, Number: `TEST-${String(cardIndex + 1).padStart(3, '0')}`, count: cardIndex === 12 ? 2 : 4, color: 'Blue', trigger: null, })) const deckUrl = new URL('http://uniare.sori.studio/result.html') deckUrl.searchParams.set('series', 'ua01') deckUrl.searchParams.set('seriesName', 'Code Geass') deckUrl.searchParams.set('cards', JSON.stringify(cards)) return deckUrl.toString() } /** * 연결 완료 또는 오류를 Promise로 기다린다. * * @param {import('socket.io-client').Socket} socket 확인할 클라이언트 소켓 * @returns {Promise} 연결 완료 Promise */ function waitForConnection(socket) { return new Promise((resolve, reject) => { socket.once('connect', resolve) socket.once('connect_error', reject) }) } /** * Socket.IO 승인 응답이 있는 이벤트를 전송한다. * * @param {import('socket.io-client').Socket} socket 이벤트를 전송할 소켓 * @param {string} eventName 이벤트 이름 * @param {unknown[]} eventArguments 이벤트 인자 * @returns {Promise} 서버 승인 응답 */ function emitWithAcknowledgement(socket, eventName, ...eventArguments) { return new Promise((resolve, reject) => { socket.timeout(3000).emit( eventName, ...eventArguments, (timeoutError, response) => { if (timeoutError) { reject(timeoutError) return } resolve(response) }, ) }) } /** * 게임 시작 이벤트를 한 번 기다린다. * * @param {import('socket.io-client').Socket} socket 이벤트를 기다릴 소켓 * @returns {Promise} 게임 시작 정보 */ function waitForGameStart(socket) { return new Promise((resolve) => { socket.once('game:started', resolve) }) } /** * 두 클라이언트의 방 생성부터 게임 시작까지 검증한다. * * @returns {Promise} 검증 완료 Promise */ async function runIntegrationTest() { const firstPlayerSocket = io(SOCKET_SERVER_URL, { path: '/socket.io', transports: ['websocket', 'polling'], }) const secondPlayerSocket = io(SOCKET_SERVER_URL, { path: '/socket.io', transports: ['websocket', 'polling'], }) try { await Promise.all([ waitForConnection(firstPlayerSocket), waitForConnection(secondPlayerSocket), ]) const createRoomResponse = await emitWithAcknowledgement( firstPlayerSocket, 'room:create', ) assert.equal(createRoomResponse.ok, true) assert.equal(createRoomResponse.room.players.length, 1) const joinRoomResponse = await emitWithAcknowledgement( secondPlayerSocket, 'room:join', createRoomResponse.room.roomCode, ) assert.equal(joinRoomResponse.ok, true) assert.equal(joinRoomResponse.room.players.length, 2) const validDeckUrl = createValidDeckUrl() const firstDeckResponse = await emitWithAcknowledgement( firstPlayerSocket, 'player:register-deck', validDeckUrl, ) const secondDeckResponse = await emitWithAcknowledgement( secondPlayerSocket, 'player:register-deck', validDeckUrl, ) assert.equal(firstDeckResponse.ok, true) assert.equal(secondDeckResponse.ok, true) const firstGameStart = waitForGameStart(firstPlayerSocket) const secondGameStart = waitForGameStart(secondPlayerSocket) await emitWithAcknowledgement( firstPlayerSocket, 'player:set-ready', true, ) await emitWithAcknowledgement( secondPlayerSocket, 'player:set-ready', true, ) const [firstStartPayload, secondStartPayload] = await Promise.all([ firstGameStart, secondGameStart, ]) assert.equal(firstStartPayload.seat, 'player-one') assert.equal(secondStartPayload.seat, 'player-two') assert.equal(firstStartPayload.roomCode, secondStartPayload.roomCode) assert.match(firstStartPayload.gameQuery, /series=ua01/) console.log('Socket.IO 2인 방 통합 테스트를 통과했습니다.') } finally { firstPlayerSocket.disconnect() secondPlayerSocket.disconnect() } } await runIntegrationTest()