diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index e4f314225..104114c5c 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -39,6 +39,7 @@ public class ApplicationQueryService { * 다른 지원자들의 성적을 조회한다. * - 유저가 다른 지원자들을 볼 수 있는지 검증한다. * - 지역과 키워드를 통해 대학을 필터링한다. + * - 지역은 영어 대문자로 받는다 e.g. ASIA * - 1지망, 2지망 지원자들을 조회한다. * */ @Transactional(readOnly = true) diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index 60b2c6cc1..e77cbd31b 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -16,10 +16,10 @@ public record SignUpRequest( @Schema(description = "카카오 인증 토큰", example = "kakaoToken123") String kakaoOauthToken, - @ArraySchema(schema = @Schema(description = "관심 지역 목록", example = "[\"아시아\", \"유럽\"]")) + @ArraySchema(schema = @Schema(description = "관심 지역 목록", example = "아시아권")) List interestedRegions, - @ArraySchema(schema = @Schema(description = "관심 국가 목록", example = "[\"일본\", \"독일\"]")) + @ArraySchema(schema = @Schema(description = "관심 국가 목록", example = "일본")) List interestedCountries, @Schema(description = "지원 준비 단계", example = "CONSIDERING") diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index e3415e9c4..1c63fe94a 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -50,7 +50,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/", "/index.html", "/favicon.ico", "/file/profile/pre", "/auth/kakao", "/auth/sign-up", "/auth/reissue", - "/university/detail/**", "/university/search/**", "/home", + "/university/detail/**", "/university/search/**", "/university/recommends", "/swagger-ui/**", "/v3/api-docs/**" ) .permitAll() diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java b/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java index 7394203cb..c0c610bce 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java +++ b/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java @@ -66,11 +66,10 @@ public ResponseEntity handleJwtException(JwtException ex) { .body(errorResponse); } - @ExceptionHandler(Exception.class) public ResponseEntity handleOtherException(Exception ex) { String errorMessage = ex.getMessage(); - log.error("알 수 없는 예외 발생 : {}", errorMessage); + log.error("서버 내부 예외 발생 : {}", errorMessage); ErrorResponse errorResponse = new ErrorResponse(NOT_DEFINED_ERROR, errorMessage); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) diff --git a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java b/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java index bbb4dd015..903bc9320 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java +++ b/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java @@ -14,6 +14,7 @@ import jakarta.persistence.OneToMany; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,6 +22,7 @@ import java.util.Set; @Getter +@EqualsAndHashCode(of = "id") @AllArgsConstructor(access = AccessLevel.PUBLIC) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @@ -71,10 +73,10 @@ public class UniversityInfoForApply { @Column(length = 500) private String details; - @OneToMany(mappedBy = "universityInfoForApply", fetch = FetchType.LAZY) + @OneToMany(mappedBy = "universityInfoForApply", fetch = FetchType.EAGER) private Set languageRequirements = new HashSet<>(); - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) private University university; public void addLanguageRequirements(LanguageRequirement languageRequirements) { diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java index bc2306737..009496be7 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java @@ -10,6 +10,6 @@ public interface UniversityFilterRepository { List findByRegionCodeAndKeywords(String regionCode, List keywords); - List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScore( - String regionCode, List keywords, LanguageTestType testType, String testScore); + List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + String regionCode, List keywords, LanguageTestType testType, String testScore, String term); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index 994618a6b..0beac6763 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -3,7 +3,6 @@ import com.example.solidconnection.entity.QCountry; import com.example.solidconnection.entity.QRegion; import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.university.domain.QLanguageRequirement; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.QUniversityInfoForApply; import com.example.solidconnection.university.domain.University; @@ -68,26 +67,25 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScore( - String regionCode, List keywords, LanguageTestType testType, String testScore) { + public List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; QUniversityInfoForApply universityInfoForApply = QUniversityInfoForApply.universityInfoForApply; - QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; List filteredUniversityInfoForApply = queryFactory .selectFrom(universityInfoForApply) .join(universityInfoForApply.university, university) .join(university.country, country) - .join(country.region, region) - .join(universityInfoForApply.languageRequirements, languageRequirement) + .join(university.region, region) .where(regionCodeEq(region, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords))) + .and(countryOrUniversityContainsKeyword(country, university, keywords)) + .and(universityInfoForApply.term.eq(term))) .fetch(); - if(testType == null || testScore == null || testScore.isEmpty()) { + if(testScore == null || testScore.isEmpty()) { if(testType != null) { return filteredUniversityInfoForApply.stream() .filter(uifa -> uifa.getLanguageRequirements().stream() diff --git a/src/main/java/com/example/solidconnection/university/service/GeneralRecommendUniversities.java b/src/main/java/com/example/solidconnection/university/service/GeneralRecommendUniversities.java index f9441c61d..6eb70f81b 100644 --- a/src/main/java/com/example/solidconnection/university/service/GeneralRecommendUniversities.java +++ b/src/main/java/com/example/solidconnection/university/service/GeneralRecommendUniversities.java @@ -1,11 +1,13 @@ package com.example.solidconnection.university.service; +import com.example.solidconnection.repositories.CountryRepository; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import jakarta.annotation.PostConstruct; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import java.util.List; @@ -24,17 +26,18 @@ public class GeneralRecommendUniversities { @Getter private final List recommendUniversities; private final UniversityInfoForApplyRepository universityInfoForApplyRepository; + private final CountryRepository countryRepository; private final List candidates = List.of( "오스트라바 대학", "RMIT멜버른공과대학(A형)", "알브슈타트 지그마링엔 대학", "뉴저지시티대학(A형)", "도요대학", "템플대학(A형)", "빈 공과대학교", "리스본대학 공과대학", "바덴뷔르템베르크 산학협력대학", "긴다이대학", "네바다주립대학 라스베이거스(B형)", "릴 가톨릭 대학", - "그라츠공과대학", "그라츠 대학", "코펜하겐 IT대학", "메이지대학", "분쿄가쿠인대학" + "그라츠공과대학", "그라츠 대학", "코펜하겐 IT대학", "메이지대학", "분쿄가쿠인대학", "린츠 카톨릭 대학교" ); @Value("${university.term}") public String term; - @PostConstruct + @EventListener(ApplicationReadyEvent.class) public void init() { int i = 0; while (recommendUniversities.size() < RECOMMEND_UNIVERSITY_NUM && i < candidates.size()) { diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java index cf58f801a..bb704faea 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java @@ -24,6 +24,7 @@ public class UniversityRecommendService { private final UniversityInfoForApplyRepository universityInfoForApplyRepository; private final GeneralRecommendUniversities generalRecommendUniversities; private final SiteUserRepository siteUserRepository; + @Value("${university.term}") public String term; @@ -39,16 +40,16 @@ public UniversityRecommendsResponse getPersonalRecommends(String email) { // 맞춤 추천 대학교를 불러온다. List personalRecommends = universityInfoForApplyRepository .findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(siteUser, term); - List shuffledList + List trimmedRecommendUniversities = personalRecommends.subList(0, Math.min(RECOMMEND_UNIVERSITY_NUM, personalRecommends.size())); - Collections.shuffle(personalRecommends); + Collections.shuffle(trimmedRecommendUniversities); // 맞춤 추천 대학교의 수가 6개보다 적다면, 일반 추천 대학교를 부족한 수 만큼 불러온다. - if (shuffledList.size() < 6) { - shuffledList.addAll(getGeneralRecommendsExcludingSelected(shuffledList)); + if (trimmedRecommendUniversities.size() < RECOMMEND_UNIVERSITY_NUM) { + trimmedRecommendUniversities.addAll(getGeneralRecommendsExcludingSelected(trimmedRecommendUniversities)); } - return new UniversityRecommendsResponse(shuffledList.stream() + return new UniversityRecommendsResponse(trimmedRecommendUniversities.stream() .map(UniversityInfoForApplyPreviewResponse::from) .toList()); } diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityService.java b/src/main/java/com/example/solidconnection/university/service/UniversityService.java index cbc97e4f6..94e8bba83 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityService.java @@ -52,6 +52,7 @@ public UniversityDetailResponse getUniversityDetail(Long universityInfoForApplyI /* * 대학교 검색 결과를 불러온다. * - 권역, 키워드, 언어 시험 종류, 언어 시험 점수를 조건으로 검색하여 결과를 반환한다. + * - 권역은 영어 대문자로 받는다 e.g. ASIA * - 키워드는 국가명 또는 대학명에 포함되는 것이 조건이다. * - 언어 시험 점수는 합격 최소 점수보다 높은 것이 조건이다. * */ @@ -59,7 +60,7 @@ public UniversityDetailResponse getUniversityDetail(Long universityInfoForApplyI public List searchUniversity( String regionCode, List keywords, LanguageTestType testType, String testScore) { return universityFilterRepository - .findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScore(regionCode, keywords, testType, testScore) + .findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) .stream() .map(UniversityInfoForApplyPreviewResponse::from) .toList(); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index c3fa34ce4..8c88643c6 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -221,4 +221,45 @@ VALUES ('2024-1', 1, 2, 1, 'IRRELEVANT', 'HOME_UNIVERSITY_PAYMENT', '파견대 '여학생만 신청가능', NULL, NULL, '기숙사 없음, 계약된 외부 기숙사 사용-“Maison de Claire Ibaraki” 62,300엔/월, 2식 포함, 계약시 66,000엔 청구 (2023년 6월기준)', NULL), ('2024-1', 20, 2, 3, 'ONE_YEAR', 'HOME_UNIVERSITY_PAYMENT', NULL, NULL, NULL, NULL, - '기숙사 보유, off campus, 식사 미제공, 45,000~50,000엔/월', NULL); \ No newline at end of file + '기숙사 보유, off campus, 식사 미제공, 45,000~50,000엔/월', NULL); + +INSERT INTO language_requirement(language_test_type, min_score, university_info_for_apply_id) +VALUES + ('TOEFL_IBT', '80', 1), + ('IELTS', '6.5', 1), + ('TOEFL_IBT', '80', 2), + ('IELTS', '6.5', 2), + ('TOEFL_IBT', '79', 3), + ('IELTS', '6.0', 3), + ('TOEFL_IBT', '88', 4), + ('IELTS', '6.5', 4), + ('TOEFL_IBT', '88', 5), + ('IELTS', '6.5', 5), + ('TOEFL_IBT', '85', 6), + ('IELTS', '6.5', 6), + ('TOEFL_IBT', '85', 7), + ('IELTS', '6.5', 7), + ('TOEFL_IBT', '80', 8), + ('IELTS', '6.0', 8), + ('TOEFL_IBT', '83', 9), + ('IELTS', '6.5', 9), + ('TOEFL_IBT', '87', 10), + ('IELTS', '6.5', 10), + ('TOEFL_IBT', '90', 11), + ('IELTS', '6.5', 11), + ('TOEFL_IBT', '85', 12), + ('IELTS', '6.5', 12), + ('TOEFL_IBT', '82', 13), + ('IELTS', '6.0', 13), + ('TOEFL_IBT', '85', 14), + ('IELTS', '6.5', 14), + ('TOEFL_IBT', '90', 15), + ('IELTS', '7.0', 15), + ('TOEFL_IBT', '85', 16), + ('IELTS', '6.5', 16), + ('DELF', 'B2', 17), + ('DALF', 'C1', 17), + ('JLPT', 'N2', 18), + ('JLPT', 'N1', 19), + ('TOEFL_IBT', '85', 20), + ('IELTS', '6.5', 20); diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java new file mode 100644 index 000000000..ee46733a1 --- /dev/null +++ b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java @@ -0,0 +1,193 @@ +package com.example.solidconnection.e2e; + +import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.entity.InterestedCountry; +import com.example.solidconnection.entity.InterestedRegion; +import com.example.solidconnection.repositories.InterestedCountyRepository; +import com.example.solidconnection.repositories.InterestedRegionRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; +import com.example.solidconnection.university.dto.UniversityRecommendsResponse; +import com.example.solidconnection.university.service.GeneralRecommendUniversities; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; + +import java.util.List; + +import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; +import static com.example.solidconnection.university.service.UniversityRecommendService.RECOMMEND_UNIVERSITY_NUM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("추천 대학 목록 조회 테스트") +class UniversityRecommendTest extends UniversityDataSetUpEndToEndTest { + + @Autowired + private SiteUserRepository siteUserRepository; + + @Autowired + private InterestedRegionRepository interestedRegionRepository; + + @Autowired + private InterestedCountyRepository interestedCountyRepository; + + @Autowired + private TokenService tokenService; + + @Autowired + private GeneralRecommendUniversities generalRecommendUniversities; + + private SiteUser siteUser; + private String accessToken; + + @BeforeEach + void setUp() { + // setUp - 회원 정보 저장 + String email = "email@email.com"; + siteUser = siteUserRepository.save(createSiteUserByEmail(email)); + generalRecommendUniversities.init(); + + // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 + accessToken = tokenService.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); + tokenService.saveToken(refreshToken, TokenType.REFRESH); + } + + @Test + void 관심_지역을_설정한_사용자의_추천_대학_목록을_조회한다() { + // setUp - 관심 지역 저장 + interestedRegionRepository.save(new InterestedRegion(siteUser, 영미권)); + + // request - 요청 + UniversityRecommendsResponse response = RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .log().all() + .get("/university/recommends") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().as(UniversityRecommendsResponse.class); + + assertAll( + String.format("관심 지역에 해당하는 학교를 포함한 %d개의 대학 목록을 반환한다.", RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .hasSize(RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .containsOnlyOnceElementsOf(List.of( + UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), + UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), + UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), + UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보) + )) + ); + } + + @Test + void 관심_국가를_설정한_사용자의_추천_대학_목록을_조회한다() { + // setUp - 관심 국가 저장 + interestedCountyRepository.save(new InterestedCountry(siteUser, 덴마크)); + + // request - 요청 + UniversityRecommendsResponse response = RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .log().all() + .get("/university/recommends") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().as(UniversityRecommendsResponse.class); + + assertAll( + String.format("관심 국가에 해당하는 학교를 포함한 %d개의 대학 목록을 반환한다.", RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .hasSize(RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .containsOnlyOnceElementsOf(List.of( + UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), + UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보) + )) + ); + } + + @Test + void 관심_지역과_관심_국가를_설정한_사용자의_추천_대학_목록을_조회한다() { + // setUp - 관심 지역과 국가 저장 + interestedRegionRepository.save(new InterestedRegion(siteUser, 영미권)); + interestedCountyRepository.save(new InterestedCountry(siteUser, 덴마크)); + + // request - 요청 + UniversityRecommendsResponse response = RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .log().all() + .get("/university/recommends") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().as(UniversityRecommendsResponse.class); + + assertAll( + String.format("관심 지역 또는 국가에 해당하는 학교를 포함한 %d개의 대학 목록을 반환한다.", RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .hasSize(RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .containsOnlyOnceElementsOf(List.of( + UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), + UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), + UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), + UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), + UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), + UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보) + )) + ); + } + + @Test + void 관심_지역_또는_관심_국가를_설정하지_않은_사용자의_추천_대학_목록을_조회한다() { + // request - 요청 + UniversityRecommendsResponse response = RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .log().all() + .get("/university/recommends") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().as(UniversityRecommendsResponse.class); + + List generalRecommendUniversities + = this.generalRecommendUniversities.getRecommendUniversities().stream() + .map(UniversityInfoForApplyPreviewResponse::from) + .toList(); + assertAll( + String.format("일반 추천 대학 목록 %d개를 반환한다.", RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .hasSize(RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(generalRecommendUniversities) + .containsOnlyOnceElementsOf(response.recommendedUniversities()) + ); + } + + @Test + void 로그인하지_않은_방문객의_추천_대학_목록을_조회한다() { + // request - 요청 + UniversityRecommendsResponse response = RestAssured.given() + .log().all() + .get("/university/recommends") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().as(UniversityRecommendsResponse.class); + + List generalRecommendUniversities + = this.generalRecommendUniversities.getRecommendUniversities().stream() + .map(UniversityInfoForApplyPreviewResponse::from) + .toList(); + assertAll( + String.format("일반 추천 대학 목록 %d개를 반환한다.", RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(response.recommendedUniversities()) + .hasSize(RECOMMEND_UNIVERSITY_NUM), + () -> assertThat(generalRecommendUniversities) + .containsOnlyOnceElementsOf(response.recommendedUniversities()) + ); + } +}