Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -1,18 +1,20 @@
package side.onetime.exception.status;

import org.springframework.http.HttpStatus;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import side.onetime.global.common.code.BaseErrorCode;
import side.onetime.global.common.dto.ErrorReasonDto;

@Getter
@RequiredArgsConstructor
public enum FixedErrorStatus implements BaseErrorCode {
_NOT_FOUND_FIXED_SCHEDULES(HttpStatus.NOT_FOUND, "FIXED-001", "고정 스케줄 목록을 가져오는 데 실패했습니다."),
_NOT_FOUND_EVERYTIME_TIMETABLE(HttpStatus.NOT_FOUND, "FIXED-002", "에브리타임 시간표를 가져오는 데 실패했습니다. 공개 범위를 확인해주세요."),
_EVERYTIME_TIMETABLE_NOT_PUBLIC(HttpStatus.NOT_FOUND, "FIXED-002", "에브리타임 시간표를 가져오는 데 실패했습니다. 공개 범위를 확인해주세요."),
_EVERYTIME_TIMETABLE_PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FIXED-003", "에브리타임 시간표 파싱 중 문제가 발생했습니다."),
_EVERYTIME_API_FAILED(HttpStatus.SERVICE_UNAVAILABLE, "FIXED-004", "에브리타임 API 연동 중 서버 오류가 발생했습니다."),
_NOT_FOUND_EVERYTIME_TIMETABLE(HttpStatus.NOT_FOUND, "FIXED-005", "에브리타임 시간표에 등록된 수업이 없습니다."),
;

private final HttpStatus httpStatus;
Expand Down
39 changes: 37 additions & 2 deletions src/main/java/side/onetime/service/FixedScheduleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.jsoup.Jsoup;
Expand Down Expand Up @@ -35,6 +37,10 @@
@Service
@RequiredArgsConstructor
public class FixedScheduleService {

private static final int EVERYTIME_PRIVATE_STATUS = -2;
private static final int EVERYTIME_PUBLIC_STATUS = 1;

private final UserRepository userRepository;
private final FixedScheduleRepository fixedScheduleRepository;
private final FixedSelectionRepository fixedSelectionRepository;
Expand Down Expand Up @@ -132,13 +138,42 @@ private String fetchTimetableXml(String identifier) {
}

if (!xmlResponse.contains("subject")) {
// 200 OK 응답이 왔지만, 테이블이 비어있는 경우 (공개 범위 설정 오류 등)
throw new CustomException(FixedErrorStatus._NOT_FOUND_EVERYTIME_TIMETABLE);
// 200 OK 응답이 왔지만, 테이블이 비어있는 경우
int status = extractStatusFromXml(xmlResponse);
if (EVERYTIME_PRIVATE_STATUS == status) {
// 1. 공개 범위가 '전체 공개'가 아닌 경우
throw new CustomException(FixedErrorStatus._EVERYTIME_TIMETABLE_NOT_PUBLIC);
} else if (EVERYTIME_PUBLIC_STATUS == status) {
// 2. '전체 공개'이지만, 등록된 수업이 없는 경우
throw new CustomException(FixedErrorStatus._NOT_FOUND_EVERYTIME_TIMETABLE);
}
}

return xmlResponse;
}

/**
* XML 문자열에서 status 속성값을 추출합니다.
* 예: <table ... status="1" ... /> -> 1 반환
*/
private int extractStatusFromXml(String xml) {
// status="숫자" 패턴을 찾음
Pattern pattern = Pattern.compile("status=\"(-?\\d+)\"");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compile메소드 내부에서 매 호출마다 Pattern 객체가 생성되기 때문에 상수로 빼면 좋겠습니다!

Matcher matcher = pattern.matcher(xml);

if (matcher.find()) {
try {
return Integer.parseInt(matcher.group(1));
} catch (NumberFormatException e) {
// 숫자가 아닌 경우 파싱 에러 처리
throw new CustomException(FixedErrorStatus._EVERYTIME_TIMETABLE_PARSE_ERROR);
}
}

// status 속성을 찾지 못한 경우 파싱 에러 처리
throw new CustomException(FixedErrorStatus._EVERYTIME_TIMETABLE_PARSE_ERROR);
}

/**
* Jsoup을 사용하여 XML을 파싱하고 DTO 리스트로 변환합니다.
*/
Expand Down
35 changes: 33 additions & 2 deletions src/test/java/side/onetime/fixed/FixedControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,13 @@ public void getEverytimeTimetable() throws Exception {
}

@Test
@DisplayName("[FAILED] 에브리타임 시간표 조회에 실패한다 (공개 범위 설정 오류 등)")
@DisplayName("[FAILED] 에브리타임 시간표 조회에 실패한다 (공개 범위가 '전체 공개'가 아님)")
public void getEverytimeTimetable_Fail_NotFound() throws Exception {
// given
String identifier = "de9YHaTAnl47JtxH0muz";

Mockito.when(fixedScheduleService.getUserEverytimeTimetable(identifier))
.thenThrow(new CustomException(FixedErrorStatus._NOT_FOUND_EVERYTIME_TIMETABLE));
.thenThrow(new CustomException(FixedErrorStatus._EVERYTIME_TIMETABLE_NOT_PUBLIC));

// when
ResultActions result = mockMvc.perform(
Expand All @@ -243,6 +243,37 @@ public void getEverytimeTimetable_Fail_NotFound() throws Exception {
));
}

@Test
@DisplayName("[FAILED] 에브리타임 시간표 조회에 실패한다 (등록된 수업 없음)")
public void getEverytimeTimetable_Fail_Empty() throws Exception {
// given
String identifier = "de9YHaTAnl47JtxH0muz";

Mockito.when(fixedScheduleService.getUserEverytimeTimetable(identifier))
.thenThrow(new CustomException(FixedErrorStatus._NOT_FOUND_EVERYTIME_TIMETABLE));

// when
ResultActions result = mockMvc.perform(
RestDocumentationRequestBuilders.get("/api/v1/fixed-schedules/everytime/{identifier}", identifier)
.accept(MediaType.APPLICATION_JSON)
);

// then
result.andExpect(status().isNotFound())
.andExpect(jsonPath("$.is_success").value(false))
.andExpect(jsonPath("$.code").value("FIXED-005"))
.andExpect(jsonPath("$.message").value("에브리타임 시간표에 등록된 수업이 없습니다."))
.andDo(MockMvcRestDocumentationWrapper.document("fixed/getEverytime-fail-empty",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.tag("Fixed API")
.build()
)
));
}

@Test
@DisplayName("[FAILED] 에브리타임 시간표 조회에 실패한다 (식별자 유효성 검증 실패)")
public void getEverytimeTimetable_Fail_Validation() throws Exception {
Expand Down
Loading