Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 우수 스터디원 지정 및 철회 API 구현 #800

Merged
merged 10 commits into from
Oct 11, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.gdschongik.gdsc.domain.study.api;

import com.gdschongik.gdsc.domain.study.application.MentorStudyAchievementService;
import com.gdschongik.gdsc.domain.study.dto.request.OutstandingStudentRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Mentor StudyAchievement", description = "멘토 스터디 우수 스터디원 관리 API입니다.")
@RestController
@RequestMapping("/mentor/study-achievements")
@RequiredArgsConstructor
public class MentorStudyAchievementController {

private final MentorStudyAchievementService mentorStudyAchievementService;

@Operation(summary = "우수 스터디원 지정", description = "우수 스터디원으로 지정합니다.")
@PostMapping
public ResponseEntity<Void> designateOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.designateOutstandingStudent(studyId, request);
return ResponseEntity.ok().build();
}
Comment on lines +25 to +31
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 및 응답 코드 개선 제안

메서드 구조는 적절하지만, 다음과 같은 개선사항을 고려해 보시기 바랍니다:

  1. 성공 시 201 Created 응답을 반환하는 것이 더 적절할 수 있습니다.
  2. 예외 처리를 추가하여 다양한 오류 상황에 대응하는 것이 좋습니다.

다음과 같이 코드를 수정해 보세요:

 @PostMapping
 public ResponseEntity<Void> designateOutstandingStudent(
         @RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
     mentorStudyAchievementService.designateOutstandingStudent(studyId, request);
-    return ResponseEntity.ok().build();
+    return ResponseEntity.status(HttpStatus.CREATED).build();
 }

또한, 전역 예외 처리기를 구현하여 다양한 예외 상황에 대응하는 것을 고려해 보세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(summary = "우수 스터디원 지정", description = "우수 스터디원으로 지정합니다.")
@PostMapping
public ResponseEntity<Void> designateOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.designateOutstandingStudent(studyId, request);
return ResponseEntity.ok().build();
}
@Operation(summary = "우수 스터디원 지정", description = "우수 스터디원으로 지정합니다.")
@PostMapping
public ResponseEntity<Void> designateOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.designateOutstandingStudent(studyId, request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}


@Operation(summary = "우수 스터디원 철회", description = "우수 스터디원 지정을 철회합니다.")
@DeleteMapping
public ResponseEntity<Void> withdrawOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.withdrawOutstandingStudent(studyId, request);
return ResponseEntity.ok().build();
}
Comment on lines +33 to +39
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 및 응답 코드 개선 제안

이 메서드도 이전 메서드와 유사한 개선이 필요합니다:

  1. 성공적인 삭제 후 204 No Content 응답을 반환하는 것이 더 적절할 수 있습니다.
  2. 예외 처리를 추가하여 다양한 오류 상황에 대응하는 것이 좋습니다.

다음과 같이 코드를 수정해 보세요:

 @DeleteMapping
 public ResponseEntity<Void> withdrawOutstandingStudent(
         @RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
     mentorStudyAchievementService.withdrawOutstandingStudent(studyId, request);
-    return ResponseEntity.ok().build();
+    return ResponseEntity.noContent().build();
 }

