-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from boostcampwm-2024/dev-be
Dev be
- Loading branch information
Showing
8 changed files
with
383 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
services: | ||
redis: | ||
image: redis:latest | ||
container_name: redis_test | ||
ports: | ||
- "6379:6379" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { INestApplication } from '@nestjs/common'; | ||
import { io, Socket } from 'socket.io-client'; | ||
import { RedisService } from '../redis/redis.service'; | ||
import { DrawingGateway } from './drawing.gateway'; | ||
import { DrawingService } from './drawing.service'; | ||
import { DrawingRepository } from './drawing.repository'; | ||
|
||
describe('DrawingGateway e2e 테스트', () => { | ||
let app: INestApplication; | ||
let redisService: RedisService; | ||
let clientA: Socket; | ||
|
||
const URL = 'http://localhost:3001/socket.io/drawing'; | ||
|
||
const mockConfigService = { | ||
provide: ConfigService, | ||
useValue: { | ||
get: jest.fn().mockImplementation((key: string) => { | ||
if (key === 'REDIS_HOST') return 'localhost'; | ||
if (key === 'REDIS_PORT') return '6379'; | ||
return null; | ||
}), | ||
}, | ||
}; | ||
|
||
beforeAll(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [DrawingGateway, DrawingService, DrawingRepository, RedisService, mockConfigService], | ||
}).compile(); | ||
|
||
app = module.createNestApplication(); | ||
redisService = module.get<RedisService>(RedisService); | ||
|
||
// 테스트용 서버 실행 | ||
await app.listen(3001); | ||
}); | ||
|
||
beforeEach(async () => { | ||
await redisService.hset('room:room1', { roomId: 'room1' }); | ||
await redisService.hset('room:room1:player:player1', { playerId: 'player1' }); | ||
|
||
clientA = io(URL, { | ||
auth: { | ||
roomId: 'room1', | ||
playerId: 'player1', | ||
}, | ||
}); | ||
|
||
await new Promise<void>((resolve) => { | ||
clientA.on('connect', resolve); | ||
}); | ||
}); | ||
|
||
afterEach(async () => { | ||
clientA.close(); | ||
await redisService.flushAll(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await app.close(); | ||
redisService.quit(); | ||
}); | ||
|
||
describe('handleConnection', () => { | ||
it('roomId 또는 playerId의 값이 존재하지 않는 경우 "Room ID and Player ID are required" 에러가 발생한다.', async () => { | ||
const authInfo = [ | ||
{ roomId: '', playerId: '' }, | ||
{ roomId: 'room1', playerId: '' }, | ||
{ roomId: '', playerId: 'player1' }, | ||
]; | ||
|
||
for (const auth of authInfo) { | ||
const socket = io(URL, { | ||
auth, | ||
}); | ||
|
||
await new Promise<void>((resolve) => { | ||
socket.on('error', (e) => { | ||
expect(e.code).toBe(4000); | ||
expect(e.message).toBe('Room ID and Player ID are required'); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
socket.close(); | ||
} | ||
}); | ||
|
||
it('room이 redis 내에 존재하지 않는 경우 "Room not found" 에러가 발생한다.', async () => { | ||
const invalidRoomClient = io(URL, { | ||
auth: { | ||
roomId: 'failed-room', | ||
playerId: 'player1', | ||
}, | ||
}); | ||
|
||
await new Promise<void>((resolve) => { | ||
invalidRoomClient.on('error', (e) => { | ||
expect(e.code).toBe(6005); | ||
expect(e.message).toBe('Room not found'); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
invalidRoomClient.close(); | ||
}); | ||
|
||
it('player가 redis 내에 존재하지 않는 경우 "Player not found in room" 에러가 발생한다.', async () => { | ||
const invalidPlayerClient = io(URL, { | ||
auth: { | ||
roomId: 'room1', | ||
playerId: 'failed-player', | ||
}, | ||
}); | ||
|
||
await new Promise<void>((resolve) => { | ||
invalidPlayerClient.on('error', (e) => { | ||
expect(e.code).toBe(6006); | ||
expect(e.message).toBe('Player not found in room'); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
invalidPlayerClient.close(); | ||
}); | ||
|
||
it('room과 player가 정상적으로 존재하는 경우 정상적으로 연결된다.', async () => { | ||
expect(clientA.connected).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('handleDraw', () => { | ||
it('roomId 값이 존재하지 않는 경우 "Room ID is required" 에러가 발생한다.', async () => { | ||
const invalidRoomClient = io(URL, { | ||
auth: { | ||
roomId: 'failed-room', | ||
playerId: 'player1', | ||
}, | ||
}); | ||
|
||
invalidRoomClient.on('connect_error', (e) => { | ||
expect(e.message).toBe('Room ID is required'); | ||
invalidRoomClient.close(); | ||
}); | ||
}); | ||
|
||
it('정상적으로 그림이 그려지는 경우', async () => { | ||
const drawingData = { | ||
pos: 56, | ||
fillColor: { R: 0, G: 0, B: 0, A: 0 }, | ||
}; | ||
|
||
/** | ||
* client.to(roomId).emit('drawUpdated') 이므로 | ||
* 그림 그린 사람을 제외하고 다른 사람이 존재해야 이벤트를 제대로 수신받는지 확인이 가능함 | ||
* 이를 위해 clientB를 생성 | ||
*/ | ||
await redisService.hset('room:room1:player:player2', { playerId: 'player2' }); | ||
|
||
const clientB = io(URL, { | ||
auth: { | ||
roomId: 'room1', | ||
playerId: 'player2', | ||
}, | ||
}); | ||
|
||
// clientB가 연결이 완료될 때까지 기다림 | ||
await new Promise<void>((resolve) => { | ||
clientB.on('connect', resolve); | ||
}); | ||
|
||
// drawUpdated 이벤트 수신을 위한 Promise 생성 | ||
const drawUpdatePromise = new Promise<void>((resolve) => { | ||
clientB.on('drawUpdated', (data) => { | ||
expect(data).toEqual({ | ||
playerId: 'player1', | ||
drawingData: drawingData, | ||
}); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
// clientA가 실제로 이벤트를 발생시킴 | ||
clientA.emit('draw', { drawingData }); | ||
|
||
await drawUpdatePromise; | ||
clientB.close(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { Socket } from 'socket.io'; | ||
import { RedisService } from '../redis/redis.service'; | ||
import { DrawingGateway } from './drawing.gateway'; | ||
import { DrawingService } from './drawing.service'; | ||
import { DrawingRepository } from './drawing.repository'; | ||
import { BadRequestException, PlayerNotFoundException, RoomNotFoundException } from '../exceptions/game.exception'; | ||
|
||
describe('DrawingGateway 통합 테스트', () => { | ||
let gateway: DrawingGateway; | ||
let service: DrawingService; | ||
let redisService: RedisService; | ||
let client: Socket; | ||
|
||
beforeAll(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ | ||
DrawingGateway, | ||
DrawingService, | ||
DrawingRepository, | ||
RedisService, | ||
{ | ||
provide: ConfigService, | ||
useValue: { | ||
get: jest.fn().mockImplementation((key: string) => { | ||
if (key === 'REDIS_HOST') return 'localhost'; | ||
if (key === 'REDIS_PORT') return '6379'; | ||
return null; | ||
}), | ||
}, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
gateway = module.get<DrawingGateway>(DrawingGateway); | ||
service = module.get<DrawingService>(DrawingService); | ||
redisService = module.get<RedisService>(RedisService); | ||
}); | ||
|
||
beforeEach(async () => { | ||
client = { | ||
handshake: { | ||
auth: { roomId: '', playerId: '' }, | ||
}, | ||
data: {}, | ||
join: jest.fn(), | ||
to: jest.fn().mockReturnValue({ | ||
emit: jest.fn(), | ||
}), | ||
} as any; | ||
|
||
await redisService.hset('room:room1', { roomId: 'room1' }); | ||
await redisService.hset('room:room1:player:player1', { playerId: 'player1' }); | ||
}); | ||
|
||
// 테스트가 수행될 때마다 DB를 비워줌 | ||
afterEach(async () => { | ||
await redisService.flushAll(); | ||
}); | ||
|
||
// 테스트가 종료되면 Redis를 종료 | ||
afterAll(() => { | ||
redisService.quit(); | ||
}); | ||
|
||
describe('handleConnection', () => { | ||
it('roomId 또는 playerId의 값이 존재하지 않는 경우', async () => { | ||
const authInfo = [ | ||
{ roomId: '', playerId: '' }, | ||
{ roomId: 'room1', playerId: '' }, | ||
{ roomId: '', playerId: 'player1' }, | ||
]; | ||
|
||
for (const auth of authInfo) { | ||
client.handshake.auth = auth; | ||
await expect(gateway.handleConnection(client)).rejects.toThrowError( | ||
new BadRequestException('Room ID and Player ID are required'), | ||
); | ||
} | ||
}); | ||
|
||
it('room이 redis 내에 존재하지 않는 경우', async () => { | ||
client.handshake.auth = { roomId: 'failed-room', playerId: 'player1' }; | ||
|
||
await expect(gateway.handleConnection(client)).rejects.toThrowError(new RoomNotFoundException('Room not found')); | ||
}); | ||
|
||
it('player가 redis 내에 존재하지 않는 경우', async () => { | ||
client.handshake.auth = { roomId: 'room1', playerId: 'failed-player' }; | ||
|
||
await expect(gateway.handleConnection(client)).rejects.toThrowError( | ||
new PlayerNotFoundException('Player not found in room'), | ||
); | ||
}); | ||
|
||
it('room과 player가 정상적으로 존재하는 경우', async () => { | ||
client.handshake.auth = { roomId: 'room1', playerId: 'player1' }; | ||
await gateway.handleConnection(client); | ||
|
||
expect(client.join).toHaveBeenCalledWith('room1'); | ||
expect(client.data.roomId).toBe('room1'); | ||
expect(client.data.playerId).toBe('player1'); | ||
}); | ||
}); | ||
|
||
describe('handleDraw', () => { | ||
it('roomId 값이 존재하지 않는 경우', async () => { | ||
client.data = {}; | ||
const data = { drawingData: {} }; | ||
|
||
await expect(gateway.handleDraw(client, data)).rejects.toThrowError( | ||
new BadRequestException('Room ID is required'), | ||
); | ||
}); | ||
|
||
it('정상적으로 그림이 그려지는 경우', async () => { | ||
client.data = { roomId: 'room1', playerId: 'player1' }; | ||
const data = { drawingData: { pos: 56, fillColor: { R: 0, G: 0, B: 0, A: 0 } } }; | ||
|
||
await gateway.handleDraw(client, data); | ||
|
||
expect(client.to).toHaveBeenCalledWith('room1'); | ||
expect(client.to('room1').emit).toHaveBeenCalledWith('drawUpdated', { | ||
playerId: 'player1', | ||
drawingData: data.drawingData, | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.