From 929acc54f25cb72202074aed2a0791eb3695dd80 Mon Sep 17 00:00:00 2001 From: Kim Heebin Date: Wed, 1 Nov 2023 23:31:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B2=8C=EB=A0=88=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Bug 임베디드 타입 생성 * feat: 벌레 조회 API 구현 * docs: PR merge 시, Issue 자동 close로 수정 * refactor: 엔티티 생성자 id 포함으로 변경 * feat: 벌레 개수 검증 추가 * test: 벌레 조회 서비스 테스트 * style: dto 내 bug 패키지 제거 * test: Bug 도메인 테스트 * style: 테스트 메서드 네이밍 수정 * test: 벌레 조회 controller 테스트 * refactor: private 생성자 추가 * test: 멤버 fixture 생성 및 적용 * test: 벌레 fixture 생성 및 적용 * test: 멤버 엔티티 테스트에 Bug 추가 * fix: code smell 제거 * style: BugMapper 메서드 네이밍 수정 * style: return 전 줄바꿈 추가 * refactor: ResponseStatus + DTO 방식으로 변경 * test: 벌레 개수 검증 테스트에 ParameterizedTest 적용 --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .../moabam/api/application/BugService.java | 24 ++++++++ .../moabam/api/application/MemberService.java | 25 +++++++++ .../com/moabam/api/domain/entity/Bug.java | 47 ++++++++++++++++ .../com/moabam/api/domain/entity/Member.java | 19 ++----- .../domain/repository/MemberRepository.java | 9 +++ .../java/com/moabam/api/dto/BugMapper.java | 18 ++++++ .../java/com/moabam/api/dto/BugResponse.java | 12 ++++ .../java/com/moabam/api/dto/OAuthMapper.java | 1 - .../api/presentation/BugController.java | 26 +++++++++ .../global/error/model/ErrorMessage.java | 6 +- .../api/application/BugServiceTest.java | 44 +++++++++++++++ .../com/moabam/api/domain/entity/BugTest.java | 30 ++++++++++ .../moabam/api/domain/entity/MemberTest.java | 8 ++- .../api/presentation/BugControllerTest.java | 55 +++++++++++++++++++ .../java/com/moabam/fixture/BugFixture.java | 18 ++++++ .../com/moabam/fixture/MemberFixture.java | 19 +++++++ 17 files changed, 345 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/moabam/api/application/BugService.java create mode 100644 src/main/java/com/moabam/api/application/MemberService.java create mode 100644 src/main/java/com/moabam/api/domain/entity/Bug.java create mode 100644 src/main/java/com/moabam/api/domain/repository/MemberRepository.java create mode 100644 src/main/java/com/moabam/api/dto/BugMapper.java create mode 100644 src/main/java/com/moabam/api/dto/BugResponse.java create mode 100644 src/main/java/com/moabam/api/presentation/BugController.java create mode 100644 src/test/java/com/moabam/api/application/BugServiceTest.java create mode 100644 src/test/java/com/moabam/api/domain/entity/BugTest.java create mode 100644 src/test/java/com/moabam/api/presentation/BugControllerTest.java create mode 100644 src/test/java/com/moabam/fixture/BugFixture.java create mode 100644 src/test/java/com/moabam/fixture/MemberFixture.java diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7362d363..a5e5b65d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,11 @@ ## 📋 Checklist + - [ ] 🔀 PR 제목의 형식을 잘 작성했나요? (e.g. `feat: 유저 조회 기능 구현`) - [ ] 🏷️ 라벨, 프로젝트, 마일스톤은 등록했나요? - [ ] 🧹 코드 스멜은 해결했나요? ## 🧩 이슈 번호 -- #이슈번호 +- close #이슈번호 ## 👩‍💻 공유 포인트 및 논의 사항 diff --git a/src/main/java/com/moabam/api/application/BugService.java b/src/main/java/com/moabam/api/application/BugService.java new file mode 100644 index 00000000..74f98623 --- /dev/null +++ b/src/main/java/com/moabam/api/application/BugService.java @@ -0,0 +1,24 @@ +package com.moabam.api.application; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.domain.entity.Member; +import com.moabam.api.dto.BugMapper; +import com.moabam.api.dto.BugResponse; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class BugService { + + private final MemberService memberService; + + public BugResponse getBug(Long memberId) { + Member member = memberService.getById(memberId); + + return BugMapper.toBugResponse(member.getBug()); + } +} diff --git a/src/main/java/com/moabam/api/application/MemberService.java b/src/main/java/com/moabam/api/application/MemberService.java new file mode 100644 index 00000000..112fcd7e --- /dev/null +++ b/src/main/java/com/moabam/api/application/MemberService.java @@ -0,0 +1,25 @@ +package com.moabam.api.application; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.domain.entity.Member; +import com.moabam.api.domain.repository.MemberRepository; +import com.moabam.global.error.exception.NotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + public Member getById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moabam/api/domain/entity/Bug.java b/src/main/java/com/moabam/api/domain/entity/Bug.java new file mode 100644 index 00000000..12fd020b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/entity/Bug.java @@ -0,0 +1,47 @@ +package com.moabam.api.domain.entity; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Bug { + + @Column(name = "morning_bug", nullable = false) + @ColumnDefault("0") + private int morningBug; + + @Column(name = "night_bug", nullable = false) + @ColumnDefault("0") + private int nightBug; + + @Column(name = "golden_bug", nullable = false) + @ColumnDefault("0") + private int goldenBug; + + @Builder + private Bug(int morningBug, int nightBug, int goldenBug) { + this.morningBug = validateBugCount(morningBug); + this.nightBug = validateBugCount(nightBug); + this.goldenBug = validateBugCount(goldenBug); + } + + private int validateBugCount(int bug) { + if (bug < 0) { + throw new BadRequestException(INVALID_BUG_COUNT); + } + + return bug; + } +} diff --git a/src/main/java/com/moabam/api/domain/entity/Member.java b/src/main/java/com/moabam/api/domain/entity/Member.java index a0a9e749..f7108bac 100644 --- a/src/main/java/com/moabam/api/domain/entity/Member.java +++ b/src/main/java/com/moabam/api/domain/entity/Member.java @@ -13,6 +13,7 @@ import com.moabam.global.common.util.BaseImageUrl; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -25,8 +26,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@Getter @Entity +@Getter @Table(name = "member") @SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where participant_id = ?") @Where(clause = "deleted_at IS NOT NULL") @@ -66,17 +67,8 @@ public class Member extends BaseTimeEntity { @ColumnDefault("0") private int currentMorningCount; - @Column(name = "morning_bug", nullable = false) - @ColumnDefault("0") - private int morningBug; - - @Column(name = "night_bug", nullable = false) - @ColumnDefault("0") - private int nightBug; - - @Column(name = "golden_bug", nullable = false) - @ColumnDefault("0") - private int goldenBug; + @Embedded + private Bug bug; @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) @@ -87,11 +79,12 @@ public class Member extends BaseTimeEntity { private LocalDateTime deletedAt; @Builder - private Member(Long id, String socialId, String nickname, String profileImage) { + private Member(Long id, String socialId, String nickname, String profileImage, Bug bug) { this.id = id; this.socialId = requireNonNull(socialId); this.nickname = requireNonNull(nickname); this.profileImage = requireNonNullElse(profileImage, BaseImageUrl.PROFILE_URL); + this.bug = requireNonNull(bug); this.role = Role.USER; } } diff --git a/src/main/java/com/moabam/api/domain/repository/MemberRepository.java b/src/main/java/com/moabam/api/domain/repository/MemberRepository.java new file mode 100644 index 00000000..095c71b4 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.entity.Member; + +public interface MemberRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/dto/BugMapper.java b/src/main/java/com/moabam/api/dto/BugMapper.java new file mode 100644 index 00000000..57bea850 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/BugMapper.java @@ -0,0 +1,18 @@ +package com.moabam.api.dto; + +import com.moabam.api.domain.entity.Bug; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public final class BugMapper { + + public static BugResponse toBugResponse(Bug bug) { + return BugResponse.builder() + .morningBug(bug.getMorningBug()) + .nightBug(bug.getNightBug()) + .goldenBug(bug.getGoldenBug()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/dto/BugResponse.java b/src/main/java/com/moabam/api/dto/BugResponse.java new file mode 100644 index 00000000..256c18cb --- /dev/null +++ b/src/main/java/com/moabam/api/dto/BugResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto; + +import lombok.Builder; + +@Builder +public record BugResponse( + int morningBug, + int nightBug, + int goldenBug +) { + +} diff --git a/src/main/java/com/moabam/api/dto/OAuthMapper.java b/src/main/java/com/moabam/api/dto/OAuthMapper.java index dac14930..8b82483b 100644 --- a/src/main/java/com/moabam/api/dto/OAuthMapper.java +++ b/src/main/java/com/moabam/api/dto/OAuthMapper.java @@ -1,6 +1,5 @@ package com.moabam.api.dto; -import com.moabam.api.dto.AuthorizationCodeRequest; import com.moabam.global.config.OAuthConfig; import lombok.AccessLevel; diff --git a/src/main/java/com/moabam/api/presentation/BugController.java b/src/main/java/com/moabam/api/presentation/BugController.java new file mode 100644 index 00000000..d17dfa32 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/BugController.java @@ -0,0 +1,26 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.BugService; +import com.moabam.api.dto.BugResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/bugs") +@RequiredArgsConstructor +public class BugController { + + private final BugService bugService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public BugResponse getBug() { + return bugService.getBug(1L); + } +} diff --git a/src/main/java/com/moabam/global/error/model/ErrorMessage.java b/src/main/java/com/moabam/global/error/model/ErrorMessage.java index 82942270..1e381ef0 100644 --- a/src/main/java/com/moabam/global/error/model/ErrorMessage.java +++ b/src/main/java/com/moabam/global/error/model/ErrorMessage.java @@ -13,7 +13,11 @@ public enum ErrorMessage { ROOM_MODIFY_UNAUTHORIZED_REQUEST("방장이 아닌 사용자는 방을 수정할 수 없습니다."), PARTICIPANT_NOT_FOUND("방에 대한 참여자의 정보가 없습니다."), LOGIN_FAILED("로그인에 실패했습니다."), - REQUEST_FAILD("네트우크 접근 실패입니다."); + REQUEST_FAILD("네트우크 접근 실패입니다."), + + MEMBER_NOT_FOUND("존재하지 않는 회원입니다."), + + INVALID_BUG_COUNT("벌레 개수는 0 이상이어야 합니다."); private final String message; } diff --git a/src/test/java/com/moabam/api/application/BugServiceTest.java b/src/test/java/com/moabam/api/application/BugServiceTest.java new file mode 100644 index 00000000..416ba5fb --- /dev/null +++ b/src/test/java/com/moabam/api/application/BugServiceTest.java @@ -0,0 +1,44 @@ +package com.moabam.api.application; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.domain.entity.Bug; +import com.moabam.api.domain.entity.Member; +import com.moabam.api.dto.BugResponse; +import com.moabam.fixture.MemberFixture; + +@ExtendWith(MockitoExtension.class) +class BugServiceTest { + + @InjectMocks + BugService bugService; + + @Mock + MemberService memberService; + + @DisplayName("벌레를 조회한다.") + @Test + void get_bug_success() { + // given + Long memberId = 1L; + Member member = MemberFixture.member(); + given(memberService.getById(memberId)).willReturn(member); + + // when + BugResponse response = bugService.getBug(memberId); + + // then + Bug bug = member.getBug(); + assertThat(response.morningBug()).isEqualTo(bug.getMorningBug()); + assertThat(response.nightBug()).isEqualTo(bug.getNightBug()); + assertThat(response.goldenBug()).isEqualTo(bug.getGoldenBug()); + } +} diff --git a/src/test/java/com/moabam/api/domain/entity/BugTest.java b/src/test/java/com/moabam/api/domain/entity/BugTest.java new file mode 100644 index 00000000..c356fd58 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/entity/BugTest.java @@ -0,0 +1,30 @@ +package com.moabam.api.domain.entity; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.moabam.global.error.exception.BadRequestException; + +class BugTest { + + @DisplayName("벌레 개수가 음수이면 예외가 발생한다.") + @ParameterizedTest + @CsvSource({ + "-10, 10, 10", + "10, -10, 10", + "10, 10, -10", + }) + void validate_bug_count_exception(int morningBug, int nightBug, int goldenBug) { + Bug.BugBuilder bugBuilder = Bug.builder() + .morningBug(morningBug) + .nightBug(nightBug) + .goldenBug(goldenBug); + + assertThatThrownBy(bugBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("벌레 개수는 0 이상이어야 합니다."); + } +} diff --git a/src/test/java/com/moabam/api/domain/entity/MemberTest.java b/src/test/java/com/moabam/api/domain/entity/MemberTest.java index dbc695fc..8255d843 100644 --- a/src/test/java/com/moabam/api/domain/entity/MemberTest.java +++ b/src/test/java/com/moabam/api/domain/entity/MemberTest.java @@ -23,6 +23,7 @@ void create_member_success() { .socialId(socialId) .nickname(nickname) .profileImage(profileImage) + .bug(Bug.builder().build()) .build()); } @@ -35,14 +36,15 @@ void create_member_noImage_success() { .socialId(socialId) .nickname(nickname) .profileImage(null) + .bug(Bug.builder().build()) .build(); assertAll( () -> assertThat(member.getProfileImage()).isEqualTo(BaseImageUrl.PROFILE_URL), () -> assertThat(member.getRole()).isEqualTo(Role.USER), - () -> assertThat(member.getNightBug()).isZero(), - () -> assertThat(member.getGoldenBug()).isZero(), - () -> assertThat(member.getMorningBug()).isZero(), + () -> assertThat(member.getBug().getNightBug()).isZero(), + () -> assertThat(member.getBug().getGoldenBug()).isZero(), + () -> assertThat(member.getBug().getMorningBug()).isZero(), () -> assertThat(member.getTotalCertifyCount()).isZero(), () -> assertThat(member.getReportCount()).isZero(), () -> assertThat(member.getCurrentMorningCount()).isZero(), diff --git a/src/test/java/com/moabam/api/presentation/BugControllerTest.java b/src/test/java/com/moabam/api/presentation/BugControllerTest.java new file mode 100644 index 00000000..4bfedb04 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/BugControllerTest.java @@ -0,0 +1,55 @@ +package com.moabam.api.presentation; + +import static java.nio.charset.StandardCharsets.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.BugService; +import com.moabam.api.dto.BugMapper; +import com.moabam.api.dto.BugResponse; +import com.moabam.fixture.BugFixture; + +@SpringBootTest +@AutoConfigureMockMvc +class BugControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + BugService bugService; + + @DisplayName("벌레를 조회한다.") + @Test + void get_bug_success() throws Exception { + // given + Long memberId = 1L; + BugResponse expected = BugMapper.toBugResponse(BugFixture.bug()); + given(bugService.getBug(memberId)).willReturn(expected); + + // expected + String content = mockMvc.perform(get("/bugs")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + BugResponse actual = objectMapper.readValue(content, BugResponse.class); + Assertions.assertThat(actual).isEqualTo(expected); + } +} diff --git a/src/test/java/com/moabam/fixture/BugFixture.java b/src/test/java/com/moabam/fixture/BugFixture.java new file mode 100644 index 00000000..8d2b8703 --- /dev/null +++ b/src/test/java/com/moabam/fixture/BugFixture.java @@ -0,0 +1,18 @@ +package com.moabam.fixture; + +import com.moabam.api.domain.entity.Bug; + +public final class BugFixture { + + public static final int MORNING_BUG = 10; + public static final int NIGHT_BUG = 20; + public static final int GOLDEN_BUG = 30; + + public static Bug bug() { + return Bug.builder() + .morningBug(MORNING_BUG) + .nightBug(NIGHT_BUG) + .goldenBug(GOLDEN_BUG) + .build(); + } +} diff --git a/src/test/java/com/moabam/fixture/MemberFixture.java b/src/test/java/com/moabam/fixture/MemberFixture.java new file mode 100644 index 00000000..02c5d84a --- /dev/null +++ b/src/test/java/com/moabam/fixture/MemberFixture.java @@ -0,0 +1,19 @@ +package com.moabam.fixture; + +import com.moabam.api.domain.entity.Member; + +public final class MemberFixture { + + public static final String SOCIAL_ID = "test123"; + public static final String NICKNAME = "모아밤"; + public static final String PROFILE_IMAGE = "/profile/moabam.png"; + + public static Member member() { + return Member.builder() + .socialId(SOCIAL_ID) + .nickname(NICKNAME) + .profileImage(PROFILE_IMAGE) + .bug(BugFixture.bug()) + .build(); + } +}