-
Notifications
You must be signed in to change notification settings - Fork 2
[fix] 특수문자가 포함된 문자열도 검색이 가능하게 한다 #928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
검색 키워드 이스케이프 backend/src/main/java/moadong/club/service/ClubSearchService.java |
입력 키워드를 quotedKeyword(String) 헬퍼로 null/빈 검사 후 Pattern.quote()로 이스케이프해 저장소 조회에 사용하도록 내부 동작 변경 |
API 문서 설명 업데이트 backend/src/main/java/moadong/club/controller/ClubSearchController.java |
검색 키워드 설명에 HTML 줄바꿈(<br>) 추가 (문구 포맷 변경, 로직 미변경) |
FCM 토큰 삭제 저장소 메서드 추가 backend/src/main/java/moadong/fcm/repository/FcmTokenRepository.java |
void deleteFcmTokenByToken(String fcmToken) 메서드 추가 |
테스트 초기화 정리 추가 backend/src/test/java/moadong/fcm/service/FcmServiceTest.java, backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java |
FCM 테스트에서 existing_token 삭제 초기화 로직 추가; ClubSearch 테스트의 모킹 호출이 Pattern.quote 적용값으로 변경 (테스트 입력/모킹 일치화) |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- 검토 시 집중할 항목:
ClubSearchService.java—Pattern.quote()적용이 DB 쿼리(예: LIKE/정규식 기반 검색)와 의도한 매칭 동작을 유지하는지 확인.FcmTokenRepository.java— JPA 메서드 네이밍으로 자동 구현되는지, 또는 별도 구현/마이그레이션 영향이 없는지 확인.- 테스트(
FcmServiceTest.java,ClubSearchServiceTest.java) — 초기화/모킹 변경이 다른 테스트와 충돌하지 않는지 확인.
Possibly related PRs
- [refactor] club api 리팩토링 #492 — 동일 엔드포인트(
searchClubsByKeyword)의 API 문서(설명) 변경을 포함함.
Suggested labels
📬 API, 🐞 Bug, 🛠Fix
Suggested reviewers
- lepitaaar
- Zepelown
- PororoAndFriends
Pre-merge checks and finishing touches
❌ Failed checks (2 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Out of Scope Changes check | FCM 토큰 저장소 메서드 추가와 테스트 설정 변경이 범위 외로 보이지만, 이러한 변경사항들이 특수문자 검색 문제 해결과 직접적인 관련이 없어 보입니다. | FcmTokenRepository의 deleteFcmTokenByToken 메서드 추가와 FcmServiceTest 수정사항이 MOA-419와 무관하므로 별도 PR로 분리하거나 해당 변경의 필요성을 설명해주시기 바랍니다. | |
| 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 (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | PR 제목이 변경 사항의 핵심을 명확하게 나타냅니다. 특수문자를 포함한 검색이 가능하도록 하는 것이 주요 목표이며, 이는 코드 변경 사항과 완벽하게 일치합니다. |
| Linked Issues check | ✅ Passed | PR의 코드 변경 사항이 링크된 이슈 MOA-419의 요구사항을 충족합니다. Pattern.quote를 사용하여 정규식 메타문자를 이스케이프 처리하고, 이스케이프된 키워드로 검색을 수행하는 방식으로 특수문자 검색 오류를 해결했습니다. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
fix/#925-search-special-char-MOA-419
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.
Test Results75 tests 72 ✅ 17s ⏱️ Results for commit 9e7d2ac. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
backend/src/main/java/moadong/club/service/ClubSearchService.java (1)
66-73: 정규화 로직에 테스트와 문서화가 필요합니다.normalizeKeyword 메서드의 로직은 정확하지만 개선할 점이 있습니다:
- 테스트 커버리지 부족: 새로운 정규화 로직에 대한 단위 테스트가 없습니다.
- 연속 공백 처리:
replaceAll후 연속된 공백이 남을 수 있습니다 (예: "hello@@world" → "hello world").- 메서드 문서화: 정규식 패턴의 의도를 설명하는 주석이나 JavaDoc이 있으면 좋습니다.
다음과 같은 개선을 권장합니다:
1. 단위 테스트 추가:
// ClubSearchServiceTest.java (새 파일 또는 기존 테스트 클래스에 추가) @Test void normalizeKeyword_withSpecialChars_removesSpecialChars() { // given String keyword = "누리?!@#$"; // when ClubSearchResponse response = clubSearchService.searchClubsByKeyword( keyword, "all", "all", "all"); // then - 특수문자가 제거되고 "누리"로 검색된 결과 확인 } @Test void normalizeKeyword_withOnlySpecialChars_returnsEmptyString() { String keyword = "?!@#$"; ClubSearchResponse response = clubSearchService.searchClubsByKeyword( keyword, "all", "all", "all"); // 전체 목록 반환 확인 }2. 연속 공백 정규화 (선택사항):
private String normalizeKeyword(String keyword) { if (keyword == null) return null; String trimmedKeyword = keyword.trim(); if (trimmedKeyword.isEmpty()) return ""; - return trimmedKeyword.replaceAll("[^0-9A-Za-z가-힣\\s]", "").trim(); + return trimmedKeyword + .replaceAll("[^0-9A-Za-z가-힣\\s]", "") + .replaceAll("\\s+", " ") // 연속된 공백을 하나로 + .trim(); }3. JavaDoc 추가:
+/** + * 검색 키워드를 정규화합니다. + * MongoDB regex 쿼리에서 오류를 방지하기 위해 특수문자를 제거합니다. + * + * @param keyword 원본 검색 키워드 + * @return 한글, 영문, 숫자, 공백만 포함하는 정규화된 키워드 + */ private String normalizeKeyword(String keyword) {backend/src/main/java/moadong/club/controller/ClubSearchController.java (1)
32-32: 특수문자 처리에 대한 문서화를 추가하세요.API 문서에 특수문자 처리 방식을 명시하면 사용자가 검색 동작을 이해하는 데 도움이 됩니다.
다음과 같이 특수문자 처리에 대한 설명을 추가하세요:
- + "keyword는 대소문자 구분 없이 자유롭게 검색<br>" + + "keyword는 대소문자 구분 없이 자유롭게 검색 (특수문자는 자동으로 제거됨)<br>"또는 더 자세하게:
+ "keyword는 대소문자 구분 없이 자유롭게 검색<br>" + + "※ 검색어에서 특수문자는 자동으로 제거되며, 한글/영문/숫자/공백만 검색에 사용됩니다<br>"
📜 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 (4)
backend/src/main/java/moadong/club/controller/ClubSearchController.java(1 hunks)backend/src/main/java/moadong/club/service/ClubSearchService.java(2 hunks)backend/src/main/java/moadong/fcm/repository/FcmTokenRepository.java(1 hunks)backend/src/test/java/moadong/fcm/service/FcmServiceTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
Repo: Moadong/moadong PR: 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/main/java/moadong/club/service/ClubSearchService.javabackend/src/main/java/moadong/club/controller/ClubSearchController.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (1)
backend/src/main/java/moadong/club/service/ClubSearchService.java (1)
26-29: 정규화된 키워드 사용은 올바르나, 빈 문자열 동작을 검증하세요.정규화된 키워드를 repository 호출에 사용하는 것은 좋습니다. 그러나 PR 설명에서 언급한 것처럼, 특수문자만 입력된 경우 빈 문자열이 되어 전체 목록이 반환됩니다. 이 동작이 의도된 것인지, 아니면 사용자에게 안내 메시지를 표시해야 하는지 확인이 필요합니다.
다음 시나리오를 테스트하여 동작을 확인하세요:
- 특수문자만 입력: "???" → 전체 목록 반환
- 공백만 입력: " " → 전체 목록 반환
- 빈 문자열 입력: "" → 전체 목록 반환
lepitaaar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
깔금한 원인 분석 좋아요~~ 버그 원인찾기힘드셨을텐데 수고하셨습니다
| .build(); | ||
| } | ||
|
|
||
| private String normalizeKeyword(String keyword) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
입력받은 키워드를 한번 replace 해주는거 좋네요. 하지만 특수문자를 모두 삭제하면 예상치못한 사이드 이펙트가 존재할꺼같네요 예를 들면 현재 동아리이름이 O.S.T 인곳은 이렇게 검색시 OST로 치환되어서 원하는 출력이 안나오지않을까요?
Pattern.quote() 써서 정규식을 문자로 취급하는건 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 지적 감사합니다.
저도 처음에는 Pattern.quote()를 사용해서 입력값을 정규식이 아닌 문자 그대로 취급하는 방안을 먼저 고민했었습니다.
다만 사용자가 동아리명 뒤에 실수로 특수문자를 하나 입력했을 경우에도
원하는 검색 결과가 전혀 나오지 않는 상황이 UX적으로 불편할 수 있겠다고 판단해서,
이번 PR에서는 특수문자를 제거하는 방향으로 우선 처리했습니다
다만 말씀해주신 것처럼,
동아리명 자체에 특수문자가 포함된 경우(O.S.T 등)를 충분히 고려하지 못한 부분은 맞는 것 같아요.
이 경우 단순히 특수문자를 제거하는 방식만으로는
의도한 검색 결과를 보장하기 어렵다는 점을 인지했습니다.
하여 특수문자를 “삭제”하는 방식은 이번 PR에서는 제외하고 방향을 수정하려고 합니다.
또 하나 더 생각해보니,
O.S.T와CERT-IS같은 동아리는 "ost"와 "Certis"처럼 특수문자를 입력하지 않았을 때도
검색 결과에 노출되는 것이 자연스러울 것 같아요.
특히 최근 다른 PR에서 검색 대상에서 소개글/상세 설명이 제외되면서,
이전에는 가능했던 검색이 더 이상 되지 않을 것 같아 추후 이 부분도 보완하면 좋을 거 같네요.
|
이런 문제가 있는지 처음 알게 되었네용. 정리도 깔끔하게 해주셔서 이해하기 편했습니당. 수고하셨습니다~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
backend/src/main/java/moadong/club/service/ClubSearchService.java (1)
67-69: trim() 호출 최적화 및 일관성 개선을 고려해보세요.현재 구현은 정상 동작하지만 다음 개선 사항을 고려할 수 있습니다:
keyword.trim()이 두 번 호출됩니다 (조건문과Pattern.quote()호출 시).- 키워드가 공백만 포함할 경우 (예:
" "), 원본 키워드(공백 포함)를 반환하는데, 이는Pattern.quote()케이스에서 trim된 값을 사용하는 것과 일관성이 떨어집니다.🔎 제안하는 개선안
private String quotedKeyword(String keyword) { - return (keyword == null || keyword.trim().isEmpty()) ? keyword : Pattern.quote(keyword.trim()); + if (keyword == null) { + return null; + } + String trimmed = keyword.trim(); + return trimmed.isEmpty() ? trimmed : Pattern.quote(trimmed); }이렇게 하면:
trim()호출이 한 번만 발생합니다.- 공백 전용 키워드도 일관되게 빈 문자열로 처리됩니다.
📜 Review details
Configuration used: Organization 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 (1)
backend/src/main/java/moadong/club/service/ClubSearchService.java(3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
Repo: Moadong/moadong PR: 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/main/java/moadong/club/service/ClubSearchService.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/service/ClubSearchService.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/service/ClubSearchService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (3)
backend/src/main/java/moadong/club/service/ClubSearchService.java (3)
4-4: LGTM! Pattern import가 적절히 추가되었습니다.Pattern.quote() 사용을 위해 필요한 import가 올바르게 추가되었습니다.
27-28: LGTM! 키워드 이스케이프 처리가 명확하게 분리되었습니다.헬퍼 메서드를 통해 키워드 전처리 로직을 분리한 것은 코드 가독성과 유지보수성 측면에서 좋은 접근입니다.
30-30: LGTM! 핵심 수정사항이 올바르게 적용되었습니다.이스케이프된 키워드를 repository에 전달함으로써 정규식 메타문자로 인한 오류와 의도치 않은 검색 결과를 방지합니다. 과거 리뷰에서 제안된 Pattern.quote() 방식이 정확히 구현되었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java (2)
29-57: Mock 설정이 서비스 계층 변경사항과 올바르게 일치합니다. 다만, 특수문자 테스트 케이스 추가를 권장합니다.테스트가
Pattern.quote(keyword)를 사용하도록 올바르게 업데이트되어 서비스 계층의 변경사항과 일치합니다. 하지만 이 PR의 핵심 목적인 특수문자 검색 수정사항을 검증하는 테스트가 누락되어 있습니다.다음 테스트 케이스들을 추가하는 것을 권장합니다:
- 정규식 메타문자가 포함된 키워드 (예: "누리?", "동아리*", "테스트+")
- 특수문자만 포함된 키워드 (예: "?", "???")
- 빈 문자열 및 null 키워드 처리
🔎 특수문자 검색 테스트 케이스 추가 예시
@Test void 특수문자가_포함된_키워드로_검색이_가능하다() { // given String keyword = "누리?"; String recruitmentStatus = "all"; String division = "all"; String category = "all"; ClubSearchResult club1 = ClubSearchResult.builder() .name("누리?클럽") .recruitmentStatus("OPEN") .division("중동") .category("봉사") .build(); List<ClubSearchResult> results = List.of(club1); when(clubSearchRepository.searchClubsByKeyword(Pattern.quote(keyword), recruitmentStatus, division, category)) .thenReturn(results); // when ClubSearchResponse response = clubSearchService.searchClubsByKeyword(keyword, recruitmentStatus, division, category); // then assertEquals(1, response.clubs().size()); assertEquals("누리?클럽", response.clubs().get(0).name()); } @Test void 특수문자만_입력된_경우_처리된다() { // given String keyword = "?"; String recruitmentStatus = "all"; String division = "all"; String category = "all"; when(clubSearchRepository.searchClubsByKeyword(Pattern.quote(keyword), recruitmentStatus, division, category)) .thenReturn(List.of()); // when ClubSearchResponse response = clubSearchService.searchClubsByKeyword(keyword, recruitmentStatus, division, category); // then assertTrue(response.clubs().isEmpty()); }
59-105: 주석 처리된 테스트 코드를 정리해주세요.주석 처리된 두 개의 테스트 케이스가 있습니다:
- 검색 결과가 없는 경우 처리
- 모집상태가 동일할 때 카테고리 및 이름순 정렬
이 테스트들이 여전히 유효하다면 활성화하고, 더 이상 필요하지 않다면 제거하는 것을 권장합니다. 만약 일시적으로 비활성화된 것이라면 그 이유를 TODO 주석으로 명시해주세요.
📜 Review details
Configuration used: Organization 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 (1)
backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
Repo: Moadong/moadong PR: 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
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (1)
backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java (1)
9-9: 정규식 이스케이프 처리를 위한 import가 올바르게 추가되었습니다.
Pattern.quote사용을 위해 필요한 import가 적절히 추가되었습니다.
lepitaaar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
변경 감사합니다! Pattern.quote 적용으로 검색 범위가 좁아질 수 있어 보여서, 추후 동작 한 번 같이 확인해보면 좋겠습니다!
#️⃣연관된 이슈
📝작업 내용
🐞 문제 상황
검색 기능에서 keyword로 물음표가 들어올 경우에 서버 오류가 발생했습니다.
특히,
?문자만 단독으로 검색했을 때 MongoDB aggregate 단계에서 에러가 발생"누리?"와 같이 문자가 섞인 경우에는 오류는 나지 않지만,정규식으로 해석되면서 의도하지 않은 검색 결과가 반환되는 문제가 있었습니다.
🔍 원인 분석
MongoDB 검색 로직에서 사용자 입력 키워드를 그대로
$regex조건에 사용하고 있었으며,정규식 메타문자(
?등)가 이스케이프되지 않은 상태로 전달되면서 다음 문제가 발생했습니다.정규식에서 ?는 바로 앞에 있는 패턴이 0번 또는 1번 등장할 수 있음을 의미하는 메타문자입니다.
예를들어,
?단독 입력 → 앞에 적용할 대상이 없기 때문에 invalid regex 오류 발생"누리?"입력 → 정규식 수량자로 해석되어 "리"가 있어도 되고 없어도 된다로 해석됨즉, 아래가 모두 매칭됩니다:
-
"누리"-
"누"-> 검색 범위가 과도하게 확장됨
즉, 사용자 입력이 "검색 문자열"이 아닌 "정규식"으로 그대로 해석되고 있던 것이 원인이었습니다.
🛠 해결 방법
Pattern.quote로 정규식 처리중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
Summary by CodeRabbit
새로운 기능
문서
테스트
✏️ Tip: You can customize this high-level summary in your review settings.