From e600aea1e6c5f2c16eb871bc597a3f98d58bb2b1 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 12 Oct 2024 22:45:51 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EB=B0=8F=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 접근 제어자 수정 - 메서드 순서 컨벤션 적용 - 메서드 최대 길이 컨벤션 적용 - 불필요한 공백 제거 --- .../kr/momo/domain/schedule/DateInterval.java | 5 +---- .../momo/domain/schedule/DateTimeInterval.java | 5 +---- .../momo/domain/schedule/ScheduleRepository.java | 16 ++++++++-------- .../recommend/FilteredScheduleRecommender.java | 5 +++-- .../schedule/recommend/ScheduleRecommender.java | 10 +++++----- .../recommend/TotalScheduleRecommender.java | 2 +- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/kr/momo/domain/schedule/DateInterval.java b/backend/src/main/java/kr/momo/domain/schedule/DateInterval.java index 3952a7a0..bbd4a035 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/DateInterval.java +++ b/backend/src/main/java/kr/momo/domain/schedule/DateInterval.java @@ -5,10 +5,7 @@ import java.time.LocalDateTime; import java.util.Objects; -public record DateInterval( - LocalDate startDate, - LocalDate endDate -) implements RecommendInterval { +public record DateInterval(LocalDate startDate, LocalDate endDate) implements RecommendInterval { @Override public boolean isSequential(RecommendInterval nextInterval) { diff --git a/backend/src/main/java/kr/momo/domain/schedule/DateTimeInterval.java b/backend/src/main/java/kr/momo/domain/schedule/DateTimeInterval.java index ab8e92a3..76947b32 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/DateTimeInterval.java +++ b/backend/src/main/java/kr/momo/domain/schedule/DateTimeInterval.java @@ -3,10 +3,7 @@ import java.time.Duration; import java.time.LocalDateTime; -public record DateTimeInterval( - LocalDateTime startDateTime, - LocalDateTime endDateTime -) implements RecommendInterval { +public record DateTimeInterval(LocalDateTime startDateTime, LocalDateTime endDateTime) implements RecommendInterval { @Override public boolean isSequential(RecommendInterval nextInterval) { diff --git a/backend/src/main/java/kr/momo/domain/schedule/ScheduleRepository.java b/backend/src/main/java/kr/momo/domain/schedule/ScheduleRepository.java index 5fdeadb4..dbfb0dc0 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/ScheduleRepository.java +++ b/backend/src/main/java/kr/momo/domain/schedule/ScheduleRepository.java @@ -23,14 +23,14 @@ public interface ScheduleRepository extends JpaRepository { void deleteByAttendee(Attendee attendee); @Query(""" - SELECT - new kr.momo.domain.schedule.DateAndTimeslot(ad.date, s.timeslot) - FROM Schedule s - JOIN s.availableDate ad - WHERE s.attendee IN :essentialAttendees - GROUP BY ad.date, s.timeslot - HAVING COUNT(s.attendee.id) = :#{#essentialAttendees.size()} - ORDER BY ad.date ASC, s.timeslot ASC + SELECT + new kr.momo.domain.schedule.DateAndTimeslot(ad.date, s.timeslot) + FROM Schedule s + JOIN s.availableDate ad + WHERE s.attendee IN :essentialAttendees + GROUP BY ad.date, s.timeslot + HAVING COUNT(s.attendee.id) = :#{#essentialAttendees.size()} + ORDER BY ad.date ASC, s.timeslot ASC """) List findAllDateAndTimeslotByEssentialAttendees( @Param("essentialAttendees") List essentialAttendees diff --git a/backend/src/main/java/kr/momo/service/schedule/recommend/FilteredScheduleRecommender.java b/backend/src/main/java/kr/momo/service/schedule/recommend/FilteredScheduleRecommender.java index e9dadccc..55f1cac7 100644 --- a/backend/src/main/java/kr/momo/service/schedule/recommend/FilteredScheduleRecommender.java +++ b/backend/src/main/java/kr/momo/service/schedule/recommend/FilteredScheduleRecommender.java @@ -18,8 +18,9 @@ public FilteredScheduleRecommender(ScheduleRepository scheduleRepository) { } @Override - protected List extractProperSortedDiscreteScheduleOf(AttendeeGroup filteredGroup, - MeetingType type) { + protected List extractProperSortedDiscreteScheduleOf( + AttendeeGroup filteredGroup, MeetingType type + ) { return findAllScheduleAvailableByEveryAttendee(filteredGroup); } diff --git a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java index 0aedbd1a..551b9593 100644 --- a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java +++ b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java @@ -29,15 +29,15 @@ private List calcCandidateSchedules(AttendeeGroup group, Meet return CandidateSchedule.mergeContinuous(intersectedDateTimes, this::isContinuous); } - abstract List extractProperSortedDiscreteScheduleOf(AttendeeGroup group, MeetingType type); - - abstract boolean isContinuous(CandidateSchedule current, CandidateSchedule next); - private void sortSchedules(List mergedCandidateSchedules, String recommendType) { RecommendedScheduleSortStandard sortStandard = RecommendedScheduleSortStandard.from(recommendType); CandidateScheduleSorter sorter = sortStandard.getSorter(); sorter.sort(mergedCandidateSchedules); } - abstract long getMaxRecommendCount(); + protected abstract List extractProperSortedDiscreteScheduleOf(AttendeeGroup group, MeetingType type); + + protected abstract boolean isContinuous(CandidateSchedule current, CandidateSchedule next); + + protected abstract long getMaxRecommendCount(); } diff --git a/backend/src/main/java/kr/momo/service/schedule/recommend/TotalScheduleRecommender.java b/backend/src/main/java/kr/momo/service/schedule/recommend/TotalScheduleRecommender.java index 64936659..360bc904 100644 --- a/backend/src/main/java/kr/momo/service/schedule/recommend/TotalScheduleRecommender.java +++ b/backend/src/main/java/kr/momo/service/schedule/recommend/TotalScheduleRecommender.java @@ -50,7 +50,7 @@ protected boolean isContinuous(CandidateSchedule current, CandidateSchedule next } @Override - long getMaxRecommendCount() { + protected long getMaxRecommendCount() { return MAXIMUM_RECOMMEND_COUNT; } } From 621ed1aaab04b720c054e3f830c41a121ea650aa Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sun, 13 Oct 2024 22:35:07 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EC=B5=9C=EC=86=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B3=B4=EC=9E=A5=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/recommend/RecommendedScheduleSortStandard.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java index 9bc58fca..a1f1abb5 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java @@ -9,7 +9,9 @@ public enum RecommendedScheduleSortStandard { EARLIEST_ORDER("earliest", new EarliestFirstSorter()), - LONG_TERM_ORDER("longTerm", new LongTermFirstSorter()); + LONG_TERM_ORDER("longTerm", new LongTermFirstSorter()), + MIN_TIME_EARLIER_ORDER("minTime", new EarliestFirstSorter()) + ; private final String type; private final CandidateScheduleSorter sorter; From 42065aa83eefc2b8a5b94d34400bfa877e9b6d17 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sun, 13 Oct 2024 22:35:36 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=B5=9C=EC=86=8C=20=EC=8B=9C=EA=B0=84=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/kr/momo/exception/code/ScheduleErrorCode.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/kr/momo/exception/code/ScheduleErrorCode.java b/backend/src/main/java/kr/momo/exception/code/ScheduleErrorCode.java index b4652983..c73a8055 100644 --- a/backend/src/main/java/kr/momo/exception/code/ScheduleErrorCode.java +++ b/backend/src/main/java/kr/momo/exception/code/ScheduleErrorCode.java @@ -5,7 +5,9 @@ public enum ScheduleErrorCode implements ErrorCodeType { INVALID_SCHEDULE_TIMESLOT(HttpStatus.BAD_REQUEST, "해당 시간을 선택할 수 없습니다. 주최자가 설정한 시간만 선택 가능합니다."), - INVALID_SCHEDULE_RECOMMEND_TYPE(HttpStatus.BAD_REQUEST, "해당 추천 기준이 없습니다."); + INVALID_SCHEDULE_RECOMMEND_TYPE(HttpStatus.BAD_REQUEST, "해당 추천 기준이 없습니다."), + INVALID_MIN_TIME(HttpStatus.BAD_REQUEST, "최소 시간 입력이 잘못되었습니다.") + ; private final HttpStatus httpStatus; private final String message; From 46e4631298bafc9ddb14b06c9026dd873b40f2ae Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sun, 13 Oct 2024 22:38:25 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=B5=9C=EC=86=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B3=B4=EC=9E=A5=20=EC=B6=94=EC=B2=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/ScheduleController.java | 7 +- .../schedule/ScheduleControllerDocs.java | 6 +- .../schedule/recommend/CandidateSchedule.java | 29 +++-- .../service/schedule/ScheduleService.java | 12 +- .../recommend/ScheduleRecommender.java | 17 ++- .../schedule/ScheduleControllerTest.java | 2 +- .../recommend/CandidateScheduleTest.java | 108 +++++++++++++++++- .../service/schedule/ScheduleServiceTest.java | 6 +- .../recommend/ScheduleRecommenderTest.java | 82 +++++++++++-- 9 files changed, 231 insertions(+), 38 deletions(-) diff --git a/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java b/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java index e7cb0f34..8973ea66 100644 --- a/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java +++ b/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java @@ -52,10 +52,13 @@ public MomoApiResponse findMySchedule(@PathVariable St @GetMapping("/api/v1/meetings/{uuid}/recommended-schedules") public MomoApiResponse recommendSchedules( - @PathVariable String uuid, @RequestParam String recommendType, @RequestParam List attendeeNames + @PathVariable String uuid, + @RequestParam String recommendType, + @RequestParam List attendeeNames, + @RequestParam(value = "minTime", defaultValue = "0") int minTime ) { RecommendedSchedulesResponse response = scheduleService.recommendSchedules( - uuid, recommendType, attendeeNames + uuid, recommendType, attendeeNames, minTime ); return new MomoApiResponse<>(response); } diff --git a/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java b/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java index ec10b80a..7f1ad1cc 100644 --- a/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java +++ b/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java @@ -98,9 +98,11 @@ MomoApiResponse findMySchedule( @ApiSuccessResponse.Ok("추천 일정 조회 성공") MomoApiResponse recommendSchedules( @PathVariable @Schema(description = "약속 UUID") String uuid, - @RequestParam @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순)", example = "earliest") + @RequestParam @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순 / 최소 시간 보장 순)", example = "earliest") String recommendType, @RequestParam @Schema(description = "추천 대상 참여자 이름", example = "페드로, 재즈, 모모") - List attendeeNames + List attendeeNames, + @RequestParam @Schema(description = "최소 만남 시간(30분 단위의 분)", example = "0, 30, 60, 90") + int minTime ); } diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java index 7ab8f1b7..748f6f83 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java @@ -9,10 +9,12 @@ import kr.momo.domain.attendee.AttendeeGroup; import kr.momo.domain.schedule.DateTimeInterval; import kr.momo.domain.schedule.RecommendInterval; +import kr.momo.exception.MomoException; +import kr.momo.exception.code.ScheduleErrorCode; -public record CandidateSchedule( - RecommendInterval dateTimeInterval, AttendeeGroup attendeeGroup -) { +public record CandidateSchedule(RecommendInterval dateTimeInterval, AttendeeGroup attendeeGroup) { + + private static final int MINIMUM_MIN_SIZE = 0; public static CandidateSchedule of( LocalDateTime startDateTime, LocalDateTime endDateTime, AttendeeGroup attendeeGroup @@ -22,7 +24,8 @@ public static CandidateSchedule of( public static List mergeContinuous( List sortedSchedules, - BiPredicate isContinuous + BiPredicate isContinuous, + int minSize ) { List mergedSchedules = new ArrayList<>(); int idx = 0; @@ -32,14 +35,26 @@ public static List mergeContinuous( .takeWhile(i -> i == headIdx || isSequential(i, sortedSchedules, isContinuous)) .map(sortedSchedules::get) .toList(); - subList.stream() - .reduce(CandidateSchedule::merge) - .ifPresent(mergedSchedules::add); + addWhenLongerOrEqualThanMinTime(subList, mergedSchedules, minSize); idx += subList.size(); } return mergedSchedules; } + private static void addWhenLongerOrEqualThanMinTime( + List subList, List mergedSchedules, int minSize + ) { + if (minSize < MINIMUM_MIN_SIZE) { + throw new MomoException(ScheduleErrorCode.INVALID_MIN_TIME); + } + if (minSize > subList.size()) { + return; + } + subList.stream() + .reduce(CandidateSchedule::merge) + .ifPresent(mergedSchedules::add); + } + private static boolean isSequential( int idx, List sortedSchedules, diff --git a/backend/src/main/java/kr/momo/service/schedule/ScheduleService.java b/backend/src/main/java/kr/momo/service/schedule/ScheduleService.java index f3d4366e..6a48474c 100644 --- a/backend/src/main/java/kr/momo/service/schedule/ScheduleService.java +++ b/backend/src/main/java/kr/momo/service/schedule/ScheduleService.java @@ -122,7 +122,9 @@ public AttendeeScheduleResponse findMySchedule(String uuid, long attendeeId) { } @Transactional(readOnly = true) - public RecommendedSchedulesResponse recommendSchedules(String uuid, String recommendType, List names) { + public RecommendedSchedulesResponse recommendSchedules( + String uuid, String recommendType, List names, int minimumTime + ) { Meeting meeting = meetingRepository.findByUuid(uuid) .orElseThrow(() -> new MomoException(MeetingErrorCode.NOT_FOUND_MEETING)); AttendeeGroup attendeeGroup = new AttendeeGroup(attendeeRepository.findAllByMeeting(meeting)); @@ -131,11 +133,13 @@ public RecommendedSchedulesResponse recommendSchedules(String uuid, String recom ScheduleRecommender recommender = scheduleRecommenderFactory.getRecommenderOf( attendeeGroup, filteredGroup ); - List recommendedResult = recommender.recommend(filteredGroup, recommendType, - meeting.getType()); + List recommendedResult = recommender.recommend( + filteredGroup, recommendType, meeting.getType(), minimumTime + ); List scheduleResponses = RecommendedScheduleResponse.fromCandidateSchedules( - recommendedResult); + recommendedResult + ); return RecommendedSchedulesResponse.of(meeting.getType(), scheduleResponses); } } diff --git a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java index 551b9593..1ca24935 100644 --- a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java +++ b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java @@ -14,19 +14,24 @@ @RequiredArgsConstructor public abstract class ScheduleRecommender { + private static final int HALF_HOUR_INTERVAL = 30; + protected final ScheduleRepository scheduleRepository; - public List recommend(AttendeeGroup group, String recommendType, MeetingType meetingType) { - List mergedCandidateSchedules = calcCandidateSchedules(group, meetingType); + public List recommend( + AttendeeGroup group, String recommendType, MeetingType meetingType, int minTime + ) { + int minSize = minTime / HALF_HOUR_INTERVAL; + List mergedCandidateSchedules = calcCandidateSchedules(group, meetingType, minSize); sortSchedules(mergedCandidateSchedules, recommendType); return mergedCandidateSchedules.stream() .limit(getMaxRecommendCount()) .toList(); } - private List calcCandidateSchedules(AttendeeGroup group, MeetingType type) { + private List calcCandidateSchedules(AttendeeGroup group, MeetingType type, int minSize) { List intersectedDateTimes = extractProperSortedDiscreteScheduleOf(group, type); - return CandidateSchedule.mergeContinuous(intersectedDateTimes, this::isContinuous); + return CandidateSchedule.mergeContinuous(intersectedDateTimes, this::isContinuous, minSize); } private void sortSchedules(List mergedCandidateSchedules, String recommendType) { @@ -35,7 +40,9 @@ private void sortSchedules(List mergedCandidateSchedules, Str sorter.sort(mergedCandidateSchedules); } - protected abstract List extractProperSortedDiscreteScheduleOf(AttendeeGroup group, MeetingType type); + protected abstract List extractProperSortedDiscreteScheduleOf( + AttendeeGroup group, MeetingType type + ); protected abstract boolean isContinuous(CandidateSchedule current, CandidateSchedule next); diff --git a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java index 98762b77..3e2a4eb0 100644 --- a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java +++ b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java @@ -154,7 +154,7 @@ void findMySchedule() { void recommendSchedules() { RestAssured.given().log().all() .pathParam("uuid", meeting.getUuid()) - .queryParams("recommendType", EARLIEST_ORDER.getType(), "attendeeNames", attendee.name()) + .queryParams("recommendType", EARLIEST_ORDER.getType(), "attendeeNames", attendee.name(), "minimumTime", 0) .contentType(ContentType.JSON) .when().get("/api/v1/meetings/{uuid}/recommended-schedules") .then().log().all() diff --git a/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java b/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java index a883ca58..ccf3ebcd 100644 --- a/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java +++ b/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java @@ -1,6 +1,7 @@ package kr.momo.domain.schedule.recommend; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import java.time.LocalDate; @@ -9,12 +10,18 @@ import kr.momo.domain.attendee.AttendeeGroup; import kr.momo.domain.schedule.DateAndTimeslot; import kr.momo.domain.timeslot.Timeslot; +import kr.momo.exception.MomoException; +import kr.momo.exception.code.ScheduleErrorCode; import kr.momo.fixture.AttendeeGroupFixture; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class CandidateScheduleTest { + private static final int DEFAULT_MIN_SIZE = 0; + @DisplayName("빈 리스트를 병합할 경우 빈 리스트를 반환한다.") @Test void mergeEmptyListTest() { @@ -22,7 +29,9 @@ void mergeEmptyListTest() { List schedules = List.of(); // when - List mergedSchedules = CandidateSchedule.mergeContinuous(schedules, this::isContinuous); + List mergedSchedules = CandidateSchedule.mergeContinuous( + schedules, this::isContinuous, DEFAULT_MIN_SIZE + ); // then assertThat(mergedSchedules).isEmpty(); @@ -43,7 +52,9 @@ void mergeContinuousTest() { ); // when - List mergedSchedules = CandidateSchedule.mergeContinuous(schedules, this::isContinuous); + List mergedSchedules = CandidateSchedule.mergeContinuous( + schedules, this::isContinuous, DEFAULT_MIN_SIZE + ); // then assertAll( @@ -59,6 +70,95 @@ void mergeContinuousTest() { ); } + @DisplayName("연속된 시간 길이가 주어진 길이보다 같거나 큰 경우만 병합한다.") + @ParameterizedTest + @CsvSource(value = {"0,9", "1,9", "2,6", "3,4", "4,2", "5,1"}) + void mergeContinuousTestWhenHasMinSize(int minSize, int expected) { + // given + LocalDate today = LocalDate.now(); + AttendeeGroup group = AttendeeGroupFixture.JAZZ_DAON_BAKEY.create(); + List schedules = List.of( + // 30분 간격 시간 후보 3개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0000), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0200), + // 60분 간격 시간 후보 2개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0300), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0330), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0500), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0530), + // 90분 간격 시간 후보 2개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1000), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1030), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1200), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1230), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1300), + // 120분 간격 시간 후보 1개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1700), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1730), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1800), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1830), + // 150분 간격 시간 후보 1개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2030), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2130), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2200), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2230) + ); + + // when + List mergedSchedules = CandidateSchedule.mergeContinuous( + schedules, this::isContinuous, minSize + ); + + // then + assertThat(mergedSchedules).hasSize(expected); + } + + @DisplayName("최소 시간이 최소 크기보다 작으면 예외가 발생한다.") + @Test + void mergeContinuousTestWhenHasMinSizeLessThan() { + // given + int givenMinSize = -1; + LocalDate today = LocalDate.now(); + AttendeeGroup group = AttendeeGroupFixture.JAZZ_DAON_BAKEY.create(); + List schedules = List.of( + // 30분 간격 시간 후보 3개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0000), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0200), + // 60분 간격 시간 후보 2개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0300), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0330), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0500), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0530), + // 90분 간격 시간 후보 2개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1000), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1030), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1200), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1230), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1300), + // 120분 간격 시간 후보 1개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1700), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1730), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1800), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1830), + // 150분 간격 시간 후보 1개 + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2030), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2100), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2130), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2200), + createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2230) + ); + + // when + assertThatThrownBy(() -> CandidateSchedule.mergeContinuous(schedules, this::isContinuous, givenMinSize)) + .isInstanceOf(MomoException.class) + .hasMessage(ScheduleErrorCode.INVALID_MIN_TIME.message()); + } + @DisplayName("자정을 포함하여 연속되는 시간의 경우 종료일자는 마지막 시간의 종료일자이다.") @Test void mergeContinuousIncludeMidnightTest() { @@ -75,7 +175,9 @@ void mergeContinuousIncludeMidnightTest() { ); // when - List mergedSchedules = CandidateSchedule.mergeContinuous(schedules, this::isContinuous); + List mergedSchedules = CandidateSchedule.mergeContinuous( + schedules, this::isContinuous, DEFAULT_MIN_SIZE + ); // then assertAll( diff --git a/backend/src/test/java/kr/momo/service/schedule/ScheduleServiceTest.java b/backend/src/test/java/kr/momo/service/schedule/ScheduleServiceTest.java index 5f614d83..599140c6 100644 --- a/backend/src/test/java/kr/momo/service/schedule/ScheduleServiceTest.java +++ b/backend/src/test/java/kr/momo/service/schedule/ScheduleServiceTest.java @@ -262,7 +262,7 @@ void recommendLongTermSchedules() { scheduleRepository.saveAll(schedules); RecommendedSchedulesResponse responses = scheduleService.recommendSchedules( - movieMeeting.getUuid(), LONG_TERM_ORDER.getType(), List.of(jazz.name(), daon.name()) + movieMeeting.getUuid(), LONG_TERM_ORDER.getType(), List.of(jazz.name(), daon.name()), 0 ); assertThat(responses.recommendedSchedules()).containsExactly( @@ -302,7 +302,7 @@ void recommendFastestSchedules() { scheduleRepository.saveAll(schedules); RecommendedSchedulesResponse responses = scheduleService.recommendSchedules( - movieMeeting.getUuid(), EARLIEST_ORDER.getType(), List.of(jazz.name(), daon.name()) + movieMeeting.getUuid(), EARLIEST_ORDER.getType(), List.of(jazz.name(), daon.name()), 0 ); assertThat(responses.recommendedSchedules()).containsExactly( @@ -399,7 +399,7 @@ void recommendContinuousSchedule() { scheduleRepository.saveAll(schedules); RecommendedSchedulesResponse responses = scheduleService.recommendSchedules( - movieMeeting.getUuid(), LONG_TERM_ORDER.getType(), List.of(jazz.name(), daon.name()) + movieMeeting.getUuid(), LONG_TERM_ORDER.getType(), List.of(jazz.name(), daon.name()), 0 ); assertThat(responses.recommendedSchedules()).containsExactly( diff --git a/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java b/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java index 4eb69c74..35e0cad6 100644 --- a/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java +++ b/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java @@ -37,6 +37,8 @@ @SpringBootTest(webEnvironment = WebEnvironment.NONE) class ScheduleRecommenderTest { + private static final int DEFAULT_MIN_SIZE = 0; + @Autowired private TotalScheduleRecommender totalScheduleRecommender; @@ -74,7 +76,8 @@ void setUp() { AvailableDate todayAvailableDate = availableDateRepository.save(new AvailableDate(today, meeting)); AvailableDate tomorrowAvailableDate = availableDateRepository.save( - new AvailableDate(today.plusDays(1), meeting)); + new AvailableDate(today.plusDays(1), meeting) + ); // jazz: 오늘 0000 ~ 2330 / 내일 0000 ~ 2330 saveAttendeeSchedule(jazz, todayAvailableDate, createTimeslotRange(Timeslot.TIME_0000, Timeslot.TIME_2330)); @@ -130,19 +133,12 @@ private AttendeeGroup createAttendeeGroup(Attendee... attendees) { @Transactional // TODO: 테스트 코드에서의 @Transactional 사용 논의 필요 class TotalScheduleRecommenderTest { - @DisplayName("참여자의 부분집합 별 참여 가능 시간을 구한 뒤, " + @DisplayName("참여자의 부분 집합 별 참여 가능 시간을 구한 뒤, " + "(참여자 많은 순, 빠른 시간 순)으로 정렬하고 상위 10개를 추천한다.") @Test void recommendByEachAttendeeSubsetOrderByAttendeeCountAndEarliestTime() { // given AttendeeGroup group = new AttendeeGroup(List.of(jazz, pedro, daon, bakey, mark)); - - // when - List recommendResult = totalScheduleRecommender.recommend( - group, RecommendedScheduleSortStandard.EARLIEST_ORDER.getType(), MeetingType.DATETIME - ); - - // then List expected = List.of( new CandidateSchedule( createDateTimeInterval(today, 12, 0, today, 15, 0), @@ -185,6 +181,67 @@ void recommendByEachAttendeeSubsetOrderByAttendeeCountAndEarliestTime() { createAttendeeGroup(jazz, daon, mark) ) ); + + // when + List recommendResult = totalScheduleRecommender.recommend( + group, + RecommendedScheduleSortStandard.EARLIEST_ORDER.getType(), + MeetingType.DATETIME, + DEFAULT_MIN_SIZE + ); + + // then + assertAll( + () -> assertThat(recommendResult).hasSizeLessThanOrEqualTo(10), + () -> assertThat(recommendResult).containsExactlyElementsOf(expected) + ); + } + + @DisplayName("참여자의 부분 집합 별 참여 가능 시간을 구한 뒤, " + + "(참여자 많은 순, 빠른 시간 순)으로 정렬하고 상위 10개를 추천한다." + + "최소 시간 보장을 곁들인") + @Test + void recommendByEachAttendeeSubsetOrderByAttendeeCountAndEarliestTimeWithMinimumTime() { + // given + int givenMinTime = 180; + AttendeeGroup group = new AttendeeGroup(List.of(jazz, pedro, daon, bakey, mark)); + List expected = List.of( + new CandidateSchedule( + createDateTimeInterval(today, 12, 0, today, 15, 0), + createAttendeeGroup(jazz, pedro, daon, mark, bakey) + ), + new CandidateSchedule( + createDateTimeInterval(tomorrow, 16, 0, tomorrow, 20, 0), + createAttendeeGroup(jazz, pedro, daon, mark, bakey) + ), + new CandidateSchedule( + createDateTimeInterval(today, 15, 0, today, 18, 30), + createAttendeeGroup(jazz, pedro, daon, bakey) + ), + new CandidateSchedule( + createDateTimeInterval(today, 19, 0, today, 22, 0), + createAttendeeGroup(jazz, pedro, daon, bakey) + ), + new CandidateSchedule( + createDateTimeInterval(tomorrow, 0, 0, tomorrow, 16, 0), + createAttendeeGroup(jazz, daon, bakey, mark) + ), + new CandidateSchedule( + createDateTimeInterval(today, 6, 0, today, 9, 0), + createAttendeeGroup(jazz, pedro, daon) + ), + new CandidateSchedule( + createDateTimeInterval(today, 0, 0, today, 6, 0), + createAttendeeGroup(jazz, daon) + ) + ); + + // when + List recommendResult = totalScheduleRecommender.recommend( + group, RecommendedScheduleSortStandard.EARLIEST_ORDER.getType(), MeetingType.DATETIME, givenMinTime + ); + + // then assertAll( () -> assertThat(recommendResult).hasSizeLessThanOrEqualTo(10), () -> assertThat(recommendResult).containsExactlyElementsOf(expected) @@ -204,7 +261,10 @@ void recommendByFilteredAttendeeOrderByAttendeeCountAndEarliestTime() { // when List recommendResult = filteredScheduleRecommender.recommend( - filteredGroup, RecommendedScheduleSortStandard.EARLIEST_ORDER.getType(), MeetingType.DATETIME + filteredGroup, + RecommendedScheduleSortStandard.EARLIEST_ORDER.getType(), + MeetingType.DATETIME, + DEFAULT_MIN_SIZE ); // then @@ -215,7 +275,7 @@ void recommendByFilteredAttendeeOrderByAttendeeCountAndEarliestTime() { expectedGroup ), new CandidateSchedule( - createDateTimeInterval(today, 12, 0, today, 15, 00), + createDateTimeInterval(today, 12, 0, today, 15, 0), expectedGroup ), new CandidateSchedule( From d7b0449723e38ad23f3e2e8df4b99edc2b506199 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 19 Oct 2024 19:51:33 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=EC=B6=94=EC=B2=9C=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EA=B0=92=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=ED=99=94=20=EB=B0=8F=20=EC=B5=9C=EC=86=8C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/ScheduleController.java | 11 ++--- .../schedule/ScheduleControllerDocs.java | 13 ++---- .../schedule/recommend/CandidateSchedule.java | 7 --- .../dto/ScheduleRecommendRequest.java | 23 ++++++++++ .../schedule/ScheduleControllerTest.java | 18 +++++++- .../recommend/CandidateScheduleTest.java | 46 ------------------- 6 files changed, 48 insertions(+), 70 deletions(-) create mode 100644 backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java diff --git a/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java b/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java index 8973ea66..46a2e45d 100644 --- a/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java +++ b/backend/src/main/java/kr/momo/controller/schedule/ScheduleController.java @@ -1,20 +1,20 @@ package kr.momo.controller.schedule; import jakarta.validation.Valid; -import java.util.List; import kr.momo.controller.MomoApiResponse; import kr.momo.controller.auth.AuthAttendee; import kr.momo.service.schedule.ScheduleService; import kr.momo.service.schedule.dto.AttendeeScheduleResponse; import kr.momo.service.schedule.dto.RecommendedSchedulesResponse; import kr.momo.service.schedule.dto.ScheduleCreateRequest; +import kr.momo.service.schedule.dto.ScheduleRecommendRequest; import kr.momo.service.schedule.dto.SchedulesResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -52,13 +52,10 @@ public MomoApiResponse findMySchedule(@PathVariable St @GetMapping("/api/v1/meetings/{uuid}/recommended-schedules") public MomoApiResponse recommendSchedules( - @PathVariable String uuid, - @RequestParam String recommendType, - @RequestParam List attendeeNames, - @RequestParam(value = "minTime", defaultValue = "0") int minTime + @PathVariable String uuid, @ModelAttribute @Valid ScheduleRecommendRequest request ) { RecommendedSchedulesResponse response = scheduleService.recommendSchedules( - uuid, recommendType, attendeeNames, minTime + uuid, request.recommendType(), request.attendeeNames(), request.minTime() ); return new MomoApiResponse<>(response); } diff --git a/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java b/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java index 7f1ad1cc..2793ad8d 100644 --- a/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java +++ b/backend/src/main/java/kr/momo/controller/schedule/ScheduleControllerDocs.java @@ -7,7 +7,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.List; import kr.momo.controller.MomoApiResponse; import kr.momo.controller.annotation.ApiErrorResponse; import kr.momo.controller.annotation.ApiSuccessResponse; @@ -15,10 +14,11 @@ import kr.momo.service.schedule.dto.AttendeeScheduleResponse; import kr.momo.service.schedule.dto.RecommendedSchedulesResponse; import kr.momo.service.schedule.dto.ScheduleCreateRequest; +import kr.momo.service.schedule.dto.ScheduleRecommendRequest; import kr.momo.service.schedule.dto.SchedulesResponse; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "Schedule", description = "일정 API") public interface ScheduleControllerDocs { @@ -90,7 +90,7 @@ MomoApiResponse findMySchedule( 추천 기준에 따라 이른 시간 순 혹은 길게 볼 수 있는 순으로 추천합니다. - earliest: 이른 시간 순 - longTerm: 길게 볼 수 있는 순 - + 추천 연산에 사용할 참여자 이름을 명시하여 필터링할 수 있습니다.
약속 내의 모든 참여자가 전달된 경우 일부 참여자들이 참여할 수 있는 일정을 함께 추천하며,
이외의 경우 전달된 참여자들이 모두 참여할 수 있는 일정이 추천됩니다. @@ -98,11 +98,6 @@ MomoApiResponse findMySchedule( @ApiSuccessResponse.Ok("추천 일정 조회 성공") MomoApiResponse recommendSchedules( @PathVariable @Schema(description = "약속 UUID") String uuid, - @RequestParam @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순 / 최소 시간 보장 순)", example = "earliest") - String recommendType, - @RequestParam @Schema(description = "추천 대상 참여자 이름", example = "페드로, 재즈, 모모") - List attendeeNames, - @RequestParam @Schema(description = "최소 만남 시간(30분 단위의 분)", example = "0, 30, 60, 90") - int minTime + @ModelAttribute @Valid ScheduleRecommendRequest request ); } diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java index 748f6f83..ad576e4a 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java @@ -9,13 +9,9 @@ import kr.momo.domain.attendee.AttendeeGroup; import kr.momo.domain.schedule.DateTimeInterval; import kr.momo.domain.schedule.RecommendInterval; -import kr.momo.exception.MomoException; -import kr.momo.exception.code.ScheduleErrorCode; public record CandidateSchedule(RecommendInterval dateTimeInterval, AttendeeGroup attendeeGroup) { - private static final int MINIMUM_MIN_SIZE = 0; - public static CandidateSchedule of( LocalDateTime startDateTime, LocalDateTime endDateTime, AttendeeGroup attendeeGroup ) { @@ -44,9 +40,6 @@ public static List mergeContinuous( private static void addWhenLongerOrEqualThanMinTime( List subList, List mergedSchedules, int minSize ) { - if (minSize < MINIMUM_MIN_SIZE) { - throw new MomoException(ScheduleErrorCode.INVALID_MIN_TIME); - } if (minSize > subList.size()) { return; } diff --git a/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java b/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java new file mode 100644 index 00000000..dfe9ad28 --- /dev/null +++ b/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java @@ -0,0 +1,23 @@ +package kr.momo.service.schedule.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Schema(description = "일정 추천 요청") +public record ScheduleRecommendRequest( + + @NotEmpty + @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순 / 최소 시간 보장 순)", example = "earliest") + String recommendType, + + @NotEmpty + @Schema(description = "추천 대상 참여자 이름", example = "페드로, 재즈, 모모") + List attendeeNames, + + @Schema(description = "최소 만남 시간(시간 단위)", example = "0, 1, 2, 3") + @Min(value = 0, message = "최소 시간은 0보다 작을 수 없습니다.") + int minTime +) { +} diff --git a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java index 3e2a4eb0..0ea43127 100644 --- a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java +++ b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java @@ -154,13 +154,29 @@ void findMySchedule() { void recommendSchedules() { RestAssured.given().log().all() .pathParam("uuid", meeting.getUuid()) - .queryParams("recommendType", EARLIEST_ORDER.getType(), "attendeeNames", attendee.name(), "minimumTime", 0) + .queryParam("recommendType", EARLIEST_ORDER.getType()) + .queryParams("attendeeNames", List.of(attendee.name())) + .queryParam("minTime", 0) .contentType(ContentType.JSON) .when().get("/api/v1/meetings/{uuid}/recommended-schedules") .then().log().all() .statusCode(HttpStatus.OK.value()); } + @DisplayName("추천 약속 조회시 최소 시간이 0보다 작으면 예외가 발생한다.") + @Test + void recommendSchedulesIfSmallerThanZero() { + RestAssured.given().log().all() + .pathParam("uuid", meeting.getUuid()) + .queryParam("recommendType", EARLIEST_ORDER.getType()) + .queryParams("attendeeNames", List.of(attendee.name())) + .queryParam("minTime", -1) + .contentType(ContentType.JSON) + .when().get("/api/v1/meetings/{uuid}/recommended-schedules") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + private void createAttendeeSchedule(Attendee attendee) { List schedules = new ArrayList<>(); schedules.add(new Schedule(attendee, today, Timeslot.TIME_0300)); diff --git a/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java b/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java index ccf3ebcd..e58dec26 100644 --- a/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java +++ b/backend/src/test/java/kr/momo/domain/schedule/recommend/CandidateScheduleTest.java @@ -1,7 +1,6 @@ package kr.momo.domain.schedule.recommend; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import java.time.LocalDate; @@ -10,8 +9,6 @@ import kr.momo.domain.attendee.AttendeeGroup; import kr.momo.domain.schedule.DateAndTimeslot; import kr.momo.domain.timeslot.Timeslot; -import kr.momo.exception.MomoException; -import kr.momo.exception.code.ScheduleErrorCode; import kr.momo.fixture.AttendeeGroupFixture; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -116,49 +113,6 @@ void mergeContinuousTestWhenHasMinSize(int minSize, int expected) { assertThat(mergedSchedules).hasSize(expected); } - @DisplayName("최소 시간이 최소 크기보다 작으면 예외가 발생한다.") - @Test - void mergeContinuousTestWhenHasMinSizeLessThan() { - // given - int givenMinSize = -1; - LocalDate today = LocalDate.now(); - AttendeeGroup group = AttendeeGroupFixture.JAZZ_DAON_BAKEY.create(); - List schedules = List.of( - // 30분 간격 시간 후보 3개 - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0000), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0100), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0200), - // 60분 간격 시간 후보 2개 - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0300), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0330), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0500), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_0530), - // 90분 간격 시간 후보 2개 - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1000), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1030), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1100), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1200), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1230), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1300), - // 120분 간격 시간 후보 1개 - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1700), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1730), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1800), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_1830), - // 150분 간격 시간 후보 1개 - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2030), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2100), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2130), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2200), - createDiscreteCandidateSchedule(group, today, Timeslot.TIME_2230) - ); - - // when - assertThatThrownBy(() -> CandidateSchedule.mergeContinuous(schedules, this::isContinuous, givenMinSize)) - .isInstanceOf(MomoException.class) - .hasMessage(ScheduleErrorCode.INVALID_MIN_TIME.message()); - } - @DisplayName("자정을 포함하여 연속되는 시간의 경우 종료일자는 마지막 시간의 종료일자이다.") @Test void mergeContinuousIncludeMidnightTest() { From f3c7c291415c742d613e46dc4c10d553f94a29f6 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 19 Oct 2024 20:27:07 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix(ScheduleRecommendRequest):=20minTime=20?= =?UTF-8?q?=EC=9D=84=20=EC=9E=85=EB=A0=A5=20=EB=B0=9B=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EA=B2=BD=EC=9A=B0=200=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/dto/ScheduleRecommendRequest.java | 10 ++++++++-- .../controller/schedule/ScheduleControllerTest.java | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java b/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java index dfe9ad28..f6a86e04 100644 --- a/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java +++ b/backend/src/main/java/kr/momo/service/schedule/dto/ScheduleRecommendRequest.java @@ -9,7 +9,7 @@ public record ScheduleRecommendRequest( @NotEmpty - @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순 / 최소 시간 보장 순)", example = "earliest") + @Schema(description = "추천 기준(이른 시간 순 / 길게 볼 수 있는 순)", example = "earliest") String recommendType, @NotEmpty @@ -18,6 +18,12 @@ public record ScheduleRecommendRequest( @Schema(description = "최소 만남 시간(시간 단위)", example = "0, 1, 2, 3") @Min(value = 0, message = "최소 시간은 0보다 작을 수 없습니다.") - int minTime + Integer minTime ) { + + public ScheduleRecommendRequest { + if (minTime == null) { + minTime = 0; + } + } } diff --git a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java index 0ea43127..851e5628 100644 --- a/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java +++ b/backend/src/test/java/kr/momo/controller/schedule/ScheduleControllerTest.java @@ -163,6 +163,19 @@ void recommendSchedules() { .statusCode(HttpStatus.OK.value()); } + @DisplayName("추천 약속을 조회시 최소 시간을 입력받지 않아도 동작한다.") + @Test + void recommendSchedulesWithoutMinTime() { + RestAssured.given().log().all() + .pathParam("uuid", meeting.getUuid()) + .queryParam("recommendType", EARLIEST_ORDER.getType()) + .queryParams("attendeeNames", List.of(attendee.name())) + .contentType(ContentType.JSON) + .when().get("/api/v1/meetings/{uuid}/recommended-schedules") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + @DisplayName("추천 약속 조회시 최소 시간이 0보다 작으면 예외가 발생한다.") @Test void recommendSchedulesIfSmallerThanZero() { From ab70b8dbd0d6441ef37f791f45b40d42086e420a Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 19 Oct 2024 20:33:04 +0900 Subject: [PATCH 7/9] =?UTF-8?q?refactor:=20=EC=B5=9C=EC=86=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EA=B8=B0=EC=A4=80=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/recommend/RecommendedScheduleSortStandard.java | 4 +--- .../momo/service/schedule/recommend/ScheduleRecommender.java | 4 ++-- .../service/schedule/recommend/ScheduleRecommenderTest.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java index a1f1abb5..9bc58fca 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/RecommendedScheduleSortStandard.java @@ -9,9 +9,7 @@ public enum RecommendedScheduleSortStandard { EARLIEST_ORDER("earliest", new EarliestFirstSorter()), - LONG_TERM_ORDER("longTerm", new LongTermFirstSorter()), - MIN_TIME_EARLIER_ORDER("minTime", new EarliestFirstSorter()) - ; + LONG_TERM_ORDER("longTerm", new LongTermFirstSorter()); private final String type; private final CandidateScheduleSorter sorter; diff --git a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java index 1ca24935..06d2adbc 100644 --- a/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java +++ b/backend/src/main/java/kr/momo/service/schedule/recommend/ScheduleRecommender.java @@ -14,14 +14,14 @@ @RequiredArgsConstructor public abstract class ScheduleRecommender { - private static final int HALF_HOUR_INTERVAL = 30; + private static final int ONE_HOUR_TIME_INTERVAL_SIZE = 2; protected final ScheduleRepository scheduleRepository; public List recommend( AttendeeGroup group, String recommendType, MeetingType meetingType, int minTime ) { - int minSize = minTime / HALF_HOUR_INTERVAL; + int minSize = minTime * ONE_HOUR_TIME_INTERVAL_SIZE; List mergedCandidateSchedules = calcCandidateSchedules(group, meetingType, minSize); sortSchedules(mergedCandidateSchedules, recommendType); return mergedCandidateSchedules.stream() diff --git a/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java b/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java index 35e0cad6..4bd7dec9 100644 --- a/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java +++ b/backend/src/test/java/kr/momo/service/schedule/recommend/ScheduleRecommenderTest.java @@ -203,7 +203,7 @@ void recommendByEachAttendeeSubsetOrderByAttendeeCountAndEarliestTime() { @Test void recommendByEachAttendeeSubsetOrderByAttendeeCountAndEarliestTimeWithMinimumTime() { // given - int givenMinTime = 180; + int givenMinTime = 3; AttendeeGroup group = new AttendeeGroup(List.of(jazz, pedro, daon, bakey, mark)); List expected = List.of( new CandidateSchedule( From f5955fddfbe1501290c2ba4115691e87c87f0c28 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 19 Oct 2024 20:34:03 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EA=B0=80=EB=8F=85=EC=84=B1=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EB=B6=84=EA=B8=B0=EB=AC=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/schedule/recommend/CandidateSchedule.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java index ad576e4a..2372b110 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java @@ -40,12 +40,11 @@ public static List mergeContinuous( private static void addWhenLongerOrEqualThanMinTime( List subList, List mergedSchedules, int minSize ) { - if (minSize > subList.size()) { - return; + if (minSize <= subList.size()) { + subList.stream() + .reduce(CandidateSchedule::merge) + .ifPresent(mergedSchedules::add); } - subList.stream() - .reduce(CandidateSchedule::merge) - .ifPresent(mergedSchedules::add); } private static boolean isSequential( From c848d794cfba82b8da0b5a8771ea380f00402537 Mon Sep 17 00:00:00 2001 From: ikjo39 Date: Sat, 19 Oct 2024 20:35:27 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=EC=9D=BC=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=AA=85=EB=AA=85=EB=B2=95=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/momo/domain/schedule/recommend/CandidateSchedule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java index 2372b110..93a2c760 100644 --- a/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java +++ b/backend/src/main/java/kr/momo/domain/schedule/recommend/CandidateSchedule.java @@ -31,13 +31,13 @@ public static List mergeContinuous( .takeWhile(i -> i == headIdx || isSequential(i, sortedSchedules, isContinuous)) .map(sortedSchedules::get) .toList(); - addWhenLongerOrEqualThanMinTime(subList, mergedSchedules, minSize); + addIfLongerThanOrEqualToMinTime(subList, mergedSchedules, minSize); idx += subList.size(); } return mergedSchedules; } - private static void addWhenLongerOrEqualThanMinTime( + private static void addIfLongerThanOrEqualToMinTime( List subList, List mergedSchedules, int minSize ) { if (minSize <= subList.size()) {