11package com .stackup .stackup .session .application ;
22
3- import static org .assertj .core .api .Assertions .assertThat ;
43import static org .mockito .ArgumentMatchers .any ;
4+ import static org .mockito .ArgumentMatchers .eq ;
55import static org .mockito .Mockito .never ;
66import static org .mockito .Mockito .verify ;
77import static org .mockito .Mockito .when ;
1515import com .stackup .stackup .session .domain .SessionMode ;
1616import com .stackup .stackup .session .domain .SessionStatus ;
1717import com .stackup .stackup .user .domain .User ;
18+ import java .time .Instant ;
1819import java .util .Optional ;
1920import org .junit .jupiter .api .Test ;
2021import org .junit .jupiter .api .extension .ExtendWith ;
@@ -33,30 +34,46 @@ class SessionTimeoutServiceTest {
3334 @ InjectMocks SessionTimeoutService service ;
3435
3536 @ Test
36- void endTimedOut_withAnswer_completesAndEmitsEndedEvent () {
37+ void endTimedOut_withAnswer_claimsCompletedAndEmitsEndedEvent () {
3738 InterviewSession session = inProgressFixture (50L );
3839 when (sessionRepository .findById (50L )).thenReturn (Optional .of (session ));
3940 when (messageRepository .existsBySession_IdAndRole (50L , MessageRole .INTERVIEWEE )).thenReturn (true );
41+ when (sessionRepository .finishIfInProgress (eq (50L ), eq (SessionStatus .COMPLETED ), any (Instant .class )))
42+ .thenReturn (1 );
4043
4144 service .endTimedOut (50L );
4245
43- assertThat (session .getStatus ()).isEqualTo (SessionStatus .COMPLETED );
4446 verify (events ).publishEvent (any (SessionEndedEvent .class ));
4547 }
4648
4749 @ Test
48- void endTimedOut_withoutAnswer_interruptsAndDoesNotTriggerFeedback () {
50+ void endTimedOut_withoutAnswer_claimsInterruptedAndDoesNotTriggerFeedback () {
4951 InterviewSession session = inProgressFixture (51L );
5052 when (sessionRepository .findById (51L )).thenReturn (Optional .of (session ));
5153 when (messageRepository .existsBySession_IdAndRole (51L , MessageRole .INTERVIEWEE )).thenReturn (false );
54+ when (sessionRepository .finishIfInProgress (eq (51L ), eq (SessionStatus .INTERRUPTED ), any (Instant .class )))
55+ .thenReturn (1 );
5256
5357 service .endTimedOut (51L );
5458
55- assertThat (session .getStatus ()).isEqualTo (SessionStatus .INTERRUPTED );
5659 // 답변이 없으면 피드백을 만들지 않는다 — SessionEndedEvent 미발행.
5760 verify (events , never ()).publishEvent (any (SessionEndedEvent .class ));
5861 }
5962
63+ @ Test
64+ void endTimedOut_lostRace_emitsNothing () {
65+ // 조건부 UPDATE 가 0행 → 다른 트랜잭션이 먼저 종료. 어떤 이벤트도 발행하지 않는다(중복 방지).
66+ InterviewSession session = inProgressFixture (53L );
67+ when (sessionRepository .findById (53L )).thenReturn (Optional .of (session ));
68+ when (messageRepository .existsBySession_IdAndRole (53L , MessageRole .INTERVIEWEE )).thenReturn (true );
69+ when (sessionRepository .finishIfInProgress (eq (53L ), eq (SessionStatus .COMPLETED ), any (Instant .class )))
70+ .thenReturn (0 );
71+
72+ service .endTimedOut (53L );
73+
74+ verify (events , never ()).publishEvent (any ());
75+ }
76+
6077 @ Test
6178 void endTimedOut_notInProgress_isNoop () {
6279 InterviewSession session = inProgressFixture (52L );
@@ -67,6 +84,7 @@ void endTimedOut_notInProgress_isNoop() {
6784
6885 verify (events , never ()).publishEvent (any ());
6986 verify (messageRepository , never ()).existsBySession_IdAndRole (any (), any ());
87+ verify (sessionRepository , never ()).finishIfInProgress (any (), any (), any ());
7088 }
7189
7290 private InterviewSession inProgressFixture (Long id ) {
0 commit comments