Skip to content

Commit

Permalink
러너 본인 프로필 조회 API 구현 (#310)
Browse files Browse the repository at this point in the history
* feat: Runner 기술 태그 추가

* feat: Runner 본인 프로필 응답 객체 추가

* test: Runner 본인 프로필 조회 인수 테스트 추가

* test: Runner 생성시 RunnerTechnicalTags 생성시 추가

* test: 인수 테스트 truncate Listener 추가

* feat: Runner 본인 프로필 조회 컨트롤러 추가

* docs: Runner 본인 프로필 조회 API 문서

* docs: Runner 본인 프로필 조회 API 문서 내부 수정

* test: 인숱 테스트 데이터 삭제 메서드 수정
  • Loading branch information
hyena0608 authored Aug 10, 2023
1 parent c4b39fc commit caccf41
Show file tree
Hide file tree
Showing 22 changed files with 400 additions and 14 deletions.
27 changes: 27 additions & 0 deletions backend/baton/src/docs/asciidoc/RunnerProfileReadApi.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: investment
:icons: font
:source-highlighter: highlight.js
:toc: left
:toclevels: 2
:sectlinks:
:operation-http-request-title: Example Request
:operation-http-response-title: Example Response

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

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

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

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

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

==== *Http Response Fields*
include::{snippets}/../../build/generated-snippets/runner-profile-read-api-test/read-my-profile-by-token/response-fields.adoc[]
22 changes: 16 additions & 6 deletions backend/baton/src/main/java/touch/baton/domain/runner/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import touch.baton.domain.common.vo.Introduction;
import touch.baton.domain.member.Member;
import touch.baton.domain.runner.exception.RunnerDomainException;
import touch.baton.domain.technicaltag.RunnerTechnicalTags;

import java.util.Objects;

Expand All @@ -37,27 +38,36 @@ public class Runner extends BaseEntity {
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "fk_runner_to_member"), nullable = false)
private Member member;

@Embedded
private RunnerTechnicalTags runnerTechnicalTags;

