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 @@ -5,6 +5,8 @@
import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto;
import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto;
import dmu.dasom.api.domain.email.enums.MailType;
import dmu.dasom.api.domain.recruit.dto.ResultCheckRequestDto;
import dmu.dasom.api.domain.recruit.dto.ResultCheckResponseDto;
import dmu.dasom.api.global.dto.PageResponse;

public interface ApplicantService {
Expand All @@ -21,4 +23,6 @@ public interface ApplicantService {

ApplicantDetailsResponseDto getApplicantByStudentNo(final String studentNo);

ResultCheckResponseDto checkResult(final ResultCheckRequestDto request);

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import dmu.dasom.api.domain.email.enums.MailType;
import dmu.dasom.api.domain.email.service.EmailService;
import dmu.dasom.api.domain.google.service.GoogleApiService;
import dmu.dasom.api.domain.recruit.dto.ResultCheckRequestDto;
import dmu.dasom.api.domain.recruit.dto.ResultCheckResponseDto;
import dmu.dasom.api.domain.recruit.enums.ResultCheckType;
import dmu.dasom.api.domain.recruit.service.RecruitService;
import dmu.dasom.api.global.dto.PageResponse;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,6 +26,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;

import java.util.List;
Expand All @@ -37,13 +42,17 @@ public class ApplicantServiceImpl implements ApplicantService {
private final ApplicantRepository applicantRepository;
private final EmailService emailService;
private final GoogleApiService googleApiService;
private final RecruitService recruitService;

@Value("${google.spreadsheet.id}")
private String spreadSheetId;

// 지원자 저장
@Override
public void apply(final ApplicantCreateRequestDto request) {
if (!recruitService.isRecruitmentActive())
throw new CustomException(ErrorCode.RECRUITMENT_NOT_ACTIVE);

final Optional<Applicant> applicant = findByStudentNo(request.getStudentNo());

// 이미 지원한 학번이 존재할 경우
Expand Down Expand Up @@ -139,6 +148,41 @@ public ApplicantDetailsResponseDto getApplicantByStudentNo(final String studentN
.orElseThrow(() -> new CustomException(ErrorCode.ARGUMENT_NOT_VALID));
}

// 합격 결과 확인
@Override
public ResultCheckResponseDto checkResult(final ResultCheckRequestDto request) {
// 결과 발표 시간 검증
final LocalDateTime resultAnnouncementSchedule = recruitService.getResultAnnouncementSchedule(request.getType());

// 설정 된 시간이 현재 시간보다 이전인 경우 예외 발생
final LocalDateTime now = LocalDateTime.now();
if (now.isBefore(resultAnnouncementSchedule))
throw new CustomException(ErrorCode.INVALID_INQUIRY_PERIOD);

// 지원자 정보 조회
final ApplicantDetailsResponseDto applicant = getApplicantByStudentNo(request.getStudentNo());

// 연락처 뒷자리가 일치하지 않을 경우 예외 발생
if (!applicant.getContact().split("-")[2].equals(request.getContactLastDigit()))
throw new CustomException(ErrorCode.ARGUMENT_NOT_VALID);

// 예약 코드 생성
String reservationCode = recruitService.generateReservationCode(request.getStudentNo(), request.getContactLastDigit());

// 합격 여부 반환
return ResultCheckResponseDto.builder()
.type(request.getType())
.studentNo(applicant.getStudentNo())
.name(applicant.getName())
.reservationCode(reservationCode)
.isPassed(request.getType().equals(ResultCheckType.DOCUMENT_PASS) ?
applicant.getStatus()
.equals(ApplicantStatus.DOCUMENT_PASSED) :
applicant.getStatus()
.equals(ApplicantStatus.INTERVIEW_PASSED))
.build();
}

// Repository에서 ID로 지원자 조회
private Applicant findById(final Long id) {
return applicantRepository.findById(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public enum ErrorCode {
SLOT_FULL(400, "C025", "해당 슬롯이 가득 찼습니다."),
RESERVATION_NOT_FOUND(400, "C026", "예약을 찾을 수 없습니다."),
SLOT_NOT_ACTIVE(400, "C027", "해당 슬롯이 비활성화 되었습니다."),
FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다.")
FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다."),
RECRUITMENT_NOT_ACTIVE(400, "C029", "모집 기간이 아닙니다."),
;

private final int status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public ResponseEntity<List<RecruitConfigResponseDto>> getRecruitSchedule() {
})
@GetMapping("/result")
public ResponseEntity<ResultCheckResponseDto> checkResult(@ModelAttribute final ResultCheckRequestDto request) {
return ResponseEntity.ok(recruitService.checkResult(request));
return ResponseEntity.ok(applicantService.checkResult(request));
}

// 면접 일정 생성
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
import dmu.dasom.api.domain.recruit.dto.ResultCheckResponseDto;
import dmu.dasom.api.domain.recruit.dto.RecruitConfigResponseDto;
import dmu.dasom.api.domain.recruit.dto.RecruitScheduleModifyRequestDto;
import dmu.dasom.api.domain.recruit.entity.Recruit;
import dmu.dasom.api.domain.recruit.enums.ConfigKey;
import dmu.dasom.api.domain.recruit.enums.ResultCheckType;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.util.List;

public interface RecruitService {
Expand All @@ -17,6 +15,10 @@ public interface RecruitService {

void modifyRecruitSchedule(final RecruitScheduleModifyRequestDto requestDto);

ResultCheckResponseDto checkResult(final ResultCheckRequestDto request);
String generateReservationCode(String studentNo, String contactLastDigits);

LocalDateTime getResultAnnouncementSchedule(ResultCheckType type);

boolean isRecruitmentActive();

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package dmu.dasom.api.domain.recruit.service;

import dmu.dasom.api.domain.applicant.dto.ApplicantDetailsResponseDto;
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
import dmu.dasom.api.domain.applicant.service.ApplicantService;
import dmu.dasom.api.domain.common.exception.CustomException;
import dmu.dasom.api.domain.common.exception.ErrorCode;
import dmu.dasom.api.domain.recruit.dto.ResultCheckRequestDto;
import dmu.dasom.api.domain.recruit.dto.ResultCheckResponseDto;
import dmu.dasom.api.domain.recruit.dto.RecruitConfigResponseDto;
import dmu.dasom.api.domain.recruit.dto.RecruitScheduleModifyRequestDto;
import dmu.dasom.api.domain.recruit.entity.Recruit;
Expand All @@ -17,7 +12,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
Expand All @@ -30,7 +24,6 @@
public class RecruitServiceImpl implements RecruitService {

private final RecruitRepository recruitRepository;
private final ApplicantService applicantService;

// 모집 일정 설정 조회
@Override
Expand All @@ -57,43 +50,28 @@ public void modifyRecruitSchedule(final RecruitScheduleModifyRequestDto request)
config.updateDateTime(dateTime);
}

// 합격 결과 확인
// 모집 기간 여부 확인
@Override
public ResultCheckResponseDto checkResult(final ResultCheckRequestDto request) {
// 예약 코드 생성
String reservationCode = generateReservationCode(request.getStudentNo(), request.getContactLastDigit());
public boolean isRecruitmentActive() {
final LocalDateTime recruitStartPeriod = parseDateTimeFormat(findByKey(ConfigKey.RECRUITMENT_PERIOD_START).getValue());
final LocalDateTime recruitEndPeriod = parseDateTimeFormat(findByKey(ConfigKey.RECRUITMENT_PERIOD_END).getValue());
final LocalDateTime now = LocalDateTime.now();

return now.isAfter(recruitStartPeriod) && now.isBefore(recruitEndPeriod);
}

@Override
public String generateReservationCode(String studentNo, String contactLastDigits) {
return studentNo + contactLastDigits; // 학번 전체 + 전화번호 뒤 4자리
}

// 결과 발표 시간 검증
final Recruit recruit = switch (request.getType()) {
@Override
public LocalDateTime getResultAnnouncementSchedule(ResultCheckType type) {
final Recruit recruit = switch (type) {
case DOCUMENT_PASS -> findByKey(ConfigKey.DOCUMENT_PASS_ANNOUNCEMENT);
case INTERVIEW_PASS -> findByKey(ConfigKey.INTERVIEW_PASS_ANNOUNCEMENT);
};
final LocalDateTime parsedTime = parseDateTimeFormat(recruit.getValue());

// 설정 된 시간이 현재 시간보다 이전인 경우 예외 발생
final LocalDateTime now = LocalDateTime.now();
if (now.isBefore(parsedTime))
throw new CustomException(ErrorCode.INVALID_INQUIRY_PERIOD);

// 지원자 정보 조회
final ApplicantDetailsResponseDto applicant = applicantService.getApplicantByStudentNo(request.getStudentNo());

// 연락처 뒷자리가 일치하지 않을 경우 예외 발생
if (!applicant.getContact().split("-")[2].equals(request.getContactLastDigit()))
throw new CustomException(ErrorCode.ARGUMENT_NOT_VALID);

// 합격 여부 반환
return ResultCheckResponseDto.builder()
.type(request.getType())
.studentNo(applicant.getStudentNo())
.name(applicant.getName())
.reservationCode(reservationCode)
.isPassed(request.getType().equals(ResultCheckType.DOCUMENT_PASS) ?
applicant.getStatus()
.equals(ApplicantStatus.DOCUMENT_PASSED) :
applicant.getStatus()
.equals(ApplicantStatus.INTERVIEW_PASSED))
.build();
return parseDateTimeFormat(recruit.getValue());
}

// DB에 저장된 모든 Recruit 객체를 찾아 반환
Expand Down Expand Up @@ -126,9 +104,4 @@ private LocalDateTime parseDateTimeFormat(String value) {
}
}

public String generateReservationCode(String studentNo, String contactLastDigits) {
return studentNo + contactLastDigits; // 학번 전체 + 전화번호 뒤 4자리
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import dmu.dasom.api.domain.email.enums.MailType;
import dmu.dasom.api.domain.email.service.EmailService;
import dmu.dasom.api.domain.google.service.GoogleApiService;
import dmu.dasom.api.domain.recruit.dto.ResultCheckRequestDto;
import dmu.dasom.api.domain.recruit.dto.ResultCheckResponseDto;
import dmu.dasom.api.domain.recruit.enums.ResultCheckType;
import dmu.dasom.api.domain.recruit.service.RecruitServiceImpl;
import dmu.dasom.api.global.dto.PageResponse;
import jakarta.mail.MessagingException;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -44,6 +48,9 @@ class ApplicantServiceTest {
@Mock
private EmailService emailService;

@Mock
private RecruitServiceImpl recruitService;

@InjectMocks
private ApplicantServiceImpl applicantService;

Expand All @@ -61,6 +68,7 @@ void apply_success() {
// given
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
when(request.getStudentNo()).thenReturn("20210000");
when(recruitService.isRecruitmentActive()).thenReturn(true);

Applicant mockApplicant = Applicant.builder()
.name("홍길동")
Expand Down Expand Up @@ -100,6 +108,7 @@ void apply_fail() {
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
when(request.getStudentNo()).thenReturn("20210000");
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(mock(Applicant.class)));
when(recruitService.isRecruitmentActive()).thenReturn(true);
when(request.getIsOverwriteConfirmed()).thenReturn(false);

// when
Expand All @@ -120,6 +129,7 @@ void apply_overwrite() {
when(request.getStudentNo()).thenReturn("20210000");
Applicant existingApplicant = mock(Applicant.class); // 기존 Applicant 객체 모킹
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(existingApplicant));
when(recruitService.isRecruitmentActive()).thenReturn(true);
when(request.getIsOverwriteConfirmed()).thenReturn(true);

// when
Expand Down Expand Up @@ -334,6 +344,7 @@ void apply_overwrite_withGoogleSheets() {
// given
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
when(request.getStudentNo()).thenReturn("20210000");
when(recruitService.isRecruitmentActive()).thenReturn(true);

Applicant existingApplicant = mock(Applicant.class); // 기존 Applicant 객체 모킹
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(existingApplicant));
Expand All @@ -348,4 +359,89 @@ void apply_overwrite_withGoogleSheets() {
verify(googleApiService).updateSheet(List.of(existingApplicant));
}

@Test
@DisplayName("합격 결과 확인 - 성공")
void checkResult_success() {
// given
LocalDateTime pastDateTime = LocalDateTime.now().minusHours(1);
when(recruitService.getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS)).thenReturn(pastDateTime);

ResultCheckRequestDto request = mock(ResultCheckRequestDto.class);
when(request.getType()).thenReturn(ResultCheckType.DOCUMENT_PASS);
when(request.getStudentNo()).thenReturn("20210000");
when(request.getContactLastDigit()).thenReturn("1234");

Applicant applicant = mock(Applicant.class);
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(applicant));

ApplicantDetailsResponseDto responseDto = mock(ApplicantDetailsResponseDto.class);
when(responseDto.getContact()).thenReturn("010-5678-1234");
when(responseDto.getStatus()).thenReturn(ApplicantStatus.DOCUMENT_PASSED);
when(responseDto.getStudentNo()).thenReturn("20210000");
when(responseDto.getName()).thenReturn("TestName");

when(applicant.toApplicantDetailsResponse()).thenReturn(responseDto);

when(recruitService.generateReservationCode("20210000", "1234")).thenReturn("202100001234");

// when
ResultCheckResponseDto result = applicantService.checkResult(request);

// then
assertNotNull(result);
assertTrue(result.getIsPassed());
verify(recruitService).getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS);
verify(applicantRepository).findByStudentNo("20210000");
}

@Test
@DisplayName("합격 결과 확인 - 실패 (확인 기간 아님)")
void checkResult_fail_inquiryPeriod() {
// given
LocalDateTime futureDateTime = LocalDateTime.now().plusHours(1);
when(recruitService.getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS)).thenReturn(futureDateTime);

ResultCheckRequestDto request = mock(ResultCheckRequestDto.class);
when(request.getType()).thenReturn(ResultCheckType.DOCUMENT_PASS);

// when
CustomException exception = assertThrows(CustomException.class, () -> {
applicantService.checkResult(request);
});

// then
assertEquals(ErrorCode.INVALID_INQUIRY_PERIOD, exception.getErrorCode());
verify(recruitService).getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS);
}

@Test
@DisplayName("합격 결과 확인 - 실패 (연락처 뒷자리 불일치)")
void checkResult_fail_contactMismatch() {
// given
LocalDateTime pastDateTime = LocalDateTime.now().minusHours(1);
when(recruitService.getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS)).thenReturn(pastDateTime);

ResultCheckRequestDto request = mock(ResultCheckRequestDto.class);
when(request.getType()).thenReturn(ResultCheckType.DOCUMENT_PASS);
when(request.getStudentNo()).thenReturn("20210000");
when(request.getContactLastDigit()).thenReturn("0000");

Applicant applicant = mock(Applicant.class);
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(applicant));

ApplicantDetailsResponseDto responseDto = mock(ApplicantDetailsResponseDto.class);
when(responseDto.getContact()).thenReturn("010-5678-1234");
when(applicant.toApplicantDetailsResponse()).thenReturn(responseDto);

// when
CustomException exception = assertThrows(CustomException.class, () -> {
applicantService.checkResult(request);
});

// then
assertEquals(ErrorCode.ARGUMENT_NOT_VALID, exception.getErrorCode());
verify(recruitService).getResultAnnouncementSchedule(ResultCheckType.DOCUMENT_PASS);
verify(applicantRepository).findByStudentNo("20210000");
}

}
Loading