diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index c3b986d40..97adc128a 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -1,19 +1,22 @@ package com.example.solidconnection.university.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; import com.example.solidconnection.university.service.LikedUnivApplyInfoService; import com.example.solidconnection.university.service.UnivApplyInfoQueryService; import com.example.solidconnection.university.service.UnivApplyInfoRecommendService; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; 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.RequestMapping; @@ -84,16 +87,19 @@ public ResponseEntity getUnivApplyInfoDetails( return ResponseEntity.ok(univApplyInfoDetailResponse); } - // todo: return타입 UniversityInfoForApplyPreviewResponses로 추후 수정 필요 - @GetMapping("/search") - public ResponseEntity> searchUnivApplyInfo( - @RequestParam(required = false, defaultValue = "") String region, - @RequestParam(required = false, defaultValue = "") List keyword, - @RequestParam(required = false, defaultValue = "") LanguageTestType testType, - @RequestParam(required = false, defaultValue = "") String testScore + @GetMapping("/search/filter") + public ResponseEntity searchUnivApplyInfoByFilter( + @Valid @ModelAttribute UnivApplyInfoFilterSearchRequest request ) { - List univApplyInfoPreviewResponse - = univApplyInfoQueryService.searchUnivApplyInfo(region, keyword, testType, testScore).univApplyInfoPreviews(); - return ResponseEntity.ok(univApplyInfoPreviewResponse); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + return ResponseEntity.ok(response); + } + + @GetMapping("/search/text") + public ResponseEntity searchUnivApplyInfoByText( + @RequestParam(required = false) String text + ) { + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java index f220ac3d3..af0f861db 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java @@ -4,8 +4,8 @@ public enum LanguageTestType { - CEFR((s1, s2) -> s1.compareTo(s2)), - JLPT((s1, s2) -> s2.compareTo(s1)), + CEFR(String::compareTo), + JLPT(Comparator.reverseOrder()), DALF(LanguageTestType::compareIntegerScores), DELF(LanguageTestType::compareIntegerScores), DUOLINGO(LanguageTestType::compareIntegerScores), @@ -16,7 +16,7 @@ public enum LanguageTestType { TOEFL_IBT(LanguageTestType::compareIntegerScores), TOEFL_ITP(LanguageTestType::compareIntegerScores), TOEIC(LanguageTestType::compareIntegerScores), - ETC((s1, s2) -> 0), // 기타 언어시험은 점수를 비교할 수 없으므로 항상 크다고 비교한다. + ETC((s1, s2) -> 0), // 기타 언어시험은 점수를 비교할 수 없으므로 항상 같다고 비교한다. ; private final Comparator comparator; diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java new file mode 100644 index 000000000..a49079319 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java @@ -0,0 +1,15 @@ +package com.example.solidconnection.university.dto; + +import com.example.solidconnection.university.domain.LanguageTestType; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record UnivApplyInfoFilterSearchRequest( + + @NotNull(message = "어학 시험 종류를 선택해주세요.") + LanguageTestType languageTestType, + String testScore, + List countryCode +) { + +} diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index 5447a14ba..15210c18d 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -4,16 +4,10 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.University; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface UniversityRepository extends JpaRepository { - @Query("SELECT u FROM University u WHERE u.country.code IN :countryCodes OR u.region.code IN :regionCodes") - List findByCountryCodeInOrRegionCodeIn(@Param("countryCodes") List countryCodes, @Param("regionCodes") List regionCodes); - default University getUniversityById(Long id) { return findById(id) .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java index 5c95e5d1e..9de0e98b6 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java @@ -8,6 +8,5 @@ public interface UnivApplyInfoFilterRepository { List findAllByRegionCodeAndKeywords(String regionCode, List keywords); - List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - String regionCode, List keywords, LanguageTestType testType, String testScore, String term); + List findAllByFilter(LanguageTestType testType, String testScore, String term, List countryKoreanNames); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index 9e57274ee..538074422 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -1,7 +1,6 @@ package com.example.solidconnection.university.repository.custom; import com.example.solidconnection.location.country.domain.QCountry; -import com.example.solidconnection.location.region.domain.QRegion; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QLanguageRequirement; import com.example.solidconnection.university.domain.QUnivApplyInfo; @@ -70,43 +69,71 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { + public List findAllByFilter( + LanguageTestType testType, String testScore, String term, List countryCodes + ) { QUniversity university = QUniversity.university; - QCountry country = QCountry.country; - QRegion region = QRegion.region; QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; + QCountry country = QCountry.country; + QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; - List filteredUnivApplyInfo = queryFactory - .selectFrom(univApplyInfo) + List filteredUnivApplyInfo = queryFactory.selectFrom(univApplyInfo) .join(univApplyInfo.university, university) .join(university.country, country) - .join(university.region, region) - .where(regionCodeEq(country, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords)) - .and(univApplyInfo.term.eq(term))) + .join(univApplyInfo.languageRequirements, languageRequirement) + .fetchJoin() + .where( + languageTestTypeEq(languageRequirement, testType), + termEq(univApplyInfo, term), + countryCodesIn(country, countryCodes) + ) + .distinct() .fetch(); - if (testScore == null || testScore.isEmpty()) { - if (testType != null) { - return filteredUnivApplyInfo.stream() - .filter(uai -> uai.getLanguageRequirements().stream() - .anyMatch(lr -> lr.getLanguageTestType().equals(testType))) - .toList(); - } + if (testScore == null || testScore.isBlank()) { return filteredUnivApplyInfo; } + /* + * 시험 유형에 따라 성적 비교 방식이 다르다. + * 입력된 점수가 대학에서 요구하는 최소 점수보다 높은지를 '쿼리로' 비교하기엔 쿼리가 지나치게 복잡해진다. + * 따라서 이 부분만 자바 코드로 필터링한다. + * */ return filteredUnivApplyInfo.stream() - .filter(uai -> compareMyTestScoreToMinPassScore(uai, testType, testScore) >= 0) + .filter(uai -> isGivenScoreOverMinPassScore(uai, testType, testScore)) .toList(); } - private int compareMyTestScoreToMinPassScore(UnivApplyInfo univApplyInfo, LanguageTestType testType, String testScore) { + private BooleanExpression languageTestTypeEq( + QLanguageRequirement languageRequirement, LanguageTestType givenTestType + ) { + if (givenTestType == null) { + return null; + } + return languageRequirement.languageTestType.eq(givenTestType); + } + + private BooleanExpression termEq(QUnivApplyInfo univApplyInfo, String givenTerm) { + if (givenTerm == null || givenTerm.isBlank()) { + return null; + } + return univApplyInfo.term.eq(givenTerm); + } + + private BooleanExpression countryCodesIn(QCountry country, List givenCountryCodes) { + if (givenCountryCodes == null || givenCountryCodes.isEmpty()) { + return null; + } + return country.code.in(givenCountryCodes); + } + + private boolean isGivenScoreOverMinPassScore( + UnivApplyInfo univApplyInfo, LanguageTestType givenTestType, String givenTestScore + ) { return univApplyInfo.getLanguageRequirements().stream() - .filter(languageRequirement -> languageRequirement.getLanguageTestType().equals(testType)) + .filter(languageRequirement -> languageRequirement.getLanguageTestType().equals(givenTestType)) .findFirst() - .map(requirement -> testType.compare(testScore, requirement.getMinScore())) - .orElse(-1); + .map(requirement -> givenTestType.compare(givenTestScore, requirement.getMinScore())) + .orElse(-1) >= 0; } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index ff0215c63..e8ba3140b 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -1,14 +1,13 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; -import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -20,7 +19,6 @@ public class UnivApplyInfoQueryService { private final UnivApplyInfoRepository univApplyInfoRepository; - private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; // todo: 구현체 숨기고 univApplyInfoRepository만 사용하도록 @Value("${university.term}") public String term; @@ -39,22 +37,19 @@ public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) return UnivApplyInfoDetailResponse.of(university, univApplyInfo); } - /* - * 대학교 검색 결과를 불러온다. - * - 권역, 키워드, 언어 시험 종류, 언어 시험 점수를 조건으로 검색하여 결과를 반환한다. - * - 권역은 영어 대문자로 받는다 e.g. ASIA - * - 키워드는 국가명 또는 대학명에 포함되는 것이 조건이다. - * - 언어 시험 점수는 합격 최소 점수보다 높은 것이 조건이다. - * */ @Transactional(readOnly = true) - @ThunderingHerdCaching(key = "univApplyInfo:{0}:{1}:{2}:{3}", cacheManager = "customCacheManager", ttlSec = 86400) - public UnivApplyInfoPreviewResponses searchUnivApplyInfo( - String regionCode, List keywords, LanguageTestType testType, String testScore) { - - return new UnivApplyInfoPreviewResponses(universityFilterRepository - .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) - .stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList()); + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request) { + List responses = univApplyInfoRepository + .findAllByFilter(request.languageTestType(), request.testScore(), term, request.countryCode()) + .stream() + .map(UnivApplyInfoPreviewResponse::from) + .toList(); + return new UnivApplyInfoPreviewResponses(responses); + } + + @Transactional(readOnly = true) + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text) { + // todo: 구현 + return null; } } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index fd6b4b26d..69fa9b619 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -1,24 +1,26 @@ package com.example.solidconnection.university.service; import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.fixture.LanguageRequirementFixture; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; -import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; import java.util.List; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; @@ -30,9 +32,6 @@ class UnivApplyInfoQueryServiceTest { @Autowired private UnivApplyInfoQueryService univApplyInfoQueryService; - @SpyBean - private UnivApplyInfoFilterRepository univApplyInfoFilterRepository; - @SpyBean private UnivApplyInfoRepository univApplyInfoRepository; @@ -42,164 +41,110 @@ class UnivApplyInfoQueryServiceTest { @Autowired private LanguageRequirementFixture languageRequirementFixture; - @Test - void 대학_지원_정보를_상세_조회한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - - // when - UnivApplyInfoDetailResponse response = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - - // then - assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); - } - - @Test - void 대학_지원_정보_상세_조회시_캐시가_적용된다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - - // when - UnivApplyInfoDetailResponse firstResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - UnivApplyInfoDetailResponse secondResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - - // then - assertThat(firstResponse).isEqualTo(secondResponse); - then(univApplyInfoRepository).should(times(1)).getUnivApplyInfoById(괌대학_A_지원_정보.getId()); - } - - @Test - void 존재하지_않는_대학_지원_정보를_조회하면_예외가_발생한다() { - // given - Long invalidUnivApplyInfoId = 9999L; - - // when & then - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> univApplyInfoQueryService.getUnivApplyInfoDetail(invalidUnivApplyInfoId)) - .havingRootCause() - .isInstanceOf(CustomException.class) - .withMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); - } - - @Test - void 전체_대학_지원_정보를_조회한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); - UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보 = univApplyInfoFixture.네바다주립대학_라스베이거스_지원_정보(); - UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); - UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); - UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of(), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder( - UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), - UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보), - UnivApplyInfoPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보), - UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), - UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) - ); - } - - @Test - void 대학_지원_정보_조회시_캐시가_적용된다() { - // given - univApplyInfoFixture.괌대학_A_지원_정보(); - String regionCode = "AMERICAS"; - List keywords = List.of("괌"); - LanguageTestType testType = LanguageTestType.TOEFL_IBT; - String testScore = "70"; - String term = "2024-1"; - - // when - UnivApplyInfoPreviewResponses firstResponse = - univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); - UnivApplyInfoPreviewResponses secondResponse = - univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); - - // then - assertThat(firstResponse).isEqualTo(secondResponse); - then(univApplyInfoFilterRepository).should(times(1)) - .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - regionCode, keywords, testType, testScore, term); - } - - @Test - void 지역으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - univApplyInfoFixture.코펜하겐IT대학_지원_정보(); - univApplyInfoFixture.그라츠공과대학_지원_정보(); - univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - "AMERICAS", List.of(), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); - } - - @Test - void 키워드로_대학_지원_정보를_필터링한다() { - // given - univApplyInfoFixture.괌대학_A_지원_정보(); - UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); - UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of("라", "일본"), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder( - UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), - UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) - ); - } - - @Test - void 어학시험_조건으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - languageRequirementFixture.토플_80(괌대학_A_지원_정보); - languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); - languageRequirementFixture.토플_70(괌대학_B_지원_정보); - languageRequirementFixture.토익_900(괌대학_B_지원_정보); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of(), LanguageTestType.TOEFL_IBT, "70"); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보)); + @Nested + class 대학_지원_정보_상세_조회 { + + @Test + void 대학_지원_정보를_상세_조회한다() { + // given + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoDetailResponse response = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + + // then + assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); + } + + @Test + void 대학_지원_정보_상세_조회시_캐시가_적용된다() { + // given + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoDetailResponse firstResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + UnivApplyInfoDetailResponse secondResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + + // then + assertThat(firstResponse).isEqualTo(secondResponse); + then(univApplyInfoRepository).should(times(1)).getUnivApplyInfoById(괌대학_A_지원_정보.getId()); + } + + @Test + void 존재하지_않는_대학_지원_정보를_조회하면_예외가_발생한다() { + // given + Long invalidUnivApplyInfoId = 9999L; + + // when & then + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> univApplyInfoQueryService.getUnivApplyInfoDetail(invalidUnivApplyInfoId)) + .havingRootCause() + .isInstanceOf(CustomException.class) + .withMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); + } } - @Test - void 모든_조건으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - languageRequirementFixture.토플_80(괌대학_A_지원_정보); - languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); - languageRequirementFixture.토플_70(서던덴마크대학교_지원_정보); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - "EUROPE", List.of(), LanguageTestType.TOEFL_IBT, "70"); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactly(UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보)); + @Nested + class 대학_지원_정보_필터링_검색 { + + @Test + void 어학_시험_종류로_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request = new UnivApplyInfoFilterSearchRequest(TOEIC, null, null); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + languageRequirementFixture.토플_70(괌대학_B_지원_정보); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); + } + + @Test + void 어학_시험_점수가_기준치_이상인_곳을_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request = new UnivApplyInfoFilterSearchRequest(TOEIC, "800", null); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + languageRequirementFixture.토익_900(괌대학_B_지원_정보); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); + } + + @Test + void 국가_코드로_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request1 = new UnivApplyInfoFilterSearchRequest(TOEIC, null, List.of("US")); + UnivApplyInfoFilterSearchRequest request2 = new UnivApplyInfoFilterSearchRequest(TOEIC, null, List.of("US", "CA")); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보 = univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보(); + languageRequirementFixture.토익_800(메모리얼대학_세인트존스_A_지원_정보); + + // when + UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1); + UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2); + + // then + assertAll( + () -> assertThat(response1.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)), + () -> assertThat(response2.univApplyInfoPreviews()) + .containsExactlyInAnyOrder( + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보) + ) + ); + } } }