Skip to content

[Feat] 직접 추가 문항 후보 API 구현#72

Merged
whc9999 merged 3 commits into
mainfrom
feat/#39-question-custom
May 22, 2026
Merged

[Feat] 직접 추가 문항 후보 API 구현#72
whc9999 merged 3 commits into
mainfrom
feat/#39-question-custom

Conversation

@whc9999
Copy link
Copy Markdown
Collaborator

@whc9999 whc9999 commented May 22, 2026

✨ 어떤 이유로 PR를 하셨나요?

  • feature 병합
  • 버그 수정(아래에 issue #를 남겨주세요)
  • 코드 개선
  • 코드 수정
  • 배포
  • 기타(아래에 자세한 내용 기입해주세요)

📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요

  • 직접 추가 문항 후보 생성 API 추가
  • 문항 후보 응답에 custom 필드 추가
  • 직접 추가 문항을 선택 확정 전까지 후보로만 관리
  • 후보 목록 조회 시 기본 문항과 직접 추가 문항 함께 반환
  • 직접 추가 문항 후보 저장 및 조회 테스트 추가

📸 작업 화면 스크린샷

⚠️ PR하기 전에 확인해주세요

  • 로컬테스트를 진행하셨나요?
  • 머지할 브랜치를 확인하셨나요?
  • 관련 label을 선택하셨나요?

🚨 관련 이슈 번호 [#39 ]

Summary by CodeRabbit

  • New Features

    • Users can add custom question candidates to their mock interview applies.
    • Custom and default questions are shown together; custom entries are marked as such and selection state is preserved.
  • Bug Fixes

    • Prevents adding custom questions that duplicate built-in defaults; repeated adds return the existing custom entry.
  • Tests

    • Service tests added/updated to cover adding, rejecting, and deduplicating custom candidates.

Review Change Stack

- 직접 추가 문항 후보 생성 API 추가
- 문항 후보 응답에 custom 필드 추가
- 직접 추가 문항을 선택 확정 전까지 후보로만 관리
- 후보 목록 조회 시 기본 문항과 직접 추가 문항 함께 반환
- 직접 추가 문항 후보 저장 및 조회 테스트 추가
@whc9999 whc9999 self-assigned this May 22, 2026
@whc9999 whc9999 added ✨ feat New feature or request fix labels May 22, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3091a801-f641-45c3-8fb1-be961b429ae3

📥 Commits

Reviewing files that changed from the base of the PR and between c4161dc and 82e609b.

📒 Files selected for processing (3)
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/repository/QuestionRepository.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java

📝 Walkthrough

Walkthrough

Adds support for user-created custom question candidates: new JPA entity and repository, DTO and response changes, service logic to merge/default-validate/upsert custom candidates, a POST controller endpoint, and tests covering creation, duplicate handling, and response flags.

Changes

Custom Question Candidates Feature

Layer / File(s) Summary
Data Model & Persistence Contracts
src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java, src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/request/QuestionCandidateCreateRequest.java, src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionCandidateResponse.java, src/main/java/com/jobdri/jobdri_api/domain/analysis/repository/CustomQuestionCandidateRepository.java, src/main/java/com/jobdri/jobdri_api/domain/analysis/repository/QuestionRepository.java
CustomQuestionCandidate JPA entity mapped to custom_question_candidates with unique (mock_apply_id, content). QuestionCandidateCreateRequest record validates content and charLimit. QuestionCandidateResponse adds custom (and retains selected). Repository adds methods to list by mockApplyId, find by (mockApplyId, content), and check existence of a selected Question by (mockApplyId, content).
Service: Merge & Validation Logic
src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java
getQuestionCandidates merges default and custom candidates producing selection and custom flags. addCustomQuestionCandidate trims and validates content (rejects default duplicates), upserts a CustomQuestionCandidate via repository lookup with DataIntegrityViolationException recovery, and returns a populated QuestionCandidateResponse. validateCustomCandidate checks against DEFAULT_CANDIDATES.
HTTP POST Endpoint
src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/QuestionController.java
New POST /api/mock-applies/{mockApplyId}/questions/candidates endpoint validates QuestionCandidateCreateRequest, requires authentication, delegates to service, and returns ApiResponse<QuestionCandidateResponse>.
Service Tests & Coverage
src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java
Tests added for addCustomQuestionCandidate: verifies custom responses (selected==false/true depending on existing selected question, custom==true), rejects custom content matching defaults with GeneralException(INVALID_PARAMETER), ensures duplicate custom requests return existing candidate, and extends candidate-list test to assert custom == false for default candidates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

In my burrow I stack questions neat and small,
Defaults first, then custom whispers one and all,
A POST from a user, a save that skips a fight,
Repos keep the record, services set the right,
The rabbit hops off cheerful — candidates in sight! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Feat] 직접 추가 문항 후보 API 구현' clearly and specifically summarizes the main change: implementing an API for custom question candidates.
Description check ✅ Passed The pull request description covers all required template sections with the feature type selected, detailed work description in Korean, local testing and branch confirmation checked, and related issue #39 specified.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#39-question-custom

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (1)
src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java (1)

114-137: ⚡ Quick win

Add regression tests for duplicate-content edge paths.

Please add tests for (1) rejecting content equal to a default candidate and (2) duplicate custom content requests returning the same candidate instead of failing. Those are key behavior contracts for this feature.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java`
around lines 114 - 137, Add two tests in QuestionServiceTest that cover the
requested regressions: (1) a test that calls
questionService.addCustomQuestionCandidate(...) with content identical to an
existing default candidate and asserts the request is rejected (e.g., throws or
returns a validation error) and that no new candidate is created in
questionRepository.findAllByMockApplyId(...); (2) a test that calls
questionService.addCustomQuestionCandidate(...) twice with the same custom
content and asserts the second call returns the same QuestionCandidateResponse
(same questionId and properties) and does not create a duplicate in
questionRepository.findAllByMockApplyId(...); use the existing helpers saveUser,
saveMockApply and questionService.getQuestionCandidates(...) to verify candidate
lists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java`:
- Around line 93-99: The current pattern using
customQuestionCandidateRepository.findByMockApplyIdAndContent(...).orElseGet(()
-> customQuestionCandidateRepository.save(CustomQuestionCandidate.create(...)))
is race-prone and can throw a unique-constraint error under concurrent requests;
fix by performing a safe upsert or by catching the persistence exception on save
(e.g., DataIntegrityViolationException/ConstraintViolationException) inside the
method that creates CustomQuestionCandidate, then re-query with
customQuestionCandidateRepository.findByMockApplyIdAndContent(...) to return the
existing record; alternatively use a DB-native upsert if supported by the
repository/ORM, ensuring the method that calls
CustomQuestionCandidate.create(...) and
customQuestionCandidateRepository.save(...) is idempotent and returns the
existing entity on conflict.

---

Nitpick comments:
In
`@src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java`:
- Around line 114-137: Add two tests in QuestionServiceTest that cover the
requested regressions: (1) a test that calls
questionService.addCustomQuestionCandidate(...) with content identical to an
existing default candidate and asserts the request is rejected (e.g., throws or
returns a validation error) and that no new candidate is created in
questionRepository.findAllByMockApplyId(...); (2) a test that calls
questionService.addCustomQuestionCandidate(...) twice with the same custom
content and asserts the second call returns the same QuestionCandidateResponse
(same questionId and properties) and does not create a duplicate in
questionRepository.findAllByMockApplyId(...); use the existing helpers saveUser,
saveMockApply and questionService.getQuestionCandidates(...) to verify candidate
lists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40b5c47c-6cfb-4a16-8641-e30a4dac7544

📥 Commits

Reviewing files that changed from the base of the PR and between 3f25dfe and 798cbae.

📒 Files selected for processing (7)
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/QuestionController.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/request/QuestionCandidateCreateRequest.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionCandidateResponse.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/repository/CustomQuestionCandidateRepository.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java

Comment thread src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java Outdated
- 직접 추가 문항 후보 저장 시 unique 충돌 재조회 처리
- 동시 중복 요청에서도 기존 후보를 반환하도록 보완
- 기본 문항과 동일한 직접 추가 후보 등록 차단 테스트 추가
- 중복 custom 문항 후보 요청 idempotency 테스트 추가
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java (1)

96-101: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

selected 응답값이 기존 선택 상태와 불일치할 수 있습니다.

중복 요청으로 기존 후보를 반환하는 경우에도 selectedfalse로 고정해서 반환하고 있어, 이미 선택된 문항이면 GET /candidates 결과와 응답이 달라집니다. 선택 여부를 실제 저장 상태 기준으로 계산해 반환해 주세요.

💡 Suggested patch
     public QuestionCandidateResponse addCustomQuestionCandidate(
             User user,
             Long mockApplyId,
             QuestionCandidateCreateRequest request
     ) {
         MockApply mockApply = getOwnedMockApply(user, mockApplyId);
         String content = request.content().trim();
         validateCustomCandidate(content);

         CustomQuestionCandidate candidate = findOrCreateCustomCandidate(mockApply, content, request.charLimit());
+        boolean selected = questionRepository.findAllByMockApplyId(mockApply.getId()).stream()
+                .anyMatch(question -> question.getContent().equals(candidate.getContent()));

         return new QuestionCandidateResponse(
                 candidate.getId(),
                 candidate.getContent(),
                 candidate.getLimit(),
-                false,
+                selected,
                 true
         );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java`
around lines 96 - 101, The response always hardcodes the selected flag to false
when building a QuestionCandidateResponse in QuestionService, which can mismatch
persisted state on duplicate requests; change the constructor call to compute
selected from the actual stored state (e.g., use candidate.isSelected() or query
the repository/aggregate that represents selection) and pass that boolean into
new QuestionCandidateResponse(candidate.getId(), candidate.getContent(),
candidate.getLimit(), /*selected=*/ computedSelected, true) so the returned
selected reflects the true saved selection state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java`:
- Around line 96-101: The response always hardcodes the selected flag to false
when building a QuestionCandidateResponse in QuestionService, which can mismatch
persisted state on duplicate requests; change the constructor call to compute
selected from the actual stored state (e.g., use candidate.isSelected() or query
the repository/aggregate that represents selection) and pass that boolean into
new QuestionCandidateResponse(candidate.getId(), candidate.getContent(),
candidate.getLimit(), /*selected=*/ computedSelected, true) so the returned
selected reflects the true saved selection state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1b0e93a-70b5-4266-897e-4cd5268ab0f2

📥 Commits

Reviewing files that changed from the base of the PR and between 798cbae and c4161dc.

📒 Files selected for processing (2)
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionServiceTest.java

@whc9999 whc9999 merged commit adf7cc8 into main May 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat New feature or request fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant