From 6876104dbeed9098167e268ea7916b9da5d0eda2 Mon Sep 17 00:00:00 2001 From: Park Seyeon Date: Thu, 23 Nov 2023 19:32:45 +0900 Subject: [PATCH] =?UTF-8?q?feature:=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#142)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 새 스킨 조회 기능 및 테스트 코드 추가 * chore: jpa관련 config 설정 - 버전 호환오류로 인한 기본 Template설정 * feat: 기본 새 스킨 조회 query 추가 * feat: 회원과 벌레에 대한 조회 쿼리 및 테스트 코드 추가 * feat: 회원 정보 조회 기능 및 테스트 코드 추가 * refactor: 회원과 Item 서비스의 의존성 순환을 피하기 위해 inventorySearchService 생성 * refactor: 회원과 Item 서비스의 의존성 순환을 피하기 위해 inventorySearchService 생성 * feat: 회원 정보 조회 API 추가 * style: 메서드 접근 제어자에 따른 순서 변경 * refactor: inventorySearchService 제거 후 memberService에서 repository 추가 * refactor: transform에서 stream으로 동작 변경 * style: 리뷰 반영 --- .../api/application/member/MemberMapper.java | 66 +- .../api/application/member/MemberService.java | 47 + .../repository/InventorySearchRepository.java | 10 + .../com/moabam/api/domain/member/Badge.java | 48 + .../moabam/api/domain/member/BadgeType.java | 35 + .../member/repository/BadgeRepository.java | 9 + .../repository/MemberSearchRepository.java | 31 + .../moabam/api/dto/member/BadgeResponse.java | 13 + .../com/moabam/api/dto/member/MemberInfo.java | 20 + .../api/dto/member/MemberInfoResponse.java | 26 + .../dto/member/MemberInfoSearchResponse.java | 21 + .../api/presentation/MemberController.java | 7 + .../global/common/util/GlobalConstant.java | 1 + .../com/moabam/global/config/JpaConfig.java | 1 + .../global/error/model/ErrorMessage.java | 2 + src/main/resources/config | 2 +- src/main/resources/static/docs/coupon.html | 2815 ++++------------- src/main/resources/static/docs/index.html | 2 +- .../resources/static/docs/notification.html | 2639 +++------------ .../application/member/MemberServiceTest.java | 131 +- .../InventorySearchRepositoryTest.java | 37 + .../domain/member/MemberRepositoryTest.java | 69 +- .../presentation/MemberControllerTest.java | 233 +- .../moabam/support/fixture/BadgeFixture.java | 14 + .../fixture/MemberInfoSearchFixture.java | 36 + 25 files changed, 1906 insertions(+), 4409 deletions(-) create mode 100644 src/main/java/com/moabam/api/domain/member/Badge.java create mode 100644 src/main/java/com/moabam/api/domain/member/BadgeType.java create mode 100644 src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java create mode 100644 src/main/java/com/moabam/api/dto/member/BadgeResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfo.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java create mode 100644 src/test/java/com/moabam/support/fixture/BadgeFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java diff --git a/src/main/java/com/moabam/api/application/member/MemberMapper.java b/src/main/java/com/moabam/api/application/member/MemberMapper.java index 6bdd4e64..3c823e22 100644 --- a/src/main/java/com/moabam/api/application/member/MemberMapper.java +++ b/src/main/java/com/moabam/api/application/member/MemberMapper.java @@ -1,8 +1,22 @@ package com.moabam.api.application.member; +import static com.moabam.global.common.util.GlobalConstant.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.member.BadgeType; import com.moabam.api.domain.member.Member; -import com.moabam.api.dto.member.DeleteMemberResponse; +import com.moabam.api.dto.member.BadgeResponse; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.MemberInfoSearchResponse; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -17,10 +31,52 @@ public static Member toMember(Long socialId) { .build(); } - public static DeleteMemberResponse toDeleteMemberResponse(Long memberId, String socialId) { - return DeleteMemberResponse.builder() - .socialId(socialId) - .id(memberId) + public static MemberInfoSearchResponse toMemberInfoSearchResponse(List memberInfos) { + MemberInfo infos = memberInfos.get(0); + List badgeTypes = memberInfos.stream() + .map(MemberInfo::badges) + .filter(Objects::nonNull) + .toList(); + + return MemberInfoSearchResponse.builder() + .nickname(infos.nickname()) + .profileImage(infos.profileImage()) + .intro(infos.intro()) + .totalCertifyCount(infos.totalCertifyCount()) + .badges(new HashSet<>(badgeTypes)) + .goldenBug(infos.goldenBug()) + .morningBug(infos.morningBug()) + .nightBug(infos.nightBug()) .build(); } + + public static MemberInfoResponse toMemberInfoResponse(MemberInfoSearchResponse memberInfoSearchResponse, + List inventories) { + long certifyCount = memberInfoSearchResponse.totalCertifyCount(); + + return MemberInfoResponse.builder() + .nickname(memberInfoSearchResponse.nickname()) + .profileImage(memberInfoSearchResponse.profileImage()) + .intro(memberInfoSearchResponse.intro()) + .level(certifyCount / LEVEL_DIVISOR) + .exp(certifyCount % LEVEL_DIVISOR) + .birds(defaultSkins(inventories)) + .badges(badgedNames(memberInfoSearchResponse.badges())) + .goldenBug(memberInfoSearchResponse.goldenBug()) + .morningBug(memberInfoSearchResponse.morningBug()) + .nightBug(memberInfoSearchResponse.nightBug()) + .build(); + } + + private static List badgedNames(Set badgeTypes) { + return BadgeType.memberBadgeMap(badgeTypes); + } + + private static Map defaultSkins(List inventories) { + return inventories.stream() + .collect(Collectors.toMap( + inventory -> inventory.getItem().getType().name(), + inventory -> inventory.getItem().getImage() + )); + } } diff --git a/src/main/java/com/moabam/api/application/member/MemberService.java b/src/main/java/com/moabam/api/application/member/MemberService.java index cf6ae849..01b3201e 100644 --- a/src/main/java/com/moabam/api/application/member/MemberService.java +++ b/src/main/java/com/moabam/api/application/member/MemberService.java @@ -3,18 +3,27 @@ import static com.moabam.global.error.model.ErrorMessage.*; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.moabam.api.application.auth.mapper.AuthMapper; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.repository.InventorySearchRepository; import com.moabam.api.domain.member.Member; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.api.domain.member.repository.MemberSearchRepository; import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.MemberInfoSearchResponse; +import com.moabam.global.auth.model.AuthMember; import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.error.exception.BadRequestException; import com.moabam.global.error.exception.NotFoundException; import lombok.RequiredArgsConstructor; @@ -25,6 +34,7 @@ public class MemberService { private final MemberRepository memberRepository; + private final InventorySearchRepository inventorySearchRepository; private final MemberSearchRepository memberSearchRepository; private final ClockHolder clockHolder; @@ -58,9 +68,46 @@ public void delete(Member member) { memberRepository.delete(member); } + public MemberInfoResponse searchInfo(AuthMember authMember, Long memberId) { + Long searchId = authMember.id(); + boolean isMe = confirmMe(searchId, memberId); + + if (!isMe) { + searchId = memberId; + } + + MemberInfoSearchResponse memberInfoSearchResponse = findMemberInfo(searchId, isMe); + List inventories = getDefaultSkin(searchId); + + return MemberMapper.toMemberInfoResponse(memberInfoSearchResponse, inventories); + } + + private List getDefaultSkin(Long searchId) { + List inventories = inventorySearchRepository.findBirdsDefaultSkin(searchId); + if (inventories.size() != GlobalConstant.DEFAULT_SKIN_SIZE) { + throw new BadRequestException(INVALID_DEFAULT_SKIN_SIZE); + } + + return inventories; + } + private Member signUp(Long socialId) { Member member = MemberMapper.toMember(socialId); return memberRepository.save(member); } + + private MemberInfoSearchResponse findMemberInfo(Long searchId, boolean isMe) { + List memberInfos = memberSearchRepository.findMemberAndBadges(searchId, isMe); + + if (memberInfos.isEmpty()) { + throw new BadRequestException(MEMBER_NOT_FOUND); + } + + return MemberMapper.toMemberInfoSearchResponse(memberInfos); + } + + private boolean confirmMe(Long myId, Long memberId) { + return Objects.isNull(memberId) || myId.equals(memberId); + } } diff --git a/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java b/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java index 2ab6e605..c4f85cb5 100644 --- a/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java +++ b/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java @@ -53,4 +53,14 @@ public List findItems(Long memberId, ItemType type) { .select(item) .fetch(); } + + public List findBirdsDefaultSkin(Long searchId) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item) + .on(inventory.item.id.eq(item.id)) + .where( + inventory.memberId.eq(searchId), + inventory.isDefault.isTrue() + ).fetch(); + } } diff --git a/src/main/java/com/moabam/api/domain/member/Badge.java b/src/main/java/com/moabam/api/domain/member/Badge.java new file mode 100644 index 00000000..491a8ebf --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/Badge.java @@ -0,0 +1,48 @@ +package com.moabam.api.domain.member; + +import static java.util.Objects.*; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class Badge { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", nullable = false) + private Long memberId; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private BadgeType type; + + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @Builder + private Badge(Long memberId, BadgeType type) { + this.memberId = requireNonNull(memberId); + this.type = requireNonNull(type); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/BadgeType.java b/src/main/java/com/moabam/api/domain/member/BadgeType.java new file mode 100644 index 00000000..92e90edf --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/BadgeType.java @@ -0,0 +1,35 @@ +package com.moabam.api.domain.member; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.moabam.api.dto.member.BadgeResponse; + +import lombok.Getter; + +@Getter +public enum BadgeType { + + MORNING_BIRTH("MORNING", "오목눈이 탄생"), + MORNING_ADULT("MORNING", "어른 오목눈이"), + NIGHT_BIRTH("NIGHT", "부엉이 탄생"), + NIGHT_ADULT("NIGHT", "어른 부엉이"); + + private final String period; + private final String korean; + + BadgeType(String period, String korean) { + this.period = period; + this.korean = korean; + } + + public static List memberBadgeMap(Set badgeTypes) { + return Arrays.stream(BadgeType.values()) + .map(badgeType -> BadgeResponse.builder() + .badge(badgeType) + .unlock(badgeTypes.contains(badgeType)) + .build()) + .toList(); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java b/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java new file mode 100644 index 00000000..dd16ebff --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.member.Badge; + +public interface BadgeRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java b/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java index 15fd6981..a90e0009 100644 --- a/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java +++ b/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java @@ -1,14 +1,20 @@ package com.moabam.api.domain.member.repository; +import static com.moabam.api.domain.member.QBadge.*; import static com.moabam.api.domain.member.QMember.*; import static com.moabam.api.domain.room.QParticipant.*; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.springframework.stereotype.Repository; import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.member.MemberInfo; import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -43,4 +49,29 @@ public Optional findMemberNotManager(Long memberId) { ) .fetchFirst()); } + + public List findMemberAndBadges(Long searchId, boolean isMe) { + List> selectExpression = new ArrayList<>(List.of( + member.nickname, + member.profileImage, + member.intro, + member.totalCertifyCount, + badge.type)); + + if (isMe) { + selectExpression.addAll(List.of( + member.bug.goldenBug, + member.bug.morningBug, + member.bug.nightBug)); + } + + return jpaQueryFactory + .select(Projections.constructor(MemberInfo.class, selectExpression.toArray(new Expression[0]))) + .from(member) + .leftJoin(badge).on(member.id.eq(badge.memberId)) + .where( + DynamicQuery.generateIsNull(true, member.deletedAt), + member.id.eq(searchId) + ).fetch(); + } } diff --git a/src/main/java/com/moabam/api/dto/member/BadgeResponse.java b/src/main/java/com/moabam/api/dto/member/BadgeResponse.java new file mode 100644 index 00000000..7e83d5d6 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/BadgeResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.member; + +import com.moabam.api.domain.member.BadgeType; + +import lombok.Builder; + +@Builder +public record BadgeResponse( + BadgeType badge, + boolean unlock +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfo.java b/src/main/java/com/moabam/api/dto/member/MemberInfo.java new file mode 100644 index 00000000..189469c4 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfo.java @@ -0,0 +1,20 @@ +package com.moabam.api.dto.member; + +import com.moabam.api.domain.member.BadgeType; + +public record MemberInfo( + String nickname, + String profileImage, + String intro, + long totalCertifyCount, + BadgeType badges, + Integer goldenBug, + Integer morningBug, + Integer nightBug +) { + + public MemberInfo(String nickname, String profileImage, String intro, + long totalCertifyCount, BadgeType badges) { + this(nickname, profileImage, intro, totalCertifyCount, badges, null, null, null); + } +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java b/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java new file mode 100644 index 00000000..c7f37b0c --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java @@ -0,0 +1,26 @@ +package com.moabam.api.dto.member; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Builder; + +@Builder +public record MemberInfoResponse( + String nickname, + String profileImage, + String intro, + long level, + long exp, + Map birds, + List badges, + @JsonInclude(NON_NULL) Integer goldenBug, + @JsonInclude(NON_NULL) Integer morningBug, + @JsonInclude(NON_NULL) Integer nightBug +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java b/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java new file mode 100644 index 00000000..e22070cc --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java @@ -0,0 +1,21 @@ +package com.moabam.api.dto.member; + +import java.util.Set; + +import com.moabam.api.domain.member.BadgeType; + +import lombok.Builder; + +@Builder +public record MemberInfoSearchResponse( + String nickname, + String profileImage, + String intro, + long totalCertifyCount, + Set badges, + Integer goldenBug, + Integer morningBug, + Integer nightBug +) { + +} diff --git a/src/main/java/com/moabam/api/presentation/MemberController.java b/src/main/java/com/moabam/api/presentation/MemberController.java index 704718e0..5c5da9a0 100644 --- a/src/main/java/com/moabam/api/presentation/MemberController.java +++ b/src/main/java/com/moabam/api/presentation/MemberController.java @@ -3,6 +3,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -15,6 +16,7 @@ import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; import com.moabam.api.dto.auth.AuthorizationTokenResponse; import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfoResponse; import com.moabam.global.auth.annotation.Auth; import com.moabam.global.auth.model.AuthMember; @@ -58,4 +60,9 @@ public void logout(@Auth AuthMember authMember, HttpServletRequest httpServletRe public void deleteMember(@Auth AuthMember authMember) { authorizationService.unLinkMember(authMember); } + + @GetMapping(value = {"", "/{memberId}"}) + public MemberInfoResponse searchInfo(@Auth AuthMember authMember, @PathVariable(required = false) Long memberId) { + return memberService.searchInfo(authMember, memberId); + } } diff --git a/src/main/java/com/moabam/global/common/util/GlobalConstant.java b/src/main/java/com/moabam/global/common/util/GlobalConstant.java index 5b3d4dae..19e2a26d 100644 --- a/src/main/java/com/moabam/global/common/util/GlobalConstant.java +++ b/src/main/java/com/moabam/global/common/util/GlobalConstant.java @@ -17,4 +17,5 @@ public class GlobalConstant { public static final int ROOM_FIXED_SEARCH_SIZE = 10; public static final int LEVEL_DIVISOR = 10; + public static final int DEFAULT_SKIN_SIZE = 2; } diff --git a/src/main/java/com/moabam/global/config/JpaConfig.java b/src/main/java/com/moabam/global/config/JpaConfig.java index 9f0b6906..a443a76e 100644 --- a/src/main/java/com/moabam/global/config/JpaConfig.java +++ b/src/main/java/com/moabam/global/config/JpaConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; 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 c4971757..ddc96335 100644 --- a/src/main/java/com/moabam/global/error/model/ErrorMessage.java +++ b/src/main/java/com/moabam/global/error/model/ErrorMessage.java @@ -36,6 +36,8 @@ public enum ErrorMessage { MEMBER_ROOM_EXCEED("참여할 수 있는 방의 개수가 모두 찼습니다."), UNLINK_REQUEST_FAIL_ROLLBACK_SUCCESS("카카오 연결 요청 실패로 Rollback하였습니다."), + INVALID_DEFAULT_SKIN_SIZE("기본 스킨은 2개여야 합니다. 관리자에게 문의하세요"), + BUG_NOT_ENOUGH("보유한 벌레가 부족합니다."), ITEM_NOT_FOUND("존재하지 않는 아이템입니다."), diff --git a/src/main/resources/config b/src/main/resources/config index 35c04d25..2e460460 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 35c04d25c466b163ffceaf81b5d7e8855b78d7ec +Subproject commit 2e460460a0048796a3ef6fef17697935027a8708 diff --git a/src/main/resources/static/docs/coupon.html b/src/main/resources/static/docs/coupon.html index ec72ba0b..d31fe33a 100644 --- a/src/main/resources/static/docs/coupon.html +++ b/src/main/resources/static/docs/coupon.html @@ -1,2138 +1,467 @@ - - - - - 쿠폰(Coupon) - - + + + + +쿠폰(Coupon) + +
-
-

