Skip to content

feat: 답안 체점 로직 구현#55

Merged
jong-0126 merged 1 commit intodevfrom
feat/51
Jun 10, 2025
Merged

feat: 답안 체점 로직 구현#55
jong-0126 merged 1 commit intodevfrom
feat/51

Conversation

@jong-0126
Copy link
Collaborator

@jong-0126 jong-0126 commented Jun 10, 2025

feat: 답안 체점 로직 구현
test:

  • 정상 체점 후 데이터 저장
  • 구독 정보 없는 경우 예외 처리
  • 퀴즈 정보 없는 경우 예외 처리

🔎 작업 내용

  • 객관식 답안 채점 기능 구현
  • 테스트 코드 작성

Summary by CodeRabbit

  • 신규 기능

    • 사용자가 퀴즈 정답을 제출할 수 있는 API 엔드포인트가 추가되었습니다.
    • 퀴즈 정답 제출을 위한 요청 DTO 및 서비스가 도입되었습니다.
  • 버그 수정

    • 퀴즈 조회 실패 시 안내 메시지가 "해당 퀴즈를 찾을 수 없습니다"로 정확하게 변경되었습니다.
  • 개선사항

    • "/quizzes/**" 경로에 대한 접근이 인증 없이 가능하도록 보안 정책이 완화되었습니다.
    • OpenAI API 키가 환경변수 대신 Spring 프로퍼티에서 주입되도록 변경되었습니다.
  • 테스트

    • 퀴즈 정답 제출 서비스에 대한 단위 테스트가 추가되었습니다.

closed #51

test:
- 정상 체점 후 데이터 저장
- 구독 정보 없는 경우 예외 처리
- 퀴즈 정보 없는 경우 예외 처리
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 10, 2025

Walkthrough

이번 변경에서는 퀴즈 답변 제출 기능이 신규로 도입되어, 관련 DTO, 서비스, 컨트롤러가 추가되었습니다. 또한, 일부 엔티티와 레포지토리에 빌더 및 생성자, 메서드 추가/삭제가 이루어졌고, 예외 메시지 및 보안, AI 설정 등도 소폭 수정되었습니다. 신규 서비스에 대한 단위 테스트도 추가되었습니다.

Changes

파일/경로 그룹 변경 요약
.../domain/quiz/entity/QuizCategory.java @builder 어노테이션 및 lombok import 추가, 단일 인자 생성자에 적용
.../domain/quiz/exception/QuizExceptionCode.java NOT_FOUND_ERROR 메시지를 "이벤트" → "퀴즈"로 수정
.../domain/subscription/dto/SubscriptionRequest.java @builder 및 all-args 생성자 추가
.../domain/subscription/repository/SubscriptionRepository.java findByEmail 메서드 삭제
.../domain/userQuizAnswer/controller/UserQuizAnswerController.java 클래스에 @RequestMapping("/quizzes"), @requiredargsconstructor, 서비스 필드 및 answerSubmit 엔드포인트 추가
.../domain/userQuizAnswer/requestDto/UserQuizAnswerRequestDto.java UserQuizAnswerRequestDto DTO 신규 추가 (@Getter, @builder, 생성자)
.../domain/userQuizAnswer/service/UserQuizAnswerService.java UserQuizAnswerService 신규 추가, answerSubmit 메서드 구현
.../domain/users/repository/UserRepository.java findBySubscription(Subscription) 메서드 추가
.../global/config/AiConfig.java OPENAI API 키 주입 방식을 @value로 변경, 필드 추가
.../global/config/SecurityConfig.java "/quizzes/**" 경로 permitAll 규칙 추가
.../userQuizAnswer/service/UserQuizAnswerServiceTest.java UserQuizAnswerService 단위 테스트 신규 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserQuizAnswerController
    participant UserQuizAnswerService
    participant SubscriptionRepository
    participant UserRepository
    participant QuizRepository
    participant UserQuizAnswerRepository

    Client->>UserQuizAnswerController: POST /quizzes/{quizId}/answers (answerSubmit)
    UserQuizAnswerController->>UserQuizAnswerService: answerSubmit(quizId, requestDto)
    UserQuizAnswerService->>SubscriptionRepository: findById(subscriptionId)
    SubscriptionRepository-->>UserQuizAnswerService: Subscription or Exception
    UserQuizAnswerService->>UserRepository: findBySubscription(subscription)
    UserRepository-->>UserQuizAnswerService: User
    UserQuizAnswerService->>QuizRepository: findById(quizId)
    QuizRepository-->>UserQuizAnswerService: Quiz or Exception
    UserQuizAnswerService->>UserQuizAnswerRepository: save(UserQuizAnswer)
    UserQuizAnswerRepository-->>UserQuizAnswerService: UserQuizAnswer
    UserQuizAnswerService-->>UserQuizAnswerController: void
    UserQuizAnswerController-->>Client: ApiResponse<String>("정답이 제출되었습니다.")
