Skip to content

Commit 68f4e44

Browse files
committed
🔧 chore : dev 기준으로 rebase 후 변경사항 반영
1 parent 15bb20c commit 68f4e44

File tree

4 files changed

+149
-2
lines changed

4 files changed

+149
-2
lines changed

backend/src/main/java/io/f1/backend/domain/game/app/ChatService.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@
1010
import io.f1.backend.domain.question.entity.Question;
1111
import io.f1.backend.domain.user.dto.UserPrincipal;
1212

13+
import io.f1.backend.global.lock.DistributedLock;
14+
import java.util.concurrent.atomic.AtomicBoolean;
1315
import lombok.RequiredArgsConstructor;
1416

17+
import org.redisson.api.RLock;
18+
import org.redisson.api.RedissonClient;
1519
import org.springframework.context.ApplicationEventPublisher;
1620
import org.springframework.stereotype.Service;
1721

@@ -20,7 +24,6 @@
2024
public class ChatService {
2125

2226
private final RoomService roomService;
23-
private final TimerService timerService;
2427
private final MessageSender messageSender;
2528
private final ApplicationEventPublisher eventPublisher;
2629

@@ -41,10 +44,16 @@ public void chat(Long roomId, UserPrincipal userPrincipal, ChatMessage chatMessa
4144

4245
String answer = currentQuestion.getAnswer();
4346

44-
if (answer.equals(chatMessage.message())) {
47+
if (!answer.equals(chatMessage.message())) {
48+
return;
49+
}
50+
51+
// false -> true
52+
if(room.compareAndSetAnsweredFlag(false, true)) {
4553
eventPublisher.publishEvent(
4654
new GameCorrectAnswerEvent(
4755
room, userPrincipal.getUserId(), chatMessage, answer));
56+
4857
}
4958
}
5059
}

backend/src/main/java/io/f1/backend/domain/game/app/GameService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public void onCorrectAnswer(GameCorrectAnswerEvent event) {
123123
ofPlayerEvent(chatMessage.nickname(), RoomEventType.CORRECT_ANSWER));
124124

125125
timerService.cancelTimer(room);
126+
room.compareAndSetAnsweredFlag(true, false);
126127

127128
if (!timerService.validateCurrentRound(room)) {
128129
gameEnd(room);
@@ -142,6 +143,12 @@ public void onCorrectAnswer(GameCorrectAnswerEvent event) {
142143
@EventListener
143144
public void onTimeout(GameTimeoutEvent event) {
144145
Room room = event.room();
146+
147+
// false -> true 여야 하는데 실패했을 때 => 이미 정답 처리가 된 경우 (onCorrectAnswer 로직 실행 중)
148+
if(!room.compareAndSetAnsweredFlag(false, true)) {
149+
return;
150+
}
151+
145152
log.debug(room.getId() + "번 방 타임아웃! 현재 라운드 : " + room.getCurrentRound());
146153

147154
String destination = getDestination(room.getId());
@@ -167,6 +174,8 @@ public void onTimeout(GameTimeoutEvent event) {
167174
destination,
168175
MessageType.QUESTION_START,
169176
toQuestionStartResponse(room, CONTINUE_DELAY));
177+
178+
room.compareAndSetAnsweredFlag(true, false);
170179
}
171180

172181
public void gameEnd(Room room) {

backend/src/main/java/io/f1/backend/domain/game/model/Room.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.f1.backend.global.exception.CustomException;
66
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
77

8+
import java.util.concurrent.atomic.AtomicBoolean;
89
import lombok.Getter;
910

1011
import java.time.LocalDateTime;
@@ -42,6 +43,8 @@ public class Room {
4243

4344
private ScheduledFuture<?> timer;
4445

46+
private final AtomicBoolean answered = new AtomicBoolean(false);
47+
4548
public Room(Long id, RoomSetting roomSetting, GameSetting gameSetting, Player host) {
4649
this.id = id;
4750
this.roomSetting = roomSetting;
@@ -194,4 +197,12 @@ public boolean isPlayerInState(Long userId, ConnectionState state) {
194197
public boolean isPasswordIncorrect(String password) {
195198
return roomSetting.locked() && !roomSetting.password().equals(password);
196199
}
200+
201+
public boolean compareAndSetAnsweredFlag(boolean expected, boolean newValue) {
202+
return answered.compareAndSet(expected, newValue);
203+
}
204+
205+
public AtomicBoolean getAnsweredFlag() {
206+
return this.answered;
207+
}
197208
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package io.f1.backend.domain.game.app;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.BDDMockito.given;
6+
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.never;
8+
import static org.mockito.Mockito.verify;
9+
10+
import io.f1.backend.domain.game.dto.ChatMessage;
11+
import io.f1.backend.domain.game.event.GameCorrectAnswerEvent;
12+
import io.f1.backend.domain.game.model.GameSetting;
13+
import io.f1.backend.domain.game.model.Player;
14+
import io.f1.backend.domain.game.model.Room;
15+
import io.f1.backend.domain.game.model.RoomSetting;
16+
import io.f1.backend.domain.game.websocket.MessageSender;
17+
import io.f1.backend.domain.question.entity.Question;
18+
import io.f1.backend.domain.user.entity.User;
19+
import java.lang.reflect.Field;
20+
import java.time.Instant;
21+
import java.time.LocalDateTime;
22+
import lombok.extern.slf4j.Slf4j;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.DisplayName;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
import org.mockito.Mock;
28+
import org.mockito.MockitoAnnotations;
29+
import org.mockito.junit.jupiter.MockitoExtension;
30+
import org.springframework.context.ApplicationEventPublisher;
31+
import org.springframework.security.core.context.SecurityContextHolder;
32+
33+
@Slf4j
34+
@ExtendWith(MockitoExtension.class)
35+
class ChatServiceTests {
36+
37+
private ChatService chatService;
38+
39+
@Mock private RoomService roomService;
40+
@Mock private ApplicationEventPublisher eventPublisher;
41+
@Mock private MessageSender messageSender;
42+
43+
@BeforeEach
44+
void setUp() {
45+
MockitoAnnotations.openMocks(this); // @Mock 어노테이션이 붙은 필드들을 초기화합니다.
46+
47+
chatService = new ChatService(roomService, messageSender, eventPublisher);
48+
49+
SecurityContextHolder.clearContext();
50+
}
51+
52+
53+
@Test
54+
@DisplayName("정답이 아닐 때 이벤트가 발행되지 않는다.")
55+
void noEventWhenIncorrect() throws Exception {
56+
57+
// given
58+
Long roomId = 1L;
59+
String sessionId = "session123";
60+
ChatMessage wrongMessage = new ChatMessage("nick", "오답", Instant.now());
61+
62+
Room room = mock(Room.class);
63+
Question question = mock(Question.class);
64+
65+
given(roomService.findRoom(roomId)).willReturn(room);
66+
given(room.isPlaying()).willReturn(true);
67+
given(room.getCurrentQuestion()).willReturn(question);
68+
given(question.getAnswer()).willReturn("정답");
69+
70+
// when
71+
chatService.chat(roomId, sessionId, wrongMessage);
72+
73+
// then
74+
verify(eventPublisher, never()).publishEvent(any(GameCorrectAnswerEvent.class));
75+
}
76+
77+
78+
}
79+
80+
private Room createRoom(
81+
Long roomId,
82+
Long playerId,
83+
Long quizId,
84+
String password,
85+
int maxUserCount,
86+
boolean locked) {
87+
RoomSetting roomSetting = new RoomSetting("방제목", maxUserCount, locked, password);
88+
GameSetting gameSetting = new GameSetting(quizId, 10, 60);
89+
Player host = new Player(playerId, "nickname");
90+
91+
return new Room(roomId, roomSetting, gameSetting, host);
92+
}
93+
94+
private User createUser(int i) {
95+
Long userId = i + 1L;
96+
String provider = "provider +" + i;
97+
String providerId = "providerId" + i;
98+
LocalDateTime lastLogin = LocalDateTime.now();
99+
100+
User user =
101+
User.builder()
102+
.provider(provider)
103+
.providerId(providerId)
104+
.lastLogin(lastLogin)
105+
.build();
106+
107+
try {
108+
Field idField = User.class.getDeclaredField("id");
109+
idField.setAccessible(true);
110+
idField.set(user, userId);
111+
} catch (Exception e) {
112+
throw new RuntimeException("ID 설정 실패", e);
113+
}
114+
115+
return user;
116+
}
117+
118+
}

0 commit comments

Comments
 (0)