쿠폰(Coupon)

-
-
-
-
쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다.
-
-
-
-
-

쿠폰 생성

-
-
-
관리자가 쿠폰을 생성합니다.
-
-
-

요청

-
-
+
+

쿠폰(Coupon)

+
+
+
+
쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다.
+
+
+
+
+

쿠폰 생성

+
+
+
관리자가 쿠폰을 생성합니다.
+
+
+

요청

+
+
POST /admins/coupons HTTP/1.1
 Content-Type: application/json;charset=UTF-8
-Content-Length: 175
+Content-Length: 183
 Host: localhost:8080
 
 {
@@ -2144,68 +473,68 @@ 

요청

"startAt" : "2023-02-01", "openAt" : "2023-01-01" }
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 201 Created
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-

쿠폰 삭제

-
-
-
관리자가 쿠폰 ID와 일치하는 쿠폰을 삭제합니다.
-
-
-

요청

-
-
+
+
+
+
+
+

쿠폰 삭제

+
+
+
관리자가 쿠폰 ID와 일치하는 쿠폰을 삭제합니다.
+
+
+

요청

+
+
DELETE /admins/coupons/1 HTTP/1.1
 Host: localhost:8080
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-

특정 쿠폰 조회

-
-
-
관리자 혹은 사용자가 특정 ID와 일치하는 쿠폰을 조회합니다.
-
-
-
-

