diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java index d5e552d..71874c9 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java @@ -13,6 +13,7 @@ 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.RequestParam; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -40,11 +41,12 @@ public ApiResponse analyze( @GetMapping public ApiResponse getAnalysis( @AuthenticationPrincipal UserDetailsImpl userDetails, - @PathVariable Long mockApplyId + @PathVariable Long mockApplyId, + @RequestParam(required = false) Integer sequence ) { return ApiResponse.onSuccess( "자소서 분석 결과 조회에 성공했습니다.", - analysisService.getAnalysis(getAuthenticatedUser(userDetails), mockApplyId) + analysisService.getAnalysis(getAuthenticatedUser(userDetails), mockApplyId, sequence) ); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index abd654b..f1ef976 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -88,19 +88,62 @@ public AnalysisResponse analyze(User user, Long mockApplyId) { } public AnalysisResponse getAnalysis(User user, Long mockApplyId) { + return getAnalysis(user, mockApplyId, null); + } + + public AnalysisResponse getAnalysis(User user, Long mockApplyId, Integer sequence) { MockApply mockApply = getOwnedMockApply(user, mockApplyId); - Analysis analysis = analysisRepository.findByMockApplyId(mockApply.getId()) + if (sequence != null) { + mockApply = resolveMockApplyBySequence(mockApply, sequence); + } + Long resolvedMockApplyId = mockApply.getId(); + + Analysis analysis = analysisRepository.findByMockApplyId(resolvedMockApplyId) .orElseThrow(() -> new GeneralException( GeneralErrorCode.ANALYSIS_NOT_FOUND, - "해당 모의 서류 지원의 분석 결과를 찾을 수 없습니다. mockApplyId=" + mockApplyId + "해당 모의 서류 지원의 분석 결과를 찾을 수 없습니다. mockApplyId=" + resolvedMockApplyId )); - List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(mockApply.getId()); + List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(resolvedMockApplyId); List questionAnalyses = questionAnalysisRepository.findAllByAnalysisIdOrderByQuestionIdAscIdAsc(analysis.getId()); return toResponse(mockApply, analysis, questions, questionAnalyses); } + private MockApply resolveMockApplyBySequence(MockApply baseMockApply, int sequence) { + if (sequence < 1) { + throw new GeneralException( + GeneralErrorCode.INVALID_PARAMETER, + "sequence는 1 이상의 값이어야 합니다." + ); + } + + List mockApplies = mockApplyRepository.findAllByUserIdAndJobPostingIdOrderByIdAsc( + baseMockApply.getUser().getId(), + baseMockApply.getJobPosting().getId() + ); + + int derivedSequence = 0; + for (MockApply mockApply : mockApplies) { + derivedSequence++; + int resolvedSequence = mockApply.getSequence() != null && mockApply.getSequence() > 0 + ? mockApply.getSequence() + : derivedSequence; + + if (resolvedSequence == sequence) { + return mockApply; + } + } + + throw new GeneralException( + GeneralErrorCode.MOCK_APPLY_NOT_FOUND, + "해당 순번의 모의 서류 지원을 찾을 수 없습니다. mockApplyId=" + + baseMockApply.getId() + + ", sequence=" + + sequence + ); + } + private void replaceExistingAnalysis(MockApply mockApply) { Optional existingAnalysis = analysisRepository.findByMockApplyId(mockApply.getId()); if (existingAnalysis.isEmpty()) { diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java index 3b89f1e..48b7183 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java @@ -11,6 +11,7 @@ public interface MockApplyRepository extends JpaRepository { List findAllByUserId(Long userId); List findAllByJobPostingId(Long jobPostingId); + List findAllByUserIdAndJobPostingIdOrderByIdAsc(Long userId, Long jobPostingId); long countByUserIdAndJobPostingId(Long userId, Long jobPostingId); @Query(""" diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java index bff8e14..d014a49 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java @@ -463,6 +463,106 @@ void getAnalysis() { assertThat(response.questions().get(0).analyses()).hasSize(1); } + @Test + @DisplayName("sequence 쿼리값으로 같은 공고의 특정 회차 분석 결과를 조회한다") + void getAnalysisBySequence() { + User user = saveUser("analysis-get-sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user); + MockApply firstMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + MockApply secondMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + saveQuestion(firstMockApply, "첫 번째 지원 동기", "첫 번째 답변입니다."); + Question secondQuestion = saveQuestion(secondMockApply, "두 번째 지원 동기", "두 번째 답변입니다."); + when(analysisAiClient.analyze(any(), any())) + .thenReturn(new AnalysisLlmResponse( + 61, + 62, + 63, + 64, + "첫 번째 분석 결과", + List.of() + )) + .thenReturn(new AnalysisLlmResponse( + 81, + 82, + 83, + 84, + "두 번째 분석 결과", + List.of(new AnalysisLlmResponse.QuestionAnalysisItem( + secondQuestion.getId(), + "두 번째 답변입니다.", + "mentioned", + "근거가 더 필요합니다.", + "두 번째 답변에 구체적인 성과를 추가했습니다." + )) + )); + + analysisService.analyze(user, firstMockApply.getId()); + AnalysisResponse saved = analysisService.analyze(user, secondMockApply.getId()); + + AnalysisResponse response = analysisService.getAnalysis(user, firstMockApply.getId(), 2); + + assertThat(response.analysisId()).isEqualTo(saved.analysisId()); + assertThat(response.mockApplyId()).isEqualTo(secondMockApply.getId()); + assertThat(response.sequence()).isEqualTo(2); + assertThat(response.feedback()).isEqualTo("두 번째 분석 결과"); + } + + @Test + @DisplayName("sequence 쿼리값 조회는 저장된 지원 순번을 우선 사용한다") + void getAnalysisByStoredSequence() { + User user = saveUser("analysis-get-stored-sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user); + MockApply firstMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + MockApply secondMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL, 4)); + saveQuestion(firstMockApply, "첫 번째 지원 동기", "첫 번째 답변입니다."); + Question secondQuestion = saveQuestion(secondMockApply, "두 번째 지원 동기", "두 번째 답변입니다."); + when(analysisAiClient.analyze(any(), any())) + .thenReturn(new AnalysisLlmResponse( + 61, + 62, + 63, + 64, + "첫 번째 분석 결과", + List.of() + )) + .thenReturn(new AnalysisLlmResponse( + 81, + 82, + 83, + 84, + "저장 순번 분석 결과", + List.of(new AnalysisLlmResponse.QuestionAnalysisItem( + secondQuestion.getId(), + "두 번째 답변입니다.", + "mentioned", + "근거가 더 필요합니다.", + "두 번째 답변에 구체적인 성과를 추가했습니다." + )) + )); + + analysisService.analyze(user, firstMockApply.getId()); + AnalysisResponse saved = analysisService.analyze(user, secondMockApply.getId()); + + AnalysisResponse response = analysisService.getAnalysis(user, firstMockApply.getId(), 4); + + assertThat(response.analysisId()).isEqualTo(saved.analysisId()); + assertThat(response.mockApplyId()).isEqualTo(secondMockApply.getId()); + assertThat(response.sequence()).isEqualTo(4); + assertThat(response.feedback()).isEqualTo("저장 순번 분석 결과"); + } + + @Test + @DisplayName("존재하지 않는 sequence로 분석 결과 조회 시 예외를 던진다") + void getAnalysisThrowsWhenSequenceDoesNotExist() { + User user = saveUser("analysis-get-sequence-missing@example.com"); + MockApply mockApply = saveMockApply(user); + + assertThatThrownBy(() -> analysisService.getAnalysis(user, mockApply.getId(), 2)) + .isInstanceOf(GeneralException.class) + .extracting("code") + .isEqualTo(GeneralErrorCode.MOCK_APPLY_NOT_FOUND); + } + @Test @DisplayName("크레딧 1개인 사용자가 동시에 분석을 요청해도 하나만 성공한다") void analyzeConcurrentlyUsesCreditOnlyOnce() throws Exception {