또한, 전역 예외 처리기를 구현하여 다양한 예외 상황(예: 존재하지 않는 studyId)에 대응하는 것을 고려해 보세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(summary = "우수 스터디원 철회", description = "우수 스터디원 지정을 철회합니다.")
@DeleteMapping
public ResponseEntity<Void> withdrawOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.withdrawOutstandingStudent(studyId, request);
return ResponseEntity.ok().build();
}
@Operation(summary = "우수 스터디원 철회", description = "우수 스터디원 지정을 철회합니다.")
@DeleteMapping
public ResponseEntity<Void> withdrawOutstandingStudent(
@RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) {
mentorStudyAchievementService.withdrawOutstandingStudent(studyId, request);
return ResponseEntity.noContent().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.gdschongik.gdsc.domain.study.application;

import com.gdschongik.gdsc.domain.member.dao.MemberRepository;
import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.study.dao.StudyAchievementRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyRepository;
import com.gdschongik.gdsc.domain.study.domain.Study;
import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import com.gdschongik.gdsc.domain.study.domain.StudyHistoryValidator;
import com.gdschongik.gdsc.domain.study.domain.StudyValidator;
import com.gdschongik.gdsc.domain.study.dto.request.OutstandingStudentRequest;
import com.gdschongik.gdsc.global.util.MemberUtil;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class MentorStudyAchievementService {

private final MemberUtil memberUtil;
private final StudyValidator studyValidator;
private final StudyHistoryValidator studyHistoryValidator;
private final StudyRepository studyRepository;
private final StudyHistoryRepository studyHistoryRepository;
private final StudyAchievementRepository studyAchievementRepository;
private final MemberRepository memberRepository;

@Transactional
public void designateOutstandingStudent(Long studyId, OutstandingStudentRequest request) {
Member currentMember = memberUtil.getCurrentMember();
Study study = studyRepository.getById(studyId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

getById 대신 findById 사용을 권장합니다.

getById 메서드는 엔티티가 존재하지 않을 경우 EntityNotFoundException을 발생시킬 수 있습니다. 이는 런타임 예외로서 예외 처리가 누락될 수 있습니다. findById를 사용하여 Optional을 반환받고, 엔티티가 존재하지 않을 경우에 대한 예외 처리를 명시적으로 하는 것이 더 안전합니다.

제안된 수정 코드:

- Study study = studyRepository.getById(studyId);
+ Study study = studyRepository.findById(studyId)
+     .orElseThrow(() -> new EntityNotFoundException("해당 스터디를 찾을 수 없습니다. studyId: " + studyId));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Study study = studyRepository.getById(studyId);
Study study = studyRepository.findById(studyId)
.orElseThrow(() -> new EntityNotFoundException("해당 스터디를 찾을 수 없습니다. studyId: " + studyId));

boolean isAllAppliedToStudy =
studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

모든 학생들이 스터디에 적용되었는지 정확한 검증이 필요합니다

existsByStudyIdAndStudentIds 메서드는 전달된 studentIds 중 하나라도 존재하면 true를 반환할 수 있습니다. 이는 요청한 모든 학생들이 스터디에 적용되었는지를 확인하는 데 부적절할 수 있습니다. 모든 학생들이 적용되었는지 확인하려면 실제로 적용된 학생 수를 비교하는 방법이 좋습니다.

제안된 수정:

- boolean isAllAppliedToStudy =
-         studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
+ long appliedStudentCount = studyHistoryRepository.countByStudyIdAndStudentIds(studyId, request.studentIds());
+ boolean isAllAppliedToStudy = appliedStudentCount == request.studentIds().size();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
boolean isAllAppliedToStudy =
studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
long appliedStudentCount = studyHistoryRepository.countByStudyIdAndStudentIds(studyId, request.studentIds());
boolean isAllAppliedToStudy = appliedStudentCount == request.studentIds().size();


studyValidator.validateStudyMentor(currentMember, study);
studyHistoryValidator.validateAppliedToStudy(isAllAppliedToStudy);

List<Member> outstandingStudents = memberRepository.findAllById(request.studentIds());
List<StudyAchievement> studyAchievements = outstandingStudents.stream()
.map(member -> StudyAchievement.create(member, study, request.achievementType()))
.toList();
studyAchievementRepository.saveAll(studyAchievements);

log.info(
"[MentorStudyAchievementService] 우수 스터디원 지정: studyId={}, studentIds={}", studyId, request.studentIds());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복된 코드의 리팩토링을 고려하세요

designateOutstandingStudentwithdrawOutstandingStudent 메서드에서 유사한 로직이 반복되고 있습니다. 공통되는 로직을 별도의 메서드로 추출하여 코드의 중복을 줄이고 가독성을 향상시킬 수 있습니다.

예시로, 공통되는 검증 로직을 메서드로 추출할 수 있습니다:

private Study validateStudyAndMentor(Long studyId) {
    Member currentMember = memberUtil.getCurrentMember();
    Study study = studyRepository.findById(studyId)
        .orElseThrow(() -> new EntityNotFoundException("해당 스터디를 찾을 수 없습니다. studyId: " + studyId));

    studyValidator.validateStudyMentor(currentMember, study);

    return study;
}

private void validateAllStudentsApplied(Long studyId, List<Long> studentIds) {
    long appliedStudentCount = studyHistoryRepository.countByStudyIdAndStudentIds(studyId, studentIds);
    boolean isAllAppliedToStudy = appliedStudentCount == studentIds.size();
    studyHistoryValidator.validateAppliedToStudy(isAllAppliedToStudy);
}

그리고 각 메서드에서 이를 활용합니다:

public void designateOutstandingStudent(Long studyId, OutstandingStudentRequest request) {
-    Member currentMember = memberUtil.getCurrentMember();
-    Study study = studyRepository.getById(studyId);
+    Study study = validateStudyAndMentor(studyId);
     
-    boolean isAllAppliedToStudy =
-            studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
-    studyHistoryValidator.validateAppliedToStudy(isAllAppliedToStudy);
+    validateAllStudentsApplied(studyId, request.studentIds());

    // 이하 생략
}

Also applies to: 54-68


@Transactional
public void withdrawOutstandingStudent(Long studyId, OutstandingStudentRequest request) {
Member currentMember = memberUtil.getCurrentMember();
Study study = studyRepository.getById(studyId);
Sangwook02 marked this conversation as resolved.
Show resolved Hide resolved
boolean isAllAppliedToStudy =
studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

모든 학생들이 스터디에 적용되었는지 정확한 검증이 필요합니다

위와 동일하게, withdrawOutstandingStudent 메서드에서도 모든 studentIds가 스터디에 적용되었는지 정확히 검증해야 합니다.

제안된 수정:

- boolean isAllAppliedToStudy =
-         studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
+ long appliedStudentCount = studyHistoryRepository.countByStudyIdAndStudentIds(studyId, request.studentIds());
+ boolean isAllAppliedToStudy = appliedStudentCount == request.studentIds().size();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
boolean isAllAppliedToStudy =
studyHistoryRepository.existsByStudyIdAndStudentIds(studyId, request.studentIds());
long appliedStudentCount = studyHistoryRepository.countByStudyIdAndStudentIds(studyId, request.studentIds());
boolean isAllAppliedToStudy = appliedStudentCount == request.studentIds().size();


studyValidator.validateStudyMentor(currentMember, study);
studyHistoryValidator.validateAppliedToStudy(isAllAppliedToStudy);

studyAchievementRepository.deleteByStudyAndAchievementTypeAndMemberIds(
studyId, request.achievementType(), request.studentIds());

log.info(
"[MentorStudyAchievementService] 우수 스터디원 철회: studyId={}, studentIds={}", studyId, request.studentIds());
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.gdschongik.gdsc.domain.study.dao;

import com.gdschongik.gdsc.domain.study.domain.AchievementType;
import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import java.util.List;

public interface StudyAchievementCustomRepository {
List<StudyAchievement> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds);

void deleteByStudyAndAchievementTypeAndMemberIds(
Long studyId, AchievementType achievementType, List<Long> memberIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.gdschongik.gdsc.domain.study.domain.QStudyAchievement.*;

import com.gdschongik.gdsc.domain.study.domain.AchievementType;
import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
Expand All @@ -21,6 +22,18 @@ public List<StudyAchievement> findByStudyIdAndMemberIds(Long studyId, List<Long>
.fetch();
}

@Override
public void deleteByStudyAndAchievementTypeAndMemberIds(
Long studyId, AchievementType achievementType, List<Long> memberIds) {
queryFactory
.delete(studyAchievement)
.where(
eqStudyId(studyId),
studyAchievement.achievementType.eq(achievementType),
studyAchievement.student.id.in(memberIds))
.execute();
}

private BooleanExpression eqStudyId(Long studyId) {
return studyId != null ? studyAchievement.study.id.eq(studyId) : null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gdschongik.gdsc.domain.study.dao;

import java.util.List;

public interface StudyHistoryCustomRepository {

boolean existsByStudyIdAndStudentIds(Long studyId, List<Long> studentIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.gdschongik.gdsc.domain.study.dao;

import static com.gdschongik.gdsc.domain.study.domain.QStudyHistory.*;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class StudyHistoryCustomRepositoryImpl implements StudyHistoryCustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public boolean existsByStudyIdAndStudentIds(Long studyId, List<Long> studentIds) {
Copy link
Member

Choose a reason for hiding this comment

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

인자로 넘긴 student id 리스트와 count가 일치하는지 비교하는 메서드로 보입니다.
일반적으로 exists 쿼리 메서드가 존재 여부를 확인하는 쿼리를 실행할 것으로 예상되는데,
해당 메서드는 실질적으로 count 쿼리를 발생시키고 있는데 exists 네이밍을 사용하고 있어 혼동이 있을 것으로 보입니다.
레포지터리에서는 count를 받아오고, validator 내부에서 이 count와 비교하여 예외를 발생시키는 로직을 사용하면 어떨까요?

Long count = queryFactory
.select(studyHistory.count())
.from(studyHistory)
.where(eqStudyId(studyId), studyHistory.student.id.in(studentIds))
.fetchOne();
return count != null && count == studentIds.size();
}

private BooleanExpression eqStudyId(Long studyId) {
return studyHistory.study.id.eq(studyId);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

전반적으로 우수한 구현이지만, 몇 가지 고려사항이 있습니다.

이 구현은 전체적으로 잘 작성되었습니다. 그러나 다음 사항들을 고려해 보시기 바랍니다:

  1. 대량의 studentIds를 처리할 때의 성능을 고려해 보세요. 필요하다면 배치 처리를 구현할 수 있습니다.
  2. 예외 처리: 잘못된 입력(예: null 값)에 대한 처리를 추가하는 것이 좋을 수 있습니다.
  3. 로깅: 디버깅을 위해 주요 지점에 로그를 추가하는 것을 고려해 보세요.
  4. 테스트: 이 구현에 대한 단위 테스트를 작성하여 다양한 시나리오를 검증하는 것이 좋습니다.

이러한 개선사항들을 구현하는 데 도움이 필요하시면 말씀해 주세요.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudyHistoryRepository extends JpaRepository<StudyHistory, Long> {
public interface StudyHistoryRepository extends JpaRepository<StudyHistory, Long>, StudyHistoryCustomRepository {

List<StudyHistory> findAllByStudent(Member member);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ public void validateUpdateRepository(
throw new CustomException(STUDY_HISTORY_REPOSITORY_NOT_UPDATABLE_OWNER_MISMATCH);
}
}

public void validateAppliedToStudy(boolean isAllAppliedToStudy) {
if (!isAllAppliedToStudy) {
throw new CustomException(STUDY_HISTORY_NOT_APPLIED_STUDENT_EXISTS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gdschongik.gdsc.domain.study.dto.request;

import com.gdschongik.gdsc.domain.study.domain.AchievementType;
import java.util.List;

public record OutstandingStudentRequest(List<Long> studentIds, AchievementType achievementType) {}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public enum ErrorCode {
STUDY_HISTORY_REPOSITORY_NOT_UPDATABLE_ASSIGNMENT_ALREADY_SUBMITTED(
HttpStatus.CONFLICT, "이미 제출한 과제가 있으므로 레포지토리를 수정할 수 없습니다."),
STUDY_HISTORY_REPOSITORY_NOT_UPDATABLE_OWNER_MISMATCH(HttpStatus.CONFLICT, "레포지토리 소유자가 현재 멤버와 다릅니다."),
STUDY_HISTORY_NOT_APPLIED_STUDENT_EXISTS(HttpStatus.CONFLICT, "해당 스터디에 신청하지 않은 멤버가 있습니다."),

// StudyAnnouncement
STUDY_ANNOUNCEMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 스터디 공지입니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.gdschongik.gdsc.domain.study.application;

import static com.gdschongik.gdsc.domain.study.domain.AchievementType.*;
import static org.assertj.core.api.Assertions.*;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.recruitment.domain.vo.Period;
import com.gdschongik.gdsc.domain.study.domain.Study;
import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import com.gdschongik.gdsc.domain.study.dto.request.OutstandingStudentRequest;
import com.gdschongik.gdsc.helper.IntegrationTest;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

public class MentorStudyAchievementServiceTest extends IntegrationTest {

@Autowired
private MentorStudyAchievementService mentorStudyAchievementService;

@Nested
class 우수_스터디원_지정시 {

@Test
void 성공한다() {
// given
LocalDateTime now = LocalDateTime.now();
Member mentor = createMentor();
Study study = createStudy(
mentor,
Period.createPeriod(now.plusDays(5), now.plusDays(10)),
Period.createPeriod(now.minusDays(5), now));

Member student = createRegularMember();
createStudyHistory(student, study);

logoutAndReloginAs(mentor.getId(), mentor.getRole());
OutstandingStudentRequest request =
new OutstandingStudentRequest(List.of(student.getId()), FIRST_ROUND_OUTSTANDING_STUDENT);

// when
mentorStudyAchievementService.designateOutstandingStudent(study.getId(), request);

// then
List<StudyAchievement> studyAchievements =
studyAchievementRepository.findByStudyIdAndMemberIds(study.getId(), request.studentIds());
assertThat(studyAchievements).hasSize(request.studentIds().size());
}
}

@Nested
class 우수_스터디원_철회시 {

@Test
void 성공한다() {
// given
Member student = createRegularMember();
LocalDateTime now = LocalDateTime.now();
Member mentor = createMentor();
Study study = createStudy(
mentor,
Period.createPeriod(now.plusDays(5), now.plusDays(10)),
Period.createPeriod(now.minusDays(5), now));
createStudyHistory(student, study);
createStudyAchievement(student, study, FIRST_ROUND_OUTSTANDING_STUDENT);

logoutAndReloginAs(mentor.getId(), mentor.getRole());
OutstandingStudentRequest request =
new OutstandingStudentRequest(List.of(student.getId()), FIRST_ROUND_OUTSTANDING_STUDENT);

// when
mentorStudyAchievementService.withdrawOutstandingStudent(study.getId(), request);

// then
List<StudyAchievement> studyAchievements =
studyAchievementRepository.findByStudyIdAndMemberIds(study.getId(), request.studentIds());
assertThat(studyAchievements).isEmpty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,16 @@ class 레포지토리_입력시 {
.hasMessage(STUDY_HISTORY_REPOSITORY_NOT_UPDATABLE_OWNER_MISMATCH.getMessage());
}
}

@Nested
class 스터디_수강신청_여부_확인시 {

@Test
void 해당_스터디를_신청하지_않은_멤버가_있다면_실패한다() {
// when & then
assertThatThrownBy(() -> studyHistoryValidator.validateAppliedToStudy(false))
.isInstanceOf(CustomException.class)
.hasMessage(STUDY_HISTORY_NOT_APPLIED_STUDENT_EXISTS.getMessage());
}
}
}
21 changes: 21 additions & 0 deletions src/test/java/com/gdschongik/gdsc/helper/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@
import com.gdschongik.gdsc.domain.recruitment.domain.RecruitmentRound;
import com.gdschongik.gdsc.domain.recruitment.domain.RoundType;
import com.gdschongik.gdsc.domain.recruitment.domain.vo.Period;
import com.gdschongik.gdsc.domain.study.dao.StudyAchievementRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyDetailRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyRepository;
import com.gdschongik.gdsc.domain.study.domain.AchievementType;
import com.gdschongik.gdsc.domain.study.domain.Study;
import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import com.gdschongik.gdsc.domain.study.domain.StudyDetail;
import com.gdschongik.gdsc.domain.study.domain.StudyHistory;
import com.gdschongik.gdsc.global.security.PrincipalDetails;
import com.gdschongik.gdsc.infra.feign.payment.client.PaymentClient;
import com.gdschongik.gdsc.infra.github.client.GithubClient;
Expand Down Expand Up @@ -81,6 +86,12 @@ public abstract class IntegrationTest {
@Autowired
protected StudyDetailRepository studyDetailRepository;

@Autowired
protected StudyHistoryRepository studyHistoryRepository;

@Autowired
protected StudyAchievementRepository studyAchievementRepository;

@MockBean
protected OnboardingRecruitmentService onboardingRecruitmentService;

Expand Down Expand Up @@ -262,6 +273,16 @@ protected StudyDetail createNewStudyDetail(Long week, Study study, LocalDateTime
return studyDetailRepository.save(studyDetail);
}

protected StudyHistory createStudyHistory(Member member, Study study) {
StudyHistory studyHistory = StudyHistory.create(member, study);
return studyHistoryRepository.save(studyHistory);
}

protected StudyAchievement createStudyAchievement(Member member, Study study, AchievementType achievementType) {
StudyAchievement studyAchievement = StudyAchievement.create(member, study, achievementType);
return studyAchievementRepository.save(studyAchievement);
}

protected StudyDetail publishAssignment(StudyDetail studyDetail) {
studyDetail.publishAssignment(ASSIGNMENT_TITLE, studyDetail.getPeriod().getEndDate(), DESCRIPTION_LINK);
return studyDetailRepository.save(studyDetail);
Expand Down