diff --git a/src/main/java/com/example/gradu/domain/ranking/service/CourseRankingService.java b/src/main/java/com/example/gradu/domain/ranking/service/CourseRankingService.java index 7c56099..b9662b6 100644 --- a/src/main/java/com/example/gradu/domain/ranking/service/CourseRankingService.java +++ b/src/main/java/com/example/gradu/domain/ranking/service/CourseRankingService.java @@ -34,19 +34,22 @@ public class CourseRankingService { private static final List LIBERAL_EXCLUDE_KEYWORDS = List.of("공동체리더십훈련", "채플"); - public CourseRankingService(CourseRankingRepository repository, MajorRoadmapIndex majorRoadmapIndex) { + public CourseRankingService( + CourseRankingRepository repository, + MajorRoadmapIndex majorRoadmapIndex + ) { this.repository = repository; this.majorRoadmapIndex = majorRoadmapIndex; } public RankingResponse getCourseRanking() { - var major = loadMajorByTerms(); // ✅ 변경 + var major = loadMajorByTerms(); var liberal = new LiberalRanking( - loadTop10(LIB_FAITH, true), - loadTop10(LIB_GENERAL_EDU, true), - loadTop10(LIB_BSM, true), - loadTop10(LIB_FREE_ELECTIVE, true) + loadTop10Merged(LIB_FAITH, true), + loadTop10Merged(LIB_GENERAL_EDU, true), + loadTop10Merged(LIB_BSM, true), + loadTop10Merged(LIB_FREE_ELECTIVE, true) ); return new RankingResponse(major, liberal); @@ -54,10 +57,10 @@ public RankingResponse getCourseRanking() { /** * ✅ 전공: MAJOR에서 많이 가져온 뒤 로드맵(term)으로 분류해서 각 term Top10 생성 - * 1-1은 없음 → y1s2부터 생성 + * - 1-1은 없음 → y1s2부터 생성 + * - 같은 과목(공백 차이)은 term 버킷 안에서 합산 */ private MajorRanking loadMajorByTerms() { - // term별 버킷 (순서 고정) Map> bucket = new LinkedHashMap<>(); bucket.put("y1s2", new ArrayList<>()); bucket.put("y2s1", new ArrayList<>()); @@ -79,35 +82,68 @@ private MajorRanking loadMajorByTerms() { var key = termOpt.get().toBucketKey(); var list = bucket.get(key); - if (list != null) list.add(r); // 혹시 1-1 같은게 들어오면 null이라 무시 + if (list != null) list.add(r); } return new MajorRanking( - toTop10(bucket.get("y1s2")), - toTop10(bucket.get("y2s1")), - toTop10(bucket.get("y2s2")), - toTop10(bucket.get("y3s1")), - toTop10(bucket.get("y3s2")), - toTop10(bucket.get("y4s1")), - toTop10(bucket.get("y4s2")) + toTop10Merged(bucket.get("y1s2")), + toTop10Merged(bucket.get("y2s1")), + toTop10Merged(bucket.get("y2s2")), + toTop10Merged(bucket.get("y3s1")), + toTop10Merged(bucket.get("y3s2")), + toTop10Merged(bucket.get("y4s1")), + toTop10Merged(bucket.get("y4s2")) ); } - private List toTop10(List rows) { - if (rows == null) return List.of(); - var finalRows = rows.stream().limit(10).toList(); + /** + * ✅ 교양/전공 공통: rows를 "공백 제거 키"로 합산 → takenCount desc 정렬 → top10 → rank 부여 + */ + private List toTop10Merged(List rows) { + if (rows == null || rows.isEmpty()) return List.of(); + + // normKey -> (sumCount, displayName) + Map map = new HashMap<>(); + + for (var r : rows) { + String raw = r.getName(); + String key = norm(raw); + if (key.isBlank()) continue; + + Agg prev = map.get(key); + long nextCount = (prev == null ? 0L : prev.takenCount) + r.getTakenCount(); + + // ✅ 대표 표기명: 사람이 읽기 편한 쪽(공백 포함, 더 긴 것) 우선 + String nextDisplay = (prev == null) + ? safe(raw) + : chooseDisplay(prev.displayName, safe(raw)); + + map.put(key, new Agg(nextCount, nextDisplay)); + } - return IntStream.range(0, finalRows.size()) + var merged = map.values().stream() + .sorted((a, b) -> { + int c = Long.compare(b.takenCount, a.takenCount); + if (c != 0) return c; + return a.displayName.compareTo(a.displayName); + }) + .limit(10) + .toList(); + + return IntStream.range(0, merged.size()) .mapToObj(i -> new RankingItem( i + 1, - finalRows.get(i).getName(), - finalRows.get(i).getTakenCount(), + merged.get(i).displayName, + merged.get(i).takenCount, 0 )) .toList(); } - private List loadTop10(Set categories, boolean applyFilter) { + /** + * ✅ 교양: 카테고리별로 가져온 뒤(필요시 필터) → 합산 top10 + */ + private List loadTop10Merged(Set categories, boolean applyFilter) { int fetchSize = applyFilter ? 30 : 10; var fetchedRows = repository.findTopCoursesByCategories( @@ -115,23 +151,13 @@ private List loadTop10(Set categories, boolean applyFilte PageRequest.of(0, fetchSize) ); - final var finalRows = applyFilter + var filtered = applyFilter ? fetchedRows.stream() .filter(r -> !containsAny(r.getName(), LIBERAL_EXCLUDE_KEYWORDS)) - .limit(10) .toList() - : fetchedRows.stream() - .limit(10) - .toList(); + : fetchedRows; - return IntStream.range(0, finalRows.size()) - .mapToObj(i -> new RankingItem( - i + 1, - finalRows.get(i).getName(), - finalRows.get(i).getTakenCount(), - 0 - )) - .toList(); + return toTop10Merged(filtered); } private boolean containsAny(String text, List keywords) { @@ -141,4 +167,36 @@ private boolean containsAny(String text, List keywords) { } return false; } + + // ======================= + // normalize / display util + // ======================= + + // ✅ 공백만 제거 + trim (웹서비스개발 / 웹 서비스 개발 동일 키) + private static String norm(String s) { + if (s == null) return ""; + return s.trim().replaceAll("\\s+", ""); + } + + private static String safe(String s) { + return s == null ? "" : s; + } + + // 더 읽기 좋은 표기(대개 공백 포함이 더 길다) 우선 + private static String chooseDisplay(String a, String b) { + if (a == null || a.isBlank()) return safe(b); + if (b == null || b.isBlank()) return safe(a); + if (b.length() > a.length()) return b; + return a; + } + + private static final class Agg { + final long takenCount; + final String displayName; + + private Agg(long takenCount, String displayName) { + this.takenCount = takenCount; + this.displayName = displayName; + } + } } diff --git a/src/main/resources/catalog/major_roadmap.json b/src/main/resources/catalog/major_roadmap.json index fbab9ab..68736a3 100644 --- a/src/main/resources/catalog/major_roadmap.json +++ b/src/main/resources/catalog/major_roadmap.json @@ -1,18 +1,17 @@ [ - { "year": 1, "semester": 2, "courseCode": "ECE10002", "nameKo": "C 프로그래밍(전산전공)", "nameEn": "C-Programming (CSEE)" }, + { "year": 1, "semester": 2, "courseCode": "ECE10002", "nameKo": "C 프로그래밍", "nameEn": "C-Programming" }, { "year": 1, "semester": 2, "courseCode": "ECE10005", "nameKo": "코딩 스튜디오", "nameEn": "Coding Studio" }, { "year": 1, "semester": 2, "courseCode": "ECE10020", "nameKo": "공학설계입문", "nameEn": "Introduction to Engineering Design" }, - { "year": 1, "semester": 2, "courseCode": "ECE20010", "nameKo": "데이터구조", "nameEn": "Data Structure" }, - { "year": 1, "semester": 2, "courseCode": "ECE20016", "nameKo": "자바프로그래밍언어", "nameEn": "Introduction to JAVA Programming" }, - { "year": 1, "semester": 2, "courseCode": "ECE20025", "nameKo": "프로그래밍 스튜디오", "nameEn": "Programming Studio" }, - { "year": 1, "semester": 2, "courseCode": "ECE20057", "nameKo": "논리설계", "nameEn": "Logic Design" }, - { "year": 1, "semester": 2, "courseCode": "ECE20064", "nameKo": "회로이론", "nameEn": "Circuit Theory" }, - { "year": 1, "semester": 2, "courseCode": "ECE20065", "nameKo": "기초회로 및 논리실습", "nameEn": "Basic Circuit and Logic Laboratory" }, - - { "year": 2, "semester": 1, "courseCode": "ECE20006", "nameKo": "신호및시스템", "nameEn": "Signal and System" }, - { "year": 2, "semester": 1, "courseCode": "ECE20009", "nameKo": "웹서비스개발", "nameEn": "Web Service Development" }, + { "year": 2, "semester": 1, "courseCode": "ECE20010", "nameKo": "데이터구조", "nameEn": "Data Structure" }, + { "year": 2, "semester": 1, "courseCode": "ECE20016", "nameKo": "자바프로그래밍언어", "nameEn": "Introduction to JAVA Programming" }, + { "year": 2, "semester": 1, "courseCode": "ECE20025", "nameKo": "프로그래밍 스튜디오", "nameEn": "Programming Studio" }, + { "year": 2, "semester": 1, "courseCode": "ECE20057", "nameKo": "논리설계", "nameEn": "Logic Design" }, + { "year": 2, "semester": 1, "courseCode": "ECE20064", "nameKo": "회로이론", "nameEn": "Circuit Theory" }, + { "year": 2, "semester": 1, "courseCode": "ECE20065", "nameKo": "기초회로 및 논리실습", "nameEn": "Basic Circuit and Logic Laboratory" }, + { "year": 2, "semester": 2, "courseCode": "ECE20006", "nameKo": "신호및시스템", "nameEn": "Signal and System" }, + { "year": 2, "semester": 2, "courseCode": "ECE20009", "nameKo": "웹서비스개발", "nameEn": "Web Service Development" }, { "year": 2, "semester": 2, "courseCode": "ECE20021", "nameKo": "컴퓨터구조", "nameEn": "Computer Architecture and Organization" }, { "year": 2, "semester": 2, "courseCode": "ECE20022", "nameKo": "컴퓨터비전", "nameEn": "Computer Vision" }, { "year": 2, "semester": 2, "courseCode": "ECE20026", "nameKo": "오픈소스 스튜디오", "nameEn": "Open Source Studio" }, @@ -26,7 +25,7 @@ { "year": 3, "semester": 1, "courseCode": "ECE30012", "nameKo": "객체지향설계패턴", "nameEn": "Object-Oriented Design Pattern" }, { "year": 3, "semester": 1, "courseCode": "ECE30021", "nameKo": "운영체제", "nameEn": "Operating System" }, { "year": 3, "semester": 1, "courseCode": "ECE30030", "nameKo": "데이터베이스", "nameEn": "Data Base" }, - { "year": 3, "semester": 1, "courseCode": "ECE30039", "nameKo": "직업과진로설계(전산전지)", "nameEn": "Vocation and Career Planning (CSEE)" }, + { "year": 3, "semester": 1, "courseCode": "ECE30039", "nameKo": "직업과진로설계", "nameEn": "Vocation and Career Planning" }, { "year": 3, "semester": 1, "courseCode": "ECE30051", "nameKo": "전자회로1", "nameEn": "Electronic Circuits 1" }, { "year": 3, "semester": 1, "courseCode": "ECE30070", "nameKo": "마이크로프로세서응용", "nameEn": "Microprocessor Application" }, @@ -41,15 +40,15 @@ { "year": 4, "semester": 1, "courseCode": "ECE40010", "nameKo": "소프트웨어공학", "nameEn": "Software Engineering" }, { "year": 4, "semester": 1, "courseCode": "ECE40012", "nameKo": "컴파일러이론", "nameEn": "Compiler Theory" }, - { "year": 4, "semester": 1, "courseCode": "ECE40027", "nameKo": "포스트캡스톤 연구", "nameEn": "Post-capstone Research" }, { "year": 4, "semester": 1, "courseCode": "ECE40035", "nameKo": "딥러닝개론", "nameEn": "Introduction to Deep Learning" }, { "year": 4, "semester": 1, "courseCode": "ECE40042", "nameKo": "컴퓨터그래픽스", "nameEn": "Computer Graphics" }, { "year": 4, "semester": 1, "courseCode": "ECE40066", "nameKo": "IoT 실습", "nameEn": "IoT Laboratories" }, { "year": 4, "semester": 1, "courseCode": "ECE40079", "nameKo": "캡스톤디자인2", "nameEn": "Capstone Design 2" }, { "year": 4, "semester": 1, "courseCode": "ECE40097", "nameKo": "특론1", "nameEn": "Special Topic 1" }, + { "year": 4, "semester": 2, "courseCode": "ECE40027", "nameKo": "포스트캡스톤 연구", "nameEn": "Post-capstone Research" }, { "year": 4, "semester": 2, "courseCode": "ECE40013", "nameKo": "지능형 신호처리", "nameEn": "Intelligent Signal Processing" }, - { "year": 4, "semester": 2, "courseCode": "ECE40014", "nameKo": "실전 AI 스튜디오", "nameEn": "Applied Project Studio" }, + { "year": 4, "semester": 2, "courseCode": "ECE40014", "nameKo": "실전 스튜디오", "nameEn": "Applied Project Studio" }, { "year": 4, "semester": 2, "courseCode": "ECE40033", "nameKo": "게임개발", "nameEn": "Game Development" }, { "year": 4, "semester": 2, "courseCode": "ECE40034", "nameKo": "클라우드 컴퓨팅", "nameEn": "Cloud Computing" }, { "year": 4, "semester": 2, "courseCode": "ECE40044", "nameKo": "컴퓨터보안", "nameEn": "Computer Security" }, diff --git a/src/test/java/com/example/gradu/domain/ranking/service/CourseRankingServiceTest.java b/src/test/java/com/example/gradu/domain/ranking/service/CourseRankingServiceTest.java index 5568ec3..e146c28 100644 --- a/src/test/java/com/example/gradu/domain/ranking/service/CourseRankingServiceTest.java +++ b/src/test/java/com/example/gradu/domain/ranking/service/CourseRankingServiceTest.java @@ -30,90 +30,115 @@ void setUp() { } @Test - void getCourseRanking_returnsMajorAndLiberalRankings_withFiltering_andMajorGroupedByTerm() { - // ===== major rows (repo 1st call) ===== + void getCourseRanking_majorBuckets_mergeByWhitespace_and_liberalFiltersAndRanks() { + // ===== major (repo 1st) ===== + // "웹 서비스 개발" + "웹서비스개발" -> 합산되어 한 아이템으로 나와야 함 List majorRows = List.of( - row("Intro to Programming", 12), - row("Discrete Math", 10), - row("Unmapped Major Course", 9) + row("웹 서비스 개발", 3), + row("웹서비스개발", 1), + row("자료구조", 5), + row(" ", 999), // blank -> 무시 분기 + row(null, 777) // null -> 무시 분기 ); - when(roadmapIndex.findTermByCourseName("Intro to Programming")) - .thenReturn(Optional.of(new MajorRoadmapIndex.TermKey(1, 2))); // y1s2 - when(roadmapIndex.findTermByCourseName("Discrete Math")) + // term mapping (전공 버킷 분배) + when(roadmapIndex.findTermByCourseName("웹 서비스 개발")) .thenReturn(Optional.of(new MajorRoadmapIndex.TermKey(2, 1))); // y2s1 - when(roadmapIndex.findTermByCourseName("Unmapped Major Course")) + when(roadmapIndex.findTermByCourseName("웹서비스개발")) + .thenReturn(Optional.of(new MajorRoadmapIndex.TermKey(2, 1))); // y2s1 (same bucket) + when(roadmapIndex.findTermByCourseName("자료구조")) + .thenReturn(Optional.of(new MajorRoadmapIndex.TermKey(2, 1))); // y2s1 + when(roadmapIndex.findTermByCourseName(" ")) + .thenReturn(Optional.empty()); + when(roadmapIndex.findTermByCourseName(null)) .thenReturn(Optional.empty()); - // ===== liberal rows (repo calls 2~5) ===== + // ===== liberal (repo 2~5) ===== + // faith: 채플/공동체리더십훈련은 제외되어야 함 List faithRows = List.of( - row("채플", 30), // 제외 - row("공동체리더십훈련(1)", 25), // 제외 - row("그리스도인과 선교", 20) + row("채플", 100), + row("공동체리더십훈련(1)", 99), + row("그리스도인과 선교", 10) ); - List generalEduRows = List.of( - row("철학의이해", 18), - row("글쓰기", 15) + + // generalEdu: 공백 차이 합산 테스트 + List generalRows = List.of( + row("철학의 이해", 2), + row("철학의이해", 5), + row("글쓰기", 1) ); + List bsmRows = List.of( - row("Calculus1", 100) + row("Calculus1", 50) ); + List freeRows = List.of( - row("자유선택과목", 22) + row("자유선택과목", 7) ); when(repository.findTopCoursesByCategories(anySet(), any(Pageable.class))) .thenReturn( - majorRows, // 1) major (전체) - faithRows, // 2) liberal-faith - generalEduRows, // 3) liberal-generalEdu - bsmRows, // 4) liberal-bsm - freeRows // 5) liberal-freeElective + majorRows, // 1) major + faithRows, // 2) faith + generalRows, // 3) general + bsmRows, // 4) bsm + freeRows // 5) free ); // when CourseRankingDto.RankingResponse res = service.getCourseRanking(); - // then: major buckets - assertThat(res.major().y1s2()).containsExactly( - new CourseRankingDto.RankingItem(1, "Intro to Programming", 12, 0) - ); - assertThat(res.major().y2s1()).containsExactly( - new CourseRankingDto.RankingItem(1, "Discrete Math", 10, 0) + // then - major y2s1: 합산 + 정렬(자료구조 5 vs 웹서비스개발 4) + var y2s1 = res.major().y2s1(); + assertThat(y2s1).hasSize(2); + + assertThat(y2s1.get(0)).isEqualTo( + new CourseRankingDto.RankingItem(1, "자료구조", 5, 0) ); - // Unmapped는 어떤 버킷에도 없어야 함 - boolean existsUnmapped = - res.major().y1s2().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y2s1().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y2s2().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y3s1().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y3s2().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y4s1().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")) - || res.major().y4s2().stream().anyMatch(i -> i.courseName().equals("Unmapped Major Course")); - assertThat(existsUnmapped).isFalse(); - - // then: liberal filtering + rank reassigned + // ✅ displayName은 더 "읽기 좋은" 쪽(보통 공백 포함/긴 쪽) 우선 + // 합산 count = 3 + 1 = 4 + assertThat(y2s1.get(1).rank()).isEqualTo(2); + assertThat(y2s1.get(1).takenCount()).isEqualTo(4); + assertThat(y2s1.get(1).courseName()).isEqualTo("웹 서비스 개발"); + + // 다른 버킷은 빈 리스트여야 함 + assertThat(res.major().y1s2()).isEmpty(); + assertThat(res.major().y2s2()).isEmpty(); + assertThat(res.major().y3s1()).isEmpty(); + assertThat(res.major().y3s2()).isEmpty(); + assertThat(res.major().y4s1()).isEmpty(); + assertThat(res.major().y4s2()).isEmpty(); + + // then - liberal faith: 필터링 후 1개만 남고 rank=1 assertThat(res.liberal().faithWorldview()).containsExactly( - new CourseRankingDto.RankingItem(1, "그리스도인과 선교", 20, 0) + new CourseRankingDto.RankingItem(1, "그리스도인과 선교", 10, 0) + ); + + // then - liberal general: "철학의 이해"(2) + "철학의이해"(5) => 7로 합쳐져 1등 + var general = res.liberal().generalEdu(); + assertThat(general).hasSize(2); + assertThat(general.get(0)).isEqualTo( + new CourseRankingDto.RankingItem(1, "철학의 이해", 7, 0) ); - assertThat(res.liberal().generalEdu()).containsExactly( - new CourseRankingDto.RankingItem(1, "철학의이해", 18, 0), - new CourseRankingDto.RankingItem(2, "글쓰기", 15, 0) + assertThat(general.get(1)).isEqualTo( + new CourseRankingDto.RankingItem(2, "글쓰기", 1, 0) ); + assertThat(res.liberal().bsm()).containsExactly( - new CourseRankingDto.RankingItem(1, "Calculus1", 100, 0) + new CourseRankingDto.RankingItem(1, "Calculus1", 50, 0) ); + assertThat(res.liberal().freeElective()).containsExactly( - new CourseRankingDto.RankingItem(1, "자유선택과목", 22, 0) + new CourseRankingDto.RankingItem(1, "자유선택과목", 7, 0) ); - verify(repository, times(5)) - .findTopCoursesByCategories(anySet(), any(Pageable.class)); + // repo 호출 5번 + verify(repository, times(5)).findTopCoursesByCategories(anySet(), any(Pageable.class)); } @Test - void getCourseRanking_callsRepository_5times_and_usesReasonablePageSizes() { + void getCourseRanking_callsRepository_5times_and_majorPageSize300() { when(repository.findTopCoursesByCategories(anySet(), any(Pageable.class))) .thenReturn(List.of(), List.of(), List.of(), List.of(), List.of()); @@ -123,77 +148,52 @@ void getCourseRanking_callsRepository_5times_and_usesReasonablePageSizes() { ArgumentCaptor> catCaptor = (ArgumentCaptor) ArgumentCaptor.forClass(Set.class); ArgumentCaptor pageableCaptor = ArgumentCaptor.forClass(Pageable.class); - verify(repository, times(5)) - .findTopCoursesByCategories(catCaptor.capture(), pageableCaptor.capture()); + verify(repository, times(5)).findTopCoursesByCategories(catCaptor.capture(), pageableCaptor.capture()); + var cats = catCaptor.getAllValues(); var pages = pageableCaptor.getAllValues(); + + assertThat(cats).hasSize(5); assertThat(pages).hasSize(5); - // page=0 - for (Pageable p : pages) { - assertThat(p.getPageNumber()).isEqualTo(0); - } + // all page=0 + assertThat(pages).allSatisfy(p -> assertThat(p.getPageNumber()).isEqualTo(0)); - // ✅ 핵심 수정: - // major는 roadmap 분류하려고 크게 가져올 수 있음(예: 300) - // liberal 4개는 필터링 때문에 30(혹은 10)일 수 있음 - // -> "10/30/300" 허용으로 테스트를 서비스 정책에 맞춘다. - for (Pageable p : pages) { - assertThat(p.getPageSize()).isIn(10, 30, 300); + // 첫 호출은 major (pageSize=300) + assertThat(pages.get(0).getPageSize()).isEqualTo(300); + + // 나머지 4개는 liberal (서비스 정책상 30 or 10) + for (int i = 1; i < pages.size(); i++) { + assertThat(pages.get(i).getPageSize()).isIn(10, 30); } + + // 첫 cats는 MAJOR 포함이어야 함(엄격히 1개만 들어있을 수도) + assertThat(cats.get(0)).contains(Category.MAJOR); } @Test - void liberalFaith_limitsTo10_afterFiltering_andRanksReassigned() { - // major 1회 + liberal 4회 = 5회 - List majorEmpty = List.of(); - - List faithRows = List.of( - row("채플", 100), - row("공동체리더십훈련", 99), - row("교양1", 98), - row("교양2", 97), - row("교양3", 96), - row("교양4", 95), - row("교양5", 94), - row("교양6", 93), - row("교양7", 92), - row("교양8", 91), - row("교양9", 90), - row("교양10", 89), - row("교양11", 88) + void getCourseRanking_handles_unmappedMajorCourse_gracefully() { + // major에만 값 있고 roadmapIndex가 empty -> 모든 버킷 비어야 함 + List majorRows = List.of( + row("매핑안되는전공", 10) ); + when(roadmapIndex.findTermByCourseName("매핑안되는전공")).thenReturn(Optional.empty()); when(repository.findTopCoursesByCategories(anySet(), any(Pageable.class))) .thenReturn( - majorEmpty, // 1) major - faithRows, // 2) liberal-faith - List.of(), // 3) generalEdu - List.of(), // 4) bsm - List.of() // 5) freeElective + majorRows, + List.of(), List.of(), List.of(), List.of() ); - CourseRankingDto.RankingResponse res = service.getCourseRanking(); - - var faith = res.liberal().faithWorldview(); - assertThat(faith).hasSize(10); - assertThat(faith.get(0)).isEqualTo(new CourseRankingDto.RankingItem(1, "교양1", 98, 0)); - assertThat(faith.get(9)).isEqualTo(new CourseRankingDto.RankingItem(10, "교양10", 89, 0)); - } - - @Test - void majorCourse_isIgnored_whenRoadmapIndexReturnsEmpty() { - when(repository.findTopCoursesByCategories(anySet(), any(Pageable.class))) - .thenReturn(List.of(row("Unknown Major", 10)), - List.of(), List.of(), List.of(), List.of()); - - when(roadmapIndex.findTermByCourseName("Unknown Major")) - .thenReturn(Optional.empty()); - - CourseRankingDto.RankingResponse res = service.getCourseRanking(); + var res = service.getCourseRanking(); assertThat(res.major().y1s2()).isEmpty(); assertThat(res.major().y2s1()).isEmpty(); + assertThat(res.major().y2s2()).isEmpty(); + assertThat(res.major().y3s1()).isEmpty(); + assertThat(res.major().y3s2()).isEmpty(); + assertThat(res.major().y4s1()).isEmpty(); + assertThat(res.major().y4s2()).isEmpty(); } private static CourseRankingRepository.CourseCountRow row(String name, long takenCount) {