Skip to content

Commit

Permalink
[BE] 호스트는 다른 사용자를 강퇴할 수 있다. (#116)
Browse files Browse the repository at this point in the history
* feat: 강퇴 기능 추가

* test: e2e, unit 테스트

* docs: swagger 업데이트
  • Loading branch information
student079 authored Nov 19, 2024
1 parent 6ba8055 commit 002f38f
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 210 deletions.
155 changes: 60 additions & 95 deletions be/gameServer/src/modules/rooms/rooms.gateway.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('RoomsGateway', () => {
mockServer = {
to: jest.fn().mockReturnValue({
emit: jest.fn(),
fetchSockets: jest.fn(),
}),
} as unknown as Server;

Expand Down Expand Up @@ -395,7 +396,7 @@ describe('RoomsGateway', () => {

mockClient.data = { roomId, playerNickname };

await gateway.handleSetReady(playerNickname, mockClient);
await gateway.handleSetReady(mockClient);

expect(redisService.set).toHaveBeenCalledWith(
`room:${roomId}`,
Expand Down Expand Up @@ -433,7 +434,7 @@ describe('RoomsGateway', () => {

mockClient.data = { roomId, playerNickname };

await gateway.handleSetReady(playerNickname, mockClient);
await gateway.handleSetReady(mockClient);

expect(mockClient.emit).toHaveBeenCalledWith(
'error',
Expand All @@ -442,97 +443,61 @@ describe('RoomsGateway', () => {
});
});

// describe('handleKickPlayer', () => {
// it('호스트만 플레이어를 강퇴할 수 있어야 한다.', async () => {
// const roomId = 'testRoomId';
// const playerNickname = 'Player1';
// const roomData: RoomDataDto = {
// roomId,
// roomName: 'testRoom',
// hostNickname: 'host',
// players: [
// { playerNickname: 'host', isReady: false },
// { playerNickname: 'Player1', isReady: false },
// ],
// status: 'waiting',
// };

// jest
// .spyOn(redisService, 'get')
// .mockResolvedValueOnce(JSON.stringify(roomData));

// mockClient.data = { roomId, playerNickname };

// await gateway.handleKickPlayer('Player1', mockClient);

// expect(mockClient.emit).toHaveBeenCalledWith(
// 'error',
// 'Only host can kick players',
// );
// });

// it('플레이어가 방에 없으면 에러를 전송해야 한다.', async () => {
// const roomId = 'testRoomId';
// const playerNickname = 'host';
// const roomData: RoomDataDto = {
// roomId,
// roomName: 'testRoom',
// hostNickname: 'host',
// players: [
// { playerNickname: 'host', isReady: false },
// { playerNickname: 'Player1', isReady: false },
// ],
// status: 'waiting',
// };

// jest
// .spyOn(redisService, 'get')
// .mockResolvedValueOnce(JSON.stringify(roomData));

// mockClient.data = { roomId, playerNickname };

// await gateway.handleKickPlayer('Player2', mockClient);

// expect(mockClient.emit).toHaveBeenCalledWith(
// 'error',
// 'Player not found in room',
// );
// });

// // it('호스트가 플레이어를 강퇴할 수 있어야 한다.', async () => {
// // const roomId = 'testRoomId';
// // const nickname = 'host';
// // const playerNickname = 'Player1';
// // const roomData: RoomDataDto = {
// // roomId,
// // roomName: 'testRoom',
// // hostNickname: 'host',
// // players: [
// // { playerNickname: 'host', isReady: false },
// // { playerNickname: 'Player1', isReady: false },
// // ],
// // status: 'waiting',
// // };

// // jest
// // .spyOn(redisService, 'get')
// // .mockResolvedValueOnce(JSON.stringify(roomData));

// // mockClient.data = { roomId, playerNickname: nickname };

// // await gateway.handleKickPlayer(playerNickname, mockClient);

// // expect(redisService.set).toHaveBeenCalledWith(
// // `room:${roomId}`,
// // JSON.stringify({
// // ...roomData,
// // players: [{ playerNickname: 'host', isReady: false }],
// // }),
// // 'roomUpdate',
// // );
// // expect(mockServer.to(roomId).emit).toHaveBeenCalledWith('updateUsers', [
// // { playerNickname: 'host', isReady: false },
// // ]);
// // });
// });
describe('handleKickPlayer', () => {
it('호스트만 플레이어를 강퇴할 수 있어야 한다.', async () => {
const roomId = 'testRoomId';
const playerNickname = 'Player1';
const roomData: RoomDataDto = {
roomId,
roomName: 'testRoom',
hostNickname: 'host',
players: [
{ playerNickname: 'host', isReady: false },
{ playerNickname: 'Player1', isReady: false },
],
status: 'waiting',
};

jest
.spyOn(redisService, 'get')
.mockResolvedValueOnce(JSON.stringify(roomData));

mockClient.data = { roomId, playerNickname };

await gateway.handleKickPlayer('Player1', mockClient);

expect(mockClient.emit).toHaveBeenCalledWith(
'error',
'Only host can kick players',
);
});

it('플레이어가 방에 없으면 에러를 전송해야 한다.', async () => {
const roomId = 'testRoomId';
const playerNickname = 'host';
const roomData: RoomDataDto = {
roomId,
roomName: 'testRoom',
hostNickname: 'host',
players: [
{ playerNickname: 'host', isReady: false },
{ playerNickname: 'Player1', isReady: false },
],
status: 'waiting',
};

jest
.spyOn(redisService, 'get')
.mockResolvedValueOnce(JSON.stringify(roomData));

mockClient.data = { roomId, playerNickname };

await gateway.handleKickPlayer('Player2', mockClient);

expect(mockClient.emit).toHaveBeenCalledWith(
'error',
'Player not found in room',
);
});
});
});
123 changes: 56 additions & 67 deletions be/gameServer/src/modules/rooms/rooms.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,9 @@ export class RoomsGateway implements OnGatewayDisconnect {
}

@SubscribeMessage('setReady')
async handleSetReady(
@MessageBody() playerNickname: string,
@ConnectedSocket() client: Socket,
) {
async handleSetReady(@ConnectedSocket() client: Socket) {
try {
const { roomId } = client.data;
const { roomId, playerNickname } = client.data;
const roomDataString = await this.redisService.get<string>(
`room:${roomId}`,
);
Expand All @@ -201,7 +198,7 @@ export class RoomsGateway implements OnGatewayDisconnect {
);

if (player) {
player.isReady = true;
player.isReady = !player.isReady;
await this.redisService.set(`room:${roomId}`, JSON.stringify(roomData));
this.server.to(roomId).emit('updateUsers', roomData.players);
} else {
Expand All @@ -213,65 +210,57 @@ export class RoomsGateway implements OnGatewayDisconnect {
}
}

// @SubscribeMessage('kickPlayer')
// async handleKickPlayer(
// @MessageBody() playerNickname: string,
// @ConnectedSocket() client: Socket,
// ) {
// try {
// const { roomId } = client.data;
// const roomDataString = await this.redisService.get<string>(
// `room:${roomId}`,
// );
// const roomData: RoomDataDto = JSON.parse(roomDataString);

// if (roomData.hostNickname !== client.data.playerNickname) {
// client.emit('error', 'Only host can kick players');
// return;
// }

// const playerIndex = roomData.players.findIndex(
// (p) => p.playerNickname === playerNickname,
// );

// if (playerIndex === -1) {
// client.emit('error', 'Player not found in room');
// return;
// }

// const roomSockets = this.server.sockets.adapter.rooms.get(roomId);
// console.log('Server Sockets Adapter:', this.server.sockets.adapter);
// if (!roomSockets) {
// client.emit('error', 'Room not found');
// return;
// }

// let targetSocketId: string | undefined;
// // `roomSockets`는 `Set` 형태이므로 직접 순회하여 찾음
// for (const socketId of roomSockets) {
// const socket = this.server.sockets.sockets.get(socketId);
// if (socket?.data.playerNickname === playerNickname) {
// targetSocketId = socketId;
// break;
// }
// }

// roomData.players.splice(playerIndex, 1);
// await this.redisService.set(
// `room:${roomId}`,
// JSON.stringify(roomData),
// 'roomUpdate',
// );

// const targetClient = this.server.sockets.sockets.get(targetSocketId);
// targetClient?.disconnect();

// this.server.to(roomId).emit('updateUsers', roomData.players);

// this.logger.log(`Player ${playerNickname} kicked from room ${roomId}`);
// } catch (error) {
// this.logger.error(`Error kicking player: ${error.message}`);
// client.emit('error', 'Failed to kick player');
// }
// }
@SubscribeMessage('kickPlayer')
async handleKickPlayer(
@MessageBody() playerNickname: string,
@ConnectedSocket() client: Socket,
) {
try {
const { roomId } = client.data;
const roomDataString = await this.redisService.get<string>(
`room:${roomId}`,
);
const roomData: RoomDataDto = JSON.parse(roomDataString);

if (roomData.hostNickname !== client.data.playerNickname) {
client.emit('error', 'Only host can kick players');
return;
}

const playerIndex = roomData.players.findIndex(
(p) => p.playerNickname === playerNickname,
);

if (playerIndex === -1) {
client.emit('error', 'Player not found in room');
return;
}

const socketsInRoom = await this.server.in(roomId).fetchSockets();
const targetSocket = socketsInRoom.find(
(socket) => socket.data.playerNickname === playerNickname,
);

if (targetSocket) {
targetSocket.disconnect(true);
this.logger.log(
`Player ${playerNickname} disconnected from room ${roomId}`,
);
}

roomData.players.splice(playerIndex, 1);
await this.redisService.set(
`room:${roomId}`,
JSON.stringify(roomData),
'roomUpdate',
);

this.server.to(roomId).emit('kicked', playerNickname);

this.logger.log(`Player ${playerNickname} kicked from room ${roomId}`);
} catch (error) {
this.logger.error(`Error kicking player: ${error.message}`);
client.emit('error', 'Failed to kick player');
}
}
}
35 changes: 22 additions & 13 deletions be/gameServer/src/modules/rooms/rooms.websocket.emit.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@ export class RoomsWebSocketEmitController {
description: 'Room created successfully',
type: RoomDataDto,
})
createRoom(): RoomDataDto {
return {
roomId: 'example-room-id',
roomName: 'example-room-name',
hostNickname: 'example-room-name',
players: [{ playerNickname: 'hostNickname', isReady: false }],
status: 'waiting',
};
createRoom() {
return;
}

@Post('updateUsers')
Expand All @@ -32,12 +26,27 @@ export class RoomsWebSocketEmitController {
description:
'방의 사용자들에게 "updateUsers" 이벤트를 통해 갱신된 사용자 목록을 제공합니다.',
})
updateUsers(): PlayerDataDto[] {
@ApiResponse({
description: '플레이어 데이터 객체 배열',
type: [PlayerDataDto],
})
updateUsers() {
// This method does not execute any logic. It's for Swagger documentation only.
return;
}

@Post('kicked')
@ApiOperation({
summary: '강퇴 이벤트 발생',
description: '방의 사용자들에게 다른 사용자의 강퇴 이벤트를 알립니다.',
})
@ApiResponse({
description: '해당 강퇴 사용자 닉네임',
type: String,
})
kicked() {
// This method does not execute any logic. It's for Swagger documentation only.
return [
{ playerNickname: 'hostNickname', isReady: true },
{ playerNickname: 'Player1', isReady: false },
];
return;
}

@Post('error')
Expand Down
Loading

0 comments on commit 002f38f

Please sign in to comment.