Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public enum ErrorStatus implements BaseErrorCode {
// 일정 관련
_SCHEDULE_NOT_FOUND(404, "SCHEDULE404", "일정을 찾을 수 없습니다."),
_SCHEDULE_ACCESS_DENIED(403, "SCHEDULE403", "해당 스터디에 속하는 일정이 아닙니다."),
_SCHEDULE_TIME_CONFLICT(400, "SCHEDULE4001", "동일한 시간대에 이미 일정이 존재합니다."),
_SCHEDULE_QR_CODE_ALREADY_ASSIGNED(400, "SCHEDULE4000", "이미 출석 QR 코드가 할당된 일정입니다."),

// 출석 관련
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kr.spot.schedule.application.command;

import kr.spot.IdGenerator;
import kr.spot.code.status.ErrorStatus;
import kr.spot.exception.GeneralException;
import kr.spot.schedule.domain.Schedule;
import kr.spot.schedule.infrastructure.jpa.ScheduleRepository;
import kr.spot.schedule.presentation.command.dto.CreateScheduleRequest;
Expand All @@ -17,6 +19,8 @@ public class ManageScheduleService {
private final ScheduleRepository scheduleRepository;

public long createSchedule(CreateScheduleRequest request, long studyId, long creatorId) {
validateScheduleTimeConflict(request, studyId);

long scheduleId = idGenerator.nextId();
Schedule schedule = Schedule.of(scheduleId, studyId, creatorId, request.title(),
request.locationInfo(),
Expand All @@ -30,4 +34,13 @@ public void deleteSchedule(long studyId, long scheduleId) {
Schedule schedule = scheduleRepository.getById(scheduleId);
schedule.delete(studyId);
}

private void validateScheduleTimeConflict(CreateScheduleRequest request, long studyId) {
boolean hasConflict = scheduleRepository.existsByStudyIdAndStartAtLessThanAndEndAtGreaterThan(
studyId, request.endAt(), request.startAt());

if (hasConflict) {
throw new GeneralException(ErrorStatus._SCHEDULE_TIME_CONFLICT);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package kr.spot.schedule.infrastructure.jpa;

import java.time.LocalDateTime;
import kr.spot.code.status.ErrorStatus;
import kr.spot.exception.GeneralException;
import kr.spot.schedule.domain.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ScheduleRepository extends JpaRepository<Schedule, Long> {

boolean existsByStudyIdAndStartAtLessThanAndEndAtGreaterThan(
Long studyId, LocalDateTime endAt, LocalDateTime startAt);

default Schedule getById(long scheduleId) {
return findById(scheduleId).orElseThrow(
() -> new GeneralException(ErrorStatus._SCHEDULE_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.time.LocalDateTime;
import kr.spot.IdGenerator;
import kr.spot.code.status.ErrorStatus;
import kr.spot.exception.GeneralException;
Expand Down Expand Up @@ -60,6 +62,8 @@ void should_create_schedule_successfully() {
CreateScheduleRequest request = createScheduleRequest();

when(idGenerator.nextId()).thenReturn(generatedId);
when(scheduleRepository.existsByStudyIdAndStartAtLessThanAndEndAtGreaterThan(
STUDY_ID, request.endAt(), request.startAt())).thenReturn(false);
when(scheduleRepository.save(any(Schedule.class)))
.thenAnswer(invocation -> invocation.getArgument(0));

Expand Down Expand Up @@ -89,6 +93,8 @@ TITLE, null, createScheduleRequest().startAt(), createScheduleRequest().endAt()
);

when(idGenerator.nextId()).thenReturn(generatedId);
when(scheduleRepository.existsByStudyIdAndStartAtLessThanAndEndAtGreaterThan(
STUDY_ID, request.endAt(), request.startAt())).thenReturn(false);
when(scheduleRepository.save(any(Schedule.class)))
.thenAnswer(invocation -> invocation.getArgument(0));

Expand All @@ -101,6 +107,29 @@ TITLE, null, createScheduleRequest().startAt(), createScheduleRequest().endAt()
Schedule capturedSchedule = scheduleCaptor.getValue();
assertThat(capturedSchedule.getLocationMemo()).isNull();
}

@Test
@DisplayName("동일한 시간대에 일정이 있으면 예외가 발생한다")
void should_throw_exception_when_schedule_time_conflicts() {
// given
CreateScheduleRequest request = new CreateScheduleRequest(
TITLE,
LOCATION_MEMO,
LocalDateTime.of(2025, 1, 15, 15, 0),
LocalDateTime.of(2025, 1, 15, 17, 0)
);

when(scheduleRepository.existsByStudyIdAndStartAtLessThanAndEndAtGreaterThan(
STUDY_ID, request.endAt(), request.startAt())).thenReturn(true);

// when & then
assertThatThrownBy(() -> manageScheduleService.createSchedule(request, STUDY_ID, CREATOR_ID))
.isInstanceOf(GeneralException.class)
.extracting(ex -> ((GeneralException) ex).getStatus())
.isEqualTo(ErrorStatus._SCHEDULE_TIME_CONFLICT);

verify(scheduleRepository, never()).save(any(Schedule.class));
}
}

@Nested
Expand Down
Loading