diff --git a/build.gradle b/build.gradle index fc82a54..a9fb93f 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-validation' // mysql runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/run/backend/domain/crew/controller/CrewController.java b/src/main/java/run/backend/domain/crew/controller/CrewController.java index 0aa0d01..d70ac7b 100644 --- a/src/main/java/run/backend/domain/crew/controller/CrewController.java +++ b/src/main/java/run/backend/domain/crew/controller/CrewController.java @@ -11,11 +11,13 @@ import run.backend.domain.crew.dto.response.*; import run.backend.domain.crew.entity.Crew; import run.backend.domain.crew.service.CrewEventService; +import run.backend.domain.crew.service.CrewRankingService; import run.backend.domain.crew.service.CrewService; import run.backend.domain.member.entity.Member; import run.backend.global.annotation.member.Login; import run.backend.global.annotation.member.MemberCrew; import run.backend.global.common.response.CommonResponse; +import run.backend.global.common.response.PageResponse; @RestController @RequiredArgsConstructor @@ -25,6 +27,7 @@ public class CrewController { private final CrewService crewService; private final CrewEventService crewEventService; + private final CrewRankingService crewRankingService; @PostMapping @Operation(summary = "크루 생성", description = "크루 생성하는 API 입니다.") @@ -114,10 +117,19 @@ public CommonResponse getUpcomingEvent(@MemberCrew Cr return new CommonResponse<>("크루 다가오는 일정 조회 성공", response); } - @GetMapping("/members") - @Operation(summary = "크루원 조회", description = "크루 내 모든 크루원을 조회하는 API 입니다.") - public CommonResponse getCrewMember(@MemberCrew Crew crew) { + @GetMapping("/rankings") + public CommonResponse> getCrewRankings( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "5") int size) { - return new CommonResponse<>("크루원 조회 성공"); + PageResponse response = crewRankingService.getCrewRanking(page, size); + return new CommonResponse<>("크루 랭킹 조회 성공", response); + } + + @GetMapping("/rankings/status") + public CommonResponse getCrewRankingsStatus(@MemberCrew Crew crew) { + + CrewRankingStatusResponse response = crewRankingService.getCrewRankingStatus(crew); + return new CommonResponse<>("크루 땅따먹기 현황 조회 성공", response); } } diff --git a/src/main/java/run/backend/domain/crew/dto/response/CrewRankingResponse.java b/src/main/java/run/backend/domain/crew/dto/response/CrewRankingResponse.java new file mode 100644 index 0000000..d4a660a --- /dev/null +++ b/src/main/java/run/backend/domain/crew/dto/response/CrewRankingResponse.java @@ -0,0 +1,9 @@ +package run.backend.domain.crew.dto.response; + +public record CrewRankingResponse( + Long crewId, + String name, + String image, + int monthlyScoreTotal +) { +} diff --git a/src/main/java/run/backend/domain/crew/dto/response/CrewRankingStatusResponse.java b/src/main/java/run/backend/domain/crew/dto/response/CrewRankingStatusResponse.java new file mode 100644 index 0000000..cc14e5a --- /dev/null +++ b/src/main/java/run/backend/domain/crew/dto/response/CrewRankingStatusResponse.java @@ -0,0 +1,8 @@ +package run.backend.domain.crew.dto.response; + +public record CrewRankingStatusResponse( + int ranking, + int totalDistanceKm, + int capturedDistanceKm +) { +} diff --git a/src/main/java/run/backend/domain/crew/entity/Crew.java b/src/main/java/run/backend/domain/crew/entity/Crew.java index 31b8f2d..40ee194 100644 --- a/src/main/java/run/backend/domain/crew/entity/Crew.java +++ b/src/main/java/run/backend/domain/crew/entity/Crew.java @@ -1,10 +1,7 @@ package run.backend.domain.crew.entity; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import run.backend.global.common.BaseEntity; import java.math.BigDecimal; @@ -13,6 +10,7 @@ @Entity @Getter @Table(name = "crews") +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Crew extends BaseEntity { @@ -35,11 +33,14 @@ public class Crew extends BaseEntity { @Column(name = "monthly_distance_total") private BigDecimal monthlyDistanceTotal; + @Column(name = "captured_distance_total") + private BigDecimal capturedDistanceTotal; + @Column(name = "monthly_time_total") private Long monthlyTimeTotal; @Column(name = "monthly_score_total") - private BigDecimal monthlyScoreTotal; + private BigDecimal monthlyScoreTotal; // monthlyDistanceTotal(70%) + capturedDistanceTotal(30%) public void incrementMemberCount() { this.memberCount++; @@ -69,6 +70,7 @@ public Crew ( this.inviteCode = UUID.randomUUID().toString(); this.memberCount = 1L; this.monthlyDistanceTotal = BigDecimal.ZERO; + this.capturedDistanceTotal = BigDecimal.ZERO; this.monthlyTimeTotal = 0L; this.monthlyScoreTotal = BigDecimal.ZERO; } diff --git a/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java b/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java index d58b5d2..447f22c 100644 --- a/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java +++ b/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java @@ -1,15 +1,32 @@ package run.backend.domain.crew.mapper; -import org.springframework.stereotype.Component; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; import run.backend.domain.crew.dto.response.CrewBaseInfoResponse; import run.backend.domain.crew.dto.response.CrewProfileResponse; +import run.backend.domain.crew.dto.response.CrewRankingResponse; +import run.backend.domain.crew.dto.response.CrewRankingStatusResponse; import run.backend.domain.crew.entity.Crew; import run.backend.domain.member.entity.Member; -@Component -public class CrewMapper { +import java.util.List; - public Crew toEntity(String imageName, String name, String description) { +@Mapper( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.IGNORE +) +public interface CrewMapper { + + @Mapping(target = "rank", source = "rank") + CrewBaseInfoResponse toCrewBaseInfo(int rank, Crew crew); + + @Mapping(target = "monthlyScoreTotal", expression = "java(crew.getMonthlyScoreTotal().intValue())") + CrewRankingResponse toCrewRankingResponse(Crew crew); + + List toCrewRankingResponseList(List crews); + + default Crew toEntity(String imageName, String name, String description) { return Crew.builder() .image(imageName) .name(name) @@ -17,7 +34,7 @@ public Crew toEntity(String imageName, String name, String description) { .build(); } - public CrewProfileResponse toCrewProfile(Crew crew, Member leader) { + default CrewProfileResponse toCrewProfile(Crew crew, Member leader) { return CrewProfileResponse.builder() .crewImage(crew.getImage()) .crewName(crew.getName()) @@ -28,15 +45,14 @@ public CrewProfileResponse toCrewProfile(Crew crew, Member leader) { .build(); } - public CrewBaseInfoResponse toCrewBaseInfo(int rank, Crew crew) { - return CrewBaseInfoResponse.builder() - .rank(rank) - .image(crew.getImage()) - .name(crew.getName()) - .description(crew.getDescription()) - .memberCount(crew.getMemberCount()) - .monthlyDistanceTotal(crew.getMonthlyDistanceTotal()) - .monthlyTimeTotal(crew.getMonthlyTimeTotal()) - .build(); + default CrewRankingStatusResponse toCrewRankingStatusResponse( + int rank, + Crew crew + ) { + return new CrewRankingStatusResponse( + rank, + crew.getMonthlyDistanceTotal().intValue(), + crew.getCapturedDistanceTotal().intValue() + ); } } diff --git a/src/main/java/run/backend/domain/crew/repository/CrewRepository.java b/src/main/java/run/backend/domain/crew/repository/CrewRepository.java index e6992eb..6c53f0e 100644 --- a/src/main/java/run/backend/domain/crew/repository/CrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/CrewRepository.java @@ -1,5 +1,7 @@ package run.backend.domain.crew.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import run.backend.domain.crew.entity.Crew; @@ -8,4 +10,7 @@ public interface CrewRepository extends JpaRepository { Optional findByInviteCode(String inviteCode); + + + Page findAllByOrderByMonthlyScoreTotalDesc(Pageable pageable); } diff --git a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java index a971cc8..fd551ba 100644 --- a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java @@ -16,6 +16,8 @@ public interface JoinCrewRepository extends JpaRepository { boolean existsByMemberAndJoinStatus(Member member, JoinStatus joinStatus); + Optional findByMember(Member member); + @Query(""" SELECT jc.member FROM JoinCrew jc diff --git a/src/main/java/run/backend/domain/crew/service/CrewRankingService.java b/src/main/java/run/backend/domain/crew/service/CrewRankingService.java new file mode 100644 index 0000000..8001892 --- /dev/null +++ b/src/main/java/run/backend/domain/crew/service/CrewRankingService.java @@ -0,0 +1,39 @@ +package run.backend.domain.crew.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import run.backend.domain.crew.dto.response.CrewRankingResponse; +import run.backend.domain.crew.dto.response.CrewRankingStatusResponse; +import run.backend.domain.crew.entity.Crew; +import run.backend.domain.crew.mapper.CrewMapper; +import run.backend.domain.crew.repository.CrewRepository; +import run.backend.global.common.response.PageResponse; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CrewRankingService { + + private final CrewMapper crewMapper; + private final CrewRepository crewRepository; + + public PageResponse getCrewRanking(int page, int size) { + + Page pageResult = crewRepository.findAllByOrderByMonthlyScoreTotalDesc(PageRequest.of(page, size)); + List content = crewMapper.toCrewRankingResponseList(pageResult.getContent()); + + return PageResponse.toPageResponse(pageResult, content); + } + + public CrewRankingStatusResponse getCrewRankingStatus(Crew crew) { + + int rank = 0; // [TODO] : 스케줄링 rank 계산 구현 수정 예정 + + return crewMapper.toCrewRankingStatusResponse(rank, crew); + } +} diff --git a/src/main/java/run/backend/domain/member/controller/MemberController.java b/src/main/java/run/backend/domain/member/controller/MemberController.java index c9b8422..a29a3f2 100644 --- a/src/main/java/run/backend/domain/member/controller/MemberController.java +++ b/src/main/java/run/backend/domain/member/controller/MemberController.java @@ -6,9 +6,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import run.backend.domain.member.dto.request.MemberInfoRequest; +import run.backend.domain.member.dto.response.MemberCrewStatusResponse; import run.backend.domain.member.dto.response.MemberInfoResponse; import run.backend.domain.member.entity.Member; -import run.backend.domain.member.service.MemberServiceImpl; +import run.backend.domain.member.service.MemberService; import run.backend.global.annotation.member.Login; import run.backend.global.common.response.CommonResponse; @@ -18,18 +19,18 @@ @Tag(name = "Members", description = "Member 관련 API") public class MemberController { - private final MemberServiceImpl memberService; + private final MemberService memberService; - @Operation(summary = "유저 정보 조회", description = "마이페이지 상단 유저 정보를 조회하는 API 입니다.") @GetMapping + @Operation(summary = "유저 정보 조회", description = "마이페이지 상단 유저 정보를 조회하는 API 입니다.") public CommonResponse getMemberInfo(@Login Member member) { MemberInfoResponse response = memberService.getMemberInfo(member); return new CommonResponse<>("유저 정보 조회 성공", response); } - @Operation(summary = "유저 정보 수정", description = "마이페이지에서 유저 정보를 수정하는 API 입니다.") @PostMapping + @Operation(summary = "유저 정보 수정", description = "마이페이지에서 유저 정보를 수정하는 API 입니다.") public CommonResponse updateMemberInfo( @Login Member member, @RequestParam String imageStatus, @@ -39,4 +40,12 @@ public CommonResponse updateMemberInfo( memberService.updateMemberInfo(member, imageStatus, image, data); return new CommonResponse<>("유저 정보 수정 성공"); } + + @GetMapping("/me/crews/exists") + @Operation(summary = "가입된 크루가 있는지 확인", description = "유저가 가입된 크루가 있는지 확인하는 API 입니다.") + public CommonResponse getMembersCrewExists(@Login Member member) { + + MemberCrewStatusResponse response = memberService.getMembersCrewExists(member); + return new CommonResponse<>("유저 크루 가입 여부 조회 완료", response); + } } diff --git a/src/main/java/run/backend/domain/member/dto/response/MemberCrewStatusResponse.java b/src/main/java/run/backend/domain/member/dto/response/MemberCrewStatusResponse.java new file mode 100644 index 0000000..6070d51 --- /dev/null +++ b/src/main/java/run/backend/domain/member/dto/response/MemberCrewStatusResponse.java @@ -0,0 +1,10 @@ +package run.backend.domain.member.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record MemberCrewStatusResponse( + + @Schema(description = "유저의 크루 가입 상태 (NONE: 가입된 크루 없음, APPLIED: 가입 대기 중, APPROVED: 가입 완료") + String status +) { +} diff --git a/src/main/java/run/backend/domain/member/service/MemberService.java b/src/main/java/run/backend/domain/member/service/MemberService.java index 0de9444..e6e82a9 100644 --- a/src/main/java/run/backend/domain/member/service/MemberService.java +++ b/src/main/java/run/backend/domain/member/service/MemberService.java @@ -1,21 +1,62 @@ package run.backend.domain.member.service; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import run.backend.domain.crew.entity.Crew; +import run.backend.domain.crew.entity.JoinCrew; +import run.backend.domain.crew.enums.JoinStatus; +import run.backend.domain.crew.repository.JoinCrewRepository; +import run.backend.domain.file.service.FileService; import run.backend.domain.member.dto.request.MemberInfoRequest; +import run.backend.domain.member.dto.response.MemberCrewStatusResponse; import run.backend.domain.member.dto.response.MemberInfoResponse; import run.backend.domain.member.entity.Member; +import run.backend.domain.member.repository.MemberRepository; -public interface MemberService { +import java.util.Optional; - MemberInfoResponse getMemberInfo(Member member); +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberService { - void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data); + private final FileService fileService; + private final MemberRepository memberRepository; + private final JoinCrewRepository joinCrewRepository; -// void updateMember(); + public MemberInfoResponse getMemberInfo(Member member) { -// void deleteMember(Member member); -// -// void leaveCrew(Member member, Long crewId); -// -// void joinCrew(String crewCode); + String crewName = memberRepository.findCrewByMemberIdAndStatus(member.getId(), JoinStatus.APPROVED) + .map(Crew::getName) + .orElse("N/A"); + return new MemberInfoResponse(member.getProfileImage(), member.getNickname(), crewName); + } + + @Transactional + public void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data) { + + String newImageName = fileService.handleImageUpdate(imageStatus, member.getProfileImage(), image); + member.updateImage(newImageName); + + if (data.gender() != null) + member.updateGender(data.gender()); + if (data.age() != null) + member.updateAge(data.age()); + if (data.nickname() != null) + member.updateNickname(data.nickname()); + + memberRepository.save(member); + } + + public MemberCrewStatusResponse getMembersCrewExists(Member member) { + + Optional joinCrew = joinCrewRepository.findByMember(member); + + if (joinCrew.isEmpty()) { + return new MemberCrewStatusResponse("NONE"); + } + return new MemberCrewStatusResponse(joinCrew.get().getJoinStatus().toString()); + } } diff --git a/src/main/java/run/backend/domain/member/service/MemberServiceImpl.java b/src/main/java/run/backend/domain/member/service/MemberServiceImpl.java deleted file mode 100644 index eb96c68..0000000 --- a/src/main/java/run/backend/domain/member/service/MemberServiceImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -package run.backend.domain.member.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import run.backend.domain.crew.entity.Crew; -import run.backend.domain.crew.enums.JoinStatus; -import run.backend.domain.file.service.FileService; -import run.backend.domain.member.dto.request.MemberInfoRequest; -import run.backend.domain.member.dto.response.MemberInfoResponse; -import run.backend.domain.member.entity.Member; -import run.backend.domain.member.repository.MemberRepository; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MemberServiceImpl implements MemberService { - - private final MemberRepository memberRepository; - private final FileService fileService; - - @Override - public MemberInfoResponse getMemberInfo(Member member) { - - String crewName = memberRepository.findCrewByMemberIdAndStatus(member.getId(), JoinStatus.APPROVED) - .map(Crew::getName) - .orElse("N/A"); - return new MemberInfoResponse(member.getProfileImage(), member.getNickname(), crewName); - } - - @Override - @Transactional - public void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data) { - - switch (imageStatus) { - - case "updated" : - fileService.deleteImage(member.getProfileImage()); // 기존 이미지 지우기 - String newImageName = fileService.saveProfileImage(image); // 새로운 이미지 저장 - member.updateImage(newImageName); - break ; - case "removed" : - fileService.deleteImage(member.getProfileImage()); - member.updateImage("default-profile-image.png"); - break ; - } - - if (data.gender() != null) - member.updateGender(data.gender()); - if (data.age() != null) - member.updateAge(data.age()); - if (data.nickname() != null) - member.updateNickname(data.nickname()); - - memberRepository.save(member); - } -} diff --git a/src/main/java/run/backend/global/common/response/PageResponse.java b/src/main/java/run/backend/global/common/response/PageResponse.java new file mode 100644 index 0000000..980c921 --- /dev/null +++ b/src/main/java/run/backend/global/common/response/PageResponse.java @@ -0,0 +1,26 @@ +package run.backend.global.common.response; + +import org.springframework.data.domain.Page; + +import java.util.List; + +public record PageResponse( + int curPage, + int curElements, + int totalPages, + long totalElements, + boolean isLast, + List content +) { + + public static PageResponse toPageResponse(Page page, List content) { + return new PageResponse<>( + page.getNumber(), + content.size(), + page.getTotalPages(), + page.getTotalElements(), + page.isLast(), + content + ); + } +} \ No newline at end of file diff --git a/src/main/java/run/backend/global/config/QuerydslConfig.java b/src/main/java/run/backend/global/config/QuerydslConfig.java new file mode 100644 index 0000000..61f1736 --- /dev/null +++ b/src/main/java/run/backend/global/config/QuerydslConfig.java @@ -0,0 +1,18 @@ +package run.backend.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} \ No newline at end of file diff --git a/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java b/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java new file mode 100644 index 0000000..6a10698 --- /dev/null +++ b/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java @@ -0,0 +1,101 @@ +package run.backend.domain.crew.mapper; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import run.backend.domain.crew.dto.response.CrewProfileResponse; +import run.backend.domain.crew.dto.response.CrewRankingStatusResponse; +import run.backend.domain.crew.entity.Crew; +import run.backend.domain.member.entity.Member; +import run.backend.domain.member.enums.Gender; +import run.backend.domain.member.enums.OAuthType; + +import java.math.BigDecimal; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("CrewMapper 테스트") +public class CrewMapperTest { + + private final CrewMapper crewMapper = new CrewMapperImpl(); + + private Crew crew1; + + @BeforeEach + void setUp() { + + crew1 = new Crew( + 1L, + "crew_name", + "crew_description", + "crew_image", + "invitecode", + 2L, + BigDecimal.valueOf(123), + BigDecimal.valueOf(30), + 50L, + BigDecimal.valueOf(70) + ); + } + + @Test + @DisplayName("정상적으로 Crew로 매핑된다") + void toCrewEntity_mapsCorrectly() { + + // given + String imageName = "image_url"; + String name = "crew_name"; + String description = "crew_description"; + + // when + Crew result = crewMapper.toEntity(imageName, name, description); + + // then + assertThat(result.getImage()).isEqualTo(imageName); + assertThat(result.getName()).isEqualTo(name); + assertThat(result.getDescription()).isEqualTo(description); + } + + @Test + @DisplayName("정상적으로 CrewProfileResponse로 매핑된다") + void toCrewProfileResponse_mapsCorrectly() { + + // given + Member leader = Member.builder() + .username("leader123") + .nickname("철수") + .gender(Gender.MALE) + .age(30) + .oauthId("oauth-123") + .oauthType(OAuthType.GOOGLE) + .profileImage("leader.png") + .build(); + + // when + CrewProfileResponse result = crewMapper.toCrewProfile(crew1, leader); + + // then + assertThat(result.crewImage()).isEqualTo(crew1.getImage()); + assertThat(result.crewName()).isEqualTo(crew1.getName()); + assertThat(result.crewDescription()).isEqualTo(crew1.getDescription()); + assertThat(result.memberCount()).isEqualTo(crew1.getMemberCount()); + assertThat(result.leaderImage()).isEqualTo(leader.getProfileImage()); + assertThat(result.leaderName()).isEqualTo(leader.getNickname()); + } + + @Test + @DisplayName("정상적으로 CrewRankingStatusResponse로 매핑된다") + void toCrewRankingStatusResponse_mapsCorrectly() { + + // given + int rank = 1; + + // when + CrewRankingStatusResponse result = crewMapper.toCrewRankingStatusResponse(rank, crew1); + + // then + assertThat(result.ranking()).isEqualTo(1); + assertThat(result.totalDistanceKm()).isEqualTo(123); + assertThat(result.capturedDistanceKm()).isEqualTo(30); + } +} diff --git a/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java b/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java new file mode 100644 index 0000000..3ef643b --- /dev/null +++ b/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java @@ -0,0 +1,126 @@ +package run.backend.domain.crew.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import run.backend.domain.crew.dto.response.CrewRankingResponse; +import run.backend.domain.crew.dto.response.CrewRankingStatusResponse; +import run.backend.domain.crew.entity.Crew; +import run.backend.domain.crew.mapper.CrewMapper; +import run.backend.domain.crew.repository.CrewRepository; +import run.backend.global.common.response.PageResponse; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@DisplayName("Crew Ranking 서비스 테스트") +@ExtendWith(MockitoExtension.class) +public class CrewRankingServiceTest { + + @InjectMocks + private CrewRankingService crewRankingService; + + @Mock + private CrewRepository crewRepository; + + @Mock + private CrewMapper crewMapper; + + private Crew crew1; + private Crew crew2; + private Crew crew3; + private Crew crew4; + private Crew crew5; + + @BeforeEach + void setUp() { + crew1 = Crew.builder() + .name("crew1") + .build(); + crew2 = Crew.builder() + .name("crew2") + .build(); + crew3 = Crew.builder() + .name("crew3") + .build(); + crew4 = Crew.builder() + .name("crew4") + .build(); + crew5 = Crew.builder() + .name("crew5") + .build(); + } + + @Nested + @DisplayName("getCrewRanking 메서드는") + class getCrewRankingTest { + + @Test + @DisplayName("페이지 정보를 기반으로 Crew 랭킹 응답을 반환한다") + void getCrewRanking_whenValidPageRequest_thenReturnsPageResponse() { + + // given + int page = 0; + int size = 5; + + List crews = List.of(crew1, crew2, crew3, crew4, crew5); + Page crewPage = new PageImpl<>(crews, PageRequest.of(page, size), crews.size()); + + List responseList = List.of( + new CrewRankingResponse(1L, "name1", "image1", 5), + new CrewRankingResponse(2L, "name2", "image2", 4), + new CrewRankingResponse(3L, "name3", "image3", 3), + new CrewRankingResponse(4L, "name4", "image4", 2), + new CrewRankingResponse(5L, "name5", "image5", 1) + ); + + when(crewRepository.findAllByOrderByMonthlyScoreTotalDesc(PageRequest.of(page, size))) + .thenReturn(crewPage); + when(crewMapper.toCrewRankingResponseList(crewPage.getContent())) + .thenReturn(responseList); + + // when + PageResponse result = crewRankingService.getCrewRanking(page, size); + + // then + assertThat(result.curPage()).isEqualTo(0); + assertThat(result.curElements()).isEqualTo(5); + assertThat(result.totalPages()).isEqualTo(1); + assertThat(result.totalElements()).isEqualTo(5); + assertThat(result.isLast()).isTrue(); + } + } + + @Nested + @DisplayName("getCrewRankingStatus 메서드는") + class getCrewRankingStatusTest { + + @Test + @DisplayName("정상적으로 Crew 랭킹 상태를 반환한다") + void getCrewRankingStatus_whenValidCrewGiven_thenReturnsStatusResponse() { + + // given + int rank = 0; + CrewRankingStatusResponse expectedResponse = new CrewRankingStatusResponse(rank, 0, 0); + + when(crewMapper.toCrewRankingStatusResponse(rank, crew1)) + .thenReturn(expectedResponse); + + // when + CrewRankingStatusResponse result = crewRankingService.getCrewRankingStatus(crew1); + + // then + assertThat(result).isEqualTo(expectedResponse); + } + } +} diff --git a/src/test/java/run/backend/domain/member/service/MemberServiceTest.java b/src/test/java/run/backend/domain/member/service/MemberServiceTest.java index ce2bf76..c206c79 100644 --- a/src/test/java/run/backend/domain/member/service/MemberServiceTest.java +++ b/src/test/java/run/backend/domain/member/service/MemberServiceTest.java @@ -10,9 +10,12 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; import run.backend.domain.crew.entity.Crew; +import run.backend.domain.crew.entity.JoinCrew; import run.backend.domain.crew.enums.JoinStatus; +import run.backend.domain.crew.repository.JoinCrewRepository; import run.backend.domain.file.service.FileService; import run.backend.domain.member.dto.request.MemberInfoRequest; +import run.backend.domain.member.dto.response.MemberCrewStatusResponse; import run.backend.domain.member.dto.response.MemberInfoResponse; import run.backend.domain.member.entity.Member; import run.backend.domain.member.enums.Gender; @@ -31,10 +34,10 @@ public class MemberServiceTest { private MemberRepository memberRepository; @Mock - private FileService fileService; + private JoinCrewRepository joinCrewRepository; @InjectMocks - private MemberServiceImpl memberService; + private MemberService memberService; private Member testMember; private Crew testCrew; @@ -83,43 +86,32 @@ public void getMemberInfoTest() { } @Test - @DisplayName("회원 정보 수정 - 이미지 업로드") - public void updateMemberInfo_whenImageUpdated() { + void getMembersCrewExists_shouldReturnNone_whenNoJoinCrew() { - // given - String imageStatus = "updated"; - String newImagename = "newImage"; - MultipartFile image = new MockMultipartFile( - "newImage", - "newImage.png", - "image/png", - "dummy image content".getBytes()); - - when(fileService.saveProfileImage(image)).thenReturn(newImagename); + // Given + Member member = mock(Member.class); + when(joinCrewRepository.findByMember(member)).thenReturn(Optional.empty()); - // when - memberService.updateMemberInfo(testMember, imageStatus, image, data); + // When + MemberCrewStatusResponse response = memberService.getMembersCrewExists(member); - // then - verify(fileService).deleteImage("test image"); - verify(fileService).saveProfileImage(image); - verify(testMember).updateImage(newImagename); - verify(memberRepository).save(testMember); + // Then + assertEquals("NONE", response.status()); } @Test - @DisplayName("회원 정보 수정 - 이미지 삭제") - public void updateMemberInfo_whenImageRemoved() { + void getMembersCrewExists_shouldReturnJoinStatus_whenJoinCrewExists() { - // given - String imageStatus = "removed"; + // Given + Member member = mock(Member.class); + JoinCrew joinCrew = mock(JoinCrew.class); + when(joinCrew.getJoinStatus()).thenReturn(JoinStatus.APPROVED); + when(joinCrewRepository.findByMember(member)).thenReturn(Optional.of(joinCrew)); - // when - memberService.updateMemberInfo(testMember, imageStatus, null, data); + // When + MemberCrewStatusResponse response = memberService.getMembersCrewExists(member); - // then - verify(fileService).deleteImage("test image"); - verify(testMember).updateImage("default-profile-image.png"); - verify(memberRepository).save(testMember); + // Then + assertEquals("APPROVED", response.status()); } }