@Builder
private Runner( final Introduction introduction,
final Member member
private Runner(final Introduction introduction,
final Member member,
final RunnerTechnicalTags runnerTechnicalTags
) {
this(null, introduction, member);
this(null, introduction, member, runnerTechnicalTags);
}

private Runner(final Long id,
final Introduction introduction,
final Member member
final Member member,
final RunnerTechnicalTags runnerTechnicalTags
) {
validateNotNull(member);
validateNotNull(member, runnerTechnicalTags);
this.id = id;
this.introduction = introduction;
this.member = member;
this.runnerTechnicalTags = runnerTechnicalTags;
}

private void validateNotNull(final Member member) {
private void validateNotNull(final Member member, final RunnerTechnicalTags runnerTechnicalTags) {
if (Objects.isNull(member)) {
throw new RunnerDomainException("Runner 의 member 는 null 일 수 없습니다.");
}
if (Objects.isNull(runnerTechnicalTags)) {
throw new RunnerDomainException("Runner 의 runnerTechnicalTags 는 null 일 수 없습니다.");
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ public ResponseEntity<RunnerMyProfileResponse> readMyProfile(@AuthRunnerPrincipa
.toList();
return ResponseEntity.ok(new RunnerMyProfileResponse(me, runnerPosts));
}

@GetMapping("/me")
public ResponseEntity<RunnerResponse.MyProfile> readMyProfileByToken(@AuthRunnerPrincipal Runner runner) {
final RunnerResponse.MyProfile response = RunnerResponse.MyProfile.from(runner);

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import touch.baton.domain.runner.Runner;

import java.util.List;

public record RunnerResponse() {

public record Detail(Long runnerId,
Expand Down Expand Up @@ -45,4 +47,30 @@ public static Mine from(final Runner runner) {
);
}
}

public record MyProfile(String name,
String company,
String imageUrl,
String githubUrl,
String introduction,
List<String> technicalTags
) {

public static MyProfile from(final Runner runner) {
return new MyProfile(
runner.getMember().getMemberName().getValue(),
runner.getMember().getCompany().getValue(),
runner.getMember().getImageUrl().getValue(),
runner.getMember().getGithubUrl().getValue(),
runner.getIntroduction().getValue(),
convertRunnerTechnicalTags(runner)
);
}

private static List<String> convertRunnerTechnicalTags(final Runner runner) {
return runner.getRunnerTechnicalTags().getRunnerTechnicalTags().stream()
.map(runnerTechnicalTag -> runnerTechnicalTag.getTechnicalTag().getTagName().getValue())
.toList();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package touch.baton.assure.runner;

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

import static touch.baton.assure.runner.RunnerProfileAssuredSupport.러너_본인_프로필_응답;
import static touch.baton.fixture.vo.IntroductionFixture.introduction;

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

@Test
void 러너_본인_프로필을_가지고_있는_토큰으로_조회에_성공한다() {
final Member 사용자_헤나 = memberRepository.save(MemberFixture.createHyena());
final Runner 러너_헤나 = runnerRepository.save(RunnerFixture.create(introduction("안녕하세요"), 사용자_헤나));
final String 로그인용_토큰 = login(사용자_헤나.getSocialId().getValue());

RunnerProfileAssuredSupport
.클라이언트_요청()
.토큰으로_로그인한다(로그인용_토큰)
.러너_본인_프로필을_가지고_있는_토큰으로_조회한다()

.서버_응답()
.러너_본인_프로필_조회_성공을_검증한다(러너_본인_프로필_응답(러너_헤나));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package touch.baton.assure.runner;

import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import touch.baton.assure.common.AssuredSupport;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.runner.controller.response.RunnerResponse;

import static org.assertj.core.api.SoftAssertions.assertSoftly;

public class RunnerProfileAssuredSupport {

private RunnerProfileAssuredSupport() {
}

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

public static RunnerResponse.MyProfile 러너_본인_프로필_응답(final Runner 러너) {
return RunnerResponse.MyProfile.from(러너);
}

public static class RunnerClientRequestBuilder {

private ExtractableResponse<Response> response;

private String accessToken;

public RunnerClientRequestBuilder 토큰으로_로그인한다(final String 토큰) {
this.accessToken = 토큰;
return this;
}

public RunnerClientRequestBuilder 러너_본인_프로필을_가지고_있는_토큰으로_조회한다() {
response = AssuredSupport.get("/api/v1/profile/runner/me", accessToken);
return this;
}

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

public static class RunnerServerResponseBuilder {

private final ExtractableResponse<Response> response;

public RunnerServerResponseBuilder(final ExtractableResponse<Response> response) {
this.response = response;
}

public void 러너_본인_프로필_조회_성공을_검증한다(final RunnerResponse.MyProfile 러너_본인_프로필_응답) {
final RunnerResponse.MyProfile actual = this.response.as(RunnerResponse.MyProfile.class);

assertSoftly(softly -> {
softly.assertThat(actual.name()).isEqualTo(러너_본인_프로필_응답.name());
softly.assertThat(actual.company()).isEqualTo(러너_본인_프로필_응답.company());
softly.assertThat(actual.imageUrl()).isEqualTo(러너_본인_프로필_응답.imageUrl());
softly.assertThat(actual.githubUrl()).isEqualTo(러너_본인_프로필_응답.githubUrl());
softly.assertThat(actual.introduction()).isEqualTo(러너_본인_프로필_응답.introduction());
softly.assertThat(actual.technicalTags()).isEqualTo(러너_본인_프로필_응답.technicalTags());
}
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import touch.baton.fixture.domain.RunnerPostFixture;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.ArrayList;

import static touch.baton.fixture.vo.DeadlineFixture.deadline;
import static touch.baton.fixture.vo.IntroductionFixture.introduction;
Expand Down Expand Up @@ -45,7 +45,7 @@ class RunnerPostAssuredReadTest extends AssuredTestConfig {
ReviewStatus.NOT_STARTED,
true,
RunnerResponse.Detail.from(러너_헤나),
Collections.emptyList()
new ArrayList<>()
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestExecutionListeners;
import touch.baton.domain.member.repository.MemberRepository;
import touch.baton.domain.runner.repository.RunnerRepository;
import touch.baton.domain.runnerpost.repository.RunnerPostRepository;
Expand All @@ -20,6 +22,8 @@

import static org.mockito.BDDMockito.when;

@Import(JpaConfig.class)
@TestExecutionListeners(value = AssuredTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AssuredTestConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package touch.baton.config;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;

import java.util.List;

public class AssuredTestExecutionListener extends AbstractTestExecutionListener {

@Override
public void afterTestMethod(final TestContext testContext) {
final JdbcTemplate jdbcTemplate = getJdbcTemplate(testContext);
final List<String> truncateQueries = getTruncateQueries(jdbcTemplate);
truncateTables(jdbcTemplate, truncateQueries);
}

private List<String> getTruncateQueries(final JdbcTemplate jdbcTemplate) {
return jdbcTemplate.queryForList("""
SELECT Concat('TRUNCATE TABLE ', TABLE_NAME, ';') AS q
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'PUBLIC'
""", String.class);
}

private JdbcTemplate getJdbcTemplate(final TestContext testContext) {
return testContext.getApplicationContext().getBean(JdbcTemplate.class);
}

private void truncateTables(final JdbcTemplate jdbcTemplate, final List<String> truncateQueries) {
execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY FALSE");
truncateQueries.forEach(v -> execute(jdbcTemplate, v));
execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY TRUE");
}

private void execute(final JdbcTemplate jdbcTemplate, final String query) {
jdbcTemplate.execute(query);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,78 @@
package touch.baton.document.profile.runner.read;

//@WebMvcTest(RunnerProfileController.class)
//public class RunnerProfileReadApiTest extends RestdocsConfig {
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.runner.controller.RunnerProfileController;
import touch.baton.domain.runnerpost.service.RunnerPostService;
import touch.baton.domain.technicaltag.TechnicalTag;
import touch.baton.fixture.domain.MemberFixture;
import touch.baton.fixture.domain.RunnerFixture;
import touch.baton.fixture.domain.TechnicalTagFixture;

import java.util.List;
import java.util.Optional;

import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.BDDMockito.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.get;
import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
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.test.web.servlet.result.MockMvcResultHandlers.print;
import static touch.baton.fixture.vo.TagNameFixture.tagName;

@WebMvcTest(RunnerProfileController.class)
class RunnerProfileReadApiTest extends RestdocsConfig {

@MockBean
RunnerPostService runnerPostService;

@BeforeEach
void setUp() {
restdocsSetUp(new RunnerProfileController(runnerPostService));
}

@DisplayName("러너 본인 프로필 조회 API")
@Test
void readMyProfileByToken() throws Exception {
// given
final TechnicalTag java = TechnicalTagFixture.create(tagName("java"));
final TechnicalTag spring = TechnicalTagFixture.create(tagName("spring"));
final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena(), List.of(java, spring));
final String token = getAccessTokenBySocialId(runner.getMember().getSocialId().getValue());

when(oauthRunnerRepository.joinByMemberSocialId(notNull()))
.thenReturn(Optional.ofNullable(runner));

// then
mockMvc.perform(get("/api/v1/profile/runner/me")
.header(AUTHORIZATION, "Bearer " + token))
.andDo(print())
.andDo(restDocs.document(
requestHeaders(
headerWithName(AUTHORIZATION).description("Bearer JWT")
),
responseFields(
fieldWithPath("name").type(STRING).description("러너 이름"),
fieldWithPath("company").type(STRING).description("러너 소속 회사"),
fieldWithPath("imageUrl").type(STRING).description("러너 프로필 이미지 url"),
fieldWithPath("githubUrl").type(STRING).description("러너 깃허브 url"),
fieldWithPath("introduction").type(STRING).description("러너 자기소개"),
fieldWithPath("technicalTags").type(ARRAY).description("러너 기술 태그 목록")
)
))
.andDo(print());
}

// @MockBean
// private RunnerPostService runnerPostService;

// @DisplayName("러너 프로필 조회 API")
// @Test
Expand All @@ -21,4 +89,4 @@
//
// // then
// }
//}
}
Loading

0 comments on commit caccf41

Please sign in to comment.