요청

-
-
+
+
+
+
+
+

특정 쿠폰 조회

+
+
+
관리자 혹은 사용자가 특정 ID와 일치하는 쿠폰을 조회합니다.
+
+
+
+

요청

+
+
GET /coupons/26 HTTP/1.1
 Host: localhost:8080
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
 Content-Type: application/json
-Content-Length: 205
+Content-Length: 215
 
 {
   "id" : 26,
@@ -2218,42 +547,42 @@ 

응답

"startAt" : "2023-02-01", "openAt" : "2023-01-01" }
-
-
-
-
-
-
-

상태에 따른 쿠폰들을 조회

-
-
-
관리자 혹은 사용자가 날짜 상태에 따라 쿠폰들을 조회합니다.
-
-
-
-

요청

-
-
+
+
+
+
+
+
+

상태에 따른 쿠폰들을 조회

+
+
+
관리자 혹은 사용자가 날짜 상태에 따라 쿠폰들을 조회합니다.
+
+
+
+

요청

+
+
POST /coupons/search HTTP/1.1
 Content-Type: application/json;charset=UTF-8
-Content-Length: 41
+Content-Length: 44
 Host: localhost:8080
 
 {
   "opened" : false,
   "ended" : false
 }
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
 Content-Type: application/json
-Content-Length: 206
+Content-Length: 216
 
 [ {
   "id" : 15,
@@ -2266,33 +595,33 @@ 

응답

"startAt" : "2023-03-01", "openAt" : "2023-01-01" } ]
-
-
-
-
-
-
-

