11/* eslint-disable @typescript-eslint/unbound-method */
22import { Test , TestingModule } from '@nestjs/testing' ;
3- import { BadRequestException , ForbiddenException } from '@nestjs/common' ;
3+ import {
4+ BadRequestException ,
5+ ForbiddenException ,
6+ ConflictException ,
7+ } from '@nestjs/common' ;
48import { ReservationService } from './reservation.service' ;
59import { RedisService } from '../redis/redis.service' ;
610
@@ -20,6 +24,7 @@ describe('ReservationService', () => {
2024 sismember : jest . fn ( ) ,
2125 setNx : jest . fn ( ) ,
2226 setNxWithTtl : jest . fn ( ) ,
27+ msetnx : jest . fn ( ) ,
2328 incr : jest . fn ( ) ,
2429 publishToQueue : jest . fn ( ) ,
2530 del : jest . fn ( ) ,
@@ -69,9 +74,20 @@ describe('ReservationService', () => {
6974 } ) ;
7075
7176 describe ( 'reserve' , ( ) => {
72- const dto = { session_id : 1 , block_id : 10 , row : 0 , col : 0 } ;
77+ const dto = {
78+ session_id : 1 ,
79+ seats : [ { block_id : 10 , row : 0 , col : 0 } ] ,
80+ } ;
7381 const userId = 'user-1' ;
7482
83+ it ( '유저 락 획득에 실패하면 ConflictException을 던져야 한다' , async ( ) => {
84+ redisService . setNxWithTtl . mockResolvedValue ( false ) ;
85+
86+ await expect ( service . reserve ( dto , userId ) ) . rejects . toThrow (
87+ ConflictException ,
88+ ) ;
89+ } ) ;
90+
7591 it ( '티켓팅이 오픈되지 않았으면 ForbiddenException을 던져야 한다' , async ( ) => {
7692 redisService . setNxWithTtl . mockResolvedValue ( true ) ;
7793 redisService . get . mockResolvedValue ( 'false' ) ;
@@ -83,7 +99,7 @@ describe('ReservationService', () => {
8399
84100 it ( '유효하지 않은 블록이면 BadRequestException을 던져야 한다' , async ( ) => {
85101 redisService . setNxWithTtl . mockResolvedValue ( true ) ;
86- redisService . get . mockResolvedValue ( 'true' ) ; // ticketing open
102+ redisService . get . mockResolvedValue ( 'true' ) ;
87103 redisService . sismember . mockResolvedValue ( false ) ;
88104
89105 await expect ( service . reserve ( dto , userId ) ) . rejects . toThrow (
@@ -101,13 +117,38 @@ describe('ReservationService', () => {
101117 } ) ;
102118 redisService . sismember . mockResolvedValue ( true ) ;
103119
104- const invalidDto = { ...dto , row : 5 } ;
120+ const invalidDto = {
121+ session_id : 1 ,
122+ seats : [ { block_id : 10 , row : 5 , col : 0 } ] ,
123+ } ;
105124 await expect ( service . reserve ( invalidDto , userId ) ) . rejects . toThrow (
106125 BadRequestException ,
107126 ) ;
108127 } ) ;
109128
110- it ( '이미 예약된 좌석이면 BadRequestException을 던져야 한다' , async ( ) => {
129+ it ( '요청에 중복된 좌석이 있으면 BadRequestException을 던져야 한다' , async ( ) => {
130+ const mockBlockData = JSON . stringify ( { rowSize : 5 , colSize : 5 } ) ;
131+ redisService . setNxWithTtl . mockResolvedValue ( true ) ;
132+ redisService . get . mockImplementation ( ( key ) => {
133+ if ( key === 'is_ticketing_open' ) return Promise . resolve ( 'true' ) ;
134+ if ( key === 'block:10' ) return Promise . resolve ( mockBlockData ) ;
135+ return Promise . resolve ( null ) ;
136+ } ) ;
137+ redisService . sismember . mockResolvedValue ( true ) ;
138+
139+ const duplicateDto = {
140+ session_id : 1 ,
141+ seats : [
142+ { block_id : 10 , row : 1 , col : 1 } ,
143+ { block_id : 10 , row : 1 , col : 1 } ,
144+ ] ,
145+ } ;
146+ await expect ( service . reserve ( duplicateDto , userId ) ) . rejects . toThrow (
147+ BadRequestException ,
148+ ) ;
149+ } ) ;
150+
151+ it ( '일부 좌석이 이미 예약되어 있으면 BadRequestException을 던져야 한다' , async ( ) => {
111152 const mockBlockData = JSON . stringify ( { rowSize : 2 , colSize : 2 } ) ;
112153 redisService . setNxWithTtl . mockResolvedValue ( true ) ;
113154 redisService . get . mockImplementation ( ( key ) => {
@@ -116,7 +157,7 @@ describe('ReservationService', () => {
116157 return Promise . resolve ( null ) ;
117158 } ) ;
118159 redisService . sismember . mockResolvedValue ( true ) ;
119- redisService . setNx . mockResolvedValue ( false ) ;
160+ redisService . msetnx . mockResolvedValue ( 0 ) ;
120161
121162 await expect ( service . reserve ( dto , userId ) ) . rejects . toThrow (
122163 BadRequestException ,
@@ -132,15 +173,47 @@ describe('ReservationService', () => {
132173 return Promise . resolve ( null ) ;
133174 } ) ;
134175 redisService . sismember . mockResolvedValue ( true ) ;
135- redisService . setNx . mockResolvedValue ( true ) ;
136- redisService . incr . mockResolvedValue ( 5 ) ; // Rank 5 발급
176+ redisService . msetnx . mockResolvedValue ( 1 ) ;
177+ redisService . incr . mockResolvedValue ( 5 ) ;
137178 redisService . publishToQueue . mockResolvedValue ( undefined ) ;
138179
139180 const result = await service . reserve ( dto , userId ) ;
140181
141182 expect ( result . rank ) . toBe ( 5 ) ;
142- expect ( redisService . setNx ) . toHaveBeenCalled ( ) ;
183+ expect ( result . seats ) . toEqual ( dto . seats ) ;
184+ expect ( redisService . msetnx ) . toHaveBeenCalled ( ) ;
143185 expect ( redisService . incr ) . toHaveBeenCalledWith ( 'rank:session:1' ) ;
144186 } ) ;
187+
188+ it ( '여러 블록의 좌석을 동시에 예약할 수 있어야 한다' , async ( ) => {
189+ redisService . setNxWithTtl . mockResolvedValue ( true ) ;
190+ redisService . get . mockImplementation ( ( key ) => {
191+ if ( key === 'is_ticketing_open' ) return Promise . resolve ( 'true' ) ;
192+ if ( key === 'block:10' )
193+ return Promise . resolve ( JSON . stringify ( { rowSize : 5 , colSize : 5 } ) ) ;
194+ if ( key === 'block:11' )
195+ return Promise . resolve ( JSON . stringify ( { rowSize : 5 , colSize : 5 } ) ) ;
196+ return Promise . resolve ( null ) ;
197+ } ) ;
198+ redisService . sismember . mockResolvedValue ( true ) ;
199+ redisService . msetnx . mockResolvedValue ( 1 ) ;
200+ redisService . incr . mockResolvedValue ( 10 ) ;
201+
202+ const multiBlockDto = {
203+ session_id : 1 ,
204+ seats : [
205+ { block_id : 10 , row : 1 , col : 1 } ,
206+ { block_id : 11 , row : 2 , col : 2 } ,
207+ ] ,
208+ } ;
209+
210+ const result = await service . reserve ( multiBlockDto , userId ) ;
211+
212+ expect ( result . rank ) . toBe ( 10 ) ;
213+ expect ( redisService . msetnx ) . toHaveBeenCalledWith ( {
214+ 'reservation:session:1:block:10:row:1:col:1' : userId ,
215+ 'reservation:session:1:block:11:row:2:col:2' : userId ,
216+ } ) ;
217+ } ) ;
145218 } ) ;
146219} ) ;
0 commit comments