Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions backend/src/main/java/moadong/club/service/ClubSearchService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package moadong.club.service;

import java.util.Comparator;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import moadong.club.enums.ClubCategory;
Expand All @@ -11,6 +10,8 @@
import moadong.club.repository.ClubSearchRepository;
import org.springframework.stereotype.Service;

import static java.util.Arrays.*;

@Service
@AllArgsConstructor
public class ClubSearchService {
Expand All @@ -29,12 +30,27 @@ public ClubSearchResponse searchClubsByKeyword(String keyword,
category
);
// 정렬
// 1. ClubCategory Enum의 모든 값을 가져와 리스트로 만들고 순서를 랜덤하게 섞습니다.
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)
)
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,53 +54,53 @@ class ClubSearchServiceTest {
assertIterableEquals(sorted, List.of(club1, club4, club2, club3));
}

@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());
}
// @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());
// }

Comment on lines +75 to 104
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

랜덤 카테고리 정렬로 기존 기준(카테고리→이름) 검증은 부적합 — 랜덤성에 독립적인 불변 규칙으로 테스트 교체 권장

카테고리 우선순위가 매호출 랜덤이면 카테고리 간 상대 순서를 단정할 수 없습니다. 대신 “같은 모집상태·같은 카테고리 내부에서는 이름 오름차순” 같은 불변 규칙을 검증하세요. 아래처럼 테스트를 교체하면 랜덤성에 영향받지 않으면서 3차 정렬(이름)을 보장합니다.

-//    @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());
-//    }
+    @Test
+    void 같은_모집상태이고_같은_카테고리라면_이름순으로_반환한다() {
+        // given
+        String keyword = "동아리";
+        String recruitmentStatus = "all";
+        String division = "all";
+        String category = "all";
+
+        ClubSearchResult b = ClubSearchResult.builder().name("clubB").recruitmentStatus("OPEN")
+                .division("중동").category("종교").build();
+        ClubSearchResult a = ClubSearchResult.builder().name("clubA").recruitmentStatus("OPEN")
+                .division("중동").category("종교").build();
+        ClubSearchResult c = ClubSearchResult.builder().name("clubC").recruitmentStatus("OPEN")
+                .division("중동").category("종교").build();
+
+        List<ClubSearchResult> unsorted = List.of(b, a, c);
+        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("clubA", sorted.get(0).name());
+        assertEquals("clubB", sorted.get(1).name());
+        assertEquals("clubC", sorted.get(2).name());
+    }

추가 권장사항:

  • 서비스에서 랜덤 소스를 주입(예: Supplier<Map<Category,Integer>> 또는 Random) 가능하게 하면 테스트에서 고정 seed로 검증이 가능합니다(비플래키). 원하시면 DI-friendly 구조로 제안 드리겠습니다.
📝 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
// @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());
// }
@Test
void 같은_모집상태이고_같은_카테고리라면_이름순으로_반환한다() {
// given
String keyword = "동아리";
String recruitmentStatus = "all";
String division = "all";
String category = "all";
ClubSearchResult b = ClubSearchResult.builder().name("clubB").recruitmentStatus("OPEN")
.division("중동").category("종교").build();
ClubSearchResult a = ClubSearchResult.builder().name("clubA").recruitmentStatus("OPEN")
.division("중동").category("종교").build();
ClubSearchResult c = ClubSearchResult.builder().name("clubC").recruitmentStatus("OPEN")
.division("중동").category("종교").build();
List<ClubSearchResult> unsorted = List.of(b, a, c);
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("clubA", sorted.get(0).name());
assertEquals("clubB", sorted.get(1).name());
assertEquals("clubC", sorted.get(2).name());
}
🤖 Prompt for AI Agents
In backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java around
lines 75 to 104, the commented test tries to assert global category ordering but
the service applies a random category priority so the test is flaky; replace it
with a deterministic assertion that when recruitmentStatus and category are the
same, results are ordered by name ascending (i.e., build test data with
identical recruitmentStatus and category but different names, mock the
repository to return them in an unsorted order, call searchClubsByKeyword and
assert the returned list is sorted by name), and optionally make randomness
testable by injecting a deterministic random/source into the service if you need
to assert category ordering in future.


}
Loading