Skip to content

Commit

Permalink
러너 게시글 삭제 API (#350)
Browse files Browse the repository at this point in the history
* test: 인수 테스트 작성

* feat: 레포지토리 구현

* feat: 서비스 구현

* test: API 문서 작성

* test: 인수 테스트 수정

* refactor: 코드 리뷰 반영

* refactor: 충돌 해결

* refactor: 포매팅

* refactor: 코드 리뷰 반영
  • Loading branch information
shb03323 authored Aug 14, 2023
1 parent 2d41d5f commit d2c291c
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 127 deletions.
27 changes: 27 additions & 0 deletions backend/baton/src/docs/asciidoc/RunnerPostDeleteApi.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: book
:icons: font
:source-highlighter: highlight.js
:toc: left
:toclevels: 3
:sectlinks:
:operation-http-request-title: Example Request
:operation-http-response-title: Example Response

=== *러너 게시글 삭제*

==== *러너 게시글 삭제 API*

===== *Http Request*

include::{snippets}/../../build/generated-snippets/runner-post-delete-api-test/delete-by-runner-post-id/http-request.adoc[]

===== *Http Request Path Parameters*

include::{snippets}/../../build/generated-snippets/runner-post-delete-api-test/delete-by-runner-post-id/path-parameters.adoc[]

===== *Http Response*

include::{snippets}/../../build/generated-snippets/runner-post-delete-api-test/delete-by-runner-post-id/http-response.adoc[]
1 change: 1 addition & 0 deletions backend/baton/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ endif::[]
include::MemberLoginProfileReadApi.adoc[]

== *[ 게시글 ]*

include::RunnerPostUpdateApi.adoc[]

== *[ 러너 ]*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@
import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;
import static touch.baton.domain.runnerpost.vo.ReviewStatus.DONE;
import static touch.baton.domain.runnerpost.vo.ReviewStatus.IN_PROGRESS;
import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED;
import static touch.baton.domain.runnerpost.vo.ReviewStatus.OVERDUE;
import static touch.baton.domain.runnerpost.vo.ReviewStatus.*;

@Getter
@NoArgsConstructor(access = PROTECTED)
Expand Down Expand Up @@ -249,6 +246,10 @@ public boolean isNotOwner(final Runner targetRunner) {
return !runner.equals(targetRunner);
}

public boolean isReviewStatusStarted() {
return !(reviewStatus.isNotStarted() || reviewStatus.isOverdue());
}

public boolean isDifferentSupporter(final Supporter targetSupporter) {
return !supporter.equals(targetSupporter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ public ResponseEntity<Void> deleteByRunnerPostId(@AuthRunnerPrincipal final Runn
@PathVariable final Long runnerPostId
) {
runnerPostService.deleteByRunnerPostId(runnerPostId, runner);

return ResponseEntity.noContent().build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,17 @@ public void increaseWatchedCount(final RunnerPost runnerPost) {

@Transactional
public void deleteByRunnerPostId(final Long runnerPostId, final Runner runner) {
// FIXME: 2023/08/03 삭제 시 본인인지 확인하는 로직 넣기
final Optional<RunnerPost> maybeRunnerPost = runnerPostRepository.findById(runnerPostId);
if (maybeRunnerPost.isEmpty()) {
throw new RunnerPostBusinessException("RunnerPost 의 식별자값으로 삭제할 러너 게시글이 존재하지 않습니다.");
final RunnerPost runnerPost = runnerPostRepository.findById(runnerPostId)
.orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 삭제할 러너 게시글이 존재하지 않습니다."));
if (runnerPost.isNotOwner(runner)) {
throw new RunnerPostBusinessException("RunnerPost 를 게시한 유저가 아닙니다.");
}
if (runnerPost.isReviewStatusStarted()) {
throw new RunnerPostBusinessException("삭제할 수 없는 상태의 리뷰 상태입니다.");
}
if (supporterRunnerPostRepository.existsByRunnerPostId(runnerPostId)) {
throw new RunnerPostBusinessException("지원자가 존재하여 삭제할 수 없습니다.");
}

runnerPostRepository.deleteById(runnerPostId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public boolean isNotSameAsNotStarted() {
return this != NOT_STARTED;
}

public boolean isOverdue() {
return this == OVERDUE;
}

public boolean isNotStarted() {
return this == NOT_STARTED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ having srp.runnerPost.id in (:runnerPostIds)
""")
List<Long> countByRunnerPostIdIn(@Param("runnerPostIds") final List<Long> runnerPostIds);

boolean existsByRunnerPostId(final Long runnerPostId);

void deleteBySupporterIdAndRunnerPostId(final Long supporterId, final Long runnerPostId);

boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,15 @@ public static ExtractableResponse<Response> delete(final String uri, final Strin
.then().log().ifError()
.extract();
}

public static ExtractableResponse<Response> delete(final String uri, final String accessToken, final String pathParamName, final Long id) {
return RestAssured
.given().log().ifValidationFails()
.auth().preemptive().oauth2(accessToken)
.pathParam(pathParamName, id)
.when().log().ifValidationFails()
.delete(uri)
.then().log().ifError()
.extract();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static class RunnerPostClientRequestBuilder {
}

public RunnerPostClientRequestBuilder 러너_게시글_식별자값으로_러너_게시글을_삭제한다(final Long 러너_게시글_식별자값) {
response = AssuredSupport.delete("/api/v1/posts/runner/{runnerPostId}", "runnerPostId", 러너_게시글_식별자값);
response = AssuredSupport.delete("/api/v1/posts/runner/{runnerPostId}", accessToken, "runnerPostId", 러너_게시글_식별자값);
return this;
}

Expand Down Expand Up @@ -147,8 +147,11 @@ public RunnerPostServerResponseBuilder(final ExtractableResponse<Response> respo
}

public void 러너_게시글_삭제_성공을_검증한다(final HttpStatus HTTP_STATUS) {
assertThat(response.statusCode())
.isEqualTo(HTTP_STATUS.value());
assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value());
}

public void 러너_게시글_삭제_실패를_검증한다(final HttpStatus HTTP_STATUS) {
assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value());
}

public void 러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package touch.baton.assure.runnerpost;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import touch.baton.config.AssuredTestConfig;
import touch.baton.domain.member.Member;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.runnerpost.RunnerPost;
import touch.baton.domain.runnerpost.vo.ReviewStatus;
import touch.baton.domain.supporter.Supporter;
import touch.baton.fixture.domain.MemberFixture;
import touch.baton.fixture.domain.RunnerFixture;
import touch.baton.fixture.domain.RunnerPostFixture;
import touch.baton.fixture.domain.SupporterFixture;
import touch.baton.fixture.domain.SupporterRunnerPostFixture;

import java.time.LocalDateTime;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static touch.baton.fixture.vo.DeadlineFixture.deadline;

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

private Runner 러너_디투;
private String 로그인용_토큰;

@BeforeEach
void setUp() {
final Member 사용자_디투 = memberRepository.save(MemberFixture.createDitoo());
러너_디투 = runnerRepository.save(RunnerFixture.createRunner(사용자_디투));
로그인용_토큰 = login(사용자_디투.getSocialId().getValue());
}

@Test
void 리뷰가_대기중이고_리뷰_지원자가_없다면_러너의_게시글_식별자값으로_러너_게시글_삭제에_성공한다() {
final RunnerPost 러너_게시글 = runnerPostRepository.save(RunnerPostFixture.create(러너_디투, deadline(LocalDateTime.now().plusHours(100))));

RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_게시글_식별자값으로_러너_게시글을_삭제한다(러너_게시글.getId())

.서버_응답()
.러너_게시글_삭제_성공을_검증한다(NO_CONTENT);
}

@Test
void 러너_게시글이_존재하지_않으면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() {
final Long 존재하지_않는_러너_게시글의_식별자값 = -1L;

RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_게시글_식별자값으로_러너_게시글을_삭제한다(존재하지_않는_러너_게시글의_식별자값)

.서버_응답()
.러너_게시글_삭제_실패를_검증한다(INTERNAL_SERVER_ERROR);
}

@Test
void 리뷰가_진행중인_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() {
final RunnerPost 러너_게시글 = runnerPostRepository.save(RunnerPostFixture.create(
러너_디투,
deadline(LocalDateTime.now().plusHours(100)),
ReviewStatus.IN_PROGRESS
));

RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_게시글_식별자값으로_러너_게시글을_삭제한다(러너_게시글.getId())

.서버_응답()
.러너_게시글_삭제_실패를_검증한다(INTERNAL_SERVER_ERROR);
}

@Test
void 리뷰가_완료된_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() {
final RunnerPost 러너_게시글 = runnerPostRepository.save(RunnerPostFixture.create(
러너_디투,
deadline(LocalDateTime.now().plusHours(100)),
ReviewStatus.DONE
));

RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_게시글_식별자값으로_러너_게시글을_삭제한다(러너_게시글.getId())

.서버_응답()
.러너_게시글_삭제_실패를_검증한다(INTERNAL_SERVER_ERROR);
}

@Test
void 리뷰_요청_대기중인_상태이고_리뷰_지원자가_있는_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() {
final RunnerPost 러너_게시글 = runnerPostRepository.save(RunnerPostFixture.create(
러너_디투,
deadline(LocalDateTime.now().plusHours(100)),
ReviewStatus.NOT_STARTED
));
final Member 지원자_맴버 = memberRepository.save(MemberFixture.createHyena());
final Supporter 지원자_서포터 = supporterRepository.save(SupporterFixture.create(지원자_맴버));
supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(러너_게시글, 지원자_서포터));

RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_게시글_식별자값으로_러너_게시글을_삭제한다(러너_게시글.getId())

.서버_응답()
.러너_게시글_삭제_실패를_검증한다(INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package touch.baton.document.runnerpost.delete;

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.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.fixture.domain.MemberFixture;
import touch.baton.fixture.domain.RunnerFixture;
import touch.baton.fixture.domain.RunnerPostFixture;

import java.time.LocalDateTime;
import java.util.Optional;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
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.status;
import static touch.baton.fixture.vo.DeadlineFixture.deadline;

@WebMvcTest(RunnerPostController.class)
public class RunnerPostDeleteApiTest extends RestdocsConfig {

@MockBean
private RunnerPostService runnerPostService;

@BeforeEach
void setUp() {
final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService);
restdocsSetUp(runnerPostController);
}

@DisplayName("러너 게시글 삭제 API")
@Test
void deleteByRunnerPostId() throws Exception {
// given
final String socialId = "ditooSocialId";
final Runner runner = RunnerFixture.createRunner(MemberFixture.createWithSocialId(socialId));
final String token = getAccessTokenBySocialId(socialId);
final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100)));
final RunnerPost spyRunnerPost = spy(runnerPost);

// when
when(oauthRunnerRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(runner));
when(spyRunnerPost.getId()).thenReturn(1L);

// then
mockMvc.perform(delete("/api/v1/posts/runner/{runnerPostId}", 1L)
.header(AUTHORIZATION, "Bearer " + token))
.andExpect(status().isNoContent())
.andDo(restDocs.document(
requestHeaders(
headerWithName(AUTHORIZATION).description("Bearer JWT")
),
pathParameters(
parameterWithName("runnerPostId").description("러너 게시글 식별자값")
)
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ void fail_createRunnerPostApplicant_if_supporter_already_applied() {
// then
assertSoftly(softly -> {
softly.assertThatCode(() -> runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()))
.doesNotThrowAnyException();
.doesNotThrowAnyException();
softly.assertThatThrownBy(() -> runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()))
.isInstanceOf(RunnerPostBusinessException.class);
});
Expand Down
Loading

0 comments on commit d2c291c

Please sign in to comment.