From 5f8b344807ccc4d55660d929908ce2a7be5ae4ed Mon Sep 17 00:00:00 2001 From: hyunchin2 Date: Sat, 6 Jun 2026 17:44:56 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20improve=20=EB=8B=B5=EB=B3=80=20Null?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interview/service/InterviewService.java | 11 ++++++++- .../service/InterviewServiceTest.java | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/trouble_log/domain/interview/service/InterviewService.java b/src/main/java/com/example/trouble_log/domain/interview/service/InterviewService.java index b91eca2..dc1d513 100644 --- a/src/main/java/com/example/trouble_log/domain/interview/service/InterviewService.java +++ b/src/main/java/com/example/trouble_log/domain/interview/service/InterviewService.java @@ -34,6 +34,9 @@ @Transactional(readOnly = true) public class InterviewService { + private static final String DEFAULT_IMPROVEMENT_MESSAGE = + "현재 답변은 질문의 핵심을 잘 짚고 있어요. 이 흐름을 유지하면서 본인이 직접 고민하고 해결한 과정까지 차분히 이어가면 더 설득력 있는 답변이 될 수 있습니다."; + private final InterviewQuestionRepository questionRepository; private final InterviewAnswerRepository answerRepository; private final ProjectSessionRepository sessionRepository; @@ -93,7 +96,7 @@ public AnswerResponse saveAnswer(Long sessionId, Long questionId, AnswerRequest feedbackJson = "{}"; } - improvement = feedbackResult.getImprovement(); + improvement = normalizeImprovement(feedbackResult.getImprovement()); warning = feedbackResult.getWarning(); } @@ -109,6 +112,12 @@ public AnswerResponse saveAnswer(Long sessionId, Long questionId, AnswerRequest return new AnswerResponse(saved.getId(), improvement, warning); } + private String normalizeImprovement(String improvement) { + return improvement == null || improvement.isBlank() + ? DEFAULT_IMPROVEMENT_MESSAGE + : improvement; + } + // ── 트러블슈팅 리포트 생성 ──────────────────────────────── @Transactional public ReportResponse generateReport(Long sessionId) { diff --git a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java index 57d924a..7462992 100644 --- a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java +++ b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java @@ -30,6 +30,7 @@ class InterviewServiceTest { private InterviewAnswerRepository answerRepository; + private AzureOpenAiPromptService promptService; private InterviewService interviewService; @BeforeEach @@ -38,7 +39,7 @@ void setUp() { answerRepository = mock(InterviewAnswerRepository.class); ProjectSessionRepository sessionRepository = mock(ProjectSessionRepository.class); PreContextRepository preContextRepository = mock(PreContextRepository.class); - AzureOpenAiPromptService promptService = mock(AzureOpenAiPromptService.class); + promptService = mock(AzureOpenAiPromptService.class); interviewService = new InterviewService( questionRepository, @@ -85,6 +86,27 @@ void saveAnswerUpdatesExistingAnswerInsteadOfCreatingDuplicate() { assertThat(existingAnswer.getFeedback()).isNotBlank(); } + @Test + void saveAnswerReturnsDefaultImprovementWhenAiImprovementIsNull() { + AnswerFeedbackResult feedbackResult = new AnswerFeedbackResult(); + feedbackResult.setImprovement(null); + when(promptService.evaluateAnswer(any(ProjectSession.class), any(PreContext.class), any(), any())) + .thenReturn(feedbackResult); + + InterviewAnswer existingAnswer = new InterviewAnswer(question(), null, true, null); + ReflectionTestUtils.setField(existingAnswer, "id", 10L); + when(answerRepository.findByInterviewQuestionId(1L)).thenReturn(Optional.of(existingAnswer)); + + AnswerRequest request = new AnswerRequest(); + request.setAnswer("좋은 답변입니다."); + + AnswerResponse response = interviewService.saveAnswer(1L, 1L, request); + + assertThat(response.getImprovement()).isEqualTo( + "현재 답변은 질문의 핵심을 잘 짚고 있어요. 이 흐름을 유지하면서 본인이 직접 고민하고 해결한 과정까지 차분히 이어가면 더 설득력 있는 답변이 될 수 있습니다." + ); + } + private ProjectSession projectSession() { Member member = new Member("user@example.com", "password", "user"); ReflectionTestUtils.setField(member, "id", 1L); From 102314919999669c93a514a57f0148ece7bc861f Mon Sep 17 00:00:00 2001 From: hyunchin2 Date: Sat, 6 Jun 2026 18:00:08 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=EB=9E=98=EB=B9=97?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20(=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=94=94=EB=B2=A8?= =?UTF-8?q?=EB=A1=AD),=20=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InterviewSubmissionController.java | 6 +-- .../dto/PersonalInfoDetectionResponse.java | 18 +++++++ .../service/InterviewSubmissionService.java | 35 +----------- .../controller/ProjectSessionController.java | 6 ++- .../service/ProjectSessionService.java | 21 ++++++++ .../exception/GlobalExceptionHandler.java | 9 ++-- .../service/InterviewServiceTest.java | 21 ++++++++ .../InterviewSubmissionServiceTest.java | 14 ++--- .../service/ProjectSessionServiceTest.java | 54 +++++++++++++++++++ 9 files changed, 130 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/example/trouble_log/domain/interview/dto/PersonalInfoDetectionResponse.java create mode 100644 src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java diff --git a/src/main/java/com/example/trouble_log/domain/interview/controller/InterviewSubmissionController.java b/src/main/java/com/example/trouble_log/domain/interview/controller/InterviewSubmissionController.java index 04013cb..055e114 100644 --- a/src/main/java/com/example/trouble_log/domain/interview/controller/InterviewSubmissionController.java +++ b/src/main/java/com/example/trouble_log/domain/interview/controller/InterviewSubmissionController.java @@ -21,19 +21,19 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/projects") -@Tag(name = "Interview Submission", description = "면접 답변 최종 제출 및 개인정보 감지 API") +@Tag(name = "Interview Submission", description = "면접 답변 최종 제출 API") public class InterviewSubmissionController { private final InterviewSubmissionService interviewSubmissionService; @Operation( summary = "면접 답변 최종 제출", - description = "프로젝트 세션에 생성된 면접 질문 3개에 대한 최종 답변을 저장합니다. 답변 내 개인정보가 감지되면 제출을 차단하고 감지 유형과 경고 메시지를 반환합니다." + description = "프로젝트 세션에 생성된 면접 질문 3개에 대한 최종 답변을 저장합니다. 개인정보와 민감정보 감지는 최초 소스코드 입력 단계에서 수행합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "최종 제출 성공", content = @Content(schema = @Schema(implementation = InterviewSubmitResponse.class))), - @ApiResponse(responseCode = "400", description = "필수 값 누락, 질문 3개 미준비, 답변 개수 불일치, 개인정보 감지"), + @ApiResponse(responseCode = "400", description = "필수 값 누락, 질문 3개 미준비, 답변 개수 불일치"), @ApiResponse(responseCode = "403", description = "해당 프로젝트 세션에 접근할 수 없음"), @ApiResponse(responseCode = "404", description = "프로젝트 세션을 찾을 수 없음") }) diff --git a/src/main/java/com/example/trouble_log/domain/interview/dto/PersonalInfoDetectionResponse.java b/src/main/java/com/example/trouble_log/domain/interview/dto/PersonalInfoDetectionResponse.java new file mode 100644 index 0000000..92505d3 --- /dev/null +++ b/src/main/java/com/example/trouble_log/domain/interview/dto/PersonalInfoDetectionResponse.java @@ -0,0 +1,18 @@ +package com.example.trouble_log.domain.interview.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "개인정보 감지 차단 응답") +public class PersonalInfoDetectionResponse { + + @Schema(description = "차단 사유", example = "PERSONAL_INFO_DETECTED") + private String blockedReason; + + @Schema(description = "개인정보 감지 경고 목록") + private List warnings; +} diff --git a/src/main/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionService.java b/src/main/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionService.java index 8becaee..6f964e0 100644 --- a/src/main/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionService.java +++ b/src/main/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionService.java @@ -4,10 +4,8 @@ import com.example.trouble_log.domain.interview.dto.InterviewSubmitAnswerRequest; import com.example.trouble_log.domain.interview.dto.InterviewSubmitRequest; import com.example.trouble_log.domain.interview.dto.InterviewSubmitResponse; -import com.example.trouble_log.domain.interview.dto.PersonalInfoWarning; import com.example.trouble_log.domain.interview.entity.InterviewAnswer; import com.example.trouble_log.domain.interview.entity.InterviewQuestion; -import com.example.trouble_log.domain.interview.exception.PersonalInfoDetectedException; import com.example.trouble_log.domain.interview.repository.InterviewAnswerRepository; import com.example.trouble_log.domain.interview.repository.InterviewQuestionRepository; import com.example.trouble_log.domain.projectSession.entity.ProjectSession; @@ -17,7 +15,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -31,15 +28,13 @@ public class InterviewSubmissionService { private static final int REQUIRED_QUESTION_COUNT = 3; - private static final String UNANSWERED_MESSAGE = "사용자 답변 안 함"; private final ProjectSessionRepository projectSessionRepository; private final InterviewQuestionRepository interviewQuestionRepository; private final InterviewAnswerRepository interviewAnswerRepository; - private final PersonalInfoDetectionService personalInfoDetectionService; private final AuditLogService auditLogService; - // 최종 답변 3개를 저장한 뒤 개인정보 감지 결과에 따라 리포트 생성 가능 상태를 반환한다. + // 최종 답변 3개를 저장한 뒤 리포트 생성 가능 상태를 반환한다. @Transactional public InterviewSubmitResponse submit(Long sessionId, InterviewSubmitRequest request) { validateRequest(sessionId, request); @@ -55,19 +50,6 @@ public InterviewSubmitResponse submit(Long sessionId, InterviewSubmitRequest req Map finalAnswers = validateAndNormalizeFinalAnswers(questions, request.getAnswers()); List savedAnswers = saveFinalAnswers(questions, finalAnswers); - String answersText = buildAnswersText(questions, savedAnswers); - List warnings = personalInfoDetectionService.detect(answersText); - if (!warnings.isEmpty()) { - auditLogService.recordFailure( - request.getMemberId(), - sessionId, - "INTERVIEW_ANSWER_SUBMIT", - buildSubmitRequestSummary(questions, savedAnswers), - "개인정보 감지: " + warnings.stream().map(PersonalInfoWarning::getType).toList() - ); - throw new PersonalInfoDetectedException(warnings); - } - auditLogService.recordSuccess( request.getMemberId(), sessionId, @@ -176,21 +158,6 @@ private InterviewAnswer saveFinalAnswer(InterviewQuestion question, String final return interviewAnswerRepository.save(interviewAnswer); } - // 개인정보 감지와 리포트 생성 요청에 사용할 질문/답변 텍스트를 만든다. - private String buildAnswersText(List questions, List answers) { - Map answerByQuestionId = answers.stream() - .collect(Collectors.toMap(answer -> answer.getInterviewQuestion().getId(), Function.identity())); - - return questions.stream() - .map(question -> question.getQuestion() + "\n" + toReportAnswer(answerByQuestionId.get(question.getId()).getAnswer())) - .reduce("", (left, right) -> left + "\n" + right); - } - - // 답변이 비어 있으면 AI 리포트가 미답변 상태를 이해할 수 있는 명시 문구로 변환한다. - private String toReportAnswer(String answer) { - return answer == null || answer.isBlank() ? UNANSWERED_MESSAGE : answer; - } - // 최종 제출 요청 내용을 감사 로그용 요약 문자열로 변환한다. private String buildSubmitRequestSummary(List questions, List answers) { int answerLength = answers.stream() diff --git a/src/main/java/com/example/trouble_log/domain/projectSession/controller/ProjectSessionController.java b/src/main/java/com/example/trouble_log/domain/projectSession/controller/ProjectSessionController.java index 38ec7c4..cdc7952 100644 --- a/src/main/java/com/example/trouble_log/domain/projectSession/controller/ProjectSessionController.java +++ b/src/main/java/com/example/trouble_log/domain/projectSession/controller/ProjectSessionController.java @@ -1,5 +1,6 @@ package com.example.trouble_log.domain.projectSession.controller; +import com.example.trouble_log.domain.interview.dto.PersonalInfoDetectionResponse; import com.example.trouble_log.domain.projectSession.dto.PreContextRequest; import com.example.trouble_log.domain.projectSession.dto.PreContextResponse; import com.example.trouble_log.domain.projectSession.dto.ProjectSessionRequest; @@ -31,12 +32,13 @@ public class ProjectSessionController { @Operation( summary = "프로젝트 세션 생성", - description = "회원이 입력한 소스코드와 선택적인 GitHub URL을 저장하고, 이후 사전 컨텍스트와 면접 질문 생성에 사용할 세션 ID를 반환합니다." + description = "회원이 입력한 소스코드에서 개인정보와 민감정보를 먼저 감지합니다. 문제가 없으면 소스코드와 선택적인 GitHub URL을 저장하고, 이후 사전 컨텍스트와 면접 질문 생성에 사용할 세션 ID를 반환합니다." ) @ApiResponses({ @ApiResponse(responseCode = "201", description = "프로젝트 세션 생성 성공", content = @Content(schema = @Schema(implementation = ProjectSessionResponse.class))), - @ApiResponse(responseCode = "400", description = "회원 ID 또는 소스코드 누락"), + @ApiResponse(responseCode = "400", description = "회원 ID 또는 소스코드 누락, 개인정보 또는 민감정보 감지", + content = @Content(schema = @Schema(implementation = PersonalInfoDetectionResponse.class))), @ApiResponse(responseCode = "404", description = "회원을 찾을 수 없음") }) @PostMapping() diff --git a/src/main/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionService.java b/src/main/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionService.java index ec65aa8..5da8483 100644 --- a/src/main/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionService.java +++ b/src/main/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionService.java @@ -3,8 +3,11 @@ import com.example.trouble_log.domain.ai.service.AzureOpenAiPromptService; import com.example.trouble_log.domain.audit.service.AuditLogService; import com.example.trouble_log.domain.interview.dto.InterviewQuestionResponse; +import com.example.trouble_log.domain.interview.dto.PersonalInfoWarning; import com.example.trouble_log.domain.interview.entity.InterviewQuestion; +import com.example.trouble_log.domain.interview.exception.PersonalInfoDetectedException; import com.example.trouble_log.domain.interview.repository.InterviewQuestionRepository; +import com.example.trouble_log.domain.interview.service.PersonalInfoDetectionService; import com.example.trouble_log.domain.projectSession.dto.PreContextRequest; import com.example.trouble_log.domain.projectSession.dto.PreContextResponse; import com.example.trouble_log.domain.projectSession.dto.ProjectSessionRequest; @@ -37,12 +40,14 @@ public class ProjectSessionService { private final InterviewQuestionRepository interviewQuestionRepository; private final AzureOpenAiPromptService azureOpenAiPromptService; private final AuditLogService auditLogService; + private final PersonalInfoDetectionService personalInfoDetectionService; @Transactional // 회원의 코드와 GitHub URL을 기반으로 새 프로젝트 세션을 생성한다. public ProjectSessionResponse create(ProjectSessionRequest request) { validateRequestBody(request); validateRequiredFields("회원 ID와 소스코드는 필수입니다.", request.getMemberId(), request.getCodeContent()); + validateCodeContentDoesNotContainPersonalInfo(request); Member member = memberRepository.findById(request.getMemberId()) .orElseThrow(() -> { @@ -75,6 +80,22 @@ public ProjectSessionResponse create(ProjectSessionRequest request) { return new ProjectSessionResponse(savedProjectSession.getId()); } + private void validateCodeContentDoesNotContainPersonalInfo(ProjectSessionRequest request) { + List warnings = personalInfoDetectionService.detect(request.getCodeContent()); + if (warnings.isEmpty()) { + return; + } + + auditLogService.recordFailure( + request.getMemberId(), + null, + "PROJECT_SESSION_CREATE", + buildProjectSessionRequestSummary(request), + "개인정보 감지: " + warnings.stream().map(PersonalInfoWarning::getType).toList() + ); + throw new PersonalInfoDetectedException(warnings); + } + @Transactional // 사전 컨텍스트를 저장하고 AI 면접 질문을 생성해 함께 반환한다. public PreContextResponse createPreContext(Long sessionId, PreContextRequest request) { diff --git a/src/main/java/com/example/trouble_log/global/exception/GlobalExceptionHandler.java b/src/main/java/com/example/trouble_log/global/exception/GlobalExceptionHandler.java index aefc67c..a370f4f 100644 --- a/src/main/java/com/example/trouble_log/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/example/trouble_log/global/exception/GlobalExceptionHandler.java @@ -1,8 +1,7 @@ package com.example.trouble_log.global.exception; -import com.example.trouble_log.domain.interview.dto.InterviewSubmitResponse; +import com.example.trouble_log.domain.interview.dto.PersonalInfoDetectionResponse; import com.example.trouble_log.domain.interview.exception.PersonalInfoDetectedException; -import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -12,10 +11,8 @@ public class GlobalExceptionHandler { @ExceptionHandler(PersonalInfoDetectedException.class) - public ResponseEntity handlePersonalInfoDetected(PersonalInfoDetectedException e) { - InterviewSubmitResponse response = new InterviewSubmitResponse( - false, - false, + public ResponseEntity handlePersonalInfoDetected(PersonalInfoDetectedException e) { + PersonalInfoDetectionResponse response = new PersonalInfoDetectionResponse( "PERSONAL_INFO_DETECTED", e.getWarnings() ); diff --git a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java index 7462992..a3b6a72 100644 --- a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java +++ b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewServiceTest.java @@ -107,6 +107,27 @@ void saveAnswerReturnsDefaultImprovementWhenAiImprovementIsNull() { ); } + @Test + void saveAnswerReturnsDefaultImprovementWhenAiImprovementIsBlank() { + AnswerFeedbackResult feedbackResult = new AnswerFeedbackResult(); + feedbackResult.setImprovement(" "); + when(promptService.evaluateAnswer(any(ProjectSession.class), any(PreContext.class), any(), any())) + .thenReturn(feedbackResult); + + InterviewAnswer existingAnswer = new InterviewAnswer(question(), null, true, null); + ReflectionTestUtils.setField(existingAnswer, "id", 10L); + when(answerRepository.findByInterviewQuestionId(1L)).thenReturn(Optional.of(existingAnswer)); + + AnswerRequest request = new AnswerRequest(); + request.setAnswer("좋은 답변입니다."); + + AnswerResponse response = interviewService.saveAnswer(1L, 1L, request); + + assertThat(response.getImprovement()).isEqualTo( + "현재 답변은 질문의 핵심을 잘 짚고 있어요. 이 흐름을 유지하면서 본인이 직접 고민하고 해결한 과정까지 차분히 이어가면 더 설득력 있는 답변이 될 수 있습니다." + ); + } + private ProjectSession projectSession() { Member member = new Member("user@example.com", "password", "user"); ReflectionTestUtils.setField(member, "id", 1L); diff --git a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionServiceTest.java b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionServiceTest.java index ecfe5f7..43770fc 100644 --- a/src/test/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionServiceTest.java +++ b/src/test/java/com/example/trouble_log/domain/interview/service/InterviewSubmissionServiceTest.java @@ -1,7 +1,6 @@ package com.example.trouble_log.domain.interview.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; @@ -13,7 +12,6 @@ import com.example.trouble_log.domain.interview.dto.InterviewSubmitResponse; import com.example.trouble_log.domain.interview.entity.InterviewAnswer; import com.example.trouble_log.domain.interview.entity.InterviewQuestion; -import com.example.trouble_log.domain.interview.exception.PersonalInfoDetectedException; import com.example.trouble_log.domain.interview.repository.InterviewAnswerRepository; import com.example.trouble_log.domain.interview.repository.InterviewQuestionRepository; import com.example.trouble_log.domain.projectSession.entity.ProjectSession; @@ -41,7 +39,6 @@ void setUp() { projectSessionRepository, interviewQuestionRepository, interviewAnswerRepository, - new PersonalInfoDetectionService(), mock(AuditLogService.class) ); @@ -50,7 +47,7 @@ void setUp() { } @Test - void submitBlocksWhenAnswerContainsPersonalInfo() { + void submitDoesNotBlockWhenAnswerContainsPersonalInfo() { ProjectSession projectSession = projectSession(); InterviewSubmitRequest request = request(List.of( answerRequest(1L, "제 이메일은 test@example.com 입니다."), @@ -67,11 +64,10 @@ void submitBlocksWhenAnswerContainsPersonalInfo() { when(interviewQuestionRepository.findByProjectSessionIdOrderByQuestionSequenceAsc(anyLong())) .thenReturn(questions); - assertThatThrownBy(() -> interviewSubmissionService.submit(1L, request)) - .isInstanceOf(PersonalInfoDetectedException.class) - .satisfies(e -> assertThat(((PersonalInfoDetectedException) e).getWarnings()) - .extracting("type") - .containsExactly(com.example.trouble_log.domain.interview.type.PersonalInfoType.EMAIL)); + InterviewSubmitResponse response = interviewSubmissionService.submit(1L, request); + + assertThat(response.getSubmitted()).isTrue(); + assertThat(response.getReportGenerationReady()).isTrue(); } @Test diff --git a/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java b/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java new file mode 100644 index 0000000..b991e1e --- /dev/null +++ b/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java @@ -0,0 +1,54 @@ +package com.example.trouble_log.domain.projectSession.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.example.trouble_log.domain.ai.service.AzureOpenAiPromptService; +import com.example.trouble_log.domain.audit.service.AuditLogService; +import com.example.trouble_log.domain.interview.exception.PersonalInfoDetectedException; +import com.example.trouble_log.domain.interview.repository.InterviewQuestionRepository; +import com.example.trouble_log.domain.interview.service.PersonalInfoDetectionService; +import com.example.trouble_log.domain.projectSession.dto.ProjectSessionRequest; +import com.example.trouble_log.domain.projectSession.entity.ProjectSession; +import com.example.trouble_log.domain.projectSession.repository.PreContextRepository; +import com.example.trouble_log.domain.projectSession.repository.ProjectSessionRepository; +import com.example.trouble_log.domain.user.repository.MemberRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ProjectSessionServiceTest { + + private ProjectSessionRepository projectSessionRepository; + private ProjectSessionService projectSessionService; + + @BeforeEach + void setUp() { + projectSessionRepository = mock(ProjectSessionRepository.class); + projectSessionService = new ProjectSessionService( + mock(MemberRepository.class), + projectSessionRepository, + mock(PreContextRepository.class), + mock(InterviewQuestionRepository.class), + mock(AzureOpenAiPromptService.class), + mock(AuditLogService.class), + new PersonalInfoDetectionService() + ); + } + + @Test + void createBlocksWhenCodeContentContainsPersonalInfo() { + ProjectSessionRequest request = new ProjectSessionRequest( + 1L, + "const email = \"test@example.com\";", + null + ); + + assertThatThrownBy(() -> projectSessionService.create(request)) + .isInstanceOf(PersonalInfoDetectedException.class); + + verify(projectSessionRepository, never()).save(any(ProjectSession.class)); + } +} From 7e02897f49864391eb89e7368d112f6cc4371b53 Mon Sep 17 00:00:00 2001 From: hyunchin2 Date: Sat, 6 Jun 2026 18:06:50 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=EB=9E=98=EB=B9=97?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20(=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=94=94=EB=B2=A8?= =?UTF-8?q?=EB=A1=AD),=20=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=82=AC=EB=A1=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ProjectSessionServiceTest.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java b/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java index b991e1e..19aa691 100644 --- a/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java +++ b/src/test/java/com/example/trouble_log/domain/projectSession/service/ProjectSessionServiceTest.java @@ -1,10 +1,12 @@ package com.example.trouble_log.domain.projectSession.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.example.trouble_log.domain.ai.service.AzureOpenAiPromptService; import com.example.trouble_log.domain.audit.service.AuditLogService; @@ -12,23 +14,29 @@ import com.example.trouble_log.domain.interview.repository.InterviewQuestionRepository; import com.example.trouble_log.domain.interview.service.PersonalInfoDetectionService; import com.example.trouble_log.domain.projectSession.dto.ProjectSessionRequest; +import com.example.trouble_log.domain.projectSession.dto.ProjectSessionResponse; import com.example.trouble_log.domain.projectSession.entity.ProjectSession; import com.example.trouble_log.domain.projectSession.repository.PreContextRepository; import com.example.trouble_log.domain.projectSession.repository.ProjectSessionRepository; +import com.example.trouble_log.domain.user.entity.Member; import com.example.trouble_log.domain.user.repository.MemberRepository; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; class ProjectSessionServiceTest { + private MemberRepository memberRepository; private ProjectSessionRepository projectSessionRepository; private ProjectSessionService projectSessionService; @BeforeEach void setUp() { + memberRepository = mock(MemberRepository.class); projectSessionRepository = mock(ProjectSessionRepository.class); projectSessionService = new ProjectSessionService( - mock(MemberRepository.class), + memberRepository, projectSessionRepository, mock(PreContextRepository.class), mock(InterviewQuestionRepository.class), @@ -51,4 +59,31 @@ void createBlocksWhenCodeContentContainsPersonalInfo() { verify(projectSessionRepository, never()).save(any(ProjectSession.class)); } + + @Test + void createSavesProjectSessionWhenCodeContentDoesNotContainPersonalInfo() { + Member member = new Member("user@example.com", "password", "user"); + ReflectionTestUtils.setField(member, "id", 1L); + + ProjectSession savedProjectSession = new ProjectSession( + member, + "public class Main { public static void main(String[] args) { } }", + "https://github.com/user/project" + ); + ReflectionTestUtils.setField(savedProjectSession, "id", 10L); + + ProjectSessionRequest request = new ProjectSessionRequest( + 1L, + "public class Main { public static void main(String[] args) { } }", + "https://github.com/user/project" + ); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(projectSessionRepository.save(any(ProjectSession.class))).thenReturn(savedProjectSession); + + ProjectSessionResponse response = projectSessionService.create(request); + + assertThat(response.getSessionId()).isEqualTo(10L); + verify(projectSessionRepository).save(any(ProjectSession.class)); + } }