diff --git a/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java b/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java index b56b49f53..d451ae057 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java @@ -1,9 +1,7 @@ package com.example.solidconnection.admin.dto; -public record UnivApplyInfoResponse( - String firstChoiceUnivName, - String secondChoiceUnivName, - String thirdChoiceUnivName -) { +import java.util.List; + +public record UnivApplyInfoResponse(List choices) { } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java index 14df833e5..9451e8100 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java @@ -1,12 +1,16 @@ package com.example.solidconnection.admin.university.dto; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public record AdminHomeUniversityCreateRequest( @NotBlank(message = "협정 대학명은 필수입니다") @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") - String name + String name, + + @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") + int maxChoiceCount ) { } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java index 719185202..6842b4909 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java @@ -4,13 +4,15 @@ public record AdminHomeUniversityResponse( long id, - String name + String name, + int maxChoiceCount ) { public static AdminHomeUniversityResponse from(HomeUniversity homeUniversity) { return new AdminHomeUniversityResponse( homeUniversity.getId(), - homeUniversity.getName() + homeUniversity.getName(), + homeUniversity.getMaxChoiceCount() ); } } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java index e22473099..464192bf3 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java @@ -1,12 +1,16 @@ package com.example.solidconnection.admin.university.dto; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public record AdminHomeUniversityUpdateRequest( @NotBlank(message = "협정 대학명은 필수입니다") @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") - String name + String name, + + @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") + int maxChoiceCount ) { } diff --git a/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java index bcf1a8edf..af3a10f35 100644 --- a/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java +++ b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java @@ -48,7 +48,7 @@ public AdminHomeUniversityResponse getHomeUniversity(Long id) { ) public AdminHomeUniversityResponse createHomeUniversity(AdminHomeUniversityCreateRequest request) { validateNameNotExists(request.name()); - HomeUniversity homeUniversity = new HomeUniversity(null, request.name()); + HomeUniversity homeUniversity = new HomeUniversity(null, request.name(), request.maxChoiceCount()); return AdminHomeUniversityResponse.from(homeUniversityRepository.save(homeUniversity)); } @@ -69,7 +69,7 @@ public AdminHomeUniversityResponse updateHomeUniversity(Long id, AdminHomeUniver HomeUniversity homeUniversity = homeUniversityRepository.findById(id) .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); validateNameNotDuplicated(request.name(), id); - homeUniversity.update(request.name()); + homeUniversity.update(request.name(), request.maxChoiceCount()); return AdminHomeUniversityResponse.from(homeUniversity); } diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 14c138868..e168eeced 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -5,19 +5,27 @@ import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OrderBy; import jakarta.persistence.Table; +import java.util.ArrayList; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -29,13 +37,7 @@ @Entity @Table(indexes = { @Index(name = "idx_app_user_term_delete", - columnList = "site_user_id, term_id, is_delete"), - @Index(name = "idx_app_first_choice_search", - columnList = "verify_status, term_id, is_delete, first_choice_university_info_for_apply_id"), - @Index(name = "idx_app_second_choice_search", - columnList = "verify_status, term_id, is_delete, second_choice_university_info_for_apply_id"), - @Index(name = "idx_app_third_choice_search", - columnList = "verify_status, term_id, is_delete, third_choice_university_info_for_apply_id") + columnList = "site_user_id, term_id, is_delete") }) public class Application extends BaseEntity { @@ -70,18 +72,19 @@ public class Application extends BaseEntity { @Column(name = "is_delete", nullable = false) private boolean isDelete = false; - @Column(nullable = false, name = "first_choice_university_info_for_apply_id") - private long firstChoiceUnivApplyInfoId; - - @Column(name = "second_choice_university_info_for_apply_id") - private Long secondChoiceUnivApplyInfoId; - - @Column(name = "third_choice_university_info_for_apply_id") - private Long thirdChoiceUnivApplyInfoId; - @Column(name = "site_user_id", nullable = false) private long siteUserId; + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name = "application_choice", + joinColumns = @JoinColumn(name = "application_id"), + indexes = @Index(name = "idx_app_choice_univ_apply_info_id", columnList = "univ_apply_info_id") + ) + @OrderBy("choiceOrder ASC") + @BatchSize(size = 100) + private List choices = new ArrayList<>(); + public Application( SiteUser siteUser, Gpa gpa, @@ -101,39 +104,14 @@ public Application( LanguageTest languageTest, long termId, Integer updateCount, - long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId, + List choices, String nicknameForApply) { this.siteUserId = siteUser.getId(); this.gpa = gpa; this.languageTest = languageTest; this.termId = termId; this.updateCount = updateCount; - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; - this.nicknameForApply = nicknameForApply; - this.verifyStatus = PENDING; - } - - public Application( - SiteUser siteUser, - Gpa gpa, - LanguageTest languageTest, - long termId, - long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId, - String nicknameForApply) { - this.siteUserId = siteUser.getId(); - this.gpa = gpa; - this.languageTest = languageTest; - this.termId = termId; - this.updateCount = 1; - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; + this.choices = new ArrayList<>(choices); this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } diff --git a/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java b/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java new file mode 100644 index 000000000..dfab460c2 --- /dev/null +++ b/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.application.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; + +@Embeddable +@Getter +public class ApplicationChoice { + + @Column(name = "choice_order", nullable = false) + private int choiceOrder; + + @Column(name = "univ_apply_info_id", nullable = false) + private long univApplyInfoId; + + protected ApplicationChoice() { + } + + public ApplicationChoice(int choiceOrder, long univApplyInfoId) { + this.choiceOrder = choiceOrder; + this.univApplyInfoId = univApplyInfoId; + } +} diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java index 657c7c2c3..793a26d6d 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java @@ -2,9 +2,6 @@ import java.util.List; -public record ApplicationsResponse( - List firstChoice, - List secondChoice, - List thirdChoice) { +public record ApplicationsResponse(List> choices) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java index 449b1ca2c..0de9eb85c 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java @@ -2,17 +2,12 @@ import com.example.solidconnection.university.dto.validation.ValidUnivApplyInfoChoice; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; @ValidUnivApplyInfoChoice public record UnivApplyInfoChoiceRequest( - @JsonProperty("firstChoiceUniversityId") - Long firstChoiceUnivApplyInfoId, - - @JsonProperty("secondChoiceUniversityId") - Long secondChoiceUnivApplyInfoId, - - @JsonProperty("thirdChoiceUniversityId") - Long thirdChoiceUnivApplyInfoId) { + @JsonProperty("choices") + List univApplyInfoIds) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java index 7755eea55..d7c6a1b14 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java @@ -1,41 +1,24 @@ package com.example.solidconnection.application.dto; -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; - import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -public record UnivApplyInfoResponse( - - @JsonProperty("firstChoiceUniversity") - String firstChoiceUnivApplyInfo, - - @JsonProperty("secondChoiceUniversity") - @JsonInclude(NON_NULL) - String secondChoiceUnivApplyInfo, - - @JsonProperty("thirdChoiceUniversity") - @JsonInclude(NON_NULL) - String thirdChoiceUnivApplyInfo) { +public record UnivApplyInfoResponse(List choices) { public static UnivApplyInfoResponse of(Application application, List univApplyInfos) { - Map univApplyInfoMap = univApplyInfos.stream() - .collect(Collectors.toMap( - UnivApplyInfo::getId, - UnivApplyInfo::getKoreanName - )); + Map nameById = univApplyInfos.stream() + .collect(Collectors.toMap(UnivApplyInfo::getId, UnivApplyInfo::getKoreanName)); + + List choiceNames = application.getChoices().stream() + .sorted(Comparator.comparingInt(ApplicationChoice::getChoiceOrder)) + .map(choice -> nameById.get(choice.getUnivApplyInfoId())) + .toList(); - return new UnivApplyInfoResponse( - univApplyInfoMap.get(application.getFirstChoiceUnivApplyInfoId()), - application.getSecondChoiceUnivApplyInfoId() != null - ? univApplyInfoMap.get(application.getSecondChoiceUnivApplyInfoId()) : null, - application.getThirdChoiceUnivApplyInfoId() != null - ? univApplyInfoMap.get(application.getThirdChoiceUnivApplyInfoId()) : null - ); + return new UnivApplyInfoResponse(choiceNames); } } diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index f9dda3096..0324cb46d 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -16,20 +16,19 @@ public interface ApplicationRepository extends JpaRepository boolean existsByNicknameForApply(String nicknameForApply); @Query(""" - SELECT a + SELECT DISTINCT a FROM Application a - WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) + JOIN a.choices c + WHERE c.univApplyInfoId IN :univApplyInfoIds AND a.verifyStatus = :status AND a.termId = :termId AND a.isDelete = false """) - List findAllByUnivApplyInfoIds(@Param("univApplyInfoIds") List univApplyInfoIds, @Param("status") VerifyStatus status, @Param("termId") long termId); + List findAllByUnivApplyInfoIds( + @Param("univApplyInfoIds") List univApplyInfoIds, + @Param("status") VerifyStatus status, + @Param("termId") long termId); - // TODO: 근본 해결 필요 - // 지원서 유일성은 DB 제약으로 강제하고 - // 이 조회는 임시 회피 로직을 제거하는 방향으로 수정 필요. Optional findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(long siteUserId, long termId); default Application getApplicationBySiteUserIdAndTermId(long siteUserId, long termId) { 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 60a495714..8ddefe476 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -5,6 +5,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicantsResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; @@ -14,7 +15,9 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.repository.TermRepository; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.repository.HomeUniversityRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import io.micrometer.common.util.StringUtils; @@ -22,10 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,16 +34,17 @@ @Service public class ApplicationQueryService { + private static final int DEFAULT_MAX_CHOICE_COUNT = 3; + private final ApplicationRepository applicationRepository; private final UnivApplyInfoRepository univApplyInfoRepository; private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; private final SiteUserRepository siteUserRepository; private final TermRepository termRepository; + private final HomeUniversityRepository homeUniversityRepository; - // todo: 캐싱 정책 변경 시 수정 필요 @Transactional(readOnly = true) public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) { - // 1. 대학 지원 정보 필터링 (regionCode, keyword) SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List keywords = StringUtils.isNotBlank(keyword) ? List.of(keyword) : List.of(); @@ -51,18 +52,20 @@ public ApplicationsResponse getApplicants(long siteUserId, String regionCode, St Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - List univApplyInfos = universityFilterRepository.findAllByRegionCodeAndKeywordsAndTermId(regionCode, keywords, term.getId()); + List univApplyInfos = universityFilterRepository + .findAllByRegionCodeAndKeywordsAndTermId(regionCode, keywords, term.getId()); if (univApplyInfos.isEmpty()) { - return new ApplicationsResponse(List.of(), List.of(), List.of()); + return new ApplicationsResponse(List.of()); } - // 2. 조건에 맞는 모든 Application 한 번에 조회 List univApplyInfoIds = univApplyInfos.stream() .map(UnivApplyInfo::getId) .toList(); - List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); - // 3. 지원서 분류 및 DTO 변환 - return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); + List applications = applicationRepository + .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); + + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser, maxChoiceCount); } @Transactional(readOnly = true) @@ -73,57 +76,59 @@ public ApplicationsResponse getApplicantsByUserApplications(long siteUserId) { Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - Application userLatestApplication = applicationRepository.getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()); + Application userLatestApplication = applicationRepository + .getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()); - List univApplyInfoIds = Stream.of( - userLatestApplication.getFirstChoiceUnivApplyInfoId(), - userLatestApplication.getSecondChoiceUnivApplyInfoId(), - userLatestApplication.getThirdChoiceUnivApplyInfoId() - ) - .filter(Objects::nonNull) + List univApplyInfoIds = userLatestApplication.getChoices().stream() + .map(ApplicationChoice::getUnivApplyInfoId) .collect(Collectors.toList()); if (univApplyInfoIds.isEmpty()) { - return new ApplicationsResponse(List.of(), List.of(), List.of()); + return new ApplicationsResponse(List.of()); } - List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); + List applications = applicationRepository + .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); List univApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); - return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser, maxChoiceCount); } private ApplicationsResponse classifyApplicationsByChoice( List univApplyInfos, List applications, - SiteUser siteUser) { - Map> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUnivApplyInfoId); - Map> secondChoiceMap = createChoiceMap(applications, Application::getSecondChoiceUnivApplyInfoId); - Map> thirdChoiceMap = createChoiceMap(applications, Application::getThirdChoiceUnivApplyInfoId); - - List firstChoiceApplicants = - createUniversityApplicantsResponses(univApplyInfos, firstChoiceMap, siteUser); - List secondChoiceApplicants = - createUniversityApplicantsResponses(univApplyInfos, secondChoiceMap, siteUser); - List thirdChoiceApplicants = - createUniversityApplicantsResponses(univApplyInfos, thirdChoiceMap, siteUser); - - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); + SiteUser siteUser, + int maxChoiceCount) { + List> allChoices = new ArrayList<>(); + for (int order = 1; order <= maxChoiceCount; order++) { + final int choiceOrder = order; + Map> choiceMap = buildChoiceMapForOrder(applications, choiceOrder); + allChoices.add(createUniversityApplicantsResponses(univApplyInfos, choiceMap, siteUser)); + } + return new ApplicationsResponse(allChoices); } - private Map> createChoiceMap( - List applications, - Function choiceIdExtractor) { - Map> choiceMap = new HashMap<>(); + private int resolveMaxChoiceCount(SiteUser siteUser) { + if (siteUser.getHomeUniversityId() == null) { + return DEFAULT_MAX_CHOICE_COUNT; + } + return homeUniversityRepository.findById(siteUser.getHomeUniversityId()) + .map(HomeUniversity::getMaxChoiceCount) + .orElse(DEFAULT_MAX_CHOICE_COUNT); + } + private Map> buildChoiceMapForOrder(List applications, int order) { + Map> map = new HashMap<>(); for (Application application : applications) { - Long choiceId = choiceIdExtractor.apply(application); - if (choiceId != null) { - choiceMap.computeIfAbsent(choiceId, k -> new ArrayList<>()).add(application); - } + application.getChoices().stream() + .filter(c -> c.getChoiceOrder() == order) + .findFirst() + .ifPresent(choice -> map + .computeIfAbsent(choice.getUnivApplyInfoId(), k -> new ArrayList<>()) + .add(application)); } - - return choiceMap; + return map; } private List createUniversityApplicantsResponses( @@ -143,7 +148,8 @@ public void validateSiteUserCanViewApplicants(long siteUserId) { Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()).getVerifyStatus(); + VerifyStatus verifyStatus = applicationRepository + .getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()).getVerifyStatus(); if (verifyStatus != VerifyStatus.APPROVED) { throw new CustomException(APPLICATION_NOT_APPROVED); } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index e8c1144b4..8a9379f67 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,14 +1,17 @@ package com.example.solidconnection.application.service; import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.CHOICE_COUNT_EXCEEDS_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.CURRENT_TERM_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -23,12 +26,13 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.repository.TermRepository; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.repository.HomeUniversityRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +42,7 @@ public class ApplicationSubmissionService { public static final int APPLICATION_UPDATE_COUNT_LIMIT = 3; + private static final int DEFAULT_MAX_CHOICE_COUNT = 3; private final ApplicationRepository applicationRepository; private final GpaScoreRepository gpaScoreRepository; @@ -45,22 +50,23 @@ public class ApplicationSubmissionService { private final SiteUserRepository siteUserRepository; private final TermRepository termRepository; private final UnivApplyInfoRepository univApplyInfoRepository; + private final HomeUniversityRepository homeUniversityRepository; - // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. - // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRequest) { SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = applyRequest.univApplyInfoChoiceRequest(); + UnivApplyInfoChoiceRequest choiceRequest = applyRequest.univApplyInfoChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - Long firstChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.firstChoiceUnivApplyInfoId(); - Long secondChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.secondChoiceUnivApplyInfoId(); - Long thirdChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.thirdChoiceUnivApplyInfoId(); + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + validateChoiceCount(choiceRequest, maxChoiceCount); + + List univApplyInfos = getValidUnivApplyInfos(choiceRequest.univApplyInfoIds()); + List choices = buildChoices(choiceRequest.univApplyInfoIds()); Optional existingApplication = applicationRepository.findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(siteUser.getId(), term.getId()); @@ -78,26 +84,14 @@ public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRe languageTestScore.getLanguageTest(), term.getId(), updateCount, - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId, + choices, getRandomNickname() ); newApplication.setVerifyStatus(VerifyStatus.APPROVED); applicationRepository.save(newApplication); - List univApplyInfoIds = Stream.of( - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId - ) - .filter(Objects::nonNull) - .toList(); - - List uniApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); - - return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, uniApplyInfos); + return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, univApplyInfos); } private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { @@ -119,12 +113,33 @@ private LanguageTestScore getValidLanguageTestScore(SiteUser siteUser, Long lang return languageTestScore; } - private String getRandomNickname() { - String randomNickname = NicknameCreator.createRandomNickname(); - while (applicationRepository.existsByNicknameForApply(randomNickname)) { - randomNickname = NicknameCreator.createRandomNickname(); + private int resolveMaxChoiceCount(SiteUser siteUser) { + if (siteUser.getHomeUniversityId() == null) { + return DEFAULT_MAX_CHOICE_COUNT; + } + return homeUniversityRepository.findById(siteUser.getHomeUniversityId()) + .map(HomeUniversity::getMaxChoiceCount) + .orElse(DEFAULT_MAX_CHOICE_COUNT); + } + + private List getValidUnivApplyInfos(List ids) { + List univApplyInfos = univApplyInfoRepository.findAllByIds(ids); + if (univApplyInfos.size() != ids.size()) { + throw new CustomException(UNIV_APPLY_INFO_NOT_FOUND); + } + return univApplyInfos; + } + + private void validateChoiceCount(UnivApplyInfoChoiceRequest request, int maxChoiceCount) { + if (request.univApplyInfoIds().size() > maxChoiceCount) { + throw new CustomException(CHOICE_COUNT_EXCEEDS_LIMIT); } - return randomNickname; + } + + private List buildChoices(List univApplyInfoIds) { + return IntStream.range(0, univApplyInfoIds.size()) + .mapToObj(i -> new ApplicationChoice(i + 1, univApplyInfoIds.get(i))) + .toList(); } private void validateUpdateLimitNotExceed(Application application) { @@ -132,4 +147,12 @@ private void validateUpdateLimitNotExceed(Application application) { throw new CustomException(APPLY_UPDATE_LIMIT_EXCEED); } } + + private String getRandomNickname() { + String randomNickname = NicknameCreator.createRandomNickname(); + while (applicationRepository.existsByNicknameForApply(randomNickname)) { + randomNickname = NicknameCreator.createRandomNickname(); + } + return randomNickname; + } } diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 8dc4ea70e..7e3c5e6c5 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -98,6 +98,8 @@ public enum ErrorCode { FIRST_CHOICE_REQUIRED(HttpStatus.BAD_REQUEST.value(), "1지망 대학교를 입력해주세요."), THIRD_CHOICE_REQUIRES_SECOND(HttpStatus.BAD_REQUEST.value(), "2지망 없이 3지망을 선택할 수 없습니다."), DUPLICATE_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "지망 선택이 중복되었습니다."), + INVALID_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "유효하지 않은 지망 대학교가 포함되어 있습니다."), + CHOICE_COUNT_EXCEEDS_LIMIT(HttpStatus.BAD_REQUEST.value(), "지망 수가 최대 지망 수를 초과했습니다."), // community INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(), "잘못된 카테고리명입니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java index caca1f013..dd840b0a3 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java @@ -1,6 +1,7 @@ package com.example.solidconnection.siteuser.repository.custom; import static com.example.solidconnection.application.domain.QApplication.application; +import static com.example.solidconnection.university.domain.QUnivApplyInfo.univApplyInfo; import static com.example.solidconnection.mentor.domain.QMentor.mentor; import static com.example.solidconnection.mentor.domain.QMentorApplication.mentorApplication; import static com.example.solidconnection.mentor.domain.QMentoring.mentoring; @@ -26,9 +27,11 @@ import com.example.solidconnection.admin.dto.UserSearchCondition; import com.example.solidconnection.admin.dto.UserSearchResponse; import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.domain.UserStatus; -import com.example.solidconnection.university.domain.QUnivApplyInfo; +import com.querydsl.core.Tuple; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -38,7 +41,10 @@ import jakarta.persistence.EntityManager; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -112,17 +118,6 @@ public class SiteUserFilterRepositoryImpl implements SiteUserFilterRepository { userBan.createdAt ); - private static final QUnivApplyInfo firstChoiceUnivApplyInfo = new QUnivApplyInfo("firstChoiceUnivApplyInfo"); - private static final QUnivApplyInfo secondChoiceUnivApplyInfo = new QUnivApplyInfo("secondChoiceUnivApplyInfo"); - private static final QUnivApplyInfo thirdChoiceUnivApplyInfo = new QUnivApplyInfo("thirdChoiceUnivApplyInfo"); - - private static final ConstructorExpression UNIV_APPLY_INFO_RESPONSE_PROJECTION = - Projections.constructor( - UnivApplyInfoResponse.class, - firstChoiceUnivApplyInfo.koreanName, - secondChoiceUnivApplyInfo.koreanName, - thirdChoiceUnivApplyInfo.koreanName - ); private final JPAQueryFactory queryFactory; @@ -327,21 +322,42 @@ private MenteeInfoResponse fetchMenteeInfo(long userId) { } private UnivApplyInfoResponse fetchUnivApplyInfo(long userId) { - UnivApplyInfoResponse result = queryFactory - .select(UNIV_APPLY_INFO_RESPONSE_PROJECTION) - .from(application) - .leftJoin(firstChoiceUnivApplyInfo).on(firstChoiceUnivApplyInfo.id.eq(application.firstChoiceUnivApplyInfoId)) - .leftJoin(secondChoiceUnivApplyInfo).on(secondChoiceUnivApplyInfo.id.eq(application.secondChoiceUnivApplyInfoId)) - .leftJoin(thirdChoiceUnivApplyInfo).on(thirdChoiceUnivApplyInfo.id.eq(application.thirdChoiceUnivApplyInfoId)) - .where(application.siteUserId.eq(userId)) + Application latestApplication = queryFactory + .selectFrom(application) + .where(application.siteUserId.eq(userId), application.isDelete.isFalse()) .orderBy(application.createdAt.desc()) .fetchFirst(); - if (result == null) { - return new UnivApplyInfoResponse(null, null, null); + if (latestApplication == null) { + return new UnivApplyInfoResponse(List.of()); + } + + List univApplyInfoIds = latestApplication.getChoices().stream() + .sorted(Comparator.comparingInt(ApplicationChoice::getChoiceOrder)) + .map(ApplicationChoice::getUnivApplyInfoId) + .toList(); + + if (univApplyInfoIds.isEmpty()) { + return new UnivApplyInfoResponse(List.of()); } - return result; + List tuples = queryFactory + .select(univApplyInfo.id, univApplyInfo.koreanName) + .from(univApplyInfo) + .where(univApplyInfo.id.in(univApplyInfoIds)) + .fetch(); + + Map nameById = tuples.stream() + .collect(Collectors.toMap( + t -> t.get(univApplyInfo.id), + t -> t.get(univApplyInfo.koreanName) + )); + + List choiceNames = univApplyInfoIds.stream() + .map(nameById::get) + .toList(); + + return new UnivApplyInfoResponse(choiceNames); } diff --git a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java index 506491c0f..aea0cb9dc 100644 --- a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java @@ -25,7 +25,11 @@ public class HomeUniversity extends BaseEntity { @Column(name = "name", nullable = false, unique = true, length = 100) private String name; - public void update(String name) { + @Column(name = "max_choice_count", nullable = false) + private int maxChoiceCount; + + public void update(String name, int maxChoiceCount) { this.name = name; + this.maxChoiceCount = maxChoiceCount; } } diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java index 500695646..c2ce41344 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java @@ -2,35 +2,38 @@ import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_UNIV_APPLY_INFO_CHOICE; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.stream.Stream; -public class ValidUnivApplyInfoChoiceValidator implements ConstraintValidator { +public class ValidUnivApplyInfoChoiceValidator + implements ConstraintValidator { @Override public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorContext context) { context.disableDefaultConstraintViolation(); - if (isFirstChoiceNotSelected(request)) { + List ids = request.univApplyInfoIds(); + + if (ids == null || ids.isEmpty()) { context.buildConstraintViolationWithTemplate(FIRST_CHOICE_REQUIRED.getMessage()) .addConstraintViolation(); return false; } - if (isThirdChoiceWithoutSecond(request)) { - context.buildConstraintViolationWithTemplate(THIRD_CHOICE_REQUIRES_SECOND.getMessage()) + if (ids.stream().anyMatch(Objects::isNull)) { + context.buildConstraintViolationWithTemplate(INVALID_UNIV_APPLY_INFO_CHOICE.getMessage()) .addConstraintViolation(); return false; } - if (isDuplicate(request)) { + if (hasDuplicate(ids)) { context.buildConstraintViolationWithTemplate(DUPLICATE_UNIV_APPLY_INFO_CHOICE.getMessage()) .addConstraintViolation(); return false; @@ -39,22 +42,8 @@ public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorCo return true; } - private boolean isFirstChoiceNotSelected(UnivApplyInfoChoiceRequest request) { - return request.firstChoiceUnivApplyInfoId() == null; - } - - private boolean isThirdChoiceWithoutSecond(UnivApplyInfoChoiceRequest request) { - return request.thirdChoiceUnivApplyInfoId() != null && request.secondChoiceUnivApplyInfoId() == null; - } - - private boolean isDuplicate(UnivApplyInfoChoiceRequest request) { - Set uniqueIds = new HashSet<>(); - return Stream.of( - request.firstChoiceUnivApplyInfoId(), - request.secondChoiceUnivApplyInfoId(), - request.thirdChoiceUnivApplyInfoId() - ) - .filter(Objects::nonNull) - .anyMatch(id -> !uniqueIds.add(id)); + private boolean hasDuplicate(List ids) { + Set unique = new HashSet<>(); + return ids.stream().anyMatch(id -> !unique.add(id)); } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 58eefa5bf..c8d040bf4 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -49,8 +49,8 @@ VALUES ('test@test.email', 'yonso', 'https://github.com/nayonsoso.png', 'CONSIDERING', 'MENTEE', '$2a$10$psmwlxPfqWnIlq9JrlQJkuXr1XtjRNsyVOgcTWYZub5jFfn0TML76', 'EMAIL'); -- 12341234 -INSERT INTO home_university (id, name) -VALUES (1, '인하대학교'); +INSERT INTO home_university (id, name, max_choice_count) +VALUES (1, '인하대학교', 3); INSERT INTO host_university(id, country_code, region_code, english_name, format_name, korean_name, accommodation_url, english_course_url, homepage_url, diff --git a/src/main/resources/db/migration/V50__dynamic_choice_count.sql b/src/main/resources/db/migration/V50__dynamic_choice_count.sql new file mode 100644 index 000000000..a3cd0e68e --- /dev/null +++ b/src/main/resources/db/migration/V50__dynamic_choice_count.sql @@ -0,0 +1,20 @@ +ALTER TABLE home_university + ADD COLUMN max_choice_count INT NOT NULL DEFAULT 3, + ADD CONSTRAINT chk_max_choice_count CHECK (max_choice_count >= 1); + +CREATE TABLE application_choice +( + application_id BIGINT NOT NULL, + choice_order INT NOT NULL, + univ_apply_info_id BIGINT NOT NULL, + PRIMARY KEY (application_id, choice_order), + CONSTRAINT fk_app_choice_application + FOREIGN KEY (application_id) REFERENCES application (id), + CONSTRAINT fk_app_choice_univ_apply_info + FOREIGN KEY (univ_apply_info_id) REFERENCES university_info_for_apply (id) +); + +CREATE INDEX idx_app_choice_univ_apply_info_id ON application_choice (univ_apply_info_id); + +ALTER TABLE application + MODIFY COLUMN first_choice_university_info_for_apply_id BIGINT NULL; diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java index 8c1d5a5f1..63231c5bd 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java @@ -38,6 +38,7 @@ import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.fixture.UniversityFixture; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -191,9 +192,7 @@ class 유저_상세_정보_조회 { termId, new Gpa(4.0, 4.5, "http://gpa-report.com/test.pdf"), new LanguageTest(LanguageTestType.TOEIC, "900", "http://language-test.com/test.pdf"), - firstChoice.getId(), - secondChoice.getId(), - null + List.of(firstChoice.getId(), secondChoice.getId()) ); // when diff --git a/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java index 425475b01..3977f2f1a 100644 --- a/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java @@ -84,7 +84,8 @@ class 협정대학_단건_조회 { // then assertAll( () -> assertThat(response.id()).isEqualTo(homeUniversity.getId()), - () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()) + () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()), + () -> assertThat(response.maxChoiceCount()).isEqualTo(homeUniversity.getMaxChoiceCount()) ); } @@ -103,7 +104,7 @@ class 협정대학_생성 { @Test void 유효한_요청으로_협정대학을_생성하면_성공한다() { // given - AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교", 3); // when AdminHomeUniversityResponse response = adminHomeUniversityService.createHomeUniversity(request); @@ -112,7 +113,9 @@ class 협정대학_생성 { HomeUniversity saved = homeUniversityRepository.findById(response.id()).orElseThrow(); assertAll( () -> assertThat(response.name()).isEqualTo("인하대학교"), - () -> assertThat(saved.getName()).isEqualTo("인하대학교") + () -> assertThat(response.maxChoiceCount()).isEqualTo(3), + () -> assertThat(saved.getName()).isEqualTo("인하대학교"), + () -> assertThat(saved.getMaxChoiceCount()).isEqualTo(3) ); } @@ -120,7 +123,7 @@ class 협정대학_생성 { void 이미_존재하는_이름으로_생성하면_예외가_발생한다() { // given homeUniversityFixture.인하대학교(); - AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.createHomeUniversity(request)) @@ -136,7 +139,7 @@ class 협정대학_수정 { void 유효한_요청으로_협정대학을_수정하면_성공한다() { // given HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교", 5); // when AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); @@ -145,14 +148,16 @@ class 협정대학_수정 { HomeUniversity updated = homeUniversityRepository.findById(homeUniversity.getId()).orElseThrow(); assertAll( () -> assertThat(response.name()).isEqualTo("연세대학교"), - () -> assertThat(updated.getName()).isEqualTo("연세대학교") + () -> assertThat(response.maxChoiceCount()).isEqualTo(5), + () -> assertThat(updated.getName()).isEqualTo("연세대학교"), + () -> assertThat(updated.getMaxChoiceCount()).isEqualTo(5) ); } @Test void 존재하지_않는_협정대학을_수정하면_예외가_발생한다() { // given - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(999L, request)) @@ -164,8 +169,8 @@ class 협정대학_수정 { void 다른_협정대학의_이름으로_수정하면_예외가_발생한다() { // given homeUniversityFixture.인하대학교(); - HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교")); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교", 3)); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(other.getId(), request)) @@ -177,7 +182,7 @@ class 협정대학_수정 { void 같은_이름으로_수정하면_성공한다() { // given HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교", 3); // when AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java index 683f794aa..850f79282 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -4,6 +4,7 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -19,9 +20,7 @@ public class ApplicationFixture { long termId, Gpa gpa, LanguageTest languageTest, - Long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId + List univApplyInfoIds ) { return applicationFixtureBuilder.application() .siteUser(siteUser) @@ -29,9 +28,7 @@ public class ApplicationFixture { .languageTest(languageTest) .nicknameForApply(nicknameForApply) .termId(termId) - .firstChoiceUnivApplyInfoId(firstChoiceUnivApplyInfoId) - .secondChoiceUnivApplyInfoId(secondChoiceUnivApplyInfoId) - .thirdChoiceUnivApplyInfoId(thirdChoiceUnivApplyInfoId) + .univApplyInfoIds(univApplyInfoIds) .create(); } } diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index d8c7fb7e9..bd1431727 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -1,11 +1,14 @@ package com.example.solidconnection.application.fixture; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -17,9 +20,7 @@ public class ApplicationFixtureBuilder { private Gpa gpa; private LanguageTest languageTest; - private Long firstChoiceUnivApplyInfoId; - private Long secondChoiceUnivApplyInfoId; - private Long thirdChoiceUnivApplyInfoId; + private List univApplyInfoIds = List.of(); private SiteUser siteUser; private String nicknameForApply; private long termId; @@ -38,18 +39,8 @@ public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { return this; } - public ApplicationFixtureBuilder firstChoiceUnivApplyInfoId(Long firstChoiceUnivApplyInfoId) { - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - return this; - } - - public ApplicationFixtureBuilder secondChoiceUnivApplyInfoId(Long secondChoiceUnivApplyInfoId) { - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - return this; - } - - public ApplicationFixtureBuilder thirdChoiceUnivApplyInfoId(Long thirdChoiceUnivApplyInfoId) { - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; + public ApplicationFixtureBuilder univApplyInfoIds(List univApplyInfoIds) { + this.univApplyInfoIds = univApplyInfoIds; return this; } @@ -69,14 +60,16 @@ public ApplicationFixtureBuilder termId(long termId) { } public Application create() { + List choices = IntStream.range(0, univApplyInfoIds.size()) + .mapToObj(i -> new ApplicationChoice(i + 1, univApplyInfoIds.get(i))) + .toList(); Application application = new Application( siteUser, gpa, languageTest, termId, - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId, + 1, + choices, nicknameForApply ); application.setVerifyStatus(VerifyStatus.APPROVED); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 42b4d1524..a897284cb 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -105,151 +105,88 @@ class 지원자_목록_조회_테스트 { void 이번_학기_전체_지원자를_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); Application application3 = applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice()).containsAll(List.of( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1), - ApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(application3), user1) - )); + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1), + ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(application3), user1) + ); } @Test void 이번_학기_특정_지역_지원자를_조회한다() { - //given + // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - regionFixture.영미권().getCode(), - "" - ); + user1.getId(), regionFixture.영미권().getCode(), ""); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1) ); } @Test void 이번_학기_지원자를_대학_국문_이름으로_필터링해서_조회한다() { - //given + // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - null, - "미국" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), null, "미국"); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1) ); } @@ -257,37 +194,22 @@ class 지원자_목록_조회_테스트 { void 현재_학기_지원서만_조회되고_이전_학기_지원서는_제외된다() { // given Term previousTerm = termFixture.이전_학기("2024-2"); - Application application = applicationFixture.지원서( - user1, - "nickname1_past", - previousTerm.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + applicationFixture.지원서( + user1, "nickname1_past", previousTerm.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); - Application currentApplication = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, List.of(currentApplication), user1), ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(), user1), ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(), user1) @@ -298,37 +220,23 @@ class 지원자_목록_조회_테스트 { void 동일_유저의_여러_지원서_중_최신_지원서만_조회된다() { // given Application firstApplication = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); firstApplication.setIsDeleteTrue(); applicationRepository.save(firstApplication); Application secondApplication = applicationFixture.지원서( - user1, - "nickname2", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user1, "nickname2", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice().stream() + assertThat(response.choices().get(0).stream() .flatMap(univ -> univ.applicants().stream()) .filter(ApplicantResponse::isMine)) .containsExactly(ApplicantResponse.of(secondApplication, true)); @@ -342,42 +250,27 @@ class 경쟁자_목록_조회_테스트 { void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); + // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1, application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1, application2), user1) ); } @@ -385,41 +278,26 @@ class 경쟁자_목록_조회_테스트 { void 이번_학기_지원한_대학_중_미선택이_있을_때_경쟁자_목록을_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - 버지니아공과대학_지원_정보.getId(), - 서던덴마크대학교_지원_정보.getId() + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId(), 버지니아공과대학_지원_정보.getId(), 서던덴마크대학교_지원_정보.getId()) ); - Application application3 = applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + applicationFixture.지원서( + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then - assertThat(response.firstChoice()) + assertThat(response.choices().get(0)) .hasSize(1) .allSatisfy(uar -> { assertThat(uar.koreanName()).isEqualTo(괌대학_A_지원_정보.getKoreanName()); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index 4b3442536..138a8203b 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.CHOICE_COUNT_EXCEEDS_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; import static org.assertj.core.api.Assertions.assertThat; @@ -9,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -24,8 +26,11 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.fixture.TermFixture; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.HomeUniversityFixture; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -56,17 +61,18 @@ class ApplicationSubmissionServiceTest { @Autowired private TermFixture termFixture; + @Autowired + private HomeUniversityFixture homeUniversityFixture; + private SiteUser user; private UnivApplyInfo 괌대학_A_지원_정보; private UnivApplyInfo 버지니아공과대학_지원_정보; private UnivApplyInfo 서던덴마크대학교_지원_정보; - private Term term; @BeforeEach void setUp() { term = termFixture.현재_학기("2025-2"); - user = siteUserFixture.사용자(); 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); 버지니아공과대학_지원_정보 = univApplyInfoFixture.버지니아공과대학_지원_정보(term.getId()); @@ -79,9 +85,11 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - 버지니아공과대학_지원_정보.getId(), - 서던덴마크대학교_지원_정보.getId() + List.of( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); @@ -92,28 +100,50 @@ void setUp() { Application savedApplication = applicationRepository .findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(user.getId(), term.getId()) .orElseThrow(); + List savedChoices = savedApplication.getChoices(); assertAll( - () -> assertThat(response.totalApplyCount()) - .isEqualTo(APPLICATION_UPDATE_COUNT_LIMIT), - () -> assertThat(response.applyCount()) - .isEqualTo(savedApplication.getUpdateCount()), - () -> assertThat(response.appliedUniversities().firstChoiceUnivApplyInfo()) - .isEqualTo(괌대학_A_지원_정보.getKoreanName()), - () -> assertThat(response.appliedUniversities().secondChoiceUnivApplyInfo()) - .isEqualTo(버지니아공과대학_지원_정보.getKoreanName()), - () -> assertThat(response.appliedUniversities().thirdChoiceUnivApplyInfo()) - .isEqualTo(서던덴마크대학교_지원_정보.getKoreanName()), - () -> assertThat(savedApplication.getVerifyStatus()) - .isEqualTo(VerifyStatus.APPROVED), - () -> assertThat(savedApplication.isDelete()) - .isFalse(), - () -> assertThat(savedApplication.getFirstChoiceUnivApplyInfoId()) - .isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSecondChoiceUnivApplyInfoId()) - .isEqualTo(버지니아공과대학_지원_정보.getId()), - () -> assertThat(savedApplication.getThirdChoiceUnivApplyInfoId()) - .isEqualTo(서던덴마크대학교_지원_정보.getId()) + () -> assertThat(response.totalApplyCount()).isEqualTo(APPLICATION_UPDATE_COUNT_LIMIT), + () -> assertThat(response.applyCount()).isEqualTo(savedApplication.getUpdateCount()), + () -> assertThat(response.appliedUniversities().choices()).containsExactly( + 괌대학_A_지원_정보.getKoreanName(), + 버지니아공과대학_지원_정보.getKoreanName(), + 서던덴마크대학교_지원_정보.getKoreanName() + ), + () -> assertThat(savedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), + () -> assertThat(savedApplication.isDelete()).isFalse(), + () -> assertThat(savedChoices).extracting(ApplicationChoice::getUnivApplyInfoId) + .containsExactly( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) + ); + } + + @Test + void 출신대학_최대_지망수를_초과하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(2); + SiteUser userWithHomeUniv = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, userWithHomeUniv); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, userWithHomeUniv); + UnivApplyInfoChoiceRequest choiceRequest = new UnivApplyInfoChoiceRequest( + List.of( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) ); + + // when & then + assertThatCode(() -> + applicationSubmissionService.apply( + userWithHomeUniv.getId(), + new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), choiceRequest) + ) + ) + .isInstanceOf(CustomException.class) + .hasMessage(CHOICE_COUNT_EXCEEDS_LIMIT.getMessage()); } @Test @@ -122,16 +152,12 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_GPA_SCORE_STATUS.getMessage()); } @@ -142,16 +168,12 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); } @@ -162,9 +184,7 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); @@ -173,9 +193,7 @@ void setUp() { } // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java index 94b7eb7fc..93445e899 100644 --- a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java @@ -2,7 +2,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; import static org.assertj.core.api.Assertions.assertThat; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -10,6 +9,7 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -31,7 +31,7 @@ void setUp() { @Test void 정상적인_지망_선택은_유효하다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L, 2L, 3L)); // when Set> violations = validator.validate(request); @@ -43,7 +43,7 @@ void setUp() { @Test void 첫_번째_지망만_선택하는_것은_유효하다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, null); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L)); // when Set> violations = validator.validate(request); @@ -53,23 +53,9 @@ void setUp() { } @Test - void 두_번째_지망_없이_세_번째_지망을_선택하면_예외가_발생한다() { + void 지망을_선택하지_않으면_예외가_발생한다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, 3L); - - // when - Set> violations = validator.validate(request); - - // then - assertThat(violations) - .extracting(MESSAGE) - .contains(THIRD_CHOICE_REQUIRES_SECOND.getMessage()); - } - - @Test - void 첫_번째_지망을_선택하지_않으면_예외가_발생한다() { - // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(null, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of()); // when Set> violations = validator.validate(request); @@ -84,7 +70,7 @@ void setUp() { @Test void 대학을_중복_선택하면_예외가_발생한다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 1L, 2L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L, 1L, 2L)); // when Set> violations = validator.validate(request); diff --git a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java index 38ae070e3..b584745af 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java @@ -13,12 +13,21 @@ public class HomeUniversityFixture { public HomeUniversity 인하대학교() { return homeUniversityFixtureBuilder.homeUniversity() .name("인하대학교") + .maxChoiceCount(3) + .create(); + } + + public HomeUniversity 인하대학교(int maxChoiceCount) { + return homeUniversityFixtureBuilder.homeUniversity() + .name("인하대학교_" + maxChoiceCount + "지망") + .maxChoiceCount(maxChoiceCount) .create(); } public HomeUniversity 인천대학교() { return homeUniversityFixtureBuilder.homeUniversity() .name("인천대학교") + .maxChoiceCount(3) .create(); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java index 092b2a0c2..5287123ed 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java @@ -12,6 +12,7 @@ public class HomeUniversityFixtureBuilder { private final HomeUniversityRepository homeUniversityRepository; private String name; + private int maxChoiceCount = 3; public HomeUniversityFixtureBuilder homeUniversity() { return new HomeUniversityFixtureBuilder(homeUniversityRepository); @@ -22,8 +23,13 @@ public HomeUniversityFixtureBuilder name(String name) { return this; } + public HomeUniversityFixtureBuilder maxChoiceCount(int maxChoiceCount) { + this.maxChoiceCount = maxChoiceCount; + return this; + } + public HomeUniversity create() { return homeUniversityRepository.findByName(name) - .orElseGet(() -> homeUniversityRepository.save(new HomeUniversity(null, name))); + .orElseGet(() -> homeUniversityRepository.save(new HomeUniversity(null, name, maxChoiceCount))); } }