Loading

Poem

🐰
새로운 퀴즈 답변길, 토끼가 달려와
정답을 담아, 서비스에 저장해
예외도 꼼꼼히, 테스트도 빼곡히
빌더와 보안, 설정까지 다듬었네
오늘도 코드밭에 푸른 당근이 한가득!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
src/main/java/com/example/cs25/domain/userQuizAnswer/requestDto/UserQuizAnswerRequestDto.java (1)

7-17: 유효성 검증 어노테이션 추가 권장

DTO 구현은 표준적이지만, 요청 데이터의 무결성을 위해 유효성 검증 어노테이션을 추가하는 것을 권장합니다.

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;

 @Getter
 public class UserQuizAnswerRequestDto {

+    @NotBlank(message = "답안은 필수입니다")
     private final String answer;
+    @NotNull(message = "구독 ID는 필수입니다")
     private final Long subscriptionId;
src/main/java/com/example/cs25/domain/userQuizAnswer/controller/UserQuizAnswerController.java (1)

21-21: 응답 메시지 상수화 권장

하드코딩된 응답 메시지를 상수로 분리하여 유지보수성을 향상시키는 것을 권장합니다.

+    private static final String ANSWER_SUBMIT_SUCCESS_MESSAGE = "답안이 제출 되었습니다.";
+
-        return new ApiResponse<>(200, "답안이 제출 되었습니다.");
+        return new ApiResponse<>(200, ANSWER_SUBMIT_SUCCESS_MESSAGE);
src/test/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerServiceTest.java (3)

3-28: 불필요한 import 정리 필요

테스트에서 사용하지 않는 import들이 있습니다. 코드 가독성을 위해 정리가 필요합니다.

다음 import들은 현재 테스트에서 사용되지 않습니다:

  • SocialType (line 3)
  • SubscriptionRequest (line 9)
  • SubscriptionService (line 15)
-import com.example.cs25.domain.oauth2.dto.SocialType;
-import com.example.cs25.domain.subscription.dto.SubscriptionRequest;
-import com.example.cs25.domain.subscription.service.SubscriptionService;

100-146: 테스트 커버리지 확장 권장

현재 테스트는 기본적인 시나리오를 다루고 있지만, 다음과 같은 추가 테스트 케이스를 고려해보세요:

  1. 틀린 답안 제출 테스트:
@Test
void answerSubmit_틀린답안_isCorrectFalse() {
    // given
    UserQuizAnswerRequestDto wrongRequestDto = UserQuizAnswerRequestDto.builder()
            .subscriptionId(subscriptionId)
            .answer("2")  // 정답은 "1"
            .build();
    
    when(subscriptionRepository.findById(subscriptionId)).thenReturn(Optional.of(subscription));
    when(userRepository.findBySubscription(subscription)).thenReturn(user);
    when(quizRepository.findById(quizId)).thenReturn(Optional.of(quiz));

    ArgumentCaptor<UserQuizAnswer> captor = ArgumentCaptor.forClass(UserQuizAnswer.class);

    // when
    userQuizAnswerService.answerSubmit(quizId, wrongRequestDto);

    // then
    verify(userQuizAnswerRepository).save(captor.capture());
    UserQuizAnswer saved = captor.getValue();
    assertThat(saved.getIsCorrect()).isFalse();
}
  1. 사용자 조회 실패 테스트 (서비스에서 null 체크가 추가되면):
@Test
void answerSubmit_사용자없음_예외() {
    // given
    when(subscriptionRepository.findById(subscriptionId)).thenReturn(Optional.of(subscription));
    when(userRepository.findBySubscription(subscription)).thenReturn(null);

    // when & then
    assertThatThrownBy(() -> userQuizAnswerService.answerSubmit(quizId, requestDto))
            .isInstanceOf(UserException.class);
}

66-66: 테스트 데이터의 오타 수정

카테고리 타입에 오타가 있습니다.

-                .categoryType("BECKEND")
+                .categoryType("BACKEND")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c519b6 and 8329747.

📒 Files selected for processing (11)
  • src/main/java/com/example/cs25/domain/quiz/entity/QuizCategory.java (2 hunks)
  • src/main/java/com/example/cs25/domain/quiz/exception/QuizExceptionCode.java (1 hunks)
  • src/main/java/com/example/cs25/domain/subscription/dto/SubscriptionRequest.java (2 hunks)
  • src/main/java/com/example/cs25/domain/subscription/repository/SubscriptionRepository.java (0 hunks)
  • src/main/java/com/example/cs25/domain/userQuizAnswer/controller/UserQuizAnswerController.java (1 hunks)
  • src/main/java/com/example/cs25/domain/userQuizAnswer/requestDto/UserQuizAnswerRequestDto.java (1 hunks)
  • src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java (1 hunks)
  • src/main/java/com/example/cs25/domain/users/repository/UserRepository.java (2 hunks)
  • src/main/java/com/example/cs25/global/config/AiConfig.java (1 hunks)
  • src/main/java/com/example/cs25/global/config/SecurityConfig.java (1 hunks)
  • src/test/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerServiceTest.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/example/cs25/domain/subscription/repository/SubscriptionRepository.java
🧰 Additional context used
🧠 Learnings (1)
src/main/java/com/example/cs25/domain/subscription/dto/SubscriptionRequest.java (1)
Learnt from: wannabeing
PR: NBC-finalProject/CS25#33
File: src/main/java/com/example/cs25/domain/subscription/service/SubscriptionService.java:0-0
Timestamp: 2025-06-04T03:57:51.848Z
Learning: SubscriptionPeriod enum에 NO_PERIOD(0) 속성을 추가하여 구독 기간 연장이 없는 경우를 명시적으로 표현하도록 개선되었습니다. SubscriptionRequest의 period 필드는 @NotNull로 설정되어 null 값을 허용하지 않습니다.
🧬 Code Graph Analysis (3)
src/main/java/com/example/cs25/domain/quiz/entity/QuizCategory.java (2)
src/main/java/com/example/cs25/global/dto/AuthUser.java (1)
  • Builder (17-43)
src/main/java/com/example/cs25/domain/users/dto/UserProfileResponse.java (1)
  • Builder (10-21)
src/main/java/com/example/cs25/domain/subscription/dto/SubscriptionRequest.java (2)
src/main/java/com/example/cs25/global/dto/AuthUser.java (1)
  • Builder (17-43)
src/main/java/com/example/cs25/domain/users/dto/UserProfileResponse.java (1)
  • Builder (10-21)
src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java (2)
src/main/java/com/example/cs25/domain/quiz/service/QuizService.java (1)
  • Service (24-70)
src/main/java/com/example/cs25/domain/users/service/UserService.java (1)
  • Service (19-63)
🔇 Additional comments (5)
src/main/java/com/example/cs25/domain/quiz/entity/QuizCategory.java (1)

9-9: 빌더 패턴 추가가 적절합니다.

Lombok @Builder 어노테이션과 생성자 추가로 객체 생성이 더욱 편리해졌습니다. 코드베이스의 다른 DTO들과 일관된 패턴을 유지하고 있어 좋습니다.

Also applies to: 25-28

src/main/java/com/example/cs25/domain/quiz/exception/QuizExceptionCode.java (1)

11-11: 에러 메시지 수정이 적절합니다.

"해당 이벤트를 찾을 수 없습니다"에서 "해당 퀴즈를 찾을 수 없습니다"로 변경하여 퀴즈 도메인에 맞는 정확한 메시지로 개선되었습니다.

src/main/java/com/example/cs25/domain/subscription/dto/SubscriptionRequest.java (1)

11-11: 빌더 패턴 구현이 잘 되었습니다.

Lombok @Builder 어노테이션과 모든 필드를 초기화하는 생성자가 추가되어 객체 생성이 더욱 편리해졌습니다. 기존의 검증 어노테이션들도 그대로 유지되어 데이터 무결성이 보장됩니다.

Also applies to: 35-42

src/main/java/com/example/cs25/global/config/AiConfig.java (1)

15-16: Spring 프로퍼티 주입 방식으로 개선됨

직접 환경변수 접근(System.getenv()) 대신 @Value 어노테이션을 사용하여 Spring의 프로퍼티 주입 방식으로 변경한 것은 좋은 개선입니다. 이는 테스트 가능성과 설정 관리를 향상시킵니다.

src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java (1)

41-41: 정답 비교 로직의 비즈니스 로직 검토 필요

현재 구현은 퀴즈 답안의 첫 번째 글자만 비교합니다. 이것이 의도된 비즈니스 로직인지 확인이 필요합니다. 일반적으로는 전체 답안을 비교하거나 더 정교한 비교 로직이 필요할 수 있습니다.

퀴즈 답안 형식과 비교 로직이 올바른지 확인해주세요. 예를 들어:

  • 객관식 문제라면 선택지 번호(1, 2, 3, 4) 비교가 맞는지
  • 답안 형식이 "1. Programming Language" 같은 형태라면 첫 글자만 비교하는 것이 적절한지

.requestMatchers("/subscription/**").permitAll()
.requestMatchers("/emails/**").permitAll()
.requestMatchers("/accuracyTest/**").permitAll()
.requestMatchers("/quizzes/**").permitAll()
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

퀴즈 엔드포인트 보안 설정 검증이 필요합니다.

/quizzes/** 경로에 대해 인증 없는 접근을 허용하고 있습니다. 퀴즈 답안 제출과 같은 민감한 기능이 포함될 수 있으므로, 이 설정이 의도된 것인지 확인해 주세요.

다음 스크립트를 실행하여 퀴즈 관련 컨트롤러의 엔드포인트를 확인하고 보안 요구사항을 검토해 주세요:


🏁 Script executed:

#!/bin/bash
# 퀴즈 관련 컨트롤러와 엔드포인트 확인
rg -A 10 "@RequestMapping.*quiz" --type java
rg -A 5 "@PostMapping\|@GetMapping\|@PutMapping\|@DeleteMapping" src/main/java/com/example/cs25/domain/userQuizAnswer/controller/ --type java

Length of output: 3863


퀴즈 엔드포인트 보안 설정 재검토 및 권한 지정 필요
/quizzes/** 경로에 .permitAll() 설정으로 인해 모든 퀴즈 기능이 인증 없이 노출됩니다. 민감 기능에 대해 아래와 같이 인증/권한 검증을 적용해주세요:

  • POST /quizzes/upload (퀴즈 파일 업로드): 관리자(ROLE_ADMIN) 권한 필요
  • POST /quizzes/{quizId} (퀴즈 답안 제출): 로그인된 사용자만 허용
  • GET /quizzes/{quizId}/feedback (AI 피드백 조회): 로그인된 사용자만 허용

SecurityConfig.java 예시 수정:

- .requestMatchers("/quizzes/**").permitAll()
+ .requestMatchers(HttpMethod.GET,  "/quizzes/**").permitAll()
+ .requestMatchers(HttpMethod.POST, "/quizzes/upload").hasRole("ADMIN")
+ .requestMatchers(HttpMethod.POST, "/quizzes/{quizId}").authenticated()
+ .requestMatchers(HttpMethod.GET,  "/quizzes/{quizId}/feedback").authenticated()

위 설정이 의도대로 반영되었는지 확인 후 반영 부탁드립니다.

📝 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
.requestMatchers("/quizzes/**").permitAll()
.requestMatchers(HttpMethod.GET, "/quizzes/**").permitAll()
.requestMatchers(HttpMethod.POST, "/quizzes/upload").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/quizzes/{quizId}").authenticated()
.requestMatchers(HttpMethod.GET, "/quizzes/{quizId}/feedback").authenticated()
🤖 Prompt for AI Agents
In src/main/java/com/example/cs25/global/config/SecurityConfig.java at line 54,
the current security configuration permits all access to the /quizzes/**
endpoint without authentication, which exposes sensitive quiz functionalities.
Update the security settings to restrict access as follows: require ROLE_ADMIN
for POST requests to /quizzes/upload, require authenticated users for POST
requests to /quizzes/{quizId} and GET requests to /quizzes/{quizId}/feedback.
Modify the requestMatchers configuration to enforce these role and
authentication requirements accordingly.

});
}

User findBySubscription(Subscription subscription);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메서드 반환 타입을 Optional로 변경하는 것을 권장합니다.

현재 User를 직접 반환하고 있지만, 해당 구독에 연결된 사용자가 없을 경우 null을 반환할 수 있습니다. 널 안전성을 위해 Optional<User>를 반환하는 것이 좋겠습니다.

다음과 같이 수정하는 것을 권장합니다:

-    User findBySubscription(Subscription subscription);
+    Optional<User> findBySubscription(Subscription subscription);
🤖 Prompt for AI Agents
In src/main/java/com/example/cs25/domain/users/repository/UserRepository.java at
line 23, change the method return type from User to Optional<User> to handle
cases where no user is found for the given subscription. Update the method
signature to return Optional<User> instead of User to improve null safety and
avoid returning null directly.

Comment on lines +16 to +17
@PostMapping("/{quizId}")
public ApiResponse<String> answerSubmit(@PathVariable Long quizId, @RequestBody UserQuizAnswerRequestDto requestDto){
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

엔드포인트 경로 명확성 개선 및 유효성 검증 추가

현재 /{quizId} 경로는 목적이 명확하지 않습니다. 더 명확한 경로와 요청 검증을 추가하는 것을 권장합니다.

-    @PostMapping("/{quizId}")
-    public ApiResponse<String> answerSubmit(@PathVariable Long quizId, @RequestBody UserQuizAnswerRequestDto requestDto){
+    @PostMapping("/{quizId}/answer")
+    public ApiResponse<String> answerSubmit(@PathVariable Long quizId, @Valid @RequestBody UserQuizAnswerRequestDto requestDto){

그리고 컨트롤러에 다음 import를 추가해야 합니다:

+import jakarta.validation.Valid;
📝 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
@PostMapping("/{quizId}")
public ApiResponse<String> answerSubmit(@PathVariable Long quizId, @RequestBody UserQuizAnswerRequestDto requestDto){
// Add this alongside your other imports
import jakarta.validation.Valid;
@PostMapping("/{quizId}/answer")
public ApiResponse<String> answerSubmit(@PathVariable Long quizId,
@Valid @RequestBody UserQuizAnswerRequestDto requestDto) {
// … existing implementation …
}
🤖 Prompt for AI Agents
In
src/main/java/com/example/cs25/domain/userQuizAnswer/controller/UserQuizAnswerController.java
around lines 16 to 17, the endpoint path "/{quizId}" is not descriptive enough.
Change the path to a more explicit one that clearly indicates the action, such
as "/submit/{quizId}". Additionally, add validation annotations to the
UserQuizAnswerRequestDto parameter to enforce request data integrity. Also,
ensure to import the necessary validation packages like javax.validation.Valid
and annotate the requestDto parameter with @Valid.

Quiz quiz = quizRepository.findById(quizId).orElseThrow(() -> new QuizException(QuizExceptionCode.NOT_FOUND_ERROR));

// 정답 체크
boolean isCorrect = requestDto.getAnswer().equals(quiz.getAnswer().substring(0,1));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

정답 비교 로직의 안전성 문제

quiz.getAnswer().substring(0,1) 코드는 답안이 빈 문자열일 경우 StringIndexOutOfBoundsException을 발생시킬 수 있습니다.

     // 정답 체크
-    boolean isCorrect = requestDto.getAnswer().equals(quiz.getAnswer().substring(0,1));
+    String correctAnswer = quiz.getAnswer();
+    boolean isCorrect = !correctAnswer.isEmpty() && 
+                       requestDto.getAnswer().equals(correctAnswer.substring(0,1));
📝 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 isCorrect = requestDto.getAnswer().equals(quiz.getAnswer().substring(0,1));
// 정답 체크
String correctAnswer = quiz.getAnswer();
boolean isCorrect = !correctAnswer.isEmpty() &&
requestDto.getAnswer().equals(correctAnswer.substring(0,1));
🤖 Prompt for AI Agents
In
src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java
at line 41, the code uses quiz.getAnswer().substring(0,1) without checking if
the answer string is empty, which can cause StringIndexOutOfBoundsException. To
fix this, add a check to ensure quiz.getAnswer() is not null or empty before
calling substring, or use a safer method to get the first character, such as
checking length or using charAt with proper validation.

Comment on lines +34 to +35
// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

사용자 조회 시 null 체크 누락

userRepository.findBySubscription(subscription) 메서드가 null을 반환할 수 있는데 null 체크가 없습니다. 이는 NullPointerException을 발생시킬 수 있습니다.

     // 유저 정보 조회
-    User user = userRepository.findBySubscription(subscription);
+    User user = userRepository.findBySubscription(subscription);
+    if (user == null) {
+        throw new UserException(UserExceptionCode.NOT_FOUND_USER);
+    }

또는 UserRepository에서 Optional<User> findBySubscription(Subscription subscription) 메서드로 변경하는 것을 고려해보세요.

📝 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
// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);
// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);
if (user == null) {
throw new UserException(UserExceptionCode.NOT_FOUND_USER);
}
🤖 Prompt for AI Agents
In
src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java
around lines 34 to 35, the call to
userRepository.findBySubscription(subscription) can return null but there is no
null check, which may cause a NullPointerException. To fix this, add a null
check after retrieving the user and handle the null case appropriately, such as
throwing a custom exception or returning an error response. Alternatively,
modify the UserRepository method to return Optional<User> and update the service
code to handle the Optional properly by checking if a value is present before
accessing it.

Comment on lines +28 to +52
public void answerSubmit(Long quizId, UserQuizAnswerRequestDto requestDto) {

// 구독 정보 조회
Subscription subscription = subscriptionRepository.findById(requestDto.getSubscriptionId())
.orElseThrow(() -> new SubscriptionException(SubscriptionExceptionCode.NOT_FOUND_SUBSCRIPTION_ERROR));

// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);

// 퀴즈 조회
Quiz quiz = quizRepository.findById(quizId).orElseThrow(() -> new QuizException(QuizExceptionCode.NOT_FOUND_ERROR));

// 정답 체크
boolean isCorrect = requestDto.getAnswer().equals(quiz.getAnswer().substring(0,1));

userQuizAnswerRepository.save(
UserQuizAnswer.builder()
.userAnswer(requestDto.getAnswer())
.isCorrect(isCorrect)
.user(user)
.quiz(quiz)
.subscription(subscription)
.build()
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

트랜잭션 어노테이션 추가 필요

데이터베이스에 저장하는 메서드이므로 @Transactional 어노테이션을 추가해야 합니다.

+    @Transactional
     public void answerSubmit(Long quizId, UserQuizAnswerRequestDto requestDto) {

그리고 import를 추가해야 합니다:

+import org.springframework.transaction.annotation.Transactional;
📝 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
public void answerSubmit(Long quizId, UserQuizAnswerRequestDto requestDto) {
// 구독 정보 조회
Subscription subscription = subscriptionRepository.findById(requestDto.getSubscriptionId())
.orElseThrow(() -> new SubscriptionException(SubscriptionExceptionCode.NOT_FOUND_SUBSCRIPTION_ERROR));
// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);
// 퀴즈 조회
Quiz quiz = quizRepository.findById(quizId).orElseThrow(() -> new QuizException(QuizExceptionCode.NOT_FOUND_ERROR));
// 정답 체크
boolean isCorrect = requestDto.getAnswer().equals(quiz.getAnswer().substring(0,1));
userQuizAnswerRepository.save(
UserQuizAnswer.builder()
.userAnswer(requestDto.getAnswer())
.isCorrect(isCorrect)
.user(user)
.quiz(quiz)
.subscription(subscription)
.build()
);
}
import org.springframework.transaction.annotation.Transactional;
@Transactional
public void answerSubmit(Long quizId, UserQuizAnswerRequestDto requestDto) {
// 구독 정보 조회
Subscription subscription = subscriptionRepository.findById(requestDto.getSubscriptionId())
.orElseThrow(() -> new SubscriptionException(SubscriptionExceptionCode.NOT_FOUND_SUBSCRIPTION_ERROR));
// 유저 정보 조회
User user = userRepository.findBySubscription(subscription);
// 퀴즈 조회
Quiz quiz = quizRepository.findById(quizId)
.orElseThrow(() -> new QuizException(QuizExceptionCode.NOT_FOUND_ERROR));
// 정답 체크
boolean isCorrect = requestDto.getAnswer()
.equals(quiz.getAnswer().substring(0, 1));
userQuizAnswerRepository.save(
UserQuizAnswer.builder()
.userAnswer(requestDto.getAnswer())
.isCorrect(isCorrect)
.user(user)
.quiz(quiz)
.subscription(subscription)
.build()
);
}
🤖 Prompt for AI Agents
In
src/main/java/com/example/cs25/domain/userQuizAnswer/service/UserQuizAnswerService.java
around lines 28 to 52, the answerSubmit method performs database save operations
but lacks the @Transactional annotation. Add the @Transactional annotation above
the method to ensure the database operations are executed within a transaction.
Also, import the necessary javax.transaction.Transactional or
org.springframework.transaction.annotation.Transactional package depending on
the project setup.

@jong-0126 jong-0126 merged commit 89994a4 into dev Jun 10, 2025
1 check passed
@jong-0126 jong-0126 deleted the feat/51 branch June 10, 2025 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

답안 채점 기능 구현

4 participants