특정 쿠폰에 대해 발급

-
-
-
사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다.
-
-
-
-

요청

-
-
+
+
+
+
+
+
+

특정 쿠폰에 대해 발급

+
+
+
사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다.
+
+
+
+

요청

+
+
POST /coupons HTTP/1.1
 Content-Type: application/x-www-form-urlencoded
 Host: localhost:8080
 Content-Length: 21
 
 couponName=couponName
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 400 Bad Request
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -2303,36 +632,36 @@ 

응답

{ "message" : "쿠폰 발급 가능 기간이 아닙니다." }
-
-
-
-
-
-
-

특정 사용자의 쿠폰 보관함을 조회

-
-
-
사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다.
-
-
-
-
-
-

쿠폰 사용 (진행 중)

-
-
-
사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
-
-
-
-
-
+
+
+
+
+
+
+

특정 사용자의 쿠폰 보관함을 조회

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다.
+
+
+
+
+
+

쿠폰 사용 (진행 중)

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
+
+
+
+
+
- + \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 8734736a..9996cde1 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -616,7 +616,7 @@

diff --git a/src/main/resources/static/docs/notification.html b/src/main/resources/static/docs/notification.html index 3727fab6..38ed7f63 100644 --- a/src/main/resources/static/docs/notification.html +++ b/src/main/resources/static/docs/notification.html @@ -1,2144 +1,473 @@ - - - - - 알림(Notification) - - + + + + +알림(Notification) + +
-
-

