Skip to content

Commit

Permalink
리뷰 상태별로 게시글 전체 개수를 조회하는 기능 구현 (#728)
Browse files Browse the repository at this point in the history
* feat: 리뷰 상태별로 게시글 전체 개수를 조회하는 기능 구현

* test: Restdocs 테스트 추가
  • Loading branch information
cookienc authored Feb 21, 2024
1 parent 3428bfe commit 7504019
Show file tree
Hide file tree
Showing 16 changed files with 411 additions and 0 deletions.
14 changes: 14 additions & 0 deletions backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,17 @@ include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-b
===== *Http Response Fields*

include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-by-reduced-name/response-fields.adoc[]

==== *러너 게시글 상태별 총 개수 조회 API*

===== *Http Request*

include::{snippets}/../../build/generated-snippets/runner-post-status-count-api-test/count-all-runner-post-by-review-status/http-request.adoc[]

===== *Http Response*

include::{snippets}/../../build/generated-snippets/runner-post-status-count-api-test/count-all-runner-post-by-review-status/http-response.adoc[]

===== *Http Response Fields*

include::{snippets}/../../build/generated-snippets/runner-post-status-count-api-test/count-all-runner-post-by-review-status/response-fields.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ public ResponseEntity<RunnerPostResponse.Count> countRunnerPostByLoginedRunnerAn
final long runnerPostCount = runnerPostQueryService.countRunnerPostByRunnerIdAndReviewStatus(runner.getId(), reviewStatus);
return ResponseEntity.ok(RunnerPostResponse.Count.from(runnerPostCount));
}

