diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/api/StudentStudyHistoryController.java b/src/main/java/com/gdschongik/gdsc/domain/study/api/StudentStudyHistoryController.java index 0894221fa..48faedcdf 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/api/StudentStudyHistoryController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/api/StudentStudyHistoryController.java @@ -35,4 +35,11 @@ public ResponseEntity> getAllAssignmentHistories List response = studentStudyHistoryService.getAllAssignmentHistories(studyId); return ResponseEntity.ok(response); } + + @Operation(summary = "과제 제출하기", description = "과제를 제출합니다. 제출된 과제는 채점되어 제출내역에 반영됩니다.") + @PostMapping("/submit") + public ResponseEntity submitAssignment(@RequestParam(name = "studyDetailId") Long studyDetailId) { + studentStudyHistoryService.submitAssignment(studyDetailId); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryService.java b/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryService.java index 3712640ac..7d4455343 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryService.java @@ -8,6 +8,8 @@ import com.gdschongik.gdsc.domain.study.dao.StudyDetailRepository; import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory; +import com.gdschongik.gdsc.domain.study.domain.AssignmentHistoryGrader; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetcher; import com.gdschongik.gdsc.domain.study.domain.Study; import com.gdschongik.gdsc.domain.study.domain.StudyAssignmentHistoryValidator; import com.gdschongik.gdsc.domain.study.domain.StudyDetail; @@ -21,6 +23,7 @@ import java.io.IOException; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.kohsuke.github.GHRepository; @@ -39,6 +42,7 @@ public class StudentStudyHistoryService { private final AssignmentHistoryRepository assignmentHistoryRepository; private final StudyHistoryValidator studyHistoryValidator; private final StudyAssignmentHistoryValidator studyAssignmentHistoryValidator; + private final AssignmentHistoryGrader assignmentHistoryGrader; @Transactional public void updateRepository(Long studyHistoryId, RepositoryUpdateRequest request) throws IOException { @@ -52,6 +56,7 @@ public void updateRepository(Long studyHistoryId, RepositoryUpdateRequest reques assignmentHistoryRepository.existsSubmittedAssignmentByMemberAndStudy(currentMember, study); String ownerRepo = getOwnerRepo(request.repositoryLink()); GHRepository repository = githubClient.getRepository(ownerRepo); + // TODO: GHRepository 등을 wrapper로 감싸서 테스트 가능하도록 변경 studyHistoryValidator.validateUpdateRepository( isAnyAssignmentSubmitted, String.valueOf(repository.getOwner().getId()), currentMember.getOauthId()); @@ -85,16 +90,27 @@ public void submitAssignment(Long studyDetailId) { StudyDetail studyDetail = studyDetailRepository .findById(studyDetailId) .orElseThrow(() -> new CustomException(STUDY_DETAIL_NOT_FOUND)); - boolean isAppliedToStudy = studyHistoryRepository.existsByMenteeAndStudy(currentMember, studyDetail.getStudy()); + Optional studyHistory = + studyHistoryRepository.findByMenteeAndStudy(currentMember, studyDetail.getStudy()); LocalDateTime now = LocalDateTime.now(); AssignmentHistory assignmentHistory = findOrCreate(currentMember, studyDetail); - studyAssignmentHistoryValidator.validateSubmitAvailable(isAppliedToStudy, now, studyDetail); + studyAssignmentHistoryValidator.validateSubmitAvailable(studyHistory.isPresent(), now, studyDetail); - // TODO: 과제 채점 및 과제이력 업데이트 로직 추가 + AssignmentSubmissionFetcher fetcher = githubClient.getLatestAssignmentSubmissionFetcher( + studyHistory.get().getRepositoryLink(), Math.toIntExact(studyDetail.getWeek())); + + assignmentHistoryGrader.judge(fetcher, assignmentHistory); assignmentHistoryRepository.save(assignmentHistory); + + log.info( + "[StudyHistoryService] 과제 제출: studyDetailId={}, menteeId={}, submissionStatus={}, submissionFailureType={}", + studyDetailId, + currentMember.getId(), + assignmentHistory.getSubmissionStatus(), + assignmentHistory.getSubmissionFailureType()); } private AssignmentHistory findOrCreate(Member currentMember, StudyDetail studyDetail) { diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistory.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistory.java index 0316c6652..bc34ae1fd 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistory.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistory.java @@ -46,7 +46,7 @@ public class AssignmentHistory extends BaseEntity { private String commitHash; - private Long contentLength; + private Integer contentLength; private LocalDateTime committedAt; @@ -85,7 +85,7 @@ public boolean isSubmitted() { // 데이터 변경 로직 - public void success(String submissionLink, String commitHash, Long contentLength, LocalDateTime committedAt) { + public void success(String submissionLink, String commitHash, Integer contentLength, LocalDateTime committedAt) { this.submissionLink = submissionLink; this.commitHash = commitHash; this.contentLength = contentLength; diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGrader.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGrader.java new file mode 100644 index 000000000..78ab2c592 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGrader.java @@ -0,0 +1,52 @@ +package com.gdschongik.gdsc.domain.study.domain; + +import static com.gdschongik.gdsc.domain.study.domain.SubmissionFailureType.*; +import static com.gdschongik.gdsc.global.exception.ErrorCode.*; + +import com.gdschongik.gdsc.global.annotation.DomainService; +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.exception.ErrorCode; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@DomainService +public class AssignmentHistoryGrader { + + private static final int MINIMUM_ASSIGNMENT_CONTENT_LENGTH = 300; + + public void judge(AssignmentSubmissionFetcher assignmentSubmissionFetcher, AssignmentHistory assignmentHistory) { + try { + AssignmentSubmission assignmentSubmission = assignmentSubmissionFetcher.fetch(); + judgeAssignmentSubmission(assignmentSubmission, assignmentHistory); + } catch (CustomException e) { + SubmissionFailureType failureType = translateException(e); + assignmentHistory.fail(failureType); + } + } + + private void judgeAssignmentSubmission( + AssignmentSubmission assignmentSubmission, AssignmentHistory assignmentHistory) { + if (assignmentSubmission.contentLength() < MINIMUM_ASSIGNMENT_CONTENT_LENGTH) { + assignmentHistory.fail(WORD_COUNT_INSUFFICIENT); + return; + } + + assignmentHistory.success( + assignmentSubmission.url(), + assignmentSubmission.commitHash(), + assignmentSubmission.contentLength(), + assignmentSubmission.committedAt()); + } + + private SubmissionFailureType translateException(CustomException e) { + ErrorCode errorCode = e.getErrorCode(); + + if (errorCode == GITHUB_CONTENT_NOT_FOUND) { + return LOCATION_UNIDENTIFIABLE; + } + + log.warn("[AssignmentHistoryGrader] 과제 제출정보 조회 중 알 수 없는 오류 발생: {}", e.getMessage()); + + return UNKNOWN; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmission.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmission.java new file mode 100644 index 000000000..307db0cbe --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmission.java @@ -0,0 +1,5 @@ +package com.gdschongik.gdsc.domain.study.domain; + +import java.time.LocalDateTime; + +public record AssignmentSubmission(String url, String commitHash, Integer contentLength, LocalDateTime committedAt) {} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetchExecutor.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetchExecutor.java new file mode 100644 index 000000000..32eab4e9c --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetchExecutor.java @@ -0,0 +1,8 @@ +package com.gdschongik.gdsc.domain.study.domain; + +import com.gdschongik.gdsc.global.exception.CustomException; + +@FunctionalInterface +public interface AssignmentSubmissionFetchExecutor { + AssignmentSubmission execute(String repo, int week) throws CustomException; +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetcher.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetcher.java new file mode 100644 index 000000000..3f19e5d12 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/AssignmentSubmissionFetcher.java @@ -0,0 +1,9 @@ +package com.gdschongik.gdsc.domain.study.domain; + +import com.gdschongik.gdsc.global.exception.CustomException; + +public record AssignmentSubmissionFetcher(String repo, int week, AssignmentSubmissionFetchExecutor fetchExecutor) { + public AssignmentSubmission fetch() throws CustomException { + return fetchExecutor.execute(repo, week); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyDetail.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyDetail.java index f8aa0244f..020ee5dea 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyDetail.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyDetail.java @@ -32,7 +32,7 @@ public class StudyDetail extends BaseEntity { private Study study; @Comment("현 주차수") - private Long week; + private Long week; // TODO: Integer로 변경 private String attendanceNumber; diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/SubmissionFailureType.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/SubmissionFailureType.java index bd875f25f..74e38fb92 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/SubmissionFailureType.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/SubmissionFailureType.java @@ -9,7 +9,9 @@ public enum SubmissionFailureType { NONE("실패 없음"), // 제출상태 성공 시 사용 NOT_SUBMITTED("미제출"), // 기본값 WORD_COUNT_INSUFFICIENT("글자수 부족"), - LOCATION_UNIDENTIFIABLE("위치 확인불가"); + LOCATION_UNIDENTIFIABLE("위치 확인불가"), + UNKNOWN("알 수 없음"), + ; private final String value; } diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java index 69777c0f7..7982912ab 100644 --- a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java @@ -167,7 +167,6 @@ public enum ErrorCode { GITHUB_CONTENT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 파일입니다."), GITHUB_FILE_READ_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "깃허브 파일 읽기에 실패했습니다."), GITHUB_COMMIT_DATE_FETCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "깃허브 커밋 날짜 조회에 실패했습니다."), - GITHUB_ASSIGNMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 과제 파일입니다."), ; private final HttpStatus status; private final String message; diff --git a/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java b/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java index 34dd73f9c..869a2a6ec 100644 --- a/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java +++ b/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java @@ -3,13 +3,14 @@ import static com.gdschongik.gdsc.global.common.constant.GithubConstant.*; import static com.gdschongik.gdsc.global.exception.ErrorCode.*; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmission; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetchExecutor; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetcher; import com.gdschongik.gdsc.global.exception.CustomException; -import com.gdschongik.gdsc.infra.github.dto.response.GithubAssignmentSubmissionResponse; import java.io.IOException; import java.io.InputStream; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Date; import lombok.RequiredArgsConstructor; import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHContent; @@ -31,7 +32,17 @@ public GHRepository getRepository(String ownerRepo) { } } - public GithubAssignmentSubmissionResponse getLatestAssignmentSubmission(String repo, int week) { + /** + * 직접 요청을 수행하는 대신, fetcher를 통해 요청을 수행합니다. + * 요청 수행 시 발생하는 예외의 경우 과제 채점에 사용되므로, 실제 요청은 채점 로직 내부에서 수행되어야 합니다. + * 따라서 지연 평가가 가능하도록 {@link AssignmentSubmissionFetchExecutor}를 인자로 받습니다. + * 또한, 인자로 전달된 repo와 week가 closure로 캡쳐되지 않도록 fetcher 내부에 컨텍스트로 저장합니다. + */ + public AssignmentSubmissionFetcher getLatestAssignmentSubmissionFetcher(String repo, int week) { + return new AssignmentSubmissionFetcher(repo, week, this::getLatestAssignmentSubmission); + } + + private AssignmentSubmission getLatestAssignmentSubmission(String repo, int week) { GHRepository ghRepository = getRepository(repo); String assignmentPath = GITHUB_ASSIGNMENT_PATH.formatted(week); @@ -47,12 +58,10 @@ public GithubAssignmentSubmissionResponse getLatestAssignmentSubmission(String r .iterator() .next(); - LocalDateTime committedAt = getCommitDate(ghLatestCommit) - .toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDateTime(); + LocalDateTime committedAt = getCommitDate(ghLatestCommit); - return new GithubAssignmentSubmissionResponse(ghLatestCommit.getSHA1(), content.length(), committedAt); + return new AssignmentSubmission( + ghContent.getHtmlUrl(), ghLatestCommit.getSHA1(), content.length(), committedAt); } private GHContent getFileContent(GHRepository ghRepository, String filePath) { @@ -71,9 +80,13 @@ private String readFileContent(GHContent ghContent) { } } - private Date getCommitDate(GHCommit ghLatestCommit) { + private LocalDateTime getCommitDate(GHCommit ghLatestCommit) { try { - return ghLatestCommit.getCommitDate(); + return ghLatestCommit + .getCommitDate() + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); } catch (IOException e) { throw new CustomException(GITHUB_COMMIT_DATE_FETCH_FAILED); } diff --git a/src/main/java/com/gdschongik/gdsc/infra/github/dto/response/GithubAssignmentSubmissionResponse.java b/src/main/java/com/gdschongik/gdsc/infra/github/dto/response/GithubAssignmentSubmissionResponse.java deleted file mode 100644 index da64c9994..000000000 --- a/src/main/java/com/gdschongik/gdsc/infra/github/dto/response/GithubAssignmentSubmissionResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.gdschongik.gdsc.infra.github.dto.response; - -import java.time.LocalDateTime; - -public record GithubAssignmentSubmissionResponse(String commitHash, Integer size, LocalDateTime committedAt) {} diff --git a/src/test/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryServiceTest.java b/src/test/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryServiceTest.java new file mode 100644 index 000000000..9aa0ad9ca --- /dev/null +++ b/src/test/java/com/gdschongik/gdsc/domain/study/application/StudentStudyHistoryServiceTest.java @@ -0,0 +1,88 @@ +package com.gdschongik.gdsc.domain.study.application; + +import static com.gdschongik.gdsc.global.common.constant.StudyConstant.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.domain.MemberRole; +import com.gdschongik.gdsc.domain.recruitment.domain.vo.Period; +import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository; +import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; +import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmission; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetcher; +import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus; +import com.gdschongik.gdsc.domain.study.domain.Study; +import com.gdschongik.gdsc.domain.study.domain.StudyDetail; +import com.gdschongik.gdsc.domain.study.domain.StudyHistory; +import com.gdschongik.gdsc.helper.IntegrationTest; +import java.time.LocalDateTime; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +class StudentStudyHistoryServiceTest extends IntegrationTest { + + @Autowired + private StudentStudyHistoryService studentStudyHistoryService; + + @Autowired + private StudyHistoryRepository studyHistoryRepository; + + @Autowired + private AssignmentHistoryRepository assignmentHistoryRepository; + + private void setCurrentTime(LocalDateTime now) { + try (MockedStatic mock = Mockito.mockStatic(LocalDateTime.class, Mockito.CALLS_REAL_METHODS)) { + mock.when(LocalDateTime::now).thenReturn(now); + } + } + + @Nested + class 과제_제출할때 { + + @Test + void 성공한다() { + // given + Member mentor = createAssociateMember(); + // TODO: LocalDateTime.now() 관련 테스트 정책 논의 필요 + LocalDateTime now = LocalDateTime.now(); // 통합 테스트에서는 LocalDateTime.now()를 사용해야 함 + Study study = createStudy( + mentor, + Period.createPeriod(now.minusWeeks(1), now.plusWeeks(7)), // 스터디 기간: 1주 전 ~ 7주 후 + Period.createPeriod(now.minusWeeks(2), now.minusWeeks(1))); // 수강신청 기간: 2주 전 ~ 1주 전 + StudyDetail studyDetail = + createStudyDetail(study, now.minusDays(6), now.plusDays(1)); // 1주차 기간: 6일 전 ~ 1일 후 + publishAssignment(studyDetail); + + Member student = createRegularMember(); + logoutAndReloginAs(student.getId(), MemberRole.REGULAR); + + // 수강신청 valiadtion 로직이 LocalDateTime.now() 기준으로 동작하기 때문에 직접 수강신청 생성 + StudyHistory studyHistory = StudyHistory.create(student, study); + studyHistory.updateRepositoryLink(REPOSITORY_LINK); + studyHistoryRepository.save(studyHistory); + + // 제출정보 조회 fetcher stubbing + AssignmentSubmissionFetcher mockFetcher = mock(AssignmentSubmissionFetcher.class); + when(mockFetcher.fetch()) + .thenReturn(new AssignmentSubmission(REPOSITORY_LINK, COMMIT_HASH, 500, COMMITTED_AT)); + when(githubClient.getLatestAssignmentSubmissionFetcher(anyString(), anyInt())) + .thenReturn(mockFetcher); + + // when + studentStudyHistoryService.submitAssignment(studyDetail.getId()); + + // then + AssignmentHistory assignmentHistory = + assignmentHistoryRepository.findById(1L).orElseThrow(); + assertThat(assignmentHistory.getSubmissionStatus()).isEqualTo(AssignmentSubmissionStatus.SUCCESS); + assertThat(assignmentHistory.getSubmissionLink()).isEqualTo(REPOSITORY_LINK); + assertThat(assignmentHistory.getCommitHash()).isEqualTo(COMMIT_HASH); + assertThat(assignmentHistory.getContentLength()).isEqualTo(500); + } + } +} diff --git a/src/test/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGraderTest.java b/src/test/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGraderTest.java new file mode 100644 index 000000000..671488c27 --- /dev/null +++ b/src/test/java/com/gdschongik/gdsc/domain/study/domain/AssignmentHistoryGraderTest.java @@ -0,0 +1,100 @@ +package com.gdschongik.gdsc.domain.study.domain; + +import static com.gdschongik.gdsc.global.common.constant.StudyConstant.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.gdschongik.gdsc.domain.recruitment.domain.vo.Period; +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.exception.ErrorCode; +import com.gdschongik.gdsc.helper.FixtureHelper; +import java.time.LocalDateTime; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class AssignmentHistoryGraderTest { + + FixtureHelper fixtureHelper = new FixtureHelper(); + AssignmentHistoryGrader grader = new AssignmentHistoryGrader(); + + AssignmentSubmissionFetcher mockFetcher = mock(AssignmentSubmissionFetcher.class); + + // FixtureHelper 래핑 메서드 + private AssignmentHistory createAssignmentHistory() { + Study study = createStudyWithMentor(1L); + StudyDetail studyDetail = fixtureHelper.createStudyDetail( + study, LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + return AssignmentHistory.create(studyDetail, fixtureHelper.createAssociateMember(2L)); + } + + private Study createStudyWithMentor(Long mentorId) { + Period period = Period.createPeriod(STUDY_START_DATETIME, STUDY_END_DATETIME); + Period applicationPeriod = + Period.createPeriod(STUDY_START_DATETIME.minusDays(7), STUDY_START_DATETIME.minusDays(1)); + return fixtureHelper.createStudyWithMentor(mentorId, period, applicationPeriod); + } + + @Nested + class 과제_채점시 { + + @Test + void 과제내용이_최소길이_이상이면_성공_처리된다() { + // given + AssignmentHistory history = createAssignmentHistory(); + AssignmentSubmission validSubmission = new AssignmentSubmission("url", "hash", 500, LocalDateTime.now()); + when(mockFetcher.fetch()).thenReturn(validSubmission); + + // when + grader.judge(mockFetcher, history); + + // then + assertThat(history.getSubmissionStatus()).isEqualTo(AssignmentSubmissionStatus.SUCCESS); + assertThat(history.getSubmissionLink()).isEqualTo("url"); + assertThat(history.getCommitHash()).isEqualTo("hash"); + assertThat(history.getContentLength()).isEqualTo(500); + } + + @Test + void 과제내용이_최소길이_미만이면_실패_처리된다() { + // given + AssignmentHistory history = createAssignmentHistory(); + AssignmentSubmission shortSubmission = new AssignmentSubmission("url", "hash", 200, LocalDateTime.now()); + when(mockFetcher.fetch()).thenReturn(shortSubmission); + + // when + grader.judge(mockFetcher, history); + + // then + assertThat(history.getSubmissionStatus()).isEqualTo(AssignmentSubmissionStatus.FAILURE); + assertThat(history.getSubmissionFailureType()).isEqualTo(SubmissionFailureType.WORD_COUNT_INSUFFICIENT); + } + + @Test + void 해당_위치에_과제파일_미존재시_위치확인불가로_실패_처리된다() { + // given + AssignmentHistory history = createAssignmentHistory(); + when(mockFetcher.fetch()).thenThrow(new CustomException(ErrorCode.GITHUB_CONTENT_NOT_FOUND)); + + // when + grader.judge(mockFetcher, history); + + // then + assertThat(history.getSubmissionStatus()).isEqualTo(AssignmentSubmissionStatus.FAILURE); + assertThat(history.getSubmissionFailureType()).isEqualTo(SubmissionFailureType.LOCATION_UNIDENTIFIABLE); + } + + @Test + void 그외_Github_문제인경우_알수없는오류로_실패_처리된다() { + // given + AssignmentHistory history = createAssignmentHistory(); + when(mockFetcher.fetch()).thenThrow(new CustomException(ErrorCode.GITHUB_FILE_READ_FAILED)); + + // when + grader.judge(mockFetcher, history); + + // then + assertThat(history.getSubmissionStatus()).isEqualTo(AssignmentSubmissionStatus.FAILURE); + assertThat(history.getSubmissionFailureType()).isEqualTo(SubmissionFailureType.UNKNOWN); + } + } +} diff --git a/src/test/java/com/gdschongik/gdsc/global/common/constant/StudyConstant.java b/src/test/java/com/gdschongik/gdsc/global/common/constant/StudyConstant.java index 02588410d..19714d487 100644 --- a/src/test/java/com/gdschongik/gdsc/global/common/constant/StudyConstant.java +++ b/src/test/java/com/gdschongik/gdsc/global/common/constant/StudyConstant.java @@ -41,6 +41,9 @@ private StudyConstant() {} // AssignmentHistory public static final String SUBMISSION_LINK = "https://github.com/ownername/reponame/blob/main/week1/WIL.md"; public static final String COMMIT_HASH = "aa11bb22cc33"; - public static final Long CONTENT_LENGTH = 2000L; + public static final Integer CONTENT_LENGTH = 2000; public static final LocalDateTime COMMITTED_AT = LocalDateTime.of(2024, 9, 8, 0, 0); + + // StudyHistory + public static final String REPOSITORY_LINK = "ownername/reponame"; } diff --git a/src/test/java/com/gdschongik/gdsc/helper/IntegrationTest.java b/src/test/java/com/gdschongik/gdsc/helper/IntegrationTest.java index fc0e07f8d..1bfbac94d 100644 --- a/src/test/java/com/gdschongik/gdsc/helper/IntegrationTest.java +++ b/src/test/java/com/gdschongik/gdsc/helper/IntegrationTest.java @@ -36,6 +36,7 @@ import com.gdschongik.gdsc.domain.study.domain.StudyDetail; import com.gdschongik.gdsc.global.security.PrincipalDetails; import com.gdschongik.gdsc.infra.feign.payment.client.PaymentClient; +import com.gdschongik.gdsc.infra.github.client.GithubClient; import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -86,6 +87,9 @@ public abstract class IntegrationTest { @MockBean protected PaymentClient paymentClient; + @MockBean + protected GithubClient githubClient; + @MockBean protected DelegateMemberDiscordEventHandler delegateMemberDiscordEventHandler; @@ -257,4 +261,9 @@ protected StudyDetail createNewStudyDetail(Long week, Study study, LocalDateTime StudyDetail.createStudyDetail(study, week, ATTENDANCE_NUMBER, Period.createPeriod(startDate, endDate)); return studyDetailRepository.save(studyDetail); } + + protected StudyDetail publishAssignment(StudyDetail studyDetail) { + studyDetail.publishAssignment(ASSIGNMENT_TITLE, studyDetail.getPeriod().getEndDate(), DESCRIPTION_LINK); + return studyDetailRepository.save(studyDetail); + } }