알림(Notification)

-
-
-
-
콕 찌르기 알림, FCM Token 저장 기능을 제공합니다.
-
-
-
-

콕 찌르기 알림

-
-
+
+

알림(Notification)

+
+
+
+
콕 찌르기 알림, FCM Token 저장 기능을 제공합니다.
+
+
+
+

콕 찌르기 알림

+
+
1) 특정 방의 사용자가 다른 사용자를 콕 찌릅니다.
 2) 서버에서 콕 찌를 대상의 FCM Token 여부를 검증합니다.
 3) Firebase 서버에 FCM Push Messaing 알림을 비동기로 요청합니다.
 4) Firebase 서버에서 FCM Token으로 식별된 기기에 알림을 보냅니다.
-
-
-

요청

-
-
+
+
+

요청

+
+
GET /notifications/rooms/4/members/4 HTTP/1.1
 Host: localhost:8080
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 404 Not Found
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -2149,45 +478,45 @@ 

응답

{ "message" : "해당 유저는 접속 중이 아닙니다." }
-
-
-
-
-

FCM TOKEN 저장

-
-
-
1) 특정 사용자의 FCM-TOKEN을 받아서 REDIS DB에 저장합니다.
-
-
-

요청

-
-
+
+
+
+
+

FCM TOKEN 저장

+
+
+
1) 특정 사용자의 FCM-TOKEN을 받아서 REDIS DB에 저장합니다.
+
+
+

요청

