diff --git a/src/main/java/com/ftm/server/adapter/in/web/user/dto/response/EmailCodeVerificationResponse.java b/src/main/java/com/ftm/server/adapter/in/web/user/dto/response/EmailCodeVerificationResponse.java index 034acd6..26fe9de 100644 --- a/src/main/java/com/ftm/server/adapter/in/web/user/dto/response/EmailCodeVerificationResponse.java +++ b/src/main/java/com/ftm/server/adapter/in/web/user/dto/response/EmailCodeVerificationResponse.java @@ -6,8 +6,9 @@ @Data public class EmailCodeVerificationResponse { private final Boolean isVerified; + private final Boolean isRecoverable; public static EmailCodeVerificationResponse from(EmailCodeVerificationVo vo) { - return new EmailCodeVerificationResponse(vo.getIsVerified()); + return new EmailCodeVerificationResponse(vo.getIsVerified(), vo.getIsRecoverable()); } } diff --git a/src/main/java/com/ftm/server/adapter/out/cache/LoadTrendingPostsTestAdapter.java b/src/main/java/com/ftm/server/adapter/out/cache/LoadTrendingPostsTestAdapter.java new file mode 100644 index 0000000..c7f5483 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/cache/LoadTrendingPostsTestAdapter.java @@ -0,0 +1,86 @@ +package com.ftm.server.adapter.out.cache; + +import com.ftm.server.application.port.out.cache.LoadTrendingPostsWithCachePort; +import com.ftm.server.application.port.out.persistence.post.LoadPostImagePort; +import com.ftm.server.application.port.out.persistence.post.LoadPostWithBookmarkCountPort; +import com.ftm.server.application.query.FindByIdsQuery; +import com.ftm.server.application.query.FindPostsByCreatedDateQuery; +import com.ftm.server.application.vo.post.PostWithBookmarkCountVo; +import com.ftm.server.application.vo.post.TrendingPostVo; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Primary +public class LoadTrendingPostsTestAdapter implements LoadTrendingPostsWithCachePort { + + private final LoadPostWithBookmarkCountPort loadPostPort; + private final LoadPostImagePort loadPostImagePort; + + private static final int N = 15; + + @Override + public List loadTrendingPosts() { + + // 현재보다 1주일 이전에 작성된 게시물만 조회(북마크 조회수 포함) (예전 게시물은 포함x) + List rawPosts = + loadPostPort.loadAllPostsWithBookmarkCount( + FindPostsByCreatedDateQuery.of(LocalDate.now())); + + if (rawPosts.isEmpty()) return List.of(); + + // 1. 최대값 계산 (정규화를 위해) + long maxView = 1, maxLike = 1, maxScrap = 1; + for (PostWithBookmarkCountVo post : rawPosts) { + maxView = Math.max(maxView, post.getViewCount()); + maxLike = Math.max(maxLike, post.getLikeCount()); + maxScrap = Math.max(maxScrap, post.getScrapCount()); + } + + // 2. 점수 계산 후 정렬 + long finalMaxView = maxView; + long finalMaxLike = maxLike; + long finalMaxScrap = maxScrap; + List topN = + rawPosts.stream() + .sorted( + Comparator.comparingDouble( + p -> + -((double) p.getViewCount() / finalMaxView + + (double) p.getLikeCount() + / finalMaxLike + + (double) p.getScrapCount() + / finalMaxScrap) + / 3.0)) + .limit(N) + .toList(); + + // 3. 각 게시물 이미지 조회 (없으면 null) + List postIds = topN.stream().map(PostWithBookmarkCountVo::getPostId).toList(); + + Map imageMap = new HashMap<>(); + postIds.forEach(p -> imageMap.put(p, null)); + loadPostImagePort + .loadRepresentativeImagesByPostIds(FindByIdsQuery.from(postIds)) + .forEach( + postImage -> imageMap.put(postImage.getPostId(), postImage.getObjectKey())); + + // 4. 결과 조합 (순위 부여) + return IntStream.range(0, topN.size()) + .mapToObj( + i -> { + PostWithBookmarkCountVo post = topN.get(i); + String imageKey = imageMap.get(post.getPostId()); + return TrendingPostVo.from(post, i + 1, imageKey); // rank = i+1 + }) + .toList(); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java index 283a8dc..b214d2f 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java @@ -25,6 +25,7 @@ public class UserDomainPersistenceAdapter implements LoadEmailVerificationLogPort, SaveEmailVerificationLogPort, UpdateEmailVerificationLogPort, + DeleteEmailVerificationLogPort, CheckUserPort, SaveUserPort, SaveUserImagePort, @@ -91,6 +92,11 @@ public void updateEmailVerificationLog(EmailVerificationLogs emailVerificationLo emailVerificationLogsJpaEntity.updateFromDomainEntity(emailVerificationLogs); } + @Override + public void deleteEmailVerificationLogsByEmail(DeleteByEmailCommand command) { + emailVerificationLogsRepository.deleteAllByEmail(command.getEmail()); + } + @Override public Boolean checksUserByEmail(FindByEmailQuery query) { return userRepository.existsByEmail(query.getEmail()); @@ -154,6 +160,13 @@ public List loadUserByDeleteOption(FindUserByDeleteOptionQuery query) { .toList(); } + @Override + public Optional loadDeletedUserByEmail(FindByEmailQuery query) { + Optional user = + userRepository.findByEmailAndIsDeleted(query.getEmail(), true); + return user.map(userMapper::toDomainEntity); + } + @Override public UserImage loadUserImageByUserId(FindByUserIdQuery query) { UserImageJpaEntity userImageJpaEntity = @@ -307,4 +320,14 @@ public List loadUserImagesByUserIdIn(FindUserImagesByIdsQuery query) .map(userImageMapper::toDomainEntity) .toList(); } + + @Override + public Boolean checksNotDeletedUserByEmail(FindByEmailQuery query) { + return userRepository.existsByEmailAndIsDeleted(query.getEmail(), false); + } + + @Override + public Boolean checksUserSoftDeletedByEmail(FindByEmailQuery query) { + return userRepository.existsByEmailAndIsDeleted(query.getEmail(), true); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/EmailVerificationLogsRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/EmailVerificationLogsRepository.java index ea40898..183a62f 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/EmailVerificationLogsRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/EmailVerificationLogsRepository.java @@ -3,6 +3,9 @@ import com.ftm.server.adapter.out.persistence.model.EmailVerificationLogsJpaEntity; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -16,4 +19,8 @@ Optional findByVerificationCodeAndEmail( Optional findByEmailAndIsVerified( String email, Boolean isVerified); + + @Modifying + @Query("DELETE FROM EmailVerificationLogsJpaEntity e WHERE e.email = :email") + void deleteAllByEmail(@Param("email") String email); } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java index 0597624..b2e90e1 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java @@ -15,6 +15,8 @@ public interface UserRepository extends JpaRepository { Optional findByIdAndIsDeleted(Long userId, Boolean isDeleted); + Optional findByEmailAndIsDeleted(String email, Boolean isDeleted); + Boolean existsByEmail(String email); Optional findByEmail(String email); @@ -33,4 +35,6 @@ Optional findBySocialProviderAndSocialId( @Query("SELECT u FROM UserJpaEntity u WHERE u.isDeleted = :isDeleted And u.deletedAt <=:end") List findAllByDeletedBefore( @Param("isDeleted") Boolean isDeleted, @Param("end") LocalDateTime end); + + Boolean existsByEmailAndIsDeleted(String email, boolean b); } diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteByEmailCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteByEmailCommand.java new file mode 100644 index 0000000..ee33c25 --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteByEmailCommand.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.command.user; + +import lombok.Data; + +@Data +public class DeleteByEmailCommand { + private final String email; + + public static DeleteByEmailCommand of(String email) { + return new DeleteByEmailCommand(email); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteUserByEmailCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteUserByEmailCommand.java new file mode 100644 index 0000000..05b72ae --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteUserByEmailCommand.java @@ -0,0 +1,14 @@ +package com.ftm.server.application.command.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class DeleteUserByEmailCommand { + private final String email; + + public static DeleteUserByEmailCommand of(String email) { + return new DeleteUserByEmailCommand(email); + } +} diff --git a/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteByEmailUseCase.java b/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteByEmailUseCase.java new file mode 100644 index 0000000..258dcc0 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteByEmailUseCase.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.in.user; + +import com.ftm.server.application.command.user.DeleteUserByEmailCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface UserHardDeleteByEmailUseCase { + void execute(DeleteUserByEmailCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/CheckUserPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/CheckUserPort.java index 1444af9..5d03fab 100644 --- a/src/main/java/com/ftm/server/application/port/out/persistence/user/CheckUserPort.java +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/CheckUserPort.java @@ -12,4 +12,8 @@ public interface CheckUserPort { Boolean checksUserBySocialValue(FindBySocialValueQuery query); Boolean checksUserById(FindByUserIdQuery query); + + Boolean checksNotDeletedUserByEmail(FindByEmailQuery query); + + Boolean checksUserSoftDeletedByEmail(FindByEmailQuery query); } diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteEmailVerificationLogPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteEmailVerificationLogPort.java new file mode 100644 index 0000000..6a70a61 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteEmailVerificationLogPort.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.command.user.DeleteByEmailCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface DeleteEmailVerificationLogPort { + void deleteEmailVerificationLogsByEmail(DeleteByEmailCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java index 012f4de..015bbd7 100644 --- a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java @@ -1,5 +1,6 @@ package com.ftm.server.application.port.out.persistence.user; +import com.ftm.server.application.query.FindByEmailQuery; import com.ftm.server.application.query.FindByUserIdQuery; import com.ftm.server.application.query.FindUserByDeleteOptionQuery; import com.ftm.server.application.query.FindUserByRoleQuery; @@ -14,4 +15,6 @@ public interface LoadUserPort { User loadUserByRole(FindUserByRoleQuery query); List loadUserByDeleteOption(FindUserByDeleteOptionQuery query); + + Optional loadDeletedUserByEmail(FindByEmailQuery query); } diff --git a/src/main/java/com/ftm/server/application/service/user/EmailCodeVerificationService.java b/src/main/java/com/ftm/server/application/service/user/EmailCodeVerificationService.java index ab0c6f9..b781cb4 100644 --- a/src/main/java/com/ftm/server/application/service/user/EmailCodeVerificationService.java +++ b/src/main/java/com/ftm/server/application/service/user/EmailCodeVerificationService.java @@ -1,9 +1,11 @@ package com.ftm.server.application.service.user; import com.ftm.server.application.port.in.user.EmailCodeVerificationUseCase; +import com.ftm.server.application.port.out.persistence.user.CheckUserPort; import com.ftm.server.application.port.out.persistence.user.LoadEmailVerificationLogPort; import com.ftm.server.application.port.out.persistence.user.UpdateEmailVerificationLogPort; import com.ftm.server.application.query.EmailCodeVerificationQuery; +import com.ftm.server.application.query.FindByEmailQuery; import com.ftm.server.application.vo.user.EmailCodeVerificationVo; import com.ftm.server.domain.entity.EmailVerificationLogs; import jakarta.transaction.Transactional; @@ -17,6 +19,7 @@ public class EmailCodeVerificationService implements EmailCodeVerificationUseCas private final LoadEmailVerificationLogPort loadEmailVerificationLogPort; private final UpdateEmailVerificationLogPort updateEmailVerificationLogPort; + private final CheckUserPort checkUserPort; @Override @Transactional @@ -28,8 +31,14 @@ public EmailCodeVerificationVo execute(EmailCodeVerificationQuery query) { if (emailVerificationLogs.isEmpty()) { // 검증 코드가 일치하지 않음 return EmailCodeVerificationVo.of(false); } + emailVerificationLogs.get().updateVerificationStatus(true); // 검증 코드가 일치함 updateEmailVerificationLogPort.updateEmailVerificationLog(emailVerificationLogs.get()); - return EmailCodeVerificationVo.of(true); + + // 해당 이메일의 계정 상태 확인 (soft delete 여부) + Boolean isRecoverable = + checkUserPort.checksUserSoftDeletedByEmail(FindByEmailQuery.of(query.getEmail())); + + return EmailCodeVerificationVo.of(true, isRecoverable); } } diff --git a/src/main/java/com/ftm/server/application/service/user/EmailDuplicationCheckService.java b/src/main/java/com/ftm/server/application/service/user/EmailDuplicationCheckService.java index ba96dc9..d1b7fd5 100644 --- a/src/main/java/com/ftm/server/application/service/user/EmailDuplicationCheckService.java +++ b/src/main/java/com/ftm/server/application/service/user/EmailDuplicationCheckService.java @@ -15,6 +15,6 @@ public class EmailDuplicationCheckService implements EmailDuplicationCheckUseCas @Override public EmailDuplicationVo execute(FindByEmailQuery query) { - return EmailDuplicationVo.of(checkUserPort.checksUserByEmail(query)); + return EmailDuplicationVo.of(checkUserPort.checksNotDeletedUserByEmail(query)); } } diff --git a/src/main/java/com/ftm/server/application/service/user/GeneralUserSignupService.java b/src/main/java/com/ftm/server/application/service/user/GeneralUserSignupService.java index a3f77ef..ada92dc 100644 --- a/src/main/java/com/ftm/server/application/service/user/GeneralUserSignupService.java +++ b/src/main/java/com/ftm/server/application/service/user/GeneralUserSignupService.java @@ -1,10 +1,14 @@ package com.ftm.server.application.service.user; import com.ftm.server.adapter.in.web.user.dto.response.GeneralUserSignupResponse; +import com.ftm.server.application.command.user.DeleteByEmailCommand; +import com.ftm.server.application.command.user.DeleteUserByEmailCommand; import com.ftm.server.application.command.user.GeneralUserCreationCommand; import com.ftm.server.application.command.user.GeneralUserSignupCommand; import com.ftm.server.application.port.in.user.GeneralUserSignupUseCase; +import com.ftm.server.application.port.in.user.UserHardDeleteByEmailUseCase; import com.ftm.server.application.port.out.persistence.user.CheckUserPort; +import com.ftm.server.application.port.out.persistence.user.DeleteEmailVerificationLogPort; import com.ftm.server.application.port.out.persistence.user.LoadEmailVerificationLogPort; import com.ftm.server.application.port.out.persistence.user.SaveUserImagePort; import com.ftm.server.application.port.out.persistence.user.SaveUserPort; @@ -25,8 +29,12 @@ @RequiredArgsConstructor public class GeneralUserSignupService implements GeneralUserSignupUseCase { + // usecase + private final UserHardDeleteByEmailUseCase userHardDeleteByEmailUseCase; + // service private final LoadEmailVerificationLogPort loadEmailVerificationLogPort; + private final DeleteEmailVerificationLogPort deleteEmailVerificationLogPort; private final CheckUserPort checksUserPort; private final SaveUserPort saveUserPort; private final SaveUserImagePort saveUserImagePort; @@ -42,7 +50,8 @@ public GeneralUserSignupResponse execute(GeneralUserSignupCommand command) { loadEmailVerificationLogPort.loadEmailVerificationLogByEmail( FindByEmailQuery.of(email)); - if (checksUserPort.checksUserByEmail(FindByEmailQuery.of(email))) { // 기존에 가입된 회원인지 검사 + if (checksUserPort.checksNotDeletedUserByEmail( + FindByEmailQuery.of(email))) { // 기존에 가입된 회원인지 검사(삭제되지 않은 user 에 한해서) throw new CustomException(ErrorResponseCode.USER_ALREADY_EXISTS); } @@ -50,6 +59,11 @@ public GeneralUserSignupResponse execute(GeneralUserSignupCommand command) { throw new CustomException(ErrorResponseCode.EMAIL_NOT_VERIFIED); } + // 회원 탈퇴 후 복구 없이 재가입하는 경우, 기존의 계정 정보 즉시 삭제 + if (checksUserPort.checksUserSoftDeletedByEmail(FindByEmailQuery.of(email))) { + userHardDeleteByEmailUseCase.execute(DeleteUserByEmailCommand.of(email)); + } + String nickname = RandomNickNameCreator.generateNickname(); // random 닉네임 생성 GeneralUserCreationCommand convertedCommand = @@ -62,6 +76,11 @@ public GeneralUserSignupResponse execute(GeneralUserSignupCommand command) { User user = saveUserPort.saveUser(User.createGeneralUser(convertedCommand)); saveUserImagePort.saveUserDefaultImage(UserImage.createUserImage(user.getId())); + + // 회원가입 완료 후 해당 이메일의 인증 로그 삭제 + deleteEmailVerificationLogPort.deleteEmailVerificationLogsByEmail( + DeleteByEmailCommand.of(email)); + return GeneralUserSignupResponse.of(user.getId()); } } diff --git a/src/main/java/com/ftm/server/application/service/user/UserHardDeleteByEmailService.java b/src/main/java/com/ftm/server/application/service/user/UserHardDeleteByEmailService.java new file mode 100644 index 0000000..6b60a92 --- /dev/null +++ b/src/main/java/com/ftm/server/application/service/user/UserHardDeleteByEmailService.java @@ -0,0 +1,58 @@ +package com.ftm.server.application.service.user; + +import com.ftm.server.application.command.user.*; +import com.ftm.server.application.port.in.user.UserHardDeleteByEmailUseCase; +import com.ftm.server.application.port.out.persistence.user.*; +import com.ftm.server.application.port.out.s3.S3ImageDeletePort; +import com.ftm.server.application.port.out.transcation.AfterCommitExecutorPort; +import com.ftm.server.application.query.FindByEmailQuery; +import com.ftm.server.domain.entity.User; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserHardDeleteByEmailService implements UserHardDeleteByEmailUseCase { + + private final LoadUserPort loadUserPort; + + private final DeleteBookmarkPort deleteBookmarkPort; + private final DeleteGroomingTestResultPort deleteGroomingTestResultPort; + private final DeleteUserImagePort deleteUserImagePort; + private final DeleteUserPort deleteUserPort; + + private final AfterCommitExecutorPort afterCommitExecutorPort; + private final S3ImageDeletePort s3ImageDeletePort; + + @Override + public void execute(DeleteUserByEmailCommand command) { + // 삭제 대상 user 조회 + Optional deletedUser = + loadUserPort.loadDeletedUserByEmail(FindByEmailQuery.of(command.getEmail())); + + if (deletedUser.isEmpty()) { + return; + } + + List userId = List.of(deletedUser.get().getId()); + + // user 관련 엔티티 모두 삭제 + // 1. 북마크 삭제 + deleteBookmarkPort.deleteBookmarkByUserList(DeleteBookmarkByUserIdCommand.of(userId)); + // 2. 그루밍 결과 삭제 + deleteGroomingTestResultPort.deleteGroomingTestResultByUserList( + DeleteGroomingTestResultByUserIdCommand.of(userId)); + // 3. user 이미지 삭제 + List imageKeyList = + deleteUserImagePort.deleteUserImageByUserList( + DeleteUserImageByUserIdCommand.of(userId)); + afterCommitExecutorPort.doAfterCommit( + () -> + s3ImageDeletePort.deleteImages( + imageKeyList)); // transaction commit 이후에 s3에 이미지 삭제 요청 + // 4. user 삭제 + deleteUserPort.deleteAllUserByIdList(DeleteAllUserByIdListCommand.of(userId)); + } +} diff --git a/src/main/java/com/ftm/server/application/vo/user/EmailCodeVerificationVo.java b/src/main/java/com/ftm/server/application/vo/user/EmailCodeVerificationVo.java index 022e946..d7988fd 100644 --- a/src/main/java/com/ftm/server/application/vo/user/EmailCodeVerificationVo.java +++ b/src/main/java/com/ftm/server/application/vo/user/EmailCodeVerificationVo.java @@ -5,8 +5,13 @@ @Data public class EmailCodeVerificationVo { private final Boolean isVerified; + private final Boolean isRecoverable; // 계정 복구 가능 여부 (soft delete 상태인지) public static EmailCodeVerificationVo of(Boolean isVerified) { - return new EmailCodeVerificationVo(isVerified); + return new EmailCodeVerificationVo(isVerified, false); + } + + public static EmailCodeVerificationVo of(Boolean isVerified, Boolean isRecoverable) { + return new EmailCodeVerificationVo(isVerified, isRecoverable); } } diff --git a/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java b/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java index d8a08a2..b81fc10 100644 --- a/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java +++ b/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java @@ -1,6 +1,7 @@ package com.ftm.server.user; import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; @@ -11,7 +12,11 @@ import com.ftm.server.adapter.in.web.user.dto.request.EmailCodeVerificationRequest; import com.ftm.server.application.command.user.EmailVerificationLogCreationCommand; import com.ftm.server.application.port.out.persistence.user.SaveEmailVerificationLogPort; +import com.ftm.server.application.port.out.persistence.user.SaveUserPort; +import com.ftm.server.application.port.out.smtp.MailSenderPort; +import com.ftm.server.domain.entity.User; import jakarta.transaction.Transactional; +import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -20,11 +25,15 @@ import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.ResultActions; public class EmailCodeVerificationTest extends BaseTest { @Autowired private SaveEmailVerificationLogPort saveEmailVerificationLogPort; + @Autowired private SaveUserPort saveUserPort; + + @MockitoBean private MailSenderPort mailSenderPort; private final List requestFieldDescriptors = List.of( @@ -39,7 +48,10 @@ public class EmailCodeVerificationTest extends BaseTest { fieldWithPath("data").type(JsonFieldType.OBJECT).optional().description("data"), fieldWithPath("data.isVerified") .type(JsonFieldType.BOOLEAN) - .description("검증 성공 여부")); + .description("검증 성공 여부"), + fieldWithPath("data.isRecoverable") + .type(JsonFieldType.BOOLEAN) + .description("계정 복구 가능 여부 : true 일 경우 계정 복구 요청 가능")); private ResultActions getResultActions(EmailCodeVerificationRequest request) throws Exception { return mockMvc.perform( // api 실행 @@ -66,6 +78,14 @@ private RestDocumentationResultHandler getDocument(Integer identifier) { .build())); } + protected User createSoftDeletedUser(String email, String password) { + User user = createTestUser(email, password); + user.updateIsDeleted(true); + user.updateDeletedAt(LocalDateTime.now()); + saveUserPort.saveUser(user); + return user; + } + @Test @Transactional void 이메일_인증코드_검증_성공() throws Exception { @@ -75,6 +95,8 @@ private RestDocumentationResultHandler getDocument(Integer identifier) { // given saveEmailVerificationLogPort.saveEmailVerificationLogs( EmailVerificationLogCreationCommand.of(email, code)); + // stub 객체 생성 + doNothing().when(mailSenderPort).sendEmail(email, code); // when ResultActions resultActions =