Conversation
- semesterYear : semester에 사용될 연도 - semesterTerm : 1학기, 여름계절학기, 2학기, 겨울계절학기
현재 날짜 기준으로 default로 3개 조회 ex) 2025-SECOND, 2025-WINTER, 2026-FIRST
- semesterOption을 semesterOptionResponse로
1. 생성 : service에서 getClubQuestion 사용을 제거하여 한 클럽 내 다중 지원서 생성을 허용하고, semester 값 반영 2. 수정 : clubQuestionId를 경로변수로 받아서 특정 지원서만 수정되도록 변경
기존 코드에 있던 클럽 지원 시, questionId 빌드를 임시로 clcubId로 해둔 거 같은데, 이를 수정함
학기 별로 분류, 최신순 정렬
- aggregation 사용 x - 성능비교를 위해 둘 다 구현 - 파라미터 값 agg/sever 로 구분
[refactor] MOA-228 백엔드 비즈니스 로직 리팩토링
- 지원서 양식(괸리자) : ClubApplicationForm - 지원자 또는 지원서(사용자) : ClubApplicant
지원서 양식 생성에서 허용 범위가 아닌 학기 요청 검증
GET /semesters 로그인 인증 구현 및 isExist 활성화
…-selection-MOA-231 [feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다
…on-semester-selection-MOA-231 Revert "[feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다"
[feature] 동아리 검색 결과에서 분과 순서가 고정된 것에서 분과 순서를 랜덤으로 바꾼다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Club metrics 제거 및 참조 정리backend/src/main/java/moadong/club/controller/ClubMetricController.java, backend/src/main/java/moadong/club/service/ClubMetricService.java, backend/src/main/java/moadong/club/entity/ClubMetric.java, backend/src/main/java/moadong/club/repository/ClubMetricRepository.java, backend/src/main/java/moadong/club/controller/ClubProfileController.java, backend/src/test/java/moadong/club/service/ClubMetricServiceTest.java, backend/src/test/java/moadong/club/fixture/MetricFixture.java |
메트릭스 컨트롤러/서비스/엔티티/리포지토리 및 테스트/픽스처 삭제. ClubProfileController에서 ClubMetricService 연동 제거 및 메서드 시그니처 단순화. |
미디어 스택(GDrive/GCS) 제거 및 빌드/보안 설정 정리backend/src/main/java/moadong/media/service/GcsClubImageService.java, .../media/service/GoogleDriveClubImageService.java, .../media/util/GoogleDriveConfig.java, backend/build.gradle, backend/src/main/java/moadong/global/config/SecurityConfig.java, backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java |
GCS/Google Drive 이미지 서비스 구현 및 설정 제거. Gradle에서 Google Drive 관련 의존성 삭제. SecurityConfig 불필요 임포트 정리. 관련 테스트 임포트 정리. |
검색 로직 변경backend/src/main/java/moadong/club/service/ClubSearchService.java, backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java |
모집상태 우선 정렬에 더해 카테고리 무작위 우선순위(셔플)와 이름을 보조 키로 사용하는 3단계 정렬 도입. 일부 테스트 비활성화(주석 처리). |
컨트롤러/서비스 임포트 정리backend/src/main/java/moadong/club/controller/ClubApplyController.java, .../club/controller/ClubSearchController.java, backend/src/main/java/moadong/user/controller/UserController.java, backend/src/main/java/moadong/club/service/ClubApplyService.java |
와일드카드 임포트를 명시 임포트로 교체 및 불필요 임포트 제거. 런타임 동작 변화 없음. |
엔티티/DTO/리포지토리 정리backend/src/main/java/moadong/club/entity/*(Club, ClubApplication, ClubApplicationQuestion, ClubQuestion), backend/src/main/java/moadong/club/payload/dto/*(ClubApplicantsResult, ClubDetailedResult, ClubSearchResult), backend/src/main/java/moadong/club/payload/request/*(ClubApplicantDeleteRequest, ClubApplicationCreateRequest, ClubApplicationEditRequest, ClubApplyQuestion, ClubApplyRequest, ClubCreateRequest) , backend/src/main/java/moadong/club/repository/*(ClubApplicationRepository, ClubQuestionRepository, ClubRepository, ClubSearchRepository) |
임포트 정리 및 중복 제거. ClubCreateRequest 레코드 삭제. 리포지토리 임포트 정리 및 와일드카드 해제. |
글로벌 어노테이션/밸리데이터 정리backend/src/main/java/moadong/global/annotation/*(Korean, Password, PhoneNumber, UserId), backend/src/main/java/moadong/global/validator/*(KoreanValidator, PasswordValidator, PhoneNumberValidator, UserIdValidator), backend/src/main/java/moadong/global/exception/ErrorCode.java, backend/src/main/java/moadong/global/util/AESCipher.java |
임포트 재배치/정리. 기능 변화 없음. |
유저 영역 포맷/임포트 정리backend/src/main/java/moadong/user/entity/*(RefreshToken, UserInformation), backend/src/main/java/moadong/user/payload/request/*(UserRegisterRequest, UserUpdateRequest), backend/src/main/java/moadong/user/payload/response/*(FindUserClubResponse, LoginResponse, RefreshResponse, TempPasswordResponse) |
불필요 임포트 제거 및 레코드 선언 포맷 단일 행으로 정리. 동작 변화 없음. |
테스트 인프라 및 기타 테스트 변경backend/src/test/java/moadong/util/annotations/*(IntegrationTest, UnitTest), backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java, backend/src/test/java/moadong/club/service/ClubProfileServiceTest.java, backend/src/test/java/moadong/unit/club/ClubProfileServiceTest.java, backend/src/test/java/moadong/unit/user/*(UserLoginTest, UserRegisterTest), backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java |
테스트 어노테이션에 @SpringBootTest/@tag, @ExtendWith(MockitoExtension) 추가. 일부 테스트 임포트/포맷 정리, 사용 시나리오 단순화 및 주석 정리. |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor Client
participant ClubProfileController as Controller
participant ClubProfileService as Service
rect rgb(245,248,255)
note over Client,Service: 기존 흐름 (삭제됨)
Client->>Controller: GET /api/club/{clubId}
Controller->>ClubMetricService: patch(clubId, requesterIP)
ClubMetricService-->>Controller: void
Controller->>Service: getClubDetail(clubId)
Service-->>Controller: ClubDetailedResult
Controller-->>Client: 200 OK (Response.ok)
end
rect rgb(242,255,245)
note over Client,Service: 변경 후
Client->>Controller: GET /api/club/{clubId}
Controller->>Service: getClubDetail(clubId)
Service-->>Controller: ClubDetailedResult
Controller-->>Client: 200 OK (Response.ok)
end
sequenceDiagram
autonumber
actor Client
participant ClubSearchController as Controller
participant ClubSearchService as Service
participant Repository as ClubRepository
Client->>Controller: GET /api/club/search?...
Controller->>Service: search(request)
Service->>Repository: findByCriteria(...)
Repository-->>Service: List<Club>
note over Service: 1) 모집상태 우선순위<br/>2) 무작위 카테고리 우선순위 생성/적용<br/>3) 이름 사전순
Service-->>Controller: ClubSearchResponse
Controller-->>Client: 200 OK (Response.ok)
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
- [refactor] MOA-228 백엔드 비즈니스 로직 리팩토링 #736 — 메트릭스 도메인 및 Google Drive/GCS 제거 등 동일한 코드 레벨 삭제/리팩터링과 직접적으로 일치
- [feature] 동아리 검색 결과에서 분과 순서가 고정된 것에서 분과 순서를 랜덤으로 바꾼다 #749 — ClubSearchService에서 카테고리 무작위 우선순위 정렬 도입과 동일 영역 변경
- [refactor] api 필드 분리 및 이름 통일 #224 — ClubMetricController에 보안/문서화 주석을 추가했던 변경과 본 PR의 컨트롤러 삭제가 동일 파일을 교차
Suggested labels
📈 release, 💾 BE, 📬 API, 🔨 Refactor
Suggested reviewers
- lepitaaar
- PororoAndFriends
- alsdddk
Pre-merge checks and finishing touches
❌ Failed checks (2 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Title Check | 현재 제목 “[Release] BE v1.0.8 배포”는 배포 목적만을 나타내고 실제 변경된 주요 기능인 검색 결과 모집 분과의 랜덤 정렬이나 메트릭 및 Google Drive 관련 기능 제거 등 코드베이스 상의 핵심 작업을 반영하지 못해 제목과 변경 내용이 불일치합니다. | 제목을 “검색 결과 모집 분과 순서를 랜덤으로 표시하도록 구현 및 메트릭/Google Drive 기능 제거”와 같이 주요 기능과 리팩토링 범위를 포함하도록 구체적으로 수정하세요. | |
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
✨ Finishing touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
develop/be
Tip
👮 Agentic pre-merge checks are now available in preview!
Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
- Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
- Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.
Please see the documentation for more information.
Example:
reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java (1)
77-79: CyclicBarrier 무한 대기 가능성 — 타임아웃 추가 필요한 스레드라도
barrier.await()이전에 예외로 빠지면 나머지 스레드가 영원히 대기하여 테스트가 hang될 수 있습니다. 타임아웃을 주고TimeoutException을 처리하세요.적용 제안:
- barrier.await(); + barrier.await(10, TimeUnit.SECONDS);필요한 import (아래 import 블록에도 추가 제안 포함):
+import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException;backend/src/main/java/moadong/club/entity/ClubQuestion.java (1)
56-59: NPE 가능성: newQuestions가 null이면 addAll에서 NPE호출자가 null을 넘기면 addAll(null)로 NPE가 발생합니다. 방어 코드 추가와 함께 editedAt 갱신까지 묶어주세요.
- public void updateQuestions(List<ClubApplicationQuestion> newQuestions) { - this.questions.clear(); - this.questions.addAll(newQuestions); - } + public void updateQuestions(List<ClubApplicationQuestion> newQuestions) { + this.questions.clear(); + if (newQuestions == null || newQuestions.isEmpty()) { + updateEditedAt(); + return; + } + this.questions.addAll(new ArrayList<>(newQuestions)); + updateEditedAt(); + }
🧹 Nitpick comments (9)
backend/src/main/java/moadong/user/payload/response/LoginResponse.java (1)
3-3: 콤마 뒤 공백 추가 제안콤마 뒤 공백이 없어 가독성과 코드 스타일 일관성이 떨어집니다. 공백을 하나 추가하는 편이 좋겠습니다.
-public record LoginResponse(String accessToken,String clubId) { +public record LoginResponse(String accessToken, String clubId) {backend/src/test/java/moadong/unit/user/UserLoginTest.java (1)
3-9: JUnit의assertTrue로 통일해주세요이 테스트 클래스는 이미 JUnit5
Assertions를 사용하고 있는데,assertTrue만com.mongodb.assertions.Assertions에 의존하고 있습니다. MongoDB 드라이버 내부 유틸을 테스트 검증에 끌어다 쓰면 향후 의존성 정리 시 불필요한 결합도가 생기고, 의도도 읽기 어렵습니다. JUnit의assertTrue로 맞춰 주세요.-import static com.mongodb.assertions.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue;backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java (5)
87-95: 충돌 판정 정밀도 개선 및 테스트 실패 처리현재는 모든
DataAccessException을 충돌로 집계합니다. 낙관적 락 실패만 충돌로 집계하고, 그 외 예외는 테스트 실패로 처리해야 오탐을 줄일 수 있습니다. 또한printStackTrace()대신fail()사용을 권장합니다.적용 제안:
- } catch (DataAccessException e) { - // DataAccessException은 WriteConflict 등을 포함 - conflictCount.incrementAndGet(); - } catch (InterruptedException | BrokenBarrierException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); + } catch (OptimisticLockingFailureException e) { + conflictCount.incrementAndGet(); + } catch (DataAccessException e) { + fail("예상치 못한 DataAccessException: " + e.getClass().getSimpleName()); + } catch (InterruptedException | BrokenBarrierException | TimeoutException e) { + Thread.currentThread().interrupt(); + fail("동기화 대기 중 예외 발생: " + e.getClass().getSimpleName()); + } catch (Exception e) { + fail("예상치 못한 예외: " + e); } finally { latch.countDown(); }추가 import:
+import static org.junit.jupiter.api.Assertions.fail; +import org.springframework.dao.OptimisticLockingFailureException;
101-102: 스레드 종료 보장
shutdown()후awaitTermination()로 스레드 종료를 확실히 보장하세요. 타임아웃은 테스트 환경에 맞게 조정하십시오.적용 제안:
latch.await(); executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS);
103-106: 합계 불변식 검증 추가예상치 못한 경로(예: 예외 처리 누락)로 인해 카운트 누락을 조기 탐지하기 위해 합계가 스레드 수와 일치하는지 검증을 추가하세요.
적용 제안:
assertEquals(1, successCount.get(), "성공한 요청은 1개여야 합니다."); assertEquals(numberOfThreads - 1, conflictCount.get(), "실패(충돌)한 요청은 " + (numberOfThreads - 1) + "개여야 합니다."); + assertEquals(numberOfThreads, successCount.get() + conflictCount.get(), "성공+실패 합은 스레드 수와 같아야 합니다.");
49-53: 테스트 데이터 전제조건 확인 필요여기서 문서가 없으면 한 스레드가
barrier.await()에 도달하지 않아 다른 스레드가 무기한 대기할 수 있습니다. 픽스처에서 해당ClubQuestion생성을 보장하거나, 없을 경우 생성/skip하도록 처리해 주세요.원하시면 픽스처/셋업 코드 보완 패치를 제안하겠습니다.
31-38: 서비스 메서드 경로로 테스트하거나, 미사용 필드 정리현재
clubApplyService를 주입만 하고 사용하지 않습니다. 실제 운영 경로 보증을 위해 서비스 메서드를 통해 갱신을 수행하거나, 이 테스트가 저장소 레벨 검증만 의도라면 미사용 필드를 정리하세요.backend/src/main/java/moadong/club/entity/ClubQuestion.java (2)
48-54: Setter 유효성 및 수정 시각 동기화@NotNull 제약과 일치하도록 null 입력을 방지하고, 필드 변경 시 editedAt을 함께 갱신하는 편이 안전합니다.
public void updateFormTitle(String title) { - this.title = title; + if (title == null) throw new IllegalArgumentException("title must not be null"); + this.title = title; + updateEditedAt(); } public void updateFormDescription(String description) { - this.description = description; + if (description == null) throw new IllegalArgumentException("description must not be null"); + this.description = description; + updateEditedAt(); }
3-3: MongoDB 문서 모델에는 JPA Id 대신 Spring Data Id를 사용하세요@document 엔티티에서는 org.springframework.data.annotation.Id 사용이 권장됩니다. jakarta.persistence.Id는 혼선을 유발하고, 도구/메타데이터 처리에서도 일관성이 떨어집니다.
- import jakarta.persistence.Id; + import org.springframework.data.annotation.Id;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (66)
backend/build.gradle(1 hunks)backend/src/main/java/moadong/club/controller/ClubApplyController.java(1 hunks)backend/src/main/java/moadong/club/controller/ClubMetricController.java(0 hunks)backend/src/main/java/moadong/club/controller/ClubProfileController.java(1 hunks)backend/src/main/java/moadong/club/controller/ClubSearchController.java(0 hunks)backend/src/main/java/moadong/club/entity/Club.java(1 hunks)backend/src/main/java/moadong/club/entity/ClubApplication.java(1 hunks)backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java(1 hunks)backend/src/main/java/moadong/club/entity/ClubMetric.java(0 hunks)backend/src/main/java/moadong/club/entity/ClubQuestion.java(1 hunks)backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java(1 hunks)backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java(0 hunks)backend/src/main/java/moadong/club/payload/dto/ClubSearchResult.java(1 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.java(0 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.java(0 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java(0 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplyQuestion.java(1 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplyRequest.java(0 hunks)backend/src/main/java/moadong/club/payload/request/ClubCreateRequest.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java(1 hunks)backend/src/main/java/moadong/club/repository/ClubMetricRepository.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubQuestionRepository.java(1 hunks)backend/src/main/java/moadong/club/repository/ClubRepository.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubSearchRepository.java(1 hunks)backend/src/main/java/moadong/club/service/ClubApplyService.java(1 hunks)backend/src/main/java/moadong/club/service/ClubMetricService.java(0 hunks)backend/src/main/java/moadong/club/service/ClubProfileService.java(1 hunks)backend/src/main/java/moadong/club/service/ClubSearchService.java(3 hunks)backend/src/main/java/moadong/club/service/RecruitmentStateChecker.java(1 hunks)backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java(1 hunks)backend/src/main/java/moadong/global/annotation/Korean.java(1 hunks)backend/src/main/java/moadong/global/annotation/Password.java(1 hunks)backend/src/main/java/moadong/global/annotation/PhoneNumber.java(1 hunks)backend/src/main/java/moadong/global/annotation/UserId.java(1 hunks)backend/src/main/java/moadong/global/config/SecurityConfig.java(0 hunks)backend/src/main/java/moadong/global/exception/ErrorCode.java(0 hunks)backend/src/main/java/moadong/global/util/AESCipher.java(1 hunks)backend/src/main/java/moadong/global/validator/KoreanValidator.java(1 hunks)backend/src/main/java/moadong/global/validator/PasswordValidator.java(1 hunks)backend/src/main/java/moadong/global/validator/PhoneNumberValidator.java(1 hunks)backend/src/main/java/moadong/global/validator/UserIdValidator.java(1 hunks)backend/src/main/java/moadong/media/service/GcsClubImageService.java(0 hunks)backend/src/main/java/moadong/media/service/GoogleDriveClubImageService.java(0 hunks)backend/src/main/java/moadong/media/util/GoogleDriveConfig.java(0 hunks)backend/src/main/java/moadong/user/controller/UserController.java(1 hunks)backend/src/main/java/moadong/user/entity/RefreshToken.java(1 hunks)backend/src/main/java/moadong/user/entity/UserInformation.java(0 hunks)backend/src/main/java/moadong/user/payload/request/UserRegisterRequest.java(0 hunks)backend/src/main/java/moadong/user/payload/request/UserUpdateRequest.java(0 hunks)backend/src/main/java/moadong/user/payload/response/FindUserClubResponse.java(1 hunks)backend/src/main/java/moadong/user/payload/response/LoginResponse.java(1 hunks)backend/src/main/java/moadong/user/payload/response/RefreshResponse.java(1 hunks)backend/src/main/java/moadong/user/payload/response/TempPasswordResponse.java(0 hunks)backend/src/main/java/moadong/user/repository/UserRepository.java(1 hunks)backend/src/test/java/moadong/club/fixture/MetricFixture.java(0 hunks)backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java(1 hunks)backend/src/test/java/moadong/club/service/ClubMetricServiceTest.java(0 hunks)backend/src/test/java/moadong/club/service/ClubProfileServiceTest.java(0 hunks)backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java(1 hunks)backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java(1 hunks)backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java(0 hunks)backend/src/test/java/moadong/unit/club/ClubProfileServiceTest.java(0 hunks)backend/src/test/java/moadong/unit/user/UserLoginTest.java(1 hunks)backend/src/test/java/moadong/unit/user/UserRegisterTest.java(1 hunks)backend/src/test/java/moadong/util/annotations/IntegrationTest.java(1 hunks)backend/src/test/java/moadong/util/annotations/UnitTest.java(1 hunks)
💤 Files with no reviewable changes (26)
- backend/src/main/java/moadong/club/controller/ClubSearchController.java
- backend/src/main/java/moadong/global/exception/ErrorCode.java
- backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.java
- backend/src/main/java/moadong/global/config/SecurityConfig.java
- backend/src/main/java/moadong/club/payload/request/ClubCreateRequest.java
- backend/src/main/java/moadong/user/entity/UserInformation.java
- backend/src/main/java/moadong/club/entity/ClubMetric.java
- backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
- backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java
- backend/src/main/java/moadong/media/service/GoogleDriveClubImageService.java
- backend/src/test/java/moadong/club/service/ClubProfileServiceTest.java
- backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java
- backend/src/main/java/moadong/user/payload/request/UserRegisterRequest.java
- backend/src/main/java/moadong/club/service/ClubMetricService.java
- backend/src/main/java/moadong/club/repository/ClubMetricRepository.java
- backend/src/test/java/moadong/club/service/ClubMetricServiceTest.java
- backend/src/test/java/moadong/unit/club/ClubProfileServiceTest.java
- backend/src/main/java/moadong/media/util/GoogleDriveConfig.java
- backend/src/main/java/moadong/club/payload/request/ClubApplyRequest.java
- backend/src/main/java/moadong/club/repository/ClubRepository.java
- backend/src/main/java/moadong/user/payload/response/TempPasswordResponse.java
- backend/src/main/java/moadong/media/service/GcsClubImageService.java
- backend/src/main/java/moadong/user/payload/request/UserUpdateRequest.java
- backend/src/main/java/moadong/club/controller/ClubMetricController.java
- backend/src/test/java/moadong/club/fixture/MetricFixture.java
- backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.java
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
PR: Moadong/moadong#703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/payload/request/ClubApplyQuestion.javabackend/src/main/java/moadong/club/service/ClubApplyService.javabackend/src/main/java/moadong/club/entity/ClubQuestion.javabackend/src/test/java/moadong/club/service/ClubApplyServiceTest.javabackend/src/test/java/moadong/fixture/ClubApplicationEditFixture.javabackend/src/main/java/moadong/club/entity/ClubApplicationQuestion.javabackend/src/main/java/moadong/club/repository/ClubQuestionRepository.javabackend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyService.javabackend/src/main/java/moadong/club/controller/ClubApplyController.javabackend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
PR: Moadong/moadong#329
File: backend/src/main/java/moadong/club/controller/ClubSearchController.java:0-0
Timestamp: 2025-05-01T08:32:09.398Z
Learning: ClubSearchController의 searchClubsByKeyword 메서드와 ClubSearchService의 searchClubsByKeyword 메서드 사이에 파라미터 순서 일치가 중요하다. 현재 컨트롤러의 매개변수 순서는 'keyword, recruitmentStatus, category, division'인 반면, 서비스 메서드의 순서는 'keyword, recruitmentStatus, division, category'이다. 컨트롤러에서 서비스 호출 시 'keyword, recruitmentStatus, division, category' 순서로 전달하면 category와 division 값이 바뀌어 검색 결과가 잘못될 수 있다.
Applied to files:
backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java
📚 Learning: 2025-05-15T12:03:57.356Z
Learnt from: Zepelown
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.
Applied to files:
backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
🧬 Code graph analysis (9)
backend/src/main/java/moadong/global/annotation/Korean.java (1)
backend/src/main/java/moadong/global/validator/KoreanValidator.java (1)
KoreanValidator(8-20)
backend/src/main/java/moadong/global/annotation/PhoneNumber.java (1)
backend/src/main/java/moadong/global/validator/PhoneNumberValidator.java (1)
PhoneNumberValidator(8-24)
backend/src/main/java/moadong/club/service/ClubApplyService.java (2)
backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
AllArgsConstructor(12-36)backend/src/main/java/moadong/club/entity/ClubQuestionAnswer.java (1)
AllArgsConstructor(7-16)
backend/src/main/java/moadong/global/annotation/UserId.java (1)
backend/src/main/java/moadong/global/validator/UserIdValidator.java (1)
UserIdValidator(8-18)
backend/src/test/java/moadong/util/annotations/UnitTest.java (1)
backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java (1)
ExtendWith(30-127)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
frontend/src/types/club.ts (1)
Club(3-12)
backend/src/main/java/moadong/club/controller/ClubProfileController.java (1)
frontend/src/apis/getClubDetail.ts (1)
getClubDetail(4-17)
backend/src/main/java/moadong/global/annotation/Password.java (1)
backend/src/main/java/moadong/global/validator/PasswordValidator.java (1)
PasswordValidator(8-20)
backend/src/test/java/moadong/util/annotations/IntegrationTest.java (1)
backend/src/test/java/moadong/MoadongApplicationTests.java (1)
SpringBootTest(6-13)
🔇 Additional comments (37)
backend/src/main/java/moadong/club/payload/request/ClubApplyQuestion.java (1)
9-9: 임포트 순서 정리 확인 완료임포트 재정렬로 인한 동작 변화는 없으며 코드 스타일만 개선된 것으로 보입니다.
backend/src/main/java/moadong/global/util/AESCipher.java (1)
3-9: LGTM – 임포트 정리가 표준 순서를 잘 따릅니다.
기존 동작과 충돌 없이 가독성이 개선되었습니다.backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java (1)
3-4: List/Optional 명시 import 적절이미 사용 중인
List와Optional타입을 명시적으로 import하여 가독성과 컴파일 안정성을 높였네요. 별도 이슈 없습니다.backend/src/main/java/moadong/user/entity/RefreshToken.java (1)
3-3: 불필요한 중복 import 정리 감사합니다.Date import를 한 번만 유지해 가독성과 유지보수성이 좋아졌습니다.
backend/src/main/java/moadong/user/repository/UserRepository.java (1)
3-3: 불필요한 중복 import 정리 감사합니다
Optional중복 선언을 정리해 import 섹션이 깔끔해졌습니다.backend/src/main/java/moadong/club/repository/ClubQuestionRepository.java (1)
3-3: Optional 임포트 정리 확인 완료중복 없이 한 번만
Optional을 임포트하도록 정리된 것 확인했습니다.backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
6-6: 불필요한 중복 임포트 정리 깔끔합니다.
java.util.List임포트가 한 번만 유지돼 가독성과 유지보수성이 좋아졌네요.backend/src/main/java/moadong/club/service/RecruitmentStateChecker.java (1)
3-4: 중복 import 정리로 가독성 개선 👍불필요한 재정의를 제거하고 import 블록을 상단으로 통일해 유지보수가 더 수월해졌습니다.
backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1)
3-7: 명시적 import 정리 완료 👍실제로 사용하는 컬렉션 타입만 명시해 와일드카드 import를 제거한 변경이 보여요. 유지보수성이 좋아져서 환영합니다.
backend/src/main/java/moadong/global/validator/UserIdValidator.java (1)
6-6: 임포트 정렬 변경 이상 없음임포트 순서 정리만 이루어졌고 동작에 영향이 없습니다.
backend/src/main/java/moadong/global/annotation/UserId.java (1)
9-9: 임포트 위치 조정 문제 없음임포트 재배치는 스타일 변경일 뿐이며 애노테이션 동작에 영향이 없습니다.
backend/src/main/java/moadong/user/controller/UserController.java (1)
26-32: 명시적 import 전환이 가독성과 유지보수성에 유리합니다.필요한 애너테이션만 선별적으로 가져오면서 와일드카드 import를 배제해 불필요한 클래스 충돌 가능성을 줄이고 코드 의도를 분명히 했습니다.
backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java (1)
3-5: 필요한 import 정리 확인했습니다.LocalDateTime,List,ArrayList를 모두 사용하는 구조라 명시적인 import 추가가 적절합니다.backend/src/main/java/moadong/club/entity/Club.java (1)
3-4: 명시적 컬렉션 임포트 추가 확인 완료
List와Map사용에 필요한 명시적 임포트가 정리되어 가독성과 유지보수성이 좋아졌습니다. 문제 없습니다.backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
6-7: 임포트 순서 정리 좋습니다내부 도메인 임포트를 표준 라이브러리 임포트 뒤로 정리해 일관성과 가독성을 확보했습니다. 이 변경은 기능에 영향을 주지 않으며 적절합니다.
backend/src/test/java/moadong/unit/user/UserRegisterTest.java (2)
4-11: 정리된 static import 구성 좋습니다테스트에서 사용하는 AssertJ/JUnit/Mockito 호출이 모두 상단으로 모여 가독성이 확실히 좋아졌습니다.
16-16: 필요한Setimport 반영 확인
ConstraintViolation검증 로직에서 사용하는Set이 정상적으로 선언되어 더 이상 중복 선언 우려가 없네요.backend/src/main/java/moadong/global/validator/KoreanValidator.java (1)
6-6: import 정리 확인 완료
Koreanimport 재배치만 수행되어 논리 변화 없고 기존 구동에 영향 없습니다.backend/src/main/java/moadong/global/annotation/Korean.java (1)
9-9: import 정리 확인 완료
KoreanValidatorimport 순서 조정만 존재하며 기능 변화는 없습니다.backend/src/main/java/moadong/global/annotation/PhoneNumber.java (1)
9-9: import 정리 확인 완료
PhoneNumberValidatorimport 재배치만 이루어져 동작에는 영향이 없습니다.backend/src/main/java/moadong/global/validator/PasswordValidator.java (1)
6-6: import 정리 확인 완료
Passwordimport 위치만 조정되었고 검증 로직은 그대로입니다.backend/src/main/java/moadong/global/annotation/Password.java (1)
9-9: import 정리 확인 완료
PasswordValidatorimport 순서 변경 외 추가 수정 없이 기존 동작을 유지합니다.backend/src/main/java/moadong/global/validator/PhoneNumberValidator.java (1)
6-6: 정렬 변경 문제 없음임포트 순서 조정이 기능이나 동작에 영향을 주지 않습니다.
backend/src/main/java/moadong/club/entity/ClubApplication.java (1)
11-16: 임포트 정리 확인로직 변경 없이 사용 중인 Lombok·상태 enum 및 Mongo 문서 애너테이션을 명시적으로 정리해 일관성이 좋아졌습니다.
backend/src/main/java/moadong/club/service/ClubApplyService.java (1)
4-28: 불필요 와일드카드 제거 환영기존에 사용되던 타입만 개별 임포트로 명시해 가독성과 IDE 리졸브가 한층 명확해졌습니다. 기능상 영향도 없고 좋습니다.
backend/src/main/java/moadong/club/controller/ClubApplyController.java (1)
8-30: 컨트롤러 임포트 정리 좋습니다컨트롤러에서 활용하는 요청 DTO와 스프링 MVC 애너테이션만 명시적으로 남겨 유지보수가 쉬워졌습니다.
backend/src/main/java/moadong/club/service/ClubProfileService.java (1)
3-3: 불필요한 중복 임포트 정리 👍
필요한List임포트만 남겨 가독성이 좋아졌습니다.backend/src/main/java/moadong/club/controller/ClubProfileController.java (1)
34-36: 의존성 정리 및 위임 흐름 확인 완료
컨트롤러가 서비스에만 위임하도록 단순화되어 호출 흐름이 명확해졌습니다.backend/src/main/java/moadong/user/payload/response/FindUserClubResponse.java (1)
3-4: 형식 변경만 적용된 것으로 보입니다. 기존 API와 행위에 영향이 없어 추가 조치 필요 없습니다.backend/src/main/java/moadong/user/payload/response/RefreshResponse.java (1)
3-4: 레코드 선언 정리가 적절합니다. 포맷 외 변경이 없어 문제없이 진행해도 되겠습니다.backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java (1)
4-4: 정리된 import 👍Fixture가 불필요한 타입 의존성을 끊고 필요한 요청 객체만 가져오기 때문에 유지보수성이 좋아졌습니다.
backend/build.gradle (1)
35-58: Google Drive 의존성 정리 👍GoogleDrive 관련 서비스와 설정이 빠지면서 Gradle에서도 관련 의존성이 모두 제거된 것 확인했습니다. 빌드 관점에서 남는 잔여 의존성도 없어 보여 정리 잘 되었습니다.
backend/src/test/java/moadong/util/annotations/IntegrationTest.java (1)
7-13: 통합 테스트 공통 구성이 잘 정리됐습니다.
@IntegrationTest에@SpringBootTest와@Tag를 메타로 부여해 통합 테스트의 기본 환경과 필터링 기준을 일관되게 적용할 수 있게 된 점이 좋습니다.backend/src/test/java/moadong/util/annotations/UnitTest.java (1)
7-15: Mockito 확장을 메타로 포함한 선택이 적절합니다.
@UnitTest에@ExtendWith(MockitoExtension.class)와@Tag를 묶어 반복 선언을 줄이고 필터링 체계를 통합한 구성이 깔끔합니다.backend/src/main/java/moadong/club/payload/dto/ClubSearchResult.java (1)
3-4: 변경 내용 이상 없습니다.임포트 정리만 이뤄졌고 동작에는 영향이 없습니다.
| import java.time.LocalDateTime; | ||
| import java.time.ZoneId; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.List; |
There was a problem hiding this comment.
중요: KST(now) → LocalDateTime 변환 시 타임존 소실로 저장 시각이 왜곡될 수 있습니다
ZonedDateTime.now(Asia/Seoul).toLocalDateTime()로 타임존 정보를 제거한 뒤, Spring Data Mongo가 LocalDateTime→Date로 변환할 때 시스템 기본 타임존을 사용합니다. 서버 타임존이 KST가 아니면 저장되는 Instant가 몇 시간씩 어긋납니다. 배포 환경이 UTC인 경우 특히 치명적입니다.
권장 수정 1 (선호): Instant로 저장하고 표현 계층에서 타임존을 적용하세요.
- import java.time.LocalDateTime;
- import java.time.ZoneId;
- import java.time.ZonedDateTime;
+ import java.time.Instant;- private LocalDateTime createdAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
+ private Instant createdAt = Instant.now();- private LocalDateTime editedAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
+ private Instant editedAt = Instant.now();- public void updateEditedAt() {
- this.editedAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
- }
+ public void updateEditedAt() {
+ this.editedAt = Instant.now();
+ }단기 핫픽스(타입 변경이 어려운 경우): 최소한 이중 타임존 변환을 멈추세요. LocalDateTime.now()만 사용하면 계산과 저장 모두 시스템 타임존에 의존하므로 즉각적인 시차 왜곡은 막을 수 있습니다(다만 KST 고정 요구사항은 충족하지 못함).
- private LocalDateTime createdAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
+ private LocalDateTime createdAt = LocalDateTime.now();- private LocalDateTime editedAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
+ private LocalDateTime editedAt = LocalDateTime.now();- public void updateEditedAt() {
- this.editedAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();
- }
+ public void updateEditedAt() {
+ this.editedAt = LocalDateTime.now();
+ }추가 권장: Spring Data Auditing(@CreatedDate, @LastModifiedDate) + Instant 사용으로 생성/수정 시각을 일관되게 관리하는 방안을 고려해 주세요.
Also applies to: 39-44, 61-63
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/ClubQuestion.java around lines 5-9
(also applies to 39-44 and 61-63): the code converts
ZonedDateTime.now(Asia/Seoul).toLocalDateTime(), which strips zone info and
causes wrong instants when the server TZ ≠ KST; change the timestamp handling to
use Instant for storage (compute and store Instant.now(ZoneOffset.UTC) or obtain
Instant from ZonedDateTime) and apply KST conversion only in the presentation
layer, or if a quick hotfix is required, stop using
ZonedDateTime.toLocalDateTime() and use LocalDateTime.now() consistently;
additionally consider switching auditing to Spring Data
@CreatedDate/@LastModifiedDate with Instant to ensure consistent
created/modified timestamps.
| List<ClubCategory> categories = new ArrayList<>(asList(ClubCategory.values())); | ||
| Collections.shuffle(categories); | ||
|
|
||
| // 2. 섞인 순서를 기반으로 Category에 대한 랜덤 우선순위 Map을 생성합니다. | ||
| Map<String, Integer> randomCategoryPriorities = new HashMap<>(); | ||
| for (int i = 0; i < categories.size(); i++) { | ||
| randomCategoryPriorities.put(categories.get(i).name(), i); | ||
| } | ||
|
|
||
| result = result.stream() | ||
| .sorted( | ||
| // | ||
| Comparator | ||
| // 1차: recruitmentStatus는 기존 enum의 우선순위로 정렬 | ||
| .comparingInt((ClubSearchResult club) -> ClubRecruitmentStatus.getPriorityFromString(club.recruitmentStatus())) | ||
| .thenComparingInt((ClubSearchResult club) -> ClubCategory.getPriorityFromString(club.category())) | ||
| // 2차: category는 랜덤하게 생성된 우선순위로 정렬 | ||
| .thenComparingInt((ClubSearchResult club) -> | ||
| randomCategoryPriorities.getOrDefault( | ||
| club.category() != null ? club.category().toUpperCase() : null, | ||
| Integer.MAX_VALUE)) | ||
| // 3차: 이름순으로 정렬 | ||
| .thenComparing(ClubSearchResult::name) | ||
| ) |
There was a problem hiding this comment.
랜덤 카테고리 우선순위가 실제로 적용되지 않습니다
randomCategoryPriorities 맵은 ClubCategory.values()의 name()(예: VOLUNTEER)을 키로 사용하지만, 실제 검색 결과에서 내려오는 club.category() 값은 테스트에서도 보이듯 "봉사"처럼 한글 표시 문자열입니다. 이 불일치 때문에 getOrDefault(...)가 항상 Integer.MAX_VALUE를 반환하여 카테고리 우선순위 비교가 무력화되고, 결국 이름순 정렬로만 동작합니다. 요구한 “랜덤 분과 순서”가 전혀 반영되지 않는 심각한 로직 버그입니다. 결과 리스트에서 실제로 등장한 카테고리 문자열을 기준으로 우선순위 맵을 만들어 주십시오.
아래와 같은 방향으로 수정하면 문제를 해결할 수 있습니다:
- List<ClubCategory> categories = new ArrayList<>(asList(ClubCategory.values()));
- Collections.shuffle(categories);
-
- Map<String, Integer> randomCategoryPriorities = new HashMap<>();
- for (int i = 0; i < categories.size(); i++) {
- randomCategoryPriorities.put(categories.get(i).name(), i);
- }
+ List<String> categories = result.stream()
+ .map(ClubSearchResult::category)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toCollection(ArrayList::new));
+ Collections.shuffle(categories);
+
+ Map<String, Integer> randomCategoryPriorities = new HashMap<>();
+ for (int i = 0; i < categories.size(); i++) {
+ randomCategoryPriorities.put(categories.get(i), i);
+ }
...
- .thenComparingInt((ClubSearchResult club) ->
- randomCategoryPriorities.getOrDefault(
- club.category() != null ? club.category().toUpperCase() : null,
- Integer.MAX_VALUE))
+ .thenComparingInt((ClubSearchResult club) ->
+ randomCategoryPriorities.getOrDefault(
+ club.category(), Integer.MAX_VALUE))📝 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.
| List<ClubCategory> categories = new ArrayList<>(asList(ClubCategory.values())); | |
| Collections.shuffle(categories); | |
| // 2. 섞인 순서를 기반으로 Category에 대한 랜덤 우선순위 Map을 생성합니다. | |
| Map<String, Integer> randomCategoryPriorities = new HashMap<>(); | |
| for (int i = 0; i < categories.size(); i++) { | |
| randomCategoryPriorities.put(categories.get(i).name(), i); | |
| } | |
| result = result.stream() | |
| .sorted( | |
| // | |
| Comparator | |
| // 1차: recruitmentStatus는 기존 enum의 우선순위로 정렬 | |
| .comparingInt((ClubSearchResult club) -> ClubRecruitmentStatus.getPriorityFromString(club.recruitmentStatus())) | |
| .thenComparingInt((ClubSearchResult club) -> ClubCategory.getPriorityFromString(club.category())) | |
| // 2차: category는 랜덤하게 생성된 우선순위로 정렬 | |
| .thenComparingInt((ClubSearchResult club) -> | |
| randomCategoryPriorities.getOrDefault( | |
| club.category() != null ? club.category().toUpperCase() : null, | |
| Integer.MAX_VALUE)) | |
| // 3차: 이름순으로 정렬 | |
| .thenComparing(ClubSearchResult::name) | |
| ) | |
| // 1. 실제 결과에 등장하는 카테고리 문자열을 기반으로 리스트를 생성하고 섞습니다. | |
| List<String> categories = result.stream() | |
| .map(ClubSearchResult::category) | |
| .filter(Objects::nonNull) | |
| .distinct() | |
| .collect(Collectors.toCollection(ArrayList::new)); | |
| Collections.shuffle(categories); | |
| // 2. 섞인 순서를 기반으로 Category에 대한 랜덤 우선순위 Map을 생성합니다. | |
| Map<String, Integer> randomCategoryPriorities = new HashMap<>(); | |
| for (int i = 0; i < categories.size(); i++) { | |
| randomCategoryPriorities.put(categories.get(i), i); | |
| } | |
| result = result.stream() | |
| .sorted( | |
| Comparator | |
| // 1차: recruitmentStatus는 기존 enum의 우선순위로 정렬 | |
| .comparingInt((ClubSearchResult club) -> | |
| ClubRecruitmentStatus.getPriorityFromString(club.recruitmentStatus())) | |
| // 2차: 실제 문자열 카테고리의 랜덤 우선순위로 정렬 | |
| .thenComparingInt((ClubSearchResult club) -> | |
| randomCategoryPriorities.getOrDefault( | |
| club.category(), Integer.MAX_VALUE)) | |
| // 3차: 이름순으로 정렬 | |
| .thenComparing(ClubSearchResult::name) | |
| ) |
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubSearchService.java around
lines 34 to 55, the randomCategoryPriorities map is built using
ClubCategory.name() (e.g. "VOLUNTEER") while club.category() returns display
strings like "봉사", causing lookups to miss and always return Integer.MAX_VALUE;
rebuild the map using the actual display/localized category string keys that
appear in results (for example by calling the enum method that returns the
display name or by mapping each ClubCategory to its localized label), ensure you
handle nulls (skip or map null to a default) and use that map in the comparator
so random priorities are applied correctly (fall back to Integer.MAX_VALUE only
when a category truly has no mapping).
| // @Test | ||
| // void 모집상태에_해당하는_동아리가_없으면_빈_리스트를_반환한다() { | ||
| // // given | ||
| // String keyword = "없는키워드"; | ||
| // String recruitmentStatus = "OPEN"; | ||
| // String division = "중동"; | ||
| // String category = "봉사"; | ||
| // | ||
| // when(clubSearchRepository.searchClubsByKeyword(keyword, recruitmentStatus, division, category)) | ||
| // .thenReturn(List.of()); // 빈 리스트 반환 | ||
| // | ||
| // // when | ||
| // ClubSearchResponse response = clubSearchService.searchClubsByKeyword(keyword, recruitmentStatus, division, category); | ||
| // | ||
| // // then | ||
| // assertTrue(response.clubs().isEmpty()); | ||
| // } | ||
| // | ||
| // @Test | ||
| // void 모집상태가_같다면_카테고리순으로_정렬하고_카테고리도_같다면_이름순으로_반환한다() { | ||
| // // given | ||
| // String keyword = "동아리"; | ||
| // String recruitmentStatus = "all"; | ||
| // String division = "all"; | ||
| // String category = "all"; // 전체 카테고리 | ||
| // | ||
| // ClubSearchResult club1 = ClubSearchResult.builder().name("club1").recruitmentStatus("OPEN") | ||
| // .division("중동").category("봉사").build(); | ||
| // ClubSearchResult club2 = ClubSearchResult.builder().name("club2").recruitmentStatus("OPEN") | ||
| // .division("중동").category("종교").build(); | ||
| // ClubSearchResult club3 = ClubSearchResult.builder().name("club3").recruitmentStatus("OPEN") | ||
| // .division("중동").category("종교").build(); | ||
| // | ||
| // List<ClubSearchResult> unsorted = List.of(club3, club2, club1); | ||
| // | ||
| // when(clubSearchRepository.searchClubsByKeyword(keyword, recruitmentStatus, division, category)) | ||
| // .thenReturn(unsorted); | ||
| // | ||
| // // when | ||
| // ClubSearchResponse response = clubSearchService.searchClubsByKeyword(keyword, recruitmentStatus, division, category); | ||
| // | ||
| // // then | ||
| // List<ClubSearchResult> sorted = response.clubs(); | ||
| // assertEquals("club1", sorted.get(0).name()); | ||
| // assertEquals("club2", sorted.get(1).name()); | ||
| // assertEquals("club3", sorted.get(2).name()); | ||
| // } |
There was a problem hiding this comment.
주석 처리로 테스트 커버리지를 없애면 안 됩니다
두 개의 핵심 시나리오 테스트를 통째로 주석 처리해 버리면 검색 서비스의 기본 동작(빈 결과 처리, 동일 모집상태 내 정렬 등)에 대한 회귀 검증이 사라집니다. 랜덤 정렬 요구사항에 맞춰 단언을 조정하거나, 서비스 쪽에서 랜덤 시드를 주입 가능하게 만드는 등 테스트 가능한 형태로 수정해 다시 활성화해 주십시오.
🤖 Prompt for AI Agents
In backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java around
lines 57-103, two tests were commented out which removes coverage for
empty-result handling and sorting within same recruitment status; re-enable
these tests and make them deterministic: either (A) update the tests to not
assume a strict deterministic order by asserting the response contains the
expected elements and that grouping/sorting rules hold (e.g., verify list size,
verify all expected names present, verify category grouping and name-order
within each category), or (B) modify the service to accept an injectable
Random/seed used for tie-breaking and in tests inject a fixed seed so the
original strict assertions hold; pick one approach and implement the test
changes (or service DI) so the tests can be un-commented and pass reliably.
PororoAndFriends
left a comment
There was a problem hiding this comment.
이번 버전은 리팩터링 위주의 패치가 진행됐네요! 고생하셨습니다~
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit