Skip to content

Commit

Permalink
러너가 서포터 목록에서 서포터를 선택하는 API 구현 (#346)
Browse files Browse the repository at this point in the history
* feat: 지원한 서포터를 선택하는 API 구현

* test: RunnerPostService 테스트 추가

* refactor: 파라미터 명 수정

* test: SupporterRunnerPostRepositoryReadTest 추가

* refactor: 잘못된 redirect uri 변경

* test: Restdocs 테스트 추가

* test: isNotOwner 테스트 추가

* style: startReview 메서드 줄 변경

* refactor: Objects.equals 대신 도메인 로직을 사용하도록 변경

* refactor: 메서드 이름 변경

* refactor: 병합 출돌 해결

* refactor: 필요없는 메서드 삭제

* docs: index.adoc에 서포터 선택 API 추가 및 depth 조절

* refactor: 사용하지 않는 메서드 제거

* test: 컨벤션 맞게 반영

* test: DeadlineFixture 로 변경

* refactor: 예외 메세지 이름 변경

* refactor: private 메서드 병합

* test: notSavedId given 절로 변경

* refactor: hasMessage 제거 및 DTO 이름 변경
  • Loading branch information
cookienc authored Aug 14, 2023
1 parent e209776 commit 3aa6bfa
Show file tree
Hide file tree
Showing 17 changed files with 464 additions and 41 deletions.
24 changes: 24 additions & 0 deletions backend/baton/src/docs/asciidoc/RunnerPostUpdateApi.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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-update-api-test/update-runner-post-supporter/http-request.adoc[]

===== *Http Request Header*
include::{snippets}/../../build/generated-snippets/runner-post-update-api-test/update-runner-post-supporter/request-headers.adoc[]

===== *Http Response*
include::{snippets}/../../build/generated-snippets/runner-post-update-api-test/update-runner-post-supporter/http-response.adoc[]
27 changes: 10 additions & 17 deletions backend/baton/src/docs/asciidoc/RunnerProfileReadApi.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,29 @@ endif::[]
:operation-http-request-title: Example Request
:operation-http-response-title: Example Response

== *러너 프로필 조회*
=== *러너 프로필 조회*

=== *러너 프로필 조회 API*

==== *Http Request*
==== *러너 프로필 조회 API*

===== *Http Request*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-runner-profile/http-request.adoc[]

==== *Http Response*

===== *Http Response*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-runner-profile/http-response.adoc[]

==== *Http Response Fields*

===== *Http Response Fields*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-runner-profile/response-fields.adoc[]

=== *러너 마이페이지 프로필 조회 API*

==== *Http Request*
==== *러너 마이페이지 프로필 조회 API*

===== *Http Request*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-my-profile-by-token/http-request.adoc[]

==== *Http Request Headers*

===== *Http Request Headers*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-my-profile-by-token/request-headers.adoc[]

==== *Http Response*

===== *Http Response*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-my-profile-by-token/http-response.adoc[]

==== *Http Response Fields*

===== *Http Response Fields*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-my-profile-by-token/response-fields.adoc[]
3 changes: 3 additions & 0 deletions backend/baton/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ endif::[]

include::MemberLoginProfileReadApi.adoc[]

== *[ 게시글 ]*
include::RunnerPostUpdateApi.adoc[]

== *[ 러너 ]*

include::RunnerProfileReadApi.adoc[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum ClientErrorCode {
PAST_DEADLINE(HttpStatus.BAD_REQUEST, "RP006", "마감일은 오늘보다 과거일 수 없습니다."),
RUNNER_POST_NOT_FOUND(HttpStatus.NOT_FOUND, "RP007", "존재하지 않는 게시물입니다."),
TAGS_ARE_NULL(HttpStatus.BAD_REQUEST, "RP008", "태그 목록을 빈 값이라도 입력해주세요."),
ASSIGN_SUPPORTER_ID_IS_NULL(HttpStatus.BAD_REQUEST, "RP009", "선택한 서포터의 식별자를 입력해주세요."),

REVIEW_TYPE_IS_NULL(HttpStatus.BAD_REQUEST, "FB001", "만족도를 입력해주세요."),
SUPPORTER_ID_IS_NULL(HttpStatus.BAD_REQUEST, "FB002", "서포터 식별자를 입력해주세요."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public ResponseEntity<Void> deleteByRunnerPostId(@AuthRunnerPrincipal final Runn
@PutMapping("/{runnerPostId}")
public ResponseEntity<Void> update(@AuthRunnerPrincipal final Runner runner,
@PathVariable final Long runnerPostId,
@Valid @RequestBody final RunnerPostUpdateRequest request
@Valid @RequestBody final RunnerPostUpdateRequest.Default request
) {
final Long updatedId = runnerPostService.updateRunnerPost(runnerPostId, runner, request);
final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner")
Expand Down Expand Up @@ -181,4 +181,18 @@ public ResponseEntity<Void> updateSupporterCancelRunnerPost(@AuthSupporterPrinci
.toUri();
return ResponseEntity.noContent().location(redirectUri).build();
}

@PatchMapping("/{runnerPostId}/supporters")
public ResponseEntity<Void> updateRunnerPostAppliedSupporter(@AuthRunnerPrincipal final Runner runner,
@PathVariable final Long runnerPostId,
@Valid @RequestBody final RunnerPostUpdateRequest.SelectSupporter request
) {
runnerPostService.updateRunnerPostAppliedSupporter(runner, runnerPostId, request);

final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner")
.path("/{runnerPostId}")
.buildAndExpand(runnerPostId)
.toUri();
return ResponseEntity.noContent().location(redirectUri).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void deleteByRunnerPostId(final Long runnerPostId, final Runner runner) {
}

@Transactional
public Long updateRunnerPost(final Long runnerPostId, final Runner runner, final RunnerPostUpdateRequest request) {
public Long updateRunnerPost(final Long runnerPostId, final Runner runner, final RunnerPostUpdateRequest.Default request) {
// TODO: 메소드 분리
// FIXME: 2023/08/03 주인 확인 로직 넣기
final RunnerPost runnerPost = runnerPostRepository.findById(runnerPostId)
Expand Down Expand Up @@ -221,4 +221,29 @@ public void deleteSupporterRunnerPost(final Supporter supporter, final Long runn
}
supporterRunnerPostRepository.deleteBySupporterIdAndRunnerPostId(supporter.getId(), runnerPostId);
}

@Transactional
public void updateRunnerPostAppliedSupporter(final Runner runner,
final Long runnerPostId,
final RunnerPostUpdateRequest.SelectSupporter request
) {
final Supporter foundApplySupporter = supporterRepository.findById(request.supporterId())
.orElseThrow(() -> new RunnerPostBusinessException("해당하는 식별자값의 서포터를 찾을 수 없습니다."));
final RunnerPost foundRunnerPost = runnerPostRepository.findById(runnerPostId)
.orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다."));

if (isApplySupporter(runnerPostId, foundApplySupporter)) {
throw new RunnerPostBusinessException("게시글에 리뷰를 제안한 서포터가 아닙니다.");
}
if (foundRunnerPost.isNotOwner(runner)) {
throw new RunnerPostBusinessException("RunnerPost 의 글쓴이와 다른 사용자입니다.");
}

foundRunnerPost.assignSupporter(foundApplySupporter);
}

private boolean isApplySupporter(final Long runnerPostId, final Supporter foundSupporter) {
return !supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runnerPostId, foundSupporter.getId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,25 @@
import java.time.LocalDateTime;
import java.util.List;

public record RunnerPostUpdateRequest(@ValidNotNull(clientErrorCode = ClientErrorCode.TITLE_IS_NULL)
String title,
@ValidNotNull(clientErrorCode = ClientErrorCode.TAGS_ARE_NULL)
List<String> tags,
@ValidNotNull(clientErrorCode = ClientErrorCode.PULL_REQUEST_URL_IS_NULL)
String pullRequestUrl,
@ValidNotNull(clientErrorCode = ClientErrorCode.DEADLINE_IS_NULL)
@ValidFuture(clientErrorCode = ClientErrorCode.PAST_DEADLINE)
LocalDateTime deadline,
@ValidNotNull(clientErrorCode = ClientErrorCode.CONTENTS_ARE_NULL)
@ValidMaxLength(clientErrorCode = ClientErrorCode.CONTENTS_OVERFLOW, max = 1000)
String contents
) {
public record RunnerPostUpdateRequest() {

public record Default(@ValidNotNull(clientErrorCode = ClientErrorCode.TITLE_IS_NULL)
String title,
@ValidNotNull(clientErrorCode = ClientErrorCode.TAGS_ARE_NULL)
List<String> tags,
@ValidNotNull(clientErrorCode = ClientErrorCode.PULL_REQUEST_URL_IS_NULL)
String pullRequestUrl,
@ValidNotNull(clientErrorCode = ClientErrorCode.DEADLINE_IS_NULL)
@ValidFuture(clientErrorCode = ClientErrorCode.PAST_DEADLINE)
LocalDateTime deadline,
@ValidNotNull(clientErrorCode = ClientErrorCode.CONTENTS_ARE_NULL)
@ValidMaxLength(clientErrorCode = ClientErrorCode.CONTENTS_OVERFLOW, max = 1000)
String contents
) {
}

public record SelectSupporter(@ValidNotNull(clientErrorCode = ClientErrorCode.ASSIGN_SUPPORTER_ID_IS_NULL)
Long supporterId
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ having srp.runnerPost.id in (:runnerPostIds)
List<Integer> countByRunnerPostIdIn(@Param("runnerPostIds") final List<Long> runnerPostIds);

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

boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ public static ExtractableResponse<Response> get(final String uri, final String a
}


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

public static ExtractableResponse<Response> patch(final String uri, final String accessToken, final Object params) {
return RestAssured
.given().log().ifValidationFails()
Expand All @@ -69,13 +80,20 @@ public static ExtractableResponse<Response> patch(final String uri, final String
.extract();
}

public static ExtractableResponse<Response> get(final String uri, final Map<String, Object> queryParams) {
public static ExtractableResponse<Response> patch(final String uri,
final String pathParamName,
final Long id,
final Object requestBody,
final String accessToken
) {
return RestAssured
.given().log().ifValidationFails()
.auth().preemptive().oauth2(accessToken)
.contentType(APPLICATION_JSON_VALUE)
.queryParams(queryParams)
.pathParam(pathParamName, id)
.body(requestBody)
.when().log().ifValidationFails()
.get(uri)
.patch(uri)
.then().log().ifError()
.extract();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import touch.baton.assure.common.AssuredSupport;
import touch.baton.assure.common.HttpStatusAndLocationHeader;
import touch.baton.domain.common.response.PageResponse;
import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse;
import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest;
import touch.baton.domain.runnerpost.vo.ReviewStatus;

import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.springframework.http.HttpHeaders.LOCATION;

@SuppressWarnings("NonAsciiCharacters")
public class RunnerPostAssuredSupport {
Expand Down Expand Up @@ -73,6 +76,17 @@ public static class RunnerPostClientRequestBuilder {
return this;
}

public RunnerPostClientRequestBuilder 러너가_서포터를_선택한다(final Long 게시글_식별자값,
final RunnerPostUpdateRequest.SelectSupporter 서포터_선택_요청_정보
) {
response = AssuredSupport.patch("/api/v1/posts/runner/{runnerPostId}/supporters",
"runnerPostId", 게시글_식별자값,
서포터_선택_요청_정보,
accessToken
);
return this;
}

public RunnerPostServerResponseBuilder 서버_응답() {
return new RunnerPostServerResponseBuilder(response);
}
Expand Down Expand Up @@ -119,5 +133,12 @@ public RunnerPostServerResponseBuilder(final ExtractableResponse<Response> respo
assertThat(response.statusCode())
.isEqualTo(HTTP_STATUS.value());
}

public void 러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) {
assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value());
softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation());
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package touch.baton.assure.runnerpost;

import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import touch.baton.assure.common.HttpStatusAndLocationHeader;
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.service.dto.RunnerPostUpdateRequest;
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 touch.baton.fixture.vo.DeadlineFixture.deadline;

@SuppressWarnings("NonAsciiCharacters")
public class RunnerPostAssuredUpdateTest extends AssuredTestConfig {

@Test
void 러너가_서포터_목록에서_서포터를_선택할__있다() {
// given
final String 디투_소셜_아이디 = "hongSile";
final Member 사용자_디투 = memberRepository.save(MemberFixture.createWithSocialId(디투_소셜_아이디));
final Runner 러너_디투 = runnerRepository.save(RunnerFixture.createRunner(사용자_디투));
final String 디투_토큰 = login(디투_소셜_아이디);

final RunnerPost 디투_게시글 = runnerPostRepository.save(RunnerPostFixture.create(러너_디투, deadline(LocalDateTime.now().plusDays(10))));

final Member 사용자_에단 = memberRepository.save(MemberFixture.createEthan());
final Supporter 서포터_에단 = supporterRepository.save(SupporterFixture.create(사용자_에단));

서포터가_리뷰_게시글에_리뷰_제안을_한다(디투_게시글, 서포터_에단);

final RunnerPostUpdateRequest.SelectSupporter 서포터_선택_요청_정보 = new RunnerPostUpdateRequest.SelectSupporter(서포터_에단.getId());

// when, then
RunnerPostAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(디투_토큰)
.러너가_서포터를_선택한다(디투_게시글.getId(), 서포터_선택_요청_정보)

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

private void 서포터가_리뷰_게시글에_리뷰_제안을_한다(final RunnerPost 지원할_게시글, final Supporter 지원한_서포터) {
supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(지원할_게시글, 지원한_서포터));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ public abstract class AssuredTestConfig {
@Autowired
protected SupporterRunnerPostRepository supporterRunnerPostRepository;

@MockBean
private JwtDecoder jwtDecoder;

@Autowired
protected TechnicalTagRepository technicalTagRepository;

@MockBean
private JwtDecoder jwtDecoder;

@BeforeEach
void assuredTestSetUp(@LocalServerPort int port) {
RestAssured.port = port;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
import static org.mockito.Mockito.spy;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.JsonFieldType.*;
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;
Expand Down
Loading

0 comments on commit 3aa6bfa

Please sign in to comment.