diff --git a/.github/workflows/ff-merge.yml b/.github/workflows/ff-merge.yml new file mode 100644 index 000000000..7b7555b92 --- /dev/null +++ b/.github/workflows/ff-merge.yml @@ -0,0 +1,133 @@ +name: FF-Only Merge to Master + +on: + pull_request: + types: [labeled] + check_suite: + types: [completed] + +jobs: + ff-merge: + if: | + (github.event_name == 'pull_request' && github.event.label.name == 'ready-to-merge') || + (github.event_name == 'check_suite' && github.event.check_suite.conclusion == 'success') + runs-on: ubuntu-latest + + steps: + - name: PR 조회 및 조건 검증 + id: validate + uses: actions/github-script@v7 + with: + script: | + let prNumber, headSha; + + if (context.eventName === 'pull_request') { + const pr = context.payload.pull_request; + if (pr.base.ref !== 'master' || pr.head.ref !== 'develop' || pr.state !== 'open') { + core.setOutput('ready', 'false'); + return; + } + + // 레이블을 부착한 주체의 저장소 권한 확인 + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: context.payload.sender.login, + }); + + if (!['admin', 'write'].includes(perm.permission)) { + core.setOutput('ready', 'false'); + return; + } + + prNumber = pr.number; + headSha = pr.head.sha; + } else { + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + base: 'master', + head: `${context.repo.owner}:develop`, + }); + + if (prs.length === 0) { + core.setOutput('ready', 'false'); + return; + } + + prNumber = prs[0].number; + headSha = prs[0].head.sha; + + if (context.payload.check_suite.head_sha !== headSha) { + core.setOutput('ready', 'false'); + return; + } + } + + // ready-to-merge 레이블 확인 + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + + const hasLabel = pr.labels.some(l => l.name === 'ready-to-merge'); + if (!hasLabel) { + core.setOutput('ready', 'false'); + return; + } + + // CI 상태 확인 + const { data: { check_runs } } = await github.rest.checks.listForRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: headSha, + per_page: 100, + }); + + const ciRuns = check_runs.filter(r => r.name !== context.job); + const allPassed = ciRuns.length > 0 && ciRuns.every(r => + r.status === 'completed' && + ['success', 'skipped', 'neutral'].includes(r.conclusion) + ); + + if (!allPassed) { + core.setOutput('ready', 'false'); + return; + } + + core.setOutput('ready', 'true'); + core.setOutput('head_sha', headSha); + + - name: Checkout + if: steps.validate.outputs.ready == 'true' + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + fetch-depth: 0 + + - name: Git 설정 + if: steps.validate.outputs.ready == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: develop 변경 여부 검증 + if: steps.validate.outputs.ready == 'true' + run: | + APPROVED_SHA="${{ steps.validate.outputs.head_sha }}" + CURRENT_SHA=$(git rev-parse origin/develop) + if [ "$APPROVED_SHA" != "$CURRENT_SHA" ]; then + echo "레이블 부착 이후 develop이 변경되었습니다." + echo " 레이블 시점 SHA: $APPROVED_SHA" + echo " 현재 SHA: $CURRENT_SHA" + exit 1 + fi + + - name: FF-Only merge develop → master + if: steps.validate.outputs.ready == 'true' + run: | + git checkout master + git merge --ff-only ${{ steps.validate.outputs.head_sha }} + git push origin master diff --git a/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java b/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java new file mode 100644 index 000000000..b0de5627f --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java @@ -0,0 +1,61 @@ +package com.example.solidconnection.admin.university.controller; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.admin.university.service.AdminHomeUniversityService; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/admin/home-universities") +@RestController +public class AdminHomeUniversityController { + + private final AdminHomeUniversityService adminHomeUniversityService; + + @GetMapping + public ResponseEntity> getHomeUniversities() { + return ResponseEntity.ok(adminHomeUniversityService.getAllHomeUniversities()); + } + + @GetMapping("/{home-university-id}") + public ResponseEntity getHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId + ) { + return ResponseEntity.ok(adminHomeUniversityService.getHomeUniversity(homeUniversityId)); + } + + @PostMapping + public ResponseEntity createHomeUniversity( + @Valid @RequestBody AdminHomeUniversityCreateRequest request + ) { + return ResponseEntity.ok(adminHomeUniversityService.createHomeUniversity(request)); + } + + @PutMapping("/{home-university-id}") + public ResponseEntity updateHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId, + @Valid @RequestBody AdminHomeUniversityUpdateRequest request + ) { + return ResponseEntity.ok(adminHomeUniversityService.updateHomeUniversity(homeUniversityId, request)); + } + + @DeleteMapping("/{home-university-id}") + public ResponseEntity deleteHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId + ) { + adminHomeUniversityService.deleteHomeUniversity(homeUniversityId); + return ResponseEntity.ok().build(); + } +} 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 new file mode 100644 index 000000000..14df833e5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.admin.university.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record AdminHomeUniversityCreateRequest( + @NotBlank(message = "협정 대학명은 필수입니다") + @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") + String name +) { + +} 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 new file mode 100644 index 000000000..719185202 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java @@ -0,0 +1,16 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.HomeUniversity; + +public record AdminHomeUniversityResponse( + long id, + String name +) { + + public static AdminHomeUniversityResponse from(HomeUniversity homeUniversity) { + return new AdminHomeUniversityResponse( + homeUniversity.getId(), + homeUniversity.getName() + ); + } +} 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 new file mode 100644 index 000000000..e22473099 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.admin.university.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record AdminHomeUniversityUpdateRequest( + @NotBlank(message = "협정 대학명은 필수입니다") + @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") + String name +) { + +} 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 new file mode 100644 index 000000000..bcf1a8edf --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java @@ -0,0 +1,104 @@ +package com.example.solidconnection.admin.university.service; + +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS; +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES; +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_NOT_FOUND; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.cache.annotation.DefaultCacheOut; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.repository.HomeUniversityRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdminHomeUniversityService { + + private final HomeUniversityRepository homeUniversityRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional(readOnly = true) + public List getAllHomeUniversities() { + return homeUniversityRepository.findAll().stream() + .map(AdminHomeUniversityResponse::from) + .toList(); + } + + @Transactional(readOnly = true) + public AdminHomeUniversityResponse getHomeUniversity(Long id) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + return AdminHomeUniversityResponse.from(homeUniversity); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminHomeUniversityResponse createHomeUniversity(AdminHomeUniversityCreateRequest request) { + validateNameNotExists(request.name()); + HomeUniversity homeUniversity = new HomeUniversity(null, request.name()); + return AdminHomeUniversityResponse.from(homeUniversityRepository.save(homeUniversity)); + } + + private void validateNameNotExists(String name) { + homeUniversityRepository.findByName(name) + .ifPresent(existing -> { + throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); + }); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminHomeUniversityResponse updateHomeUniversity(Long id, AdminHomeUniversityUpdateRequest request) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + validateNameNotDuplicated(request.name(), id); + homeUniversity.update(request.name()); + return AdminHomeUniversityResponse.from(homeUniversity); + } + + private void validateNameNotDuplicated(String name, Long excludeId) { + homeUniversityRepository.findByName(name) + .ifPresent(existing -> { + if (!existing.getId().equals(excludeId)) { + throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); + } + }); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public void deleteHomeUniversity(Long id) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + validateNoReferences(id); + homeUniversityRepository.delete(homeUniversity); + } + + private void validateNoReferences(Long id) { + if (univApplyInfoRepository.existsByHomeUniversityId(id) + || siteUserRepository.existsByHomeUniversityId(id)) { + throw new CustomException(HOME_UNIVERSITY_HAS_REFERENCES); + } + } +} 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 78b653da8..8dc4ea70e 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -45,6 +45,9 @@ public enum ErrorCode { COUNTRY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 국가입니다."), HOST_UNIVERSITY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 파견 대학입니다."), HOST_UNIVERSITY_HAS_REFERENCES(HttpStatus.CONFLICT.value(), "해당 파견 대학을 참조하는 대학 지원 정보가 존재합니다."), + HOME_UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "협정 대학교를 찾을 수 없습니다."), + HOME_UNIVERSITY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 협정 대학입니다."), + HOME_UNIVERSITY_HAS_REFERENCES(HttpStatus.CONFLICT.value(), "해당 협정 대학을 참조하는 데이터가 존재합니다."), COUNTRY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "국가를 찾을 수 없습니다."), COUNTRY_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 국가를 찾을 수 없습니다."), GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index b151ade94..3d02c77e5 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -25,6 +25,8 @@ public interface SiteUserRepository extends JpaRepository, SiteU List findAllByIdIn(List ids); + boolean existsByHomeUniversityId(Long homeUniversityId); + @Modifying @Query("UPDATE SiteUser u SET u.userStatus = :status WHERE u.id IN :userIds") void bulkUpdateUserStatus(@Param("userIds") List userIds, @Param("status") UserStatus status); 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 f39378e97..506491c0f 100644 --- a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java @@ -24,4 +24,8 @@ public class HomeUniversity extends BaseEntity { @Column(name = "name", nullable = false, unique = true, length = 100) private String name; + + public void update(String name) { + this.name = name; + } } diff --git a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index 5ebfebcbb..1b245d410 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -15,12 +15,15 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.HashSet; +import java.util.Map; import java.util.Set; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Getter @EqualsAndHashCode(of = "id") @@ -83,6 +86,10 @@ public class UnivApplyInfo extends BaseEntity { @Column(name = "details", length = 1000) private String details; + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "extra_info", columnDefinition = "JSON") + private Map extraInfo; + @OneToMany(mappedBy = "univApplyInfo", cascade = CascadeType.ALL, orphanRemoval = true) private Set languageRequirements = new HashSet<>(); @@ -92,4 +99,8 @@ public class UnivApplyInfo extends BaseEntity { public void addLanguageRequirements(LanguageRequirement languageRequirements) { this.languageRequirements.add(languageRequirements); } + + public void updateExtraInfo(Map extraInfo) { + this.extraInfo = extraInfo; + } } diff --git a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java index 0b016750c..4c2b6578e 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java @@ -69,6 +69,8 @@ default UnivApplyInfo getUnivApplyInfoById(Long id) { boolean existsByUniversityId(Long universityId); + boolean existsByHomeUniversityId(Long homeUniversityId); + @Query(""" SELECT uai.id FROM UnivApplyInfo uai diff --git a/src/main/resources/db/migration/V49__add_extra_info_to_university_info_for_apply.sql b/src/main/resources/db/migration/V49__add_extra_info_to_university_info_for_apply.sql new file mode 100644 index 000000000..6cbf2e25f --- /dev/null +++ b/src/main/resources/db/migration/V49__add_extra_info_to_university_info_for_apply.sql @@ -0,0 +1,2 @@ +ALTER TABLE university_info_for_apply + ADD COLUMN extra_info JSON NULL; 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 new file mode 100644 index 000000000..425475b01 --- /dev/null +++ b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java @@ -0,0 +1,237 @@ +package com.example.solidconnection.admin.university.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.fixture.HomeUniversityFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import com.example.solidconnection.university.repository.HomeUniversityRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@TestContainerSpringBootTest +@DisplayName("협정 대학 관리자 서비스 테스트") +class AdminHomeUniversityServiceTest { + + @Autowired + private AdminHomeUniversityService adminHomeUniversityService; + + @Autowired + private HomeUniversityRepository homeUniversityRepository; + + @Autowired + private HomeUniversityFixture homeUniversityFixture; + + @Autowired + private UnivApplyInfoFixture univApplyInfoFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Nested + class 전체_협정대학_조회 { + + @Test + void 협정대학이_없으면_빈_목록을_반환한다() { + // when + List responses = adminHomeUniversityService.getAllHomeUniversities(); + + // then + assertThat(responses).isEmpty(); + } + + @Test + void 저장된_모든_협정대학을_반환한다() { + // given + HomeUniversity homeUniversity1 = homeUniversityFixture.인하대학교(); + HomeUniversity homeUniversity2 = homeUniversityFixture.인천대학교(); + + // when + List responses = adminHomeUniversityService.getAllHomeUniversities(); + + // then + assertThat(responses) + .hasSize(2) + .extracting(AdminHomeUniversityResponse::name) + .containsExactlyInAnyOrder(homeUniversity1.getName(), homeUniversity2.getName()); + } + } + + @Nested + class 협정대학_단건_조회 { + + @Test + void 존재하는_협정대학을_조회하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.getHomeUniversity(homeUniversity.getId()); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(homeUniversity.getId()), + () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()) + ); + } + + @Test + void 존재하지_않는_협정대학을_조회하면_예외가_발생한다() { + // when & then + assertThatCode(() -> adminHomeUniversityService.getHomeUniversity(999L)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + } + + @Nested + class 협정대학_생성 { + + @Test + void 유효한_요청으로_협정대학을_생성하면_성공한다() { + // given + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.createHomeUniversity(request); + + // then + HomeUniversity saved = homeUniversityRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(response.name()).isEqualTo("인하대학교"), + () -> assertThat(saved.getName()).isEqualTo("인하대학교") + ); + } + + @Test + void 이미_존재하는_이름으로_생성하면_예외가_발생한다() { + // given + homeUniversityFixture.인하대학교(); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.createHomeUniversity(request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS.getMessage()); + } + } + + @Nested + class 협정대학_수정 { + + @Test + void 유효한_요청으로_협정대학을_수정하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); + + // then + HomeUniversity updated = homeUniversityRepository.findById(homeUniversity.getId()).orElseThrow(); + assertAll( + () -> assertThat(response.name()).isEqualTo("연세대학교"), + () -> assertThat(updated.getName()).isEqualTo("연세대학교") + ); + } + + @Test + void 존재하지_않는_협정대학을_수정하면_예외가_발생한다() { + // given + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(999L, request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + + @Test + void 다른_협정대학의_이름으로_수정하면_예외가_발생한다() { + // given + homeUniversityFixture.인하대학교(); + HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교")); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(other.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS.getMessage()); + } + + @Test + void 같은_이름으로_수정하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); + + // then + assertThat(response.name()).isEqualTo("인하대학교"); + } + } + + @Nested + class 협정대학_삭제 { + + @Test + void 참조가_없는_협정대학을_삭제하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + + // when + adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId()); + + // then + assertThat(homeUniversityRepository.findById(homeUniversity.getId())).isEmpty(); + } + + @Test + void 존재하지_않는_협정대학을_삭제하면_예외가_발생한다() { + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(999L)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + + @Test + void UnivApplyInfo가_참조하는_협정대학을_삭제하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + univApplyInfoFixture.괌대학_A_지원_정보(1L); + + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES.getMessage()); + } + + @Test + void SiteUser가_참조하는_협정대학을_삭제하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + SiteUser siteUser = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); + + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES.getMessage()); + } + } +} 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 97391a06d..38ae070e3 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java @@ -15,4 +15,10 @@ public class HomeUniversityFixture { .name("인하대학교") .create(); } + + public HomeUniversity 인천대학교() { + return homeUniversityFixtureBuilder.homeUniversity() + .name("인천대학교") + .create(); + } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java index 2320f2544..7f57a0038 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java @@ -52,7 +52,7 @@ public UnivApplyInfo create() { "1", "detailsForLanguage", "gpaRequirement", "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), university + null, new HashSet<>(), university ); return univApplyInfoRepository.save(univApplyInfo); }