@GetMapping("/count")
public ResponseEntity<RunnerPostResponse.StatusCount> countAllRunnerPostByReviewStatus() {
return ResponseEntity.ok(runnerPostQueryService.countAllRunnerPostByReviewStatus());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,12 @@ private static List<String> convertToTags(final RunnerPost runnerPost, final Lis
.map(runnerPostTag -> runnerPostTag.getTag().getTagName().getValue())
.toList();
}

public record StatusCount(
long notStarted,
long inProgress,
long done,
long overdue
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,12 @@ select count(1)
where rp.supporter.id = :supporterId and rp.reviewStatus = :reviewStatus
""")
long countBySupporterIdAndReviewStatus(@Param("supporterId") final Long supporterId, @Param("reviewStatus") final ReviewStatus reviewStatus);


@Query("""
select count(1)
from RunnerPost rp
where rp.reviewStatus = :reviewStatus
""")
long countByReviewStatus(@Param("reviewStatus") ReviewStatus reviewStatus);
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ public long countRunnerPostBySupporterIdAndReviewStatus(final Long supporterId,
return runnerPostQueryRepository.countBySupporterIdAndReviewStatus(supporterId, reviewStatus);
}

public RunnerPostResponse.StatusCount countAllRunnerPostByReviewStatus() {
long notStartedCount = runnerPostQueryRepository.countByReviewStatus(ReviewStatus.NOT_STARTED);
long inProgressCount = runnerPostQueryRepository.countByReviewStatus(ReviewStatus.IN_PROGRESS);
long doneCount = runnerPostQueryRepository.countByReviewStatus(ReviewStatus.DONE);
long overdueCount = runnerPostQueryRepository.countByReviewStatus(ReviewStatus.OVERDUE);

return new RunnerPostResponse.StatusCount(notStartedCount, inProgressCount, doneCount, overdueCount);
}

@Transactional
public void increaseWatchedCount(final RunnerPost runnerPost) {
runnerPost.increaseWatchedCount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ public static ExtractableResponse<Response> post(final String uri,
.extract();
}

public static ExtractableResponse<Response> get(final String uri) {
return RestAssured
.given().log().ifValidationFails()
.when().log().ifValidationFails()
.get(uri)
.then().log().ifError()
.extract();
}

public static ExtractableResponse<Response> get(final String uri, final PathParams pathParams) {
return RestAssured
.given().log().ifValidationFails()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package touch.baton.assure.repository;

import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import touch.baton.domain.runnerpost.command.repository.RunnerPostCommandRepository;

@Profile("test")
public interface TestRunnerPostCommandRepository extends RunnerPostCommandRepository {

@Transactional
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("""
update RunnerPost rp
set rp.reviewStatus = 'OVERDUE'
where rp.id = :id
""")
void expireRunnerPost(@Param("id") Long runnerPostId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package touch.baton.assure.runnerpost;

import org.springframework.http.HttpStatus;
import touch.baton.assure.common.HttpStatusAndLocationHeader;
import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport;
import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport;
import touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport;
import touch.baton.domain.member.command.Supporter;

import java.time.LocalDateTime;
import java.util.List;

import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청;
import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청;

public abstract class RunnerPostSteps {

public static void 러너가_게시글을_작성하고_리뷰를_받은_뒤_리뷰완료로_변경한다(final String 러너_액세스_토큰, final String 서포터_액세스_토큰, final Supporter 서포터) {
final Long 게시글_식별자 = 러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);
서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_액세스_토큰, 게시글_식별자);
러너가_서포터의_리뷰_신청_선택에_성공한다(서포터, 러너_액세스_토큰, 게시글_식별자);
서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(서포터_액세스_토큰, 게시글_식별자);
}

public static void 러너가_게시글을_작성하고_서포터가_선택된다(final String 러너_액세스_토큰, final String 서포터_액세스_토큰, final Supporter 서포터) {
final Long 게시글_식별자 = 러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);
서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_액세스_토큰, 게시글_식별자);
러너가_서포터의_리뷰_신청_선택에_성공한다(서포터, 러너_액세스_토큰, 게시글_식별자);
}

public static Long 러너_게시글을_생성하고_게시을글_식별자를_반환한다(final String 러너_엑세스_토큰) {
return RunnerPostCreateSupport
.클라이언트_요청()
.액세스_토큰으로_로그인한다(러너_엑세스_토큰)
.러너_게시글_등록_요청한다(
러너_게시글_생성_요청(
"테스트용_러너_게시글_제목",
List.of("자바", "스프링"),
"https://test-pull-request.com",
LocalDateTime.now().plusHours(100),
"테스트용_러너_게시글_구현_내용",
"테스트용_러너_게시글_궁금한_내용",
"테스트용_러너_게시글_참고_사항"
))

.서버_응답()
.러너_게시글_생성_성공을_검증한다()
.생성한_러너_게시글의_식별자값을_반환한다();
}

public static void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) {
RunnerPostApplicantCreateSupport
.클라이언트_요청()
.액세스_토큰으로_로그인한다(서포터_액세스_토큰)
.서포터가_러너_게시글에_리뷰를_신청한다(러너_게시글_식별자값, "안녕하세요. 서포터입니다.")

.서버_응답()
.서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(러너_게시글_식별자값);
}

public static void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터, final String 러너_액세스_토큰, final Long 러너_게시글_식별자값) {
RunnerPostUpdateSupport
.클라이언트_요청()
.액세스_토큰으로_로그인한다(러너_액세스_토큰)
.러너가_서포터를_선택한다(러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터.getId()))

.서버_응답()
.러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner"));
}

public static void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) {
RunnerPostUpdateSupport
.클라이언트_요청()
.액세스_토큰으로_로그인한다(서포터_액세스_토큰)
.서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(러너_게시글_식별자값)

.서버_응답()
.러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package touch.baton.assure.runnerpost.query.count;

import org.junit.jupiter.api.Test;
import touch.baton.assure.runnerpost.support.query.count.RunnerPostStatusCountAssuredSupport;
import touch.baton.config.AssuredTestConfig;
import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes;
import touch.baton.domain.member.command.Supporter;
import touch.baton.domain.member.command.vo.SocialId;

import static touch.baton.assure.runnerpost.RunnerPostSteps.러너_게시글을_생성하고_게시을글_식별자를_반환한다;
import static touch.baton.assure.runnerpost.RunnerPostSteps.러너가_게시글을_작성하고_리뷰를_받은_뒤_리뷰완료로_변경한다;
import static touch.baton.assure.runnerpost.RunnerPostSteps.러너가_게시글을_작성하고_서포터가_선택된다;

@SuppressWarnings("NonAsciiCharacters")
class RunnerPostStatusCountAssruedTest extends AssuredTestConfig {

@Test
void 리뷰_상태_별_게시글의_총_개수를_조회한다() {
// given
final String 러너_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode());
final String 서포터_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode());
final SocialId 서포터_소셜_아이디 = jwtTestManager.parseToSocialId(서포터_액세스_토큰);
final Supporter 서포터 = supporterRepository.getBySocialId(서포터_소셜_아이디);

final Long 리뷰_진행중_글_개수 = 1L;
러너가_게시글을_작성하고_서포터가_선택된다(러너_액세스_토큰, 서포터_액세스_토큰, 서포터);

final Long 리뷰_완료_글_개수 = 2L;
러너가_게시글을_작성하고_리뷰를_받은_뒤_리뷰완료로_변경한다(러너_액세스_토큰, 서포터_액세스_토큰, 서포터);
러너가_게시글을_작성하고_리뷰를_받은_뒤_리뷰완료로_변경한다(러너_액세스_토큰, 서포터_액세스_토큰, 서포터);

final Long 리뷰_대기중_글_개수 = 3L;
러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);
러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);
러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);

final Long 기간_만료_글_개수 = 4L;
러너_게시글을_생성하고_마감기한이_지나_마감된다(러너_액세스_토큰);
러너_게시글을_생성하고_마감기한이_지나_마감된다(러너_액세스_토큰);
러너_게시글을_생성하고_마감기한이_지나_마감된다(러너_액세스_토큰);
러너_게시글을_생성하고_마감기한이_지나_마감된다(러너_액세스_토큰);

// when, then
RunnerPostStatusCountAssuredSupport
.클라이언트_요청()
.게시글의_상태별로_게시글_총_수를_반환한다()

.서버_응답()
.게시글_상태별_게시글_총_수_반환_성공을_확인한다(리뷰_대기중_글_개수, 리뷰_진행중_글_개수, 리뷰_완료_글_개수, 기간_만료_글_개수);
}

private void 러너_게시글을_생성하고_마감기한이_지나_마감된다(final String 러너_액세스_토큰) {
final Long 만료된_게시글 = 러너_게시글을_생성하고_게시을글_식별자를_반환한다(러너_액세스_토큰);
runnerPostCommandRepository.expireRunnerPost(만료된_게시글);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package touch.baton.assure.runnerpost.support.query.count;

import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.springframework.http.HttpStatus;
import touch.baton.assure.common.AssuredSupport;
import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

@SuppressWarnings("NonAsciiCharacters")
public class RunnerPostStatusCountAssuredSupport {

private RunnerPostStatusCountAssuredSupport() {
}

public static RunnerPostStatusCountBuilder 클라이언트_요청() {
return new RunnerPostStatusCountBuilder();
}

public static class RunnerPostStatusCountBuilder {

private ExtractableResponse<Response> response;

public RunnerPostStatusCountBuilder 게시글의_상태별로_게시글_총_수를_반환한다() {
response = AssuredSupport.get("/api/v1/posts/runner/count");
return this;
}

public RunnerPostStatusCountResponseBuilder 서버_응답() {
return new RunnerPostStatusCountResponseBuilder(response);
}

public class RunnerPostStatusCountResponseBuilder {

private final ExtractableResponse<Response> response;

public RunnerPostStatusCountResponseBuilder(final ExtractableResponse<Response> 응답) {
this.response = 응답;
}

public void 게시글_상태별_게시글_총_수_반환_성공을_확인한다(final Long 리뷰_대기중_글_개수,
final Long 리뷰_진행중_글_개수,
final Long 리뷰_완료_글_개수,
final Long 기간_만료_글_개수
) {
final RunnerPostResponse.StatusCount actual = response.as(RunnerPostResponse.StatusCount.class);

assertAll(
() -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()),
() -> assertThat(actual.notStarted()).isEqualTo(리뷰_대기중_글_개수),
() -> assertThat(actual.inProgress()).isEqualTo(리뷰_진행중_글_개수),
() -> assertThat(actual.done()).isEqualTo(리뷰_완료_글_개수),
() -> assertThat(actual.overdue()).isEqualTo(기간_만료_글_개수)
);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import touch.baton.assure.repository.TestNotificationCommandRepository;
import touch.baton.assure.repository.TestRankQueryRepository;
import touch.baton.assure.repository.TestRefreshTokenRepository;
import touch.baton.assure.repository.TestRunnerPostCommandRepository;
import touch.baton.assure.repository.TestRunnerPostQueryRepository;
import touch.baton.assure.repository.TestRunnerQueryRepository;
import touch.baton.assure.repository.TestSupporterQueryRepository;
Expand All @@ -41,6 +42,9 @@ public abstract class AssuredTestConfig {
@Autowired
protected TestRunnerQueryRepository runnerRepository;

@Autowired
protected TestRunnerPostCommandRepository runnerPostCommandRepository;

@Autowired
protected TestSupporterQueryRepository supporterRepository;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ protected Supporter persistSupporter(final ReviewCount reviewCount, final Member
return supporter;
}

protected RunnerPost persistRunnerPost(final RunnerPost runnerPost) {
em.persist(runnerPost);
return runnerPost;
}

protected RunnerPost persistRunnerPost(final Runner runner) {
final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100)));
em.persist(runnerPost);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package touch.baton.document.runnerpost.read;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldType;
import touch.baton.config.RestdocsConfig;
import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse;

import static org.mockito.BDDMockito.when;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class RunnerPostStatusCountApiTest extends RestdocsConfig {

@DisplayName("게시글 상태 별 총 개수 반환 API 구현")
@Test
void countAllRunnerPostByReviewStatus() throws Exception {
// when
when(runnerPostQueryService.countAllRunnerPostByReviewStatus())
.thenReturn(new RunnerPostResponse.StatusCount(1L, 2L, 3L, 4L));

// then
mockMvc.perform(get("/api/v1/posts/runner/count"))
.andExpect(status().isOk())
.andDo(restDocs.document(
responseFields(
fieldWithPath("notStarted").type(JsonFieldType.NUMBER).description("리뷰_대기중_게시글_총_수"),
fieldWithPath("inProgress").type(JsonFieldType.NUMBER).description("리뷰_진행중_게시글_총_수"),
fieldWithPath("done").type(JsonFieldType.NUMBER).description("리뷰_완료_게시글_총_수"),
fieldWithPath("overdue").type(JsonFieldType.NUMBER).description("기간_만료_게시글_총_수")
))
);
}
}
Loading

0 comments on commit 7504019

Please sign in to comment.