From 06ce7e3fcb47f596c784e4b16f9f7ea03c86424d Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 2 Nov 2026 23:30:43 +0900 Subject: [PATCH] =?UTF-8?q?#606=20[Fix]=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=C2=B7=ED=8E=80=EB=94=A9=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EA=B9=A8=EC=A7=84=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classroom/service/ClassMemberServiceImplTest.java | 5 ++++- .../classroom/service/ClassroomServiceImplTest.java | 10 +++++----- .../picke/domain/oauth/service/OAuthServiceTest.java | 7 ++++--- .../perspective/service/PerspectiveServiceTest.java | 2 +- src/test/resources/application-test.yml | 3 +++ 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImplTest.java b/src/test/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImplTest.java index 49bbd616..e15bb370 100644 --- a/src/test/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImplTest.java +++ b/src/test/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImplTest.java @@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -78,6 +79,8 @@ class ClassMemberServiceImplTest { void setUp() { owner = user(OWNER_ID, "선생님"); student = user(STUDENT_ID, "학생"); + // 참여는 가장 먼저 참여자 계정을 잠그고 ACTIVE 인지 확인한다 (#584) + lenient().when(userRepository.findByIdForUpdate(STUDENT_ID)).thenReturn(Optional.of(student)); classroom = classroom(owner, 5, null); } @@ -91,7 +94,6 @@ void join_success_normalizesCode() { when(classMemberRepository.existsByClassroomIdAndDisplayNameAndStatus(CLASS_ID, "김학생", ClassMemberStatus.ACTIVE)) .thenReturn(false); when(classMemberRepository.countByClassroomIdAndSeatHeldTrue(CLASS_ID)).thenReturn(4L, 5L); - when(userRepository.findById(STUDENT_ID)).thenReturn(Optional.of(student)); ClassDetailResponse response = service.join(STUDENT_ID, new ClassJoinRequest(" abc-234 ", " 김학생 ")); @@ -139,6 +141,7 @@ void join_unknownCode_404() { @DisplayName("운영자가 자기 클래스 코드를 넣으면 409 CLASS_409_OWNER") void join_ownerOwnCode_409() { givenClassByCode(); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); assertThatThrownBy(() -> service.join(OWNER_ID, new ClassJoinRequest(CODE, "선생님"))) .satisfies(e -> assertThat(errorCodeOf(e)).isEqualTo(ErrorCode.CLASS_OWNER_CANNOT_JOIN)); diff --git a/src/test/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImplTest.java b/src/test/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImplTest.java index b6b72818..38974282 100644 --- a/src/test/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImplTest.java +++ b/src/test/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImplTest.java @@ -123,7 +123,7 @@ void create_success_withOwnerParticipating() { @Test @DisplayName("직전에 같은 이름·배틀로 만든 클래스가 있으면 새로 만들지 않고 그 클래스를 돌려준다") void create_duplicateRequest_returnsExisting() { - when(userRepository.findById(OWNER_ID)).thenReturn(Optional.of(owner)); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); when(classTicketRepository.findByRegisteredUserIdAndStatus(OWNER_ID, TicketStatus.ACTIVE)) .thenReturn(Optional.of(ticket)); Classroom existing = classroom(null); @@ -187,7 +187,7 @@ void create_retriesInviteCodeOnCollision() { @Test @DisplayName("사용 중 이용권이 없으면 CLASS_403_TICKET") void create_rejectsWithoutActiveTicket() { - when(userRepository.findById(OWNER_ID)).thenReturn(Optional.of(owner)); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); when(classTicketRepository.findByRegisteredUserIdAndStatus(OWNER_ID, TicketStatus.ACTIVE)) .thenReturn(Optional.empty()); @@ -203,7 +203,7 @@ void create_rejectsWithoutActiveTicket() { @DisplayName("기간이 끝난 ACTIVE 이용권으로는 만들 수 없고(CLASS_403_TICKET), 이용권 상태는 건드리지 않는다") void create_rejectsStaleTicketWithoutTouchingIt() { ClassTicket stale = activeTicket(owner, LocalDateTime.now().minusMonths(7)); - when(userRepository.findById(OWNER_ID)).thenReturn(Optional.of(owner)); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); when(classTicketRepository.findByRegisteredUserIdAndStatus(OWNER_ID, TicketStatus.ACTIVE)) .thenReturn(Optional.of(stale)); @@ -218,7 +218,7 @@ void create_rejectsStaleTicketWithoutTouchingIt() { @Test @DisplayName("공개되지 않은 배틀은 CLASS_400_BATTLE") void create_rejectsUnpublishedBattle() { - when(userRepository.findById(OWNER_ID)).thenReturn(Optional.of(owner)); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); when(classTicketRepository.findByRegisteredUserIdAndStatus(OWNER_ID, TicketStatus.ACTIVE)) .thenReturn(Optional.of(ticket)); when(battleRepository.findById(BATTLE_ID)).thenReturn(Optional.of(battle(BattleStatus.PENDING))); @@ -422,7 +422,7 @@ void update_rejectsWhenTicketRevoked() { } private void givenOwnerTicketBattle() { - when(userRepository.findById(OWNER_ID)).thenReturn(Optional.of(owner)); + when(userRepository.findByIdForUpdate(OWNER_ID)).thenReturn(Optional.of(owner)); when(classTicketRepository.findByRegisteredUserIdAndStatus(OWNER_ID, TicketStatus.ACTIVE)) .thenReturn(Optional.of(ticket)); when(battleRepository.findById(BATTLE_ID)).thenReturn(Optional.of(battle)); diff --git a/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java b/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java index 14a17a9b..bc2f2dd9 100644 --- a/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java +++ b/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java @@ -333,7 +333,7 @@ void setUp() { .status(UserStatus.ACTIVE) .build(); - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(userRepository.findByIdForUpdate(1L)).thenReturn(Optional.of(user)); when(userWithdrawalRepository.existsByUser_Id(1L)).thenReturn(false); authService.withdraw(1L, new WithdrawRequest(WithdrawalReason.NO_TIME)); @@ -344,6 +344,7 @@ void setUp() { verify(userWithdrawalRepository).save(withdrawalCaptor.capture()); assertThat(withdrawalCaptor.getValue().getReason()).isEqualTo(WithdrawalReason.NO_TIME); + verify(userClassWithdrawalService).apply(eq(user), any()); assertThat(user.getStatus()).isEqualTo(UserStatus.DELETED); assertThat(user.getDeletedAt()).isNotNull(); } @@ -356,7 +357,7 @@ void setUp() { .status(UserStatus.ACTIVE) .build(); - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(userRepository.findByIdForUpdate(1L)).thenReturn(Optional.of(user)); when(userWithdrawalRepository.existsByUser_Id(1L)).thenReturn(true); authService.withdraw(1L, new WithdrawRequest(WithdrawalReason.OTHER)); @@ -383,7 +384,7 @@ void setUp() { .build(); socialAccount.updateAppleRefreshToken("apple-stored-token"); - when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(userRepository.findByIdForUpdate(userId)).thenReturn(Optional.of(user)); when(userWithdrawalRepository.existsByUser_Id(userId)).thenReturn(false); when(socialAccountRepository.findByUser(user)).thenReturn(Optional.of(socialAccount)); diff --git a/src/test/java/com/swyp/picke/domain/perspective/service/PerspectiveServiceTest.java b/src/test/java/com/swyp/picke/domain/perspective/service/PerspectiveServiceTest.java index 1f78d70c..9e6df437 100644 --- a/src/test/java/com/swyp/picke/domain/perspective/service/PerspectiveServiceTest.java +++ b/src/test/java/com/swyp/picke/domain/perspective/service/PerspectiveServiceTest.java @@ -108,7 +108,7 @@ void createPerspective_allows_multiple_perspectives_for_same_user_and_battle() { BattleOption option = option(10L, battle, BattleOptionLabel.A, "예술이다"); when(battleService.findById(100L)).thenReturn(battle); - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(userRepository.findByIdForUpdate(1L)).thenReturn(Optional.of(user)); when(battleVoteService.findPostVoteOptionId(100L, 1L, null)).thenReturn(10L); when(battleService.findOptionById(10L)).thenReturn(option); when(perspectiveRepository.save(org.mockito.ArgumentMatchers.any(Perspective.class))) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 270acc1c..9ad69064 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -86,6 +86,9 @@ app: baseUrl: http://localhost:8080 picke: baseUrl: http://localhost:8080 + ticket: + # 테스트 전용 이용권 코드 암호화 키 (Base64 로 인코딩한 32바이트) + code-encryption-key: cGlja2UtdGVzdC10aWNrZXQtY29kZS12YXVsdC1rZXk= local-storage: root: ${java.io.tmpdir}/picke-local-storage-test class-report: