Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

리뷰 상태별로 게시글 전체 개수를 조회하는 기능 구현 #728

Merged
merged 2 commits into from
Feb 21, 2024
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
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
Loading