Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface StudyRepositoryCustom {
List<Study> findByStudyTheme(List<StudyTheme> studyThemes);

List<Study> findByStudyThemeAndNotInIds(List<StudyTheme> studyThemes, List<Long> studyIds);
List<Study> findByRegionStudyAndNotInIds(List<RegionStudy> regionStudies, List<Long> studyIds);

// 모집중 스터디 조회
List<Study> findRecruitingStudyByConditions(Map<String, Object> search, StudySortBy sortBy, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,18 @@ public List<Study> findByStudyThemeAndNotInIds(List<StudyTheme> studyThemes,
return queryFactory.selectFrom(study)
.where(study.studyThemes.any().in(studyThemes))
.where(study.id.notIn(studyIds))
.orderBy(study.createdAt.desc())
.orderBy(study.hitNum.desc())
.offset(0)
.limit(3)
.fetch();
}

@Override
public List<Study> findByRegionStudyAndNotInIds(List<RegionStudy> regionStudies, List<Long> studyIds) {
return queryFactory.selectFrom(study)
.where(study.regionStudies.any().in(regionStudies))
.where(study.id.notIn(studyIds))
.orderBy(study.heartCount.desc())
.offset(0)
.limit(3)
.fetch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@
import com.example.spot.web.dto.study.response.StudyScheduleResponseDTO.StudyScheduleDTO;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -254,33 +257,66 @@ public StudyPreviewDTO findRecommendStudies(Long memberId) {

// 회원 관심사 조회
List<MemberTheme> memberThemes = memberThemeRepository.findAllByMemberId(memberId);
List<PreferredRegion> preferredRegions = preferredRegionRepository.findAllByMemberId(memberId);

// 회원 관심사가 없을 경우
if (memberThemes.isEmpty())
throw new MemberHandler(ErrorStatus._STUDY_THEME_IS_INVALID);

// MemberId로 회원 관심사 전체 조회
if (preferredRegions.isEmpty())
throw new MemberHandler(ErrorStatus._STUDY_REGION_IS_INVALID);

// MemberId로 회원 관심사 및 관심 지역 전체 조회
List<Theme> themes = memberThemes.stream()
.map(MemberTheme::getTheme)
.toList();

List<Region> regions = preferredRegions.stream()
.map(PreferredRegion::getRegion)
.toList();

// 회원 관심사로 스터디 테마 조회
List<StudyTheme> studyThemes = themes.stream()
.flatMap(theme -> studyThemeRepository.findAllByTheme(theme).stream())
.toList();

// 회원 관심 지역으로 스터디 지역 조회
List<RegionStudy> regionStudies = regions.stream()
.flatMap(region -> regionStudyRepository.findAllByRegion(region).stream())
.toList();

// 해당 관심사에 해당하는 스터디가 존재하지 않을 경우
if (studyThemes.isEmpty())
throw new StudyHandler(ErrorStatus._STUDY_THEME_NOT_EXIST);

// 해당 관심 지역에 해당하는 스터디가 존재하지 않을 경우
if (regionStudies.isEmpty())
throw new StudyHandler(ErrorStatus._STUDY_REGION_NOT_EXIST);

// 회원 관심사로 추천 스터디 조회
List<Study> studies = studyRepository.findByStudyThemeAndNotInIds(studyThemes, memberOngoingStudyIds);
List<Study> preferThemeStudies = studyRepository.findByStudyThemeAndNotInIds(studyThemes, memberOngoingStudyIds);

// 회원 관심 지역으로 추천 스터디 조회
List<Study> preferRegionStudies = studyRepository.findByRegionStudyAndNotInIds(regionStudies, memberOngoingStudyIds);

// 추천 스터디가 없을 경우
if (studies.isEmpty())
if (preferRegionStudies.isEmpty() || preferThemeStudies.isEmpty())
throw new StudyHandler(ErrorStatus._STUDY_IS_NOT_MATCH);

return getDTOs(studies, Pageable.unpaged(), studies.size(), memberId);
// 두 리스트를 합쳐서 중복 제거
Set<Study> combinedStudies = new HashSet<>(preferThemeStudies);
combinedStudies.addAll(preferRegionStudies);

// 리스트 변환
List<Study> studyList = new ArrayList<>(combinedStudies);

// 랜덤으로 최대 3개 선택
Collections.shuffle(studyList);
List<Study> selectedStudies = studyList.stream()
.limit(3)
.collect(Collectors.toList());

return getDTOs(selectedStudies, Pageable.unpaged(), selectedStudies.size(), memberId);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,17 @@ void findRecommendStudies() {

// Mock the memberThemeRepository to return a list of MemberTheme
when(memberThemeRepository.findAllByMemberId(member.getId())).thenReturn(List.of(memberTheme1, memberTheme2));
when(preferredRegionRepository.findAllByMemberId(member.getId())).thenReturn(List.of(preferredRegion1, preferredRegion2));

when(studyThemeRepository.findAllByTheme(theme1)).thenReturn(List.of(studyTheme1));
when(studyThemeRepository.findAllByTheme(theme2)).thenReturn(List.of(studyTheme2));

when(regionStudyRepository.findAllByRegion(region1)).thenReturn(List.of(regionStudy1));
when(regionStudyRepository.findAllByRegion(region2)).thenReturn(List.of(regionStudy2));

// Mocking the studyRepository to return studies based on the study themes
when(studyRepository.findByStudyThemeAndNotInIds(anyList(), anyList())).thenReturn(List.of(study1, study2));
when(studyRepository.findByRegionStudyAndNotInIds(anyList(), anyList())).thenReturn(List.of(study1, study2));

when(memberRepository.existsById(member.getId())).thenReturn(true);

Expand All @@ -422,13 +427,17 @@ void findRecommendStudiesOnFail() {
// given

when(memberThemeRepository.findAllByMemberId(member.getId())).thenReturn(List.of(memberTheme1, memberTheme2));

when(preferredRegionRepository.findAllByMemberId(member.getId())).thenReturn(List.of(preferredRegion1, preferredRegion2));

when(studyThemeRepository.findAllByTheme(theme1)).thenReturn(List.of(studyTheme1));
when(studyThemeRepository.findAllByTheme(theme2)).thenReturn(List.of(studyTheme2));

when(regionStudyRepository.findAllByRegion(region1)).thenReturn(List.of(regionStudy1));
when(regionStudyRepository.findAllByRegion(region2)).thenReturn(List.of(regionStudy2));

// Mocking the studyRepository to return studies based on the study themes
when(studyRepository.findByStudyThemeAndNotInIds(anyList(), anyList())).thenReturn(List.of());
when(studyRepository.findByRegionStudyAndNotInIds(anyList(), anyList())).thenReturn(List.of());

when(memberRepository.existsById(member.getId())).thenReturn(true);

Expand All @@ -443,18 +452,46 @@ void findRecommendStudiesOnFail() {
}

@Test
@DisplayName("추천 스터디 조회 - 회원의 관심 테마에_해당하는_스터디가 없는 경우")
@DisplayName("추천 스터디 조회 - 회원의 관심 테마에 해당하는 스터디가 없는 경우")
void 추천_스터디_조회_시_회원의_관심_테마에_해당하는_스터디가_없는_경우() {
// given
when(memberThemeRepository.findAllByMemberId(member.getId())).thenReturn(List.of(memberTheme1, memberTheme2));
when(preferredRegionRepository.findAllByMemberId(member.getId())).thenReturn(List.of(preferredRegion1, preferredRegion2));

when(studyThemeRepository.findAllByTheme(theme1)).thenReturn(List.of());
when(studyThemeRepository.findAllByTheme(theme2)).thenReturn(List.of());

when(regionStudyRepository.findAllByRegion(region1)).thenReturn(List.of(regionStudy1));
when(regionStudyRepository.findAllByRegion(region2)).thenReturn(List.of(regionStudy2));

// when & then
assertThrows(StudyHandler.class, () -> {
studyQueryService.findRecommendStudies(member.getId());
});

verify(memberThemeRepository).findAllByMemberId(member.getId());
verify(studyThemeRepository, times(1)).findAllByTheme(theme1);
verify(studyThemeRepository, times(1)).findAllByTheme(theme2);
}

@Test
@DisplayName("추천 스터디 조회 - 회원의 관심 지역에 해당하는 스터디가 없는 경우")
void 추천_스터디_조회_시_회원의_관심_지역에_해당하는_스터디가_없는_경우() {
// given
when(memberThemeRepository.findAllByMemberId(member.getId())).thenReturn(List.of(memberTheme1, memberTheme2));
when(preferredRegionRepository.findAllByMemberId(member.getId())).thenReturn(List.of(preferredRegion1, preferredRegion2));

when(studyThemeRepository.findAllByTheme(theme1)).thenReturn(List.of(studyTheme1));
when(studyThemeRepository.findAllByTheme(theme2)).thenReturn(List.of(studyTheme2));

when(regionStudyRepository.findAllByRegion(region1)).thenReturn(List.of());
when(regionStudyRepository.findAllByRegion(region2)).thenReturn(List.of());

// when & then
assertThrows(StudyHandler.class, () -> {
studyQueryService.findRecommendStudies(member.getId());
});

verify(memberThemeRepository).findAllByMemberId(member.getId());
verify(studyThemeRepository, times(1)).findAllByTheme(theme1);
verify(studyThemeRepository, times(1)).findAllByTheme(theme2);
Expand All @@ -468,9 +505,9 @@ void findRecommendStudiesOnInvalidUser() {
Member member = getMember();

// when & then
when(memberRepository.findById(member.getId())).thenReturn(Optional.empty());
when(memberRepository.existsById(member.getId())).thenReturn(false);

assertThrows(StudyHandler.class, () -> {
assertThrows(MemberHandler.class, () -> {
studyQueryService.findRecommendStudies(member.getId());
});
}
Expand All @@ -492,6 +529,23 @@ void findRecommendStudiesOnNoInterest() {
});
}

@Test
@DisplayName("추천 스터디 조회 - 사용자의 관심 지역이 없는 경우")
void findRecommendStudiesOnNoRegion() {
// given
Member member = getMember();
Long memberId = member.getId();

// Mock the preferredRegionRepository to return an empty list
when(preferredRegionRepository.findAllByMemberId(memberId)).thenReturn(List.of());

// when & then
assertThrows(MemberHandler.class, () -> {
studyQueryService.findRecommendStudies(memberId);
});

}

/* -------------------------------------------------------- 관심 Best 스터디 조회 ------------------------------------------------------------------------*/
@Test
@DisplayName("관심 Best 스터디 조회 - 성공")
Expand Down