+
+
POST /notifications HTTP/1.1
 Content-Type: application/x-www-form-urlencoded
 Host: localhost:8080
 Content-Length: 9
 
 fcmToken=
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
+
+
+
+
+
- + \ No newline at end of file diff --git a/src/test/java/com/moabam/api/application/member/MemberServiceTest.java b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java index 88bc37c7..8db4966b 100644 --- a/src/test/java/com/moabam/api/application/member/MemberServiceTest.java +++ b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java @@ -1,30 +1,42 @@ package com.moabam.api.application.member; +import static com.moabam.global.error.model.ErrorMessage.*; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.*; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; 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.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventorySearchRepository; import com.moabam.api.domain.member.Member; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.api.domain.member.repository.MemberSearchRepository; import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfoResponse; import com.moabam.global.auth.model.AuthMember; import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; import com.moabam.support.annotation.WithMember; import com.moabam.support.common.FilterProcessExtension; import com.moabam.support.fixture.AuthorizationResponseFixture; -import com.moabam.support.fixture.DeleteMemberFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.MemberInfoSearchFixture; @ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) class MemberServiceTest { @@ -38,6 +50,9 @@ class MemberServiceTest { @Mock MemberSearchRepository memberSearchRepository; + @Mock + InventorySearchRepository inventorySearchRepository; + @Mock ClockHolder clockHolder; @@ -95,4 +110,118 @@ void undo_delete_member(@WithMember AuthMember authMember) { assertThat(member).isNotNull(); assertThat(member.getSocialId()).contains("delete"); } + + @DisplayName("내 회원 정보가 없어서 예외 발생") + @Test + void search_my_info_failBy_member_null(@WithMember AuthMember authMember) { + // given + given(memberSearchRepository.findMemberAndBadges(authMember.id(), true)) + .willReturn(List.of()); + + // When + Then + assertThatThrownBy(() -> memberService.searchInfo(authMember, null)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("친구 회원 정보가 없어서 예외 발생") + @Test + void search_friend_info_failBy_member_null(@WithMember AuthMember authMember) { + // given + given(memberSearchRepository.findMemberAndBadges(123L, false)) + .willReturn(List.of()); + + // When + Then + assertThatThrownBy(() -> memberService.searchInfo(authMember, 123L)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("내 기본 스킨 2개가 없을 때 예외 발생") + @Test + void search_my_info_success(@WithMember AuthMember authMember) { + // Given + long total = 36; + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + + given(memberSearchRepository.findMemberAndBadges(authMember.id(), true)) + .willReturn(MemberInfoSearchFixture.friendMemberInfo(total)); + given(inventorySearchRepository.findBirdsDefaultSkin(authMember.id())) + .willReturn(List.of( + InventoryFixture.inventory(authMember.id(), morning), + InventoryFixture.inventory(authMember.id(), night))); + + // When + Then + MemberInfoResponse memberInfoResponse = memberService.searchInfo(authMember, null); + + assertAll( + () -> assertThat(memberInfoResponse.exp()).isEqualTo(total % 10), + () -> assertThat(memberInfoResponse.level()).isEqualTo(total / 10) + ); + } + + @DisplayName("기본 스킨을 가져온다.") + @Nested + class GetDefaultSkin { + + @DisplayName("성공") + @Test + void success(@WithMember AuthMember authMember) { + // given + long searchId = 1L; + Item morning = ItemFixture.morningSantaSkin().build(); + Item night = ItemFixture.nightMageSkin(); + Inventory morningSkin = InventoryFixture.inventory(searchId, morning); + Inventory nightSkin = InventoryFixture.inventory(searchId, night); + + given(memberSearchRepository.findMemberAndBadges(anyLong(), anyBoolean())) + .willReturn(MemberInfoSearchFixture.myInfo()); + given(inventorySearchRepository.findBirdsDefaultSkin(searchId)).willReturn(List.of(morningSkin, nightSkin)); + + // when + MemberInfoResponse memberInfoResponse = memberService.searchInfo(authMember, null); + + // then + assertThat(memberInfoResponse.birds()).containsEntry("MORNING", morningSkin.getItem().getImage()); + assertThat(memberInfoResponse.birds()).containsEntry("NIGHT", nightSkin.getItem().getImage()); + } + + @DisplayName("기본 스킨이 없어서 예외 발생") + @Test + void failBy_underSize(@WithMember AuthMember authMember) { + // given + given(memberSearchRepository.findMemberAndBadges(anyLong(), anyBoolean())) + .willReturn(MemberInfoSearchFixture.friendMemberInfo()); + given(inventorySearchRepository.findBirdsDefaultSkin(anyLong())).willReturn(List.of()); + + // when + assertThatThrownBy(() -> memberService.searchInfo(authMember, 123L)) + .isInstanceOf(BadRequestException.class) + .hasMessage(INVALID_DEFAULT_SKIN_SIZE.getMessage()); + } + + @DisplayName("기본 스킨이 3개 이상이어서 예외 발생") + @Test + void failBy_overSize(@WithMember AuthMember authMember) { + // given + long searchId = 1L; + Item morning = ItemFixture.morningSantaSkin().build(); + Item night = ItemFixture.nightMageSkin(); + Item kill = ItemFixture.morningKillerSkin().build(); + Inventory morningSkin = InventoryFixture.inventory(searchId, morning); + Inventory nightSkin = InventoryFixture.inventory(searchId, night); + Inventory killSkin = InventoryFixture.inventory(searchId, kill); + + given(memberSearchRepository.findMemberAndBadges(anyLong(), anyBoolean())) + .willReturn(MemberInfoSearchFixture.myInfo()); + given(inventorySearchRepository.findBirdsDefaultSkin(searchId)) + .willReturn(List.of(morningSkin, nightSkin, killSkin)); + + // when + assertThatThrownBy(() -> memberService.searchInfo(authMember, null)) + .isInstanceOf(BadRequestException.class) + .hasMessage(INVALID_DEFAULT_SKIN_SIZE.getMessage()); + } + } } diff --git a/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java index 58f2a8df..cfeddcea 100644 --- a/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java +++ b/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java @@ -19,6 +19,9 @@ import com.moabam.api.domain.member.Member; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; +import com.moabam.support.fixture.MemberFixture; @QuerydslRepositoryTest class InventorySearchRepositoryTest { @@ -107,4 +110,38 @@ void find_default_success() { // then assertThat(actual).isPresent().contains(inventory); } + + @DisplayName("기본 새 찾는 쿼리") + @Nested + class FindDefaultBird { + + @DisplayName("default 가져오기 성공") + @Test + void bird_find_success() { + // given + Member member = MemberFixture.member(); + member.enterMorningRoom(); + memberRepository.save(member); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // when + List inventories = inventorySearchRepository.findBirdsDefaultSkin(member.getId()); + + // then + assertThat(inventories).hasSize(2); + } + } } diff --git a/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java b/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java index 72cfe18b..071605d1 100644 --- a/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java +++ b/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.*; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; @@ -9,13 +10,18 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import com.moabam.api.application.member.MemberMapper; +import com.moabam.api.domain.member.repository.BadgeRepository; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.api.domain.member.repository.MemberSearchRepository; import com.moabam.api.domain.room.Participant; import com.moabam.api.domain.room.Room; import com.moabam.api.domain.room.repository.ParticipantRepository; import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoSearchResponse; import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.BadgeFixture; import com.moabam.support.fixture.MemberFixture; import com.moabam.support.fixture.ParticipantFixture; import com.moabam.support.fixture.RoomFixture; @@ -32,6 +38,9 @@ class MemberRepositoryTest { @Autowired RoomRepository roomRepository; + @Autowired + BadgeRepository badgeRepository; + @Autowired ParticipantRepository participantRepository; @@ -60,8 +69,6 @@ void room_exist_and_manager_error() { Member member = MemberFixture.member(); memberRepository.save(member); - Optional test1 = memberRepository.findById(1L); - Room room = RoomFixture.room(); roomRepository.save(room); @@ -97,4 +104,62 @@ void room_exist_and_not_manager_success() { assertThat(memberOptional).isNotEmpty(); } } + + @DisplayName("회원 정보 찾는 Query") + @Nested + class FindMemberInfo { + + @DisplayName("회원 없어서 실패") + @Test + void member_not_found() { + // Given + List memberInfos = memberSearchRepository.findMemberAndBadges(1L, false); + + // When + Then + assertThat(memberInfos).isEmpty(); + } + + @DisplayName("성공") + @Test + void search_info_success() { + // given + Member member = MemberFixture.member(); + member.enterMorningRoom(); + memberRepository.save(member); + + Badge morningBirth = BadgeFixture.badge(member.getId(), BadgeType.MORNING_BIRTH); + Badge morningAdult = BadgeFixture.badge(member.getId(), BadgeType.MORNING_ADULT); + Badge nightBirth = BadgeFixture.badge(member.getId(), BadgeType.NIGHT_BIRTH); + Badge nightAdult = BadgeFixture.badge(member.getId(), BadgeType.NIGHT_ADULT); + List badges = List.of(morningBirth, morningAdult, nightBirth, nightAdult); + badgeRepository.saveAll(badges); + + // when + List memberInfos = memberSearchRepository.findMemberAndBadges(member.getId(), true); + + // then + assertThat(memberInfos).isNotEmpty(); + + MemberInfoSearchResponse memberInfoSearchResponse = MemberMapper.toMemberInfoSearchResponse(memberInfos); + assertThat(memberInfoSearchResponse.badges()).hasSize(badges.size()); + } + + @DisplayName("성공") + @Test + void no_badges_search_success() { + // given + Member member = MemberFixture.member(); + member.enterMorningRoom(); + memberRepository.save(member); + + // when + List memberInfos = memberSearchRepository.findMemberAndBadges(member.getId(), true); + + // then + assertThat(memberInfos).isNotEmpty(); + + MemberInfoSearchResponse memberInfoSearchResponse = MemberMapper.toMemberInfoSearchResponse(memberInfos); + assertThat(memberInfoSearchResponse.badges()).isEmpty(); + } + } } diff --git a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java index ef769faf..8fc82662 100644 --- a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java @@ -1,12 +1,15 @@ package com.moabam.api.presentation; +import static com.moabam.global.common.util.GlobalConstant.*; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.*; 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 java.util.List; import java.util.Optional; import org.assertj.core.api.Assertions; @@ -30,15 +33,25 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.moabam.api.application.auth.OAuth2AuthorizationServerRequestService; import com.moabam.api.domain.auth.repository.TokenRepository; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.BadgeRepository; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; import com.moabam.api.domain.room.repository.ParticipantRepository; import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.dto.auth.TokenSaveValue; @@ -47,7 +60,12 @@ import com.moabam.global.error.handler.RestTemplateResponseHandler; import com.moabam.support.annotation.WithMember; import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.BadgeFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.ParticipantFixture; +import com.moabam.support.fixture.RoomFixture; import com.moabam.support.fixture.TokenSaveValueFixture; @Transactional @@ -75,6 +93,15 @@ class MemberControllerTest extends WithoutFilterSupporter { @Autowired RoomRepository roomRepository; + @Autowired + ItemRepository itemRepository; + + @Autowired + BadgeRepository badgeRepository; + + @Autowired + InventoryRepository inventoryRepository; + @Autowired ParticipantRepository participantRepository; @@ -95,7 +122,8 @@ void allSetUp() { restTemplateBuilder = new RestTemplateBuilder() .errorHandler(new RestTemplateResponseHandler()); - member = MemberFixture.member(); + member = MemberFixture.member("1", "nickname"); + member.increaseTotalCertifyCount(); memberRepository.save(member); } @@ -186,4 +214,207 @@ void unlink_social_member_failby_connection_error_and_rollback(int code) throws () -> assertThat(rollMember.getDeletedAt()).isNull() ); } + + @DisplayName("방장으로 인해 회원 삭제 조회 실패") + @WithMember + @Test + void unlink_social_member_failby_meber_is_manger() throws Exception { + // given + Room room = RoomFixture.room(); + room.changeManagerNickname(member.getNickname()); + + Participant participant = ParticipantFixture.participant(room, member.getId()); + participant.enableManager(); + roomRepository.save(room); + participantRepository.save(participant); + + // then + mockMvc.perform(delete("/members")) + .andExpect(status().isNotFound()); + } + + @DisplayName("내 정보 조회 성공") + @WithMember + @Test + void search_my_info_success() throws Exception { + // given + Badge morningBirth = BadgeFixture.badge(member.getId(), BadgeType.MORNING_BIRTH); + Badge morningAdult = BadgeFixture.badge(member.getId(), BadgeType.MORNING_ADULT); + Badge nightBirth = BadgeFixture.badge(member.getId(), BadgeType.NIGHT_BIRTH); + List badges = List.of(morningBirth, morningAdult, nightBirth); + badgeRepository.saveAll(badges); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // expected + mockMvc.perform(get("/members")) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(member.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(member.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(member.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(member.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(member.getTotalCertifyCount() % LEVEL_DIVISOR), + + MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getImage()), + MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("MORNING_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("MORNING_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("NIGHT_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[3].badge").value("NIGHT_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[3].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.goldenBug").value(member.getBug().getGoldenBug()), + MockMvcResultMatchers.jsonPath("$.morningBug").value(member.getBug().getMorningBug()), + MockMvcResultMatchers.jsonPath("$.nightBug").value(member.getBug().getNightBug()) + ).andDo(print()); + } + + @DisplayName("뱃지없는 내 정보 조회 성공") + @WithMember + @Test + void search_my_info_with_no_badge_success() throws Exception { + // given + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // expected + mockMvc.perform(get("/members")) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(member.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(member.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(member.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(member.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(member.getTotalCertifyCount() % LEVEL_DIVISOR), + + MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getImage()), + MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("MORNING_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("MORNING_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("NIGHT_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.badges[3].badge").value("NIGHT_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[3].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.goldenBug").value(member.getBug().getGoldenBug()), + MockMvcResultMatchers.jsonPath("$.morningBug").value(member.getBug().getMorningBug()), + MockMvcResultMatchers.jsonPath("$.nightBug").value(member.getBug().getNightBug()) + ).andDo(print()); + } + + @DisplayName("친구 정보 조회 성공") + @WithMember + @Test + void search_friend_info_success() throws Exception { + // given + Member friend = MemberFixture.member("123456789", "nick"); + memberRepository.save(friend); + + Badge morningBirth = BadgeFixture.badge(friend.getId(), BadgeType.MORNING_BIRTH); + Badge morningAdult = BadgeFixture.badge(friend.getId(), BadgeType.MORNING_ADULT); + Badge nightBirth = BadgeFixture.badge(friend.getId(), BadgeType.NIGHT_BIRTH); + Badge nightAdult = BadgeFixture.badge(friend.getId(), BadgeType.NIGHT_ADULT); + List badges = List.of(morningBirth, morningAdult, nightBirth, nightAdult); + badgeRepository.saveAll(badges); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(friend.getId(), night); + nightInven.select(); + + Inventory morningInven = InventoryFixture.inventory(friend.getId(), morning); + morningInven.select(); + + Inventory killerInven = InventoryFixture.inventory(friend.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // expected + mockMvc.perform(get("/members/{memberId}", friend.getId())) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(friend.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(friend.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(friend.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(friend.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(friend.getTotalCertifyCount() % LEVEL_DIVISOR), + + MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getImage()), + MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("MORNING_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("MORNING_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("NIGHT_BIRTH"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[3].badge").value("NIGHT_ADULT"), + MockMvcResultMatchers.jsonPath("$.badges[3].unlock").value(true) + ).andDo(print()); + } + + @DisplayName("회원 정보 찾기 실패로 예외 발생") + @WithMember(id = 123L) + @Test + void search_member_failBy_not_found_member() throws Exception { + // expected + mockMvc.perform(get("/members/{memberId}", 123L)) + .andExpect(status().is4xxClientError()); + } + + @DisplayName("기본 스킨의 갯수가 다를때 예외 발생") + @Test + void search_member_failBy_default_skin_size() throws Exception { + // given + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + killerInven.select(); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // expected + mockMvc.perform(get("/members/{memberId}", 123L)) + .andExpect(status().is4xxClientError()); + + } } diff --git a/src/test/java/com/moabam/support/fixture/BadgeFixture.java b/src/test/java/com/moabam/support/fixture/BadgeFixture.java new file mode 100644 index 00000000..de7d40b1 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/BadgeFixture.java @@ -0,0 +1,14 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; + +public class BadgeFixture { + + public static Badge badge(Long memberId, BadgeType badgeType) { + return Badge.builder() + .memberId(memberId) + .type(badgeType) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java b/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java new file mode 100644 index 00000000..9c0c4d26 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java @@ -0,0 +1,36 @@ +package com.moabam.support.fixture; + +import java.util.List; + +import com.moabam.api.domain.member.BadgeType; +import com.moabam.api.dto.member.MemberInfo; + +public class MemberInfoSearchFixture { + + private static final String NICKNAME = "nickname"; + private static final String PROFILE_IMAGE = "profileuri"; + private static final String INTRO = "intro"; + private static final long TOTAL_CERTIFY_COUNT = 15; + + public static List friendMemberInfo() { + return friendMemberInfo(TOTAL_CERTIFY_COUNT); + } + + public static List friendMemberInfo(long total) { + return List.of( + new MemberInfo(NICKNAME, PROFILE_IMAGE, INTRO, total, BadgeType.MORNING_BIRTH, + 0, 0, 0), + new MemberInfo(NICKNAME, PROFILE_IMAGE, INTRO, total, BadgeType.NIGHT_BIRTH, + 0, 0, 0) + ); + } + + public static List myInfo() { + return List.of( + new MemberInfo(NICKNAME, PROFILE_IMAGE, INTRO, TOTAL_CERTIFY_COUNT, BadgeType.MORNING_BIRTH, + 0, 0, 0), + new MemberInfo(NICKNAME, PROFILE_IMAGE, INTRO, TOTAL_CERTIFY_COUNT, BadgeType.NIGHT_BIRTH, + 0, 0, 0) + ); + } +}