From 213ea51ec8e73f6084bb09f1d222aa564549e7f8 Mon Sep 17 00:00:00 2001 From: "HyunSeo Park (Hyena)" Date: Wed, 16 Aug 2023 10:34:37 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9F=AC=EB=84=88=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=ED=8F=AC=ED=84=B0=20=EB=A6=AC=EB=B7=B0=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EC=9C=A0=EB=AC=B4=20=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20uri=20=EC=88=98=EC=A0=95=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Supporter 의 RunnerPost 지원 여부 확인 조회 메서드 레포지터리, 서비스 구현 * feat: RunnerPost 상세 조회시 Supporter 리뷰 지원 여부 * test: RunnerPost 상세 조회시 Supporter 지원 여부 테스트 구현 * fix: RunnerPost 에 Supporter 리뷰 지원 api 의 uri 수정 * docs: 러너 게시글 상세 조회 테스트 클래스명 수정으로 인한 adoc 파일 변경 --- .../src/docs/asciidoc/RunnerPostReadApi.adoc | 12 +- .../baton/domain/runnerpost/RunnerPost.java | 5 + .../controller/RunnerPostController.java | 15 ++- .../response/RunnerPostResponse.java | 8 +- .../runnerpost/service/RunnerPostService.java | 3 + .../SupporterRunnerPostRepository.java | 19 ++- .../RunnerPostAssuredCreateSupport.java | 2 +- .../RunnerPostAssuredCreateTest.java | 6 +- .../runnerpost/RunnerPostAssuredSupport.java | 2 + ...nnerPostReadByRunnerPostIdAssuredTest.java | 1 + .../RunnerPostReadWithLoginedAssuredTest.java | 6 +- .../create/RunnerPostCreateApiTest.java | 2 +- .../read/RunnerPostReadAllApiTest.java | 64 ---------- .../read/RunnerPostReadOneApiTest.java | 119 ++++++++++++++++++ .../service/RunnerPostServiceReadTest.java | 64 ++++++++++ .../SupporterRunnerPostRepositoryTest.java | 64 ++++++++++ 16 files changed, 306 insertions(+), 86 deletions(-) create mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java diff --git a/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc b/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc index 052d8c684..18e25f35d 100644 --- a/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc +++ b/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc @@ -36,20 +36,20 @@ include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-by-runner-post-id/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/http-request.adoc[] -===== *Http Request Headers +===== *Http Request Headers* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-by-runner-post-id/request-headers.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/request-headers.adoc[] ===== *Http Request Path Parameters* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-by-runner-post-id/path-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/path-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-by-runner-post-id/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-by-runner-post-id/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/response-fields.adoc[] diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java index 038037910..157b42171 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java @@ -16,6 +16,7 @@ import touch.baton.domain.common.vo.Contents; import touch.baton.domain.common.vo.Title; import touch.baton.domain.common.vo.WatchedCount; +import touch.baton.domain.member.Member; import touch.baton.domain.runner.Runner; import touch.baton.domain.runnerpost.exception.RunnerPostDomainException; import touch.baton.domain.runnerpost.vo.Deadline; @@ -253,6 +254,10 @@ public boolean isNotOwner(final Runner targetRunner) { return !runner.equals(targetRunner); } + public boolean isNotOwner(final Member targetMember) { + return !runner.getMember().equals(targetMember); + } + public boolean isReviewStatusStarted() { return !(reviewStatus.isNotStarted() || reviewStatus.isOverdue()); } diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java index 62852903a..98899ce2b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java @@ -20,6 +20,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.Member; +import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipal; import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipal; import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipal; import touch.baton.domain.runner.Runner; @@ -71,7 +73,7 @@ public ResponseEntity createRunnerPostVersionTest(@AuthRunnerPrincipal fin return ResponseEntity.created(redirectUri).build(); } - @PostMapping("{runnerPostId}/applicant") + @PostMapping("{runnerPostId}/application") public ResponseEntity createRunnerPostApplicant(@AuthSupporterPrincipal final Supporter supporter, @PathVariable final Long runnerPostId, @RequestBody @Valid final RunnerPostApplicantCreateRequest request @@ -87,15 +89,20 @@ public ResponseEntity createRunnerPostApplicant(@AuthSupporterPrincipal fi } @GetMapping("/{runnerPostId}") - public ResponseEntity readByRunnerPostId(@AuthRunnerPrincipal(required = false) final Runner runner, + public ResponseEntity readByRunnerPostId(@AuthMemberPrincipal(required = false) final Member member, @PathVariable final Long runnerPostId ) { final RunnerPost foundRunnerPost = runnerPostService.readByRunnerPostId(runnerPostId); final long applicantCount = runnerPostService.readCountByRunnerPostId(foundRunnerPost.getId()); + final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId(runnerPostId, member.getId()); runnerPostService.increaseWatchedCount(foundRunnerPost); - final RunnerPostResponse.Detail response - = RunnerPostResponse.Detail.of(foundRunnerPost, foundRunnerPost.isNotOwner(runner), applicantCount); + final RunnerPostResponse.Detail response = RunnerPostResponse.Detail.of( + foundRunnerPost, + foundRunnerPost.isNotOwner(member), + isApplicantHistoryExist, + applicantCount + ); return ResponseEntity.ok(response); } diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java index 42d90f82b..bab2efb1f 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java @@ -18,11 +18,16 @@ public record Detail(Long runnerPostId, long applicantCount, ReviewStatus reviewStatus, boolean isOwner, + boolean isApplied, List tags, RunnerResponse.Detail runnerProfile ) { - public static Detail of(final RunnerPost runnerPost, final boolean isOwner, final long applicantCount) { + public static Detail of(final RunnerPost runnerPost, + final boolean isOwner, + final boolean isApplied, + final long applicantCount + ) { return new Detail( runnerPost.getId(), runnerPost.getTitle().getValue(), @@ -33,6 +38,7 @@ public static Detail of(final RunnerPost runnerPost, final boolean isOwner, fina applicantCount, runnerPost.getReviewStatus(), isOwner, + isApplied, convertToTags(runnerPost), RunnerResponse.Detail.from(runnerPost.getRunner()) ); diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java index 5186aa4a5..b1f3796da 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java @@ -306,4 +306,7 @@ private boolean isApplySupporter(final Long runnerPostId, final Supporter foundS return !supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runnerPostId, foundSupporter.getId()); } + public boolean existsRunnerPostApplicantByRunnerPostIdAndMemberId(final Long runnerPostId, final Long memberId) { + return supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId(runnerPostId, memberId); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java b/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java index d9b6fe76c..686f34246 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java @@ -19,13 +19,22 @@ having srp.runnerPost.id in (:runnerPostIds) List countByRunnerPostIdIn(@Param("runnerPostIds") final List runnerPostIds); @Query(""" - select count(1) - from SupporterRunnerPost srp - group by srp.runnerPost.id - having srp.runnerPost.id = :runnerPostId - """) + select count(1) + from SupporterRunnerPost srp + group by srp.runnerPost.id + having srp.runnerPost.id = :runnerPostId + """) Optional countByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); + @Query(""" + select (count(1) >= 1) + from SupporterRunnerPost srp + join fetch Member m on m.id = srp.supporter.member.id + where srp.runnerPost.id = :runnerPostId + and srp.supporter.member.id = :memberId + """) + boolean existsByRunnerPostIdAndMemberId(@Param("runnerPostId") final Long runnerPostId, @Param("memberId") final Long memberId); + boolean existsByRunnerPostId(final Long runnerPostId); void deleteBySupporterIdAndRunnerPostId(final Long supporterId, final Long runnerPostId); diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java index d902bf9d7..29a79a7d1 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java @@ -49,7 +49,7 @@ public static class RunnerPostClientRequestBuilder { } public RunnerPostClientRequestBuilder 서포터가_러너_게시글에_리뷰를_신청한다(final Long 러너_게시글_식별자값, final String 리뷰_지원_메시지) { - response = AssuredSupport.post("/api/v1/posts/runner/{runnerPostId}/applicant", + response = AssuredSupport.post("/api/v1/posts/runner/{runnerPostId}/application", accessToken, Map.of("runnerPostId", 러너_게시글_식별자값), new RunnerPostApplicantCreateRequest(리뷰_지원_메시지) diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java index 0ce3d969a..42e2fa71c 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java @@ -45,7 +45,7 @@ class RunnerPostAssuredCreateTest extends AssuredTestConfig { .러너_게시글_생성_성공을_검증한다() .생성한_러너_게시글의_식별자값을_반환한다(); - final RunnerPostResponse.Detail 리뷰가_시작되지_않은_에단의_러너_게시글_Detail_응답 = 러너_게시글_Detail_응답을_생성한다(러너_에단, 러너_게시글_생성_요청, NOT_STARTED, 에단의_러너_게시글_식별자값, 1, 0L); + final RunnerPostResponse.Detail 리뷰가_시작되지_않은_에단의_러너_게시글_Detail_응답 = 러너_게시글_Detail_응답을_생성한다(러너_에단, 러너_게시글_생성_요청, NOT_STARTED, 에단의_러너_게시글_식별자값, 1, 0L, false); RunnerPostAssuredSupport .클라이언트_요청() .토큰으로_로그인한다(에단_로그인_토큰) @@ -92,7 +92,8 @@ class RunnerPostAssuredCreateTest extends AssuredTestConfig { final ReviewStatus 리뷰_상태, final Long 러너_게시글_식별자값, final int 조회수, - final long 서포터_지원자수 + final long 서포터_지원자수, + final boolean 서포터_지원_여부 ) { return 러너_게시글_Detail_응답( 러너_게시글_식별자값, @@ -104,6 +105,7 @@ class RunnerPostAssuredCreateTest extends AssuredTestConfig { 서포터_지원자수, 리뷰_상태, true, + 서포터_지원_여부, 러너, 러너_게시글_생성_요청.tags() ); diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java index 2c3348cd7..1197ccae9 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java @@ -45,6 +45,7 @@ private RunnerPostAssuredSupport() { final long 서포터_지원자수, final ReviewStatus 리뷰_상태, final boolean 주인_여부, + final boolean 서포터_지원_여부, final Runner 러너, final List 태그_목록 ) { @@ -58,6 +59,7 @@ private RunnerPostAssuredSupport() { 서포터_지원자수, 리뷰_상태, 주인_여부, + 서포터_지원_여부, 태그_목록, RunnerResponse.Detail.from(러너) ); diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java index 15d777dbf..cd8d070f9 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java @@ -45,6 +45,7 @@ class RunnerPostReadByRunnerPostIdAssuredTest extends AssuredTestConfig { 0L, ReviewStatus.NOT_STARTED, true, + false, new ArrayList<>(), RunnerResponse.Detail.from(러너_헤나) )); diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java index b957bf534..e9be0ae99 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java @@ -27,7 +27,7 @@ class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { final RunnerPost 러너_게시글 = runnerPostRepository.save(RunnerPostFixture.create(러너_헤나, deadline(now().plusHours(100)))); final String 로그인용_토큰 = login(사용자_헤나.getSocialId().getValue()); - final RunnerPostResponse.Detail 러너_게시글_detail_응답 = 러너_게시글_Detail_응답을_생성한다(러너_헤나, 러너_게시글, ReviewStatus.NOT_STARTED, 러너_게시글.getId(), 1, 0); + final RunnerPostResponse.Detail 러너_게시글_detail_응답 = 러너_게시글_Detail_응답을_생성한다(러너_헤나, 러너_게시글, ReviewStatus.NOT_STARTED, 러너_게시글.getId(), 1, 0, false); 클라이언트_요청() .토큰으로_로그인한다(로그인용_토큰) @@ -42,7 +42,8 @@ class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { final ReviewStatus 리뷰_상태, final Long 러너_게시글_식별자값, final int 조회수, - final int 서포터_지원자수 + final long 서포터_지원자수, + final boolean 서포터_지원_여부 ) { return 러너_게시글_Detail_응답( 러너_게시글_식별자값, @@ -54,6 +55,7 @@ class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { 서포터_지원자수, 리뷰_상태, !러너_게시글.isNotOwner(로그인한_러너), + 서포터_지원_여부, 러너_게시글.getRunner(), 러너_게시글.getRunnerPostTags().getRunnerPostTags().stream() .map(runnerPostTag -> runnerPostTag.getTag().getTagName().getValue()) diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java index 592fc4796..1d99ac56e 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java @@ -83,7 +83,7 @@ void createRunnerPostApplicant() throws Exception { // then final RunnerPostApplicantCreateRequest request = new RunnerPostApplicantCreateRequest("안녕하세요, 서포터 헤나입니다."); - mockMvc.perform(post("/api/v1/posts/runner/{runnerPostId}/applicant", spyRunnerPost.getId()) + mockMvc.perform(post("/api/v1/posts/runner/{runnerPostId}/application", spyRunnerPost.getId()) .header(AUTHORIZATION, "Bearer " + token) .contentType(APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(request))) diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java index 360b9219a..5e709fbef 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java @@ -24,19 +24,14 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.LocalDateTime.now; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.spy; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; @@ -45,7 +40,6 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -67,64 +61,6 @@ void setUp() { restdocsSetUp(runnerPostController); } - @DisplayName("러너 게시글 상세 조회 API") - @Test - void readByRunnerPostId() throws Exception { - // given - final Runner runnerHyena = RunnerFixture.createRunner(MemberFixture.createHyena()); - - final Deadline deadline = deadline(now().plusHours(100)); - final Tag javaTag = TagFixture.create(tagName("자바")); - - // when - final Runner spyRunnerHyena = spy(runnerHyena); - when(spyRunnerHyena.getId()).thenReturn(1L); - - final RunnerPost runnerPost = RunnerPostFixture.create(spyRunnerHyena, deadline, List.of(javaTag)); - final RunnerPost spyRunnerPost = spy(runnerPost); - when(spyRunnerPost.getId()).thenReturn(1L); - - when(runnerPostService.readByRunnerPostId(any())) - .thenReturn(spyRunnerPost); - when(runnerPostService.readCountByRunnerPostId(any())) - .thenReturn(3L); - - final String token = getAccessTokenBySocialId(runnerHyena.getMember().getSocialId().getValue()); - - when(oauthRunnerRepository.joinByMemberSocialId(any())) - .thenReturn(Optional.ofNullable(runnerHyena)); - - // then - mockMvc.perform(get("/api/v1/posts/runner/{runnerPostId}", spyRunnerPost.getId()) - .header(AUTHORIZATION, "Bearer " + token)) - .andExpect(status().isOk()) - .andExpect(content().contentType(APPLICATION_JSON)) - .andDo(restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION).description("Bearer JWT (필수값 x)") - ), - pathParameters( - parameterWithName("runnerPostId").description("러너 게시글 식별자값") - ), - responseFields( - fieldWithPath("runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("title").type(STRING).description("러너 게시글 제목"), - fieldWithPath("contents").type(STRING).description("러너 게시글 내용"), - fieldWithPath("deadline").type(STRING).description("러너 게시글 마감기한"), - fieldWithPath("isOwner").type(BOOLEAN).description("러너 게시글 주인 여부"), - fieldWithPath("applicantCount").type(NUMBER).description("러너 게시글 서포터 지원자수"), - fieldWithPath("watchedCount").type(NUMBER).description("러너 게시글 조회수"), - fieldWithPath("reviewStatus").type(STRING).description("러너 게시글 리뷰 상태"), - fieldWithPath("pullRequestUrl").type(STRING).description("러너 게시글 PR URL"), - fieldWithPath("tags").type(ARRAY).description("러너 게시글 태그 목록"), - fieldWithPath("runnerProfile.runnerId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("runnerProfile.name").type(STRING).description("러너 게시글 식별자값(id)"), - fieldWithPath("runnerProfile.company").type(STRING).description("러너 게시글 식별자값(id)"), - fieldWithPath("runnerProfile.imageUrl").type(STRING).description("러너 게시글 식별자값(id)") - )) - ); - } - @DisplayName("러너 게시글 전체 조회 API") @Test void readAllRunnerPosts() throws Exception { diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java new file mode 100644 index 000000000..d95fda106 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java @@ -0,0 +1,119 @@ +package touch.baton.document.runnerpost.read; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.Member; +import touch.baton.domain.runner.Runner; +import touch.baton.domain.runnerpost.RunnerPost; +import touch.baton.domain.runnerpost.controller.RunnerPostController; +import touch.baton.domain.runnerpost.service.RunnerPostService; +import touch.baton.domain.runnerpost.vo.Deadline; +import touch.baton.domain.tag.Tag; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.TagFixture; + +import java.util.List; +import java.util.Optional; + +import static java.time.LocalDateTime.now; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.when; +import static org.mockito.Mockito.spy; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.TagNameFixture.tagName; + +@WebMvcTest(RunnerPostController.class) +class RunnerPostReadOneApiTest extends RestdocsConfig { + + @MockBean + private RunnerPostService runnerPostService; + + @BeforeEach + void setUp() { + final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); + restdocsSetUp(runnerPostController); + } + + @DisplayName("러너 게시글 상세 조회 API") + @Test + void readByRunnerPostId() throws Exception { + // given + final Member memberHyena = MemberFixture.createHyena(); + final Runner runnerHyena = RunnerFixture.createRunner(memberHyena); + + final Deadline deadline = deadline(now().plusHours(100)); + final Tag javaTag = TagFixture.create(tagName("자바")); + + // when + final Member spyMemberHyena = spy(memberHyena); + final Runner spyRunnerHyena = spy(runnerHyena); + when(spyRunnerHyena.getId()).thenReturn(1L); + when(spyMemberHyena.getId()).thenReturn(1L); + + final RunnerPost runnerPost = RunnerPostFixture.create(spyRunnerHyena, deadline, List.of(javaTag)); + final RunnerPost spyRunnerPost = spy(runnerPost); + when(spyRunnerPost.getId()).thenReturn(1L); + + when(runnerPostService.readByRunnerPostId(any())) + .thenReturn(spyRunnerPost); + when(runnerPostService.readCountByRunnerPostId(any())) + .thenReturn(3L); + + final String token = getAccessTokenBySocialId(memberHyena.getSocialId().getValue()); + + when(oauthMemberRepository.findBySocialId(any())) + .thenReturn(Optional.ofNullable(memberHyena)); + + // then + mockMvc.perform(get("/api/v1/posts/runner/{runnerPostId}", spyRunnerPost.getId()) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT (필수값 x)") + ), + pathParameters( + parameterWithName("runnerPostId").description("러너 게시글 식별자값") + ), + responseFields( + fieldWithPath("runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), + fieldWithPath("title").type(STRING).description("러너 게시글 제목"), + fieldWithPath("contents").type(STRING).description("러너 게시글 내용"), + fieldWithPath("deadline").type(STRING).description("러너 게시글 마감기한"), + fieldWithPath("isOwner").type(BOOLEAN).description("러너 게시글 주인 여부"), + fieldWithPath("isApplied").type(BOOLEAN).description("로그인한 서포터 리뷰 지원 여부"), + fieldWithPath("applicantCount").type(NUMBER).description("러너 게시글 서포터 지원자수"), + fieldWithPath("watchedCount").type(NUMBER).description("러너 게시글 조회수"), + fieldWithPath("reviewStatus").type(STRING).description("러너 게시글 리뷰 상태"), + fieldWithPath("pullRequestUrl").type(STRING).description("러너 게시글 PR URL"), + fieldWithPath("tags").type(ARRAY).description("러너 게시글 태그 목록"), + fieldWithPath("runnerProfile.runnerId").type(NUMBER).description("러너 게시글 식별자값(id)"), + fieldWithPath("runnerProfile.name").type(STRING).description("러너 게시글 식별자값(id)"), + fieldWithPath("runnerProfile.company").type(STRING).description("러너 게시글 식별자값(id)"), + fieldWithPath("runnerProfile.imageUrl").type(STRING).description("러너 게시글 식별자값(id)") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java index dec7c9d24..d44813181 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java @@ -238,4 +238,68 @@ void readRunnerPostsBySupporterIdAndReviewStatusIs_NOT_STARTED() { () -> assertThat(pageRunnerPosts.getContent()).containsExactly(savedRunnerPost) ); } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId() { + // given + final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPost, savedSupporterHyena)); + + // when + final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryExist).isTrue(); + } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { + // given + final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); + + // when + final Long notExistRunnerPostId = -1L; + final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + notExistRunnerPostId, + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryExist).isFalse(); + } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_member_is_not_exist_then_return_false() { + // given + final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + // when + final Long notExistMemberId = -1L; + final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + notExistMemberId + ); + + // then + assertThat(isApplicantHistoryExist).isFalse(); + } } diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java index 17b38104e..7dca5324d 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java @@ -91,6 +91,70 @@ private SupporterRunnerPost createSupporterRunnerPost(final Supporter supporter, .build(); } + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_return_true() { + // given + final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + final SupporterRunnerPost runnerPostApplicant = createSupporterRunnerPost(savedSupporterHyena, savedRunnerPost); + supporterRunnerPostRepository.save(runnerPostApplicant); + + // when + final Long notExistMemberId = -1L; + final boolean isApplicantHistoryExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryExist).isTrue(); + } + + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_if_supporterRunnerPost_is_not_exist_then_return_false() { + // given + final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); + + final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + // when + final boolean isApplicantHistoryNotExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryNotExist).isFalse(); + } + + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { + // given + final Member savedMemberHyena = memberRepository.save(MemberFixture.createDitoo()); + + // when + final Long notExistRunnerPostId = -1L; + final boolean isApplicantHistoryNotExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( + notExistRunnerPostId, + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryNotExist).isFalse(); + } + @DisplayName("RunnerPost 외래키로 된 SupporterRunnerPost 가 존재하는지 확인한다.") @Test void existsByRunnerPostId() {