From 3d5c260d619506dd706b3b3fa3c63bd7509ba9ab Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:35:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=EB=8F=99=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EC=9E=A5=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84=20(#463)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add: BaseTimeEntity 추가 * feat: 공동모임장인 모임도 조회할 수 있도록 구현 * fix: co_leader로 수정 * test: test data 추가 * refactor: 내가 신청한 모임 정렬 조건 추가 * test: Mock 테스트 삭제 - 이제 해당 케이스들이 통합테스트로 커버됨. * test: 내가 만든 모임 조회, 내가 신청한 모임 조회 테스트 코드 작성 * refactor: 1. 모임장, 공동모임장의 경우 검증 로직 추가 2. LocalDateTime.now 부분 수정 * feat: 모임장, 공동모임장의 경우 검증 로직 구현 * test: 1. 모임장, 공동 모임장인 경우 모임 신청 테스트 코드 작성 2. 시간 수정으로 인한 일부 테스트 코드 수정 * test: 시간 수정으로 인한 일부 테스트 코드 수정 * fix: clearAutomatically = true 추가 * fix: User -> userId 변경 * test: '모임 수정', '모임 삭제', '모임 지원자 상태 변경'에 공동모임장 관련 테스트 코드 작성 * test: 테스트 시퀀스 및 데이터 일부 수정 --- .../main/entity/apply/ApplyRepository.java | 78 ++-- .../crew/main/entity/meeting/CoLeader.java | 3 +- .../entity/meeting/CoLeaderRepository.java | 3 + .../crew/main/entity/meeting/CoLeaders.java | 12 +- .../crew/main/entity/meeting/Meeting.java | 6 + .../entity/meeting/MeetingRepository.java | 12 +- .../main/global/exception/ErrorStatus.java | 2 + .../v2/service/MeetingV2ServiceImpl.java | 10 +- ...gV2GetCreatedMeetingByUserResponseDto.java | 4 +- .../user/v2/service/UserV2ServiceImpl.java | 30 +- main/src/main/resources/schema.sql | 6 +- .../v2/service/MeetingV2MockServiceTest.java | 222 ----------- .../v2/service/MeetingV2ServiceTest.java | 225 ++++++++++- .../crew/main/user/v2/UserServiceTest.java | 348 +++++++++++++----- .../test/resources/sql/delete-all-data.sql | 20 +- .../sql/meeting-service-sequence-restart.sql | 1 + .../sql/meeting-service-test-data.sql | 12 +- .../resources/sql/user-service-test-data.sql | 58 +++ 18 files changed, 650 insertions(+), 402 deletions(-) delete mode 100644 main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2MockServiceTest.java create mode 100644 main/src/test/resources/sql/meeting-service-sequence-restart.sql create mode 100644 main/src/test/resources/sql/user-service-test-data.sql diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index a480a5bd..06350b91 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -14,41 +14,47 @@ public interface ApplyRepository extends JpaRepository, ApplySearchRepository { - @Query("select a from Apply a join fetch a.meeting m where a.userId = :userId and a.status = :statusValue") - List findAllByUserIdAndStatus(@Param("userId") Integer userId, - @Param("statusValue") EnApplyStatus statusValue); - - @Query("select a from Apply a join fetch a.meeting m join fetch m.user u where a.userId = :userId") - List findAllByUserId(@Param("userId") Integer userId); - - @Query("select a " - + "from Apply a " - + "join fetch a.user u " - + "where a.meetingId = :meetingId " - + "and a.status in :statuses order by :order") - List findAllByMeetingIdWithUser(@Param("meetingId") Integer meetingId, @Param("statuses") List statuses, @Param("order") String order); - - List findAllByMeetingIdAndStatus(Integer meetingId, EnApplyStatus statusValue); - - List findAllByMeetingId(Integer meetingId); - - List findAllByMeetingIdIn(List meetingIds); - - boolean existsByMeetingIdAndUserId(Integer meetingId, Integer userId); - - @Transactional - @Modifying - @Query("delete from Apply a where a.meeting.id = :meetingId and a.userId = :userId") - void deleteByMeetingIdAndUserId(@Param("meetingId") Integer meetingId, @Param("userId") Integer userId); - - default Apply findByIdOrThrow(Integer applyId) { - return findById(applyId) - .orElseThrow(() -> new BadRequestException(NOT_FOUND_APPLY.getErrorCode())); - } - - @Modifying(clearAutomatically = true) - @Transactional - @Query("DELETE FROM Apply a WHERE a.meetingId = :meetingId") - void deleteAllByMeetingIdQuery(Integer meetingId); + @Query("select a from Apply a join fetch a.meeting m where a.userId = :userId and a.status = :statusValue") + List findAllByUserIdAndStatus(@Param("userId") Integer userId, + @Param("statusValue") EnApplyStatus statusValue); + + @Query("select a " + + "from Apply a " + + "join fetch a.meeting m " + + "join fetch m.user u " + + "where a.userId = :userId " + + "ORDER BY a.id DESC ") + List findAllByUserIdOrderByIdDesc(@Param("userId") Integer userId); + + @Query("select a " + + "from Apply a " + + "join fetch a.user u " + + "where a.meetingId = :meetingId " + + "and a.status in :statuses order by :order") + List findAllByMeetingIdWithUser(@Param("meetingId") Integer meetingId, + @Param("statuses") List statuses, @Param("order") String order); + + List findAllByMeetingIdAndStatus(Integer meetingId, EnApplyStatus statusValue); + + List findAllByMeetingId(Integer meetingId); + + List findAllByMeetingIdIn(List meetingIds); + + boolean existsByMeetingIdAndUserId(Integer meetingId, Integer userId); + + @Transactional + @Modifying(clearAutomatically = true) + @Query("delete from Apply a where a.meeting.id = :meetingId and a.userId = :userId") + void deleteByMeetingIdAndUserId(@Param("meetingId") Integer meetingId, @Param("userId") Integer userId); + + default Apply findByIdOrThrow(Integer applyId) { + return findById(applyId) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_APPLY.getErrorCode())); + } + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Apply a WHERE a.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeader.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeader.java index 729b7e68..d8836a65 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeader.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeader.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.entity.meeting; +import org.sopt.makers.crew.main.entity.common.BaseTimeEntity; import org.sopt.makers.crew.main.entity.user.User; import jakarta.persistence.Entity; @@ -20,7 +21,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "co_leader") -public class CoLeader { +public class CoLeader extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaderRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaderRepository.java index 717365f8..734591b0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaderRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaderRepository.java @@ -2,6 +2,7 @@ import java.util.List; +import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; public interface CoLeaderRepository extends JpaRepository { @@ -12,4 +13,6 @@ public interface CoLeaderRepository extends JpaRepository { List findAllByMeetingIdIn(List meetingId); + List findAllByUserId(Integer userId); + } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaders.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaders.java index d88afb15..ee77312a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaders.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/CoLeaders.java @@ -1,17 +1,21 @@ package org.sopt.makers.crew.main.entity.meeting; +import static org.sopt.makers.crew.main.global.exception.ErrorStatus.*; + import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.sopt.makers.crew.main.global.exception.BadRequestException; + public class CoLeaders { /** * Key : MeetingId * Value : 해당 모임의 공동 모임장 목록 * * @implNote : List 내에 있는 CoLeader 객체는 fetch join 으로 다른 객체를 불러오지 않은 상태 - * @implNote : 해당 자료형을 사용할 때는 'hasCoLeader' 메서드 사용 적극 권장 + * @implNote : 해당 자료형을 사용할 때는 'isCoLeaderPresent' 메서드 사용 적극 권장 * * */ private final Map> coLeadersMap; @@ -21,6 +25,12 @@ public CoLeaders(List coLeaders) { .collect(Collectors.groupingBy(coLeader -> coLeader.getMeeting().getId())); } + public void validateCoLeader(Integer meetingId, Integer requestUserId) { + if (isCoLeader(meetingId, requestUserId)) { + throw new BadRequestException(CO_LEADER_CANNOT_APPLY.getErrorCode()); + } + } + public boolean isCoLeader(Integer meetingId, Integer requestUserId) { if (!isCoLeaderPresent(meetingId)) { return false; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index 8d815dda..7ecb0b5e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -218,6 +218,12 @@ public void validateMeetingCreator(Integer requestUserId) { } } + public void validateIsNotMeetingLeader(Integer requestUserId) { + if (checkMeetingLeader(requestUserId)) { + throw new BadRequestException(LEADER_CANNOT_APPLY.getErrorCode()); + } + } + public Boolean checkMeetingLeader(Integer userId) { return this.userId.equals(userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index 6e1fe392..25173c0f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -5,7 +5,6 @@ import java.util.List; import org.sopt.makers.crew.main.global.exception.BadRequestException; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -13,7 +12,16 @@ public interface MeetingRepository extends JpaRepository, Meet List findAllByUserId(Integer userId); - List findAllByUser(User user); + /** + * @implSpec : 특정 유저가 모임장이거나 공동모임장인 모임을 최근에 만들어진 순으로 조회한다. + * **/ + @Query("SELECT m " + + "FROM Meeting m " + + "JOIN fetch m.user " + + "WHERE m.user.id =:userId " + + "OR m.id IN (:coLeaderMeetingIds)" + + "ORDER BY m.id DESC ") + List findAllByUserIdOrIdInWithUser(Integer userId, List coLeaderMeetingIds); default Meeting findByIdOrThrow(Integer meetingId) { return findById(meetingId) diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java index 0bd9ddd6..645bee08 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java @@ -34,6 +34,8 @@ public enum ErrorStatus { NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), MAX_IMAGE_UPLOAD_EXCEEDED("이미지는 최대 10개까지만 업로드 가능합니다."), + LEADER_CANNOT_APPLY("모임장은 신청할 수 없습니다."), + CO_LEADER_CANNOT_APPLY("공동 모임장은 신청할 수 없습니다."), /** * 401 UNAUTHORIZED diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index ae3451c4..1ea3af6f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -218,6 +218,7 @@ public MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBod public MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); User user = userRepository.findByIdOrThrow(userId); + CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingId(meeting.getId())); List applies = applyRepository.findAllByMeetingId(meeting.getId()); @@ -226,9 +227,10 @@ public MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto re validateApplyPeriod(meeting); validateUserActivities(user); validateUserJoinableParts(user, meeting); + coLeaders.validateCoLeader(meeting.getId(), user.getId()); + meeting.validateIsNotMeetingLeader(userId); - Apply apply = applyMapper.toApplyEntity(requestBody, EnApplyType.APPLY, meeting, user, - userId); + Apply apply = applyMapper.toApplyEntity(requestBody, EnApplyType.APPLY, meeting, user, userId); Apply savedApply = applyRepository.save(apply); return MeetingV2ApplyMeetingResponseDto.of(savedApply.getId()); } @@ -449,7 +451,7 @@ private String createCsvFile(List applies) { } private Boolean checkActivityStatus(Meeting meeting) { - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = time.now(); LocalDateTime mStartDate = meeting.getMStartDate(); LocalDateTime mEndDate = meeting.getMEndDate(); return now.isEqual(mStartDate) || (now.isAfter(mStartDate) && now.isBefore(mEndDate)); @@ -494,7 +496,7 @@ private void validateUserAlreadyApplied(Integer userId, List applies) { } private void validateApplyPeriod(Meeting meeting) { - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = time.now(); if (now.isAfter(meeting.getEndDate()) || now.isBefore(meeting.getStartDate())) { throw new BadRequestException(NOT_IN_APPLY_PERIOD.getErrorCode()); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java index 5832c10c..c4c39067 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java @@ -72,9 +72,9 @@ public record MeetingV2GetCreatedMeetingByUserResponseDto( @NotNull int appliedCount ) { - public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, boolean isCoLeader, User meetingCreator, int appliedCount, + public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, boolean isCoLeader, int appliedCount, LocalDateTime now) { - MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meeting.getUser()); boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION && meeting.getCanJoinOnlyActiveGeneration(); diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index 3a54c78c..879880ae 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.sopt.makers.crew.main.entity.meeting.CoLeader; import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository; import org.sopt.makers.crew.main.entity.meeting.CoLeaders; import org.sopt.makers.crew.main.global.exception.BaseException; @@ -94,34 +95,43 @@ public UserV2GetUserOwnProfileResponseDto getUserOwnProfile(Integer userId) { return UserV2GetUserOwnProfileResponseDto.of(user); } + /** + * @implSpec : 유저가 모임장이거나 공동모임장인 모임을 모두 조회한다. + * @implNote : my 의미 == 내가 모임장이거나 공동모임장인 경우 + * + * **/ @Override public UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId) { - User meetingCreator = userRepository.findByIdOrThrow(userId); + List coLeaderMeetingIds = getCoLeaderMeetingIds(coLeaderRepository.findAllByUserId(userId)); - List meetings = meetingRepository.findAllByUser(meetingCreator); - List meetingIds = meetings.stream().map(Meeting::getId).toList(); - Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); - CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingIdIn(meetingIds)); + List myMeetings = meetingRepository.findAllByUserIdOrIdInWithUser(userId, coLeaderMeetingIds); + List myMeetingIds = myMeetings.stream().map(Meeting::getId).toList(); + Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(myMeetingIds)); + CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingIdIn(myMeetingIds)); - List meetingByUserDtos = meetings.stream() + List meetingByUserDtos = myMeetings.stream() .map(meeting -> MeetingV2GetCreatedMeetingByUserResponseDto.of(meeting, - coLeaders.isCoLeader(meeting.getId(), userId), meetingCreator, - applies.getApprovedCount(meeting.getId()), time.now())) + coLeaders.isCoLeader(meeting.getId(), userId), applies.getApprovedCount(meeting.getId()), time.now())) .toList(); return UserV2GetCreatedMeetingByUserResponseDto.of(meetingByUserDtos); } + private List getCoLeaderMeetingIds(List coLeaders) { + return coLeaders.stream() + .map(coLeader -> coLeader.getMeeting().getId()).toList(); + } + @Override public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer userId) { - List myApplies = applyRepository.findAllByUserId(userId); + List myApplies = applyRepository.findAllByUserIdOrderByIdDesc(userId); List meetingIds = myApplies.stream().map(Apply::getMeetingId).toList(); Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); List appliedMeetingByUserDtos = myApplies.stream() .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of(apply.getId(), apply.getStatus().getValue(), - MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), false, apply.getMeeting().getUser(), + MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), false, allApplies.getApprovedCount(apply.getMeetingId()), time.now()))) .toList(); diff --git a/main/src/main/resources/schema.sql b/main/src/main/resources/schema.sql index c80c5d2f..1d9f3d15 100644 --- a/main/src/main/resources/schema.sql +++ b/main/src/main/resources/schema.sql @@ -7,7 +7,7 @@ drop table if exists "notice" cascade; drop table if exists "post" cascade; drop table if exists "report" cascade; drop table if exists "user" cascade; -drop table if exists "joint_leader" cascade; +drop table if exists "co_leader" cascade; DROP TYPE IF EXISTS meeting_joinableparts_enum; @@ -72,7 +72,9 @@ create table if not exists co_leader "userId" integer not null constraint fk_user references "user" - on delete cascade + on delete cascade, + "createdTimestamp" timestamp default CURRENT_TIMESTAMP not null, + "modifiedTimestamp" timestamp default CURRENT_TIMESTAMP not null ); create table if not exists apply diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2MockServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2MockServiceTest.java deleted file mode 100644 index cccd033b..00000000 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2MockServiceTest.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.sopt.makers.crew.main.meeting.v2.service; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.sopt.makers.crew.main.global.exception.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.global.exception.ErrorStatus.NOT_IN_APPLY_PERIOD; - -import java.time.LocalDateTime; -import java.time.Month; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.sopt.makers.crew.main.global.exception.BadRequestException; -import org.sopt.makers.crew.main.entity.apply.Apply; -import org.sopt.makers.crew.main.entity.apply.ApplyRepository; -import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; -import org.sopt.makers.crew.main.entity.meeting.Meeting; -import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; -import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; -import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; -import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; -import org.sopt.makers.crew.main.entity.user.User; -import org.sopt.makers.crew.main.entity.user.UserFixture; -import org.sopt.makers.crew.main.entity.user.UserRepository; -import org.sopt.makers.crew.main.entity.user.enums.UserPart; -import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; -import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; -import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; -import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; -import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; - -@ExtendWith(MockitoExtension.class) -public class MeetingV2MockServiceTest { - @InjectMocks - private MeetingV2ServiceImpl meetingV2Service; - @Mock - private UserRepository userRepository; - @Mock - private ApplyRepository applyRepository; - @Mock - private MeetingRepository meetingRepository; - @Mock - private ApplyMapper applyMapper; - - - private List applies; - - private User ownerUser; - - private User applyUser; - - private Meeting meeting; - - @BeforeEach - void init() { - List activityVOS = new ArrayList<>(); - activityVOS.add(new UserActivityVO(UserPart.SERVER.getValue(), 33)); - - ownerUser = User.builder() - .name("송민규") - .orgId(1) - .activities(activityVOS) - .phone("010-9472-6796") - .build(); - ownerUser.setUserIdForTest(1); - - applyUser = User.builder() - .name("홍길동") - .orgId(2) - .activities(activityVOS) - .phone("010-1234-5678") - .build(); - applyUser.setUserIdForTest(2); - - ImageUrlVO imageUrlVO = new ImageUrlVO(1, "www.~~"); - - meeting = Meeting.builder() - .user(ownerUser) - .userId(ownerUser.getId()) - .title("사람 구해요") - .category(MeetingCategory.STUDY) - .imageURL(List.of(imageUrlVO)) - .startDate(LocalDateTime.of(2024, Month.MARCH, 17, 0, 0)) - .endDate(LocalDateTime.of(2024, Month.MARCH, 20, 23, 59)) - .capacity(10) - .desc("열정 많은 사람 구해요") - .processDesc("이렇게 할거에여") - .mStartDate(LocalDateTime.of(2024, Month.APRIL, 1, 0, 0)) - .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) - .leaderDesc("저는 이런 사람이에요.") - .targetDesc("이런 사람이 왔으면 좋겠어요") - .note("유의사항은 이거에요") - .isMentorNeeded(true) - .canJoinOnlyActiveGeneration(true) - .createdGeneration(33) - .targetActiveGeneration(33) - .joinableParts(MeetingJoinablePart.values()) - .build(); - - Apply apply = Apply.builder() - .meeting(meeting) - .meetingId(1) - .user(applyUser) - .userId(2) - .content("제 지원동기는요") - .build(); - - //meeting.addApply(apply); - apply.updateApplyStatus(EnApplyStatus.APPROVE); - - applies = new ArrayList<>(); - applies.add(apply); - } - - @Test - void 내모임조회_성공() { - // given - User user = UserFixture.createStaticUser(); - user.setUserIdForTest(3); - doReturn(Optional.of(user)).when(userRepository).findByOrgId(any()); - doReturn(applies).when(applyRepository).findAllByUserIdAndStatus(any(), any()); - - MeetingV2GetAllMeetingByOrgUserQueryDto dto = new MeetingV2GetAllMeetingByOrgUserQueryDto( - applyUser.getOrgId(), 1, 12); - - // when - MeetingV2GetAllMeetingByOrgUserDto myMeetings = meetingV2Service.getAllMeetingByOrgUser(dto); - - // then - Assertions.assertThat(myMeetings.getMeetings().get(0)) - .extracting("isMeetingLeader", "title", "category", "mStartDate", "mEndDate", "isActiveMeeting") - .containsExactly(false, meeting.getTitle(), meeting.getCategory().getValue(), meeting.getMStartDate(), - meeting.getMEndDate(), true); - } - - @Test - public void 모임신청시_정원이찼을때_예외발생() { - // given - applies = new ArrayList<>(); - for (int i = 0; i < meeting.getCapacity(); i++) { - User tempUser = User.builder() - .name("user" + i) - .orgId(2 + i) - .phone("010-0000-000" + i) - .build(); - tempUser.setUserIdForTest(3 + i); // 3부터 시작하는 userId 설정 - - Apply apply = Apply.builder() - .meeting(meeting) - .meetingId(meeting.getId()) - .user(tempUser) - .userId(tempUser.getId()) - .content("꼭 하고 싶어요" + i) - .build(); - - apply.updateApplyStatus(EnApplyStatus.APPROVE); // 승인 상태로 변경 - applies.add(apply); - } - - MeetingV2ApplyMeetingDto requestBody = new MeetingV2ApplyMeetingDto(meeting.getId(), "열심히 하겠습니다."); - - doReturn(meeting).when(meetingRepository).findByIdOrThrow(requestBody.getMeetingId()); - doReturn(applyUser).when(userRepository).findByIdOrThrow(applyUser.getId()); - doReturn(applies).when(applyRepository).findAllByMeetingId(any()); - - // when & then - BadRequestException exception = assertThrows(BadRequestException.class, () -> { - meetingV2Service.applyMeeting(requestBody, applyUser.getId()); - }); - - assertEquals(FULL_MEETING_CAPACITY.getErrorCode(), exception.getMessage()); - } - - @Test - public void 모집시작시간이전_모임지원시_예외발생() { - // given - LocalDateTime oneMinuteAfterNow = LocalDateTime.now().plusMinutes(1); - - meeting = Meeting.builder() - .user(ownerUser) - .userId(ownerUser.getId()) - .title("사람 구해요") - .category(MeetingCategory.STUDY) - .startDate(oneMinuteAfterNow) // 모집 시작 시간을 현재 시간으로부터 1분 후로 설정 - .endDate(oneMinuteAfterNow.plusDays(1)) - .capacity(10) - .desc("열정 많은 사람 구해요") - .processDesc("이렇게 할거에여") - .mStartDate(LocalDateTime.of(2028, Month.APRIL, 20, 0, 0)) - .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) - .leaderDesc("저는 이런 사람이에요.") - .targetDesc("이런 사람이 왔으면 좋겠어요") - .note("유의사항은 이거에요") - .isMentorNeeded(true) - .canJoinOnlyActiveGeneration(true) - .createdGeneration(33) - .targetActiveGeneration(33) - .joinableParts(MeetingJoinablePart.values()) - .build(); - - MeetingV2ApplyMeetingDto requestBody = new MeetingV2ApplyMeetingDto(meeting.getId(), "열심히 하겠습니다."); - - doReturn(meeting).when(meetingRepository).findByIdOrThrow(requestBody.getMeetingId()); - doReturn(applyUser).when(userRepository).findByIdOrThrow(applyUser.getId()); - - // when & then - BadRequestException exception = assertThrows(BadRequestException.class, () -> { - meetingV2Service.applyMeeting(requestBody, applyUser.getId()); - }); - - assertEquals(NOT_IN_APPLY_PERIOD.getErrorCode(), exception.getMessage()); - } -} diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java index 0e55f470..78ffd8a6 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java @@ -33,11 +33,13 @@ import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; import org.sopt.makers.crew.main.global.dto.MeetingCreatorDto; import org.sopt.makers.crew.main.global.dto.MeetingResponseDto; +import org.sopt.makers.crew.main.global.exception.ForbiddenException; import org.sopt.makers.crew.main.global.exception.NotFoundException; import org.sopt.makers.crew.main.global.exception.BadRequestException; import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; @@ -444,8 +446,8 @@ void isImageFileEmpty_createMeeting_exception() { @Nested @SqlGroup({ @Sql(value = "/sql/meeting-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/meeting-service-sequence-restart.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) - }) class 모임_전체_조회 { @Test @@ -1310,8 +1312,8 @@ void applyMeeting_ShouldSaveSuccessfully() { .title("모임 지원 테스트") .category(MeetingCategory.STUDY) .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) - .startDate(LocalDateTime.of(2024, 10, 6, 0, 0, 0)) - .endDate(LocalDateTime.of(2029, 10, 7, 23, 59, 59)) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) + .endDate(LocalDateTime.of(2029, 4, 27, 23, 59, 59)) .capacity(20) .desc("모임 지원 테스트입니다.") .processDesc("테스트 진행 방식입니다.") @@ -1368,7 +1370,7 @@ void applyMeeting_ShouldFailWhenCapacityExceeded() { .title("모임 지원 테스트") .category(MeetingCategory.STUDY) .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) - .startDate(LocalDateTime.of(2024, 10, 6, 0, 0, 0)) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) .endDate(LocalDateTime.of(2029, 10, 7, 23, 59, 59)) .capacity(1) .desc("모임 지원 테스트입니다.") @@ -1440,7 +1442,7 @@ void applyMeeting_Fail_WhenAlreadyApplied() { .title("모임 지원 테스트") .category(MeetingCategory.STUDY) .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) - .startDate(LocalDateTime.of(2024, 10, 6, 0, 0, 0)) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) .endDate(LocalDateTime.of(2029, 10, 7, 23, 59, 59)) .capacity(50) .desc("모임 지원 테스트입니다.") @@ -1505,7 +1507,7 @@ void applyMeeting_Fail_WhenOutsideApplicationPeriod() { .title("모임 지원 테스트") .category(MeetingCategory.STUDY) .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) - .startDate(LocalDateTime.of(2030, 10, 7, 0, 0, 0)) + .startDate(LocalDateTime.of(2030, 4, 7, 0, 0, 0)) .endDate(LocalDateTime.of(2030, 10, 7, 23, 59, 59)) .capacity(50) .desc("모임 지원 테스트입니다.") @@ -1537,8 +1539,219 @@ void applyMeeting_Fail_WhenOutsideApplicationPeriod() { .isInstanceOf(BadRequestException.class) .hasMessageContaining("지원 기간이 아닙니다."); } + + @Test + @DisplayName("공동 모임장은 지원할 수 없다.") + void applyMeeting_Fail_WhenIsCoLeader() { + // given + User leader = User.builder() + .name("모임장") + .orgId(1) + .activities(List.of(new UserActivityVO("iOS", 35))) + .profileImage("testProfileImage.jpg") + .phone("010-1234-5678") + .build(); + + User coLeaderUser1 = User.builder() + .name("공동 모임장1") + .orgId(2) + .activities(List.of(new UserActivityVO("서버", 35))) + .profileImage("testProfileImage.jpg") + .phone("010-2222-2222") + .build(); + + User coLeaderUser2 = User.builder() + .name("공동 모임장2") + .orgId(3) + .activities(List.of(new UserActivityVO("기획", 33))) + .profileImage("testProfileImage.jpg") + .phone("010-3333-3333") + .build(); + User savedLeader = userRepository.save(leader); + User savedCoLeader1 = userRepository.save(coLeaderUser1); + User savedCoLeader2 = userRepository.save(coLeaderUser2); + + Meeting meeting = Meeting.builder() + .user(savedLeader) + .userId(savedLeader.getId()) + .title("모임 지원 테스트") + .category(MeetingCategory.STUDY) + .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) + .endDate(LocalDateTime.of(2029, 4, 27, 23, 59, 59)) + .capacity(20) + .desc("모임 지원 테스트입니다.") + .processDesc("테스트 진행 방식입니다.") + .mStartDate(LocalDateTime.of(2024, 5, 24, 0, 0, 0)) + .mEndDate(LocalDateTime.of(2024, 5, 30, 23, 59, 59)) + .leaderDesc("모임 리더 설명입니다.") + .note("유의사항입니다.") + .isMentorNeeded(false) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(35) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + meetingRepository.save(meeting); + + CoLeader coLeader1 = CoLeader.builder() + .meeting(meeting) + .user(savedCoLeader1) + .build(); + + CoLeader coLeader2 = CoLeader.builder() + .meeting(meeting) + .user(savedCoLeader2) + .build(); + coLeaderRepository.saveAll(List.of(coLeader1, coLeader2)); + + // when & then + MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); + Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, savedCoLeader1.getId())) + .isInstanceOf(BadRequestException.class) + .hasMessage("공동 모임장은 신청할 수 없습니다."); + } + + @Test + @DisplayName("모임장은 지원할 수 없다.") + void applyMeeting_Fail_WhenIsLeader() { + // given + User leader = User.builder() + .name("모임장") + .orgId(1) + .activities(List.of(new UserActivityVO("iOS", 35))) + .profileImage("testProfileImage.jpg") + .phone("010-1234-5678") + .build(); + + userRepository.save(leader); + + Meeting meeting = Meeting.builder() + .user(leader) + .userId(leader.getId()) + .title("모임 지원 테스트") + .category(MeetingCategory.STUDY) + .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) + .endDate(LocalDateTime.of(2029, 4, 27, 23, 59, 59)) + .capacity(20) + .desc("모임 지원 테스트입니다.") + .processDesc("테스트 진행 방식입니다.") + .mStartDate(LocalDateTime.of(2024, 5, 24, 0, 0, 0)) + .mEndDate(LocalDateTime.of(2024, 5, 30, 23, 59, 59)) + .leaderDesc("모임 리더 설명입니다.") + .note("유의사항입니다.") + .isMentorNeeded(false) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(35) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + meetingRepository.save(meeting); + + // when & then + MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); + Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, leader.getId())) + .isInstanceOf(BadRequestException.class) + .hasMessage("모임장은 신청할 수 없습니다."); + } + } + + @Nested + @SqlGroup({ + @Sql(value = "/sql/meeting-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + + }) + class 모임_지원자_상태_변경 { + @Test + @DisplayName("공동 모임장은 모임 지원자 상태 변경을 할 수 없다.") + void updateApplyStatus_Fail_isCoLeader() { + // given + Integer coLeaderId = 5; + ApplyV2UpdateStatusBodyDto dto = new ApplyV2UpdateStatusBodyDto(1, 1); + + // when, then + Assertions.assertThatThrownBy( + () -> meetingV2Service.updateApplyStatus(1, dto, coLeaderId)) + .isInstanceOf(ForbiddenException.class) + .hasMessage(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } + + @Nested + @SqlGroup({ + @Sql(value = "/sql/meeting-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + + }) + class 모임_수정 { + @Test + @DisplayName("공동 모임장은 모임을 수정할 수 없다.") + void modifyMeeting_Fail_isCoLeader() { + // given + Integer coLeaderId = 5; + + // 모임 이미지 리스트 + List files = Arrays.asList( + "https://example.com/image1.jpg" + ); + + // 대상 파트 목록 + MeetingJoinablePart[] joinableParts = { + MeetingJoinablePart.SERVER, + MeetingJoinablePart.IOS + }; + + // DTO 생성 + MeetingV2CreateMeetingBodyDto dto = new MeetingV2CreateMeetingBodyDto( + "알고보면 쓸데있는 개발 프로세스", // title + files, // files (모임 이미지 리스트) + "스터디", // category + "2024.10.01", // startDate (모집 시작 날짜) + "2024.10.15", // endDate (모집 끝 날짜) + 10, // capacity (모집 인원) + "백엔드 개발에 관심 있는 사람들을 위한 스터디입니다.", // desc (모집 정보) + "매주 온라인으로 진행되며, 발표와 토론이 포함됩니다.", // processDesc (진행 방식 소개) + "2024.10.16", // mStartDate (모임 활동 시작 날짜) + "2024.12.30", // mEndDate (모임 활동 종료 날짜) + "5년차 백엔드 개발자입니다.", // leaderDesc (개설자 소개) + "준비물은 노트북과 열정입니다.", // note (유의할 사항) + false, // isMentorNeeded (멘토 필요 여부) + true, // canJoinOnlyActiveGeneration (활동기수만 지원 가능 여부) + joinableParts, // joinableParts (대상 파트 목록) + null + ); + // when, then + Assertions.assertThatThrownBy( + () -> meetingV2Service.updateMeeting(1, dto, coLeaderId)) + .isInstanceOf(ForbiddenException.class) + .hasMessage(FORBIDDEN_EXCEPTION.getErrorCode()); + } } + @Nested + @SqlGroup({ + @Sql(value = "/sql/meeting-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + + }) + class 모임_삭제 { + @Test + @DisplayName("공동 모임장은 모임을 삭제할 수 없다.") + void deleteMeeting_Fail_isCoLeader() { + // given + Integer coLeaderId = 5; + + // when, then + Assertions.assertThatThrownBy( + () -> meetingV2Service.deleteMeeting(1, coLeaderId)) + .isInstanceOf(ForbiddenException.class) + .hasMessage(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } private Meeting createMeetingFixture(Integer index, User user) { diff --git a/main/src/test/java/org/sopt/makers/crew/main/user/v2/UserServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/user/v2/UserServiceTest.java index f621c756..c9a9596e 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/user/v2/UserServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/user/v2/UserServiceTest.java @@ -1,118 +1,264 @@ package org.sopt.makers.crew.main.user.v2; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.*; +import static org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart.*; +import java.time.LocalDateTime; import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.global.annotation.IntegratedTest; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; -import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.ApplyV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.MeetingV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlGroup; @IntegratedTest public class UserServiceTest { - @Autowired - private UserV2Service userV2Service; - - @Autowired - private UserRepository userRepository; - - - @Test - void 멘션_사용자_조회(){ - // given - User user1 = User.builder() - .name("홍길동") - .orgId(1) - .activities(List.of(new UserActivityVO("서버", 33), new UserActivityVO("iOS", 34))) - .profileImage("image-url1") - .phone("010-1234-5678") - .build(); - User user2 = User.builder() - .name("김철수") - .orgId(2) - .activities(List.of(new UserActivityVO("iOS", 30), new UserActivityVO("안드로이드", 33))) - .profileImage("image-url2") - .phone("010-1111-2222") - .build(); - userRepository.saveAll(List.of(user1, user2)); - - // when - List allMentionUsers = userV2Service.getAllMentionUser(); - - // then - assertThat(allMentionUsers).hasSize(2); - assertThat(allMentionUsers.get(0)) - .extracting( "userName", "recentPart", "recentGeneration", "profileImageUrl") - .containsExactly( "홍길동", "iOS", 34, "image-url1"); - assertThat(allMentionUsers.get(1)) - .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") - .containsExactly("김철수", "안드로이드", 33, "image-url2"); - - } - - @Test - void 멘션_사용자_조회시_db에_null_저장된_경우(){ - // given - User user1 = User.builder() - .name("홍길동") - .orgId(1) - .activities(null) - .profileImage("image-url1") - .phone("010-1234-5678") - .build(); - User user2 = User.builder() - .name("김철수") - .orgId(2) - .activities(List.of(new UserActivityVO("iOS", 30), new UserActivityVO("안드로이드", 33))) - .profileImage("image-url2") - .phone("010-1111-2222") - .build(); - userRepository.saveAll(List.of(user1, user2)); - - // when - List allMentionUsers = userV2Service.getAllMentionUser(); - - // then - assertThat(allMentionUsers).hasSize(1); - assertThat(allMentionUsers.get(0)) - .extracting( "userName", "recentPart", "recentGeneration", "profileImageUrl") - .containsExactly("김철수", "안드로이드", 33, "image-url2"); - } - - @Test - void 멘션_사용자_조회시_db에_올바르지_않은_데이터_저장된_경우(){ - // given - User user1 = User.builder() - .name("홍길동") - .orgId(1) - .activities(List.of(new UserActivityVO("서버", 33), new UserActivityVO("iOS", 34))) - .profileImage("image-url1") - .phone("010-1234-5678") - .build(); - User user2 = User.builder() - .name("김철수") - .orgId(2) - .activities(List.of(new UserActivityVO(null, 30), new UserActivityVO("", 34))) - .profileImage("image-url2") - .phone("010-1111-2222") - .build(); - userRepository.saveAll(List.of(user1, user2)); - - // when - List allMentionUsers = userV2Service.getAllMentionUser(); - - // then - assertThat(allMentionUsers).hasSize(2); - assertThat(allMentionUsers.get(0)) - .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") - .containsExactly("홍길동", "iOS", 34, "image-url1"); - assertThat(allMentionUsers.get(1)) - .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") - .containsExactly("김철수", "", 34, "image-url2"); - } + @Autowired + private UserV2Service userV2Service; + + @Autowired + private UserRepository userRepository; + + @Nested + class 전체_사용자_조회 { + @Test + void 멘션_사용자_조회() { + // given + User user1 = User.builder() + .name("홍길동") + .orgId(1) + .activities(List.of(new UserActivityVO("서버", 33), new UserActivityVO("iOS", 34))) + .profileImage("image-url1") + .phone("010-1234-5678") + .build(); + User user2 = User.builder() + .name("김철수") + .orgId(2) + .activities(List.of(new UserActivityVO("iOS", 30), new UserActivityVO("안드로이드", 33))) + .profileImage("image-url2") + .phone("010-1111-2222") + .build(); + userRepository.saveAll(List.of(user1, user2)); + + // when + List allMentionUsers = userV2Service.getAllUser(); + + // then + assertThat(allMentionUsers).hasSize(2); + assertThat(allMentionUsers.get(0)) + .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") + .containsExactly("홍길동", "iOS", 34, "image-url1"); + assertThat(allMentionUsers.get(1)) + .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") + .containsExactly("김철수", "안드로이드", 33, "image-url2"); + + } + + @Test + void 멘션_사용자_조회시_db에_null_저장된_경우() { + // given + User user1 = User.builder() + .name("홍길동") + .orgId(1) + .activities(null) + .profileImage("image-url1") + .phone("010-1234-5678") + .build(); + User user2 = User.builder() + .name("김철수") + .orgId(2) + .activities(List.of(new UserActivityVO("iOS", 30), new UserActivityVO("안드로이드", 33))) + .profileImage("image-url2") + .phone("010-1111-2222") + .build(); + userRepository.saveAll(List.of(user1, user2)); + + // when + List allMentionUsers = userV2Service.getAllUser(); + + // then + assertThat(allMentionUsers).hasSize(1); + assertThat(allMentionUsers.get(0)) + .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") + .containsExactly("김철수", "안드로이드", 33, "image-url2"); + } + + @Test + void 멘션_사용자_조회시_db에_올바르지_않은_데이터_저장된_경우() { + // given + User user1 = User.builder() + .name("홍길동") + .orgId(1) + .activities(List.of(new UserActivityVO("서버", 33), new UserActivityVO("iOS", 34))) + .profileImage("image-url1") + .phone("010-1234-5678") + .build(); + User user2 = User.builder() + .name("김철수") + .orgId(2) + .activities(List.of(new UserActivityVO(null, 30), new UserActivityVO("", 34))) + .profileImage("image-url2") + .phone("010-1111-2222") + .build(); + userRepository.saveAll(List.of(user1, user2)); + + // when + List allMentionUsers = userV2Service.getAllUser(); + + // then + assertThat(allMentionUsers).hasSize(2); + assertThat(allMentionUsers.get(0)) + .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") + .containsExactly("홍길동", "iOS", 34, "image-url1"); + assertThat(allMentionUsers.get(1)) + .extracting("userName", "recentPart", "recentGeneration", "profileImageUrl") + .containsExactly("김철수", "", 34, "image-url2"); + } + } + + @Nested + @SqlGroup({ + @Sql(value = "/sql/user-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + + }) + class 내가_신청한_모임_조회 { + + @Test + @DisplayName("내가 신청한 모임 목록을 가장 최근에 신청한 순으로 조회할 수 있다.") + void getAppliedMeetingByUser_userApplyMeetings() { + // given + Integer userId = 2; + + // when + UserV2GetAppliedMeetingByUserResponseDto responseDto = userV2Service.getAppliedMeetingByUser( + userId); + + // then + Assertions.assertThat(responseDto.apply()).hasSize(4); + + List applyResponseDto = responseDto.apply(); + + Assertions.assertThat(applyResponseDto) + .extracting("status", "meeting.title", "meeting.status") + .containsExactly( + tuple(1, "세미나 구합니다 - 신청후", 2), + tuple(0, "스터디 구합니다 - 신청후", 2), + tuple(0, "스터디 구합니다 - 신청전", 0), + tuple(0, "스터디 구합니다1", 1) + ); + } + + @Test + @DisplayName("내가 신청한 모임 목록에서 모임장이나 공동모임장이 될 수 없다.") + void getAppliedMeetingByUser_notLeaderOrCoLeader() { + // given + Integer userId = 2; + + // when + UserV2GetAppliedMeetingByUserResponseDto responseDto = userV2Service.getAppliedMeetingByUser( + userId); + + // then + Assertions.assertThat(responseDto.apply()).hasSize(4); + + List applyResponseDto = responseDto.apply(); + + Assertions.assertThat(applyResponseDto) + .extracting("status", "meeting.isCoLeader", "meeting.title", "meeting.status") + .containsExactly( + tuple(1, false, "세미나 구합니다 - 신청후", 2), + tuple(0, false, "스터디 구합니다 - 신청후", 2), + tuple(0, false, "스터디 구합니다 - 신청전", 0), + tuple(0, false, "스터디 구합니다1", 1) + ); + } + } + + @Nested + @SqlGroup({ + @Sql(value = "/sql/user-service-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + + }) + class 내가_만든_모임_조회 { + + @Test + @DisplayName("내가 만든 모임 목록을 조회할 수 있다.") + void getCreatedMeetingByUser_meetingMadeByUser() { + // given + Integer userId = 4; + + // when + UserV2GetCreatedMeetingByUserResponseDto responseDto = userV2Service.getCreatedMeetingByUser(userId); + + // then + Assertions.assertThat(responseDto.meetings()).hasSize(3); + + List meetings = responseDto.meetings(); + + Assertions.assertThat(meetings) + .extracting("title", "targetActiveGeneration", "joinableParts", "category", + "canJoinOnlyActiveGeneration", "status", "isCoLeader", "isMentorNeeded", + "mStartDate", "mEndDate", "capacity", + "appliedCount", "user.orgId") + .containsExactly( + tuple("세미나 구합니다 - 신청후", null, new MeetingJoinablePart[]{WEB, IOS}, "세미나", + false, 2, false, false, + LocalDateTime.of(2024, 5, 29, 0, 0, 0), LocalDateTime.of(2024, 5, 31, 23, 59, 59), + 13, 1, 1004), + tuple("스터디 구합니다 - 신청후", null, new MeetingJoinablePart[]{PM, SERVER}, "스터디", + false, 2, false, false, + LocalDateTime.of(2024, 5, 29, 0, 0, 0), LocalDateTime.of(2024, 5, 31, 23, 59, 59), + 10, 0, 1004), + tuple("스터디 구합니다 - 신청전", null, new MeetingJoinablePart[]{PM, SERVER}, "스터디", + false, 0, false, false, + LocalDateTime.of(2024, 5, 29, 0, 0, 0), LocalDateTime.of(2024, 5, 31, 23, 59, 59), + 10, 0, 1004) + ); + } + + @Test + @DisplayName("공동 모임장인 모임도 내가 만든 모임 목록에 조회된다.") + void getCreatedMeetingByUser_coLeaderMeeting() { + // given + Integer userId = 5; + + // when + UserV2GetCreatedMeetingByUserResponseDto responseDto = userV2Service.getCreatedMeetingByUser(userId); + + // then + Assertions.assertThat(responseDto.meetings()).hasSize(1); + + List meetings = responseDto.meetings(); + + Assertions.assertThat(meetings) + .extracting("title", "targetActiveGeneration", "joinableParts", "category", + "canJoinOnlyActiveGeneration", "status", "isCoLeader", "isMentorNeeded", + "mStartDate", "mEndDate", "capacity", + "appliedCount", "user.orgId") + .containsExactly( + tuple("세미나 구합니다 - 신청후", null, new MeetingJoinablePart[]{WEB, IOS}, "세미나", + false, 2, true, false, + LocalDateTime.of(2024, 5, 29, 0, 0, 0), LocalDateTime.of(2024, 5, 31, 23, 59, 59), + 13, 1, 1004) + ); + } + } } diff --git a/main/src/test/resources/sql/delete-all-data.sql b/main/src/test/resources/sql/delete-all-data.sql index 493bd583..c685999e 100644 --- a/main/src/test/resources/sql/delete-all-data.sql +++ b/main/src/test/resources/sql/delete-all-data.sql @@ -1,12 +1,3 @@ --- 테이블의 데이터 삭제 -DELETE FROM "apply"; -DELETE FROM "comment"; -DELETE FROM "like"; -DELETE FROM "post"; -DELETE FROM "meeting"; -DELETE FROM "notice"; -DELETE FROM "user"; - -- 시퀀스 초기화 -- apply 테이블의 시퀀스 초기화 ALTER SEQUENCE "apply_id_seq" RESTART WITH 1; @@ -27,4 +18,13 @@ ALTER SEQUENCE "notice_id_seq" RESTART WITH 1; ALTER SEQUENCE "post_id_seq" RESTART WITH 1; -- user 테이블의 시퀀스 초기화 -ALTER SEQUENCE "user_id_seq" RESTART WITH 1; \ No newline at end of file +ALTER SEQUENCE "user_id_seq" RESTART WITH 1; + +-- 테이블의 데이터 삭제 +DELETE FROM "apply"; +DELETE FROM "comment"; +DELETE FROM "like"; +DELETE FROM "post"; +DELETE FROM "meeting"; +DELETE FROM "notice"; +DELETE FROM "user"; \ No newline at end of file diff --git a/main/src/test/resources/sql/meeting-service-sequence-restart.sql b/main/src/test/resources/sql/meeting-service-sequence-restart.sql new file mode 100644 index 00000000..9e42d6d7 --- /dev/null +++ b/main/src/test/resources/sql/meeting-service-sequence-restart.sql @@ -0,0 +1 @@ +ALTER SEQUENCE "meeting_id_seq" RESTART WITH 6; \ No newline at end of file diff --git a/main/src/test/resources/sql/meeting-service-test-data.sql b/main/src/test/resources/sql/meeting-service-test-data.sql index a7c5548a..283ea460 100644 --- a/main/src/test/resources/sql/meeting-service-test-data.sql +++ b/main/src/test/resources/sql/meeting-service-test-data.sql @@ -15,32 +15,32 @@ VALUES (1, '모임개설자', 1001, '[{"part": "iOS", "generation": 35}, {"part": "안드로이드", "generation": 34}]', 'profile5.jpg', '010-6666-6666'); -INSERT INTO meeting ("userId", title, category, "imageURL", "startDate", "endDate", capacity, +INSERT INTO meeting ("id", "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", "targetActiveGeneration", "joinableParts") -VALUES (1, '스터디 구합니다1', '행사', +VALUES (1, 1, '스터디 구합니다1', '행사', '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', '2024-04-24 00:00:00.000000', '2024-05-24 23:59:59.000000', 10, '스터디 설명입니다.', '스터디 진행방식입니다.', '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', '스터디장 설명입니다.', '시간지키세요.', true, true, 35, 35, '{PM,SERVER}'), - (5, '스터디 구합니다 - 신청전', '스터디', + (2, 5, '스터디 구합니다 - 신청전', '스터디', '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', '2024-04-25 00:00:00.000000', '2024-05-24 23:59:59.000000', 10, '스터디 설명입니다.', '스터디 진행방식입니다.', '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', null, null, false, false, 34, null, '{PM,SERVER}'), - (5, '스터디 구합니다 - 신청후', '스터디', + (3, 5, '스터디 구합니다 - 신청후', '스터디', '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', '2024-04-22 00:00:00.000000', '2024-04-22 23:59:59.000000', 10, '스터디 설명입니다.', '스터디 진행방식입니다.', '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', null, null, false, false, 34, null, '{PM,SERVER}'), - (5, '세미나 구합니다 - 신청후', '세미나', + (4, 5, '세미나 구합니다 - 신청후', '세미나', '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', '2024-04-22 00:00:00.000000', '2024-04-22 23:59:59.000000', 13, '세미나 설명입니다.', '세미나 진행방식입니다.', @@ -52,3 +52,5 @@ VALUES (0, 1, 2, '2024-05-19 00:00:00.913489', 1), (0, 1, 3, '2024-05-19 00:00:02.413489', 1), (0, 1, 4, '2024-05-19 00:00:03.413489', 0); +INSERT INTO co_leader("meetingId", "userId") +VALUES (1, 5); diff --git a/main/src/test/resources/sql/user-service-test-data.sql b/main/src/test/resources/sql/user-service-test-data.sql new file mode 100644 index 00000000..43b79d80 --- /dev/null +++ b/main/src/test/resources/sql/user-service-test-data.sql @@ -0,0 +1,58 @@ +INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) +VALUES (1, '김삼순', 1001, + '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', + 'profile1.jpg', '010-1234-5678'), + (2, '홍길동', 1002, + '[{"part": "기획", "generation": 32}, {"part": "기획", "generation": 29}, {"part": "기획", "generation": 33}, {"part": "기획", "generation": 30}]', + 'profile2.jpg', '010-1111-2222'), + (3, '김철수', 1003, + '[{"part": "웹", "generation": 34}]', + 'profile3.jpg', '010-3333-4444'), + (4, '이영지', 1004, + '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', + 'profile4.jpg', '010-5555-5555'), + (5, '김솝트', 1005, + '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', + 'profile4.jpg', '010-5555-5555'); + +INSERT INTO meeting ("userId", title, category, "imageURL", "startDate", "endDate", capacity, + "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", + note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", + "targetActiveGeneration", "joinableParts") +VALUES (1, '스터디 구합니다1', '행사', + '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', + '2024-04-24 00:00:00.000000', '2024-05-24 23:59:59.000000', 10, + '스터디 설명입니다.', '스터디 진행방식입니다.', + '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', '스터디장 설명입니다.', + '시간지키세요.', true, true, 35, 35, '{PM,SERVER}'), + + (4, '스터디 구합니다 - 신청전', '스터디', + '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', + '2024-04-25 00:00:00.000000', '2024-05-24 23:59:59.000000', 10, + '스터디 설명입니다.', '스터디 진행방식입니다.', + '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', null, + null, false, false, 34, null, '{PM,SERVER}'), + + (4, '스터디 구합니다 - 신청후', '스터디', + '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', + '2024-04-22 00:00:00.000000', '2024-04-22 23:59:59.000000', 10, + '스터디 설명입니다.', '스터디 진행방식입니다.', + '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', null, + null, false, false, 34, null, '{PM,SERVER}'), + + (4, '세미나 구합니다 - 신청후', '세미나', + '[{"id": 0, "url": "https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2024/05/19/79ba8312-0ebf-48a2-9a5e-b372fb8a9e64.png"}]', + '2024-04-22 00:00:00.000000', '2024-04-22 23:59:59.000000', 13, + '세미나 설명입니다.', '세미나 진행방식입니다.', + '2024-05-29 00:00:00.000000', '2024-05-31 23:59:59.000000', null, + null, false, false, 34, null, '{WEB, IOS}'); + +INSERT INTO apply (type, "meetingId", "userId", "appliedDate", status) +VALUES (0, 1, 2, '2024-05-19 00:00:00.913489', 0), + (0, 2, 2, '2024-05-19 00:00:02.413489', 0), + (0, 3, 2, '2024-05-19 00:00:03.413489', 0), + (0, 4, 2, '2024-05-19 00:00:03.413489', 1); + +INSERT INTO co_leader ("meetingId", "userId") +VALUES (4, 3), + (4, 5); \ No newline at end of file