diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java index a0a02000..ca60bc25 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java @@ -3,6 +3,7 @@ import ddingdong.ddingdongBE.auth.PrincipalDetails; import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedRequest; import ddingdong.ddingdongBE.domain.feed.controller.dto.request.UpdateFeedRequest; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.ClubMonthlyStatusResponse; import ddingdong.ddingdongBE.domain.feed.controller.dto.response.MyFeedPageResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -11,6 +12,8 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -67,4 +70,16 @@ MyFeedPageResponse getMyFeedPage( @RequestParam(value = "size", defaultValue = "9") int size, @RequestParam(value = "currentCursorId", defaultValue = "-1") Long currentCursorId ); + + @Operation(summary = "동아리 이달의 현황 조회 API") + @ApiResponse(responseCode = "200", description = "동아리 이달의 현황 조회 성공", + content = @Content(schema = @Schema(implementation = ClubMonthlyStatusResponse.class))) + @ResponseStatus(HttpStatus.OK) + @SecurityRequirement(name = "AccessToken") + @GetMapping("/feeds/status") + ClubMonthlyStatusResponse getFeedStatus( + @AuthenticationPrincipal PrincipalDetails principalDetails, + @RequestParam("year") @Min(value = 2000, message = "year는 2000 이상이어야 합니다.") @Max(value = 2100, message = "year는 2100 이하여야 합니다.") int year, + @RequestParam("month") @Min(value = 1, message = "month는 1 이상이어야 합니다.") @Max(value = 12, message = "month는 12 이하여야 합니다.") int month + ); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedController.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedController.java index 9891988d..4d1b5388 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedController.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedController.java @@ -4,18 +4,24 @@ import ddingdong.ddingdongBE.domain.feed.api.ClubFeedApi; import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedRequest; import ddingdong.ddingdongBE.domain.feed.controller.dto.request.UpdateFeedRequest; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.ClubMonthlyStatusResponse; import ddingdong.ddingdongBE.domain.feed.controller.dto.response.MyFeedPageResponse; import ddingdong.ddingdongBE.domain.feed.service.FacadeClubFeedService; +import ddingdong.ddingdongBE.domain.feed.service.FeedRankingService; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.MyFeedPageQuery; import ddingdong.ddingdongBE.domain.user.entity.User; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor +@Validated public class ClubFeedController implements ClubFeedApi { private final FacadeClubFeedService facadeClubFeedService; + private final FeedRankingService feedRankingService; @Override public void createFeed( @@ -45,4 +51,11 @@ public MyFeedPageResponse getMyFeedPage(PrincipalDetails principalDetails, int s MyFeedPageQuery query = facadeClubFeedService.getMyFeedPage(user, size, currentCursorId); return MyFeedPageResponse.from(query); } + + @Override + public ClubMonthlyStatusResponse getFeedStatus(PrincipalDetails principalDetails, int year, int month) { + Long userId = principalDetails.getUser().getId(); + ClubMonthlyStatusQuery query = feedRankingService.getClubMonthlyStatus(userId, year, month); + return ClubMonthlyStatusResponse.from(query); + } } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminFeedRankingWinnerResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminFeedRankingWinnerResponse.java index c80a96f6..b3c5dfa2 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminFeedRankingWinnerResponse.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminFeedRankingWinnerResponse.java @@ -5,6 +5,7 @@ @Builder public record AdminFeedRankingWinnerResponse( + Long clubId, String clubName, long feedCount, long viewCount, @@ -17,6 +18,7 @@ public record AdminFeedRankingWinnerResponse( public static AdminFeedRankingWinnerResponse from(FeedRankingWinnerQuery query) { return AdminFeedRankingWinnerResponse.builder() + .clubId(query.clubId()) .clubName(query.clubName()) .feedCount(query.feedCount()) .viewCount(query.viewCount()) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java new file mode 100644 index 00000000..bee4d13f --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java @@ -0,0 +1,30 @@ +package ddingdong.ddingdongBE.domain.feed.controller.dto.response; + +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; +import lombok.Builder; + +@Builder +public record ClubMonthlyStatusResponse( + int year, + int month, + int rank, + long feedCount, + long viewCount, + long likeCount, + long commentCount, + long score +) { + + public static ClubMonthlyStatusResponse from(ClubMonthlyStatusQuery query) { + return ClubMonthlyStatusResponse.builder() + .year(query.year()) + .month(query.month()) + .rank(query.rank()) + .feedCount(query.feedCount()) + .viewCount(query.viewCount()) + .likeCount(query.likeCount()) + .commentCount(query.commentCount()) + .score(query.score()) + .build(); + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedRankingService.java index 3fc1fdaa..fca1d2d3 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedRankingService.java @@ -1,6 +1,7 @@ package ddingdong.ddingdongBE.domain.feed.service; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedRankingWinnerQuery; import java.util.List; @@ -9,4 +10,6 @@ public interface FeedRankingService { List getMonthlyWinners(int year); List getClubFeedRanking(int year, int month); + + ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int month); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index 58212bd1..dfb6f0c7 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -1,10 +1,13 @@ package ddingdong.ddingdongBE.domain.feed.service; +import ddingdong.ddingdongBE.domain.club.entity.Club; +import ddingdong.ddingdongBE.domain.club.service.ClubService; import ddingdong.ddingdongBE.domain.feed.entity.FeedMonthlyRanking; import ddingdong.ddingdongBE.domain.feed.repository.FeedMonthlyRankingRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.feed.repository.dto.MonthlyFeedRankingDto; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedRankingWinnerQuery; import java.util.ArrayList; import java.util.Comparator; @@ -26,6 +29,7 @@ public class GeneralFeedRankingService implements FeedRankingService { private final FeedMonthlyRankingRepository feedMonthlyRankingRepository; private final FeedRepository feedRepository; + private final ClubService clubService; @Override public List getMonthlyWinners(int year) { @@ -60,6 +64,19 @@ public List getClubFeedRanking(int year, int month) { return result; } + @Override + public ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int month) { + Club club = clubService.getByUserId(userId); + List rankings = getClubFeedRanking(year, month); + + return rankings.stream() + .filter(rankingQuery -> rankingQuery.clubId().equals(club.getId())) + .findFirst() + .filter(rankingQuery -> rankingQuery.score() > 0) + .map(rankingQuery -> ClubMonthlyStatusQuery.from(year, month, rankingQuery)) + .orElse(ClubMonthlyStatusQuery.createEmpty(year, month)); + } + private long calculateScore(MonthlyFeedRankingDto dto) { return dto.getFeedCount() * FEED_WEIGHT + dto.getViewCount() * VIEW_WEIGHT diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java new file mode 100644 index 00000000..da956cd8 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java @@ -0,0 +1,42 @@ +package ddingdong.ddingdongBE.domain.feed.service.dto.query; + +import lombok.Builder; + +@Builder +public record ClubMonthlyStatusQuery( + int year, + int month, + int rank, + long feedCount, + long viewCount, + long likeCount, + long commentCount, + long score +) { + + public static ClubMonthlyStatusQuery from(int year, int month, ClubFeedRankingQuery ranking) { + return ClubMonthlyStatusQuery.builder() + .year(year) + .month(month) + .rank(ranking.rank()) + .feedCount(ranking.feedCount()) + .viewCount(ranking.viewCount()) + .likeCount(ranking.likeCount()) + .commentCount(ranking.commentCount()) + .score(ranking.score()) + .build(); + } + + public static ClubMonthlyStatusQuery createEmpty(int year, int month) { + return ClubMonthlyStatusQuery.builder() + .year(year) + .month(month) + .rank(0) + .feedCount(0) + .viewCount(0) + .likeCount(0) + .commentCount(0) + .score(0) + .build(); + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedRankingWinnerQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedRankingWinnerQuery.java index 44e1fe49..ec6a1499 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedRankingWinnerQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedRankingWinnerQuery.java @@ -5,6 +5,7 @@ @Builder public record FeedRankingWinnerQuery( + Long clubId, String clubName, long feedCount, long viewCount, @@ -17,6 +18,7 @@ public record FeedRankingWinnerQuery( public static FeedRankingWinnerQuery from(FeedMonthlyRanking entity) { return FeedRankingWinnerQuery.builder() + .clubId(entity.getClubId()) .clubName(entity.getClubName()) .feedCount(entity.getFeedCount()) .viewCount(entity.getViewCount()) diff --git a/src/test/java/ddingdong/ddingdongBE/common/fixture/UserFixture.java b/src/test/java/ddingdong/ddingdongBE/common/fixture/UserFixture.java index 908f3991..2ac94da4 100644 --- a/src/test/java/ddingdong/ddingdongBE/common/fixture/UserFixture.java +++ b/src/test/java/ddingdong/ddingdongBE/common/fixture/UserFixture.java @@ -32,6 +32,15 @@ public static User createClubUser(String encode) { .build(); } + public static User createClubUser(String authId, String encode) { + return User.builder() + .authId(authId) + .password(encode) + .name("동아리 사용자") + .role(Role.CLUB) + .build(); + } + public static User createClubUser() { return User.builder() .authId("club123") diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java new file mode 100644 index 00000000..713c8639 --- /dev/null +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java @@ -0,0 +1,130 @@ +package ddingdong.ddingdongBE.domain.feed.controller; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import ddingdong.ddingdongBE.auth.controller.dto.request.SignInRequest; +import ddingdong.ddingdongBE.auth.controller.dto.response.SignInResponse; +import ddingdong.ddingdongBE.common.fixture.ClubFixture; +import ddingdong.ddingdongBE.common.fixture.FeedFixture; +import ddingdong.ddingdongBE.common.fixture.UserFixture; +import ddingdong.ddingdongBE.common.support.NonTxTestContainerSupport; +import ddingdong.ddingdongBE.domain.club.entity.Club; +import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; +import ddingdong.ddingdongBE.domain.feed.entity.Feed; +import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository; +import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; +import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; +import ddingdong.ddingdongBE.domain.user.entity.User; +import ddingdong.ddingdongBE.domain.user.repository.UserRepository; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import java.time.LocalDate; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.security.crypto.password.PasswordEncoder; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClubFeedStatusE2ETest extends NonTxTestContainerSupport { + + @LocalServerPort + private int port; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ClubRepository clubRepository; + + @Autowired + private FeedRepository feedRepository; + + @Autowired + private FeedLikeRepository feedLikeRepository; + + @Autowired + private FeedCommentRepository feedCommentRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + private int year; + private int month; + + @BeforeEach + void setUp() { + RestAssured.port = port; + year = LocalDate.now().getYear(); + month = LocalDate.now().getMonthValue(); + } + + @DisplayName("동아리 이달의 현황 조회 성공 - 피드 2개, 좋아요 1, 댓글 1") + @Test + void getFeedStatus_success() { + // given + User clubUser = userRepository.save(UserFixture.createClubUser(passwordEncoder.encode("1234"))); + Club club = clubRepository.save(ClubFixture.createClub(clubUser)); + + Feed feed1 = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용 1")); + Feed feed2 = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용 2")); + feedLikeRepository.save(FeedFixture.createFeedLike(feed1, "uuid-1")); + feedCommentRepository.save(FeedFixture.createFeedComment(feed2, "uuid-2", 1, "댓글")); + + String token = signIn("club123", "1234"); + + // when & then + // score = feedCount(2)*10 + viewCount(0)*1 + likeCount(1)*3 + commentCount(1)*5 = 28 + Map response = given() + .contentType(ContentType.JSON) + .header("Authorization", "Bearer " + token) + .queryParam("year", year) + .queryParam("month", month) + .when() + .get("/server/central/feeds/status") + .then() + .statusCode(200) + .extract() + .as(Map.class); + + assertSoftly(softly -> { + softly.assertThat(response.get("year")).isEqualTo(year); + softly.assertThat(response.get("month")).isEqualTo(month); + softly.assertThat(((Number) response.get("feedCount")).longValue()).isEqualTo(2L); + softly.assertThat(((Number) response.get("likeCount")).longValue()).isEqualTo(1L); + softly.assertThat(((Number) response.get("commentCount")).longValue()).isEqualTo(1L); + softly.assertThat(((Number) response.get("score")).longValue()).isEqualTo(28L); + softly.assertThat(((Number) response.get("rank")).intValue()).isEqualTo(1); + }); + } + + @DisplayName("동아리 이달의 현황 조회 - 미인증 접근 시 401") + @Test + void getFeedStatus_unauthorized() { + given() + .contentType(ContentType.JSON) + .queryParam("year", year) + .queryParam("month", month) + .when() + .get("/server/central/feeds/status") + .then() + .statusCode(401); + } + + private String signIn(String authId, String password) { + SignInResponse response = given() + .contentType(ContentType.JSON) + .body(new SignInRequest(authId, password)) + .when() + .post("/server/auth/sign-in") + .then() + .statusCode(200) + .extract() + .as(SignInResponse.class); + return response.getToken(); + } +} diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java index 6f5ee6c4..b3bbfa52 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java @@ -6,6 +6,7 @@ import ddingdong.ddingdongBE.common.fixture.ClubFixture; import ddingdong.ddingdongBE.common.fixture.FeedFixture; import ddingdong.ddingdongBE.common.fixture.FeedMonthlyRankingFixture; +import ddingdong.ddingdongBE.common.fixture.UserFixture; import ddingdong.ddingdongBE.common.support.TestContainerSupport; import ddingdong.ddingdongBE.domain.club.entity.Club; import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; @@ -15,7 +16,11 @@ import ddingdong.ddingdongBE.domain.feed.repository.FeedMonthlyRankingRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedRankingWinnerQuery; +import ddingdong.ddingdongBE.domain.user.entity.User; +import ddingdong.ddingdongBE.domain.user.repository.UserRepository; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -43,6 +48,9 @@ class GeneralFeedRankingServiceTest extends TestContainerSupport { @Autowired private FeedCommentRepository feedCommentRepository; + @Autowired + private UserRepository userRepository; + @DisplayName("월별 1위 동아리 목록 조회 - 성공: 각 월의 1위 동아리가 반환된다") @Test void getMonthlyWinners_success() { @@ -320,4 +328,80 @@ void getClubFeedRanking_withLikesAndComments() { softly.assertThat(result.get(0).score()).isEqualTo(21L); }); } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 있으면 내 동아리 통계와 rank가 반환된다") + @Test + void getClubMonthlyStatus_withFeeds() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + Club club = clubRepository.save(ClubFixture.createClub(user)); + feedRepository.save(FeedFixture.createImageFeed(club, "피드1")); + feedRepository.save(FeedFixture.createImageFeed(club, "피드2")); + + int year = LocalDate.now().getYear(); + int month = LocalDate.now().getMonthValue(); + + // when + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), year, month); + + // then + // score = feedCount(2)*10 = 20 + assertSoftly(softly -> { + softly.assertThat(result.year()).isEqualTo(year); + softly.assertThat(result.month()).isEqualTo(month); + softly.assertThat(result.rank()).isEqualTo(1); + softly.assertThat(result.feedCount()).isEqualTo(2L); + softly.assertThat(result.score()).isEqualTo(20L); + }); + } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 없으면 모든 값 0, rank=0으로 반환된다") + @Test + void getClubMonthlyStatus_noFeeds() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + clubRepository.save(ClubFixture.createClub(user)); + + // when — 피드가 없는 연/월 + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), 2000, 1); + + // then + assertSoftly(softly -> { + softly.assertThat(result.year()).isEqualTo(2000); + softly.assertThat(result.month()).isEqualTo(1); + softly.assertThat(result.rank()).isEqualTo(0); + softly.assertThat(result.feedCount()).isEqualTo(0L); + softly.assertThat(result.viewCount()).isEqualTo(0L); + softly.assertThat(result.likeCount()).isEqualTo(0L); + softly.assertThat(result.commentCount()).isEqualTo(0L); + softly.assertThat(result.score()).isEqualTo(0L); + }); + } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 2개 동아리 중 내 동아리의 정확한 순위가 반환된다") + @Test + void getClubMonthlyStatus_rankAccuracy() { + // given + User userA = userRepository.save(UserFixture.createClubUser("clubA", "1234")); + Club clubA = clubRepository.save(ClubFixture.createClub(userA)); + feedRepository.save(FeedFixture.createImageFeed(clubA, "피드A-1")); + feedRepository.save(FeedFixture.createImageFeed(clubA, "피드A-2")); + + User userB = userRepository.save(UserFixture.createClubUser("clubB", "1234")); + Club clubB = clubRepository.save(ClubFixture.createClub(userB)); + feedRepository.save(FeedFixture.createImageFeed(clubB, "피드B")); + + int year = LocalDate.now().getYear(); + int month = LocalDate.now().getMonthValue(); + + // when — 동아리B 회장이 조회 + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(userB.getId(), year, month); + + // then — 동아리A(score=20) > 동아리B(score=10), 동아리B는 rank=2 + assertSoftly(softly -> { + softly.assertThat(result.rank()).isEqualTo(2); + softly.assertThat(result.feedCount()).isEqualTo(1L); + softly.assertThat(result.score()).isEqualTo(10L); + }); + } }