From bfb8dc48e49ea90ed9e3ed7cda106a34ec79248a Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 16:37:24 +0900 Subject: [PATCH 01/17] =?UTF-8?q?:recycle:=20[STMT-166]=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=EC=83=9D=EC=84=B1=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/adapter/in/web/StudyCreateApi.java | 2 +- .../port/in/StudyCreateUseCase.java | 3 +-- .../port/in/mapper/StudyUseCaseMapper.java | 25 ++++------------- .../service/StudyCreateService.java | 15 ++++++----- .../stumeet/server/study/domain/Study.java | 27 +++++++++---------- 5 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java index 0edfc37e..a6482798 100644 --- a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java +++ b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java @@ -28,7 +28,7 @@ public ResponseEntity> create( @AuthenticationPrincipal LoginMember member, @Valid StudyCreateCommand request ) { - studyCreateUseCase.create(request, member.getMember()); + studyCreateUseCase.create(request, member.getMember().getId()); return new ResponseEntity<>( ApiResponse.success(SuccessCode.STUDY_CREATE_SUCCESS), diff --git a/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java b/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java index 6cbc068e..e73a8897 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java @@ -1,9 +1,8 @@ package com.stumeet.server.study.application.port.in; -import com.stumeet.server.member.domain.Member; import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; public interface StudyCreateUseCase { - Long create(StudyCreateCommand command, Member member); + Long create(StudyCreateCommand command, Long memberId); } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyUseCaseMapper.java b/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyUseCaseMapper.java index e3ac214e..0ebdc5c0 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyUseCaseMapper.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyUseCaseMapper.java @@ -1,7 +1,7 @@ package com.stumeet.server.study.application.port.in.mapper; -import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Component; import com.stumeet.server.study.adapter.in.web.response.StudyDetailResponse; @@ -12,21 +12,6 @@ @RequiredArgsConstructor public class StudyUseCaseMapper { - private final StudyDomainUseCaseMapper studyDomainUseCaseMapper; - private final StudyPeriodUseCaseMapper studyPeriodUseCaseMapper; - private final StudyMeetingScheduleUseCaseMapper studyMeetingScheduleUseCaseMapper; - - public Study toDomain(StudyCreateCommand command) { - return Study.create( - studyDomainUseCaseMapper.toDomain(command), - command.name(), - command.intro(), - command.rule(), - command.region(), - studyPeriodUseCaseMapper.toDomain(command), - studyMeetingScheduleUseCaseMapper.toDomain(command)); - } - public StudyDetailResponse toStudyDetailResponse(Study study) { return StudyDetailResponse.builder() .id(study.getId()) @@ -49,9 +34,9 @@ public StudyDetailResponse toStudyDetailResponse(Study study) { public StudyMemberJoinCommand toAdminStudyMemberJoinCommand(Long memberId, Long studyId) { return StudyMemberJoinCommand.builder() - .memberId(memberId) - .studyId(studyId) - .isAdmin(true) - .build(); + .memberId(memberId) + .studyId(studyId) + .isAdmin(true) + .build(); } } diff --git a/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java b/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java index 43b3efcc..babe9c77 100644 --- a/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java +++ b/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java @@ -1,12 +1,13 @@ package com.stumeet.server.study.application.service; import com.stumeet.server.file.application.port.out.FileUrl; +import com.stumeet.server.member.application.port.in.MemberValidationUseCase; import com.stumeet.server.study.application.port.out.StudyTagCommandPort; + import org.springframework.transaction.annotation.Transactional; import com.stumeet.server.common.annotation.UseCase; import com.stumeet.server.file.application.port.in.FileUploadUseCase; -import com.stumeet.server.member.domain.Member; import com.stumeet.server.study.application.port.in.StudyCreateUseCase; import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; import com.stumeet.server.study.application.port.in.mapper.StudyUseCaseMapper; @@ -23,6 +24,7 @@ public class StudyCreateService implements StudyCreateUseCase { private final FileUploadUseCase fileUploadUseCase; private final StudyMemberJoinUseCase memberJoinUseCase; + private final MemberValidationUseCase memberValidationUseCase; private final StudyCommandPort studyCommandPort; private final StudyTagCommandPort studyTagCommandPort; @@ -30,15 +32,16 @@ public class StudyCreateService implements StudyCreateUseCase { private final StudyUseCaseMapper studyUseCaseMapper; @Override - public Long create(StudyCreateCommand command, Member member) { - Study study = studyUseCaseMapper.toDomain(command); + public Long create(StudyCreateCommand command, Long memberId) { + memberValidationUseCase.checkById(memberId); + FileUrl mainImageUrl = fileUploadUseCase.uploadStudyMainImage(command.image()); - study.setImageUrl(mainImageUrl); + Study study = Study.create(command, mainImageUrl.url()); Long studyCreatedId = studyCommandPort.save(study).getId(); - studyTagCommandPort.saveAll(study.getStudyTags(), studyCreatedId); + studyTagCommandPort.saveAllStudyTags(study.getStudyTags(), studyCreatedId); - memberJoinUseCase.join(studyUseCaseMapper.toAdminStudyMemberJoinCommand(member.getId(), studyCreatedId)); + memberJoinUseCase.join(studyUseCaseMapper.toAdminStudyMemberJoinCommand(memberId, studyCreatedId)); return studyCreatedId; } } diff --git a/src/main/java/com/stumeet/server/study/domain/Study.java b/src/main/java/com/stumeet/server/study/domain/Study.java index 2ed2d207..3e1fec99 100644 --- a/src/main/java/com/stumeet/server/study/domain/Study.java +++ b/src/main/java/com/stumeet/server/study/domain/Study.java @@ -1,6 +1,6 @@ package com.stumeet.server.study.domain; -import com.stumeet.server.file.application.port.out.FileUrl; +import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; import java.time.LocalDate; import java.time.LocalTime; @@ -40,25 +40,24 @@ public class Study { private boolean isDeleted; - public static Study create(StudyDomain studyDomain, String name, String intro, String rule, - String region, StudyPeriod studyPeriod, StudyMeetingSchedule meetingSchedule) { + public static Study create(StudyCreateCommand command, String imageUrl) { return Study.builder() - .studyDomain(studyDomain) - .name(name) - .intro(intro) - .rule(rule) - .region(region) - .period(studyPeriod) - .meetingSchedule(meetingSchedule) + .studyDomain(StudyDomain.of(StudyField.getByName(command.studyField()), command.studyTags())) + .name(command.name()) + .intro(command.intro()) + .rule(command.rule()) + .region(command.region()) + .period(StudyPeriod.of(command.startDate(), command.endDate())) + .meetingSchedule( + StudyMeetingSchedule.of( + command.meetingTime(), + Repetition.of(command.meetingRepetitionType(), command.meetingRepetitionDates()))) + .imageUrl(imageUrl) .isFinished(false) .isDeleted(false) .build(); } - public void setImageUrl(FileUrl fileUrl) { - imageUrl = fileUrl.url(); - } - public String getStudyFieldName() { return studyDomain.getStudyFieldName(); } From f598605d38fbcbf3b01d6d14839abf51576ca805 Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 16:46:15 +0900 Subject: [PATCH 02/17] =?UTF-8?q?:recycle:=20[STMT-200]=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=EC=A0=95=EA=B8=B0=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EC=97=90=EC=84=9C=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EC=9D=84=20=EB=8F=85=EB=A6=BD=EC=A0=81=EC=9D=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 내부 클래스로 선언하는 이점이 없다고 생각하여 분리하였습니다. --- .../MeetingRepetitionPersistenceMapper.java | 13 +++---- .../server/study/domain/Repetition.java | 30 +++++++++++++++ .../study/domain/StudyMeetingSchedule.java | 28 +++----------- .../stumeet/server/stub/RepetitionStub.java | 23 ++++------- .../server/study/domain/RepetitionTest.java | 38 +++++-------------- 5 files changed, 58 insertions(+), 74 deletions(-) create mode 100644 src/main/java/com/stumeet/server/study/domain/Repetition.java diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java index 2761d8a7..7290660d 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java @@ -4,24 +4,23 @@ import org.springframework.stereotype.Component; +import com.stumeet.server.study.domain.Repetition; import com.stumeet.server.study.domain.RepetitionType; -import com.stumeet.server.study.domain.StudyMeetingSchedule; @Component public class MeetingRepetitionPersistenceMapper { private static final String REPEAT_DELIMITER = ";"; - public StudyMeetingSchedule.Repetition toDomain(String meetingRepetition) { + public Repetition toDomain(String meetingRepetition) { List repetitionElements = List.of(meetingRepetition.split(REPEAT_DELIMITER)); - return StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.valueOf(repetitionElements.getFirst())) - .dates(repetitionElements.subList(1, repetitionElements.size())) - .build(); + return Repetition.of( + RepetitionType.valueOf(repetitionElements.getFirst()), + repetitionElements.subList(1, repetitionElements.size())); } - public String toColumn(StudyMeetingSchedule.Repetition repetition) { + public String toColumn(Repetition repetition) { String dates = repetition.getDates() != null ? String.join(REPEAT_DELIMITER, repetition.getDates()) : null; diff --git a/src/main/java/com/stumeet/server/study/domain/Repetition.java b/src/main/java/com/stumeet/server/study/domain/Repetition.java new file mode 100644 index 00000000..9926b1b1 --- /dev/null +++ b/src/main/java/com/stumeet/server/study/domain/Repetition.java @@ -0,0 +1,30 @@ +package com.stumeet.server.study.domain; + +import java.util.List; + +import com.stumeet.server.study.domain.exception.InvalidRepetitionDatesException; + +import lombok.Getter; + +@Getter +public class Repetition { + + private final RepetitionType type; + private final List dates; + + private Repetition(RepetitionType type, List dates) { + validateRepetition(type, dates); + this.type = type; + this.dates = type.equals(RepetitionType.DAILY) ? null : dates; + } + + private void validateRepetition(RepetitionType type, List dates) { + if(!type.equals(RepetitionType.DAILY) && dates == null) { + throw new InvalidRepetitionDatesException(type.toString()); + } + } + + public static Repetition of(RepetitionType type, List dates) { + return new Repetition(type, dates); + } +} \ No newline at end of file diff --git a/src/main/java/com/stumeet/server/study/domain/StudyMeetingSchedule.java b/src/main/java/com/stumeet/server/study/domain/StudyMeetingSchedule.java index a5a29f54..503d65a9 100644 --- a/src/main/java/com/stumeet/server/study/domain/StudyMeetingSchedule.java +++ b/src/main/java/com/stumeet/server/study/domain/StudyMeetingSchedule.java @@ -3,14 +3,12 @@ import java.time.LocalTime; import java.util.List; -import com.stumeet.server.study.domain.exception.InvalidRepetitionDatesException; - import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -@Builder +@Builder(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter public class StudyMeetingSchedule { @@ -18,25 +16,11 @@ public class StudyMeetingSchedule { private final LocalTime time; private final Repetition repetition; - @Getter - public static class Repetition { - - private final RepetitionType type; - - private final List dates; - - @Builder - private Repetition(RepetitionType type, List dates) { - validateRepetition(type, dates); - this.type = type; - this.dates = type.equals(RepetitionType.DAILY) ? null : dates; - } - - private void validateRepetition(RepetitionType type, List dates) { - if(!type.equals(RepetitionType.DAILY) && dates == null) { - throw new InvalidRepetitionDatesException(type.toString()); - } - } + public static StudyMeetingSchedule of(LocalTime meetingTime, Repetition repetition) { + return StudyMeetingSchedule.builder() + .time(meetingTime) + .repetition(repetition) + .build(); } public LocalTime getTime() { diff --git a/src/test/java/com/stumeet/server/stub/RepetitionStub.java b/src/test/java/com/stumeet/server/stub/RepetitionStub.java index 71ba00e4..d533faa6 100644 --- a/src/test/java/com/stumeet/server/stub/RepetitionStub.java +++ b/src/test/java/com/stumeet/server/stub/RepetitionStub.java @@ -2,29 +2,20 @@ import java.util.List; +import com.stumeet.server.study.domain.Repetition; import com.stumeet.server.study.domain.RepetitionType; -import com.stumeet.server.study.domain.StudyMeetingSchedule; public class RepetitionStub { - public static StudyMeetingSchedule.Repetition getDailyRepetition() { - return StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.DAILY) - .dates(null) - .build(); + public static Repetition getDailyRepetition() { + return Repetition.of(RepetitionType.DAILY, null); } - public static StudyMeetingSchedule.Repetition getWeeklyRepetition() { - return StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.WEEKLY) - .dates(List.of("수", "금")) - .build(); + public static Repetition getWeeklyRepetition() { + return Repetition.of(RepetitionType.WEEKLY, List.of("수", "금")); } - public static StudyMeetingSchedule.Repetition getMonthlyRepetition() { - return StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.MONTHLY) - .dates(List.of("10", "20", "30")) - .build(); + public static Repetition getMonthlyRepetition() { + return Repetition.of(RepetitionType.MONTHLY, List.of("10", "20", "30")); } } diff --git a/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java b/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java index be1b1fc1..3772ba47 100644 --- a/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java +++ b/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java @@ -21,16 +21,10 @@ class create { @Test @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 null을 전달 받았을 경우 생성에 성공한다.") void weeklyTypeNullDatesSuccessTest() { - assertThatCode(() -> StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.DAILY) - .dates(null) - .build()) + assertThatCode(() -> Repetition.of(RepetitionType.DAILY, null)) .doesNotThrowAnyException(); - assertThat(StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.DAILY) - .dates(null) - .build()) + assertThat(Repetition.of(RepetitionType.DAILY, null)) .usingRecursiveComparison() .isEqualTo(RepetitionStub.getDailyRepetition()); } @@ -38,28 +32,20 @@ void weeklyTypeNullDatesSuccessTest() { @Test @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 null이 아닌 값을 전달 받았을 경우 생성에 성공한다.") void weeklyTypeInvalidDatesSuccessTest() { - assertThat(StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.DAILY) - .dates(List.of("월", "화", "수", "목", "금", "토", "일")) - .build() + assertThat( + Repetition.of(RepetitionType.DAILY, List.of("월", "화", "수", "목", "금", "토", "일")) .getDates()) - .isNull(); + .isNull(); } @Test @DisplayName("[성공] 반복 유형이 DAILY가 아닐 때, 유효한 값을 전달 받았을 경우 생성에 성공한다.") void otherTypesSuccessTest() { - assertThat(StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.WEEKLY) - .dates(List.of("수", "금")) - .build()) + assertThat(Repetition.of(RepetitionType.WEEKLY, List.of("수", "금"))) .usingRecursiveComparison() .isEqualTo(RepetitionStub.getWeeklyRepetition()); - assertThat(StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.MONTHLY) - .dates(List.of("10", "20", "30")) - .build()) + assertThat(Repetition.of(RepetitionType.MONTHLY, List.of("10", "20", "30"))) .usingRecursiveComparison() .isEqualTo(RepetitionStub.getMonthlyRepetition()); } @@ -67,16 +53,10 @@ void otherTypesSuccessTest() { @Test @DisplayName("[실패] 반복 타입이 DAILY가 아닌 경우 반복일이 null이면 테스트에 실패한다.") void invalidDatesFailTest() { - assertThatThrownBy(() -> StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.WEEKLY) - .dates(null) - .build()) + assertThatThrownBy(() -> Repetition.of(RepetitionType.WEEKLY, null)) .isInstanceOf(InvalidRepetitionDatesException.class); - assertThatThrownBy(() -> StudyMeetingSchedule.Repetition.builder() - .type(RepetitionType.MONTHLY) - .dates(null) - .build()) + assertThatThrownBy(() -> Repetition.of(RepetitionType.MONTHLY, null)) .isInstanceOf(InvalidRepetitionDatesException.class); } } From f744bb4db958cf37c31227fc7c977a8e0a9c0b91 Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 16:50:49 +0900 Subject: [PATCH 03/17] =?UTF-8?q?:recycle:=20[STMT-200]=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=9D=91=EC=9A=A9=EA=B3=84=EC=B8=B5,=20=EC=9D=B8=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EA=B3=84=EC=B8=B5=20mapper=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/StudyDomainPersistenceMapper.java | 14 +++++------- ...StudyMeetingSchedulePersistenceMapper.java | 20 +++++++++++++++++ .../mapper/StudyPersistenceMapper.java | 17 +++++--------- .../mapper/StudyTagPersistenceMapper.java | 22 +++++++++---------- .../in/mapper/StudyDomainUseCaseMapper.java | 21 ------------------ .../StudyMeetingScheduleUseCaseMapper.java | 19 ---------------- .../in/mapper/StudyPeriodUseCaseMapper.java | 16 -------------- .../server/study/domain/StudyDomain.java | 18 +++++++++------ .../server/study/domain/StudyPeriod.java | 5 +---- 9 files changed, 53 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyMeetingSchedulePersistenceMapper.java delete mode 100644 src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyDomainUseCaseMapper.java delete mode 100644 src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyMeetingScheduleUseCaseMapper.java delete mode 100644 src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyPeriodUseCaseMapper.java diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyDomainPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyDomainPersistenceMapper.java index 7c4d9f6d..8d0fcfad 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyDomainPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyDomainPersistenceMapper.java @@ -2,20 +2,18 @@ import com.stumeet.server.study.adapter.out.persistance.entity.StudyJpaEntity; import com.stumeet.server.study.domain.StudyDomain; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor public class StudyDomainPersistenceMapper { - private final StudyTagPersistenceMapper studyTagPersistenceMapper; - - public StudyDomain toDomain(StudyJpaEntity entity) { - return StudyDomain.builder() - .studyField(entity.getStudyField()) - .studyTags(studyTagPersistenceMapper.toDomains(entity.getStudyTags())) - .build(); + private final StudyTagPersistenceMapper studyTagPersistenceMapper; - } + public StudyDomain toDomain(StudyJpaEntity entity) { + return StudyDomain.of(entity.getStudyField(), studyTagPersistenceMapper.toDomains(entity.getStudyTags())); + } } \ No newline at end of file diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyMeetingSchedulePersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyMeetingSchedulePersistenceMapper.java new file mode 100644 index 00000000..d30fe0e7 --- /dev/null +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyMeetingSchedulePersistenceMapper.java @@ -0,0 +1,20 @@ +package com.stumeet.server.study.adapter.out.persistance.mapper; + +import java.time.LocalTime; + +import org.springframework.stereotype.Component; + +import com.stumeet.server.study.domain.StudyMeetingSchedule; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class StudyMeetingSchedulePersistenceMapper { + + private final MeetingRepetitionPersistenceMapper meetingRepetitionPersistenceMapper; + + public StudyMeetingSchedule toDomain(LocalTime meetingTime, String meetingRepetition) { + return StudyMeetingSchedule.of(meetingTime, meetingRepetitionPersistenceMapper.toDomain(meetingRepetition)); + } +} diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyPersistenceMapper.java index 44327b3e..ebfc833f 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyPersistenceMapper.java @@ -4,7 +4,6 @@ import com.stumeet.server.study.adapter.out.persistance.entity.StudyJpaEntity; import com.stumeet.server.study.domain.Study; -import com.stumeet.server.study.domain.StudyMeetingSchedule; import com.stumeet.server.study.domain.StudyPeriod; import lombok.RequiredArgsConstructor; @@ -14,8 +13,9 @@ public class StudyPersistenceMapper { private final StudyDomainPersistenceMapper studyDomainPersistenceMapper; - private final MeetingRepetitionPersistenceMapper meetingRepetitionPersistenceMapper; private final StudyTagPersistenceMapper studyTagPersistenceMapper; + private final StudyMeetingSchedulePersistenceMapper studyMeetingSchedulePersistenceMapper; + private final MeetingRepetitionPersistenceMapper meetingRepetitionPersistenceMapper; public Study toDomain(StudyJpaEntity entity) { return Study.builder() @@ -25,15 +25,9 @@ public Study toDomain(StudyJpaEntity entity) { .region(entity.getRegion()) .intro(entity.getIntro()) .rule(entity.getRule()) - .period(StudyPeriod.builder() - .startDate(entity.getStartDate()) - .endDate(entity.getEndDate()) - .build()) + .period(StudyPeriod.of(entity.getStartDate(), entity.getEndDate())) .imageUrl(entity.getImage()) - .meetingSchedule(StudyMeetingSchedule.builder() - .time(entity.getMeetingTime()) - .repetition(meetingRepetitionPersistenceMapper.toDomain(entity.getMeetingRepetition())) - .build()) + .meetingSchedule(studyMeetingSchedulePersistenceMapper.toDomain(entity.getMeetingTime(), entity.getMeetingRepetition())) .isFinished(entity.getIsFinished()) .isDeleted(entity.getIsDeleted()) .build(); @@ -52,8 +46,7 @@ public StudyJpaEntity toEntity(Study domain) { .endDate(domain.getEndDate()) .image(domain.getImageUrl()) .meetingTime(domain.getMeetingSchedule().getTime()) - .meetingRepetition( - meetingRepetitionPersistenceMapper.toColumn(domain.getMeetingSchedule().getRepetition())) + .meetingRepetition(meetingRepetitionPersistenceMapper.toColumn(domain.getMeetingSchedule().getRepetition())) .isFinished(domain.isFinished()) .isDeleted(domain.isDeleted()) .build(); diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java index 0f72220d..93d7b793 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java @@ -11,20 +11,18 @@ public class StudyTagPersistenceMapper { public List toDomains(List entities) { return entities != null - ? entities.stream() - .map(StudyTagJpaEntity::getName) - .toList() - : List.of(); + ? entities.stream() + .map(StudyTagJpaEntity::getName) + .toList() + : List.of(); } public List toEntities(List domains, Long studyId) { - return !domains.isEmpty() - ? domains.stream() - .map(tag -> StudyTagJpaEntity.builder() - .studyId(studyId) - .name(tag) - .build()) - .toList() - : List.of(); + return domains.stream() + .map(domain -> StudyTagJpaEntity.builder() + .studyId(studyId) + .name(domain) + .build()) + .toList(); } } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyDomainUseCaseMapper.java b/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyDomainUseCaseMapper.java deleted file mode 100644 index e91128a7..00000000 --- a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyDomainUseCaseMapper.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.stumeet.server.study.application.port.in.mapper; - -import com.stumeet.server.study.domain.StudyField; -import org.springframework.stereotype.Component; - -import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; -import com.stumeet.server.study.domain.StudyDomain; - -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class StudyDomainUseCaseMapper { - - public StudyDomain toDomain(StudyCreateCommand command) { - return StudyDomain.builder() - .studyField(StudyField.getByName(command.studyField())) - .studyTags(command.studyTags()) - .build(); - } -} diff --git a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyMeetingScheduleUseCaseMapper.java b/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyMeetingScheduleUseCaseMapper.java deleted file mode 100644 index 723bc5da..00000000 --- a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyMeetingScheduleUseCaseMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.stumeet.server.study.application.port.in.mapper; - -import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; -import com.stumeet.server.study.domain.StudyMeetingSchedule; -import org.springframework.stereotype.Component; - -@Component -public class StudyMeetingScheduleUseCaseMapper { - - public StudyMeetingSchedule toDomain(StudyCreateCommand command) { - return StudyMeetingSchedule.builder() - .time(command.meetingTime()) - .repetition(StudyMeetingSchedule.Repetition.builder() - .type(command.meetingRepetitionType()) - .dates(command.meetingRepetitionDates()) - .build()) - .build(); - } -} diff --git a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyPeriodUseCaseMapper.java b/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyPeriodUseCaseMapper.java deleted file mode 100644 index 3f332959..00000000 --- a/src/main/java/com/stumeet/server/study/application/port/in/mapper/StudyPeriodUseCaseMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.stumeet.server.study.application.port.in.mapper; - -import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; -import com.stumeet.server.study.domain.StudyPeriod; -import org.springframework.stereotype.Component; - -@Component -public class StudyPeriodUseCaseMapper { - - public StudyPeriod toDomain(StudyCreateCommand command) { - return StudyPeriod.builder() - .startDate(command.startDate()) - .endDate(command.endDate()) - .build(); - } -} diff --git a/src/main/java/com/stumeet/server/study/domain/StudyDomain.java b/src/main/java/com/stumeet/server/study/domain/StudyDomain.java index 4e74c2ce..6072933b 100644 --- a/src/main/java/com/stumeet/server/study/domain/StudyDomain.java +++ b/src/main/java/com/stumeet/server/study/domain/StudyDomain.java @@ -2,19 +2,23 @@ import java.util.List; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; @Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) public class StudyDomain { - private StudyField studyField; + private final StudyField studyField; - private List studyTags; + private final List studyTags; + + private StudyDomain(StudyField studyField, List studyTags) { + this.studyField = studyField; + this.studyTags = studyTags != null ? studyTags : List.of(); + } + + public static StudyDomain of(StudyField studyField, List studyTags) { + return new StudyDomain(studyField, studyTags); + } public String getStudyFieldName() { return studyField.getName(); diff --git a/src/main/java/com/stumeet/server/study/domain/StudyPeriod.java b/src/main/java/com/stumeet/server/study/domain/StudyPeriod.java index b7227e33..b032c6bc 100644 --- a/src/main/java/com/stumeet/server/study/domain/StudyPeriod.java +++ b/src/main/java/com/stumeet/server/study/domain/StudyPeriod.java @@ -2,13 +2,10 @@ import java.time.LocalDate; -import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(staticName = "of") @Getter public class StudyPeriod { From c56e4dc37dc4f74cf1e8c5e80bd11e437c333111 Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 16:53:41 +0900 Subject: [PATCH 04/17] =?UTF-8?q?:recycle:=20[STMT-200]=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=ED=83=9C=EA=B7=B8=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=99=80=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=EC=9D=98=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/persistance/entity/StudyJpaEntity.java | 10 ++++++---- .../out/persistance/entity/StudyTagJpaEntity.java | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyJpaEntity.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyJpaEntity.java index bdb1db20..e379e83b 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyJpaEntity.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyJpaEntity.java @@ -1,14 +1,18 @@ package com.stumeet.server.study.adapter.out.persistance.entity; import com.stumeet.server.study.domain.StudyField; + import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.OneToMany; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; + import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Comment; @@ -19,7 +23,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -40,8 +43,7 @@ public class StudyJpaEntity extends BaseTimeEntity { @Comment("스터디 그룹 아이디") private Long id; - @OneToMany(orphanRemoval = true) - @JoinColumn(name = "study_id") + @OneToMany(mappedBy = "studyId", fetch = FetchType.EAGER) private List studyTags; @Enumerated(EnumType.STRING) @@ -111,4 +113,4 @@ public Boolean getIsFinished() { public Boolean getIsDeleted() { return this.isDeleted; } -} +} \ No newline at end of file diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyTagJpaEntity.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyTagJpaEntity.java index efaae3f1..1b601081 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyTagJpaEntity.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/entity/StudyTagJpaEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -27,7 +28,7 @@ public class StudyTagJpaEntity { @Comment("스터디 태그 id") private Long id; - @Column(name = "study_id", nullable = false) + @JoinColumn(name = "study_id", nullable = false) @Comment("스터디 id") private Long studyId; From 270333cde2895dc0fdd0ab3b0ad03717ceca9bdd Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 16:54:31 +0900 Subject: [PATCH 05/17] =?UTF-8?q?:sparkles:=20[STMT-200]=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=EC=88=98=EC=A0=95=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/response/SuccessCode.java | 1 + .../study/adapter/in/web/StudyUpdateApi.java | 39 +++++++++++++++ .../persistance/JpaStudyTagRepository.java | 2 + .../StudyTagPersistenceAdapter.java | 13 ++++- .../port/in/StudyUpdateUseCase.java | 8 ++++ .../port/in/command/StudyUpdateCommand.java | 40 ++++++++++++++++ .../port/out/StudyTagCommandPort.java | 6 ++- .../service/StudyUpdateService.java | 48 +++++++++++++++++++ .../stumeet/server/study/domain/Study.java | 24 ++++++++++ src/test/resources/db/setup.sql | 2 +- 10 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java create mode 100644 src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java create mode 100644 src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java create mode 100644 src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java diff --git a/src/main/java/com/stumeet/server/common/response/SuccessCode.java b/src/main/java/com/stumeet/server/common/response/SuccessCode.java index 5637c239..b271229b 100644 --- a/src/main/java/com/stumeet/server/common/response/SuccessCode.java +++ b/src/main/java/com/stumeet/server/common/response/SuccessCode.java @@ -13,6 +13,7 @@ public enum SuccessCode { * 200 - OK */ GET_SUCCESS(HttpStatus.OK, "조회에 성공했습니다."), + UPDATE_SUCCESS(HttpStatus.OK, "수정에 성공했습니다."), STUDY_LEAVE_SUCCESS(HttpStatus.OK, "스터디 탈퇴에 성공했습니다."), STUDY_KICK_SUCCESS(HttpStatus.OK, "스터디원 강퇴에 성공했습니다."), diff --git a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java new file mode 100644 index 00000000..16926bcf --- /dev/null +++ b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java @@ -0,0 +1,39 @@ +package com.stumeet.server.study.adapter.in.web; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.stumeet.server.common.annotation.WebAdapter; +import com.stumeet.server.common.auth.model.LoginMember; +import com.stumeet.server.common.model.ApiResponse; +import com.stumeet.server.common.response.SuccessCode; +import com.stumeet.server.study.application.port.in.StudyUpdateUseCase; +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@WebAdapter +@RequiredArgsConstructor +@RequestMapping("/api/v1/studies") +public class StudyUpdateApi { + + private final StudyUpdateUseCase studyUpdateUseCase; + + @PatchMapping("/{studyId}") + public ResponseEntity> update( + @AuthenticationPrincipal LoginMember member, + @PathVariable Long studyId, + @Valid StudyUpdateCommand request + ) { + studyUpdateUseCase.update(studyId, member.getMember().getId(), request); + + return new ResponseEntity<>( + ApiResponse.success(SuccessCode.UPDATE_SUCCESS), + HttpStatus.CREATED); + } +} diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/JpaStudyTagRepository.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/JpaStudyTagRepository.java index 737c54e8..0b3e5dc9 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/JpaStudyTagRepository.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/JpaStudyTagRepository.java @@ -5,4 +5,6 @@ import com.stumeet.server.study.adapter.out.persistance.entity.StudyTagJpaEntity; public interface JpaStudyTagRepository extends JpaRepository { + + void deleteAllByStudyId(Long studyId); } diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/StudyTagPersistenceAdapter.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/StudyTagPersistenceAdapter.java index 1ecccc45..e0711bff 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/StudyTagPersistenceAdapter.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/StudyTagPersistenceAdapter.java @@ -17,10 +17,21 @@ public class StudyTagPersistenceAdapter implements StudyTagCommandPort { private final StudyTagPersistenceMapper studyTagPersistenceMapper; @Override - public List saveAll(List studyTags, Long studyId) { + public List saveAllStudyTags(List studyTags, Long studyId) { List entities = studyTagRepository.saveAll(studyTagPersistenceMapper.toEntities(studyTags, studyId)); return studyTagPersistenceMapper.toDomains(entities); } + + @Override + public void clearStudyTags(Long studyId) { + studyTagRepository.deleteAllByStudyId(studyId); + } + + @Override + public void replaceStudyTags(List newStudyTags, Long studyId) { + clearStudyTags(studyId); + saveAllStudyTags(newStudyTags, studyId); + } } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java b/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java new file mode 100644 index 00000000..1a8dbea3 --- /dev/null +++ b/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java @@ -0,0 +1,8 @@ +package com.stumeet.server.study.application.port.in; + +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; + +public interface StudyUpdateUseCase { + + void update(Long studyId, Long memberId, StudyUpdateCommand command); +} diff --git a/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java new file mode 100644 index 00000000..8a9e2e8c --- /dev/null +++ b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java @@ -0,0 +1,40 @@ +package com.stumeet.server.study.application.port.in.command; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.stumeet.server.common.annotation.validator.NullOrImageFile; +import com.stumeet.server.common.annotation.validator.NullOrNotBlank; +import com.stumeet.server.study.domain.RepetitionType; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record StudyUpdateCommand( + @NullOrImageFile(message = "이미지 파일을 첨부해주세요") + MultipartFile newImage, + @NotBlank(message = "스터디 분야를 입력해주세요.") + String studyField, + @NotBlank(message = "이름을 입력해주세요.") + String name, + @NotBlank(message = "소개글을 입력해주세요.") + String intro, + @NotBlank(message = "지역을 입력해주세요.") + String region, + @NullOrNotBlank(message = "규칙은 공백일 수 없습니다.") + String rule, + @NotNull(message = "시작일을 입력해주세요.") + LocalDate startDate, + @NotNull(message = "종료일을 입력해주세요.") + LocalDate endDate, + @NotNull(message = "정기모임 시간을 입력해주세요.") + LocalTime meetingTime, + @NotNull(message = "정기모임 반복 유형을 입력해주세요.") + RepetitionType meetingRepetitionType, + List meetingRepetitionDates, + List studyTags +) { +} diff --git a/src/main/java/com/stumeet/server/study/application/port/out/StudyTagCommandPort.java b/src/main/java/com/stumeet/server/study/application/port/out/StudyTagCommandPort.java index 4180443e..528ed7cd 100644 --- a/src/main/java/com/stumeet/server/study/application/port/out/StudyTagCommandPort.java +++ b/src/main/java/com/stumeet/server/study/application/port/out/StudyTagCommandPort.java @@ -4,5 +4,9 @@ public interface StudyTagCommandPort { - List saveAll(List studyTags, Long studyId); + List saveAllStudyTags(List studyTags, Long studyId); + + void clearStudyTags(Long studyId); + + void replaceStudyTags(List newStudyTags, Long studyId); } diff --git a/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java b/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java new file mode 100644 index 00000000..5956a093 --- /dev/null +++ b/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java @@ -0,0 +1,48 @@ +package com.stumeet.server.study.application.service; + +import org.springframework.transaction.annotation.Transactional; + +import com.stumeet.server.common.annotation.UseCase; +import com.stumeet.server.file.application.port.in.FileUploadUseCase; +import com.stumeet.server.file.application.port.out.FileUrl; +import com.stumeet.server.member.application.port.in.MemberValidationUseCase; +import com.stumeet.server.study.application.port.in.StudyUpdateUseCase; +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; +import com.stumeet.server.study.application.port.out.StudyCommandPort; +import com.stumeet.server.study.application.port.out.StudyQueryPort; +import com.stumeet.server.study.application.port.out.StudyTagCommandPort; +import com.stumeet.server.study.domain.Study; +import com.stumeet.server.studymember.application.port.in.StudyMemberValidationUseCase; + +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +@Transactional +public class StudyUpdateService implements StudyUpdateUseCase { + + private final MemberValidationUseCase memberValidationUseCase; + private final StudyMemberValidationUseCase studyMemberValidationUseCase; + private final FileUploadUseCase fileUploadUseCase; + + private final StudyQueryPort studyQueryPort; + private final StudyCommandPort studyCommandPort; + private final StudyTagCommandPort studyTagCommandPort; + + @Override + public void update(Long studyId, Long memberId, StudyUpdateCommand command) { + memberValidationUseCase.checkById(memberId); + studyMemberValidationUseCase.checkStudyJoinMember(studyId, memberId); + studyMemberValidationUseCase.checkAdmin(studyId, memberId); + + Study existingStudy = studyQueryPort.getById(studyId); + + FileUrl mainImageUrl = fileUploadUseCase.uploadStudyMainImage(command.newImage()); + Study updatedStudy = Study.update(command, existingStudy, mainImageUrl.url()); + studyCommandPort.save(updatedStudy); + + if (!existingStudy.isStudyTagsEquals(updatedStudy.getStudyTags())) { + studyTagCommandPort.replaceStudyTags(updatedStudy.getStudyTags(), updatedStudy.getId()); + } + } +} diff --git a/src/main/java/com/stumeet/server/study/domain/Study.java b/src/main/java/com/stumeet/server/study/domain/Study.java index 3e1fec99..4b95a1af 100644 --- a/src/main/java/com/stumeet/server/study/domain/Study.java +++ b/src/main/java/com/stumeet/server/study/domain/Study.java @@ -1,6 +1,7 @@ package com.stumeet.server.study.domain; import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; import java.time.LocalDate; import java.time.LocalTime; @@ -58,6 +59,29 @@ public static Study create(StudyCreateCommand command, String imageUrl) { .build(); } + public static Study update(StudyUpdateCommand command, Study existingStudy, String newImageUrl) { + return Study.builder() + .id(existingStudy.getId()) + .studyDomain(StudyDomain.of(StudyField.getByName(command.studyField()), command.studyTags())) + .name(command.name()) + .intro(command.intro()) + .rule(command.rule()) + .region(command.region()) + .period(StudyPeriod.of(command.startDate(), command.endDate())) + .meetingSchedule( + StudyMeetingSchedule.of( + command.meetingTime(), + Repetition.of(command.meetingRepetitionType(), command.meetingRepetitionDates()))) + .imageUrl(newImageUrl != null ? newImageUrl : existingStudy.imageUrl) + .isFinished(existingStudy.isFinished) + .isDeleted(existingStudy.isDeleted) + .build(); + } + + public boolean isStudyTagsEquals(List studyTags) { + return getStudyTags().equals(studyTags); + } + public String getStudyFieldName() { return studyDomain.getStudyFieldName(); } diff --git a/src/test/resources/db/setup.sql b/src/test/resources/db/setup.sql index 561525ca..1992368d 100644 --- a/src/test/resources/db/setup.sql +++ b/src/test/resources/db/setup.sql @@ -1,6 +1,6 @@ INSERT INTO study (id, study_field, name, region, intro, rule, image, meeting_time, meeting_repetition, start_date, end_date) -VALUES (1, 'LANGUAGE', '[임시] 프로그래밍 스터디', '서울', '프로그래밍 스터디 입니다.', '- 매주 목요일 8시\n- 장소: 안암역\n- 제시간에 제출하기!', +VALUES (1, 'PROGRAMMING', '[임시] 프로그래밍 스터디', '서울', '프로그래밍 스터디 입니다.', '- 매주 목요일 8시\n- 장소: 안암역\n- 제시간에 제출하기!', 'https://stumeet.s3.ap-northeast-2.amazonaws.com/study/1/image/2023062711172178420.png', '21:00:00', 'WEEKLY;월;수;목;', '2024-04-01', '2024-05-01'); From 4af473a41e7b122e8c3886e9a60a48d291f3f15b Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 18:31:49 +0900 Subject: [PATCH 06/17] =?UTF-8?q?:white=5Fcheck=5Fmark:=20[STMT-167]=20dis?= =?UTF-8?q?play=20name=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/study/adapter/in/web/StudyCreateApiTest.java | 6 +++--- .../server/study/adapter/in/web/StudyQueryApiTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java index e881aaf3..13db5244 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java @@ -25,7 +25,7 @@ class StudyCreateApiTest extends ApiTest { @Nested - @DisplayName("스터디 생성하기") + @DisplayName("스터디 생성 API") class CreateStudy { private final String path = "/api/v1/studies"; @@ -117,7 +117,7 @@ void successWithoutImageFile() throws Exception { @Test @WithMockMember - @DisplayName("[실패] 존재하지 않는 스터디 분야의 ID로 요청한 경우 스터디 생성을 실패한다.") + @DisplayName("[실패] 존재하지 않는 스터디 분야로 요청한 경우 스터디 생성을 실패한다.") void failWithNotExistStudyFieldId() throws Exception { StudyCreateCommand request = StudyStub.getInvalidFieldStudyCreateCommand(); @@ -153,7 +153,7 @@ void failWithNotExistStudyFieldId() throws Exception { @WithMockMember @DisplayName("[실패] 유효하지 않은 반복 일정 값으로 요청한 경우 스터디 생성을 실패한다.") void failWithInvalidStudyMeetingSchedule() throws Exception { - StudyCreateCommand request = StudyStub.getInvalidMeetingSchduleStudyCreateCommand(); + StudyCreateCommand request = StudyStub.getInvalidMeetingScheduleStudyCreateCommand(); mockMvc.perform(multipart(path) .file((MockMultipartFile) request.image()) diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyQueryApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyQueryApiTest.java index 39bc4e64..5b8b8506 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyQueryApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyQueryApiTest.java @@ -21,7 +21,7 @@ class StudyQueryApiTest extends ApiTest { @Nested - @DisplayName("스터디 상세 정보 가져오기") + @DisplayName("스터디 상세정보 조회 API") class GetStudyDetail { @Test From e9bc3262dd84f30a8213147cb93713f34e7ff86e Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 18:32:20 +0900 Subject: [PATCH 07/17] =?UTF-8?q?:adhesive=5Fbandage:=20[STMT-167]=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=A0=95=EB=B3=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20api=20return=20http=20code=20=EC=88=98=EC=A0=95=20C?= =?UTF-8?q?REATED=20->=20OK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java index 16926bcf..77b8c602 100644 --- a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java +++ b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java @@ -34,6 +34,6 @@ public ResponseEntity> update( return new ResponseEntity<>( ApiResponse.success(SuccessCode.UPDATE_SUCCESS), - HttpStatus.CREATED); + HttpStatus.OK); } } From cc8b7c3da32afc6c143235384628a1adbe17f1c0 Mon Sep 17 00:00:00 2001 From: 05AM Date: Sat, 13 Apr 2024 18:32:48 +0900 Subject: [PATCH 08/17] =?UTF-8?q?:white=5Fcheck=5Fmark:=20[STMT-167]=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=88=98=EC=A0=95=20api=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/stumeet/server/stub/StudyStub.java | 190 +++++++++----- .../adapter/in/web/StudyUpdateApiTest.java | 241 ++++++++++++++++++ 2 files changed, 363 insertions(+), 68 deletions(-) create mode 100644 src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java diff --git a/src/test/java/com/stumeet/server/stub/StudyStub.java b/src/test/java/com/stumeet/server/stub/StudyStub.java index 9edc8657..fabd1575 100644 --- a/src/test/java/com/stumeet/server/stub/StudyStub.java +++ b/src/test/java/com/stumeet/server/stub/StudyStub.java @@ -2,80 +2,134 @@ import java.time.LocalDate; import java.time.LocalTime; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; + import org.springframework.mock.web.MockMultipartFile; import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; import com.stumeet.server.study.domain.RepetitionType; public class StudyStub { - private StudyStub() { - - } - - public static Long getStudyId() { - return 1L; - } - - public static Long getInvalidStudyId() { - return 0L; - } - - public static StudyCreateCommand getStudyCreateCommand() { - MockMultipartFile image = new MockMultipartFile("image", "test.jpg", "image/jpeg", "test".getBytes()); - return new StudyCreateCommand( - image, - "어학", - "영어 회화 클럽", - "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", - "부산", - "주 1회 대면 미팅 및 온라인 토론", - LocalDate.parse("2024-05-15"), - LocalDate.parse("2024-12-15"), - LocalTime.parse("18:30:00"), - RepetitionType.valueOf("WEEKLY"), - List.of("월", "금"), - List.of("영어", "회화", "언어 교환") - ); - } - - public static StudyCreateCommand getInvalidFieldStudyCreateCommand() { - MockMultipartFile image = new MockMultipartFile("image", "test.jpg", "image/jpeg", "test".getBytes()); - return new StudyCreateCommand( - image, - "존재하지 않는 분야 값", - "영어 회화 클럽", - "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", - "부산", - "주 1회 대면 미팅 및 온라인 토론", - LocalDate.parse("2024-05-15"), - LocalDate.parse("2024-12-15"), - LocalTime.parse("18:30:00"), - RepetitionType.valueOf("WEEKLY"), - List.of("월", "금"), - List.of("영어", "회화", "언어 교환") - ); - } - - public static StudyCreateCommand getInvalidMeetingSchduleStudyCreateCommand() { - MockMultipartFile image = new MockMultipartFile("image", "test.jpg", "image/jpeg", - "test".getBytes()); - return new StudyCreateCommand( - image, - "어학", - "영어 회화 클럽", - "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", - "부산", - "주 1회 대면 미팅 및 온라인 토론", - LocalDate.parse("2024-05-15"), - LocalDate.parse("2024-12-15"), - LocalTime.parse("18:30:00"), - RepetitionType.valueOf("WEEKLY"), - List.of(), - List.of("영어", "회화", "언어 교환") - ); - } + + private static final MockMultipartFile image = + new MockMultipartFile("image", "test.jpg", "image/jpeg", "test".getBytes()); + + private static final MockMultipartFile newImage = + new MockMultipartFile("newImage", "test.jpg", "image/jpeg", "test".getBytes()); + + private StudyStub() { + + } + + public static Long getStudyId() { + return 1L; + } + + public static Long getInvalidStudyId() { + return 0L; + } + + public static StudyCreateCommand getStudyCreateCommand() { + return new StudyCreateCommand( + image, + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of("영어", "회화", "언어 교환") + ); + } + + public static StudyCreateCommand getInvalidFieldStudyCreateCommand() { + return new StudyCreateCommand( + image, + "존재하지 않는 분야 값", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of("영어", "회화", "언어 교환") + ); + } + + public static StudyCreateCommand getInvalidMeetingScheduleStudyCreateCommand() { + return new StudyCreateCommand( + image, + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of(), + List.of("영어", "회화", "언어 교환") + ); + } + + public static StudyUpdateCommand getStudyUpdateCommand() { + return new StudyUpdateCommand( + newImage, + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of("영어", "회화", "언어 교환", "어학") + ); + } + + public static StudyUpdateCommand getInvalidFieldStudyUpdateCommand() { + return new StudyUpdateCommand( + newImage, + "존재하지 않는 분야 값", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of("영어", "회화", "언어 교환", "어학") + ); + } + + public static StudyUpdateCommand getInvalidMeetingScheduleStudyUpdateCommand() { + return new StudyUpdateCommand( + newImage, + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of(), + List.of("영어", "회화", "언어 교환", "어학") + ); + } } diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java new file mode 100644 index 00000000..2429f3fd --- /dev/null +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java @@ -0,0 +1,241 @@ +package com.stumeet.server.study.adapter.in.web; + +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +import com.stumeet.server.common.auth.model.AuthenticationHeader; +import com.stumeet.server.helper.WithMockMember; +import com.stumeet.server.stub.StudyStub; +import com.stumeet.server.stub.TokenStub; +import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; +import com.stumeet.server.template.ApiTest; + +class StudyUpdateApiTest extends ApiTest { + + @Nested + @DisplayName("스터디 정보 수정 API") + class CreateStudy { + + private final String path = "/api/v1/studies/" + StudyStub.getStudyId(); + + @Test + @WithMockMember + @DisplayName("[성공] 스터디 정보 수정에 성공한다.") + void successUpdateTest() throws Exception { + StudyUpdateCommand request = StudyStub.getStudyUpdateCommand(); + + RequestPostProcessor patchMethod = http -> { + http.setMethod("PATCH"); + return http; + }; + + mockMvc.perform(multipart(path) + .file((MockMultipartFile) request.newImage()) + .with(patchMethod) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .queryParam("studyField", request.studyField()) + .queryParam("name", request.name()) + .queryParam("intro", request.intro()) + .queryParam("region", request.region()) + .queryParam("rule", request.rule()) + .queryParam("startDate", String.valueOf(request.startDate())) + .queryParam("endDate", String.valueOf(request.endDate())) + .queryParam("meetingTime", String.valueOf(request.meetingTime())) + .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) + .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) + .queryParam("studyTags", request.studyTags().toArray(String[]::new)) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("update-study/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()) + .description("서버로부터 전달받은 액세스 토큰") + ), + requestParts( + partWithName("newImage").description("새로운 스터디 메인 이미지 파일").optional() + .description("새로운 이미지가 첨부되지 않은 경우 생략 가능합니다.") + ), + queryParameters( + parameterWithName("studyField").description("스터디 분야 ID") + .attributes(key("constraint").value("NotNull, 스터디 분야 ID를 입력해주세요.")), + parameterWithName("name").description("스터디 이름") + .attributes(key("constraint").value("NotBlank, 이름을 입력해주세요.")), + parameterWithName("intro").description("소개") + .attributes(key("constraint").value("NotBlank, 소개글을 입력해주세요.")), + parameterWithName("region").description("활동 지역") + .attributes(key("constraint").value("NotBlank, 지역을 입력해주세요.")), + parameterWithName("rule").description("규칙").optional() + .attributes(key("constraint").value("NullOrNotBlank, 규칙은 공백일 수 없습니다.")), + parameterWithName("startDate").description("시작일") + .attributes(key("constraint").value("NotNull, 시작일을 입력해주세요.")), + parameterWithName("endDate").description("종료일") + .attributes(key("constraint").value("NotNull, 종료일을 입력해주세요.")), + parameterWithName("meetingTime").description("정기모임 시간") + .attributes(key("constraint").value("NotNull, 정기모임 시간을 입력해주세요.")), + parameterWithName("meetingRepetitionType").description("정기모임 반복 유형") + .attributes(key("constraint").value("NotNull, 정기모임 반복 유형을 입력해주세요.")), + parameterWithName("meetingRepetitionDates").description("정기모임 반복 일자").optional(), + parameterWithName("studyTags").description("스터디 태그").optional() + ), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); + } + + @Test + @WithMockMember + @DisplayName("[성공] 새 이미지가 없는 요청으로 스터디 정보 수정에 성공한다.") + void successUpdateWithoutImageTest() throws Exception { + StudyUpdateCommand request = StudyStub.getStudyUpdateCommand(); + + RequestPostProcessor patchMethod = http -> { + http.setMethod("PATCH"); + return http; + }; + + mockMvc.perform(multipart(path) + .with(patchMethod) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .queryParam("studyField", request.studyField()) + .queryParam("name", request.name()) + .queryParam("intro", request.intro()) + .queryParam("region", request.region()) + .queryParam("rule", request.rule()) + .queryParam("startDate", String.valueOf(request.startDate())) + .queryParam("endDate", String.valueOf(request.endDate())) + .queryParam("meetingTime", String.valueOf(request.meetingTime())) + .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) + .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) + .queryParam("studyTags", request.studyTags().toArray(String[]::new)) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + @WithMockMember + @DisplayName("[성공] 태그가 없는 요청으로 스터디 정보 수정에 성공한다.") + void successUpdateWithoutTagsTest() throws Exception { + StudyUpdateCommand request = StudyStub.getStudyUpdateCommand(); + + RequestPostProcessor patchMethod = http -> { + http.setMethod("PATCH"); + return http; + }; + + mockMvc.perform(multipart(path) + .file((MockMultipartFile) request.newImage()) + .with(patchMethod) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .queryParam("studyField", request.studyField()) + .queryParam("name", request.name()) + .queryParam("intro", request.intro()) + .queryParam("region", request.region()) + .queryParam("rule", request.rule()) + .queryParam("startDate", String.valueOf(request.startDate())) + .queryParam("endDate", String.valueOf(request.endDate())) + .queryParam("meetingTime", String.valueOf(request.meetingTime())) + .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) + .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + @WithMockMember + @DisplayName("[실패] 존재하지 않는 스터디 분야로 요청한 경우 스터디 정보 수정을 실패한다.") + void failWithNotExistStudyFieldId() throws Exception { + StudyUpdateCommand request = StudyStub.getInvalidFieldStudyUpdateCommand(); + + RequestPostProcessor patchMethod = http -> { + http.setMethod("PATCH"); + return http; + }; + + mockMvc.perform(multipart(path) + .file((MockMultipartFile) request.newImage()) + .with(patchMethod) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .queryParam("studyField", request.studyField()) + .queryParam("name", request.name()) + .queryParam("intro", request.intro()) + .queryParam("region", request.region()) + .queryParam("rule", request.rule()) + .queryParam("startDate", String.valueOf(request.startDate())) + .queryParam("endDate", String.valueOf(request.endDate())) + .queryParam("meetingTime", String.valueOf(request.meetingTime())) + .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) + .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) + .queryParam("studyTags", request.studyTags().toArray(String[]::new)) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andDo(document("update-study/fail/study-field-not-found", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( + "서버로부터 전달받은 액세스 토큰")), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); + } + + @Test + @WithMockMember + @DisplayName("[실패] 유효하지 않은 반복 일정 값으로 요청한 경우 스터디 생성을 실패한다.") + void failWithInvalidStudyMeetingSchedule() throws Exception { + StudyUpdateCommand request = StudyStub.getInvalidMeetingScheduleStudyUpdateCommand(); + + RequestPostProcessor patchMethod = http -> { + http.setMethod("PATCH"); + return http; + }; + + mockMvc.perform(multipart(path) + .file((MockMultipartFile) request.newImage()) + .with(patchMethod) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .queryParam("studyField", request.studyField()) + .queryParam("name", request.name()) + .queryParam("intro", request.intro()) + .queryParam("region", request.region()) + .queryParam("rule", request.rule()) + .queryParam("startDate", String.valueOf(request.startDate())) + .queryParam("endDate", String.valueOf(request.endDate())) + .queryParam("meetingTime", String.valueOf(request.meetingTime())) + .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) + .queryParam("studyTags", request.studyTags().toArray(String[]::new)) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(document("update-study/fail/invalid-study-meeting-schedule", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( + "서버로부터 전달받은 액세스 토큰")), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); + } + } +} \ No newline at end of file From 630afe3e173847a549888da629e6ec44ea629e2e Mon Sep 17 00:00:00 2001 From: 05AM Date: Fri, 12 Apr 2024 23:49:26 +0900 Subject: [PATCH 09/17] =?UTF-8?q?:memo:=20[STMT-167]=20=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=EB=94=94=20=EC=83=9D=EC=84=B1,=20=EC=88=98=EC=A0=95=20api=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 67b595d3..83c4da90 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -233,8 +233,16 @@ include::{snippets}/get-study-detail/fail/not-found/response-fields.adoc[] ===== 요청 include::{snippets}/create-study/success/http-request.adoc[] + +===== 헤더 include::{snippets}/create-study/success/request-headers.adoc[] +===== 파트 +include::{snippets}/create-study/success/request-parts.adoc[] + +===== 매개변수 +include::{snippets}/create-study/success/query-parameters.adoc[] + ===== 응답 성공 (200) include::{snippets}/create-study/success/response-body.adoc[] @@ -253,6 +261,42 @@ include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-bo include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-fields.adoc[] +=== 스터디 수정 + +스터디 정보를 수정하는 API 입니다. + +==== PATCH /api/v1/studies/{studyId} + +===== 요청 +include::{snippets}/update-study/success/http-request.adoc[] + +===== 헤더 +include::{snippets}/update-study/success/request-headers.adoc[] + +===== 파트 +include::{snippets}/update-study/success/request-parts.adoc[] + +===== 매개변수 +include::{snippets}/update-study/success/query-parameters.adoc[] + +===== 응답 성공 (200) + +include::{snippets}/update-study/success/response-body.adoc[] +include::{snippets}/update-study/success/response-fields.adoc[] + +===== 응답 실패 (404) + +.존재하지 않는 스터디 분야 ID를 요청한 경우 + +include::{snippets}/update-study/fail/study-field-not-found/response-body.adoc[] +include::{snippets}/update-study/fail/study-field-not-found/response-fields.adoc[] + +.일정 반복 유형 DAILY일 때를 제외하고 반복일을 null 값으로 요청한 경우 + +include::{snippets}/update-study/fail/invalid-study-meeting-schedule/response-body.adoc[] +include::{snippets}/update-study/fail/invalid-study-meeting-schedule/response-fields.adoc[] + + == 스터디 멤버 관리 === 스터디 멤버 조회 From fc46a1f5343797e9ee08c386d0fc9d92cf832c97 Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 19:50:47 +0900 Subject: [PATCH 10/17] =?UTF-8?q?:construction:=20[STMT-167]=20null=20safe?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistance/mapper/StudyTagPersistenceMapper.java | 6 ++---- .../java/com/stumeet/server/study/domain/Repetition.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java index 93d7b793..1893b835 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/StudyTagPersistenceMapper.java @@ -10,11 +10,9 @@ public class StudyTagPersistenceMapper { public List toDomains(List entities) { - return entities != null - ? entities.stream() + return entities.stream() .map(StudyTagJpaEntity::getName) - .toList() - : List.of(); + .toList(); } public List toEntities(List domains, Long studyId) { diff --git a/src/main/java/com/stumeet/server/study/domain/Repetition.java b/src/main/java/com/stumeet/server/study/domain/Repetition.java index 9926b1b1..8d223337 100644 --- a/src/main/java/com/stumeet/server/study/domain/Repetition.java +++ b/src/main/java/com/stumeet/server/study/domain/Repetition.java @@ -19,7 +19,7 @@ private Repetition(RepetitionType type, List dates) { } private void validateRepetition(RepetitionType type, List dates) { - if(!type.equals(RepetitionType.DAILY) && dates == null) { + if(!RepetitionType.DAILY.equals(type) && dates == null) { throw new InvalidRepetitionDatesException(type.toString()); } } From 8fd168507717ece28aec7044a9433d55eec01062 Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 22:59:19 +0900 Subject: [PATCH 11/17] =?UTF-8?q?:construction:=20[STMT-167]=20null=20safe?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EC=B6=94=EA=B0=80=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/MeetingRepetitionPersistenceMapper.java | 4 ++-- .../com/stumeet/server/study/domain/Repetition.java | 4 ++-- .../com/stumeet/server/study/domain/StudyDomain.java | 11 ++--------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java index 7290660d..9a6615a3 100644 --- a/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java +++ b/src/main/java/com/stumeet/server/study/adapter/out/persistance/mapper/MeetingRepetitionPersistenceMapper.java @@ -21,9 +21,9 @@ public Repetition toDomain(String meetingRepetition) { } public String toColumn(Repetition repetition) { - String dates = repetition.getDates() != null + String dates = !repetition.getDates().isEmpty() ? String.join(REPEAT_DELIMITER, repetition.getDates()) - : null; + : ""; return repetition.getType().toString() + REPEAT_DELIMITER diff --git a/src/main/java/com/stumeet/server/study/domain/Repetition.java b/src/main/java/com/stumeet/server/study/domain/Repetition.java index 8d223337..70f43466 100644 --- a/src/main/java/com/stumeet/server/study/domain/Repetition.java +++ b/src/main/java/com/stumeet/server/study/domain/Repetition.java @@ -15,11 +15,11 @@ public class Repetition { private Repetition(RepetitionType type, List dates) { validateRepetition(type, dates); this.type = type; - this.dates = type.equals(RepetitionType.DAILY) ? null : dates; + this.dates = type.equals(RepetitionType.DAILY) ? List.of() : dates; } private void validateRepetition(RepetitionType type, List dates) { - if(!RepetitionType.DAILY.equals(type) && dates == null) { + if (!RepetitionType.DAILY.equals(type) && dates.isEmpty()) { throw new InvalidRepetitionDatesException(type.toString()); } } diff --git a/src/main/java/com/stumeet/server/study/domain/StudyDomain.java b/src/main/java/com/stumeet/server/study/domain/StudyDomain.java index 6072933b..2d6f8635 100644 --- a/src/main/java/com/stumeet/server/study/domain/StudyDomain.java +++ b/src/main/java/com/stumeet/server/study/domain/StudyDomain.java @@ -2,8 +2,10 @@ import java.util.List; +import lombok.AllArgsConstructor; import lombok.Getter; +@AllArgsConstructor(staticName = "of") @Getter public class StudyDomain { @@ -11,15 +13,6 @@ public class StudyDomain { private final List studyTags; - private StudyDomain(StudyField studyField, List studyTags) { - this.studyField = studyField; - this.studyTags = studyTags != null ? studyTags : List.of(); - } - - public static StudyDomain of(StudyField studyField, List studyTags) { - return new StudyDomain(studyField, studyTags); - } - public String getStudyFieldName() { return studyField.getName(); } From 7456a05e4216ed7d4123616a7af252951dae8698 Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 23:03:54 +0900 Subject: [PATCH 12/17] =?UTF-8?q?:recycle:=20[STMT-167]=20=EA=B0=92?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B9=88=20=EB=B0=B0=EC=97=B4=EC=9D=84=20?= =?UTF-8?q?=EB=B0=9B=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EA=B3=BC=20=EC=9A=94=EC=B2=AD=EB=B3=B8=EB=AC=B8=EC=9D=84=20req?= =?UTF-8?q?uest=20part=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit : 현재 사용되고 있는 modelAttribute에서 Multipart file과 다른 파라미터 값이 혼용되어 있는 객체에서 multiform data 요청은 빈 배열을 받지 못하고 해당 파라미터의 값을 null로 인식하였습니다. 따라서 파일과 요청본문을 request part로 분리하여 파라미터 값으로 빈 문자열도 받을 수 있도록 리팩토링 하였습니다. --- .../annotation/validator/NullOrImageFile.java | 2 +- .../study/adapter/in/web/StudyCreateApi.java | 14 ++++-- .../study/adapter/in/web/StudyUpdateApi.java | 8 +++- .../port/in/StudyCreateUseCase.java | 4 +- .../port/in/StudyUpdateUseCase.java | 4 +- .../port/in/command/StudyCreateCommand.java | 7 +-- .../port/in/command/StudyUpdateCommand.java | 47 +++++++++---------- .../service/StudyCreateService.java | 5 +- .../service/StudyUpdateService.java | 11 +++-- .../stumeet/server/study/domain/Study.java | 4 +- 10 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/stumeet/server/common/annotation/validator/NullOrImageFile.java b/src/main/java/com/stumeet/server/common/annotation/validator/NullOrImageFile.java index 9ed05407..45f6b893 100644 --- a/src/main/java/com/stumeet/server/common/annotation/validator/NullOrImageFile.java +++ b/src/main/java/com/stumeet/server/common/annotation/validator/NullOrImageFile.java @@ -7,7 +7,7 @@ @Documented @Constraint(validatedBy = NullOrImageFileValidator.class) -@Target({ElementType.FIELD }) +@Target({ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface NullOrImageFile { String message() default "값이 전달되지 않거나 이미지 파일이어야 합니다."; diff --git a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java index a6482798..d8423ba4 100644 --- a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java +++ b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyCreateApi.java @@ -5,8 +5,11 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; import com.stumeet.server.common.annotation.WebAdapter; +import com.stumeet.server.common.annotation.validator.NullOrImageFile; import com.stumeet.server.common.auth.model.LoginMember; import com.stumeet.server.common.model.ApiResponse; import com.stumeet.server.common.response.SuccessCode; @@ -25,13 +28,14 @@ public class StudyCreateApi { @PostMapping public ResponseEntity> create( - @AuthenticationPrincipal LoginMember member, - @Valid StudyCreateCommand request + @AuthenticationPrincipal LoginMember member, + @RequestPart @Valid StudyCreateCommand request, + @RequestPart(required = false) @Valid @NullOrImageFile MultipartFile mainImageFile ) { - studyCreateUseCase.create(request, member.getMember().getId()); + studyCreateUseCase.create(member.getMember().getId(), request, mainImageFile); return new ResponseEntity<>( - ApiResponse.success(SuccessCode.STUDY_CREATE_SUCCESS), - HttpStatus.CREATED); + ApiResponse.success(SuccessCode.STUDY_CREATE_SUCCESS), + HttpStatus.CREATED); } } diff --git a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java index 77b8c602..70877968 100644 --- a/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java +++ b/src/main/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApi.java @@ -6,8 +6,11 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; import com.stumeet.server.common.annotation.WebAdapter; +import com.stumeet.server.common.annotation.validator.NullOrImageFile; import com.stumeet.server.common.auth.model.LoginMember; import com.stumeet.server.common.model.ApiResponse; import com.stumeet.server.common.response.SuccessCode; @@ -28,9 +31,10 @@ public class StudyUpdateApi { public ResponseEntity> update( @AuthenticationPrincipal LoginMember member, @PathVariable Long studyId, - @Valid StudyUpdateCommand request + @RequestPart @Valid StudyUpdateCommand request, + @RequestPart(required = false) @Valid @NullOrImageFile MultipartFile mainImageFile ) { - studyUpdateUseCase.update(studyId, member.getMember().getId(), request); + studyUpdateUseCase.update(studyId, member.getMember().getId(), request, mainImageFile); return new ResponseEntity<>( ApiResponse.success(SuccessCode.UPDATE_SUCCESS), diff --git a/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java b/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java index e73a8897..73d45745 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/StudyCreateUseCase.java @@ -1,8 +1,10 @@ package com.stumeet.server.study.application.port.in; +import org.springframework.web.multipart.MultipartFile; + import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; public interface StudyCreateUseCase { - Long create(StudyCreateCommand command, Long memberId); + Long create(Long memberId, StudyCreateCommand command, MultipartFile mainImageFile); } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java b/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java index 1a8dbea3..c7ba64d9 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/StudyUpdateUseCase.java @@ -1,8 +1,10 @@ package com.stumeet.server.study.application.port.in; +import org.springframework.web.multipart.MultipartFile; + import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; public interface StudyUpdateUseCase { - void update(Long studyId, Long memberId, StudyUpdateCommand command); + void update(Long studyId, Long memberId, StudyUpdateCommand command, MultipartFile mainImageFile); } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/command/StudyCreateCommand.java b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyCreateCommand.java index 0eda0ffb..38db0a47 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/command/StudyCreateCommand.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyCreateCommand.java @@ -4,9 +4,6 @@ import java.time.LocalTime; import java.util.List; -import org.springframework.web.multipart.MultipartFile; - -import com.stumeet.server.common.annotation.validator.NullOrImageFile; import com.stumeet.server.common.annotation.validator.NullOrNotBlank; import com.stumeet.server.study.domain.RepetitionType; @@ -15,8 +12,6 @@ public record StudyCreateCommand( - @NullOrImageFile(message = "이미지 파일을 첨부해주세요") - MultipartFile image, @NotBlank(message = "스터디 분야를 입력해주세요.") String studyField, @NotBlank(message = "이름을 입력해주세요.") @@ -35,7 +30,9 @@ public record StudyCreateCommand( LocalTime meetingTime, @NotNull(message = "정기모임 반복 유형을 입력해주세요.") RepetitionType meetingRepetitionType, + @NotNull(message = "정기모임 반복 일정을 입력해주세요.") List meetingRepetitionDates, + @NotNull(message = "스터디 태그를 입력해주세요.") List studyTags ) { } diff --git a/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java index 8a9e2e8c..dcaca964 100644 --- a/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java +++ b/src/main/java/com/stumeet/server/study/application/port/in/command/StudyUpdateCommand.java @@ -4,9 +4,6 @@ import java.time.LocalTime; import java.util.List; -import org.springframework.web.multipart.MultipartFile; - -import com.stumeet.server.common.annotation.validator.NullOrImageFile; import com.stumeet.server.common.annotation.validator.NullOrNotBlank; import com.stumeet.server.study.domain.RepetitionType; @@ -14,27 +11,27 @@ import jakarta.validation.constraints.NotNull; public record StudyUpdateCommand( - @NullOrImageFile(message = "이미지 파일을 첨부해주세요") - MultipartFile newImage, - @NotBlank(message = "스터디 분야를 입력해주세요.") - String studyField, - @NotBlank(message = "이름을 입력해주세요.") - String name, - @NotBlank(message = "소개글을 입력해주세요.") - String intro, - @NotBlank(message = "지역을 입력해주세요.") - String region, - @NullOrNotBlank(message = "규칙은 공백일 수 없습니다.") - String rule, - @NotNull(message = "시작일을 입력해주세요.") - LocalDate startDate, - @NotNull(message = "종료일을 입력해주세요.") - LocalDate endDate, - @NotNull(message = "정기모임 시간을 입력해주세요.") - LocalTime meetingTime, - @NotNull(message = "정기모임 반복 유형을 입력해주세요.") - RepetitionType meetingRepetitionType, - List meetingRepetitionDates, - List studyTags + @NotBlank(message = "스터디 분야를 입력해주세요.") + String studyField, + @NotBlank(message = "이름을 입력해주세요.") + String name, + @NotBlank(message = "소개글을 입력해주세요.") + String intro, + @NotBlank(message = "지역을 입력해주세요.") + String region, + @NullOrNotBlank(message = "규칙은 공백일 수 없습니다.") + String rule, + @NotNull(message = "시작일을 입력해주세요.") + LocalDate startDate, + @NotNull(message = "종료일을 입력해주세요.") + LocalDate endDate, + @NotNull(message = "정기모임 시간을 입력해주세요.") + LocalTime meetingTime, + @NotNull(message = "정기모임 반복 유형을 입력해주세요.") + RepetitionType meetingRepetitionType, + @NotNull(message = "정기모임 반복 일정을 입력해주세요.") + List meetingRepetitionDates, + @NotNull(message = "스터디 태그를 입력해주세요.") + List studyTags ) { } diff --git a/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java b/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java index babe9c77..e63a619b 100644 --- a/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java +++ b/src/main/java/com/stumeet/server/study/application/service/StudyCreateService.java @@ -5,6 +5,7 @@ import com.stumeet.server.study.application.port.out.StudyTagCommandPort; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import com.stumeet.server.common.annotation.UseCase; import com.stumeet.server.file.application.port.in.FileUploadUseCase; @@ -32,10 +33,10 @@ public class StudyCreateService implements StudyCreateUseCase { private final StudyUseCaseMapper studyUseCaseMapper; @Override - public Long create(StudyCreateCommand command, Long memberId) { + public Long create(Long memberId, StudyCreateCommand command, MultipartFile mainImageFile) { memberValidationUseCase.checkById(memberId); - FileUrl mainImageUrl = fileUploadUseCase.uploadStudyMainImage(command.image()); + FileUrl mainImageUrl = fileUploadUseCase.uploadStudyMainImage(mainImageFile); Study study = Study.create(command, mainImageUrl.url()); Long studyCreatedId = studyCommandPort.save(study).getId(); diff --git a/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java b/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java index 5956a093..ccaedc22 100644 --- a/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java +++ b/src/main/java/com/stumeet/server/study/application/service/StudyUpdateService.java @@ -1,10 +1,10 @@ package com.stumeet.server.study.application.service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import com.stumeet.server.common.annotation.UseCase; import com.stumeet.server.file.application.port.in.FileUploadUseCase; -import com.stumeet.server.file.application.port.out.FileUrl; import com.stumeet.server.member.application.port.in.MemberValidationUseCase; import com.stumeet.server.study.application.port.in.StudyUpdateUseCase; import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; @@ -30,15 +30,18 @@ public class StudyUpdateService implements StudyUpdateUseCase { private final StudyTagCommandPort studyTagCommandPort; @Override - public void update(Long studyId, Long memberId, StudyUpdateCommand command) { + public void update(Long studyId, Long memberId, StudyUpdateCommand command, MultipartFile mainImageFile) { memberValidationUseCase.checkById(memberId); studyMemberValidationUseCase.checkStudyJoinMember(studyId, memberId); studyMemberValidationUseCase.checkAdmin(studyId, memberId); Study existingStudy = studyQueryPort.getById(studyId); - FileUrl mainImageUrl = fileUploadUseCase.uploadStudyMainImage(command.newImage()); - Study updatedStudy = Study.update(command, existingStudy, mainImageUrl.url()); + String mainImageUrl = mainImageFile != null + ? fileUploadUseCase.uploadStudyMainImage(mainImageFile).url() + : existingStudy.getImageUrl(); + + Study updatedStudy = Study.update(command, existingStudy, mainImageUrl); studyCommandPort.save(updatedStudy); if (!existingStudy.isStudyTagsEquals(updatedStudy.getStudyTags())) { diff --git a/src/main/java/com/stumeet/server/study/domain/Study.java b/src/main/java/com/stumeet/server/study/domain/Study.java index 4b95a1af..d5e107c4 100644 --- a/src/main/java/com/stumeet/server/study/domain/Study.java +++ b/src/main/java/com/stumeet/server/study/domain/Study.java @@ -59,7 +59,7 @@ public static Study create(StudyCreateCommand command, String imageUrl) { .build(); } - public static Study update(StudyUpdateCommand command, Study existingStudy, String newImageUrl) { + public static Study update(StudyUpdateCommand command, Study existingStudy, String mainImageUrl) { return Study.builder() .id(existingStudy.getId()) .studyDomain(StudyDomain.of(StudyField.getByName(command.studyField()), command.studyTags())) @@ -72,7 +72,7 @@ public static Study update(StudyUpdateCommand command, Study existingStudy, Stri StudyMeetingSchedule.of( command.meetingTime(), Repetition.of(command.meetingRepetitionType(), command.meetingRepetitionDates()))) - .imageUrl(newImageUrl != null ? newImageUrl : existingStudy.imageUrl) + .imageUrl(mainImageUrl) .isFinished(existingStudy.isFinished) .isDeleted(existingStudy.isDeleted) .build(); From 507256bef87e5a8c4b75bf696b8a6af418b5f8de Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 23:04:44 +0900 Subject: [PATCH 13/17] =?UTF-8?q?:white=5Fcheck=5Fmark:=20[STMT-167]=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=83=9D=EC=84=B1/=EC=88=98?= =?UTF-8?q?=EC=A0=95=20api=20null=20=EC=97=86=EC=95=A0=EA=B8=B0=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stumeet/server/stub/RepetitionStub.java | 2 +- .../com/stumeet/server/stub/StudyStub.java | 47 ++++- .../adapter/in/web/StudyCreateApiTest.java | 199 +++++++----------- .../adapter/in/web/StudyUpdateApiTest.java | 115 +++------- .../server/study/domain/RepetitionTest.java | 19 +- 5 files changed, 154 insertions(+), 228 deletions(-) diff --git a/src/test/java/com/stumeet/server/stub/RepetitionStub.java b/src/test/java/com/stumeet/server/stub/RepetitionStub.java index d533faa6..42d6d59e 100644 --- a/src/test/java/com/stumeet/server/stub/RepetitionStub.java +++ b/src/test/java/com/stumeet/server/stub/RepetitionStub.java @@ -8,7 +8,7 @@ public class RepetitionStub { public static Repetition getDailyRepetition() { - return Repetition.of(RepetitionType.DAILY, null); + return Repetition.of(RepetitionType.DAILY, List.of()); } public static Repetition getWeeklyRepetition() { diff --git a/src/test/java/com/stumeet/server/stub/StudyStub.java b/src/test/java/com/stumeet/server/stub/StudyStub.java index fabd1575..a2a9b89b 100644 --- a/src/test/java/com/stumeet/server/stub/StudyStub.java +++ b/src/test/java/com/stumeet/server/stub/StudyStub.java @@ -14,10 +14,7 @@ public class StudyStub { private static final MockMultipartFile image = - new MockMultipartFile("image", "test.jpg", "image/jpeg", "test".getBytes()); - - private static final MockMultipartFile newImage = - new MockMultipartFile("newImage", "test.jpg", "image/jpeg", "test".getBytes()); + new MockMultipartFile("mainImageFile", "test.jpg", "image/jpeg", "test".getBytes()); private StudyStub() { @@ -31,9 +28,12 @@ public static Long getInvalidStudyId() { return 0L; } + public static MockMultipartFile getStudyMainImageFile() { + return image; + } + public static StudyCreateCommand getStudyCreateCommand() { return new StudyCreateCommand( - image, "어학", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", @@ -48,9 +48,24 @@ public static StudyCreateCommand getStudyCreateCommand() { ); } + public static StudyCreateCommand getStudyCreateCommandWithoutTags() { + return new StudyCreateCommand( + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of() + ); + } + public static StudyCreateCommand getInvalidFieldStudyCreateCommand() { return new StudyCreateCommand( - image, "존재하지 않는 분야 값", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", @@ -67,7 +82,6 @@ public static StudyCreateCommand getInvalidFieldStudyCreateCommand() { public static StudyCreateCommand getInvalidMeetingScheduleStudyCreateCommand() { return new StudyCreateCommand( - image, "어학", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", @@ -84,7 +98,6 @@ public static StudyCreateCommand getInvalidMeetingScheduleStudyCreateCommand() { public static StudyUpdateCommand getStudyUpdateCommand() { return new StudyUpdateCommand( - newImage, "어학", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", @@ -99,9 +112,24 @@ public static StudyUpdateCommand getStudyUpdateCommand() { ); } + public static StudyUpdateCommand getStudyUpdateCommandWithoutTags() { + return new StudyUpdateCommand( + "어학", + "영어 회화 클럽", + "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", + "부산", + "주 1회 대면 미팅 및 온라인 토론", + LocalDate.parse("2024-05-15"), + LocalDate.parse("2024-12-15"), + LocalTime.parse("18:30:00"), + RepetitionType.valueOf("WEEKLY"), + List.of("월", "금"), + List.of() + ); + } + public static StudyUpdateCommand getInvalidFieldStudyUpdateCommand() { return new StudyUpdateCommand( - newImage, "존재하지 않는 분야 값", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", @@ -118,7 +146,6 @@ public static StudyUpdateCommand getInvalidFieldStudyUpdateCommand() { public static StudyUpdateCommand getInvalidMeetingScheduleStudyUpdateCommand() { return new StudyUpdateCommand( - newImage, "어학", "영어 회화 클럽", "매주 영어로 대화하며 언어 실력을 향상시키는 스터디 그룹입니다.", diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java index 13db5244..09d83012 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java @@ -7,13 +7,12 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.springframework.restdocs.snippet.Attributes.key; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; import com.stumeet.server.common.auth.model.AuthenticationHeader; import com.stumeet.server.helper.WithMockMember; @@ -37,82 +36,65 @@ void successTest() throws Exception { StudyCreateCommand request = StudyStub.getStudyCreateCommand(); mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.image()) - .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andDo(document("create-study/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders( - headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()) - .description("서버로부터 전달받은 액세스 토큰") - ), - requestParts( - partWithName("image").description("스터디 메인 이미지 파일").optional() - ), - queryParameters( - parameterWithName("studyField").description("스터디 분야 ID") - .attributes(key("constraint").value("NotNull, 스터디 분야 ID를 입력해주세요.")), - parameterWithName("name").description("스터디 이름") - .attributes(key("constraint").value("NotBlank, 이름을 입력해주세요.")), - parameterWithName("intro").description("소개") - .attributes(key("constraint").value("NotBlank, 소개글을 입력해주세요.")), - parameterWithName("region").description("활동 지역") - .attributes(key("constraint").value("NotBlank, 지역을 입력해주세요.")), - parameterWithName("rule").description("규칙").optional() - .attributes(key("constraint").value("NullOrNotBlank, 규칙은 공백일 수 없습니다.")), - parameterWithName("startDate").description("시작일") - .attributes(key("constraint").value("NotNull, 시작일을 입력해주세요.")), - parameterWithName("endDate").description("종료일") - .attributes(key("constraint").value("NotNull, 종료일을 입력해주세요.")), - parameterWithName("meetingTime").description("정기모임 시간") - .attributes(key("constraint").value("NotNull, 정기모임 시간을 입력해주세요.")), - parameterWithName("meetingRepetitionType").description("정기모임 반복 유형") - .attributes(key("constraint").value("NotNull, 정기모임 반복 유형을 입력해주세요.")), - parameterWithName("meetingRepetitionDates").description("정기모임 반복 일자").optional(), - parameterWithName("studyTags").description("스터디 태그").optional() - ), - responseFields( - fieldWithPath("code").description("응답 상태"), - fieldWithPath("message").description("응답 메시지") - ))); + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andDo(document("create-study/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()) + .description("서버로부터 전달받은 액세스 토큰") + ), + requestParts( + partWithName("mainImageFile").description("스터디 메인 이미지 파일").optional(), + partWithName("request").description("스터디 수정 요청 본문") + ), + requestPartFields( "request", + fieldWithPath("studyField").description("스터디 분야 ID"), + fieldWithPath("name").description("스터디 이름"), + fieldWithPath("intro").description("소개"), + fieldWithPath("region").description("활동 지역"), + fieldWithPath("rule").description("규칙").optional(), + fieldWithPath("startDate").description("시작일"), + fieldWithPath("endDate").description("종료일"), + fieldWithPath("meetingTime").description("정기모임 시간"), + fieldWithPath("meetingRepetitionType").description("정기모임 반복 유형"), + fieldWithPath("meetingRepetitionDates").description("정기모임 반복 일자 | DAILY 유형의 경우 빈 배열"), + fieldWithPath("studyTags").description("스터디 태그 | 값이 없는 경우 빈 배열") + ), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); } @Test @WithMockMember - @DisplayName("[성공] 이미지 파일이 없어도 요청에 성공한다.") + @DisplayName("[성공] 이미지 파일이 없는 경우에도 요청에 성공한다.") void successWithoutImageFile() throws Exception { StudyCreateCommand request = StudyStub.getStudyCreateCommand(); mockMvc.perform(multipart(path) - .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", String.valueOf(request.studyField())) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()); + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockMember + @DisplayName("[성공] 스터디 태그가 빈 배열인 경우 요청에 성공한다.") + void successWithoutStudyTags() throws Exception { + StudyCreateCommand request = StudyStub.getStudyCreateCommandWithoutTags(); + + mockMvc.perform(multipart(path) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); } @Test @@ -122,31 +104,20 @@ void failWithNotExistStudyFieldId() throws Exception { StudyCreateCommand request = StudyStub.getInvalidFieldStudyCreateCommand(); mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.image()) - .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andDo(document("create-study/fail/study-field-not-found", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( - "서버로부터 전달받은 액세스 토큰")), - responseFields( - fieldWithPath("code").description("응답 상태"), - fieldWithPath("message").description("응답 메시지") - ))); + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andDo(document("create-study/fail/study-field-not-found", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( + "서버로부터 전달받은 액세스 토큰")), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); } @Test @@ -156,30 +127,20 @@ void failWithInvalidStudyMeetingSchedule() throws Exception { StudyCreateCommand request = StudyStub.getInvalidMeetingScheduleStudyCreateCommand(); mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.image()) - .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andDo(document("create-study/fail/invalid-study-meeting-schedule", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( - "서버로부터 전달받은 액세스 토큰")), - responseFields( - fieldWithPath("code").description("응답 상태"), - fieldWithPath("message").description("응답 메시지") - ))); + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(document("create-study/fail/invalid-study-meeting-schedule", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders(headerWithName(AuthenticationHeader.ACCESS_TOKEN.getName()).description( + "서버로부터 전달받은 액세스 토큰")), + responseFields( + fieldWithPath("code").description("응답 상태"), + fieldWithPath("message").description("응답 메시지") + ))); } } } \ No newline at end of file diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java index 2429f3fd..7835aeb6 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java @@ -6,14 +6,13 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.*; -import static org.springframework.restdocs.snippet.Attributes.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; import org.springframework.test.web.servlet.request.RequestPostProcessor; import com.stumeet.server.common.auth.model.AuthenticationHeader; @@ -43,21 +42,10 @@ void successUpdateTest() throws Exception { }; mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.newImage()) + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("update-study/success", @@ -68,30 +56,21 @@ void successUpdateTest() throws Exception { .description("서버로부터 전달받은 액세스 토큰") ), requestParts( - partWithName("newImage").description("새로운 스터디 메인 이미지 파일").optional() - .description("새로운 이미지가 첨부되지 않은 경우 생략 가능합니다.") + partWithName("mainImageFile").description("스터디 메인 이미지 파일").optional(), + partWithName("request").description("스터디 수정 요청 본문") ), - queryParameters( - parameterWithName("studyField").description("스터디 분야 ID") - .attributes(key("constraint").value("NotNull, 스터디 분야 ID를 입력해주세요.")), - parameterWithName("name").description("스터디 이름") - .attributes(key("constraint").value("NotBlank, 이름을 입력해주세요.")), - parameterWithName("intro").description("소개") - .attributes(key("constraint").value("NotBlank, 소개글을 입력해주세요.")), - parameterWithName("region").description("활동 지역") - .attributes(key("constraint").value("NotBlank, 지역을 입력해주세요.")), - parameterWithName("rule").description("규칙").optional() - .attributes(key("constraint").value("NullOrNotBlank, 규칙은 공백일 수 없습니다.")), - parameterWithName("startDate").description("시작일") - .attributes(key("constraint").value("NotNull, 시작일을 입력해주세요.")), - parameterWithName("endDate").description("종료일") - .attributes(key("constraint").value("NotNull, 종료일을 입력해주세요.")), - parameterWithName("meetingTime").description("정기모임 시간") - .attributes(key("constraint").value("NotNull, 정기모임 시간을 입력해주세요.")), - parameterWithName("meetingRepetitionType").description("정기모임 반복 유형") - .attributes(key("constraint").value("NotNull, 정기모임 반복 유형을 입력해주세요.")), - parameterWithName("meetingRepetitionDates").description("정기모임 반복 일자").optional(), - parameterWithName("studyTags").description("스터디 태그").optional() + requestPartFields( "request", + fieldWithPath("studyField").description("스터디 분야 ID"), + fieldWithPath("name").description("스터디 이름"), + fieldWithPath("intro").description("소개"), + fieldWithPath("region").description("활동 지역"), + fieldWithPath("rule").description("규칙").optional(), + fieldWithPath("startDate").description("시작일"), + fieldWithPath("endDate").description("종료일"), + fieldWithPath("meetingTime").description("정기모임 시간"), + fieldWithPath("meetingRepetitionType").description("정기모임 반복 유형"), + fieldWithPath("meetingRepetitionDates").description("정기모임 반복 일자 | DAILY 유형의 경우 빈 배열"), + fieldWithPath("studyTags").description("스터디 태그 | 값이 없는 경우 빈 배열") ), responseFields( fieldWithPath("code").description("응답 상태"), @@ -101,7 +80,7 @@ void successUpdateTest() throws Exception { @Test @WithMockMember - @DisplayName("[성공] 새 이미지가 없는 요청으로 스터디 정보 수정에 성공한다.") + @DisplayName("[성공] 이미지이 없는 요청으로 스터디 정보 수정에 성공한다.") void successUpdateWithoutImageTest() throws Exception { StudyUpdateCommand request = StudyStub.getStudyUpdateCommand(); @@ -111,20 +90,9 @@ void successUpdateWithoutImageTest() throws Exception { }; mockMvc.perform(multipart(path) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } @@ -133,7 +101,7 @@ void successUpdateWithoutImageTest() throws Exception { @WithMockMember @DisplayName("[성공] 태그가 없는 요청으로 스터디 정보 수정에 성공한다.") void successUpdateWithoutTagsTest() throws Exception { - StudyUpdateCommand request = StudyStub.getStudyUpdateCommand(); + StudyUpdateCommand request = StudyStub.getStudyUpdateCommandWithoutTags(); RequestPostProcessor patchMethod = http -> { http.setMethod("PATCH"); @@ -141,20 +109,10 @@ void successUpdateWithoutTagsTest() throws Exception { }; mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.newImage()) + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } @@ -171,21 +129,10 @@ void failWithNotExistStudyFieldId() throws Exception { }; mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.newImage()) + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("meetingRepetitionDates", request.meetingRepetitionDates().toArray(String[]::new)) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()) .andDo(document("update-study/fail/study-field-not-found", @@ -211,20 +158,10 @@ void failWithInvalidStudyMeetingSchedule() throws Exception { }; mockMvc.perform(multipart(path) - .file((MockMultipartFile) request.newImage()) + .file(StudyStub.getStudyMainImageFile()) + .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) - .queryParam("studyField", request.studyField()) - .queryParam("name", request.name()) - .queryParam("intro", request.intro()) - .queryParam("region", request.region()) - .queryParam("rule", request.rule()) - .queryParam("startDate", String.valueOf(request.startDate())) - .queryParam("endDate", String.valueOf(request.endDate())) - .queryParam("meetingTime", String.valueOf(request.meetingTime())) - .queryParam("meetingRepetitionType", request.meetingRepetitionType().toString()) - .queryParam("studyTags", request.studyTags().toArray(String[]::new)) - .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andDo(document("update-study/fail/invalid-study-meeting-schedule", diff --git a/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java b/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java index 3772ba47..cf4f8887 100644 --- a/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java +++ b/src/test/java/com/stumeet/server/study/domain/RepetitionTest.java @@ -19,23 +19,24 @@ public class RepetitionTest extends UnitTest { class create { @Test - @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 null을 전달 받았을 경우 생성에 성공한다.") + @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 빈 리스트를 전달 받았을 경우 생성에 성공한다.") void weeklyTypeNullDatesSuccessTest() { - assertThatCode(() -> Repetition.of(RepetitionType.DAILY, null)) + assertThatCode(() -> Repetition.of(RepetitionType.DAILY, List.of())) .doesNotThrowAnyException(); - assertThat(Repetition.of(RepetitionType.DAILY, null)) + assertThat(Repetition.of(RepetitionType.DAILY, List.of())) .usingRecursiveComparison() .isEqualTo(RepetitionStub.getDailyRepetition()); } @Test - @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 null이 아닌 값을 전달 받았을 경우 생성에 성공한다.") + @DisplayName("[성공] 반복 유형이 DAILY일 때, 인수 dates에 빈 리스트가 아닌 값을 전달 받았을 경우 생성에 성공한다.") void weeklyTypeInvalidDatesSuccessTest() { assertThat( Repetition.of(RepetitionType.DAILY, List.of("월", "화", "수", "목", "금", "토", "일")) - .getDates()) - .isNull(); + .getDates() + .isEmpty()) + .isTrue(); } @Test @@ -51,12 +52,12 @@ void otherTypesSuccessTest() { } @Test - @DisplayName("[실패] 반복 타입이 DAILY가 아닌 경우 반복일이 null이면 테스트에 실패한다.") + @DisplayName("[실패] 반복 타입이 DAILY가 아닌 경우 반복일이 빈 리스트인 경우 테스트에 실패한다.") void invalidDatesFailTest() { - assertThatThrownBy(() -> Repetition.of(RepetitionType.WEEKLY, null)) + assertThatThrownBy(() -> Repetition.of(RepetitionType.WEEKLY, List.of())) .isInstanceOf(InvalidRepetitionDatesException.class); - assertThatThrownBy(() -> Repetition.of(RepetitionType.MONTHLY, null)) + assertThatThrownBy(() -> Repetition.of(RepetitionType.MONTHLY, List.of())) .isInstanceOf(InvalidRepetitionDatesException.class); } } From 2f534ca59945af7ae35810603d41e85cb8c10ae6 Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 23:05:44 +0900 Subject: [PATCH 14/17] =?UTF-8?q?:lipstick:=20[STMT-167]=20test=20db=20?= =?UTF-8?q?=EC=98=88=EC=8B=9C=20row=20=EA=B0=92=EC=9D=84=20=ED=98=84?= =?UTF-8?q?=EC=8B=A4=EC=A0=81=EC=9D=B8=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/db/setup.sql | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/resources/db/setup.sql b/src/test/resources/db/setup.sql index 1992368d..af5afe38 100644 --- a/src/test/resources/db/setup.sql +++ b/src/test/resources/db/setup.sql @@ -1,12 +1,15 @@ INSERT INTO study (id, study_field, name, region, intro, rule, image, meeting_time, meeting_repetition, start_date, end_date) -VALUES (1, 'PROGRAMMING', '[임시] 프로그래밍 스터디', '서울', '프로그래밍 스터디 입니다.', '- 매주 목요일 8시\n- 장소: 안암역\n- 제시간에 제출하기!', +VALUES (1, 'PROGRAMMING', 'effective java 스터디', '서울', 'java 스터디 입니다.', '- 장소: 태릉입구역\n- 제시간에 제출하기!', 'https://stumeet.s3.ap-northeast-2.amazonaws.com/study/1/image/2023062711172178420.png', - '21:00:00', 'WEEKLY;월;수;목;', '2024-04-01', '2024-05-01'); + '18:00:00', 'WEEKLY;월;수;목;', '2024-04-01', '2024-05-01'); -INSERT INTO study_tag (study_id, name) VALUES (1, 'springboot'); -INSERT INTO study_tag (study_id, name) VALUES (1, 'java'); -INSERT INTO study_tag (study_id, name) VALUES (1, '객체지향프로그래밍'); +INSERT INTO study_tag (study_id, name) +VALUES (1, 'springboot'); +INSERT INTO study_tag (study_id, name) +VALUES (1, 'java'); +INSERT INTO study_tag (study_id, name) +VALUES (1, '객체지향프로그래밍'); INSERT INTO member (id, name, image, region, profession_id, role, auth_type, tier, experience, is_deleted, deleted_at) VALUES (1, 'test1', 'http://localhost:4572/user/1/profile/2024030416531039839905-b7e8-4ad3-9552-7d9cbc01cb14-test.jpg', From 6981f4dd4e5a686de9de7a15a4137bb7d82ae517 Mon Sep 17 00:00:00 2001 From: 05AM Date: Mon, 15 Apr 2024 23:06:28 +0900 Subject: [PATCH 15/17] =?UTF-8?q?:memo:=20[STMT-167]=20request=20part=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20API=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 83c4da90..11040363 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -213,14 +213,11 @@ include::{snippets}/get-study-detail/success/http-request.adoc[] include::{snippets}/get-study-detail/success/request-headers.adoc[] ===== 응답 성공 (200) - include::{snippets}/get-study-detail/success/response-body.adoc[] include::{snippets}/get-study-detail/success/response-fields.adoc[] ===== 응답 실패 (404) - .존재하지 않는 스터디 ID를 요청한 경우 - include::{snippets}/get-study-detail/fail/not-found/response-body.adoc[] include::{snippets}/get-study-detail/fail/not-found/response-fields.adoc[] @@ -240,23 +237,19 @@ include::{snippets}/create-study/success/request-headers.adoc[] ===== 파트 include::{snippets}/create-study/success/request-parts.adoc[] -===== 매개변수 -include::{snippets}/create-study/success/query-parameters.adoc[] +===== 요청 본문 매개변수 +include::{snippets}/create-study/success/request-part-request-fields.adoc[] ===== 응답 성공 (200) - include::{snippets}/create-study/success/response-body.adoc[] include::{snippets}/create-study/success/response-fields.adoc[] ===== 응답 실패 (404) - .존재하지 않는 스터디 분야 ID를 요청한 경우 - include::{snippets}/create-study/fail/study-field-not-found/response-body.adoc[] include::{snippets}/create-study/fail/study-field-not-found/response-fields.adoc[] .일정 반복 유형 DAILY일 때를 제외하고 반복일을 null 값으로 요청한 경우 - include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-body.adoc[] include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-fields.adoc[] @@ -276,23 +269,19 @@ include::{snippets}/update-study/success/request-headers.adoc[] ===== 파트 include::{snippets}/update-study/success/request-parts.adoc[] -===== 매개변수 -include::{snippets}/update-study/success/query-parameters.adoc[] +===== 요청 본문 매개변수 +include::{snippets}/update-study/success/request-part-request-fields.adoc[] ===== 응답 성공 (200) - include::{snippets}/update-study/success/response-body.adoc[] include::{snippets}/update-study/success/response-fields.adoc[] ===== 응답 실패 (404) - .존재하지 않는 스터디 분야 ID를 요청한 경우 - include::{snippets}/update-study/fail/study-field-not-found/response-body.adoc[] include::{snippets}/update-study/fail/study-field-not-found/response-fields.adoc[] .일정 반복 유형 DAILY일 때를 제외하고 반복일을 null 값으로 요청한 경우 - include::{snippets}/update-study/fail/invalid-study-meeting-schedule/response-body.adoc[] include::{snippets}/update-study/fail/invalid-study-meeting-schedule/response-fields.adoc[] From 2389781124601708b37e6516ea11c426a15885a3 Mon Sep 17 00:00:00 2001 From: 05AM Date: Fri, 19 Apr 2024 21:15:43 +0900 Subject: [PATCH 16/17] =?UTF-8?q?:white=5Fcheck=5Fmark:=20[STMT-167]=20Mul?= =?UTF-8?q?tiPartFile=EC=9D=84=20=EB=B2=94=EC=9A=A9=EC=84=B1=EC=9E=88?= =?UTF-8?q?=EA=B2=8C=20=EC=9A=94=EC=B2=AD=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=84=A3=EC=96=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/util/FileValidatorTest.java | 22 +++++++++----- .../service/FileUploadServiceTest.java | 10 ++++--- .../server/stub/MockMultipartFileStub.java | 29 ------------------ .../server/stub/MockMultipartStub.java | 30 +++++++++++++++++++ .../com/stumeet/server/stub/StudyStub.java | 9 ------ 5 files changed, 50 insertions(+), 50 deletions(-) delete mode 100644 src/test/java/com/stumeet/server/stub/MockMultipartFileStub.java create mode 100644 src/test/java/com/stumeet/server/stub/MockMultipartStub.java diff --git a/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java b/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java index aa4ca9fd..e3750b7e 100644 --- a/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java +++ b/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java @@ -9,14 +9,20 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.mock.web.MockMultipartFile; import com.stumeet.server.file.domain.exception.InvalidFileException; -import com.stumeet.server.stub.MockMultipartFileStub; +import com.stumeet.server.stub.MockMultipartStub; import com.stumeet.server.template.UnitTest; @DisplayName("File Validator 테스트") class FileValidatorTest extends UnitTest { + private final String PARAMETER_NAME = "file"; + private final MockMultipartFile jpegFile = MockMultipartStub.createJpegFile(PARAMETER_NAME); + private final MockMultipartFile jpgFile = MockMultipartStub.createJpgFile(PARAMETER_NAME); + private final MockMultipartFile pngFile = MockMultipartStub.createPngFile(PARAMETER_NAME); + @Nested @DisplayName("이미지 파일 검증") class ValidateImageFile { @@ -24,13 +30,13 @@ class ValidateImageFile { @Test @DisplayName("[성공] 유효한 파일이 주어졌을 때 파일 검증을 성공한다.") void validImageFile_validateImageFile_success() { - assertThatCode(() -> FileValidator.validateImageFile(MockMultipartFileStub.getJpegFile())) + assertThatCode(() -> FileValidator.validateImageFile(jpegFile)) .doesNotThrowAnyException(); - assertThatCode(() -> FileValidator.validateImageFile(MockMultipartFileStub.getJpgFile())) + assertThatCode(() -> FileValidator.validateImageFile(jpgFile)) .doesNotThrowAnyException(); - assertThatCode(() -> FileValidator.validateImageFile(MockMultipartFileStub.getPngFile())) + assertThatCode(() -> FileValidator.validateImageFile(pngFile)) .doesNotThrowAnyException(); } } @@ -42,7 +48,7 @@ class ValidateFileName { @Test @DisplayName("[성공] 유효한 파일 이름이 주어졌을 때 파일 검증을 성공한다.") void validFileName_validateFileName_success() { - assertThatCode(() -> FileValidator.validateFileName(MockMultipartFileStub.getJpegFile().getOriginalFilename())) + assertThatCode(() -> FileValidator.validateFileName(jpegFile.getOriginalFilename())) .doesNotThrowAnyException(); } @@ -91,13 +97,13 @@ class ValidateImageType { @Test @DisplayName("[성공] 유효한 파일이 주어졌을 때 true를 반환한다.") void validImageFile_isValidImageFile_success() { - assertThat(FileValidator.isValidImageFile(MockMultipartFileStub.getJpegFile())) + assertThat(FileValidator.isValidImageFile(jpegFile)) .isTrue(); - assertThat(FileValidator.isValidImageFile(MockMultipartFileStub.getJpgFile())) + assertThat(FileValidator.isValidImageFile(jpgFile)) .isTrue(); - assertThat(FileValidator.isValidImageFile(MockMultipartFileStub.getPngFile())) + assertThat(FileValidator.isValidImageFile(pngFile)) .isTrue(); } } diff --git a/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java b/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java index 862bde66..09f0abe8 100644 --- a/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java +++ b/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java @@ -8,11 +8,13 @@ import org.springframework.beans.factory.annotation.Autowired; import com.stumeet.server.stub.MemberStub; -import com.stumeet.server.stub.MockMultipartFileStub; +import com.stumeet.server.stub.MockMultipartStub; import com.stumeet.server.template.IntegrationTest; class FileUploadServiceTest extends IntegrationTest { + private final String PARAMETER_NAME = "file"; + @Autowired private FileUploadService fileUploadService; @@ -24,13 +26,13 @@ class UploadUserProfileImageFile { @Test @DisplayName("[성공] 유효한 파일이 주어졌을 때 파일 업로드에 성공한다.") void uploadUserProfileImage_success() { - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartFileStub.getJpegFile())) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createJpegFile(PARAMETER_NAME))) .doesNotThrowAnyException(); - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartFileStub.getJpgFile())) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createJpgFile(PARAMETER_NAME))) .doesNotThrowAnyException(); - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartFileStub.getPngFile())) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createPngFile(PARAMETER_NAME))) .doesNotThrowAnyException(); } } diff --git a/src/test/java/com/stumeet/server/stub/MockMultipartFileStub.java b/src/test/java/com/stumeet/server/stub/MockMultipartFileStub.java deleted file mode 100644 index d6c5df93..00000000 --- a/src/test/java/com/stumeet/server/stub/MockMultipartFileStub.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.stumeet.server.stub; - -import org.springframework.mock.web.MockMultipartFile; - -public class MockMultipartFileStub { - public static MockMultipartFile getJpegFile() { - return new MockMultipartFile( - "file", - "file.jpeg", - "image/jpeg", - "test".getBytes()); - } - - public static MockMultipartFile getJpgFile() { - return new MockMultipartFile( - "file", - "file.jpg", - "image/jpeg", - "test".getBytes()); - } - - public static MockMultipartFile getPngFile() { - return new MockMultipartFile( - "file", - "file.png", - "image/png", - "test".getBytes()); - } -} diff --git a/src/test/java/com/stumeet/server/stub/MockMultipartStub.java b/src/test/java/com/stumeet/server/stub/MockMultipartStub.java new file mode 100644 index 00000000..ada41952 --- /dev/null +++ b/src/test/java/com/stumeet/server/stub/MockMultipartStub.java @@ -0,0 +1,30 @@ +package com.stumeet.server.stub; + +import org.springframework.mock.web.MockMultipartFile; + +public class MockMultipartStub { + + public static MockMultipartFile createJpegFile(String parameterName) { + return new MockMultipartFile( + parameterName, + "file.jpeg", + "image/jpeg", + "test".getBytes()); + } + + public static MockMultipartFile createJpgFile(String parameterName) { + return new MockMultipartFile( + parameterName, + "file.jpg", + "image/jpeg", + "test".getBytes()); + } + + public static MockMultipartFile createPngFile(String parameterName) { + return new MockMultipartFile( + parameterName, + "file.png", + "image/png", + "test".getBytes()); + } +} diff --git a/src/test/java/com/stumeet/server/stub/StudyStub.java b/src/test/java/com/stumeet/server/stub/StudyStub.java index a2a9b89b..3d3de35b 100644 --- a/src/test/java/com/stumeet/server/stub/StudyStub.java +++ b/src/test/java/com/stumeet/server/stub/StudyStub.java @@ -5,17 +5,12 @@ import java.util.List; -import org.springframework.mock.web.MockMultipartFile; - import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; import com.stumeet.server.study.domain.RepetitionType; public class StudyStub { - private static final MockMultipartFile image = - new MockMultipartFile("mainImageFile", "test.jpg", "image/jpeg", "test".getBytes()); - private StudyStub() { } @@ -28,10 +23,6 @@ public static Long getInvalidStudyId() { return 0L; } - public static MockMultipartFile getStudyMainImageFile() { - return image; - } - public static StudyCreateCommand getStudyCreateCommand() { return new StudyCreateCommand( "어학", From 8c79e9a0da20124a6b43fa375199fe27c39acf81 Mon Sep 17 00:00:00 2001 From: 05AM Date: Fri, 19 Apr 2024 21:19:50 +0900 Subject: [PATCH 17/17] =?UTF-8?q?:white=5Fcheck=5Fmark:=20[STMT-167]=20Mul?= =?UTF-8?q?tiPart=20=EA=B4=80=EB=A0=A8=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20fac?= =?UTF-8?q?tory=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/util/FileValidatorTest.java | 8 +++---- .../MockMultipartFactory.java} | 10 +++++++-- .../service/FileUploadServiceTest.java | 12 ++++++---- .../adapter/in/web/StudyCreateApiTest.java | 9 +++++--- .../adapter/in/web/StudyUpdateApiTest.java | 22 ++++++++++--------- 5 files changed, 38 insertions(+), 23 deletions(-) rename src/test/java/com/stumeet/server/{stub/MockMultipartStub.java => factory/MockMultipartFactory.java} (66%) diff --git a/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java b/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java index e3750b7e..2cf712b0 100644 --- a/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java +++ b/src/test/java/com/stumeet/server/common/util/FileValidatorTest.java @@ -12,16 +12,16 @@ import org.springframework.mock.web.MockMultipartFile; import com.stumeet.server.file.domain.exception.InvalidFileException; -import com.stumeet.server.stub.MockMultipartStub; +import com.stumeet.server.factory.MockMultipartFactory; import com.stumeet.server.template.UnitTest; @DisplayName("File Validator 테스트") class FileValidatorTest extends UnitTest { private final String PARAMETER_NAME = "file"; - private final MockMultipartFile jpegFile = MockMultipartStub.createJpegFile(PARAMETER_NAME); - private final MockMultipartFile jpgFile = MockMultipartStub.createJpgFile(PARAMETER_NAME); - private final MockMultipartFile pngFile = MockMultipartStub.createPngFile(PARAMETER_NAME); + private final MockMultipartFile jpegFile = MockMultipartFactory.createJpegFile(PARAMETER_NAME); + private final MockMultipartFile jpgFile = MockMultipartFactory.createJpgFile(PARAMETER_NAME); + private final MockMultipartFile pngFile = MockMultipartFactory.createPngFile(PARAMETER_NAME); @Nested @DisplayName("이미지 파일 검증") diff --git a/src/test/java/com/stumeet/server/stub/MockMultipartStub.java b/src/test/java/com/stumeet/server/factory/MockMultipartFactory.java similarity index 66% rename from src/test/java/com/stumeet/server/stub/MockMultipartStub.java rename to src/test/java/com/stumeet/server/factory/MockMultipartFactory.java index ada41952..5e3dbc54 100644 --- a/src/test/java/com/stumeet/server/stub/MockMultipartStub.java +++ b/src/test/java/com/stumeet/server/factory/MockMultipartFactory.java @@ -1,8 +1,10 @@ -package com.stumeet.server.stub; +package com.stumeet.server.factory; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; -public class MockMultipartStub { +public class MockMultipartFactory { public static MockMultipartFile createJpegFile(String parameterName) { return new MockMultipartFile( @@ -27,4 +29,8 @@ public static MockMultipartFile createPngFile(String parameterName) { "image/png", "test".getBytes()); } + + public static MockPart createMockPart(byte[] request) { + return new MockPart("request", "", request, MediaType.APPLICATION_JSON); + } } diff --git a/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java b/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java index 09f0abe8..df3b42fe 100644 --- a/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java +++ b/src/test/java/com/stumeet/server/file/application/service/FileUploadServiceTest.java @@ -6,14 +6,18 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockMultipartFile; import com.stumeet.server.stub.MemberStub; -import com.stumeet.server.stub.MockMultipartStub; +import com.stumeet.server.factory.MockMultipartFactory; import com.stumeet.server.template.IntegrationTest; class FileUploadServiceTest extends IntegrationTest { private final String PARAMETER_NAME = "file"; + private final MockMultipartFile jpegFile = MockMultipartFactory.createJpegFile(PARAMETER_NAME); + private final MockMultipartFile jpgFile = MockMultipartFactory.createJpgFile(PARAMETER_NAME); + private final MockMultipartFile pngFile = MockMultipartFactory.createPngFile(PARAMETER_NAME); @Autowired private FileUploadService fileUploadService; @@ -26,13 +30,13 @@ class UploadUserProfileImageFile { @Test @DisplayName("[성공] 유효한 파일이 주어졌을 때 파일 업로드에 성공한다.") void uploadUserProfileImage_success() { - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createJpegFile(PARAMETER_NAME))) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), jpegFile)) .doesNotThrowAnyException(); - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createJpgFile(PARAMETER_NAME))) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), jpgFile)) .doesNotThrowAnyException(); - assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), MockMultipartStub.createPngFile(PARAMETER_NAME))) + assertThatCode(() -> fileUploadService.uploadUserProfileImage(MemberStub.getMemberId(), pngFile)) .doesNotThrowAnyException(); } } diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java index 09d83012..bcb5fcf5 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyCreateApiTest.java @@ -12,10 +12,12 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockPart; import com.stumeet.server.common.auth.model.AuthenticationHeader; import com.stumeet.server.helper.WithMockMember; +import com.stumeet.server.factory.MockMultipartFactory; import com.stumeet.server.stub.StudyStub; import com.stumeet.server.stub.TokenStub; import com.stumeet.server.study.application.port.in.command.StudyCreateCommand; @@ -28,6 +30,7 @@ class StudyCreateApiTest extends ApiTest { class CreateStudy { private final String path = "/api/v1/studies"; + private final MockMultipartFile studyMainImage = MockMultipartFactory.createJpegFile("mainImageFile"); @Test @WithMockMember @@ -36,7 +39,7 @@ void successTest() throws Exception { StudyCreateCommand request = StudyStub.getStudyCreateCommand(); mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) + .file(studyMainImage) .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -104,7 +107,7 @@ void failWithNotExistStudyFieldId() throws Exception { StudyCreateCommand request = StudyStub.getInvalidFieldStudyCreateCommand(); mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) + .file(studyMainImage) .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -127,7 +130,7 @@ void failWithInvalidStudyMeetingSchedule() throws Exception { StudyCreateCommand request = StudyStub.getInvalidMeetingScheduleStudyCreateCommand(); mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) + .file(studyMainImage) .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java index 7835aeb6..52bc491e 100644 --- a/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java +++ b/src/test/java/com/stumeet/server/study/adapter/in/web/StudyUpdateApiTest.java @@ -12,11 +12,12 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockPart; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.RequestPostProcessor; import com.stumeet.server.common.auth.model.AuthenticationHeader; import com.stumeet.server.helper.WithMockMember; +import com.stumeet.server.factory.MockMultipartFactory; import com.stumeet.server.stub.StudyStub; import com.stumeet.server.stub.TokenStub; import com.stumeet.server.study.application.port.in.command.StudyUpdateCommand; @@ -29,6 +30,7 @@ class StudyUpdateApiTest extends ApiTest { class CreateStudy { private final String path = "/api/v1/studies/" + StudyStub.getStudyId(); + private final MockMultipartFile studyMainImage = MockMultipartFactory.createJpegFile("mainImageFile"); @Test @WithMockMember @@ -42,8 +44,8 @@ void successUpdateTest() throws Exception { }; mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) - .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .file(studyMainImage) + .part(MockMultipartFactory.createMockPart(objectMapper.writeValueAsBytes(request))) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -90,7 +92,7 @@ void successUpdateWithoutImageTest() throws Exception { }; mockMvc.perform(multipart(path) - .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .part(MockMultipartFactory.createMockPart(objectMapper.writeValueAsBytes(request))) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -109,8 +111,8 @@ void successUpdateWithoutTagsTest() throws Exception { }; mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) - .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .file(studyMainImage) + .part(MockMultipartFactory.createMockPart(objectMapper.writeValueAsBytes(request))) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -129,8 +131,8 @@ void failWithNotExistStudyFieldId() throws Exception { }; mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) - .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .file(studyMainImage) + .part(MockMultipartFactory.createMockPart(objectMapper.writeValueAsBytes(request))) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON)) @@ -158,8 +160,8 @@ void failWithInvalidStudyMeetingSchedule() throws Exception { }; mockMvc.perform(multipart(path) - .file(StudyStub.getStudyMainImageFile()) - .part(new MockPart("request", "", objectMapper.writeValueAsBytes(request), MediaType.APPLICATION_JSON)) + .file(studyMainImage) + .part(MockMultipartFactory.createMockPart(objectMapper.writeValueAsBytes(request))) .with(patchMethod) .header(AuthenticationHeader.ACCESS_TOKEN.getName(), TokenStub.getMockAccessToken()) .accept(MediaType.APPLICATION_JSON))